@@ -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 copy = require ( '@stdlib/array/base/copy' ) ;
30
31
var min = require ( '@stdlib/math/base/special/min' ) ;
31
32
var displayPrompt = require ( './display_prompt.js' ) ;
32
33
var drain = require ( './drain.js' ) ;
@@ -84,10 +85,10 @@ function MultilineHandler( repl, ttyWrite ) {
84
85
// Cache the length of the input prompt:
85
86
this . _promptLength = repl . _inputPrompt . length ;
86
87
87
- // Initialize an internal status object for multi-line mode:
88
+ // Initialize an internal status object for multiline mode:
88
89
this . _multiline = { } ;
89
90
this . _multiline . active = false ;
90
- this . _multiline . mode = 'incomplete_expression' ;
91
+ this . _multiline . trigger = false ;
91
92
92
93
// Initialize a buffer for caching input lines:
93
94
this . _lines = [ ] ;
@@ -312,6 +313,74 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_resetInput', function re
312
313
this . _lines . length = 0 ;
313
314
} ) ;
314
315
316
+ /**
317
+ * Updates flags and buffers before to trigger multiline mode.
318
+ *
319
+ * @private
320
+ * @name _triggerMultiline
321
+ * @memberof MultilineHandler.prototype
322
+ * @type {Function }
323
+ * @returns {void }
324
+ */
325
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_triggerMultiline' , function triggerMultiline ( ) {
326
+ // Update flag:
327
+ this . _multiline . trigger = true ;
328
+
329
+ // Save expression after cursor in buffer:
330
+ readline . clearLine ( this . _ostream , 1 ) ; // clear line after cursor
331
+ this . _remainingLine = this . _rli . line . substring ( this . _rli . cursor ) ;
332
+ this . _rli . line = this . _rli . line . substring ( 0 , this . _rli . cursor ) ;
333
+ } ) ;
334
+
335
+ /**
336
+ * Checks if the command is incomplete and a multiline input.
337
+ *
338
+ * @private
339
+ * @name _isMultilineInput
340
+ * @memberof MultilineHandler.prototype
341
+ * @type {Function }
342
+ * @param {string } cmd - command
343
+ * @returns {boolean } boolean indicating whether the command is a multiline input
344
+ */
345
+ setNonEnumerableReadOnly ( MultilineHandler . prototype , '_isMultilineInput' , function isMultilineInput ( cmd ) {
346
+ var node ;
347
+ var tmp ;
348
+ var ast ;
349
+
350
+ debug ( 'Attempting to detect multi-line input...' ) ;
351
+ if ( RE_WHITESPACE . test ( cmd ) ) {
352
+ debug ( 'Detected multi-line input. Triggering multiline mode...' ) ;
353
+ return true ;
354
+ }
355
+ if ( RE_SINGLE_LINE_COMMENT . test ( cmd ) || RE_MULTI_LINE_COMMENT . test ( cmd ) ) { // eslint-disable-line max-len
356
+ debug ( 'Multi-line input not detected.' ) ;
357
+ return false ;
358
+ }
359
+ // Check if the command has valid syntax...
360
+ tmp = processCommand ( cmd ) ;
361
+ if ( ! ( tmp instanceof Error ) ) {
362
+ return false ;
363
+ }
364
+ if ( hasMultilineError ( cmd , AOPTS ) ) {
365
+ debug ( 'Detected multi-line input. Triggering multiline mode...' ) ;
366
+ return true ;
367
+ }
368
+ // Still possible that a user is attempting to enter an object literal across multiple lines...
369
+ ast = parseLoose ( cmd , AOPTS ) ;
370
+
371
+ // Check for a trailing node which is being interpreted as a block statement, as this could be an object literal...
372
+ node = ast . body [ ast . body . length - 1 ] ;
373
+ if ( node . type === 'BlockStatement' && node . end === ast . end ) {
374
+ tmp = cmd . slice ( node . start , node . end ) ;
375
+ if ( hasMultilineError ( tmp , AOPTS ) ) {
376
+ debug ( 'Detected multi-line input. Triggering multiline mode...' ) ;
377
+ return true ;
378
+ }
379
+ }
380
+ debug ( 'Multi-line input not detected.' ) ;
381
+ return false ;
382
+ } ) ;
383
+
315
384
/**
316
385
* Updates current input line in buffer.
317
386
*
@@ -336,20 +405,17 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'updateLine', function upd
336
405
*/
337
406
setNonEnumerableReadOnly ( MultilineHandler . prototype , 'processLine' , function processLine ( line ) {
338
407
var code ;
339
- var node ;
340
- var ast ;
341
408
var cmd ;
342
409
var tmp ;
343
410
var dy ;
344
411
412
+ // Save line:
345
413
debug ( 'Line: %s' , line ) ;
414
+ this . _cmd [ this . _lineIndex ] = line ;
346
415
347
- // Check for manual newline input...
348
- if ( this . _multiline . active && this . _multiline . mode === 'manual' ) {
349
- debug ( 'Detected newline input via modifier-key. Waiting for additional lines...' ) ;
350
-
351
- // Update current line:
352
- this . _cmd [ this . _lineIndex ] = line ;
416
+ // Check for multiline triggers...
417
+ if ( this . _multiline . trigger ) {
418
+ debug ( 'Detected multiline trigger input. Waiting for additional lines...' ) ;
353
419
354
420
// Insert a newline:
355
421
this . _lineIndex += 1 ;
@@ -362,18 +428,14 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'processLine', function pr
362
428
readline . cursorTo ( this . _ostream , 0 ) ;
363
429
this . _rli . line = this . _remainingLine ;
364
430
365
- // Clear buffer :
431
+ // Update flags and buffers :
366
432
this . _remainingLine = '' ;
367
- this . _multiline . mode = 'incomplete_expression' ; // reset flag
433
+ this . _multiline . trigger = false ;
434
+ this . _multiline . active = true ;
368
435
return ;
369
436
}
370
437
this . _multiline . active = false ; // false until proven otherwise
371
- this . _cmd [ this . _lineIndex ] = line ;
372
438
cmd = this . _cmd . join ( '\n' ) ;
373
- if ( RE_WHITESPACE . test ( cmd ) ) {
374
- displayPrompt ( this . _repl , false ) ;
375
- return ;
376
- }
377
439
if ( RE_SINGLE_LINE_COMMENT . test ( cmd ) || RE_MULTI_LINE_COMMENT . test ( cmd ) ) { // eslint-disable-line max-len
378
440
debug ( 'Detected single-line comment.' ) ;
379
441
tmp = cmd ;
@@ -382,45 +444,6 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'processLine', function pr
382
444
debug ( 'Processing command...' ) ;
383
445
tmp = processCommand ( cmd ) ;
384
446
if ( tmp instanceof Error ) {
385
- debug ( 'Unable to process command.' ) ;
386
- debug ( 'Error: %s' , tmp . message ) ;
387
- debug ( 'Attempting to detect multi-line input...' ) ;
388
- if ( hasMultilineError ( cmd , AOPTS ) ) {
389
- debug ( 'Detected multi-line input. Waiting for additional lines...' ) ;
390
- this . _multiline . active = true ;
391
-
392
- // Insert a newline:
393
- this . _lineIndex += 1 ;
394
- this . _cmd . splice ( this . _lineIndex , 0 , '' ) ;
395
- this . _lines . splice ( this . _lineIndex , 0 , '' ) ;
396
-
397
- // Display next prompt:
398
- displayPrompt ( this . _repl , false ) ;
399
- return ;
400
- }
401
- // Still possible that a user is attempting to enter an object literal across multiple lines...
402
- ast = parseLoose ( cmd , AOPTS ) ;
403
-
404
- // Check for a trailing node which is being interpreted as a block statement, as this could be an object literal...
405
- node = ast . body [ ast . body . length - 1 ] ;
406
- if ( node . type === 'BlockStatement' && node . end === ast . end ) {
407
- tmp = cmd . slice ( node . start , node . end ) ;
408
- if ( hasMultilineError ( tmp , AOPTS ) ) {
409
- debug ( 'Detected multi-line input. Waiting for additional lines...' ) ;
410
- this . _multiline . active = true ;
411
-
412
- // Insert a newline:
413
- this . _lineIndex += 1 ;
414
- this . _cmd . splice ( this . _lineIndex , 0 , '' ) ;
415
- this . _lines . splice ( this . _lineIndex , 0 , '' ) ;
416
-
417
- // Display next prompt:
418
- displayPrompt ( this . _repl , false ) ;
419
- return ;
420
- }
421
- }
422
- debug ( 'Multi-line input not detected.' ) ;
423
-
424
447
// Move cursor to the output row:
425
448
dy = this . _lines . length - this . _lineIndex - 1 ;
426
449
readline . moveCursor ( this . _ostream , 0 , dy ) ;
@@ -479,15 +502,8 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'onKeypress', function onK
479
502
return ;
480
503
}
481
504
// Add manual newline when encountering `CTRL+E` keybinding...
482
- if ( key . name === 'e' && key . ctrl ) {
483
- // Update flags:
484
- this . _multiline . active = true ;
485
- this . _multiline . mode = 'manual' ;
486
-
487
- // Save expression after cursor in buffer:
488
- readline . clearLine ( this . _ostream , 1 ) ; // clear line after cursor
489
- this . _remainingLine = this . _rli . line . substring ( this . _rli . cursor ) ;
490
- this . _rli . line = this . _rli . line . substring ( 0 , this . _rli . cursor ) ;
505
+ if ( key . name === 'o' && key . ctrl ) {
506
+ this . _triggerMultiline ( ) ;
491
507
492
508
// Simulate `line` event:
493
509
this . _rli . write ( '\n' ) ;
@@ -509,10 +525,33 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'onKeypress', function onK
509
525
* @returns {void }
510
526
*/
511
527
setNonEnumerableReadOnly ( MultilineHandler . prototype , 'beforeKeypress' , function beforeKeypress ( data , key ) {
512
- if ( ! key || ! this . _multiline . active ) {
528
+ var cmd ;
529
+
530
+ if ( ! key ) {
531
+ this . _ttyWrite . call ( this . _rli , data , key ) ;
532
+ return ;
533
+ }
534
+ // Check whether to trigger multiline mode or execute the command when `return` key is encountered...
535
+ if ( key . name === 'return' ) {
536
+ cmd = copy ( this . _cmd ) ;
537
+ cmd [ this . _lineIndex ] = this . _rli . line ;
538
+
539
+ // If command is incomplete, trigger multiline mode...
540
+ if ( ! this . _isMultilineInput ( cmd . join ( '\n' ) ) ) {
541
+ this . _ttyWrite . call ( this . _rli , data , key ) ;
542
+ return ;
543
+ }
544
+ this . _triggerMultiline ( ) ;
545
+
546
+ // Trigger `line` event:
547
+ this . _ttyWrite . call ( this . _rli , data , key ) ;
548
+ return ;
549
+ }
550
+ if ( ! this . _multiline . active ) {
513
551
this . _ttyWrite . call ( this . _rli , data , key ) ;
514
552
return ;
515
553
}
554
+ // If multiline mode is active, enable navigation...
516
555
switch ( key . name ) {
517
556
case 'up' :
518
557
this . _moveUp ( ) ;
0 commit comments