@@ -223,6 +223,7 @@ function REPLServer(prompt,
223
223
self . underscoreAssigned = false ;
224
224
self . last = undefined ;
225
225
self . breakEvalOnSigint = ! ! breakEvalOnSigint ;
226
+ self . editorMode = false ;
226
227
227
228
self . _inTemplateLiteral = false ;
228
229
@@ -394,7 +395,12 @@ function REPLServer(prompt,
394
395
// Figure out which "complete" function to use.
395
396
self . completer = ( typeof options . completer === 'function' )
396
397
? 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
+ }
398
404
399
405
Interface . call ( this , {
400
406
input : self . inputStream ,
@@ -428,9 +434,11 @@ function REPLServer(prompt,
428
434
} ) ;
429
435
430
436
var sawSIGINT = false ;
437
+ var sawCtrlD = false ;
431
438
self . on ( 'SIGINT' , function ( ) {
432
439
var empty = self . line . length === 0 ;
433
440
self . clearLine ( ) ;
441
+ self . turnOffEditorMode ( ) ;
434
442
435
443
if ( ! ( self . bufferedCommand && self . bufferedCommand . length > 0 ) && empty ) {
436
444
if ( sawSIGINT ) {
@@ -454,6 +462,11 @@ function REPLServer(prompt,
454
462
debug ( 'line %j' , cmd ) ;
455
463
sawSIGINT = false ;
456
464
465
+ if ( self . editorMode ) {
466
+ self . bufferedCommand += cmd + '\n' ;
467
+ return ;
468
+ }
469
+
457
470
// leading whitespaces in template literals should not be trimmed.
458
471
if ( self . _inTemplateLiteral ) {
459
472
self . _inTemplateLiteral = false ;
@@ -499,7 +512,8 @@ function REPLServer(prompt,
499
512
500
513
// If error was SyntaxError and not JSON.parse error
501
514
if ( e ) {
502
- if ( e instanceof Recoverable && ! self . lineParser . shouldFail ) {
515
+ if ( e instanceof Recoverable && ! self . lineParser . shouldFail &&
516
+ ! sawCtrlD ) {
503
517
// Start buffering data like that:
504
518
// {
505
519
// ... x: 1
@@ -515,6 +529,7 @@ function REPLServer(prompt,
515
529
// Clear buffer if no SyntaxErrors
516
530
self . lineParser . reset ( ) ;
517
531
self . bufferedCommand = '' ;
532
+ sawCtrlD = false ;
518
533
519
534
// If we got any output - print it (if no error)
520
535
if ( ! e &&
@@ -555,9 +570,55 @@ function REPLServer(prompt,
555
570
} ) ;
556
571
557
572
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
+ }
559
582
} ) ;
560
583
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
+
561
622
self . displayPrompt ( ) ;
562
623
}
563
624
inherits ( REPLServer , Interface ) ;
@@ -680,6 +741,12 @@ REPLServer.prototype.setPrompt = function setPrompt(prompt) {
680
741
REPLServer . super_ . prototype . setPrompt . call ( this , prompt ) ;
681
742
} ;
682
743
744
+ REPLServer . prototype . turnOffEditorMode = function ( ) {
745
+ this . editorMode = false ;
746
+ this . setPrompt ( this . _initialPrompt ) ;
747
+ } ;
748
+
749
+
683
750
// A stream to push an array into a REPL
684
751
// used in REPLServer.complete
685
752
function ArrayStream ( ) {
@@ -987,6 +1054,39 @@ function complete(line, callback) {
987
1054
}
988
1055
}
989
1056
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
+ } ;
990
1090
991
1091
/**
992
1092
* Used to parse and execute the Node REPL commands.
@@ -1189,6 +1289,17 @@ function defineDefaultCommands(repl) {
1189
1289
this . displayPrompt ( ) ;
1190
1290
}
1191
1291
} ) ;
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
+ } ) ;
1192
1303
}
1193
1304
1194
1305
function regexpEscape ( s ) {
0 commit comments