| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e018f9a commit c25b8e3
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,26 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common.js'); | ||
| 3 | + const readline = require('readline'); | ||
| 4 | + const { Readable, Writable } = require('stream'); | ||
| 5 | + | ||
| 6 | + const bench = common.createBenchmark(main, { | ||
| 7 | + n: [1e5], | ||
| 8 | + terminal: [0, 1], | ||
| 9 | + }); | ||
| 10 | + | ||
| 11 | + function main({ n, terminal }) { | ||
| 12 | + bench.start(); | ||
| 13 | + for (let i = 0; i < n; i++) { | ||
| 14 | + const input = new Readable({ read() {} }); | ||
| 15 | + const output = new Writable({ write(chunk, encoding, callback) { | ||
| 16 | + callback(); | ||
| 17 | + } }); | ||
| 18 | + const rl = readline.createInterface({ | ||
| 19 | + input, | ||
| 20 | + output, | ||
| 21 | + terminal: Boolean(terminal), | ||
| 22 | + }); | ||
| 23 | + rl.close(); | ||
| 24 | + } | ||
| 25 | + bench.end(n); | ||
| 26 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,9 +17,10 @@ const { | |||
| 17 | 17 | MathMax, | |
| 18 | 18 | MathMaxApply, | |
| 19 | 19 | NumberIsFinite, | |
| 20 | - ObjectDefineProperty, | ||
| 20 | + ObjectDefineProperties, | ||
| 21 | 21 | ObjectSetPrototypeOf, | |
| 22 | 22 | RegExpPrototypeExec, | |
| 23 | + RegExpPrototypeSymbolSplit, | ||
| 23 | 24 | SafeStringIterator, | |
| 24 | 25 | StringPrototypeCodePointAt, | |
| 25 | 26 | StringPrototypeEndsWith, | |
@@ -172,6 +173,7 @@ function InterfaceConstructor(input, output, completer, terminal) { | |||
| 172 | 173 | let crlfDelay; | |
| 173 | 174 | let prompt = '> '; | |
| 174 | 175 | let signal; | |
| 176 | + let historyOptions; | ||
| 175 | 177 | ||
| 176 | 178 | if (input?.input) { | |
| 177 | 179 | // An options object was given | |
@@ -210,12 +212,15 @@ function InterfaceConstructor(input, output, completer, terminal) { | |||
| 210 | 212 | crlfDelay = input.crlfDelay; | |
| 211 | 213 | input = input.input; | |
| 212 | 214 | ||
| 213 | - input.size = historySize; | ||
| 214 | - input.history = history; | ||
| 215 | - input.removeHistoryDuplicates = removeHistoryDuplicates; | ||
| 215 | + historyOptions = { | ||
| 216 | + __proto__: null, | ||
| 217 | + size: historySize, | ||
| 218 | + history, | ||
| 219 | + removeHistoryDuplicates, | ||
| 220 | + }; | ||
| 216 | 221 | } | |
| 217 | 222 | ||
| 218 | - this.setupHistoryManager(input); | ||
| 223 | + this.setupHistoryManager(historyOptions ?? input); | ||
| 219 | 224 | ||
| 220 | 225 | if (completer !== undefined && typeof completer !== 'function') { | |
| 221 | 226 | throw new ERR_INVALID_ARG_VALUE('completer', completer); | |
@@ -358,6 +363,30 @@ function InterfaceConstructor(input, output, completer, terminal) { | |||
| 358 | 363 | ObjectSetPrototypeOf(InterfaceConstructor.prototype, EventEmitter.prototype); | |
| 359 | 364 | ObjectSetPrototypeOf(InterfaceConstructor, EventEmitter); | |
| 360 | 365 | ||
| 366 | + // Shared descriptors for the history accessors defined on each instance. | ||
| 367 | + // Hoisted to avoid allocating fresh closures on every construction. | ||
| 368 | + const kHistoryAccessorDescriptors = { | ||
| 369 | + __proto__: null, | ||
| 370 | + history: { | ||
| 371 | + __proto__: null, configurable: true, enumerable: true, | ||
| 372 | + get() { return this.historyManager.history; }, | ||
| 373 | + set(newHistory) { return this.historyManager.history = newHistory; }, | ||
| 374 | + }, | ||
| 375 | + historyIndex: { | ||
| 376 | + __proto__: null, configurable: true, enumerable: true, | ||
| 377 | + get() { return this.historyManager.index; }, | ||
| 378 | + set(historyIndex) { return this.historyManager.index = historyIndex; }, | ||
| 379 | + }, | ||
| 380 | + historySize: { | ||
| 381 | + __proto__: null, configurable: true, enumerable: true, | ||
| 382 | + get() { return this.historyManager.size; }, | ||
| 383 | + }, | ||
| 384 | + isFlushing: { | ||
| 385 | + __proto__: null, configurable: true, enumerable: true, | ||
| 386 | + get() { return this.historyManager.isFlushing; }, | ||
| 387 | + }, | ||
| 388 | + }; | ||
| 389 | + | ||
| 361 | 390 | class Interface extends InterfaceConstructor { | |
| 362 | 391 | get columns() { | |
| 363 | 392 | if (this.output?.columns) return this.output.columns; | |
@@ -388,27 +417,7 @@ class Interface extends InterfaceConstructor { | |||
| 388 | 417 | this.historyManager.initialize(options.onHistoryFileLoaded); | |
| 389 | 418 | } | |
| 390 | 419 | ||
| 391 | - ObjectDefineProperty(this, 'history', { | ||
| 392 | - __proto__: null, configurable: true, enumerable: true, | ||
| 393 | - get() { return this.historyManager.history; }, | ||
| 394 | - set(newHistory) { return this.historyManager.history = newHistory; }, | ||
| 395 | - }); | ||
| 396 | - | ||
| 397 | - ObjectDefineProperty(this, 'historyIndex', { | ||
| 398 | - __proto__: null, configurable: true, enumerable: true, | ||
| 399 | - get() { return this.historyManager.index; }, | ||
| 400 | - set(historyIndex) { return this.historyManager.index = historyIndex; }, | ||
| 401 | - }); | ||
| 402 | - | ||
| 403 | - ObjectDefineProperty(this, 'historySize', { | ||
| 404 | - __proto__: null, configurable: true, enumerable: true, | ||
| 405 | - get() { return this.historyManager.size; }, | ||
| 406 | - }); | ||
| 407 | - | ||
| 408 | - ObjectDefineProperty(this, 'isFlushing', { | ||
| 409 | - __proto__: null, configurable: true, enumerable: true, | ||
| 410 | - get() { return this.historyManager.isFlushing; }, | ||
| 411 | - }); | ||
| 420 | + ObjectDefineProperties(this, kHistoryAccessorDescriptors); | ||
| 412 | 421 | } | |
| 413 | 422 | ||
| 414 | 423 | [kSetRawMode](mode) { | |
@@ -622,37 +631,44 @@ class Interface extends InterfaceConstructor { | |||
| 622 | 631 | this[kSawReturnAt] = 0; | |
| 623 | 632 | } | |
| 624 | 633 | ||
| 625 | - // Run test() on the new string chunk, not on the entire line buffer. | ||
| 626 | - let newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); | ||
| 627 | - if (newPartContainsEnding !== null) { | ||
| 628 | - if (this[kLine_buffer]) { | ||
| 629 | - string = this[kLine_buffer] + string; | ||
| 630 | - this[kLine_buffer] = null; | ||
| 631 | - lineEnding.lastIndex = 0; // Start the search from the beginning of the string. | ||
| 632 | - newPartContainsEnding = RegExpPrototypeExec(lineEnding, string); | ||
| 633 | - } | ||
| 634 | - this[kSawReturnAt] = StringPrototypeEndsWith(string, '\r') ? | ||
| 635 | - DateNow() : | ||
| 636 | - 0; | ||
| 637 | - | ||
| 638 | - const indexes = [0, newPartContainsEnding.index, lineEnding.lastIndex]; | ||
| 639 | - let nextMatch; | ||
| 640 | - while ((nextMatch = RegExpPrototypeExec(lineEnding, string)) !== null) { | ||
| 641 | - ArrayPrototypePush(indexes, nextMatch.index, lineEnding.lastIndex); | ||
| 642 | - } | ||
| 643 | - const lastIndex = indexes.length - 1; | ||
| 644 | - // Either '' or (conceivably) the unfinished portion of the next line | ||
| 645 | - this[kLine_buffer] = StringPrototypeSlice(string, indexes[lastIndex]); | ||
| 646 | - for (let i = 1; i < lastIndex; i += 2) { | ||
| 647 | - this[kOnLine](StringPrototypeSlice(string, indexes[i - 1], indexes[i])); | ||
| 648 | - } | ||
| 649 | - } else if (string) { | ||
| 650 | - // No newlines this time, save what we have for next time | ||
| 634 | + if (!string) { | ||
| 635 | + return; | ||
| 636 | + } | ||
| 637 | + | ||
| 638 | + // Split the new string chunk, not the entire line buffer: a single | ||
| 639 | + // split pass avoids allocating a match object per line ending. | ||
| 640 | + // When the chunk contains none of the rare line endings, a plain | ||
| 641 | + // string split is much cheaper than the regular expression. | ||
| 642 | + const lines = | ||
| 643 | + StringPrototypeIncludes(string, '\r') || | ||
| 644 | + StringPrototypeIncludes(string, '\u2028') || | ||
| 645 | + StringPrototypeIncludes(string, '\u2029') ? | ||
| 646 | + RegExpPrototypeSymbolSplit(lineEnding, string) : | ||
| 647 | + StringPrototypeSplit(string, '\n'); | ||
| 648 | + const lastIndex = lines.length - 1; | ||
| 649 | + if (lastIndex === 0) { | ||
| 650 | + // No line endings this time, save what we have for next time. | ||
| 651 | 651 | if (this[kLine_buffer]) { | |
| 652 | 652 | this[kLine_buffer] += string; | |
| 653 | 653 | } else { | |
| 654 | 654 | this[kLine_buffer] = string; | |
| 655 | 655 | } | |
| 656 | + return; | ||
| 657 | + } | ||
| 658 | + | ||
| 659 | + this[kSawReturnAt] = StringPrototypeEndsWith(string, '\r') ? | ||
| 660 | + DateNow() : | ||
| 661 | + 0; | ||
| 662 | + | ||
| 663 | + let first = lines[0]; | ||
| 664 | + if (this[kLine_buffer]) { | ||
| 665 | + first = this[kLine_buffer] + first; | ||
| 666 | + } | ||
| 667 | + // Either '' or (conceivably) the unfinished portion of the next line | ||
| 668 | + this[kLine_buffer] = lines[lastIndex]; | ||
| 669 | + this[kOnLine](first); | ||
| 670 | + for (let i = 1; i < lastIndex; i++) { | ||
| 671 | + this[kOnLine](lines[i]); | ||
| 656 | 672 | } | |
| 657 | 673 | } | |
| 658 | 674 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -115,7 +115,9 @@ function Interface(input, output, completer, terminal) { | |||
| 115 | 115 | FunctionPrototypeCall(InterfaceConstructor, this, | |
| 116 | 116 | input, output, completer, terminal); | |
| 117 | 117 | ||
| 118 | - if (process.env.TERM === 'dumb') { | ||
| 118 | + // Reading process.env is expensive and _ttyWrite is only used in | ||
| 119 | + // terminal mode, so only check for a dumb terminal when relevant. | ||
| 120 | + if (this.terminal && process.env.TERM === 'dumb') { | ||
| 119 | 121 | this._ttyWrite = FunctionPrototypeBind(_ttyWriteDumb, this); | |
| 120 | 122 | } | |
| 121 | 123 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments