@@ -223,6 +223,7 @@ function REPLServer(prompt,
223223 self . underscoreAssigned = false ;
224224 self . last = undefined ;
225225 self . breakEvalOnSigint = ! ! breakEvalOnSigint ;
226+ self . editorMode = false ;
226227
227228 self . _inTemplateLiteral = false ;
228229
@@ -394,7 +395,12 @@ function REPLServer(prompt,
394395 // Figure out which "complete" function to use.
395396 self . completer = ( typeof options . completer === 'function' )
396397 ? options . completer
397- : complete ;
398+ : completer ;
399+
400+ function completer ( text , cb ) {
401+ complete . call ( self , text , self . editorMode
402+ ? self . completeOnEditorMode ( cb ) : cb ) ;
403+ }
398404
399405 Interface . call ( this , {
400406 input : self . inputStream ,
@@ -428,9 +434,11 @@ function REPLServer(prompt,
428434 } ) ;
429435
430436 var sawSIGINT = false ;
437+ var sawCtrlD = false ;
431438 self . on ( 'SIGINT' , function ( ) {
432439 var empty = self . line . length === 0 ;
433440 self . clearLine ( ) ;
441+ self . turnOffEditorMode ( ) ;
434442
435443 if ( ! ( self . bufferedCommand && self . bufferedCommand . length > 0 ) && empty ) {
436444 if ( sawSIGINT ) {
@@ -454,6 +462,11 @@ function REPLServer(prompt,
454462 debug ( 'line %j' , cmd ) ;
455463 sawSIGINT = false ;
456464
465+ if ( self . editorMode ) {
466+ self . bufferedCommand += cmd + '\n' ;
467+ return ;
468+ }
469+
457470 // leading whitespaces in template literals should not be trimmed.
458471 if ( self . _inTemplateLiteral ) {
459472 self . _inTemplateLiteral = false ;
@@ -499,7 +512,8 @@ function REPLServer(prompt,
499512
500513 // If error was SyntaxError and not JSON.parse error
501514 if ( e ) {
502- if ( e instanceof Recoverable && ! self . lineParser . shouldFail ) {
515+ if ( e instanceof Recoverable && ! self . lineParser . shouldFail &&
516+ ! sawCtrlD ) {
503517 // Start buffering data like that:
504518 // {
505519 // ... x: 1
@@ -515,6 +529,7 @@ function REPLServer(prompt,
515529 // Clear buffer if no SyntaxErrors
516530 self . lineParser . reset ( ) ;
517531 self . bufferedCommand = '' ;
532+ sawCtrlD = false ;
518533
519534 // If we got any output - print it (if no error)
520535 if ( ! e &&
@@ -555,9 +570,55 @@ function REPLServer(prompt,
555570 } ) ;
556571
557572 self . on ( 'SIGCONT' , function ( ) {
558- self . displayPrompt ( true ) ;
573+ if ( self . editorMode ) {
574+ self . outputStream . write ( `${ self . _initialPrompt } .editor\n` ) ;
575+ self . outputStream . write (
576+ '// Entering editor mode (^D to finish, ^C to cancel)\n' ) ;
577+ self . outputStream . write ( `${ self . bufferedCommand } \n` ) ;
578+ self . prompt ( true ) ;
579+ } else {
580+ self . displayPrompt ( true ) ;
581+ }
559582 } ) ;
560583
584+ // Wrap readline tty to enable editor mode
585+ const ttyWrite = self . _ttyWrite . bind ( self ) ;
586+ self . _ttyWrite = ( d , key ) => {
587+ if ( ! self . editorMode || ! self . terminal ) {
588+ ttyWrite ( d , key ) ;
589+ return ;
590+ }
591+
592+ // editor mode
593+ if ( key . ctrl && ! key . shift ) {
594+ switch ( key . name ) {
595+ case 'd' : // End editor mode
596+ self . turnOffEditorMode ( ) ;
597+ sawCtrlD = true ;
598+ ttyWrite ( d , { name : 'return' } ) ;
599+ break ;
600+ case 'n' : // Override next history item
601+ case 'p' : // Override previous history item
602+ break ;
603+ default :
604+ ttyWrite ( d , key ) ;
605+ }
606+ } else {
607+ switch ( key . name ) {
608+ case 'up' : // Override previous history item
609+ case 'down' : // Override next history item
610+ break ;
611+ case 'tab' :
612+ // prevent double tab behavior
613+ self . _previousKey = null ;
614+ ttyWrite ( d , key ) ;
615+ break ;
616+ default :
617+ ttyWrite ( d , key ) ;
618+ }
619+ }
620+ } ;
621+
561622 self . displayPrompt ( ) ;
562623}
563624inherits ( REPLServer , Interface ) ;
@@ -680,6 +741,12 @@ REPLServer.prototype.setPrompt = function setPrompt(prompt) {
680741 REPLServer . super_ . prototype . setPrompt . call ( this , prompt ) ;
681742} ;
682743
744+ REPLServer . prototype . turnOffEditorMode = function ( ) {
745+ this . editorMode = false ;
746+ this . setPrompt ( this . _initialPrompt ) ;
747+ } ;
748+
749+
683750// A stream to push an array into a REPL
684751// used in REPLServer.complete
685752function ArrayStream ( ) {
@@ -987,6 +1054,39 @@ function complete(line, callback) {
9871054 }
9881055}
9891056
1057+ function longestCommonPrefix ( arr = [ ] ) {
1058+ const cnt = arr . length ;
1059+ if ( cnt === 0 ) return '' ;
1060+ if ( cnt === 1 ) return arr [ 0 ] ;
1061+
1062+ const first = arr [ 0 ] ;
1063+ // complexity: O(m * n)
1064+ for ( let m = 0 ; m < first . length ; m ++ ) {
1065+ const c = first [ m ] ;
1066+ for ( let n = 1 ; n < cnt ; n ++ ) {
1067+ const entry = arr [ n ] ;
1068+ if ( m >= entry . length || c !== entry [ m ] ) {
1069+ return first . substring ( 0 , m ) ;
1070+ }
1071+ }
1072+ }
1073+ return first ;
1074+ }
1075+
1076+ REPLServer . prototype . completeOnEditorMode = ( callback ) => ( err , results ) => {
1077+ if ( err ) return callback ( err ) ;
1078+
1079+ const [ completions , completeOn = '' ] = results ;
1080+ const prefixLength = completeOn . length ;
1081+
1082+ if ( prefixLength === 0 ) return callback ( null , [ [ ] , completeOn ] ) ;
1083+
1084+ const isNotEmpty = ( v ) => v . length > 0 ;
1085+ const trimCompleteOnPrefix = ( v ) => v . substring ( prefixLength ) ;
1086+ const data = completions . filter ( isNotEmpty ) . map ( trimCompleteOnPrefix ) ;
1087+
1088+ callback ( null , [ [ `${ completeOn } ${ longestCommonPrefix ( data ) } ` ] , completeOn ] ) ;
1089+ } ;
9901090
9911091/**
9921092 * Used to parse and execute the Node REPL commands.
@@ -1189,6 +1289,17 @@ function defineDefaultCommands(repl) {
11891289 this . displayPrompt ( ) ;
11901290 }
11911291 } ) ;
1292+
1293+ repl . defineCommand ( 'editor' , {
1294+ help : 'Entering editor mode (^D to finish, ^C to cancel)' ,
1295+ action ( ) {
1296+ if ( ! this . terminal ) return ;
1297+ this . editorMode = true ;
1298+ REPLServer . super_ . prototype . setPrompt . call ( this , '' ) ;
1299+ this . outputStream . write (
1300+ '// Entering editor mode (^D to finish, ^C to cancel)\n' ) ;
1301+ }
1302+ } ) ;
11921303}
11931304
11941305function regexpEscape ( s ) {
0 commit comments