diff --git a/lib/internal/readline/interface.js b/lib/internal/readline/interface.js index 41e84099cf51e5..937f426615c1b9 100644 --- a/lib/internal/readline/interface.js +++ b/lib/internal/readline/interface.js @@ -22,13 +22,10 @@ const { NumberIsNaN, ObjectSetPrototypeOf, RegExpPrototypeExec, - RegExpPrototypeSymbolReplace, - RegExpPrototypeSymbolSplit, StringPrototypeCodePointAt, StringPrototypeEndsWith, StringPrototypeRepeat, StringPrototypeSlice, - StringPrototypeSplit, StringPrototypeStartsWith, StringPrototypeTrim, Symbol, @@ -77,7 +74,7 @@ const kHistorySize = 30; const kMaxUndoRedoStackSize = 2048; const kMincrlfDelay = 100; // \r\n, \n, or \r followed by something other than \n -const lineEnding = /\r?\n|\r(?!\n)/; +const lineEnding = /\r?\n|\r(?!\n)/g; const kLineObjectStream = Symbol('line object stream'); const kQuestionCancel = Symbol('kQuestionCancel'); @@ -590,31 +587,40 @@ class Interface extends InterfaceConstructor { this[kSawReturnAt] && DateNow() - this[kSawReturnAt] <= this.crlfDelay ) { - string = RegExpPrototypeSymbolReplace(/^\n/, string, ''); + if (StringPrototypeCodePointAt(string) === 10) string = StringPrototypeSlice(string, 1); this[kSawReturnAt] = 0; } // Run test() on the new string chunk, not on the entire line buffer. - const newPartContainsEnding = RegExpPrototypeExec(lineEnding, string) !== null; - - if (this[kLine_buffer]) { - string = this[kLine_buffer] + string; - this[kLine_buffer] = null; - } - if (newPartContainsEnding) { + let newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); + if (newPartContainsEnding !== null) { + if (this[kLine_buffer]) { + string = this[kLine_buffer] + string; + this[kLine_buffer] = null; + newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); + } this[kSawReturnAt] = StringPrototypeEndsWith(string, '\r') ? DateNow() : 0; - // Got one or more newlines; process into "line" events - const lines = StringPrototypeSplit(string, lineEnding); + const indexes = [0, newPartContainsEnding.index, lineEnding.lastIndex]; + let nextMatch; + while ((nextMatch = RegExpPrototypeExec(lineEnding, string)) !== null) { + ArrayPrototypePush(indexes, nextMatch.index, lineEnding.lastIndex); + } + const lastIndex = indexes.length - 1; // Either '' or (conceivably) the unfinished portion of the next line - string = ArrayPrototypePop(lines); - this[kLine_buffer] = string; - for (let n = 0; n < lines.length; n++) this[kOnLine](lines[n]); + this[kLine_buffer] = StringPrototypeSlice(string, indexes[lastIndex]); + for (let i = 1; i < lastIndex; i += 2) { + this[kOnLine](StringPrototypeSlice(string, indexes[i - 1], indexes[i])); + } } else if (string) { // No newlines this time, save what we have for next time - this[kLine_buffer] = string; + if (this[kLine_buffer]) { + this[kLine_buffer] += string; + } else { + this[kLine_buffer] = string; + } } } @@ -1322,12 +1328,18 @@ class Interface extends InterfaceConstructor { // falls through default: if (typeof s === 'string' && s) { - const lines = RegExpPrototypeSymbolSplit(/\r\n|\n|\r/, s); - for (let i = 0, len = lines.length; i < len; i++) { - if (i > 0) { + let nextMatch = RegExpPrototypeExec(lineEnding, s); + if (nextMatch !== null) { + this[kInsertString](StringPrototypeSlice(s, 0, nextMatch.index)); + let { lastIndex } = lineEnding; + while ((nextMatch = RegExpPrototypeExec(lineEnding, s)) !== null) { this[kLine](); + this[kInsertString](StringPrototypeSlice(s, lastIndex, nextMatch.index)); + ({ lastIndex } = lineEnding); } - this[kInsertString](lines[i]); + if (lastIndex === s.length) this[kLine](); + } else { + this[kInsertString](s); } } }