@@ -17,9 +17,10 @@ const {
1717 MathMax,
1818 MathMaxApply,
1919 NumberIsFinite,
20- ObjectDefineProperty ,
20+ ObjectDefineProperties ,
2121 ObjectSetPrototypeOf,
2222 RegExpPrototypeExec,
23+ RegExpPrototypeSymbolSplit,
2324 SafeStringIterator,
2425 StringPrototypeCodePointAt,
2526 StringPrototypeEndsWith,
@@ -172,6 +173,7 @@ function InterfaceConstructor(input, output, completer, terminal) {
172173 let crlfDelay ;
173174 let prompt = '> ' ;
174175 let signal ;
176+ let historyOptions ;
175177
176178 if ( input ?. input ) {
177179 // An options object was given
@@ -210,12 +212,15 @@ function InterfaceConstructor(input, output, completer, terminal) {
210212 crlfDelay = input . crlfDelay ;
211213 input = input . input ;
212214
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+ } ;
216221 }
217222
218- this . setupHistoryManager ( input ) ;
223+ this . setupHistoryManager ( historyOptions ?? input ) ;
219224
220225 if ( completer !== undefined && typeof completer !== 'function' ) {
221226 throw new ERR_INVALID_ARG_VALUE ( 'completer' , completer ) ;
@@ -358,6 +363,30 @@ function InterfaceConstructor(input, output, completer, terminal) {
358363ObjectSetPrototypeOf ( InterfaceConstructor . prototype , EventEmitter . prototype ) ;
359364ObjectSetPrototypeOf ( InterfaceConstructor , EventEmitter ) ;
360365
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+
361390class Interface extends InterfaceConstructor {
362391 get columns ( ) {
363392 if ( this . output ?. columns ) return this . output . columns ;
@@ -388,27 +417,7 @@ class Interface extends InterfaceConstructor {
388417 this . historyManager . initialize ( options . onHistoryFileLoaded ) ;
389418 }
390419
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 ) ;
412421 }
413422
414423 [ kSetRawMode ] ( mode ) {
@@ -622,37 +631,44 @@ class Interface extends InterfaceConstructor {
622631 this [ kSawReturnAt ] = 0 ;
623632 }
624633
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.
651651 if ( this [ kLine_buffer ] ) {
652652 this [ kLine_buffer ] += string ;
653653 } else {
654654 this [ kLine_buffer ] = string ;
655655 }
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 ] ) ;
656672 }
657673 }
658674
0 commit comments