@@ -27,6 +27,7 @@ var logger = require( 'debug' );
27
27
var Parser = require ( 'acorn' ) . Parser ;
28
28
var parseLoose = require ( 'acorn-loose' ) . parse ;
29
29
var setNonEnumerableReadOnly = require ( '@stdlib/utils/define-nonenumerable-read-only-property' ) ;
30
+ var startsWith = require ( '@stdlib/string/starts-with' ) ;
30
31
var copy = require ( '@stdlib/array/base/copy' ) ;
31
32
var min = require ( '@stdlib/math/base/special/min' ) ;
32
33
var max = require ( '@stdlib/math/base/special/max' ) ;
@@ -83,6 +84,12 @@ function MultilineHandler( repl, ttyWrite ) {
83
84
// Cache a reference to the command queue:
84
85
this . _queue = repl . _queue ;
85
86
87
+ // Initialize an internal object for command history:
88
+ this . _history = { } ;
89
+ this . _history . list = repl . _history ;
90
+ this . _history . index = 0 ; // index points to the next "previous" command in history
91
+ this . _history . prefix = '' ;
92
+
86
93
// Initialize an internal status object for multi-line mode:
87
94
this . _multiline = { } ;
88
95
this . _multiline . active = false ;
@@ -155,6 +162,95 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_moveCursor', function mo
155
162
this . _rli . cursor = x ;
156
163
} ) ;
157
164
165
+ /**
166
+ * Inserts a command in the input prompt.
167
+ *
168
+ * @private
169
+ * @name _insertCommand
170
+ * @memberof MultilineHandler.prototype
171
+ * @type {Function }
172
+ * @param {string } cmd - command
173
+ * @returns {void }
174
+ */
175
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_insertCommand' , function insertCommand ( cmd ) {
176
+ var i ;
177
+
178
+ this . clearInput ( ) ;
179
+
180
+ // For each newline, trigger a `return` keypress in paste-mode...
181
+ cmd = cmd . split ( '\n' ) ;
182
+ this . _multiline . pasteMode = true ;
183
+ for ( i = 0 ; i < cmd . length - 1 ; i ++ ) {
184
+ this . _rli . write ( cmd [ i ] ) ;
185
+ this . _rli . write ( null , {
186
+ 'name' : 'return'
187
+ } ) ;
188
+ }
189
+ this . _rli . write ( cmd [ cmd . length - 1 ] ) ;
190
+ this . _multiline . pasteMode = false ;
191
+ } ) ;
192
+
193
+ /**
194
+ * Inserts previous command matching the prefix from history.
195
+ *
196
+ * @private
197
+ * @name _prevCommand
198
+ * @memberof MultilineHandler.prototype
199
+ * @type {Function }
200
+ * @returns {void }
201
+ */
202
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_prevCommand' , function prevCommand ( ) {
203
+ var cmd ;
204
+
205
+ // If we are starting from zero, save the prefix for this cycle...
206
+ if ( this . _history . index === 0 ) {
207
+ this . _history . prefix = this . _rli . line . slice ( 0 , this . _rli . cursor ) ;
208
+ }
209
+ // Traverse the history until we find the command with a common prefix...
210
+ while ( this . _history . index < this . _history . list . length / 3 ) {
211
+ cmd = this . _history . list [ this . _history . list . length - ( 3 * this . _history . index ) - 2 ] ; // eslint-disable-line max-len
212
+ if ( startsWith ( cmd , this . _history . prefix ) ) {
213
+ this . _insertCommand ( cmd ) ;
214
+ this . _history . index += 1 ; // update index to point to the next "previous" command
215
+ break ;
216
+ }
217
+ this . _history . index += 1 ;
218
+ }
219
+ } ) ;
220
+
221
+ /**
222
+ * Inserts next command matching the prefix from history.
223
+ *
224
+ * @private
225
+ * @name _nextCommand
226
+ * @memberof MultilineHandler.prototype
227
+ * @type {Function }
228
+ * @returns {void }
229
+ */
230
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_nextCommand' , function nextCommand ( ) {
231
+ var cmd ;
232
+
233
+ if ( this . _history . index === 0 ) {
234
+ return ; // no more history to traverse
235
+ }
236
+ // Traverse the history until we find the command with a common prefix...
237
+ this . _history . index -= 1 ; // updating index to point to the next "previous" command
238
+ while ( this . _history . index > 0 ) {
239
+ cmd = this . _history . list [ this . _history . list . length - ( 3 * ( this . _history . index - 1 ) ) - 2 ] ; // eslint-disable-line max-len
240
+ if ( startsWith ( cmd , this . _history . prefix ) ) {
241
+ this . _insertCommand ( cmd ) ;
242
+ break ;
243
+ }
244
+ this . _history . index -= 1 ;
245
+ }
246
+ // If we didn't find a match in history, bring up the original prefix and reset cycle...
247
+ if ( this . _history . index === 0 ) {
248
+ this . clearInput ( ) ;
249
+ this . _rli . write ( this . _history . prefix ) ;
250
+ this . _resetHistoryBuffers ( ) ;
251
+ }
252
+ } ) ;
253
+
158
254
/**
159
255
* Moves cursor up to the previous line.
160
256
*
@@ -167,8 +263,9 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_moveCursor', function mo
167
263
setNonEnumerableReadOnly ( MultilineHandler . prototype , '_moveUp' , function moveUp ( ) {
168
264
var cursor ;
169
265
170
- // If already at the first line, ignore ...
266
+ // If already at the first line, try to insert previous command from history ...
171
267
if ( this . _lineIndex <= 0 ) {
268
+ this . _prevCommand ( ) ;
172
269
return ;
173
270
}
174
271
this . _cmd [ this . _lineIndex ] = this . _rli . line ; // update current line in command
@@ -190,8 +287,9 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_moveUp', function moveUp
190
287
setNonEnumerableReadOnly ( MultilineHandler . prototype , '_moveDown' , function moveDown ( ) {
191
288
var cursor ;
192
289
193
- // If already at the last line, ignore ...
290
+ // If already at the last line, try to insert next command from history ...
194
291
if ( this . _lineIndex >= this . _lines . length - 1 ) {
292
+ this . _nextCommand ( ) ;
195
293
return ;
196
294
}
197
295
this . _cmd [ this . _lineIndex ] = this . _rli . line ; // update current line in command
@@ -347,9 +445,37 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_isMultilineInput', funct
347
445
} ) ;
348
446
349
447
/**
350
- * Returns current line number in input.
448
+ * Resets input buffers.
449
+ *
450
+ * @private
451
+ * @name _resetInputBuffers
452
+ * @memberof MultilineHandler.prototype
453
+ * @type {Function }
454
+ * @returns {void }
455
+ */
456
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_resetInputBuffers' , function resetInputBuffers ( ) {
457
+ this . _cmd . length = 0 ;
458
+ this . _lineIndex = 0 ;
459
+ this . _lines . length = 0 ;
460
+ } ) ;
461
+
462
+ /**
463
+ * Resets history buffers.
351
464
*
352
465
* @private
466
+ * @name _resetHistoryBuffers
467
+ * @memberof MultilineHandler.prototype
468
+ * @type {Function }
469
+ * @returns {void }
470
+ */
471
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_resetHistoryBuffers' , function resetHistoryBuffers ( ) {
472
+ this . _history . index = 0 ;
473
+ this . _history . prefix = '' ;
474
+ } ) ;
475
+
476
+ /**
477
+ * Returns current line number in input.
478
+ *
353
479
* @name lineIndex
354
480
* @memberof MultilineHandler.prototype
355
481
* @type {Function }
@@ -362,7 +488,6 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'lineIndex', function line
362
488
/**
363
489
* Returns the number of rows occupied by current input.
364
490
*
365
- * @private
366
491
* @name inputHeight
367
492
* @memberof MultilineHandler.prototype
368
493
* @type {Function }
@@ -385,19 +510,39 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'updateLine', function upd
385
510
this . _lines [ this . _lineIndex ] = line ;
386
511
} ) ;
387
512
513
+ /**
514
+ * Clears current input.
515
+ *
516
+ * @name clearInput
517
+ * @memberof MultilineHandler.prototype
518
+ * @type {Function }
519
+ * @returns {void }
520
+ */
521
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , 'clearInput' , function clearInput ( ) {
522
+ if ( this . _lineIndex !== 0 ) {
523
+ // Bring the cursor to the first line:
524
+ readline . moveCursor ( this . _ostream , 0 , - 1 * this . _lineIndex ) ;
525
+ }
526
+ // Clear lines and buffers:
527
+ this . _resetInputBuffers ( ) ;
528
+ readline . cursorTo ( this . _ostream , this . _repl . promptLength ( ) ) ;
529
+ readline . clearLine ( this . _ostream , 1 ) ;
530
+ readline . clearScreenDown ( this . _ostream ) ;
531
+ this . _rli . line = '' ;
532
+ this . _rli . cursor = 0 ;
533
+ } ) ;
534
+
388
535
/**
389
536
* Resets input and command buffers.
390
537
*
391
- * @private
392
538
* @name resetInput
393
539
* @memberof MultilineHandler.prototype
394
540
* @type {Function }
395
541
* @returns {void }
396
542
*/
397
543
setNonEnumerableReadOnly ( MultilineHandler . prototype , 'resetInput' , function resetInput ( ) {
398
- this . _cmd . length = 0 ;
399
- this . _lineIndex = 0 ;
400
- this . _lines . length = 0 ;
544
+ this . _resetHistoryBuffers ( ) ;
545
+ this . _resetInputBuffers ( ) ;
401
546
} ) ;
402
547
403
548
/**
@@ -587,8 +732,9 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'beforeKeypress', function
587
732
this . _ttyWrite . call ( this . _rli , data , key ) ;
588
733
return ;
589
734
}
735
+ switch ( key . name ) {
590
736
// Check whether to trigger multi-line mode or execute the command when `return` key is encountered...
591
- if ( key . name === 'return' ) {
737
+ case 'return' :
592
738
cmd = copy ( this . _cmd ) ;
593
739
cmd [ this . _lineIndex ] = this . _rli . line ;
594
740
@@ -598,53 +744,77 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'beforeKeypress', function
598
744
return ;
599
745
}
600
746
this . _triggerMultiline ( ) ;
601
-
747
+ if ( this . _history . index !== 0 && ! this . _multiline . pasteMode ) {
748
+ // Reset current history cycle:
749
+ this . _resetHistoryBuffers ( ) ;
750
+ }
602
751
// Trigger `line` event:
603
752
this . _ttyWrite . call ( this . _rli , data , key ) ;
604
- return ;
605
- }
606
- if ( ! this . _multiline . active ) {
607
- this . _ttyWrite . call ( this . _rli , data , key ) ;
608
- return ;
609
- }
753
+ break ;
754
+
610
755
// If multi-line mode is active, enable navigation...
611
- switch ( key . name ) {
612
756
case 'up' :
613
757
this . _moveUp ( ) ;
614
- this . _renderLines ( ) ;
758
+ if ( this . _multiline . active ) {
759
+ this . _renderLines ( ) ;
760
+ }
615
761
break ;
616
762
case 'down' :
617
763
this . _moveDown ( ) ;
618
- this . _renderLines ( ) ;
764
+ if ( this . _multiline . active ) {
765
+ this . _renderLines ( ) ;
766
+ }
619
767
break ;
620
768
case 'left' :
769
+ if ( this . _history . index !== 0 ) {
770
+ // Reset current history cycle:
771
+ this . _resetHistoryBuffers ( ) ;
772
+ }
621
773
// If at the beginning of the line, move up to the previous line; otherwise, trigger default behavior...
622
774
if ( this . _rli . cursor === 0 ) {
623
775
this . _moveLeft ( ) ;
624
- this . _renderLines ( ) ;
776
+ if ( this . _multiline . active ) {
777
+ this . _renderLines ( ) ;
778
+ }
625
779
return ;
626
780
}
627
781
this . _ttyWrite . call ( this . _rli , data , key ) ;
628
782
break ;
629
783
case 'right' :
784
+ if ( this . _history . index !== 0 ) {
785
+ // Reset current history cycle:
786
+ this . _resetHistoryBuffers ( ) ;
787
+ }
630
788
// If at the end of the line, move up to the next line; otherwise, trigger default behavior...
631
789
if ( this . _rli . cursor === this . _rli . line . length ) {
632
790
this . _moveRight ( ) ;
633
- this . _renderLines ( ) ;
791
+ if ( this . _multiline . active ) {
792
+ this . _renderLines ( ) ;
793
+ }
634
794
return ;
635
795
}
636
796
this . _ttyWrite . call ( this . _rli , data , key ) ;
637
797
break ;
638
798
case 'backspace' :
799
+ if ( this . _history . index !== 0 ) {
800
+ // Reset current history cycle:
801
+ this . _resetHistoryBuffers ( ) ;
802
+ }
639
803
// If at the beginning of the line, remove and move up to the previous line; otherwise, trigger default behavior...
640
804
if ( this . _rli . cursor === 0 ) {
641
805
this . _backspace ( ) ;
642
- this . _renderLines ( ) ;
806
+ if ( this . _multiline . active ) {
807
+ this . _renderLines ( ) ;
808
+ }
643
809
return ;
644
810
}
645
811
this . _ttyWrite . call ( this . _rli , data , key ) ;
646
812
break ;
647
813
default :
814
+ if ( this . _history . index !== 0 ) {
815
+ // Reset current history cycle:
816
+ this . _resetHistoryBuffers ( ) ;
817
+ }
648
818
this . _ttyWrite . call ( this . _rli , data , key ) ;
649
819
break ;
650
820
}
0 commit comments