-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
readline: move utilities to internal modules
PR-URL: #38466 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]> Reviewed-By: Michaël Zasso <[email protected]>
- Loading branch information
Showing
6 changed files
with
243 additions
and
196 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
'use strict'; | ||
|
||
const { | ||
NumberIsNaN, | ||
} = primordials; | ||
|
||
const { | ||
codes: { | ||
ERR_INVALID_ARG_VALUE, | ||
ERR_INVALID_CURSOR_POS, | ||
}, | ||
} = require('internal/errors'); | ||
|
||
const { | ||
validateCallback, | ||
} = require('internal/validators'); | ||
const { | ||
CSI, | ||
} = require('internal/readline/utils'); | ||
|
||
const { | ||
kClearLine, | ||
kClearScreenDown, | ||
kClearToLineBeginning, | ||
kClearToLineEnd, | ||
} = CSI; | ||
|
||
|
||
/** | ||
* moves the cursor to the x and y coordinate on the given stream | ||
*/ | ||
|
||
function cursorTo(stream, x, y, callback) { | ||
if (callback !== undefined) { | ||
validateCallback(callback); | ||
} | ||
|
||
if (typeof y === 'function') { | ||
callback = y; | ||
y = undefined; | ||
} | ||
|
||
if (NumberIsNaN(x)) throw new ERR_INVALID_ARG_VALUE('x', x); | ||
if (NumberIsNaN(y)) throw new ERR_INVALID_ARG_VALUE('y', y); | ||
|
||
if (stream == null || (typeof x !== 'number' && typeof y !== 'number')) { | ||
if (typeof callback === 'function') process.nextTick(callback, null); | ||
return true; | ||
} | ||
|
||
if (typeof x !== 'number') throw new ERR_INVALID_CURSOR_POS(); | ||
|
||
const data = typeof y !== 'number' ? CSI`${x + 1}G` : CSI`${y + 1};${x + 1}H`; | ||
return stream.write(data, callback); | ||
} | ||
|
||
/** | ||
* moves the cursor relative to its current location | ||
*/ | ||
|
||
function moveCursor(stream, dx, dy, callback) { | ||
if (callback !== undefined) { | ||
validateCallback(callback); | ||
} | ||
|
||
if (stream == null || !(dx || dy)) { | ||
if (typeof callback === 'function') process.nextTick(callback, null); | ||
return true; | ||
} | ||
|
||
let data = ''; | ||
|
||
if (dx < 0) { | ||
data += CSI`${-dx}D`; | ||
} else if (dx > 0) { | ||
data += CSI`${dx}C`; | ||
} | ||
|
||
if (dy < 0) { | ||
data += CSI`${-dy}A`; | ||
} else if (dy > 0) { | ||
data += CSI`${dy}B`; | ||
} | ||
|
||
return stream.write(data, callback); | ||
} | ||
|
||
/** | ||
* clears the current line the cursor is on: | ||
* -1 for left of the cursor | ||
* +1 for right of the cursor | ||
* 0 for the entire line | ||
*/ | ||
|
||
function clearLine(stream, dir, callback) { | ||
if (callback !== undefined) { | ||
validateCallback(callback); | ||
} | ||
|
||
if (stream === null || stream === undefined) { | ||
if (typeof callback === 'function') process.nextTick(callback, null); | ||
return true; | ||
} | ||
|
||
const type = | ||
dir < 0 ? kClearToLineBeginning : dir > 0 ? kClearToLineEnd : kClearLine; | ||
return stream.write(type, callback); | ||
} | ||
|
||
/** | ||
* clears the screen from the current position of the cursor down | ||
*/ | ||
|
||
function clearScreenDown(stream, callback) { | ||
if (callback !== undefined) { | ||
validateCallback(callback); | ||
} | ||
|
||
if (stream === null || stream === undefined) { | ||
if (typeof callback === 'function') process.nextTick(callback, null); | ||
return true; | ||
} | ||
|
||
return stream.write(kClearScreenDown, callback); | ||
} | ||
|
||
module.exports = { | ||
clearLine, | ||
clearScreenDown, | ||
cursorTo, | ||
moveCursor, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
'use strict'; | ||
|
||
const { | ||
SafeStringIterator, | ||
Symbol, | ||
} = primordials; | ||
|
||
const { | ||
charLengthAt, | ||
CSI, | ||
emitKeys, | ||
} = require('internal/readline/utils'); | ||
|
||
const { clearTimeout, setTimeout } = require('timers'); | ||
const { | ||
kEscape, | ||
} = CSI; | ||
|
||
const { StringDecoder } = require('string_decoder'); | ||
|
||
const KEYPRESS_DECODER = Symbol('keypress-decoder'); | ||
const ESCAPE_DECODER = Symbol('escape-decoder'); | ||
|
||
// GNU readline library - keyseq-timeout is 500ms (default) | ||
const ESCAPE_CODE_TIMEOUT = 500; | ||
|
||
/** | ||
* accepts a readable Stream instance and makes it emit "keypress" events | ||
*/ | ||
|
||
function emitKeypressEvents(stream, iface = {}) { | ||
if (stream[KEYPRESS_DECODER]) return; | ||
|
||
stream[KEYPRESS_DECODER] = new StringDecoder('utf8'); | ||
|
||
stream[ESCAPE_DECODER] = emitKeys(stream); | ||
stream[ESCAPE_DECODER].next(); | ||
|
||
const triggerEscape = () => stream[ESCAPE_DECODER].next(''); | ||
const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface; | ||
let timeoutId; | ||
|
||
function onData(input) { | ||
if (stream.listenerCount('keypress') > 0) { | ||
const string = stream[KEYPRESS_DECODER].write(input); | ||
if (string) { | ||
clearTimeout(timeoutId); | ||
|
||
// This supports characters of length 2. | ||
iface._sawKeyPress = charLengthAt(string, 0) === string.length; | ||
iface.isCompletionEnabled = false; | ||
|
||
let length = 0; | ||
for (const character of new SafeStringIterator(string)) { | ||
length += character.length; | ||
if (length === string.length) { | ||
iface.isCompletionEnabled = true; | ||
} | ||
|
||
try { | ||
stream[ESCAPE_DECODER].next(character); | ||
// Escape letter at the tail position | ||
if (length === string.length && character === kEscape) { | ||
timeoutId = setTimeout(triggerEscape, escapeCodeTimeout); | ||
} | ||
} catch (err) { | ||
// If the generator throws (it could happen in the `keypress` | ||
// event), we need to restart it. | ||
stream[ESCAPE_DECODER] = emitKeys(stream); | ||
stream[ESCAPE_DECODER].next(); | ||
throw err; | ||
} | ||
} | ||
} | ||
} else { | ||
// Nobody's watching anyway | ||
stream.removeListener('data', onData); | ||
stream.on('newListener', onNewListener); | ||
} | ||
} | ||
|
||
function onNewListener(event) { | ||
if (event === 'keypress') { | ||
stream.on('data', onData); | ||
stream.removeListener('newListener', onNewListener); | ||
} | ||
} | ||
|
||
if (stream.listenerCount('keypress') > 0) { | ||
stream.on('data', onData); | ||
} else { | ||
stream.on('newListener', onNewListener); | ||
} | ||
} | ||
|
||
module.exports = emitKeypressEvents; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.