From b5b8a996f378c06ab5ae09360869bbe572b90e3b Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 15 Nov 2020 23:28:06 +0100 Subject: [PATCH] readline: refactor to use more primordials PR-URL: https://github.com/nodejs/node/pull/36296 Reviewed-By: Rich Trott --- lib/internal/readline/utils.js | 37 ++++++---- lib/readline.js | 119 ++++++++++++++++++++------------- 2 files changed, 99 insertions(+), 57 deletions(-) diff --git a/lib/internal/readline/utils.js b/lib/internal/readline/utils.js index cce4045a5a0b02..55b3e07b4c1782 100644 --- a/lib/internal/readline/utils.js +++ b/lib/internal/readline/utils.js @@ -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; diff --git a/lib/readline.js b/lib/readline.js index 6fe456ec313493..e1bed3a95cf53a 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -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 ondata(data) { @@ -215,7 +236,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(); } @@ -358,19 +379,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; @@ -464,24 +485,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]); @@ -493,8 +514,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(); @@ -512,7 +533,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) { @@ -528,9 +550,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; } @@ -539,7 +562,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) { @@ -555,7 +579,7 @@ Interface.prototype._tabComplete = function(lastKeypressWasTab) { lineIndex = 0; whitespace = 0; } else { - output += ' '.repeat(whitespace); + output += StringPrototypeRepeat(' ', whitespace); } if (completion !== '') { output += completion; @@ -577,9 +601,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); } }; @@ -587,8 +612,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); } }; @@ -597,8 +622,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(); @@ -610,8 +635,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(); } }; @@ -621,11 +646,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(); } @@ -634,24 +662,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(); }; @@ -683,7 +711,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--; } @@ -703,7 +731,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++; } @@ -753,7 +781,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; @@ -843,7 +872,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; @@ -1067,7 +1096,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();