Skip to content

feat: add multiline editing in the REPL #2347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/node_modules/@stdlib/repl/lib/completer_preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
return;
}
// Check for existing content beyond the cursor which could "collide" with a preview completion...
if ( /[^a-zA-Z0-9_$]/.test( this._rli.line.substring( this._rli.cursor ) ) ) { // FIXME: this is not robust (see https://mathiasbynens.be/notes/javascript-identifiers)

Check warning on line 231 in lib/node_modules/@stdlib/repl/lib/completer_preview.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'fixme' comment: 'FIXME: this is not robust (see...'
return;
}
try {
Expand All @@ -246,34 +246,37 @@
* @type {Function}
* @param {string} data - input data
* @param {(Object|void)} key - key object
* @returns {void}
* @returns {boolean} boolean indicating whether the preview was auto-completed
*/
setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function beforeKeypress( data, key ) {
if ( !this._enabled ) {
return;
return false;
}
if ( !key || this._preview === '' ) {
return;
return false;
}
// Avoid clashing with existing TAB completion behavior...
if ( key.name === 'tab' ) {
return this.clear();
this.clear();
return false;
}
// Handle the case where the user is not at the end of the line...
if ( this._rli.cursor !== this._rli.line.length ) {
// If a user is in the middle of a line and presses ENTER, clear the preview string, as the preview was not accepted prior to executing the expression...
if ( key.name === 'return' || key.name === 'enter' ) {
debug( 'Received an ENTER keypress event while in the middle of the line.' );
return this.clear();
this.clear();
}
return;
return false;
}
// When the user is at the end of the line, auto-complete the line with the completion preview when a user presses RETURN or the RIGHT arrow key (note: pressing ENTER will result in both completion AND execution)...
if ( key.name === 'return' || key.name === 'enter' || key.name === 'right' ) {
debug( 'Completion preview accepted. Performing auto-completion...' );
this._rli.write( this._preview );
this._preview = '';
return true;
}
return false;
});


Expand Down
25 changes: 16 additions & 9 deletions lib/node_modules/@stdlib/repl/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
var displayPrompt = require( './display_prompt.js' );
var inputPrompt = require( './input_prompt.js' );
var OutputStream = require( './output_stream.js' );
var processLine = require( './process_line.js' );
var completerFactory = require( './completer.js' );
var MultilineHandler = require( './multiline_handler.js' );
var PreviewCompleter = require( './completer_preview.js' );
var AutoCloser = require( './auto_close_pairs.js' );
var SyntaxHighlighter = require( './syntax_highlighter.js' );
Expand Down Expand Up @@ -237,11 +237,6 @@
// Define the current workspace:
setNonEnumerable( this, '_currentWorkspace', 'base' );

// Initialize an internal status object for multi-line mode:
setNonEnumerable( this, '_multiline', {} );
setNonEnumerable( this._multiline, 'active', false );
setNonEnumerable( this._multiline, 'mode', 'incomplete_expression' );

// Initialize an internal flag indicating whether the REPL has been closed:
setNonEnumerable( this, '_closed', false );

Expand Down Expand Up @@ -273,6 +268,9 @@
'completer': this._completer
}));

// Initialize a multi-line handler:
setNonEnumerableReadOnly( this, '_multilineHandler', new MultilineHandler( this, this._rli._ttyWrite ) );

// Create a new auto-closer:
setNonEnumerableReadOnly( this, '_autoCloser', new AutoCloser( this._rli, this._settings.autoClosePairs, this._settings.autoDeletePairs ) );

Expand All @@ -286,7 +284,7 @@
setNonEnumerableReadOnly( this, '_ttyWrite', this._rli._ttyWrite );

// Overwrite the private `ttyWrite` method to allow processing input before a "keypress" event is triggered:
this._rli._ttyWrite = beforeKeypress; // WARNING: overwriting a private property

Check warning on line 287 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'warning' comment: 'WARNING: overwriting a private property'

// Add event listeners:
this._rli.on( 'close', onClose );
Expand All @@ -306,9 +304,9 @@
// Write a welcome message:
this._wstream.write( opts.welcome );

// TODO: check whether to synchronously initialize a REPL history file

Check warning on line 307 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: check whether to synchronously...'

// TODO: check whether to synchronously initialize a REPL log file

Check warning on line 309 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: check whether to synchronously...'

// Add any provided user-defined themes...
if ( opts.themes ) {
Expand Down Expand Up @@ -337,12 +335,20 @@
* @param {(Object|void)} key - key object
*/
function beforeKeypress( data, key ) {
var completed;

if ( self._ostream.isPaging ) {
self._ostream.beforeKeypress( data, key );
return;
}
self._autoCloser.beforeKeypress( data, key );
self._previewCompleter.beforeKeypress( data, key );
completed = self._previewCompleter.beforeKeypress( data, key );

// If completion was auto-completed, don't trigger multi-line keybindings to avoid double operations...
if ( !completed ) {
self._multilineHandler.beforeKeypress( data, key );
return;
}
self._ttyWrite.call( self._rli, data, key );
}

Expand All @@ -366,6 +372,7 @@
if ( autoClosed ) {
self._previewCompleter.clear();
}
self._multilineHandler.onKeypress( data, key );
self._syntaxHighlighter.onKeypress();
self._previewCompleter.onKeypress( data, key );
}
Expand All @@ -379,7 +386,7 @@
function onLine( line ) {
self._SIGINT = false; // reset flag
if ( self._closed === false ) {
processLine( self, line );
self._multilineHandler.processLine( line );
}
}

Expand Down Expand Up @@ -469,9 +476,9 @@
// Update the internal command history buffer: [..., <id>, <cmd>, <success>, ...]
self._history.push( self._count, cmd, success );

// TODO: if successful and if necessary, (asynchronously?) write the command to a history file (question: do we only want to write successful commands to the history file? maybe we need to option for limiting to successful commands?)

Check warning on line 479 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: if successful and if necessary,...'

// TODO: if necessary, (asynchronously?) write the command and result to a log file (JSON serialization?)

Check warning on line 481 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: if necessary, (asynchronously?)...'
}
}

Expand Down Expand Up @@ -695,7 +702,7 @@

// Before creating a new execution context in a non-sandboxed environment, remove current workspace variables in order to allow garbage collection and avoid memory leaks (e.g., variables/functions declared during a REPL session which might remain bound to the environment `global` after clearing a REPL):
if ( this._sandbox === false ) {
// WARNING: in a non-sandboxed environment, if a global variable is externally introduced during a REPL session (i.e., introduced via a mechanism outside of the REPL environment), we will delete that global variable, which means the following logic may introduce unintended side-effects for this particular edge case (e.g., application code may expect the presence of the subsequently deleted global variable). While not ideal, (a) user applications should not be introducing globals to begin with and (b) the probability of a user running a REPL session, a user clearing that REPL session, AND a global variable being introduced between starting a REPL and clearing the REPL should be negligible.

Check warning on line 705 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'warning' comment: 'WARNING: in a non-sandboxed environment,...'
tmp = this._context.vars();
for ( i = 0; i < tmp.length; i++ ) {
if ( isConfigurableProperty( this._context, tmp[ i ] ) ) {
Expand Down Expand Up @@ -1261,7 +1268,7 @@
throw new Error( 'invalid operation. Cannot clear the command buffer of a REPL which has already closed.' );
}
// Clear any command which has been buffered but not yet executed:
this._cmd.length = 0;
this._multilineHandler.resetInput();

return this;
});
Expand Down Expand Up @@ -1299,9 +1306,9 @@
// Clear the command queue:
this._queue.clear();

// TODO: ensure REPL history is saved (flushed) to file before closing the REPL (see https://github.com/nodejs/node/blob/b21e7c7bcf23a2715951e4cd96180e4dbf1dcd4d/lib/repl.js#L805)

Check warning on line 1309 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: ensure REPL history is saved...'

// TODO: ensure REPL log is saved (flushed) to file before closing the REPL

Check warning on line 1311 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'todo' comment: 'TODO: ensure REPL log is saved (flushed)...'

nextTick( onTick );

Expand All @@ -1324,7 +1331,7 @@

// If this is a non-sandboxed REPL, remove global variables/properties which were introduced during context creation and by a user during a REPL session...
if ( self._sandbox === false ) {
// WARNING: in a non-sandboxed environment, if a global variable is externally introduced during a REPL session (i.e., introduced via a mechanism outside of the REPL environment), we will delete that global variable, which means the following logic may introduce unintended side-effects for this particular edge case (e.g., application code may expect the presence of the subsequently deleted global variable). While not ideal, (a) user applications should not be introducing globals to begin with and (b) the probability of a user running a REPL session, a user closing that REPL session, AND a global variable being introduced between starting a REPL and closing the REPL should be negligible.

Check warning on line 1334 in lib/node_modules/@stdlib/repl/lib/main.js

View workflow job for this annotation

GitHub Actions / Lint Changed Files

Unexpected 'warning' comment: 'WARNING: in a non-sandboxed environment,...'
tmp = self._context.vars(); // current workspace variables
for ( i = 0; i < tmp.length; i++ ) {
if ( isConfigurableProperty( self._context, tmp[ i ] ) ) {
Expand Down
Loading
Loading