readline: refactor to use more primordials

PR-URL: https://github.com/nodejs/node/pull/36296
Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
Antoine du Hamel
2020-11-15 23:28:06 +01:00
committed by Node.js GitHub Bot
parent e074bee7da
commit ed6e71a1ca
2 changed files with 99 additions and 57 deletions

View File

@@ -1,7 +1,15 @@
'use strict';
const {
ArrayPrototypeSlice,
ArrayPrototypeSort,
RegExpPrototypeTest,
StringFromCharCode,
StringPrototypeCharCodeAt,
StringPrototypeCodePointAt,
StringPrototypeMatch,
StringPrototypeSlice,
StringPrototypeToLowerCase,
Symbol,
} = primordials;
@@ -32,8 +40,9 @@ CSI.kClearScreenDown = CSI`0J`;
function charLengthLeft(str, i) {
if (i <= 0)
return 0;
if ((i > 1 && str.codePointAt(i - 2) >= kUTF16SurrogateThreshold) ||
str.codePointAt(i - 1) >= kUTF16SurrogateThreshold) {
if ((i > 1 &&
StringPrototypeCodePointAt(str, i - 2) >= kUTF16SurrogateThreshold) ||
StringPrototypeCodePointAt(str, i - 1) >= kUTF16SurrogateThreshold) {
return 2;
}
return 1;
@@ -45,7 +54,7 @@ function charLengthAt(str, i) {
// moving to the right.
return 1;
}
return str.codePointAt(i) >= kUTF16SurrogateThreshold ? 2 : 1;
return StringPrototypeCodePointAt(str, i) >= kUTF16SurrogateThreshold ? 2 : 1;
}
/*
@@ -178,13 +187,15 @@ function* emitKeys(stream) {
* We buffered enough data, now trying to extract code
* and modifier from it
*/
const cmd = s.slice(cmdStart);
const cmd = StringPrototypeSlice(s, cmdStart);
let match;
if ((match = cmd.match(/^(\d\d?)(;(\d))?([~^$])$/))) {
if ((match = StringPrototypeMatch(cmd, /^(\d\d?)(;(\d))?([~^$])$/))) {
code += match[1] + match[4];
modifier = (match[3] || 1) - 1;
} else if ((match = cmd.match(/^((\d;)?(\d))?([A-Za-z])$/))) {
} else if (
(match = StringPrototypeMatch(cmd, /^((\d;)?(\d))?([A-Za-z])$/))
) {
code += match[4];
modifier = (match[3] || 1) - 1;
} else {
@@ -325,12 +336,14 @@ function* emitKeys(stream) {
key.meta = escaped;
} else if (!escaped && ch <= '\x1a') {
// ctrl+letter
key.name = StringFromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
key.name = StringFromCharCode(
StringPrototypeCharCodeAt(ch) + StringPrototypeCharCodeAt('a') - 1
);
key.ctrl = true;
} else if (/^[0-9A-Za-z]$/.test(ch)) {
} else if (RegExpPrototypeTest(/^[0-9A-Za-z]$/, ch)) {
// Letter, number, shift+letter
key.name = ch.toLowerCase();
key.shift = /^[A-Z]$/.test(ch);
key.name = StringPrototypeToLowerCase(ch);
key.shift = RegExpPrototypeTest(/^[A-Z]$/, ch);
key.meta = escaped;
} else if (escaped) {
// Escape sequence timeout
@@ -356,12 +369,12 @@ function commonPrefix(strings) {
if (strings.length === 1) {
return strings[0];
}
const sorted = strings.slice().sort();
const sorted = ArrayPrototypeSort(ArrayPrototypeSlice(strings));
const min = sorted[0];
const max = sorted[sorted.length - 1];
for (let i = 0; i < min.length; i++) {
if (min[i] !== max[i]) {
return min.slice(0, i);
return StringPrototypeSlice(min, 0, i);
}
}
return min;

View File

@@ -28,7 +28,18 @@
'use strict';
const {
ArrayFrom,
ArrayPrototypeFilter,
ArrayPrototypeIndexOf,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePop,
ArrayPrototypeReverse,
ArrayPrototypeSplice,
ArrayPrototypeUnshift,
DateNow,
FunctionPrototypeBind,
FunctionPrototypeCall,
MathCeil,
MathFloor,
MathMax,
@@ -36,6 +47,16 @@ const {
NumberIsNaN,
ObjectDefineProperty,
ObjectSetPrototypeOf,
RegExpPrototypeTest,
StringPrototypeCodePointAt,
StringPrototypeEndsWith,
StringPrototypeMatch,
StringPrototypeRepeat,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
StringPrototypeTrim,
Symbol,
SymbolAsyncIterator,
} = primordials;
@@ -110,7 +131,7 @@ function Interface(input, output, completer, terminal) {
this.escapeCodeTimeout = ESCAPE_CODE_TIMEOUT;
this.tabSize = 8;
EventEmitter.call(this);
FunctionPrototypeCall(EventEmitter, this,);
let historySize;
let removeHistoryDuplicates = false;
let crlfDelay;
@@ -187,7 +208,7 @@ function Interface(input, output, completer, terminal) {
this.terminal = !!terminal;
if (process.env.TERM === 'dumb') {
this._ttyWrite = _ttyWriteDumb.bind(this);
this._ttyWrite = FunctionPrototypeBind(_ttyWriteDumb, this);
}
function onerror(err) {
@@ -219,7 +240,7 @@ function Interface(input, output, completer, terminal) {
// If the key.sequence is half of a surrogate pair
// (>= 0xd800 and <= 0xdfff), refresh the line so
// the character is displayed appropriately.
const ch = key.sequence.codePointAt(0);
const ch = StringPrototypeCodePointAt(key.sequence, 0);
if (ch >= 0xd800 && ch <= 0xdfff)
self._refreshLine();
}
@@ -366,19 +387,19 @@ Interface.prototype._addHistory = function() {
if (this.historySize === 0) return this.line;
// If the trimmed line is empty then return the line
if (this.line.trim().length === 0) return this.line;
if (StringPrototypeTrim(this.line).length === 0) return this.line;
if (this.history.length === 0 || this.history[0] !== this.line) {
if (this.removeHistoryDuplicates) {
// Remove older history line if identical to new one
const dupIndex = this.history.indexOf(this.line);
if (dupIndex !== -1) this.history.splice(dupIndex, 1);
const dupIndex = ArrayPrototypeIndexOf(this.history, this.line);
if (dupIndex !== -1) ArrayPrototypeSplice(this.history, dupIndex, 1);
}
this.history.unshift(this.line);
ArrayPrototypeUnshift(this.history, this.line);
// Only store so many
if (this.history.length > this.historySize) this.history.pop();
if (this.history.length > this.historySize) ArrayPrototypePop(this.history);
}
this.historyIndex = -1;
@@ -472,24 +493,24 @@ Interface.prototype._normalWrite = function(b) {
let string = this._decoder.write(b);
if (this._sawReturnAt &&
DateNow() - this._sawReturnAt <= this.crlfDelay) {
string = string.replace(/^\n/, '');
string = StringPrototypeReplace(string, /^\n/, '');
this._sawReturnAt = 0;
}
// Run test() on the new string chunk, not on the entire line buffer.
const newPartContainsEnding = lineEnding.test(string);
const newPartContainsEnding = RegExpPrototypeTest(lineEnding, string);
if (this._line_buffer) {
string = this._line_buffer + string;
this._line_buffer = null;
}
if (newPartContainsEnding) {
this._sawReturnAt = string.endsWith('\r') ? DateNow() : 0;
this._sawReturnAt = StringPrototypeEndsWith(string, '\r') ? DateNow() : 0;
// Got one or more newlines; process into "line" events
const lines = string.split(lineEnding);
const lines = StringPrototypeSplit(string, lineEnding);
// Either '' or (conceivably) the unfinished portion of the next line
string = lines.pop();
string = ArrayPrototypePop(lines);
this._line_buffer = string;
for (let n = 0; n < lines.length; n++)
this._onLine(lines[n]);
@@ -501,8 +522,8 @@ Interface.prototype._normalWrite = function(b) {
Interface.prototype._insertString = function(c) {
if (this.cursor < this.line.length) {
const beg = this.line.slice(0, this.cursor);
const end = this.line.slice(this.cursor, this.line.length);
const beg = StringPrototypeSlice(this.line, 0, this.cursor);
const end = StringPrototypeSlice(this.line, this.cursor, this.line.length);
this.line = beg + c + end;
this.cursor += c.length;
this._refreshLine();
@@ -520,7 +541,8 @@ Interface.prototype._insertString = function(c) {
Interface.prototype._tabComplete = function(lastKeypressWasTab) {
this.pause();
this.completer(this.line.slice(0, this.cursor), (err, value) => {
const string = StringPrototypeSlice(this.line, 0, this.cursor);
this.completer(string, (err, value) => {
this.resume();
if (err) {
@@ -536,9 +558,10 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) {
}
// If there is a common prefix to all matches, then apply that portion.
const prefix = commonPrefix(completions.filter((e) => e !== ''));
const prefix = commonPrefix(ArrayPrototypeFilter(completions,
(e) => e !== ''));
if (prefix.length > completeOn.length) {
this._insertString(prefix.slice(completeOn.length));
this._insertString(StringPrototypeSlice(prefix, completeOn.length));
return;
}
@@ -547,7 +570,8 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) {
}
// Apply/show completions.
const completionsWidth = completions.map((e) => getStringWidth(e));
const completionsWidth = ArrayPrototypeMap(completions,
(e) => getStringWidth(e));
const width = MathMax(...completionsWidth) + 2; // 2 space padding
let maxColumns = MathFloor(this.columns / width) || 1;
if (maxColumns === Infinity) {
@@ -563,7 +587,7 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) {
lineIndex = 0;
whitespace = 0;
} else {
output += ' '.repeat(whitespace);
output += StringPrototypeRepeat(' ', whitespace);
}
if (completion !== '') {
output += completion;
@@ -585,9 +609,10 @@ Interface.prototype._wordLeft = function() {
if (this.cursor > 0) {
// Reverse the string and match a word near beginning
// to avoid quadratic time complexity
const leading = this.line.slice(0, this.cursor);
const reversed = leading.split('').reverse().join('');
const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/);
const leading = StringPrototypeSlice(this.line, 0, this.cursor);
const reversed = ArrayPrototypeJoin(
ArrayPrototypeReverse(ArrayFrom(leading)), '');
const match = StringPrototypeMatch(reversed, /^\s*(?:[^\w\s]+|\w+)?/);
this._moveCursor(-match[0].length);
}
};
@@ -595,8 +620,8 @@ Interface.prototype._wordLeft = function() {
Interface.prototype._wordRight = function() {
if (this.cursor < this.line.length) {
const trailing = this.line.slice(this.cursor);
const match = trailing.match(/^(?:\s+|[^\w\s]+|\w+)\s*/);
const trailing = StringPrototypeSlice(this.line, this.cursor);
const match = StringPrototypeMatch(trailing, /^(?:\s+|[^\w\s]+|\w+)\s*/);
this._moveCursor(match[0].length);
}
};
@@ -605,8 +630,8 @@ Interface.prototype._deleteLeft = function() {
if (this.cursor > 0 && this.line.length > 0) {
// The number of UTF-16 units comprising the character to the left
const charSize = charLengthLeft(this.line, this.cursor);
this.line = this.line.slice(0, this.cursor - charSize) +
this.line.slice(this.cursor, this.line.length);
this.line = StringPrototypeSlice(this.line, 0, this.cursor - charSize) +
StringPrototypeSlice(this.line, this.cursor, this.line.length);
this.cursor -= charSize;
this._refreshLine();
@@ -618,8 +643,8 @@ Interface.prototype._deleteRight = function() {
if (this.cursor < this.line.length) {
// The number of UTF-16 units comprising the character to the left
const charSize = charLengthAt(this.line, this.cursor);
this.line = this.line.slice(0, this.cursor) +
this.line.slice(this.cursor + charSize, this.line.length);
this.line = StringPrototypeSlice(this.line, 0, this.cursor) +
StringPrototypeSlice(this.line, this.cursor + charSize, this.line.length);
this._refreshLine();
}
};
@@ -629,11 +654,14 @@ Interface.prototype._deleteWordLeft = function() {
if (this.cursor > 0) {
// Reverse the string and match a word near beginning
// to avoid quadratic time complexity
let leading = this.line.slice(0, this.cursor);
const reversed = leading.split('').reverse().join('');
const match = reversed.match(/^\s*(?:[^\w\s]+|\w+)?/);
leading = leading.slice(0, leading.length - match[0].length);
this.line = leading + this.line.slice(this.cursor, this.line.length);
let leading = StringPrototypeSlice(this.line, 0, this.cursor);
const reversed = ArrayPrototypeJoin(
ArrayPrototypeReverse(ArrayFrom(leading)), '');
const match = StringPrototypeMatch(reversed, /^\s*(?:[^\w\s]+|\w+)?/);
leading = StringPrototypeSlice(leading, 0,
leading.length - match[0].length);
this.line = leading + StringPrototypeSlice(this.line, this.cursor,
this.line.length);
this.cursor = leading.length;
this._refreshLine();
}
@@ -642,24 +670,24 @@ Interface.prototype._deleteWordLeft = function() {
Interface.prototype._deleteWordRight = function() {
if (this.cursor < this.line.length) {
const trailing = this.line.slice(this.cursor);
const match = trailing.match(/^(?:\s+|\W+|\w+)\s*/);
this.line = this.line.slice(0, this.cursor) +
trailing.slice(match[0].length);
const trailing = StringPrototypeSlice(this.line, this.cursor);
const match = StringPrototypeMatch(trailing, /^(?:\s+|\W+|\w+)\s*/);
this.line = StringPrototypeSlice(this.line, 0, this.cursor) +
StringPrototypeSlice(trailing, match[0].length);
this._refreshLine();
}
};
Interface.prototype._deleteLineLeft = function() {
this.line = this.line.slice(this.cursor);
this.line = StringPrototypeSlice(this.line, this.cursor);
this.cursor = 0;
this._refreshLine();
};
Interface.prototype._deleteLineRight = function() {
this.line = this.line.slice(0, this.cursor);
this.line = StringPrototypeSlice(this.line, 0, this.cursor);
this._refreshLine();
};
@@ -691,7 +719,7 @@ Interface.prototype._historyNext = function() {
const search = this[kSubstringSearch] || '';
let index = this.historyIndex - 1;
while (index >= 0 &&
(!this.history[index].startsWith(search) ||
(!StringPrototypeStartsWith(this.history[index], search) ||
this.line === this.history[index])) {
index--;
}
@@ -711,7 +739,7 @@ Interface.prototype._historyPrev = function() {
const search = this[kSubstringSearch] || '';
let index = this.historyIndex + 1;
while (index < this.history.length &&
(!this.history[index].startsWith(search) ||
(!StringPrototypeStartsWith(this.history[index], search) ||
this.line === this.history[index])) {
index++;
}
@@ -761,7 +789,8 @@ Interface.prototype._getDisplayPos = function(str) {
// Returns current cursor's position and line
Interface.prototype.getCursorPos = function() {
const strBeforeCursor = this._prompt + this.line.substring(0, this.cursor);
const strBeforeCursor = this._prompt +
StringPrototypeSlice(this.line, 0, this.cursor);
return this._getDisplayPos(strBeforeCursor);
};
Interface.prototype._getCursorPos = Interface.prototype.getCursorPos;
@@ -851,7 +880,7 @@ Interface.prototype._ttyWrite = function(s, key) {
if ((key.name === 'up' || key.name === 'down') &&
!key.ctrl && !key.meta && !key.shift) {
if (this[kSubstringSearch] === null) {
this[kSubstringSearch] = this.line.slice(0, this.cursor);
this[kSubstringSearch] = StringPrototypeSlice(this.line, 0, this.cursor);
}
} else if (this[kSubstringSearch] !== null) {
this[kSubstringSearch] = null;
@@ -1075,7 +1104,7 @@ Interface.prototype._ttyWrite = function(s, key) {
// falls through
default:
if (typeof s === 'string' && s) {
const lines = s.split(/\r\n|\n|\r/);
const lines = StringPrototypeSplit(s, /\r\n|\n|\r/);
for (let i = 0, len = lines.length; i < len; i++) {
if (i > 0) {
this._line();