From 08841528e27306818a51689186d03ae116cc14d6 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 18 Sep 2023 23:35:44 +0000 Subject: [PATCH 01/14] Initial versions of new bansi codes --- core/ansi_escape_parser.js | 86 ++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 18 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 796a1cf7..45fb38ce 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -77,6 +77,7 @@ function ANSIEscapeParser(options) { self.clearScreen = function () { self.column = 1; self.row = 1; + self.positionUpdated(); self.emit('clear screen'); }; @@ -281,8 +282,8 @@ function ANSIEscapeParser(options) { if (pos < buffer.length) { var lastBit = buffer.slice(pos); - // :TODO: check for various ending LF's, not just DOS \r\n - if ('\r\n' === lastBit.slice(-2).toString()) { + // handles either \r\n or \n + if ('\n' === lastBit.slice(-1).toString()) { switch (self.trailingLF) { case 'default': // @@ -290,14 +291,14 @@ function ANSIEscapeParser(options) { // if we're going to end on termHeight // if (this.termHeight === self.row) { - lastBit = lastBit.slice(0, -2); + lastBit = lastBit.slice(0, -1); } break; case 'omit': case 'no': case false: - lastBit = lastBit.slice(0, -2); + lastBit = lastBit.slice(0, -1); break; } } @@ -382,6 +383,18 @@ function ANSIEscapeParser(options) { self.moveCursor(-arg, 0); break; + // line feed + case 'E': + arg = isNaN(args[0]) ? 1 : args[0]; + if(this.row + arg > this.termHeight) { + this.emit('scroll', arg - (this.termHeight - this.row)); + self.moveCursor(0, this.termHeight); + } + else { + self.moveCursor(0, arg); + } + break; + case 'f': // horiz & vertical case 'H': // cursor position //self.row = args[0] || 1; @@ -392,14 +405,19 @@ function ANSIEscapeParser(options) { self.positionUpdated(); break; - // save position - case 's': - self.saveCursorPosition(); + + // erase display/screen + case 'J': + // :TODO: Handle other 'J' types! + if (2 === args[0]) { + self.clearScreen(); + } break; - // restore position - case 'u': - self.restoreCursorPosition(); + // insert line + case 'L': + arg = isNaN(args[0]) ? 1 : args[0]; + self.emit('insert line', self.row, arg); break; // set graphic rendition @@ -471,15 +489,47 @@ function ANSIEscapeParser(options) { self.emit('sgr update', self.graphicRendition); break; // m - // :TODO: s, u, K - - // erase display/screen - case 'J': - // :TODO: Handle other 'J' types! - if (2 === args[0]) { - self.clearScreen(); - } + // save position + case 's': + self.saveCursorPosition(); break; + + // Scroll up + case 'S': + arg = isNaN(args[0]) ? 1 : args[0]; + self.emit('scroll', arg); + break; + + // Scroll down + case 'T': + arg = isNaN(args[0]) ? 1 : args[0]; + self.emit('scroll', -arg); + break; + + // restore position + case 'u': + self.restoreCursorPosition(); + break; + + // clear + case 'U': + self.clearScreen(); + break; + + // delete line + // TODO: how should we handle 'M'? + case 'Y': + arg = isNaN(args[0]) ? 1 : args[0]; + self.emit('delete line', self.row, arg); + break; + + // back tab + case 'Z': + // calculate previous tabstop + self.column = Math.max( 1, self.column - (self.column % 8 || 8) ); + self.positionUpdated(); + break; + } } } From d8f45f914718e66d241f848a37be36506667a539 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Mon, 18 Sep 2023 23:36:27 +0000 Subject: [PATCH 02/14] Removed unused code --- core/ansi_escape_parser.js | 42 -------------------------------------- 1 file changed, 42 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 45fb38ce..3b376c89 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -309,48 +309,6 @@ function ANSIEscapeParser(options) { self.emit('complete'); }; - /* - self.parse = function(buffer, savedRe) { - // :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc. - // :TODO: move this to "constants" section @ top - var re = /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g; - var pos = 0; - var match; - var opCode; - var args; - - // ignore anything past EOF marker, if any - buffer = buffer.split(String.fromCharCode(0x1a), 1)[0]; - - do { - pos = re.lastIndex; - match = re.exec(buffer); - - if(null !== match) { - if(match.index > pos) { - parseMCI(buffer.slice(pos, match.index)); - } - - opCode = match[2]; - args = getArgArray(match[1].split(';')); - - escape(opCode, args); - - self.emit('chunk', match[0]); - } - - - - } while(0 !== re.lastIndex); - - if(pos < buffer.length) { - parseMCI(buffer.slice(pos)); - } - - self.emit('complete'); - }; - */ - function escape(opCode, args) { let arg; From a99b55abb524e82b91a1b51c591213aa6a55d05b Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Tue, 19 Sep 2023 01:04:36 +0000 Subject: [PATCH 03/14] Added support for some non-bracket escape sequences --- core/ansi_escape_parser.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 3b376c89..4d30c29c 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -24,7 +24,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -232,7 +232,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsutEFGST])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -269,8 +269,25 @@ function ANSIEscapeParser(options) { parseMCI(buffer.slice(pos, match.index)); } - opCode = match[2]; - args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints + opCode = match[3]; + args = match[2].split(';').map(v => parseInt(v, 10)); // convert to array of ints + + if(_.isNil(match[1])) { + // no bracket + switch(opCode) { + // scroll up + case 'D': + opCode = 'S'; + args = [1]; + break; + + // scroll down + case 'M': + opCode = 'T'; + args = [1]; + break; + } + } escape(opCode, args); From 5712645299dd1caac610262697b7f46b00852e93 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Tue, 19 Sep 2023 01:39:04 +0000 Subject: [PATCH 04/14] Added additional characters that change position --- core/ansi_escape_parser.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 4d30c29c..d1bc6606 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -12,8 +12,11 @@ const _ = require('lodash'); exports.ANSIEscapeParser = ANSIEscapeParser; +const TAB = 0x09; const CR = 0x0d; const LF = 0x0a; +const FF = 0x0c; +const BKSP = 0x08; function ANSIEscapeParser(options) { var self = this; @@ -96,6 +99,28 @@ function ANSIEscapeParser(options) { charCode = text.charCodeAt(pos) & 0xff; // 8bit clean switch (charCode) { + case TAB: + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; + + self.column += 8 - ((self.column - 1) % 8); + + self.positionUpdated(); + break; + case BKSP: + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; + + self.column = Math.max(1, self.column - 1); + + self.positionUpdated(); + break; + case FF: + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; + + self.clearScreen(); + break; case CR: self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; From 855fabe34df502555e70c172110fd004a604f125 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 13:44:54 +0000 Subject: [PATCH 05/14] Added additional vt100 codes --- core/ansi_escape_parser.js | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index d1bc6606..542e11d5 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -27,7 +27,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([78ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -257,7 +257,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([78ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -300,23 +300,41 @@ function ANSIEscapeParser(options) { if(_.isNil(match[1])) { // no bracket switch(opCode) { + // save cursor position + case '7': + escape('s', args); + break; + // restore cursor position + case '8': + escape('u', args); + break; + // scroll up case 'D': - opCode = 'S'; - args = [1]; + escape('S', [1]); + break; + + // move to next line + case 'E': + // functonality is the same as ESC [ E + escape(opCode, [1]); + break; + + // create a tab at current cursor position + case 'H': + literal('\t'); break; // scroll down case 'M': - opCode = 'T'; - args = [1]; + escape('T', [1]); break; } } + else { + escape(opCode, args); + } - escape(opCode, args); - - //self.emit('chunk', match[0]); self.emit('control', match[0], opCode, args); } } while (0 !== re.lastIndex); From 80295213999c75aeffe92dfa3bc3933b9eb0fd50 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 21:19:03 +0000 Subject: [PATCH 06/14] changed handling of regex --- core/ansi_escape_parser.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 542e11d5..7f60c438 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -27,7 +27,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([78ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -257,7 +257,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(\x5b?)([?=;0-9]*?)([78ABCDEfHJLmMsSTuUYZ])/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -294,10 +294,13 @@ function ANSIEscapeParser(options) { parseMCI(buffer.slice(pos, match.index)); } - opCode = match[3]; - args = match[2].split(';').map(v => parseInt(v, 10)); // convert to array of ints + opCode = match[2]; + args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints - if(_.isNil(match[1])) { + // Handle the case where there is no bracket + if(!(_.isNil(match[3]))) { + opCode = match[3]; + args = []; // no bracket switch(opCode) { // save cursor position @@ -311,13 +314,13 @@ function ANSIEscapeParser(options) { // scroll up case 'D': - escape('S', [1]); + escape('S', args); break; // move to next line case 'E': // functonality is the same as ESC [ E - escape(opCode, [1]); + escape(opCode, args); break; // create a tab at current cursor position @@ -327,7 +330,7 @@ function ANSIEscapeParser(options) { // scroll down case 'M': - escape('T', [1]); + escape('T', args); break; } } From 372d84f5729eb7679c69800245c1fe0c03643305 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 21:23:00 +0000 Subject: [PATCH 07/14] Added backspace --- core/ansi_escape_parser.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 7f60c438..8cc4dbb6 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -12,6 +12,7 @@ const _ = require('lodash'); exports.ANSIEscapeParser = ANSIEscapeParser; +const BS = 0x08; const TAB = 0x09; const CR = 0x0d; const LF = 0x0a; @@ -99,6 +100,14 @@ function ANSIEscapeParser(options) { charCode = text.charCodeAt(pos) & 0xff; // 8bit clean switch (charCode) { + case BS: + self.emit('literal', text.slice(start, pos + 1)); + start = pos + 1; + + self.column = Math.max(1, self.column - 1); + + self.positionUpdated(); + break; case TAB: self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; From f3c9afc684acdc564f76fe1b81dcc3d9b96d291e Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 21:40:59 +0000 Subject: [PATCH 08/14] Added F and G --- core/ansi_escape_parser.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 8cc4dbb6..55dc9a82 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -28,7 +28,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -266,7 +266,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -425,6 +425,25 @@ function ANSIEscapeParser(options) { } break; + // reverse line feed + case 'F': + arg = isNaN(args[0]) ? 1 : args[0]; + if(this.row - arg < 1) { + this.emit('scroll', -(arg - this.row)); + self.moveCursor(0, 1 - this.row); + } + else { + self.moveCursor(0, -arg); + } + break; + + // absolute horizontal cursor position + case 'G': + arg = isNaN(args[0]) ? 1 : args[0]; + self.column = Math.max(1, arg); + self.positionUpdated(); + break; + case 'f': // horiz & vertical case 'H': // cursor position //self.row = args[0] || 1; From 826db2d718e1ed515eaa40c2d731f827fa8b67db Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 23:00:32 +0000 Subject: [PATCH 09/14] Added scrolling. --- core/ansi_escape_parser.js | 14 ++++++++++++++ core/art.js | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 55dc9a82..974399c8 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -86,6 +86,20 @@ function ANSIEscapeParser(options) { }; self.positionUpdated = function () { + if(self.row > self.termHeight) { + if(this.savedPosition) { + this.savedPosition.row -= self.row - self.termHeight; + } + self.emit('scroll', self.row - self.termHeight); + self.row = self.termHeight; + } + else if(self.row < 1) { + if(this.savedPosition) { + this.savedPosition.row -= self.row - 1; + } + self.emit('scroll', -(self.row - 1)); + self.row = 1; + } self.emit('position update', self.row, self.column); }; diff --git a/core/art.js b/core/art.js index 2145f5ad..f6c0c4d9 100644 --- a/core/art.js +++ b/core/art.js @@ -316,6 +316,12 @@ function display(client, art, options, cb) { } }); + ansiParser.on('scroll', (scrollY) => { + _.forEach(mciMap, (mciInfo) => { + mciInfo.position[0] -= scrollY; + }); + }); + ansiParser.on('literal', literal => client.term.write(literal, false)); ansiParser.on('control', control => client.term.rawWrite(control)); From c0c262c971e2de7eb88382a9104b94c0d9460706 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 23:30:01 +0000 Subject: [PATCH 10/14] Added insert and delete rows --- core/art.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/art.js b/core/art.js index f6c0c4d9..114800ee 100644 --- a/core/art.js +++ b/core/art.js @@ -322,6 +322,29 @@ function display(client, art, options, cb) { }); }); + ansiParser.on('insert line', (row, numLines) => { + _.forEach(mciMap, (mciInfo) => { + if (mciInfo.position[0] >= row) { + mciInfo.position[0] += numLines; + } + }); + }); + + ansiParser.on('delete line', (row, numLines) => { + _.forEach(mciMap, (mciInfo, mapKey) => { + if (mciInfo.position[0] >= row) { + if(mciInfo.position[0] < row + numLines) { + // unlike scrolling, the rows are actually gone, + // so we need to delete any MCI's that are in them + delete mciMap[mapKey]; + } + else { + mciInfo.position[0] -= numLines; + } + } + }); + }); + ansiParser.on('literal', literal => client.term.write(literal, false)); ansiParser.on('control', control => client.term.rawWrite(control)); From 79a3f4e1ce087623c4af8ae10c0b55f45a256206 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Wed, 20 Sep 2023 23:52:34 +0000 Subject: [PATCH 11/14] Handle deleting rows / columns --- core/ansi_escape_parser.js | 26 ++++++++++++++++++++++---- core/art.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 974399c8..44eccfbd 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -28,7 +28,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -280,7 +280,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -471,12 +471,30 @@ function ANSIEscapeParser(options) { // erase display/screen case 'J': - // :TODO: Handle other 'J' types! - if (2 === args[0]) { + if(isNaN(args[0]) || 0 === args[0]) { + self.emit('erase rows', self.row, self.termHeight); + } + else if (1 === args[0]) { + self.emit('erase rows', 1, self.row); + } + else if (2 === args[0]) { self.clearScreen(); } break; + // erase text in line + case 'K': + if(isNaN(args[0]) || 0 === args[0]) { + self.emit('erase columns', self.row, self.column, self.termWidth); + } + else if (1 === args[0]) { + self.emit('erase columns', self.row, 1, self.column); + } + else if (2 === args[0]) { + self.emit('erase columns', self.row, 1, self.termWidth); + } + break; + // insert line case 'L': arg = isNaN(args[0]) ? 1 : args[0]; diff --git a/core/art.js b/core/art.js index 114800ee..61471a47 100644 --- a/core/art.js +++ b/core/art.js @@ -316,6 +316,35 @@ function display(client, art, options, cb) { } }); + // Remove any MCI's that are in erased rows + ansiParser.on('erase row', (startRow, endRow) => { + _.forEach(mciMap, (mciInfo, mapKey) => { + if (mciInfo.position[0] >= startRow && mciInfo.position[0] <= endRow) { + delete mciMap[mapKey]; + } + }); + }); + + // Remove any MCI's that are in erased columns + ansiParser.on('erase columns', (row, startCol, endCol) => { + _.forEach(mciMap, (mciInfo, mapKey) => { + if ( + mciInfo.position[0] === row && + mciInfo.position[1] >= startCol && + mciInfo.position[1] <= endCol + ) { + delete mciMap[mapKey]; + } + }); + }); + + // Clear the screen, removing any MCI's + ansiParser.on('clear screen', () => { + _.forEach(mciMap, (mciInfo, mapKey) => { + delete mciMap[mapKey]; + }); + }); + ansiParser.on('scroll', (scrollY) => { _.forEach(mciMap, (mciInfo) => { mciInfo.position[0] -= scrollY; From 72a8546d7448db66375197c177a4610a5534b42c Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Fri, 22 Sep 2023 01:18:38 +0000 Subject: [PATCH 12/14] Removed special handling of backspace and form feed --- core/ansi_escape_parser.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 44eccfbd..7b6914ea 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -12,12 +12,9 @@ const _ = require('lodash'); exports.ANSIEscapeParser = ANSIEscapeParser; -const BS = 0x08; const TAB = 0x09; const CR = 0x0d; const LF = 0x0a; -const FF = 0x0c; -const BKSP = 0x08; function ANSIEscapeParser(options) { var self = this; @@ -114,14 +111,6 @@ function ANSIEscapeParser(options) { charCode = text.charCodeAt(pos) & 0xff; // 8bit clean switch (charCode) { - case BS: - self.emit('literal', text.slice(start, pos + 1)); - start = pos + 1; - - self.column = Math.max(1, self.column - 1); - - self.positionUpdated(); - break; case TAB: self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; @@ -130,20 +119,6 @@ function ANSIEscapeParser(options) { self.positionUpdated(); break; - case BKSP: - self.emit('literal', text.slice(start, pos + 1)); - start = pos + 1; - - self.column = Math.max(1, self.column - 1); - - self.positionUpdated(); - break; - case FF: - self.emit('literal', text.slice(start, pos + 1)); - start = pos + 1; - - self.clearScreen(); - break; case CR: self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; From 450ba655651db21a8ab5b15a989dbe0c3b3edad6 Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Sun, 24 Sep 2023 20:39:57 +0000 Subject: [PATCH 13/14] Fixed missing ansi codes causing formatting misses --- core/ansi_escape_parser.js | 18 +++++++----------- core/art.js | 11 +++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 7b6914ea..6fdbc667 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -12,7 +12,6 @@ const _ = require('lodash'); exports.ANSIEscapeParser = ANSIEscapeParser; -const TAB = 0x09; const CR = 0x0d; const LF = 0x0a; @@ -25,7 +24,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PX]))|([78DEHM]))/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -111,14 +110,6 @@ function ANSIEscapeParser(options) { charCode = text.charCodeAt(pos) & 0xff; // 8bit clean switch (charCode) { - case TAB: - self.emit('literal', text.slice(start, pos + 1)); - start = pos + 1; - - self.column += 8 - ((self.column - 1) % 8); - - self.positionUpdated(); - break; case CR: self.emit('literal', text.slice(start, pos + 1)); start = pos + 1; @@ -255,7 +246,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZ]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PX]))|([78DEHM]))/g, // eslint-disable-line no-control-regex stop: false, }; }; @@ -585,6 +576,11 @@ function ANSIEscapeParser(options) { self.column = Math.max( 1, self.column - (self.column % 8 || 8) ); self.positionUpdated(); break; + case '@': + // insert column(s) + arg = isNaN(args[0]) ? 1 : args[0]; + self.emit('insert columns', self.row, self.column, arg); + break; } } diff --git a/core/art.js b/core/art.js index 61471a47..baf02c70 100644 --- a/core/art.js +++ b/core/art.js @@ -338,6 +338,17 @@ function display(client, art, options, cb) { }); }); + ansiParser.on('insert columns', (row, startCol, numCols) => { + _.forEach(mciMap, (mciInfo, mapKey) => { + if (mciInfo.position[0] === row && mciInfo.position[1] >= startCol) { + mciInfo.position[1] += numCols; + if(mciInfo.position[1] > client.term.termWidth) { + delete mciMap[mapKey]; + } + } + }); + }); + // Clear the screen, removing any MCI's ansiParser.on('clear screen', () => { _.forEach(mciMap, (mciInfo, mapKey) => { From 0ca22de6e9beac031437e96842569c89b122ef9f Mon Sep 17 00:00:00 2001 From: Nathan Byrd Date: Tue, 26 Sep 2023 22:02:49 +0000 Subject: [PATCH 14/14] Added back in unused escape codes for backwards compat --- core/ansi_escape_parser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/ansi_escape_parser.js b/core/ansi_escape_parser.js index 6fdbc667..d0440582 100644 --- a/core/ansi_escape_parser.js +++ b/core/ansi_escape_parser.js @@ -24,7 +24,7 @@ function ANSIEscapeParser(options) { this.graphicRendition = {}; this.parseState = { - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PX]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex }; options = miscUtil.valueWithDefault(options, { @@ -246,7 +246,7 @@ function ANSIEscapeParser(options) { self.parseState = { // ignore anything past EOF marker, if any buffer: input.split(String.fromCharCode(0x1a), 1)[0], - re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PX]))|([78DEHM]))/g, // eslint-disable-line no-control-regex + re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex stop: false, }; };