Skip to content

Commit f20063d

Browse files
committed
feat: allow breaking line with ENTER as a multiline trigger
Signed-off-by: Snehil Shah <[email protected]>
1 parent a4f20ea commit f20063d

File tree

1 file changed

+105
-66
lines changed

1 file changed

+105
-66
lines changed

lib/node_modules/@stdlib/repl/lib/multiline_handler.js

Lines changed: 105 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var logger = require( 'debug' );
2727
var Parser = require( 'acorn' ).Parser;
2828
var parseLoose = require( 'acorn-loose' ).parse;
2929
var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
30+
var copy = require( '@stdlib/array/base/copy' );
3031
var min = require( '@stdlib/math/base/special/min' );
3132
var displayPrompt = require( './display_prompt.js' );
3233
var 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
*/
337406
setNonEnumerableReadOnly( 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
*/
511527
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 ) {
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

Comments
 (0)