Merge pull request #407 from cognitivegears/feature/Remove_position_check

#222 - Removing Cursor Position Report
This commit is contained in:
Bryan Ashby 2022-04-05 19:34:05 -06:00 committed by GitHub
commit 3d1b97cf3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 214 additions and 127 deletions

View File

@ -28,5 +28,8 @@
],
"comma-dangle": 0,
"no-trailing-spaces" :"warn"
},
"parserOptions": {
"ecmaVersion": 2020
}
}

View File

@ -28,6 +28,33 @@ npm install # or simply 'yarn'
Report your issue on Xibalba BBS, hop in #enigma-bbs on FreeNode and chat, or
[file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues).
# 0.0.12-beta to 0.0.13-beta
* All features and changes are backwards compatible. There are a few new configuration options in a new `term` section in the configuration. These are all optional, but include the following options in case you use them:
```hjson
{
term: {
// checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals.
// Using this with a terminal that does not support cursor position reports results in a 2 second delay
// during the connect process, but provides better autoconfiguration of utf-8
checkUtf8Encoding: true
// Checking the ANSI home position also requires the use of cursor position reports, which are not
// supported on all terminals. Using this with a terminal that does not support cursor position reports
// results in a 3 second delay during the connect process, but works around positioning problems with
// non-standard terminals.
checkAnsiHomePosition: true
}
}
```
In addition to these, there are also new options for `term.cp437TermList` and `term.utf8TermList`. Under most circumstances these should not need to be changed. If you want to customize these lists, more information is available in `config_default.js`
# 0.0.11-beta to 0.0.12-beta
* Be aware that `master` is now mainline! This means all `git pull`'s will yield the latest version. See [WHATSNEW](WHATSNEW.md) for more information.
* **BREAKING CHANGE** There is no longer a `prompt.hjson` file. Prompts are now simply part of the menu set in the `prompts` section. If you have an existing system you will need to add your `prompt.hjson` to your `menu.hjson`'s `includes` section at a minimum. Example:

View File

