Skip to content

Commit 7ba179c

Browse files
Snehil-Shahkgryte
andauthored
feat: add bracketed-paste mode in the REPL
PR-URL: #2502 Closes: #2068 Co-authored-by: Athan Reines <kgryte@gmail.com> Reviewed-by: Athan Reines <kgryte@gmail.com> Signed-off-by: Snehil Shah <snehilshah.989@gmail.com> Signed-off-by: Athan Reines <kgryte@gmail.com>
1 parent dd73ff2 commit 7ba179c

File tree

7 files changed

+105
-8
lines changed

7 files changed

+105
-8
lines changed

lib/node_modules/@stdlib/repl/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ The function supports specifying the following settings:
8383
- **autoClosePairs**: boolean indicating whether to automatically insert matching brackets, parentheses, and quotes. Default: `true`.
8484
- **autoDeletePairs**: boolean indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes. Default: `true`.
8585
- **autoPage**: boolean indicating whether to automatically page return values having a display size exceeding the visible screen. When streams are TTY, the default is `true`; otherwise, the default is `false`.
86+
- **bracketedPaste**: boolean indicating whether to enable bracketed-paste mode. When streams are TTY, the default is `true`; otherwise, the default is `false`.
87+
- **autoDisableBracketedPasteOnExit**: boolean indicating whether to automatically disable bracketed-paste upon exiting the REPL. When streams are TTY and bracketed paste is enabled, the default is `true`; otherwise, the default is `false`.
8688
- **completionPreviews**: boolean indicating whether to display completion previews for auto-completion. When streams are TTY, the default is `true`; otherwise, the default is `false`.
8789
- **syntaxHighlighting**: boolean indicating whether to enable syntax highlighting of entered input expressions. When streams are TTY, the default is `true`; otherwise, the default is `false`.
8890
- **theme**: initial color theme for syntax highlighting. Default: `stdlib-ansi-basic`.

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,19 @@ function isQuote( ch ) {
6565
* @param {Object} rli - readline instance
6666
* @param {boolean} autoClose - boolean indicating whether auto-closing should be initially enabled
6767
* @param {boolean} autoDelete - boolean indicating whether auto-deleting should be initially enabled
68+
* @param {MultilineHandler} multiline - multiline handler instance
6869
* @returns {AutoCloser} auto-closer instance
6970
*/
70-
function AutoCloser( rli, autoClose, autoDelete ) {
71+
function AutoCloser( rli, autoClose, autoDelete, multiline ) {
7172
if ( !(this instanceof AutoCloser) ) {
72-
return new AutoCloser( rli, autoClose, autoDelete );
73+
return new AutoCloser( rli, autoClose, autoDelete, multiline );
7374
}
7475
debug( 'Creating an auto-closer...' );
7576
this._rli = rli;
7677
this._ignoreBackspace = false;
7778
this._autoClose = autoClose;
7879
this._autoDelete = autoDelete;
80+
this._multiline = multiline;
7981
return this;
8082
}
8183

@@ -316,6 +318,9 @@ setNonEnumerableReadOnly( AutoCloser.prototype, 'beforeKeypress', function befor
316318
if ( !this._autoDelete ) {
317319
return false;
318320
}
321+
if ( this._multiline.isPasting() ) {
322+
return false;
323+
}
319324
if ( !key || key.name !== 'backspace' ) {
320325
return false;
321326
}
@@ -360,6 +365,9 @@ setNonEnumerableReadOnly( AutoCloser.prototype, 'onKeypress', function onKeypres
360365
if ( !this._autoClose ) {
361366
return false;
362367
}
368+
if ( this._multiline.isPasting() ) {
369+
return false;
370+
}
363371
cursor = this._rli.cursor;
364372
line = this._rli.line;
365373

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,6 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'beforeKeypress', function
628628
this._ttyWrite.call( this._rli, data, key );
629629
return;
630630
}
631-
632631
// If user is already viewing completions, allow navigating it...
633632
if ( this._isNavigating ) {
634633
switch ( key.name ) {
@@ -638,6 +637,12 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'beforeKeypress', function
638637
this.closeCompleter();
639638
break;
640639

640+
// If paste sequences detected, close the completer:
641+
case 'paste-start':
642+
this.closeCompleter();
643+
this._ttyWrite.call( this._rli, data, key );
644+
break;
645+
641646
// If arrow keys detected, allow navigating the completions...
642647
case 'down':
643648
debug( 'Received a DOWN keypress event...' );
@@ -660,6 +665,11 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'beforeKeypress', function
660665
}
661666
return;
662667
}
668+
// If we are in the middle of receiving pasted input, use TAB for indentation and don't trigger completions...
669+
if ( this._multiline.isPasting() ) {
670+
this._ttyWrite.call( this._rli, data, key );
671+
return;
672+
}
663673
// Trigger TAB completions:
664674
cursor = this._rli.cursor;
665675
line = this._rli.line;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,15 @@ function defaults() {
8989
// Flag indicating whether to enable automatically page return values requiring a display size exceeding the visible screen (note: default depends on whether TTY):
9090
'autoPage': void 0,
9191

92+
// Flag indicating whether to enable bracketed-paste mode (note: default depends on whether TTY):
93+
'bracketedPaste': void 0,
94+
9295
// Flag indicating whether to enable the display of completion previews for auto-completion (note: default depends on whether TTY):
9396
'completionPreviews': void 0,
9497

98+
// Flag indicating whether to automatically disable bracketed-paste upon exiting the REPL (note: default depends on whether TTY):
99+
'autoDisableBracketedPasteOnExit': void 0,
100+
95101
// Flag indicating whether to enable syntax highlighting (note: default depends on whether TTY):
96102
'syntaxHighlighting': void 0,
97103

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
/* eslint-disable no-restricted-syntax, no-invalid-this, no-underscore-dangle, max-lines */
19+
/* eslint-disable no-restricted-syntax, no-invalid-this, no-underscore-dangle, max-lines, max-lines-per-function */
2020

2121
'use strict';
2222

@@ -102,7 +102,9 @@ var debug = logger( 'repl' );
102102
* @param {boolean} [options.settings.autoClosePairs=true] - boolean indicating whether to automatically insert matching brackets, parentheses, and quotes
103103
* @param {boolean} [options.settings.autoDeletePairs=true] - boolean indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes
104104
* @param {boolean} [options.settings.autoPage] - boolean indicating whether to automatically page return values requiring a display size exceeding the visible screen
105+
* @param {boolean} [options.settings.bracketedPaste] - boolean indicating whether to enable bracketed-paste mode
105106
* @param {boolean} [options.settings.completionPreviews] - boolean indicating whether to enable completion previews for auto-completion
107+
* @param {boolean} [options.settings.autoDisableBracketedPasteOnExit] - boolean indicating whether to automatically disable bracketed-paste upon exiting the REPL
106108
* @param {boolean} [options.settings.syntaxHighlighting] - boolean indicating whether to enable syntax highlighting
107109
* @param {string} [options.settings.theme] - initial color theme for syntax highlighting
108110
* @throws {Error} must provide valid options
@@ -151,7 +153,9 @@ function REPL( options ) {
151153
}
152154
opts.isTTY = ( opts.isTTY === void 0 ) ? opts.output.isTTY : opts.isTTY;
153155
opts.settings.autoPage = ( opts.settings.autoPage === void 0 ) ? opts.isTTY : opts.settings.autoPage; // eslint-disable-line max-len
156+
opts.settings.bracketedPaste = ( opts.settings.bracketedPaste === void 0 && opts.isTTY ) ? true : opts.settings.bracketedPaste; // eslint-disable-line max-len
154157
opts.settings.completionPreviews = ( opts.settings.completionPreviews === void 0 ) ? opts.isTTY : opts.settings.completionPreviews; // eslint-disable-line max-len
158+
opts.settings.autoDisableBracketedPasteOnExit = ( opts.settings.autoDisableBracketedPasteOnExit === void 0 ) ? opts.isTTY : opts.settings.autoDisableBracketedPasteOnExit; // eslint-disable-line max-len
155159
opts.settings.syntaxHighlighting = ( opts.settings.syntaxHighlighting === void 0 ) ? opts.isTTY : opts.settings.syntaxHighlighting; // eslint-disable-line max-len
156160

157161
debug( 'Options: %s', JSON.stringify({
@@ -275,7 +279,7 @@ function REPL( options ) {
275279
setNonEnumerableReadOnly( this, '_completerEngine', new CompleterEngine( this, this._completer, this._wstream, this._rli._ttyWrite ) );
276280

277281
// Create a new auto-closer:
278-
setNonEnumerableReadOnly( this, '_autoCloser', new AutoCloser( this._rli, this._settings.autoClosePairs, this._settings.autoDeletePairs ) );
282+
setNonEnumerableReadOnly( this, '_autoCloser', new AutoCloser( this._rli, this._settings.autoClosePairs, this._settings.autoDeletePairs, this._multilineHandler ) );
279283

280284
// Initialize a preview completer:
281285
setNonEnumerableReadOnly( this, '_previewCompleter', new PreviewCompleter( this._rli, this._completer, this._ostream, this._settings.completionPreviews ) );
@@ -321,6 +325,10 @@ function REPL( options ) {
321325
// Set the syntax highlighting theme...
322326
this.settings( 'theme', opts.settings.theme );
323327

328+
// Initialize bracketed-paste:
329+
if ( opts.settings.bracketedPaste !== void 0 ) {
330+
this.settings( 'bracketedPaste', opts.settings.bracketedPaste );
331+
}
324332
// Check whether to load and execute a JavaScript file (e.g., prior REPL history) upon startup...
325333
if ( opts.load ) {
326334
this.load( opts.load );
@@ -404,6 +412,9 @@ function REPL( options ) {
404412
* @private
405413
*/
406414
function onClose() {
415+
if ( self._settings.bracketedPaste && self._settings.autoDisableBracketedPasteOnExit ) { // eslint-disable-line max-len
416+
self._multilineHandler.disableBracketedPaste();
417+
}
407418
ostream.end();
408419
ostream.unpipe();
409420

@@ -1481,6 +1492,12 @@ setNonEnumerableReadOnly( REPL.prototype, 'settings', function settings() {
14811492
}
14821493
} else if ( name === 'theme' ) {
14831494
this._syntaxHighlighter.setTheme( value );
1495+
} else if ( name === 'bracketedPaste' ) {
1496+
if ( value ) {
1497+
this._multilineHandler.enableBracketedPaste();
1498+
} else {
1499+
this._multilineHandler.disableBracketedPaste();
1500+
}
14841501
}
14851502

14861503
return this;

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
/* eslint-disable no-underscore-dangle, no-restricted-syntax, no-invalid-this */
19+
/* eslint-disable no-underscore-dangle, no-restricted-syntax, no-invalid-this, max-lines */
2020

2121
'use strict';
2222

@@ -87,6 +87,7 @@ function MultilineHandler( repl, ttyWrite ) {
8787
this._multiline = {};
8888
this._multiline.active = false;
8989
this._multiline.trigger = false;
90+
this._multiline.pasteMode = false;
9091

9192
// Initialize a buffer for caching input lines:
9293
this._lines = [];
@@ -399,6 +400,42 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'resetInput', function res
399400
this._lines.length = 0;
400401
});
401402

403+
/**
404+
* Enables bracketed-paste mode.
405+
*
406+
* @name enableBracketedPaste
407+
* @memberof MultilineHandler.prototype
408+
* @type {Function}
409+
* @returns {void}
410+
*/
411+
setNonEnumerableReadOnly( MultilineHandler.prototype, 'enableBracketedPaste', function enableBracketedPaste() {
412+
this._ostream.write( '\u001b[?2004h' );
413+
});
414+
415+
/**
416+
* Disables bracketed-paste mode.
417+
*
418+
* @name disableBracketedPaste
419+
* @memberof MultilineHandler.prototype
420+
* @type {Function}
421+
* @returns {void}
422+
*/
423+
setNonEnumerableReadOnly( MultilineHandler.prototype, 'disableBracketedPaste', function disableBracketedPaste() {
424+
this._ostream.write( '\u001b[?2004l' );
425+
});
426+
427+
/**
428+
* Checks whether the REPL is currently receiving pasted input.
429+
*
430+
* @name isPasting
431+
* @memberof MultilineHandler.prototype
432+
* @type {Function}
433+
* @returns {boolean} boolean indicating whether the REPL is currently receiving pasted input
434+
*/
435+
setNonEnumerableReadOnly( MultilineHandler.prototype, 'isPasting', function isPasting() {
436+
return this._multiline.pasteMode;
437+
});
438+
402439
/**
403440
* Processes input line data.
404441
*
@@ -511,6 +548,15 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'onKeypress', function onK
511548
if ( !key ) {
512549
return;
513550
}
551+
// Check for paste sequences...
552+
if ( key.name === 'paste-start' ) {
553+
this._multiline.pasteMode = true;
554+
return;
555+
}
556+
if ( key.name === 'paste-end' ) {
557+
this._multiline.pasteMode = false;
558+
return;
559+
}
514560
// Trigger multi-line input when encountering `CTRL+O` keybinding...
515561
if ( key.name === 'o' && key.ctrl ) {
516562
this._triggerMultiline();
@@ -546,8 +592,8 @@ setNonEnumerableReadOnly( MultilineHandler.prototype, 'beforeKeypress', function
546592
cmd = copy( this._cmd );
547593
cmd[ this._lineIndex ] = this._rli.line;
548594

549-
// If command is incomplete, trigger multi-line mode...
550-
if ( !this._isMultilineInput( cmd.join( '\n' ) ) ) {
595+
// If we are in paste mode or the command is incomplete, trigger multi-line mode...
596+
if ( !this._multiline.pasteMode && !this._isMultilineInput( cmd.join( '\n' ) ) ) {
551597
this._ttyWrite.call( this._rli, data, key );
552598
return;
553599
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,18 @@ var SETTINGS = {
4040
'desc': 'Automatically page return values whose display size exceeds the visible screen.',
4141
'type': 'boolean'
4242
},
43+
'bracketedPaste': {
44+
'desc': 'Enable bracketed-paste mode.',
45+
'type': 'boolean'
46+
},
4347
'completionPreviews': {
4448
'desc': 'Enable the display of completion previews for auto-completion.',
4549
'type': 'boolean'
4650
},
51+
'autoDisableBracketedPasteOnExit': {
52+
'desc': 'Automatically disable bracketed-paste upon exiting the REPL.',
53+
'type': 'boolean'
54+
},
4755
'syntaxHighlighting': {
4856
'desc': 'Enable syntax highlighting.',
4957
'type': 'boolean'

0 commit comments

Comments
 (0)