diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-05-12 17:42:09 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-05-13 12:48:36 -0300 |
commit | 6ea74c268a12325296a1d2e7fc31b02030ddb8bc (patch) | |
tree | 5da94d93acb64e7ab650d240d6cb23c659ad02ca /util | |
parent | e783bcf8522fa68e6b221afd18469c3c265b1bb7 (diff) |
use ESM module syntax & minor cleanups
The biggest change here is moving various element classes under more scope-specific directories, which helps to avoid circular dependencies and is just cleaner to navigate and expand in the future. Otherwise this is a largely uncritical port to ESM module syntax! There are probably a number of changes and other cleanups that remain much needed. Whenever I make changes to tui-lib it's hard to believe it's already been <INSERT COUNTING NUMBER HERE> years since the previous time. First commits are from January 2017, and the code originates a month earlier in KAaRMNoD!
Diffstat (limited to 'util')
-rw-r--r-- | util/ansi.js | 778 | ||||
-rw-r--r-- | util/count.js | 2 | ||||
-rw-r--r-- | util/exception.js | 2 | ||||
-rw-r--r-- | util/index.js | 11 | ||||
-rw-r--r-- | util/interfaces/CommandLineInterface.js (renamed from util/CommandLineInterfacer.js) | 9 | ||||
-rw-r--r-- | util/interfaces/Flushable.js (renamed from util/Flushable.js) | 6 | ||||
-rw-r--r-- | util/interfaces/TelnetInterface.js (renamed from util/TelnetInterfacer.js) | 9 | ||||
-rw-r--r-- | util/interfaces/index.js | 4 | ||||
-rw-r--r-- | util/smoothen.js | 2 | ||||
-rw-r--r-- | util/telchars.js | 2 | ||||
-rw-r--r-- | util/tui-app.js | 19 | ||||
-rw-r--r-- | util/unichars.js | 2 | ||||
-rw-r--r-- | util/waitForData.js | 2 | ||||
-rw-r--r-- | util/wrap.js | 2 |
14 files changed, 432 insertions, 418 deletions
diff --git a/util/ansi.js b/util/ansi.js index ac511ed..2ae5166 100644 --- a/util/ansi.js +++ b/util/ansi.js @@ -1,498 +1,496 @@ -const wcwidth = require('wcwidth') +import wcwidth from 'wcwidth' -const ESC = '\x1b' +function isDigit(char) { + return '0123456789'.indexOf(char) >= 0 +} -const isDigit = char => '0123456789'.indexOf(char) >= 0 +export const ESC = '\x1b' + +// Attributes +export const A_RESET = 0 +export const A_BRIGHT = 1 +export const A_DIM = 2 +export const A_INVERT = 7 + +// Colors +export const C_BLACK = 30 +export const C_RED = 31 +export const C_GREEN = 32 +export const C_YELLOW = 33 +export const C_BLUE = 34 +export const C_MAGENTA = 35 +export const C_CYAN = 36 +export const C_WHITE = 37 +export const C_RESET = 39 + +export function clearScreen() { + // Clears the screen, removing any characters displayed, and resets the + // cursor position. + + return `${ESC}[2J` +} -const ansi = { - ESC, +export function moveCursorRaw(line, col) { + // Moves the cursor to the given line and column on the screen. + // Returns the pure ANSI code, with no modification to line or col. - // Attributes - A_RESET: 0, - A_BRIGHT: 1, - A_DIM: 2, - A_INVERT: 7, - C_BLACK: 30, - C_RED: 31, - C_GREEN: 32, - C_YELLOW: 33, - C_BLUE: 34, - C_MAGENTA: 35, - C_CYAN: 36, - C_WHITE: 37, - C_RESET: 39, + return `${ESC}[${line};${col}H` +} - clearScreen() { - // Clears the screen, removing any characters displayed, and resets the - // cursor position. +export function moveCursor(line, col) { + // Moves the cursor to the given line and column on the screen. + // Note that since in JavaScript indexes start at 0, but in ANSI codes + // the top left of the screen is (1, 1), this function adjusts the + // arguments to act as if the top left of the screen is (0, 0). - return `${ESC}[2J` - }, + return `${ESC}[${line + 1};${col + 1}H` +} - moveCursorRaw(line, col) { - // Moves the cursor to the given line and column on the screen. - // Returns the pure ANSI code, with no modification to line or col. +export function cleanCursor() { + // A combination of codes that generally cleans up the cursor. - return `${ESC}[${line};${col}H` - }, + return resetAttributes() + + stopTrackingMouse() + + showCursor() +} - moveCursor(line, col) { - // Moves the cursor to the given line and column on the screen. - // Note that since in JavaScript indexes start at 0, but in ANSI codes - // the top left of the screen is (1, 1), this function adjusts the - // arguments to act as if the top left of the screen is (0, 0). +export function hideCursor() { + // Makes the cursor invisible. - return `${ESC}[${line + 1};${col + 1}H` - }, + return `${ESC}[?25l` +} - cleanCursor() { - // A combination of codes that generally cleans up the cursor. +export function showCursor() { + // Makes the cursor visible. - return ansi.resetAttributes() + - ansi.stopTrackingMouse() + - ansi.showCursor() - }, + return `${ESC}[?25h` +} - hideCursor() { - // Makes the cursor invisible. +export function resetAttributes() { + // Resets all attributes, including text decorations, foreground and + // background color. - return `${ESC}[?25l` - }, + return `${ESC}[0m` +} - showCursor() { - // Makes the cursor visible. +export function setAttributes(attrs) { + // Set some raw attributes. See the attributes section of the ansi.js + // source code for attributes that can be used with this; A_RESET resets + // all attributes. - return `${ESC}[?25h` - }, + return `${ESC}[${attrs.join(';')}m` +} - resetAttributes() { - // Resets all attributes, including text decorations, foreground and - // background color. +export function setForeground(color) { + // Sets the foreground color to print text with. See C_(COLOR) for colors + // that can be used with this; C_RESET resets the foreground. + // + // If null or undefined is passed, this function will return a blank + // string (no ANSI escape codes). - return `${ESC}[0m` - }, + if (typeof color === 'undefined' || color === null) { + return '' + } - setAttributes(attrs) { - // Set some raw attributes. See the attributes section of the ansi.js - // source code for attributes that can be used with this; A_RESET resets - // all attributes. + return setAttributes([color]) +} - return `${ESC}[${attrs.join(';')}m` - }, +export function setBackground(color) { + // Sets the background color to print text with. Accepts the same arguments + // as setForeground (C_(COLOR), C_RESET, etc). + // + // Note that attributes such as A_BRIGHT and A_DIM apply apply to only the + // foreground, not the background. To set a bright or dim background, you + // can set the appropriate color as the foreground and then invert. - setForeground(color) { - // Sets the foreground color to print text with. See C_(COLOR) for colors - // that can be used with this; C_RESET resets the foreground. - // - // If null or undefined is passed, this function will return a blank - // string (no ANSI escape codes). + if (typeof color === 'undefined' || color === null) { + return '' + } - if (typeof color === 'undefined' || color === null) { - return '' - } + return setAttributes([color + 10]) +} - return ansi.setAttributes([color]) - }, +export function invert() { + // Inverts the foreground and background colors. - setBackground(color) { - // Sets the background color to print text with. Accepts the same arguments - // as setForeground (C_(COLOR), C_RESET, etc). - // - // Note that attributes such as A_BRIGHT and A_DIM apply apply to only the - // foreground, not the background. To set a bright or dim background, you - // can set the appropriate color as the foreground and then invert. + return `${ESC}[7m` +} - if (typeof color === 'undefined' || color === null) { - return '' - } +export function invertOff() { + // Un-inverts the foreground and backgrund colors. - return ansi.setAttributes([color + 10]) - }, + return `${ESC}[27m` +} - invert() { - // Inverts the foreground and background colors. +export function startTrackingMouse() { + return `${ESC}[?1002h` +} - return `${ESC}[7m` - }, +export function stopTrackingMouse() { + return `${ESC}[?1002l` +} - invertOff() { - // Un-inverts the foreground and backgrund colors. +export function requestCursorPosition() { + // Requests the position of the cursor. + // Expect a stdin-result '\ESC[l;cR', where l is the line number (1-based), + // c is the column number (also 1-based), and R is the literal character + // 'R' (decimal code 82). - return `${ESC}[27m` - }, + return `${ESC}[6n` +} - startTrackingMouse() { - return `${ESC}[?1002h` - }, +export function enableAlternateScreen() { + // Enables alternate screen: + // "Xterm maintains two screen buffers. The normal screen buffer allows + // you to scroll back to view saved lines of output up to the maximum set + // by the saveLines resource. The alternate screen buffer is exactly as + // large as the display, contains no additional saved lines." - stopTrackingMouse() { - return `${ESC}[?1002l` - }, + return `${ESC}[?1049h` +} - requestCursorPosition() { - // Requests the position of the cursor. - // Expect a stdin-result '\ESC[l;cR', where l is the line number (1-based), - // c is the column number (also 1-based), and R is the literal character - // 'R' (decimal code 82). +export function disableAlternateScreen() { + return `${ESC}[?1049l` +} - return `${ESC}[6n` - }, +export function measureColumns(text) { + // Returns the number of columns the given text takes. - enableAlternateScreen() { - // Enables alternate screen: - // "Xterm maintains two screen buffers. The normal screen buffer allows - // you to scroll back to view saved lines of output up to the maximum set - // by the saveLines resource. The alternate screen buffer is exactly as - // large as the display, contains no additional saved lines." + return wcwidth(text) +} - return `${ESC}[?1049h` - }, +export function trimToColumns(text, cols) { + // Trims off the end of the passed text so that its width doesn't exceed + // the size passed in columns. - disableAlternateScreen() { - return `${ESC}[?1049l` - }, + let out = '' + for (const char of text) { + if (measureColumns(out + char) <= cols) { + out += char + } else { + break + } + } + return out +} + +export function isANSICommand(buffer, code = null) { + return ( + buffer[0] === 0x1b && buffer[1] === 0x5b && + (code ? buffer[buffer.length - 1] === code : true) + ) +} - measureColumns(text) { - // Returns the number of columns the given text takes. +export function interpret(text, scrRows, scrCols, { + oldChars = null, oldLastChar = null, + oldScrRows = null, oldScrCols = null, + oldCursorRow = 1, oldCursorCol = 1, oldShowCursor = true +} = {}) { + // Interprets the given ansi code, more or less. - return wcwidth(text) - }, + const blank = { + attributes: [], + char: ' ' + } - trimToColumns(text, cols) { - // Trims off the end of the passed text so that its width doesn't exceed - // the size passed in columns. + const chars = new Array(scrRows * scrCols).fill(blank) - let out = '' - for (const char of text) { - if (ansi.measureColumns(out + char) <= cols) { - out += char - } else { - break + if (oldChars) { + for (let row = 0; row < scrRows && row < oldScrRows; row++) { + for (let col = 0; col < scrCols && col < oldScrCols; col++) { + chars[row * scrCols + col] = oldChars[row * oldScrCols + col] } } - return out - }, - - isANSICommand(buffer, code = null) { - return ( - buffer[0] === 0x1b && buffer[1] === 0x5b && - (code ? buffer[buffer.length - 1] === code : true) - ) - }, - - interpret(text, scrRows, scrCols, { - oldChars = null, oldLastChar = null, - oldScrRows = null, oldScrCols = null, - oldCursorRow = 1, oldCursorCol = 1, oldShowCursor = true - } = {}) { - // Interprets the given ansi code, more or less. - - const blank = { - attributes: [], - char: ' ' - } + } - const chars = new Array(scrRows * scrCols).fill(blank) + let showCursor = oldShowCursor + let cursorRow = oldCursorRow + let cursorCol = oldCursorCol + let attributes = [] - if (oldChars) { - for (let row = 0; row < scrRows && row < oldScrRows; row++) { - for (let col = 0; col < scrCols && col < oldScrCols; col++) { - chars[row * scrCols + col] = oldChars[row * oldScrCols + col] - } - } - } + for (let charI = 0; charI < text.length; charI++) { + const cursorIndex = (cursorRow - 1) * scrCols + (cursorCol - 1) + + if (text[charI] === ESC) { + charI++ - let showCursor = oldShowCursor - let cursorRow = oldCursorRow - let cursorCol = oldCursorCol - let attributes = [] + if (text[charI] !== '[') { + throw new Error('ESC not followed by [') + } - for (let charI = 0; charI < text.length; charI++) { - const cursorIndex = (cursorRow - 1) * scrCols + (cursorCol - 1) + charI++ - if (text[charI] === ESC) { + // Selective control sequences (look them up) - we can just skip the + // question mark. + if (text[charI] === '?') { charI++ + } - if (text[charI] !== '[') { - throw new Error('ESC not followed by [') - } - + const args = [] + let val = '' + while (isDigit(text[charI])) { + val += text[charI] charI++ - // Selective control sequences (look them up) - we can just skip the - // question mark. - if (text[charI] === '?') { + if (text[charI] === ';') { charI++ + args.push(val) + val = '' + continue } + } + args.push(val) - const args = [] - let val = '' - while (isDigit(text[charI])) { - val += text[charI] - charI++ + // CUP - Cursor Position (moveCursor) + if (text[charI] === 'H') { + cursorRow = parseInt(args[0]) + cursorCol = parseInt(args[1]) + } - if (text[charI] === ';') { - charI++ - args.push(val) - val = '' - continue - } + // SM - Set Mode + if (text[charI] === 'h') { + if (args[0] === '25') { + showCursor = true } - args.push(val) + } - // CUP - Cursor Position (moveCursor) - if (text[charI] === 'H') { - cursorRow = parseInt(args[0]) - cursorCol = parseInt(args[1]) + // ED - Erase Display (clearScreen) + if (text[charI] === 'J') { + // ESC[2J - erase whole display + if (args[0] === '2') { + chars.fill(blank) + charI += 3 + cursorCol = 1 + cursorRow = 1 } - // SM - Set Mode - if (text[charI] === 'h') { - if (args[0] === '25') { - showCursor = true + // ESC[1J - erase to beginning + else if (args[0] === '1') { + for (let i = 0; i < cursorIndex; i++) { + chars[i * 2] = ' ' + chars[i * 2 + 1] = [] } } - // ED - Erase Display (clearScreen) - if (text[charI] === 'J') { - // ESC[2J - erase whole display - if (args[0] === '2') { - chars.fill(blank) - charI += 3 - cursorCol = 1 - cursorRow = 1 - } - - // ESC[1J - erase to beginning - else if (args[0] === '1') { - for (let i = 0; i < cursorIndex; i++) { - chars[i * 2] = ' ' - chars[i * 2 + 1] = [] - } + // ESC[0J - erase to end + else if (args.length === 0 || args[0] === '0') { + for (let i = cursorIndex; i < chars.length; i++) { + chars[i * 2] = ' ' + chars[i * 2 + 1] = [] } + } + } - // ESC[0J - erase to end - else if (args.length === 0 || args[0] === '0') { - for (let i = cursorIndex; i < chars.length; i++) { - chars[i * 2] = ' ' - chars[i * 2 + 1] = [] - } - } + // RM - Reset Mode + if (text[charI] === 'l') { + if (args[0] === '25') { + showCursor = false } + } - // RM - Reset Mode - if (text[charI] === 'l') { - if (args[0] === '25') { - showCursor = false + // SGR - Select Graphic Rendition + if (text[charI] === 'm') { + const removeAttribute = attr => { + if (attributes.includes(attr)) { + attributes = attributes.slice() + attributes.splice(attributes.indexOf(attr), 1) } } - // SGR - Select Graphic Rendition - if (text[charI] === 'm') { - const removeAttribute = attr => { - if (attributes.includes(attr)) { - attributes = attributes.slice() - attributes.splice(attributes.indexOf(attr), 1) + for (const arg of args) { + if (arg === '0') { + attributes = [] + } else if (arg === '22') { // Neither bold nor faint + removeAttribute('1') + removeAttribute('2') + } else if (arg === '23') { // Neither italic nor Fraktur + removeAttribute('3') + removeAttribute('20') + } else if (arg === '24') { // Not underlined + removeAttribute('4') + } else if (arg === '25') { // Blink off + removeAttribute('5') + } else if (arg === '27') { // Inverse off + removeAttribute('7') + } else if (arg === '28') { // Conceal off + removeAttribute('8') + } else if (arg === '29') { // Not crossed out + removeAttribute('9') + } else if (arg === '39') { // Default foreground + for (let i = 0; i < 10; i++) { + removeAttribute('3' + i) } - } - - for (const arg of args) { - if (arg === '0') { - attributes = [] - } else if (arg === '22') { // Neither bold nor faint - removeAttribute('1') - removeAttribute('2') - } else if (arg === '23') { // Neither italic nor Fraktur - removeAttribute('3') - removeAttribute('20') - } else if (arg === '24') { // Not underlined - removeAttribute('4') - } else if (arg === '25') { // Blink off - removeAttribute('5') - } else if (arg === '27') { // Inverse off - removeAttribute('7') - } else if (arg === '28') { // Conceal off - removeAttribute('8') - } else if (arg === '29') { // Not crossed out - removeAttribute('9') - } else if (arg === '39') { // Default foreground - for (let i = 0; i < 10; i++) { - removeAttribute('3' + i) - } - } else if (arg === '49') { // Default background - for (let i = 0; i < 10; i++) { - removeAttribute('4' + i) - } - } else { - attributes = attributes.concat([arg]) + } else if (arg === '49') { // Default background + for (let i = 0; i < 10; i++) { + removeAttribute('4' + i) } + } else { + attributes = attributes.concat([arg]) } } - - continue } - chars[cursorIndex] = { - char: text[charI], attributes - } + continue + } - // Some characters take up multiple columns, e.g. Japanese text. Take - // this into consideration when drawing. - const charColumns = wcwidth(text[charI]) - cursorCol += charColumns + chars[cursorIndex] = { + char: text[charI], attributes + } - // If the character takes up 2+ columns, treat columns past the first - // one (where the character is) as empty. (Note this is different from - // "blank", which represents an empty space character ' '.) - for (let i = 1; i < charColumns; i++) { - chars[cursorIndex + i] = {char: '', attributes: []} - } + // Some characters take up multiple columns, e.g. Japanese text. Take + // this into consideration when drawing. + const charColumns = wcwidth(text[charI]) + cursorCol += charColumns - if (cursorCol > scrCols) { - cursorCol = 1 - cursorRow++ - } + // If the character takes up 2+ columns, treat columns past the first + // one (where the character is) as empty. (Note this is different from + // "blank", which represents an empty space character ' '.) + for (let i = 1; i < charColumns; i++) { + chars[cursorIndex + i] = {char: '', attributes: []} } - // SPOooooOOoky diffing! ------------- - // - // - Search for series of differences. This means a collection of characters - // which have different text or attribute properties. - // - // - Figure out how to print these differences. Move the cursor to the beginning - // character's row/column, then print the differences. + if (cursorCol > scrCols) { + cursorCol = 1 + cursorRow++ + } + } - const newChars = chars + // SPOooooOOoky diffing! ------------- + // + // - Search for series of differences. This means a collection of characters + // which have different text or attribute properties. + // + // - Figure out how to print these differences. Move the cursor to the beginning + // character's row/column, then print the differences. + + const newChars = chars + + const differences = [] + + if (oldChars === null) { + differences.push(0) + differences.push(newChars.slice()) + } else { + const charsEqual = (oldChar, newChar) => { + if (oldChar.char !== newChar.char) { + return false + } - const differences = [] + let oldAttrs = oldChar.attributes.slice() + let newAttrs = newChar.attributes.slice() - if (oldChars === null) { - differences.push(0) - differences.push(newChars.slice()) - } else { - const charsEqual = (oldChar, newChar) => { - if (oldChar.char !== newChar.char) { + while (newAttrs.length) { + const attr = newAttrs.shift() + if (oldAttrs.includes(attr)) { + oldAttrs.splice(oldAttrs.indexOf(attr), 1) + } else { return false } + } - let oldAttrs = oldChar.attributes.slice() - let newAttrs = newChar.attributes.slice() - - while (newAttrs.length) { - const attr = newAttrs.shift() - if (oldAttrs.includes(attr)) { - oldAttrs.splice(oldAttrs.indexOf(attr), 1) - } else { - return false - } - } - - oldAttrs = oldChar.attributes.slice() - newAttrs = newChar.attributes.slice() + oldAttrs = oldChar.attributes.slice() + newAttrs = newChar.attributes.slice() - while (oldAttrs.length) { - const attr = oldAttrs.shift() - if (newAttrs.includes(attr)) { - newAttrs.splice(newAttrs.indexOf(attr), 1) - } else { - return false - } + while (oldAttrs.length) { + const attr = oldAttrs.shift() + if (newAttrs.includes(attr)) { + newAttrs.splice(newAttrs.indexOf(attr), 1) + } else { + return false } - - return true } - let curChars = null + return true + } - for (let i = 0; i < chars.length; i++) { - const oldChar = oldChars[i] - const newChar = newChars[i] + let curChars = null - // TODO: Some sort of "distance" before we should clear curDiff? - // It may take *less* characters if this diff and the next are merged - // (entering a single character is smaller than the length of the code - // used to move past that character). Probably not very significant of - // an impact, though. - if (charsEqual(oldChar, newChar)) { - curChars = null - } else { - if (curChars === null) { - curChars = [] - differences.push(i, curChars) - } + for (let i = 0; i < chars.length; i++) { + const oldChar = oldChars[i] + const newChar = newChars[i] - curChars.push(newChar) + // TODO: Some sort of "distance" before we should clear curDiff? + // It may take *less* characters if this diff and the next are merged + // (entering a single character is smaller than the length of the code + // used to move past that character). Probably not very significant of + // an impact, though. + if (charsEqual(oldChar, newChar)) { + curChars = null + } else { + if (curChars === null) { + curChars = [] + differences.push(i, curChars) } + + curChars.push(newChar) } } + } - // Character concatenation ----------- + // Character concatenation ----------- - let lastChar = oldLastChar || { - char: '', - attributes: [] - } + let lastChar = oldLastChar || { + char: '', + attributes: [] + } - const result = [] - - for (let parse = 0; parse < differences.length; parse += 2) { - const i = differences[parse] - const chars = differences[parse + 1] - - const col = i % scrCols - const row = (i - col) / scrCols - result.push(ansi.moveCursor(row, col)) - - for (const char of chars) { - const newAttributes = ( - char.attributes.filter(attr => !(lastChar.attributes.includes(attr))) - ) - - const removedAttributes = ( - lastChar.attributes.filter(attr => !(char.attributes.includes(attr))) - ) - - // The only way to practically remove any character attribute is to - // reset all of its attributes and then re-add its existing attributes. - // If we do that, there's no need to add new attributes. - if (removedAttributes.length) { - result.push(ansi.resetAttributes()) - result.push(`${ESC}[${char.attributes.join(';')}m`) - } else if (newAttributes.length) { - result.push(`${ESC}[${newAttributes.join(';')}m`) - } + const result = [] + + for (let parse = 0; parse < differences.length; parse += 2) { + const i = differences[parse] + const chars = differences[parse + 1] + + const col = i % scrCols + const row = (i - col) / scrCols + result.push(moveCursor(row, col)) + + for (const char of chars) { + const newAttributes = ( + char.attributes.filter(attr => !(lastChar.attributes.includes(attr))) + ) + + const removedAttributes = ( + lastChar.attributes.filter(attr => !(char.attributes.includes(attr))) + ) + + // The only way to practically remove any character attribute is to + // reset all of its attributes and then re-add its existing attributes. + // If we do that, there's no need to add new attributes. + if (removedAttributes.length) { + result.push(resetAttributes()) + result.push(`${ESC}[${char.attributes.join(';')}m`) + } else if (newAttributes.length) { + result.push(`${ESC}[${newAttributes.join(';')}m`) + } - result.push(char.char) + result.push(char.char) - lastChar = char - } + lastChar = char } + } - // If anything changed *or* the cursor moved, we need to put it back where - // it was before: - if (result.length || cursorCol !== oldCursorCol || cursorRow !== oldCursorRow) { - result.push(ansi.moveCursor(cursorRow, cursorCol)) - } + // If anything changed *or* the cursor moved, we need to put it back where + // it was before: + if (result.length || cursorCol !== oldCursorCol || cursorRow !== oldCursorRow) { + result.push(moveCursor(cursorRow, cursorCol)) + } - // If the cursor is visible and wasn't before, or vice versa, we need to - // show that: - if (showCursor && !oldShowCursor) { - result.push(ansi.showCursor()) - } else if (!showCursor && oldShowCursor) { - result.push(ansi.hideCursor()) - } + // If the cursor is visible and wasn't before, or vice versa, we need to + // show that: + if (showCursor && !oldShowCursor) { + result.push(showCursor()) + } else if (!showCursor && oldShowCursor) { + result.push(hideCursor()) + } - return { - oldChars: newChars.slice(), - oldLastChar: Object.assign({}, lastChar), - oldScrRows: scrRows, - oldScrCols: scrCols, - oldCursorRow: cursorRow, - oldCursorCol: cursorCol, - oldShowCursor: showCursor, - screen: result.join('') - } + return { + oldChars: newChars.slice(), + oldLastChar: Object.assign({}, lastChar), + oldScrRows: scrRows, + oldScrCols: scrCols, + oldCursorRow: cursorRow, + oldCursorCol: cursorCol, + oldShowCursor: showCursor, + screen: result.join('') } } - -module.exports = ansi diff --git a/util/count.js b/util/count.js index 24c11b0..d4c0919 100644 --- a/util/count.js +++ b/util/count.js @@ -1,4 +1,4 @@ -module.exports = function count(arr) { +export default function count(arr) { // Counts the number of times the items of an array appear (only on the top // level; it doesn't search through nested arrays!). Returns a map of // item -> count. diff --git a/util/exception.js b/util/exception.js index e88ff99..1271b6a 100644 --- a/util/exception.js +++ b/util/exception.js @@ -1,4 +1,4 @@ -module.exports = function exception(code, message) { +export default function exception(code, message) { // Makes a custom error with the given code and message. const err = new Error(`${code}: ${message}`) diff --git a/util/index.js b/util/index.js new file mode 100644 index 0000000..db3d8a7 --- /dev/null +++ b/util/index.js @@ -0,0 +1,11 @@ +export * as ansi from './ansi.js' +export * as interfaces from './interfaces/index.js' + +export {default as count} from './count.js' +export {default as exception} from './exception.js' +export {default as smoothen} from './smoothen.js' +export {default as telchars} from './telchars.js' +export {default as tuiApp} from './tui-app.js' +export {default as unichars} from './unichars.js' +export {default as waitForData} from './waitForData.js' +export {default as wrap} from './wrap.js' diff --git a/util/CommandLineInterfacer.js b/util/interfaces/CommandLineInterface.js index d2007fb..66c8c43 100644 --- a/util/CommandLineInterfacer.js +++ b/util/interfaces/CommandLineInterface.js @@ -1,8 +1,9 @@ -const EventEmitter = require('events') -const waitForData = require('./waitForData') -const ansi = require('./ansi') +import EventEmitter from 'node:events' -module.exports = class CommandLineInterfacer extends EventEmitter { +import * as ansi from '../ansi.js' +import waitForData from '../waitForData.js' + +export default class CommandLineInterface extends EventEmitter { constructor(inStream = process.stdin, outStream = process.stdout, proc = process) { super() diff --git a/util/Flushable.js b/util/interfaces/Flushable.js index 058d186..d8b72d3 100644 --- a/util/Flushable.js +++ b/util/interfaces/Flushable.js @@ -1,7 +1,7 @@ -const ansi = require('./ansi') -const unic = require('./unichars') +import * as ansi from '../ansi.js' +import unic from '../unichars.js' -module.exports = class Flushable { +export default class Flushable { // A writable that can be used to collect chunks of data before writing // them. diff --git a/util/TelnetInterfacer.js b/util/interfaces/TelnetInterface.js index dc71157..8777680 100644 --- a/util/TelnetInterfacer.js +++ b/util/interfaces/TelnetInterface.js @@ -1,8 +1,9 @@ -const ansi = require('./ansi') -const waitForData = require('./waitForData') -const EventEmitter = require('events') +import EventEmitter from 'node:events' -module.exports = class TelnetInterfacer extends EventEmitter { +import * as ansi from '../ansi.js' +import waitForData from '../waitForData.js' + +export default class TelnetInterface extends EventEmitter { constructor(socket) { super() diff --git a/util/interfaces/index.js b/util/interfaces/index.js new file mode 100644 index 0000000..83aeb2c --- /dev/null +++ b/util/interfaces/index.js @@ -0,0 +1,4 @@ +export {default as Flushable} from './Flushable.js' + +export {default as CommandLineInterface} from './CommandLineInterface.js' +export {default as TelnetInterface} from './TelnetInterface.js' diff --git a/util/smoothen.js b/util/smoothen.js index 55ba23c..5809271 100644 --- a/util/smoothen.js +++ b/util/smoothen.js @@ -1,4 +1,4 @@ -module.exports = function(tx, x, divisor) { +export default function smoothen(tx, x, divisor) { // Smoothly transitions givens X to TX using a given divisor. Rounds the // amount moved. diff --git a/util/telchars.js b/util/telchars.js index 12d4095..5a5ad42 100644 --- a/util/telchars.js +++ b/util/telchars.js @@ -97,4 +97,4 @@ const telchars = { isCharacter: (buf, char) => compareBufStr(buf, char), } -module.exports = telchars +export default telchars diff --git a/util/tui-app.js b/util/tui-app.js index a695e57..2f09818 100644 --- a/util/tui-app.js +++ b/util/tui-app.js @@ -2,27 +2,26 @@ // program. Contained to reduce boilerplate and improve consistency between // programs. -const ansi = require('./ansi'); +import {Root} from 'tui-lib/ui/primitives' -const CommandLineInterfacer = require('./CommandLineInterfacer'); -const Flushable = require('./Flushable'); -const Root = require('../ui/Root'); +import {CommandLineInterface, Flushable} from './interfaces/index.js' +import * as ansi from './ansi.js' -module.exports = async function tuiApp(callback) { - // TODO: Support other interfacers. - const interfacer = new CommandLineInterfacer(); +export default async function tuiApp(callback) { + // TODO: Support other screen interfaces. + const screenInterface = new CommandLineInterface(); const flushable = new Flushable(process.stdout, true); - const root = new Root(interfacer); + const root = new Root(screenInterface); - const size = await interfacer.getScreenSize(); + const size = await screenInterface.getScreenSize(); root.w = size.width; root.h = size.height; flushable.resizeScreen(size); root.on('rendered', () => flushable.flush()); - interfacer.on('resize', newSize => { + screenInterface.on('resize', newSize => { root.w = newSize.width; root.h = newSize.height; flushable.resizeScreen(newSize); diff --git a/util/unichars.js b/util/unichars.js index ee137e8..2099b62 100644 --- a/util/unichars.js +++ b/util/unichars.js @@ -1,6 +1,6 @@ // Useful Unicode characters. -module.exports = { +export default { /* … */ ELLIPSIS: '\u2026', /* ─ */ BOX_H: '\u2500', diff --git a/util/waitForData.js b/util/waitForData.js index bf40c52..f8d4a92 100644 --- a/util/waitForData.js +++ b/util/waitForData.js @@ -1,4 +1,4 @@ -module.exports = function waitForData(stream, cond = null) { +export default function waitForData(stream, cond = null) { return new Promise(resolve => { stream.on('data', data => { if (cond ? cond(data) : true) { diff --git a/util/wrap.js b/util/wrap.js index 3c381d4..2c720c8 100644 --- a/util/wrap.js +++ b/util/wrap.js @@ -1,4 +1,4 @@ -module.exports = function wrap(str, width) { +export default function wrap(str, width) { // Wraps a string into separate lines. Returns an array of strings, for // each line of the text. |