@@ -27,6 +27,7 @@ var logger = require( 'debug' );
2727var Parser = require ( 'acorn' ) . Parser ;
2828var parseLoose = require ( 'acorn-loose' ) . parse ;
2929var setNonEnumerableReadOnly = require ( '@stdlib/utils/define-nonenumerable-read-only-property' ) ;
30+ var copy = require ( '@stdlib/array/base/copy' ) ;
3031var min = require ( '@stdlib/math/base/special/min' ) ;
3132var displayPrompt = require ( './display_prompt.js' ) ;
3233var drain = require ( './drain.js' ) ;
@@ -84,10 +85,10 @@ function MultilineHandler( repl, ttyWrite ) {
8485 // Cache the length of the input prompt:
8586 this . _promptLength = repl . _inputPrompt . length ;
8687
87- // Initialize an internal status object for multi-line mode:
88+ // Initialize an internal status object for multiline mode:
8889 this . _multiline = { } ;
8990 this . _multiline . active = false ;
90- this . _multiline . mode = 'incomplete_expression' ;
91+ this . _multiline . trigger = false ;
9192
9293 // Initialize a buffer for caching input lines:
9394 this . _lines = [ ] ;
@@ -312,6 +313,74 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, '_resetInput', function re
312313 this . _lines . length = 0 ;
313314} ) ;
314315
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+
315384/**
316385* Updates current input line in buffer.
317386*
@@ -336,20 +405,17 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'updateLine', function upd
336405*/
337406setNonEnumerableReadOnly ( MultilineHandler . prototype , 'processLine' , function processLine ( line ) {
338407 var code ;
339- var node ;
340- var ast ;
341408 var cmd ;
342409 var tmp ;
343410 var dy ;
344411
412+ // Save line:
345413 debug ( 'Line: %s' , line ) ;
414+ this . _cmd [ this . _lineIndex ] = line ;
346415
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...' ) ;
353419
354420 // Insert a newline:
355421 this . _lineIndex += 1 ;
@@ -362,18 +428,14 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'processLine', function pr
362428 readline . cursorTo ( this . _ostream , 0 ) ;
363429 this . _rli . line = this . _remainingLine ;
364430
365- // Clear buffer :
431+ // Update flags and buffers :
366432 this . _remainingLine = '' ;
367- this . _multiline . mode = 'incomplete_expression' ; // reset flag
433+ this . _multiline . trigger = false ;
434+ this . _multiline . active = true ;
368435 return ;
369436 }
370437 this . _multiline . active = false ; // false until proven otherwise
371- this . _cmd [ this . _lineIndex ] = line ;
372438 cmd = this . _cmd . join ( '\n' ) ;
373- if ( RE_WHITESPACE . test ( cmd ) ) {
374- displayPrompt ( this . _repl , false ) ;
375- return ;
376- }
377439 if ( RE_SINGLE_LINE_COMMENT . test ( cmd ) || RE_MULTI_LINE_COMMENT . test ( cmd ) ) { // eslint-disable-line max-len
378440 debug ( 'Detected single-line comment.' ) ;
379441 tmp = cmd ;
@@ -382,45 +444,6 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'processLine', function pr
382444 debug ( 'Processing command...' ) ;
383445 tmp = processCommand ( cmd ) ;
384446 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-
424447 // Move cursor to the output row:
425448 dy = this . _lines . length - this . _lineIndex - 1 ;
426449 readline . moveCursor ( this . _ostream , 0 , dy ) ;
@@ -479,15 +502,8 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'onKeypress', function onK
479502 return ;
480503 }
481504 // 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 ( ) ;
491507
492508 // Simulate `line` event:
493509 this . _rli . write ( '\n' ) ;
@@ -509,10 +525,33 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'onKeypress', function onK
509525* @returns {void }
510526*/
511527setNonEnumerableReadOnly ( 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 ) {
513551 this . _ttyWrite . call ( this . _rli , data , key ) ;
514552 return ;
515553 }
554+ // If multiline mode is active, enable navigation...
516555 switch ( key . name ) {
517556 case 'up' :
518557 this . _moveUp ( ) ;
0 commit comments