forked from nodejs/node
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes: nodejs#37287
- Loading branch information
Showing
10 changed files
with
2,205 additions
and
1,356 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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,133 @@ | ||
'use strict'; | ||
|
||
const { | ||
NumberIsNaN, | ||
} = primordials; | ||
|
||
const { | ||
codes | ||
} = require('internal/errors'); | ||
|
||
const { | ||
ERR_INVALID_ARG_VALUE, | ||
ERR_INVALID_CURSOR_POS, | ||
} = codes; | ||
const { | ||
validateCallback, | ||
} = require('internal/validators'); | ||
const { | ||
CSI, | ||
} = require('internal/readline/utils'); | ||
|
||
const { | ||
kClearToLineBeginning, | ||
kClearToLineEnd, | ||
kClearLine, | ||
kClearScreenDown | ||
} = 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; |
Oops, something went wrong.