@ -1,6 +1,12 @@
# Whats New
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
## 0.0.13-beta
* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with Enigma 1/2. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know!
* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to V14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience.
* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePostion`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in `UPGRADE.md`
* Many additional backward-compatible bug fixes since the first release of 0.0.12-beta. See the [project repository](https://github.com/NuSkooler/enigma-bbs) for more information.
## 0.0.12-beta
* The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](./docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276).
* Development now occurs against [Node.js 14 LTS](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md).
@ -127,4 +133,4 @@ submit: [
...LOTS more!
## Pre 0.0.8-alpha
See GitHub
See GitHub

View File

@ -21,8 +21,6 @@ function ANSIEscapeParser(options) {
events.EventEmitter.call(this);
this.column = 1;
this.row = 1;
this.scrollBack = 0;
this.graphicRendition = {};
this.parseState = {
@ -36,11 +34,15 @@ function ANSIEscapeParser(options) {
trailingLF : 'default', // default|omit|no|yes, ...
});
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
this.row = Math.min(options?.startRow ?? 1, this.termHeight);
self.moveCursor = function(cols, rows) {
self.column += cols;
self.row += rows;
@ -69,14 +71,11 @@ function ANSIEscapeParser(options) {
};
self.clearScreen = function() {
// :TODO: should be doing something with row/column?
self.column = 1;
self.row = 1;
self.emit('clear screen');
};
/*
self.rowUpdated = function() {
self.emit('row update', self.row + self.scrollBack);
};*/
self.positionUpdated = function() {
self.emit('position update', self.row, self.column);
@ -190,10 +189,11 @@ function ANSIEscapeParser(options) {
self.emit('mci', {
mci : mciCode,
id : id ? parseInt(id, 10) : null,
args : args,
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
position : [self.row, self.column],
mci : mciCode,
id : id ? parseInt(id, 10) : null,
args : args,
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
});
if(self.mciReplaceChar.length > 0) {
@ -215,6 +215,9 @@ function ANSIEscapeParser(options) {
}
self.reset = function(input) {
self.column = 1;
self.row = Math.min(options?.startRow ?? 1, self.termHeight);
self.parseState = {
// ignore anything past EOF marker, if any
buffer : input.split(String.fromCharCode(0x1a), 1)[0],

View File

@ -269,19 +269,16 @@ function display(client, art, options, cb) {
termHeight : client.term.termHeight,
termWidth : client.term.termWidth,
trailingLF : options.trailingLF,
startRow : options.startRow,
});
let parseComplete = false;
let cprListener;
let mciMap;
const mciCprQueue = [];
let artHash;
let mciMapFromCache;
function completed() {
if(cprListener) {
client.removeListener('cursor position report', cprListener);
}
if(!options.disableMciCache && !mciMapFromCache) {
// cache our MCI findings...
@ -314,18 +311,6 @@ function display(client, art, options, cb) {
// no cached MCI info
mciMap = {};
cprListener = function(pos) {
if(mciCprQueue.length > 0) {
mciMap[mciCprQueue.shift()].position = pos;
if(parseComplete && 0 === mciCprQueue.length) {
return completed();
}
}
};
client.on('cursor position report', cprListener);
let generatedId = 100;
ansiParser.on('mci', mciInfo => {
@ -339,18 +324,16 @@ function display(client, art, options, cb) {
mapEntry.focusArgs = mciInfo.args;
} else {
mciMap[mapKey] = {
args : mciInfo.args,
SGR : mciInfo.SGR,
code : mciInfo.mci,
id : id,
position : mciInfo.position,
args : mciInfo.args,
SGR : mciInfo.SGR,
code : mciInfo.mci,
id : id,
};
if(!mciInfo.id) {
++generatedId;
}
mciCprQueue.push(mapKey);
client.term.rawWrite(ansi.queryPos());
}
});

View File

@ -4,11 +4,12 @@
// ENiGMA½
var Log = require('./logger.js').log;
var renegadeToAnsi = require('./color_codes.js').renegadeToAnsi;
const Config = require('./config.js').get;
var iconv = require('iconv-lite');
var assert = require('assert');
var _ = require('lodash');
exports.ClientTerminal = ClientTerminal;
function ClientTerminal(output) {
@ -115,7 +116,8 @@ ClientTerminal.prototype.isNixTerm = function() {
return true;
}
return [ 'xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator' ].includes(this.termType);
const utf8TermList = Config().term.utf8TermList;
return utf8TermList.includes(this.termType);
};
ClientTerminal.prototype.isANSI = function() {
@ -153,7 +155,8 @@ ClientTerminal.prototype.isANSI = function() {
// linux:
// * JuiceSSH (note: TERM=linux also)
//
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb' ].includes(this.termType);
const cp437TermList = Config().term.cp437TermList;
return cp437TermList.includes(this.termType);
};
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)

View File

@ -17,6 +17,24 @@ module.exports = () => {
achievementFile : 'achievements.hjson',
},
term : {
// checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals.
// Using this with a terminal that does not support cursor position reports results in a 2 second delay
// during the connect process, but provides better autoconfiguration of utf-8
checkUtf8Encoding : true,
// Checking the ANSI home position also requires the use of cursor position reports, which are not
// supported on all terminals. Using this with a terminal that does not support cursor position reports
// results in a 3 second delay during the connect process, but works around positioning problems with
// non-standard terminals.
checkAnsiHomePosition: true,
// List of terms that should be assumed to use cp437 encoding
cp437TermList : ['ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb'],
// List of terms that should be assumed to use utf8 encoding
utf8TermList : ['xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator'],
},
users : {
usernameMin : 2,
usernameMax : 16, // Note that FidoNet wants 36 max

View File

@ -4,6 +4,7 @@
// ENiGMA½
const ansi = require('./ansi_term.js');
const Events = require('./events.js');
const Config = require('./config.js').get;
const { Errors } = require('./enig_error.js');
// deps
@ -18,6 +19,13 @@ function ansiDiscoverHomePosition(client, cb) {
// think of home as 0,0. If this is the case, we need to offset
// our positioning to accommodate for such.
//
if( !Config().term.checkAnsiHomePosition ) {
// Skip (and assume 1,1) if the home position check is disabled.
return cb(null);
}
const done = (err) => {
client.removeListener('cursor position report', cprListener);
clearTimeout(giveUpTimer);
@ -68,8 +76,9 @@ function ansiAttemptDetectUTF8(client, cb) {
//
// We currently only do this if the term hasn't already been ID'd as a
// "*nix" terminal -- that is, xterm, etc.
//
if(!client.term.isNixTerm()) {
// Also skip this check if checkUtf8Encoding is disabled in the config
if(!client.term.isNixTerm() || !Config().term.checkUtf8Encoding) {
return cb(null);
}
@ -119,6 +128,8 @@ function ansiAttemptDetectUTF8(client, cb) {
return giveUp();
}, 2000);
client.once('cursor position report', cprListener);
client.term.rawWrite(ansi.goHome() + ansi.queryPos());
}
@ -216,7 +227,6 @@ function connectEntry(client, nextMenu) {
},
function discoverHomePosition(callback) {
ansiDiscoverHomePosition(client, () => {
// :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required
return callback(null); // we try to continue anyway
});
},

View File

@ -603,7 +603,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
theme.displayThemedAsset(
footerArt,
self.client,
{ font : self.menuConfig.font },
{ font : self.menuConfig.font, startRow: self.header.height + self.body.height },
function displayed(err, artData) {
callback(err, artData);
}
@ -626,19 +626,34 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
async.series(
[
function displayHeaderAndBody(callback) {
async.eachSeries( comps, function dispArt(n, next) {
theme.displayThemedAsset(
art[n],
self.client,
{ font : self.menuConfig.font },
function displayed(err) {
next(err);
async.waterfall(
[
function displayHeader(callback) {
theme.displayThemedAsset(
art['header'],
self.client,
{ font : self.menuConfig.font },
function displayed(err, artInfo) {
return callback(err, artInfo);
}
);
},
function displayBody(artInfo, callback) {
theme.displayThemedAsset(
art['header'],
self.client,
{ font : self.menuConfig.font, startRow: artInfo.height + 1 },
function displayed(err, artInfo) {
return callback(err, artInfo);
}
);
}
);
}, function complete(err) {
//self.body.height = self.client.term.termHeight - self.header.height - 1;
callback(err);
});
],
function complete(err) {
//self.body.height = self.client.term.termHeight - self.header.height - 1;
callback(err);
}
);
},
function displayFooter(callback) {
// we have to treat the footer special
@ -700,31 +715,39 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
assert(_.isObject(art));
async.series(
async.waterfall(
[
function beforeDisplayArt(callback) {
self.beforeArt(callback);
},
function displayHeaderAndBodyArt(callback) {
async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) {
theme.displayThemedAsset(
art[n],
self.client,
{ font : self.menuConfig.font },
function displayed(err, artData) {
if(artData) {
mciData[n] = artData;
self[n] = { height : artData.height };
}
next(err);
function displayHeader(callback) {
theme.displayThemedAsset(
art.header,
self.client,
{ font : self.menuConfig.font },
function displayed(err, artInfo) {
if(artInfo) {
mciData['header'] = artInfo;
self.header = {height: artInfo.height};
}
);
}, function complete(err) {
callback(err);
});
return callback(err, artInfo);
}
);
},
function displayFooter(callback) {
function displayBody(artInfo, callback) {
theme.displayThemedAsset(
art.body,
self.client,
{ font : self.menuConfig.font, startRow: artInfo.height + 1 },
function displayed(err, artInfo) {
if(artInfo) {
mciData['body'] = artInfo;
self.body = {height: artInfo.height - self.header.height};
}
return callback(err, artInfo);
});
},
function displayFooter(artInfo, callback) {
self.setInitialFooterMode();
var footerName = self.getFooterName();
@ -825,8 +848,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
{
const fromView = self.viewControllers.header.getView(MciViewIds.header.from);
const area = getMessageAreaByTag(self.messageAreaTag);
if(fromView !== undefined) {
if(fromView !== undefined) {
if(area && area.realNames) {
fromView.setText(self.client.user.properties[UserProps.RealName] || self.client.user.username);
} else {

View File

@ -56,14 +56,14 @@ exports.MenuModule = class MenuModule extends PluginModule {
initSequence() {
const self = this;
const mciData = {};
let pausePosition;
let pausePosition = {row: 0, column: 0};
const hasArt = () => {
return _.isString(self.menuConfig.art) ||
(Array.isArray(self.menuConfig.art) && _.has(self.menuConfig.art[0], 'acs'));
};
async.series(
async.waterfall(
[
function beforeArtInterrupt(callback) {
return self.displayQueuedInterruptions(callback);
@ -73,7 +73,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
},
function displayMenuArt(callback) {
if(!hasArt()) {
return callback(null);
return callback(null, null);
}
self.displayAsset(
@ -86,18 +86,15 @@ exports.MenuModule = class MenuModule extends PluginModule {
mciData.menu = artData.mciMap;
}
return callback(null); // any errors are non-fatal
if(artData) {
pausePosition.row = artData.height + 1;
}
return callback(null, artData); // any errors are non-fatal
}
);
},
function moveToPromptLocation(callback) {
if(self.menuConfig.prompt) {
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
}
return callback(null);
},
function displayPromptArt(callback) {
function displayPromptArt(artData, callback) {
if(!_.isString(self.menuConfig.prompt)) {
return callback(null);
}
@ -106,41 +103,41 @@ exports.MenuModule = class MenuModule extends PluginModule {
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found'));
}
const options = Object.assign({}, self.menuConfig.config);
if(_.isNumber(artData?.height)) {
options.startRow = artData.height + 1;
}
self.displayAsset(
self.menuConfig.promptConfig.art,
self.menuConfig.config,
options,
(err, artData) => {
if(artData) {
mciData.prompt = artData.mciMap;
pausePosition.row = artData.height + 1;
}
return callback(err); // pass err here; prompts *must* have art
}
);
},
function recordCursorPosition(callback) {
if(!self.shouldPause()) {
return callback(null); // cursor position not needed
}
self.client.once('cursor position report', pos => {
pausePosition = { row : pos[0], col : 1 };
self.client.log.trace('After art position recorded', pausePosition );
return callback(null);
});
self.client.term.rawWrite(ansi.queryPos());
},
function afterArtDisplayed(callback) {
return self.mciReady(mciData, callback);
},
function displayPauseIfRequested(callback) {
if(!self.shouldPause()) {
return callback(null);
return callback(null, null);
}
if(self.client.term.termHeight > 0 && pausePosition.row > self.client.termHeight) {
// If this scrolled, the prompt will go to the bottom of the screen
pausePosition.row = self.client.termHeight;
}
return self.pausePrompt(pausePosition, callback);
},
function finishAndNext(callback) {
function finishAndNext(artInfo, callback) {
self.finishedLoading();
self.realTimeInterrupt = 'allowed';
return self.autoNextMenu(callback);
@ -512,7 +509,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.optionalMoveToPosition(position);
return theme.displayThemedPause(this.client, cb);
return theme.displayThemedPause(this.client, {position}, cb);
}
promptForInput( { formName, formId, promptName, prevFormName, position } = {}, options, cb) {

View File

@ -171,22 +171,15 @@ exports.getModule = class ShowArtModule extends MenuModule {
return callback(err);
}
const mciData = { menu : artData.mciMap };
return callback(null, mciData);
if(self.client.term.termHeight > 0 && artData.height > self.client.term.termHeight) {
// We must have scrolled, adjust the positioning for pause
artData.height = self.client.term.termHeight;
}
const pausePosition = { row: artData.height + 1, col: 1};
return callback(null, mciData, pausePosition);
}
);
},
function recordCursorPosition(mciData, callback) {
if(!options.pause) {
return callback(null, mciData, null); // cursor position not needed
}
self.client.once('cursor position report', pos => {
const pausePosition = { row : pos[0], col : 1 };
return callback(null, mciData, pausePosition);
});
self.client.term.rawWrite(ANSI.queryPos());
},
function afterArtDisplayed(mciData, pausePosition, callback) {
self.mciReady(mciData, err => {
return callback(err, pausePosition);

View File

@ -495,6 +495,7 @@ function displayPreparedArt(options, artInfo, cb) {
sauce : artInfo.sauce,
font : options.font,
trailingLF : options.trailingLF,
startRow : options.startRow,
};
art.display(options.client, artInfo.data, displayOpts, (err, mciMap, extraInfo) => {
return cb(err, { mciMap : mciMap, artInfo : artInfo, extraInfo : extraInfo } );
@ -551,6 +552,7 @@ function displayThemedPrompt(name, client, options, cb) {
if(options.clearScreen) {
client.term.rawWrite(ansi.resetScreen());
options.position = {row: 1, column: 1};
}
//
@ -583,12 +585,15 @@ function displayThemedPrompt(name, client, options, cb) {
return callback(null, promptConfig, artInfo);
}
client.once('cursor position report', pos => {
artInfo.startRow = pos[0] - artInfo.height;
return callback(null, promptConfig, artInfo);
});
if(_.isNumber(options?.position?.row)) {
artInfo.startRow = options.position.row;
if(client.term.termHeight > 0 && artInfo.startRow + artInfo.height > client.term.termHeight) {
// in this case, we will have scrolled
artInfo.startRow = client.term.termHeight - artInfo.height;
}
}
client.term.rawWrite(ansi.queryPos());
return callback(null, promptConfig, artInfo);
},
function createMCIViews(promptConfig, artInfo, callback) {
const assocViewController = usingTempViewController ? new ViewController( { client : client } ) : options.viewController;
@ -614,7 +619,9 @@ function displayThemedPrompt(name, client, options, cb) {
});
},
function clearPauseArt(artInfo, assocViewController, callback) {
if(options.clearPrompt) {
// Only clear with height if clearPrompt is true and if we were able
// to determine the row
if(options.clearPrompt && artInfo.startRow) {
if(artInfo.startRow && artInfo.height) {
client.term.rawWrite(ansi.goto(artInfo.startRow, 1));

View File

@ -6,7 +6,7 @@ For Linux environments it's recommended you run the [install script](install-scr
do things manually, read on...
## Prerequisites
* [Node.js](https://nodejs.org/) version **v12.x LTS or higher** (Other versions may work but are not supported).
* [Node.js](https://nodejs.org/) version **v14.x LTS or higher**. Versions under v14 are known not to work due to language level changes.
* :bulb: It is **highly** recommended to use [Node Version Manager (NVM)](https://github.com/creationix/nvm) to manage your Node.js installation if you're on a Linux/Unix environment.
* [Python](https://www.python.org/downloads/) for compiling Node.js packages with native extensions via `node-gyp`.

View File

@ -67,6 +67,21 @@
description : 'Yet another awesome ENiGMA½ BBS'
}
term: {
// checkUtf8Encoding requires the use of cursor position reports, which are not supported on all terminals.
// Using this with a terminal that does not support cursor position reports results in a 2 second delay
// during the connect process, but provides better autoconfiguration of utf-8
checkUtf8Encoding : true
// Checking the ANSI home position also requires the use of cursor position reports, which are not
// supported on all terminals. Using this with a terminal that does not support cursor position reports
// results in a 3 second delay during the connect process, but works around positioning problems with
// non-standard terminals.
checkAnsiHomePosition: true
// other options here include cp437TermList and utf8TermList, see config_default.js for more information
}
paths: {
//
// Other paths can also be configured as well,

View File

@ -1,6 +1,6 @@
{
"name": "enigma-bbs",
"version": "0.0.12-beta",
"version": "0.0.13-beta",
"description": "ENiGMA½ Bulletin Board System",
"author": "Bryan Ashby <bryan@l33t.codes>",
"license": "BSD-2-Clause",
@ -62,9 +62,9 @@
"yazl": "^2.5.1"
},
"devDependencies": {
"eslint": "7.19.0"
"eslint": "8.12.0"
},
"engines": {
"node": ">=12"
"node": ">=14"
}
}