From 6e309e38c040c735938e059801d709a8be02b0e1 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Fri, 8 Mar 2024 16:26:36 +0100 Subject: [PATCH 01/37] feat(repl): add preview for REPL tab completion Change repl to show a gray preview of the possible tab completion, whenever there is only one available. This feature is modeled after the preview in the node.js REPL, and should enhance the user experience. --- lib/node_modules/@stdlib/repl/lib/main.js | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index 53b133d586ea..aea4f8038e8d 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -24,6 +24,7 @@ var EventEmitter = require( 'events' ).EventEmitter; var readline = require( 'readline' ).createInterface; +var rl = require('readline'); var resolve = require( 'path' ).resolve; var logger = require( 'debug' ); var inherit = require( '@stdlib/utils/inherit' ); @@ -57,6 +58,7 @@ var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); var completer = require( './completer.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); +var longestCommonPrefix = require('./longest_common_prefix.js'); // VARIABLES // @@ -236,6 +238,9 @@ function REPL( options ) { this._rli.on( 'line', onLine ); this._rli.on( 'SIGINT', onSIGINT ); + // Add an event listener for any key press on the readline input: + this._rli.input.on( 'keypress', onKeypress ); + // Add listener for "command" events: this.on( 'command', onCommand ); @@ -255,6 +260,28 @@ function REPL( options ) { } return this; + /** + * Callback invoked upon a readline interface "keypress" event. + * @private + */ + function onKeypress() { + self._rli.completer( self._rli.line, function callback(_, completions) { + var charactersToEnd; + var commonPrefix; + var completion; + + if ( completions[0].length === 1 ) { + completion = completions[0][0]; + commonPrefix = longestCommonPrefix( completion, completions[1] ); + completion = completion.substring( commonPrefix.length ); + charactersToEnd = self._rli.line.length - self._rli.cursor; + rl.moveCursor( self._ostream, charactersToEnd ); + self._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); + rl.moveCursor( self._ostream, -completion.length - charactersToEnd ); + } + }); + } + /** * Callback invoked upon a readline interface "line" event. * From f4e96ca305b5dfdc8f9373c5758b859d164a554f Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Mon, 11 Mar 2024 00:20:02 +0100 Subject: [PATCH 02/37] feat(repl): add special interactions for right and return Adds several special interactions inspired by the node REPL, to enhance user experience. When the user moves the caret into the preview it is now automatically filled. Also, when the user presses enter while the preview is showing, it will be filled in before the line is processed. --- lib/node_modules/@stdlib/repl/lib/main.js | 51 ++++--- .../@stdlib/repl/lib/preview_completer.js | 139 ++++++++++++++++++ 2 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 lib/node_modules/@stdlib/repl/lib/preview_completer.js diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index aea4f8038e8d..d6bdeb36b718 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -24,7 +24,6 @@ var EventEmitter = require( 'events' ).EventEmitter; var readline = require( 'readline' ).createInterface; -var rl = require('readline'); var resolve = require( 'path' ).resolve; var logger = require( 'debug' ); var inherit = require( '@stdlib/utils/inherit' ); @@ -58,7 +57,7 @@ var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); var completer = require( './completer.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); -var longestCommonPrefix = require('./longest_common_prefix.js'); +var previewCompleterFactory = require('./preview_completer.js'); // VARIABLES // @@ -108,6 +107,8 @@ var debug = logger( 'repl' ); * repl.close(); */ function REPL( options ) { + var previewCompleter; + var ttyWrite; var opts; var self; var err; @@ -234,12 +235,15 @@ function REPL( options ) { 'prompt': opts.inputPrompt, 'completer': completer( this ) })); + + previewCompleter = previewCompleterFactory(this); + this._rli.on( 'close', onClose ); this._rli.on( 'line', onLine ); this._rli.on( 'SIGINT', onSIGINT ); - // Add an event listener for any key press on the readline input: - this._rli.input.on( 'keypress', onKeypress ); + // Handle drawing the gray preview + this._rli.input.on( 'keypress', previewCompleter.onKeypress ); // Add listener for "command" events: this.on( 'command', onCommand ); @@ -247,6 +251,12 @@ function REPL( options ) { // Write a welcome message: this._ostream.write( opts.welcome ); + // Keep the old ttyWrite so we can call it for default behaviour + ttyWrite = self._rli._ttyWrite.bind( self._rli ); + + // Add event listener that triggers before a key press is processed + self._rli._ttyWrite = beforeKeypress; + // TODO: check whether to synchronously initialize a REPL history file // TODO: check whether to synchronously initialize a REPL log file @@ -260,28 +270,6 @@ function REPL( options ) { } return this; - /** - * Callback invoked upon a readline interface "keypress" event. - * @private - */ - function onKeypress() { - self._rli.completer( self._rli.line, function callback(_, completions) { - var charactersToEnd; - var commonPrefix; - var completion; - - if ( completions[0].length === 1 ) { - completion = completions[0][0]; - commonPrefix = longestCommonPrefix( completion, completions[1] ); - completion = completion.substring( commonPrefix.length ); - charactersToEnd = self._rli.line.length - self._rli.cursor; - rl.moveCursor( self._ostream, charactersToEnd ); - self._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); - rl.moveCursor( self._ostream, -completion.length - charactersToEnd ); - } - }); - } - /** * Callback invoked upon a readline interface "line" event. * @@ -295,6 +283,17 @@ function REPL( options ) { } } + /** + * Handle special cases related to preview, such as when users presses right to fill in the preview. + * @private + * @param {*} d data + * @param {*} key the key pressed + */ + function beforeKeypress(d, key) { + previewCompleter.beforeKeypress(d, key); + ttyWrite(d, key); + } + /** * Callback invoked upon a readline interface "close" event. * diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js new file mode 100644 index 000000000000..13c4a20a08f5 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -0,0 +1,139 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2019 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* eslint-disable no-underscore-dangle */ + +'use strict'; + +// MODULES // + +var rl = require( 'readline' ); +var longestCommonPrefix = require( './longest_common_prefix.js' ); + + +// MAIN // + +/** +* Handles the gray preview that displays when there is only one possible tab completion. +* +* @private +* @param {REPL} repl repl instance +* @returns {Object} object containing two event listeners that handle drawing the preview +*/ +function previewCompleter( repl ) { + // This represents the preview currently being displayed + var completionCache = ''; + + /** + * Clears the gray preview, so that it is no longer shown to the user. + * @private + */ + function clearPreview() { + var charactersToEnd; + if ( completionCache !== '' ) { + charactersToEnd = repl._rli.line.length - repl._rli.cursor; + rl.moveCursor( repl._ostream, charactersToEnd ); + repl._rli.output.write( ' '.repeat( completionCache.length ) ); + rl.moveCursor( repl._ostream, -completionCache.length - charactersToEnd ); + completionCache = ''; + } + } + + /** + * This event fires before the keypress is processed by the readline input and ensures. + * the following interactions: + * If the user pressed 'return' while a preview is showing, and the user has the cursor + * at the end of the line, the preview is filled in before the line is executed. + * If the user pressed 'right' while the caret is at the end of the line and a preview is + * showing, the preview is filled in. + * + * @param {*} _ not used + * @param {Object} key the key the user pressed + */ + function beforeKeypress( _, key ) { + if ( key && ( key.name === 'return' || key.name === 'enter' ) && completionCache !== '' ) { + if ( repl._rli.cursor === repl._rli.line.length ) { + repl._rli.write( completionCache ); + completionCache = ''; + } else { + clearPreview(); + } + } + if ( key && key.name === 'right' && + repl._rli.cursor === repl._rli.line.length && + completionCache !== '' ) { + repl._rli.write( completionCache ); + completionCache = ''; + } + } + + /** + * Event listener which fires after a key is pressed. + */ + function onKeypress() { + // Get the possible tab completions from the completer + repl._rli.completer( repl._rli.line, callback ); + } + + /** + * Callback function passed to the completer. + * @private + * + * @param {*} _ not used + * @param {*} completions the possible completions passed by the completer + */ + function callback( _, completions ) { + var charactersToEnd; + var commonPrefix; + var completion; + + // If there is only one possible tab completion + if ( completions[ 0 ].length === 1 ) { + completion = completions[ 0 ][ 0 ]; + + // Find the part of the completion the user hasn't typed yet + commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); + completion = completion.substring( commonPrefix.length ); + if ( completion !== '' ) { + charactersToEnd = repl._rli.line.length - repl._rli.cursor; + + // Write the completion at the end of the line regardless of where the cursor was + rl.moveCursor( repl._ostream, charactersToEnd ); + + // The ASCII escape codes make the text grayed out + repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); + + // Move cursor back to original posittion + rl.moveCursor( repl._ostream, -completion.length - charactersToEnd ); + } + completionCache = completion; + } else { + clearPreview(); + } + } + + return { + 'beforeKeypress': beforeKeypress, + 'onKeypress': onKeypress + }; +} + + +// EXPORTS // + +module.exports = previewCompleter; From 891fbc0477ad762fa0615273d13f9e245fd9dab7 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Mon, 11 Mar 2024 15:29:18 +0100 Subject: [PATCH 03/37] test(repl): test the preview functionality --- .../@stdlib/repl/test/preview_test.js | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 lib/node_modules/@stdlib/repl/test/preview_test.js diff --git a/lib/node_modules/@stdlib/repl/test/preview_test.js b/lib/node_modules/@stdlib/repl/test/preview_test.js new file mode 100644 index 000000000000..d2694797f5f0 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/test/preview_test.js @@ -0,0 +1,220 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2019 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var InspectStream = require( '@stdlib/streams/node/inspect' ); +var noop = require( '@stdlib/utils/noop' ); +var previewCompleter = require( './../lib/preview_completer.js' ); +var REPL = require( './../lib' ); + + +// TESTS // + +tape( 'main export is an object', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof previewCompleter, 'function', 'main export is an object' ); + t.strictEqual( typeof previewCompleter().beforeKeypress, 'function' ); + t.strictEqual( typeof previewCompleter().onKeypress, 'function' ); + t.end(); +}); + +tape( 'Preview completion is shown', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog(chunk) { + outputs.push(chunk.toString()); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write('conso'); + + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); + r.close(); + t.end(); +}); + +tape( 'Preview completion is shown when word is not first in the line', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write('this is a prefix console.ta'); + + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mble\x1b[0m' ); + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); + r.close(); + t.end(); +}); + +tape( 'Preview completion is shown when the cursor moves to the left', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write( 'console.l' ); + + // Move cursor to the left + inputStream.write( '\x1b[1D' ); + t.strictEqual( outputs[ outputs.length - 6 ], '\x1b[90mog\x1b[0m' ); + t.strictEqual( outputs[ outputs.length - 5 ], '\x1b[2D' ); + t.strictEqual( outputs[ outputs.length - 4 ], '\x1b[1D' ); + t.strictEqual( outputs[ outputs.length - 3 ], '\x1b[1C' ); + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mog\x1b[0m' ); + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); + r.close(); + t.end(); +}); + +tape( 'Preview is erased when user types something else', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write( 'conso' ); + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); + inputStream.write( 'o' ); + + // Preview is replaced with empty spaces + t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); + r.close(); + t.end(); +}); + +tape( 'Preview is filled in when user moves cursor into it', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write( 'conso' ); + inputStream.write( '\x1b[1C' ); + t.strictEqual( outputs[ outputs.length - 1 ], 'le' ); + r.close(); + t.end(); +}); + +tape( 'Preview is filled in when user presses return', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write( 'console.l\n' ); + t.strictEqual( outputs[ outputs.length - 3 ], 'og' ); + t.strictEqual( outputs[ outputs.length - 1 ].trim(), 'Out[1]: [Function: log]' ); + r.close(); + t.end(); +}); From 1baf23af8b9cd183acd948c6652810ccfc7d38c4 Mon Sep 17 00:00:00 2001 From: stdlib-bot <82920195+stdlib-bot@users.noreply.github.com> Date: Mon, 11 Mar 2024 17:01:08 +0000 Subject: [PATCH 04/37] chore: update copyright years --- lib/node_modules/@stdlib/repl/lib/preview_completer.js | 2 +- lib/node_modules/@stdlib/repl/test/preview_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index 13c4a20a08f5..d09a4362b732 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2019 The Stdlib Authors. +* Copyright (c) 2024 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/lib/node_modules/@stdlib/repl/test/preview_test.js b/lib/node_modules/@stdlib/repl/test/preview_test.js index d2694797f5f0..c1765ecee719 100644 --- a/lib/node_modules/@stdlib/repl/test/preview_test.js +++ b/lib/node_modules/@stdlib/repl/test/preview_test.js @@ -1,7 +1,7 @@ /** * @license Apache-2.0 * -* Copyright (c) 2019 The Stdlib Authors. +* Copyright (c) 2024 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From fd21d1252aaeb12c8f93773a3e32985d89dba169 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Tue, 12 Mar 2024 11:24:39 +0100 Subject: [PATCH 05/37] refactor(repl): make preview_completer class Refactor preview_completer from factory function to small class. Also, use repeat from stdlib instead of the string prototype, and make some small style fixes. --- lib/node_modules/@stdlib/repl/lib/main.js | 8 +++---- .../@stdlib/repl/lib/preview_completer.js | 24 ++++++++++++------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index d6bdeb36b718..9d88719c0ece 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -57,7 +57,7 @@ var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); var completer = require( './completer.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); -var previewCompleterFactory = require('./preview_completer.js'); +var PreviewCompleter = require('./preview_completer.js'); // VARIABLES // @@ -236,7 +236,7 @@ function REPL( options ) { 'completer': completer( this ) })); - previewCompleter = previewCompleterFactory(this); + previewCompleter = new PreviewCompleter( this ); this._rli.on( 'close', onClose ); this._rli.on( 'line', onLine ); @@ -289,8 +289,8 @@ function REPL( options ) { * @param {*} d data * @param {*} key the key pressed */ - function beforeKeypress(d, key) { - previewCompleter.beforeKeypress(d, key); + function beforeKeypress( d, key ) { + previewCompleter.beforeKeypress( d, key ); ttyWrite(d, key); } diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index d09a4362b732..2c6cc6e84cda 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -23,6 +23,7 @@ // MODULES // var rl = require( 'readline' ); +var repeat = require('@stdlib/string/repeat'); var longestCommonPrefix = require( './longest_common_prefix.js' ); @@ -35,9 +36,19 @@ var longestCommonPrefix = require( './longest_common_prefix.js' ); * @param {REPL} repl repl instance * @returns {Object} object containing two event listeners that handle drawing the preview */ -function previewCompleter( repl ) { +function PreviewCompleter( repl ) { + var completionCache; + if ( !(this instanceof PreviewCompleter) ) { + return new PreviewCompleter( repl ); + } + // This represents the preview currently being displayed - var completionCache = ''; + completionCache = ''; + + this.beforeKeypress = beforeKeypress; + this.onKeypress = onKeypress; + + return this; /** * Clears the gray preview, so that it is no longer shown to the user. @@ -48,7 +59,7 @@ function previewCompleter( repl ) { if ( completionCache !== '' ) { charactersToEnd = repl._rli.line.length - repl._rli.cursor; rl.moveCursor( repl._ostream, charactersToEnd ); - repl._rli.output.write( ' '.repeat( completionCache.length ) ); + repl._rli.output.write( repeat(' ', completionCache.length) ); rl.moveCursor( repl._ostream, -completionCache.length - charactersToEnd ); completionCache = ''; } @@ -126,14 +137,9 @@ function previewCompleter( repl ) { clearPreview(); } } - - return { - 'beforeKeypress': beforeKeypress, - 'onKeypress': onKeypress - }; } // EXPORTS // -module.exports = previewCompleter; +module.exports = PreviewCompleter; From 77980f9d2d2d3f83b2e3cd6fb7afd0d5c3da7d38 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Tue, 12 Mar 2024 11:42:23 +0100 Subject: [PATCH 06/37] style(repl): fix parantheses spacing --- lib/node_modules/@stdlib/repl/lib/main.js | 2 +- lib/node_modules/@stdlib/repl/lib/preview_completer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index 9d88719c0ece..b6ee5d0ba0a8 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -57,7 +57,7 @@ var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); var completer = require( './completer.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); -var PreviewCompleter = require('./preview_completer.js'); +var PreviewCompleter = require( './preview_completer.js' ); // VARIABLES // diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index 2c6cc6e84cda..dffa82ef5efb 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -23,7 +23,7 @@ // MODULES // var rl = require( 'readline' ); -var repeat = require('@stdlib/string/repeat'); +var repeat = require( '@stdlib/string/repeat' ); var longestCommonPrefix = require( './longest_common_prefix.js' ); From 4456788b7168bd411bc21436d5fb0f86194b4b86 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Thu, 14 Mar 2024 12:33:40 +0100 Subject: [PATCH 07/37] fix(repl): put PreviewCompleter methods in prototype Make the beforeKeypress and onKeypress methods be defined in the prototype of PreviewCompleter so that they are not created again for every instance. --- lib/node_modules/@stdlib/repl/lib/main.js | 2 +- .../@stdlib/repl/lib/preview_completer.js | 173 +++++++++--------- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index b6ee5d0ba0a8..d258be919b2b 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -243,7 +243,7 @@ function REPL( options ) { this._rli.on( 'SIGINT', onSIGINT ); // Handle drawing the gray preview - this._rli.input.on( 'keypress', previewCompleter.onKeypress ); + this._rli.input.on( 'keypress', previewCompleter.onKeypress.bind(previewCompleter) ); // Add listener for "command" events: this.on( 'command', onCommand ); diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index dffa82ef5efb..fd70996f79ac 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -16,7 +16,7 @@ * limitations under the License. */ -/* eslint-disable no-underscore-dangle */ +/* eslint-disable no-underscore-dangle, no-invalid-this */ 'use strict'; @@ -37,105 +37,108 @@ var longestCommonPrefix = require( './longest_common_prefix.js' ); * @returns {Object} object containing two event listeners that handle drawing the preview */ function PreviewCompleter( repl ) { - var completionCache; if ( !(this instanceof PreviewCompleter) ) { return new PreviewCompleter( repl ); } - // This represents the preview currently being displayed - completionCache = ''; + // This represents the preview currently being displayed. + this.completionCache = ''; - this.beforeKeypress = beforeKeypress; - this.onKeypress = onKeypress; + // Keep the repl this instance is associated with. + this.repl = repl; + + // We need to pass a bound callback to the completer, so we calculate it here. + this.completerCallback = completerCallback.bind( this ); return this; +} - /** - * Clears the gray preview, so that it is no longer shown to the user. - * @private - */ - function clearPreview() { - var charactersToEnd; - if ( completionCache !== '' ) { - charactersToEnd = repl._rli.line.length - repl._rli.cursor; - rl.moveCursor( repl._ostream, charactersToEnd ); - repl._rli.output.write( repeat(' ', completionCache.length) ); - rl.moveCursor( repl._ostream, -completionCache.length - charactersToEnd ); - completionCache = ''; - } +PreviewCompleter.prototype.clearPreview = clearPreview; +PreviewCompleter.prototype.beforeKeypress = beforeKeypress; +PreviewCompleter.prototype.onKeypress = onKeypress; + +/** +* Clears the gray preview, so that it is no longer shown to the user. +*/ +function clearPreview() { + var charactersToEnd; + if ( this.completionCache !== '' ) { + charactersToEnd = this.repl._rli.line.length - this.repl._rli.cursor; + rl.moveCursor( this.repl._ostream, charactersToEnd ); + this.repl._rli.output.write( repeat(' ', this.completionCache.length) ); + rl.moveCursor( this.repl._ostream, -this.completionCache.length - charactersToEnd ); + this.completionCache = ''; } +} - /** - * This event fires before the keypress is processed by the readline input and ensures. - * the following interactions: - * If the user pressed 'return' while a preview is showing, and the user has the cursor - * at the end of the line, the preview is filled in before the line is executed. - * If the user pressed 'right' while the caret is at the end of the line and a preview is - * showing, the preview is filled in. - * - * @param {*} _ not used - * @param {Object} key the key the user pressed - */ - function beforeKeypress( _, key ) { - if ( key && ( key.name === 'return' || key.name === 'enter' ) && completionCache !== '' ) { - if ( repl._rli.cursor === repl._rli.line.length ) { - repl._rli.write( completionCache ); - completionCache = ''; - } else { - clearPreview(); - } - } - if ( key && key.name === 'right' && - repl._rli.cursor === repl._rli.line.length && - completionCache !== '' ) { - repl._rli.write( completionCache ); - completionCache = ''; +/** +* This event fires before the keypress is processed by the readline input and ensures. +* the following interactions: +* If the user pressed 'return' while a preview is showing, and the user has the cursor +* at the end of the line, the preview is filled in before the line is executed. +* If the user pressed 'right' while the caret is at the end of the line and a preview is +* showing, the preview is filled in. +* +* @param {*} _ not used +* @param {Object} key the key the user pressed +*/ +function beforeKeypress( _, key ) { + if ( key && ( key.name === 'return' || key.name === 'enter' ) && this.completionCache !== '' ) { + if ( this.repl._rli.cursor === this.repl._rli.line.length ) { + this.repl._rli.write( this.completionCache ); + this.completionCache = ''; + } else { + this.clearPreview(); } } - - /** - * Event listener which fires after a key is pressed. - */ - function onKeypress() { - // Get the possible tab completions from the completer - repl._rli.completer( repl._rli.line, callback ); + if ( key && key.name === 'right' && + this.repl._rli.cursor === this.repl._rli.line.length && + this.completionCache !== '' ) { + this.repl._rli.write( this.completionCache ); + this.completionCache = ''; } +} - /** - * Callback function passed to the completer. - * @private - * - * @param {*} _ not used - * @param {*} completions the possible completions passed by the completer - */ - function callback( _, completions ) { - var charactersToEnd; - var commonPrefix; - var completion; - - // If there is only one possible tab completion - if ( completions[ 0 ].length === 1 ) { - completion = completions[ 0 ][ 0 ]; - - // Find the part of the completion the user hasn't typed yet - commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); - completion = completion.substring( commonPrefix.length ); - if ( completion !== '' ) { - charactersToEnd = repl._rli.line.length - repl._rli.cursor; - - // Write the completion at the end of the line regardless of where the cursor was - rl.moveCursor( repl._ostream, charactersToEnd ); - - // The ASCII escape codes make the text grayed out - repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); - - // Move cursor back to original posittion - rl.moveCursor( repl._ostream, -completion.length - charactersToEnd ); - } - completionCache = completion; - } else { - clearPreview(); +/** +* Event listener which fires after a key is pressed. +*/ +function onKeypress() { + // Get the possible tab completions from the completer + this.repl._rli.completer( this.repl._rli.line, this.completerCallback ); +} + +/** +* Callback function passed to the completer. +* @private +* +* @param {*} _ not used +* @param {*} completions the possible completions passed by the completer +*/ +function completerCallback( _, completions ) { + var charactersToEnd; + var commonPrefix; + var completion; + + // If there is only one possible tab completion + if ( completions[ 0 ].length === 1 ) { + completion = completions[ 0 ][ 0 ]; + + // Find the part of the completion the user hasn't typed yet + commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); + completion = completion.substring( commonPrefix.length ); + if ( completion !== '' ) { + charactersToEnd = this.repl._rli.line.length - this.repl._rli.cursor; + + // Write the completion at the end of the line regardless of where the cursor was + rl.moveCursor( this.repl._ostream, charactersToEnd ); + + // The ASCII escape codes make the text grayed out + this.repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); + + // Move cursor back to original posittion + rl.moveCursor( this.repl._ostream, -completion.length - charactersToEnd ); } + this.completionCache = completion; } } From f6dd2e79675849d5d766205015b8734d1e3bc53e Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Thu, 14 Mar 2024 12:48:40 +0100 Subject: [PATCH 08/37] fix(repl): put back accidentally remove clearPreview The clearPreview function in completerCallback() was accidentally removed, which caused some tests to fail. This commit puts it back. --- lib/node_modules/@stdlib/repl/lib/preview_completer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index fd70996f79ac..20266a2d899a 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -139,6 +139,8 @@ function completerCallback( _, completions ) { rl.moveCursor( this.repl._ostream, -completion.length - charactersToEnd ); } this.completionCache = completion; + } else { + this.clearPreview(); } } From 85fb8866984f0bf803025274dd654c1282e0ab48 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Thu, 14 Mar 2024 13:13:01 +0100 Subject: [PATCH 09/37] docs(repl): make comment clearer --- lib/node_modules/@stdlib/repl/lib/preview_completer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index 20266a2d899a..71b1262671a4 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -47,7 +47,7 @@ function PreviewCompleter( repl ) { // Keep the repl this instance is associated with. this.repl = repl; - // We need to pass a bound callback to the completer, so we calculate it here. + // Cache bound callback so it can be passed to the completer. this.completerCallback = completerCallback.bind( this ); return this; From 1251c881a8acc4ed480a4c03c1f324facb47362f Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 02:24:17 -0700 Subject: [PATCH 10/37] refactor: make property private --- .../@stdlib/repl/lib/preview_completer.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index 71b1262671a4..46e81de30881 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -45,7 +45,7 @@ function PreviewCompleter( repl ) { this.completionCache = ''; // Keep the repl this instance is associated with. - this.repl = repl; + this._repl = repl; // Cache bound callback so it can be passed to the completer. this.completerCallback = completerCallback.bind( this ); @@ -63,10 +63,10 @@ PreviewCompleter.prototype.onKeypress = onKeypress; function clearPreview() { var charactersToEnd; if ( this.completionCache !== '' ) { - charactersToEnd = this.repl._rli.line.length - this.repl._rli.cursor; - rl.moveCursor( this.repl._ostream, charactersToEnd ); - this.repl._rli.output.write( repeat(' ', this.completionCache.length) ); - rl.moveCursor( this.repl._ostream, -this.completionCache.length - charactersToEnd ); + charactersToEnd = this._repl._rli.line.length - this._repl._rli.cursor; + rl.moveCursor( this._repl._ostream, charactersToEnd ); + this._repl._rli.output.write( repeat(' ', this.completionCache.length) ); + rl.moveCursor( this._repl._ostream, -this.completionCache.length - charactersToEnd ); this.completionCache = ''; } } @@ -84,17 +84,17 @@ function clearPreview() { */ function beforeKeypress( _, key ) { if ( key && ( key.name === 'return' || key.name === 'enter' ) && this.completionCache !== '' ) { - if ( this.repl._rli.cursor === this.repl._rli.line.length ) { - this.repl._rli.write( this.completionCache ); + if ( this._repl._rli.cursor === this._repl._rli.line.length ) { + this._repl._rli.write( this.completionCache ); this.completionCache = ''; } else { this.clearPreview(); } } if ( key && key.name === 'right' && - this.repl._rli.cursor === this.repl._rli.line.length && + this._repl._rli.cursor === this._repl._rli.line.length && this.completionCache !== '' ) { - this.repl._rli.write( this.completionCache ); + this._repl._rli.write( this.completionCache ); this.completionCache = ''; } } @@ -104,7 +104,7 @@ function beforeKeypress( _, key ) { */ function onKeypress() { // Get the possible tab completions from the completer - this.repl._rli.completer( this.repl._rli.line, this.completerCallback ); + this._repl._rli.completer( this._repl._rli.line, this.completerCallback ); } /** @@ -127,16 +127,16 @@ function completerCallback( _, completions ) { commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); completion = completion.substring( commonPrefix.length ); if ( completion !== '' ) { - charactersToEnd = this.repl._rli.line.length - this.repl._rli.cursor; + charactersToEnd = this._repl._rli.line.length - this._repl._rli.cursor; // Write the completion at the end of the line regardless of where the cursor was - rl.moveCursor( this.repl._ostream, charactersToEnd ); + rl.moveCursor( this._repl._ostream, charactersToEnd ); // The ASCII escape codes make the text grayed out - this.repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); + this._repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); // Move cursor back to original posittion - rl.moveCursor( this.repl._ostream, -completion.length - charactersToEnd ); + rl.moveCursor( this._repl._ostream, -completion.length - charactersToEnd ); } this.completionCache = completion; } else { From 57046cd25df9d4fa965a2143fa9b523db987081c Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 03:12:53 -0700 Subject: [PATCH 11/37] refactor: only enable a preview completer when TTY --- lib/node_modules/@stdlib/repl/lib/main.js | 69 ++++++++++++------- .../@stdlib/repl/lib/preview_completer.js | 3 +- 2 files changed, 46 insertions(+), 26 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index d258be919b2b..9a3b706a8700 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -23,7 +23,7 @@ // MODULES // var EventEmitter = require( 'events' ).EventEmitter; -var readline = require( 'readline' ).createInterface; +var readline = require( 'readline' ); var resolve = require( 'path' ).resolve; var logger = require( 'debug' ); var inherit = require( '@stdlib/utils/inherit' ); @@ -228,7 +228,7 @@ function REPL( options ) { // Create an internal readline interface: debug( 'Creating readline interface...' ); - setNonEnumerableReadOnly( this, '_rli', readline({ + setNonEnumerableReadOnly( this, '_rli', readline.createInterface({ 'input': this._istream, 'output': this._ostream, 'terminal': opts.isTTY, @@ -236,26 +236,33 @@ function REPL( options ) { 'completer': completer( this ) })); - previewCompleter = new PreviewCompleter( this ); - this._rli.on( 'close', onClose ); this._rli.on( 'line', onLine ); this._rli.on( 'SIGINT', onSIGINT ); - // Handle drawing the gray preview - this._rli.input.on( 'keypress', previewCompleter.onKeypress.bind(previewCompleter) ); - // Add listener for "command" events: this.on( 'command', onCommand ); - // Write a welcome message: - this._ostream.write( opts.welcome ); + // If operating in "terminal" mode, initialize a preview completer... + if ( opts.isTTY ) { + // Create a new preview completer: + previewCompleter = new PreviewCompleter( this ); + + // Instruct the input stream to begin emitting "keypress" events: + readline.emitKeypressEvents( this._istream, this._rli ); + + // Add a listener for "keypress" events: + this._istream.on( 'keypress', onKeypress ); + + // Cache a reference to the private readline interface `ttyWrite` to allow calling the method when wanting default behavior: + ttyWrite = this._rli._ttyWrite; - // Keep the old ttyWrite so we can call it for default behaviour - ttyWrite = self._rli._ttyWrite.bind( self._rli ); + // Overwrite the private `ttyWrite` method to allow processing input before a "keypress" event is triggered: + this._rli._ttyWrite = beforeKeypress; // WARNING: overwriting a private property + } - // Add event listener that triggers before a key press is processed - self._rli._ttyWrite = beforeKeypress; + // Write a welcome message: + this._ostream.write( opts.welcome ); // TODO: check whether to synchronously initialize a REPL history file @@ -270,6 +277,29 @@ function REPL( options ) { } return this; + /** + * Callback invoked prior to emitting a "keypress" event. + * + * @private + * @param {string} data - input data + * @param {Object} key - key object + */ + function beforeKeypress( data, key ) { + previewCompleter.beforeKeypress( key ); + ttyWrite.call( self._rli, data, key ); + } + + /** + * Callback invoked upon an input stream "keypress" event. + * + * @private + * @param {string} data - input data + * @param {Object} key - key object + */ + function onKeypress() { + previewCompleter.onKeypress(); + } + /** * Callback invoked upon a readline interface "line" event. * @@ -283,17 +313,6 @@ function REPL( options ) { } } - /** - * Handle special cases related to preview, such as when users presses right to fill in the preview. - * @private - * @param {*} d data - * @param {*} key the key pressed - */ - function beforeKeypress( d, key ) { - previewCompleter.beforeKeypress( d, key ); - ttyWrite(d, key); - } - /** * Callback invoked upon a readline interface "close" event. * @@ -301,6 +320,8 @@ function REPL( options ) { */ function onClose() { debug( 'Readline interface closed.' ); + self._istream.removeListener( 'keypress', onKeypress ); + debug( 'Exiting REPL...' ); self.emit( 'exit' ); } diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/preview_completer.js index 46e81de30881..2305e8f79368 100644 --- a/lib/node_modules/@stdlib/repl/lib/preview_completer.js +++ b/lib/node_modules/@stdlib/repl/lib/preview_completer.js @@ -79,10 +79,9 @@ function clearPreview() { * If the user pressed 'right' while the caret is at the end of the line and a preview is * showing, the preview is filled in. * -* @param {*} _ not used * @param {Object} key the key the user pressed */ -function beforeKeypress( _, key ) { +function beforeKeypress( key ) { if ( key && ( key.name === 'return' || key.name === 'enter' ) && this.completionCache !== '' ) { if ( this._repl._rli.cursor === this._repl._rli.line.length ) { this._repl._rli.write( this.completionCache ); From e4345e443c36e2372807f48caa0c7f69dd337ca6 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 03:15:05 -0700 Subject: [PATCH 12/37] refactor: rename file --- .../repl/lib/{preview_completer.js => completer_preview.js} | 0 lib/node_modules/@stdlib/repl/lib/main.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/node_modules/@stdlib/repl/lib/{preview_completer.js => completer_preview.js} (100%) diff --git a/lib/node_modules/@stdlib/repl/lib/preview_completer.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js similarity index 100% rename from lib/node_modules/@stdlib/repl/lib/preview_completer.js rename to lib/node_modules/@stdlib/repl/lib/completer_preview.js diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index 9a3b706a8700..7006783c45ff 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -56,8 +56,8 @@ var displayPrompt = require( './display_prompt.js' ); var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); var completer = require( './completer.js' ); +var PreviewCompleter = require( './completer_preview.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); -var PreviewCompleter = require( './preview_completer.js' ); // VARIABLES // From acc1e108cce7e27ebc6ab86b399c330c538d4b2a Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 04:13:02 -0700 Subject: [PATCH 13/37] refactor: clean-up logic for clearing a completion preview --- .../@stdlib/repl/lib/completer_preview.js | 135 ++++++++++++------ lib/node_modules/@stdlib/repl/lib/main.js | 12 +- 2 files changed, 97 insertions(+), 50 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 2305e8f79368..4a1877d01e08 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -16,60 +16,87 @@ * limitations under the License. */ -/* eslint-disable no-underscore-dangle, no-invalid-this */ +/* eslint-disable no-restricted-syntax, no-underscore-dangle, no-invalid-this */ 'use strict'; // MODULES // -var rl = require( 'readline' ); +var readline = require( 'readline' ); +var logger = require( 'debug' ); +var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var repeat = require( '@stdlib/string/repeat' ); var longestCommonPrefix = require( './longest_common_prefix.js' ); +// VARIABLES // + +var debug = logger( 'repl:preview_completer' ); + + // MAIN // /** -* Handles the gray preview that displays when there is only one possible tab completion. +* Constructor for creating a preview completer. * * @private -* @param {REPL} repl repl instance -* @returns {Object} object containing two event listeners that handle drawing the preview +* @constructor +* @param {Object} rli - readline instance +* @param {Function} completer - function for generating possible completions +* @param {WritableStream} ostream - writable stream +* @returns {PreviewCompleter} completer instance */ -function PreviewCompleter( repl ) { +function PreviewCompleter( rli, completer, ostream ) { if ( !(this instanceof PreviewCompleter) ) { - return new PreviewCompleter( repl ); + return new PreviewCompleter( rli, ostream ); } + debug( 'Creating a preview completer...' ); - // This represents the preview currently being displayed. - this.completionCache = ''; + // Cache a reference to the provided readline interface: + this._rli = rli; - // Keep the repl this instance is associated with. - this._repl = repl; + // Cache a reference to the output writable stream: + this._ostream = ostream; - // Cache bound callback so it can be passed to the completer. - this.completerCallback = completerCallback.bind( this ); + // Cache a reference to the provided completer: + this._completer = completer; + + // Initialize a buffer containing the currently displayed completion preview: + this._preview = ''; return this; } -PreviewCompleter.prototype.clearPreview = clearPreview; -PreviewCompleter.prototype.beforeKeypress = beforeKeypress; -PreviewCompleter.prototype.onKeypress = onKeypress; - /** -* Clears the gray preview, so that it is no longer shown to the user. +* Clears a completion preview. +* +* @private +* @name _clear +* @memberof PreviewCompleter.prototype +* @returns {void} */ -function clearPreview() { - var charactersToEnd; - if ( this.completionCache !== '' ) { - charactersToEnd = this._repl._rli.line.length - this._repl._rli.cursor; - rl.moveCursor( this._repl._ostream, charactersToEnd ); - this._repl._rli.output.write( repeat(' ', this.completionCache.length) ); - rl.moveCursor( this._repl._ostream, -this.completionCache.length - charactersToEnd ); - this.completionCache = ''; +setNonEnumerableReadOnly( PreviewCompleter.prototype, '_clear', function clear() { + var preview = this._preview; + + // If no preview currently displayed, nothing to clear... + if ( preview === '' ) { + return; } -} + debug( 'Clearing completion preview...' ); + + // Replace the current display text with whitespace: + this._ostream.write( repeat( ' ', preview.length ) ); + + // Reset the cursor: + readline.moveCursor( this._ostream, -preview.length ); + + // Reset the completion preview buffer: + this._preview = ''; +}); + +PreviewCompleter.prototype.beforeKeypress = beforeKeypress; +PreviewCompleter.prototype.onKeypress = onKeypress; +PreviewCompleter.prototype._onCompletions = onCompletions; /** * This event fires before the keypress is processed by the readline input and ensures. @@ -82,19 +109,19 @@ function clearPreview() { * @param {Object} key the key the user pressed */ function beforeKeypress( key ) { - if ( key && ( key.name === 'return' || key.name === 'enter' ) && this.completionCache !== '' ) { - if ( this._repl._rli.cursor === this._repl._rli.line.length ) { - this._repl._rli.write( this.completionCache ); - this.completionCache = ''; + if ( key && ( key.name === 'return' || key.name === 'enter' ) && this._preview !== '' ) { + if ( this._rli.cursor === this._rli.line.length ) { + this._rli.write( this._preview ); + this._preview = ''; } else { - this.clearPreview(); + this._clear(); } } if ( key && key.name === 'right' && - this._repl._rli.cursor === this._repl._rli.line.length && - this.completionCache !== '' ) { - this._repl._rli.write( this.completionCache ); - this.completionCache = ''; + this._rli.cursor === this._rli.line.length && + this._preview !== '' ) { + this._rli.write( this._preview ); + this._preview = ''; } } @@ -102,18 +129,34 @@ function beforeKeypress( key ) { * Event listener which fires after a key is pressed. */ function onKeypress() { + var self = this; + // Get the possible tab completions from the completer - this._repl._rli.completer( this._repl._rli.line, this.completerCallback ); + this._completer( this._rli.line, clbk ); + + /** + * Callback invoked upon generating potential TAB completions. + * + * @private + * @param {(Error|null)} error - error object + * @param {Array} completions - completion results + * @returns {void} + */ + function clbk( error, completions ) { + if ( error ) { + return; + } + self._onCompletions( completions ); + } } /** * Callback function passed to the completer. -* @private * -* @param {*} _ not used +* @private * @param {*} completions the possible completions passed by the completer */ -function completerCallback( _, completions ) { +function onCompletions( completions ) { var charactersToEnd; var commonPrefix; var completion; @@ -126,20 +169,20 @@ function completerCallback( _, completions ) { commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); completion = completion.substring( commonPrefix.length ); if ( completion !== '' ) { - charactersToEnd = this._repl._rli.line.length - this._repl._rli.cursor; + charactersToEnd = this._rli.line.length - this._rli.cursor; // Write the completion at the end of the line regardless of where the cursor was - rl.moveCursor( this._repl._ostream, charactersToEnd ); + readline.moveCursor( this._ostream, charactersToEnd ); // The ASCII escape codes make the text grayed out - this._repl._rli.output.write( '\x1b[90m' + completion + '\x1b[0m' ); + this._ostream.write( '\x1b[90m' + completion + '\x1b[0m' ); // Move cursor back to original posittion - rl.moveCursor( this._repl._ostream, -completion.length - charactersToEnd ); + readline.moveCursor( this._ostream, -completion.length - charactersToEnd ); } - this.completionCache = completion; + this._preview = completion; } else { - this.clearPreview(); + this._clear(); } } diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index 7006783c45ff..ded541fd9946 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -55,7 +55,7 @@ var commands = require( './commands.js' ); var displayPrompt = require( './display_prompt.js' ); var inputPrompt = require( './input_prompt.js' ); var processLine = require( './process_line.js' ); -var completer = require( './completer.js' ); +var completerFactory = require( './completer.js' ); var PreviewCompleter = require( './completer_preview.js' ); var ALIAS_OVERRIDES = require( './alias_overrides.js' ); @@ -108,6 +108,7 @@ var debug = logger( 'repl' ); */ function REPL( options ) { var previewCompleter; + var completer; var ttyWrite; var opts; var self; @@ -226,6 +227,9 @@ function REPL( options ) { // Create a REPL execution context: setNonEnumerable( this, '_context', this.createContext() ); + // Create a new TAB completer: + completer = completerFactory( this ); + // Create an internal readline interface: debug( 'Creating readline interface...' ); setNonEnumerableReadOnly( this, '_rli', readline.createInterface({ @@ -233,7 +237,7 @@ function REPL( options ) { 'output': this._ostream, 'terminal': opts.isTTY, 'prompt': opts.inputPrompt, - 'completer': completer( this ) + 'completer': completer })); this._rli.on( 'close', onClose ); @@ -244,9 +248,9 @@ function REPL( options ) { this.on( 'command', onCommand ); // If operating in "terminal" mode, initialize a preview completer... - if ( opts.isTTY ) { + if ( this._isTTY ) { // Create a new preview completer: - previewCompleter = new PreviewCompleter( this ); + previewCompleter = new PreviewCompleter( this._rli, completer, this._ostream ); // Instruct the input stream to begin emitting "keypress" events: readline.emitKeypressEvents( this._istream, this._rli ); From 280d20a90726a2689677b900e06fccaf70ece639 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 04:37:27 -0700 Subject: [PATCH 14/37] refactor: update callback for processing completion candidates --- .../@stdlib/repl/lib/completer_preview.js | 131 ++++++++++-------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 4a1877d01e08..dfd7fbe346ce 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -61,6 +61,9 @@ function PreviewCompleter( rli, completer, ostream ) { // Cache a reference to the provided completer: this._completer = completer; + // Create a callback for processing potential preview completions: + this._onCompletions = this._completionCallback(); + // Initialize a buffer containing the currently displayed completion preview: this._preview = ''; @@ -68,14 +71,77 @@ function PreviewCompleter( rli, completer, ostream ) { } /** -* Clears a completion preview. +* Returns a callback for processing potential preview completions. * * @private -* @name _clear +* @name _completionCallback +* @memberof PreviewCompleter.prototype +* @returns {Function} completion callback +*/ +setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', function completionCallback() { + var self = this; + return clbk; + + /** + * Callback invoked upon resolving potential preview completions. + * + * @private + * @param {(Error|null)} error - error object + * @param {Array} completions - completion results + * @returns {void} + */ + function clbk( error, completions ) { + var prefix; + var list; + var line; + var N; + + // Check whether we encountered an error when generating completions... + if ( error ) { + debug( 'Encountered an error when generating completions. Unable to display a preview completion...' ); + return; + } + // If multiple completions are possible, do not display a completion preview... + list = completions[ 0 ]; + if ( list.length > 1 ) { + debug( 'Multiple potential preview completions. Unable to display a preview completion...' ); + self.clear(); + return; + } + // Extract the preview completion substring: + line = completions[ 1 ]; + prefix = longestCommonPrefix( list[ 0 ], line ); + self._preview = list[ 0 ].substring( prefix.length ); + + // If the substring is empty, nothing to display... + if ( self._preview === '' ) { + debug( 'Exact match. Unable to display a preview completion...' ); + return; + } + debug( 'Preview completion: %s', self._preview ); + + // Compute the number of character until the end of the line from the current cursor position: + N = self._rli.line.length - self._rli.cursor; + + // Move the cursor to the end of the line: + readline.moveCursor( self._ostream, N ); + + // Append the preview completion to the current line (using ASCII color escape codes for displaying grey text): + self._ostream.write( '\x1b[90m' + self._preview + '\x1b[0m' ); + + // Move the cursor back to previous position: + readline.moveCursor( self._ostream, -self._preview.length-N ); + } +}); + +/** +* Clears a completion preview. +* +* @name clear * @memberof PreviewCompleter.prototype * @returns {void} */ -setNonEnumerableReadOnly( PreviewCompleter.prototype, '_clear', function clear() { +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() { var preview = this._preview; // If no preview currently displayed, nothing to clear... @@ -96,7 +162,6 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_clear', function clear() PreviewCompleter.prototype.beforeKeypress = beforeKeypress; PreviewCompleter.prototype.onKeypress = onKeypress; -PreviewCompleter.prototype._onCompletions = onCompletions; /** * This event fires before the keypress is processed by the readline input and ensures. @@ -114,7 +179,7 @@ function beforeKeypress( key ) { this._rli.write( this._preview ); this._preview = ''; } else { - this._clear(); + this.clear(); } } if ( key && key.name === 'right' && @@ -129,61 +194,7 @@ function beforeKeypress( key ) { * Event listener which fires after a key is pressed. */ function onKeypress() { - var self = this; - - // Get the possible tab completions from the completer - this._completer( this._rli.line, clbk ); - - /** - * Callback invoked upon generating potential TAB completions. - * - * @private - * @param {(Error|null)} error - error object - * @param {Array} completions - completion results - * @returns {void} - */ - function clbk( error, completions ) { - if ( error ) { - return; - } - self._onCompletions( completions ); - } -} - -/** -* Callback function passed to the completer. -* -* @private -* @param {*} completions the possible completions passed by the completer -*/ -function onCompletions( completions ) { - var charactersToEnd; - var commonPrefix; - var completion; - - // If there is only one possible tab completion - if ( completions[ 0 ].length === 1 ) { - completion = completions[ 0 ][ 0 ]; - - // Find the part of the completion the user hasn't typed yet - commonPrefix = longestCommonPrefix( completion, completions[ 1 ] ); - completion = completion.substring( commonPrefix.length ); - if ( completion !== '' ) { - charactersToEnd = this._rli.line.length - this._rli.cursor; - - // Write the completion at the end of the line regardless of where the cursor was - readline.moveCursor( this._ostream, charactersToEnd ); - - // The ASCII escape codes make the text grayed out - this._ostream.write( '\x1b[90m' + completion + '\x1b[0m' ); - - // Move cursor back to original posittion - readline.moveCursor( this._ostream, -completion.length - charactersToEnd ); - } - this._preview = completion; - } else { - this._clear(); - } + this._completer( this._rli.line, this._onCompletions ); } From a586946b7d5b01adfd31b0350a1d604aeb3a28bd Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 04:40:29 -0700 Subject: [PATCH 15/37] refactor: inline the prototype method --- .../@stdlib/repl/lib/completer_preview.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index dfd7fbe346ce..d3a07570e8be 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -160,8 +160,18 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() this._preview = ''; }); +/** +* Callback for handling a "keypress" event. +* +* @name onKeypress +* @memberof PreviewCompleter.prototype +* @returns {void} +*/ +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress() { + this._completer( this._rli.line, this._onCompletions ); +}); + PreviewCompleter.prototype.beforeKeypress = beforeKeypress; -PreviewCompleter.prototype.onKeypress = onKeypress; /** * This event fires before the keypress is processed by the readline input and ensures. @@ -190,13 +200,6 @@ function beforeKeypress( key ) { } } -/** -* Event listener which fires after a key is pressed. -*/ -function onKeypress() { - this._completer( this._rli.line, this._onCompletions ); -} - // EXPORTS // From 06aac39497d64bf08dce159ead33d834b80bc37e Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:05:10 -0700 Subject: [PATCH 16/37] refactor: clean-up keypress handlers --- .../@stdlib/repl/lib/completer_preview.js | 58 +++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index d3a07570e8be..bbc00aaf4305 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -101,8 +101,13 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun debug( 'Encountered an error when generating completions. Unable to display a preview completion...' ); return; } - // If multiple completions are possible, do not display a completion preview... list = completions[ 0 ]; + if ( list.length === 0 ) { + debug( 'No potential preview completion. Unable to display a preview completion...' ); + self.clear(); + return; + } + // If multiple completions are possible, do not display a completion preview... if ( list.length > 1 ) { debug( 'Multiple potential preview completions. Unable to display a preview completion...' ); self.clear(); @@ -142,7 +147,10 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun * @returns {void} */ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() { - var preview = this._preview; + var preview; + var N; + + preview = this._preview; // If no preview currently displayed, nothing to clear... if ( preview === '' ) { @@ -150,11 +158,17 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() } debug( 'Clearing completion preview...' ); + // Compute the number of character until the end of the line from the current cursor position: + N = this._rli.line.length - this._rli.cursor; + + // Move the cursor to the end of the line: + readline.moveCursor( this._ostream, N ); + // Replace the current display text with whitespace: this._ostream.write( repeat( ' ', preview.length ) ); // Reset the cursor: - readline.moveCursor( this._ostream, -preview.length ); + readline.moveCursor( this._ostream, -preview.length-N ); // Reset the completion preview buffer: this._preview = ''; @@ -171,34 +185,32 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onK this._completer( this._rli.line, this._onCompletions ); }); -PreviewCompleter.prototype.beforeKeypress = beforeKeypress; - /** -* This event fires before the keypress is processed by the readline input and ensures. -* the following interactions: -* If the user pressed 'return' while a preview is showing, and the user has the cursor -* at the end of the line, the preview is filled in before the line is executed. -* If the user pressed 'right' while the caret is at the end of the line and a preview is -* showing, the preview is filled in. +* Callback which should be invoked **before** a "keypress" event is processed by a readline interface. * -* @param {Object} key the key the user pressed +* @name beforeKeypress +* @memberof PreviewCompleter.prototype +* @param {Object} key - key object +* @returns {void} */ -function beforeKeypress( key ) { - if ( key && ( key.name === 'return' || key.name === 'enter' ) && this._preview !== '' ) { - if ( this._rli.cursor === this._rli.line.length ) { - this._rli.write( this._preview ); - this._preview = ''; - } else { - this.clear(); +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function beforeKeypress( key ) { + if ( !key || this._preview === '' ) { + return; + } + // 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' ) { + return this.clear(); } + return; } - if ( key && key.name === 'right' && - this._rli.cursor === this._rli.line.length && - this._preview !== '' ) { + // When the user is at the end of the line, auto-complete the line with the preview completion 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' ) { this._rli.write( this._preview ); this._preview = ''; } -} +}); // EXPORTS // From 2792326f5cb572d18ba6eecf650c07938aa5f998 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:07:53 -0700 Subject: [PATCH 17/37] test: rename file --- .../repl/test/{preview_test.js => test.completer_preview.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename lib/node_modules/@stdlib/repl/test/{preview_test.js => test.completer_preview.js} (98%) diff --git a/lib/node_modules/@stdlib/repl/test/preview_test.js b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js similarity index 98% rename from lib/node_modules/@stdlib/repl/test/preview_test.js rename to lib/node_modules/@stdlib/repl/test/test.completer_preview.js index c1765ecee719..d61f616e6472 100644 --- a/lib/node_modules/@stdlib/repl/test/preview_test.js +++ b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js @@ -23,7 +23,7 @@ var tape = require( 'tape' ); var InspectStream = require( '@stdlib/streams/node/inspect' ); var noop = require( '@stdlib/utils/noop' ); -var previewCompleter = require( './../lib/preview_completer.js' ); +var previewCompleter = require( './../lib/completer_preview.js' ); var REPL = require( './../lib' ); From caf9295eff313941b642776550393bd0db4251c7 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:23:24 -0700 Subject: [PATCH 18/37] fix: add missing argument --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index bbc00aaf4305..1eb70a3ec379 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -48,7 +48,7 @@ var debug = logger( 'repl:preview_completer' ); */ function PreviewCompleter( rli, completer, ostream ) { if ( !(this instanceof PreviewCompleter) ) { - return new PreviewCompleter( rli, ostream ); + return new PreviewCompleter( rli, completer, ostream ); } debug( 'Creating a preview completer...' ); From 8fe54aa93e11848fbbf3add643d54a139a53479a Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:26:42 -0700 Subject: [PATCH 19/37] refactor: add debug statements --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 1eb70a3ec379..ab97dfa28e4b 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -201,12 +201,14 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function 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(); } return; } // When the user is at the end of the line, auto-complete the line with the preview completion 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 = ''; } From 026fd3c4bb73d9bb892a7a9638938b6ff322a8d8 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:29:50 -0700 Subject: [PATCH 20/37] refactor: update debug messages --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index ab97dfa28e4b..a2a9bf5b667c 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -98,18 +98,18 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun // Check whether we encountered an error when generating completions... if ( error ) { - debug( 'Encountered an error when generating completions. Unable to display a preview completion...' ); + debug( 'Encountered an error when generating completions. Unable to display a preview completion.' ); return; } list = completions[ 0 ]; if ( list.length === 0 ) { - debug( 'No potential preview completion. Unable to display a preview completion...' ); + debug( 'No potential preview completions. Unable to display a preview completion.' ); self.clear(); return; } // If multiple completions are possible, do not display a completion preview... if ( list.length > 1 ) { - debug( 'Multiple potential preview completions. Unable to display a preview completion...' ); + debug( 'Multiple potential preview completions. Unable to display a preview completion.' ); self.clear(); return; } @@ -120,7 +120,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun // If the substring is empty, nothing to display... if ( self._preview === '' ) { - debug( 'Exact match. Unable to display a preview completion...' ); + debug( 'Exact match. No preview completion to display.' ); return; } debug( 'Preview completion: %s', self._preview ); From 915a596731f59ba64c695f9c47b13b65fe76894f Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:31:58 -0700 Subject: [PATCH 21/37] docs: update comments and debug messages --- .../@stdlib/repl/lib/completer_preview.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index a2a9bf5b667c..6e292157db47 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -61,7 +61,7 @@ function PreviewCompleter( rli, completer, ostream ) { // Cache a reference to the provided completer: this._completer = completer; - // Create a callback for processing potential preview completions: + // Create a callback for processing potential completion previews: this._onCompletions = this._completionCallback(); // Initialize a buffer containing the currently displayed completion preview: @@ -71,7 +71,7 @@ function PreviewCompleter( rli, completer, ostream ) { } /** -* Returns a callback for processing potential preview completions. +* Returns a callback for processing potential completion previews. * * @private * @name _completionCallback @@ -83,7 +83,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun return clbk; /** - * Callback invoked upon resolving potential preview completions. + * Callback invoked upon resolving potential completion previews. * * @private * @param {(Error|null)} error - error object @@ -98,32 +98,32 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun // Check whether we encountered an error when generating completions... if ( error ) { - debug( 'Encountered an error when generating completions. Unable to display a preview completion.' ); + debug( 'Encountered an error when generating completions. Unable to display a completion preview.' ); return; } list = completions[ 0 ]; if ( list.length === 0 ) { - debug( 'No potential preview completions. Unable to display a preview completion.' ); + debug( 'No potential completion previews. Unable to display a completion preview.' ); self.clear(); return; } // If multiple completions are possible, do not display a completion preview... if ( list.length > 1 ) { - debug( 'Multiple potential preview completions. Unable to display a preview completion.' ); + debug( 'Multiple potential completion previews. Unable to display a completion preview.' ); self.clear(); return; } - // Extract the preview completion substring: + // Extract the completion preview substring: line = completions[ 1 ]; prefix = longestCommonPrefix( list[ 0 ], line ); self._preview = list[ 0 ].substring( prefix.length ); // If the substring is empty, nothing to display... if ( self._preview === '' ) { - debug( 'Exact match. No preview completion to display.' ); + debug( 'Exact match. No completion preview to display.' ); return; } - debug( 'Preview completion: %s', self._preview ); + debug( 'Completion preview: %s', self._preview ); // Compute the number of character until the end of the line from the current cursor position: N = self._rli.line.length - self._rli.cursor; @@ -131,7 +131,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun // Move the cursor to the end of the line: readline.moveCursor( self._ostream, N ); - // Append the preview completion to the current line (using ASCII color escape codes for displaying grey text): + // Append the completion preview to the current line (using ASCII color escape codes for displaying grey text): self._ostream.write( '\x1b[90m' + self._preview + '\x1b[0m' ); // Move the cursor back to previous position: @@ -206,7 +206,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function } return; } - // When the user is at the end of the line, auto-complete the line with the preview completion when a user presses RETURN or the RIGHT arrow key (note: pressing ENTER will result in both completion AND execution)... + // 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 ); From 47b53cf0183caed96af3ef7ca69bc853c0b997bd Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:33:29 -0700 Subject: [PATCH 22/37] refactor: update debug messages --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 6e292157db47..b7e5a0cc3cd4 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -103,13 +103,13 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun } list = completions[ 0 ]; if ( list.length === 0 ) { - debug( 'No potential completion previews. Unable to display a completion preview.' ); + debug( 'Unable to display a completion preview. No potential completion previews.' ); self.clear(); return; } // If multiple completions are possible, do not display a completion preview... if ( list.length > 1 ) { - debug( 'Multiple potential completion previews. Unable to display a completion preview.' ); + debug( 'Unable to display a completion preview. Multiple potential completion previews.' ); self.clear(); return; } @@ -120,7 +120,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun // If the substring is empty, nothing to display... if ( self._preview === '' ) { - debug( 'Exact match. No completion preview to display.' ); + debug( 'Unable to display a completion preview. Exact match.' ); return; } debug( 'Completion preview: %s', self._preview ); From 6afbb5522da3c06d8695ef77690da3651b5adb91 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 05:34:17 -0700 Subject: [PATCH 23/37] refactor: update debug messages --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index b7e5a0cc3cd4..68bf92f42b5a 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -103,13 +103,13 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun } list = completions[ 0 ]; if ( list.length === 0 ) { - debug( 'Unable to display a completion preview. No potential completion previews.' ); + debug( 'Unable to display a completion preview. No completion preview candidates.' ); self.clear(); return; } // If multiple completions are possible, do not display a completion preview... if ( list.length > 1 ) { - debug( 'Unable to display a completion preview. Multiple potential completion previews.' ); + debug( 'Unable to display a completion preview. Multiple completion preview candidates.' ); self.clear(); return; } From a25e60dc602fdd12f2845d7df64f5bb608f1f5d7 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Sat, 16 Mar 2024 17:04:32 +0100 Subject: [PATCH 24/37] fix: don't show preview when line has trailing space Fixes a bug where the preview would still show after user pressed space. --- .../@stdlib/repl/lib/completer_preview.js | 9 ++++++ .../repl/test/test.completer_preview.js | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 68bf92f42b5a..d943d107c56f 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -26,6 +26,7 @@ var readline = require( 'readline' ); var logger = require( 'debug' ); var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var repeat = require( '@stdlib/string/repeat' ); +var endsWith = require( '@stdlib/string/ends-with' ); var longestCommonPrefix = require( './longest_common_prefix.js' ); @@ -113,6 +114,14 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun self.clear(); return; } + + // If line ends in a trailling space, do not display a completion preview... + if ( endsWith( self._rli.line, ' ' ) ) { + debug( 'Line ends with a trailing space. Unable to display a preview completion...' ); + self.clear(); + return; + } + // Extract the completion preview substring: line = completions[ 1 ]; prefix = longestCommonPrefix( list[ 0 ], line ); diff --git a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js index d61f616e6472..de5b11170fc0 100644 --- a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js +++ b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js @@ -218,3 +218,32 @@ tape( 'Preview is filled in when user presses return', function test( t ) { r.close(); t.end(); }); + +tape( 'Preview is erased after user presses space', function test( t ) { + var outputStream; + var inputStream; + var outputs; + var opts; + var r; + + inputStream = new InspectStream( noop ); + outputStream = new InspectStream( outputLog ); + + outputs = []; + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } + + opts = { + 'input': inputStream, + 'output': outputStream, + 'isTTY': true + }; + r = new REPL( opts ); + inputStream.write( 'conso' ); + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + inputStream.write( ' ' ); + t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); + r.close(); + t.end(); +} ); From e110c2c78181cd8419649b67f42c4518b315ca88 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 13:28:13 -0700 Subject: [PATCH 25/37] refactor: avoid executing completion logic when user enters whitespace --- .../@stdlib/repl/lib/completer_preview.js | 19 +++++++++---------- lib/node_modules/@stdlib/repl/lib/main.js | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index d943d107c56f..5aaa068914e3 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -26,7 +26,7 @@ var readline = require( 'readline' ); var logger = require( 'debug' ); var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var repeat = require( '@stdlib/string/repeat' ); -var endsWith = require( '@stdlib/string/ends-with' ); +var RE_WHITESPACE = require( '@stdlib/regexp/whitespace' ).REGEXP; var longestCommonPrefix = require( './longest_common_prefix.js' ); @@ -114,14 +114,6 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun self.clear(); return; } - - // If line ends in a trailling space, do not display a completion preview... - if ( endsWith( self._rli.line, ' ' ) ) { - debug( 'Line ends with a trailing space. Unable to display a preview completion...' ); - self.clear(); - return; - } - // Extract the completion preview substring: line = completions[ 1 ]; prefix = longestCommonPrefix( list[ 0 ], line ); @@ -188,9 +180,16 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() * * @name onKeypress * @memberof PreviewCompleter.prototype +* @param {string} data - input data * @returns {void} */ -setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress() { +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress( data ) { + // When a user enters whitespace characters, assume that this means the beginning of new input and remove the existing completion preview (if present)... + if ( RE_WHITESPACE.test( data ) ) { + debug( 'Line ends with a trailing whitespace. Unable to display a preview completion...' ); + this.clear(); + return; + } this._completer( this._rli.line, this._onCompletions ); }); diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index ded541fd9946..b24c4033bdc0 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -300,8 +300,8 @@ function REPL( options ) { * @param {string} data - input data * @param {Object} key - key object */ - function onKeypress() { - previewCompleter.onKeypress(); + function onKeypress( data ) { + previewCompleter.onKeypress( data ); } /** From b6976b5122389a60cbc53de3b85b443ba75373ba Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 13:34:21 -0700 Subject: [PATCH 26/37] refactor: use try/catch to prevent REPL crash This commit addresses a bug wherein, if a user types `var x = [` and hits TAB, the REPL crashes, as it assumes that the expression is a valid expression. --- lib/node_modules/@stdlib/repl/lib/completer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer.js b/lib/node_modules/@stdlib/repl/lib/completer.js index a8ca2b851b2c..5782bdbb8225 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer.js +++ b/lib/node_modules/@stdlib/repl/lib/completer.js @@ -163,7 +163,12 @@ function completer( repl ) { return clbk( null, [ res, line ] ); } debug( 'Attempting to complete an incomplete expression.' ); - line = completeExpression( res, repl._context, line ); + try { + line = completeExpression( res, repl._context, line ); + } catch ( err ) { + debug( 'Unable to complete incomplete expression. Error: %s', err.message ); + return clbk( null, [ res, line ] ); + } res = normalize( res ); debug( 'Results: %s', res.join( ', ' ) ); return clbk( null, [ res, line ] ); From 7adbdff93572459ff3032445de8e7a6f6d00b4ee Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 14:31:28 -0700 Subject: [PATCH 27/37] fix: handle case when hitting TAB for line starting with `[` bracket --- .../@stdlib/repl/lib/complete_walk_find_last.js | 4 ++++ lib/node_modules/@stdlib/repl/lib/completer.js | 7 +------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js index 5eb4c1ead097..388f81571430 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js @@ -124,6 +124,10 @@ function walk( node ) { // eslint-disable-line max-lines-per-function break; case 'ArrayExpression': // `[ <|>` || `[ foo<|>` || `[ 1, 2, <|>` || `[ 1, 2, foo<|>` || etc + if ( node.elements.length === 0 ) { + FLG = false; + break; + } node = node.elements[ node.elements.length-1 ]; break; case 'ForStatement': diff --git a/lib/node_modules/@stdlib/repl/lib/completer.js b/lib/node_modules/@stdlib/repl/lib/completer.js index 5782bdbb8225..a8ca2b851b2c 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer.js +++ b/lib/node_modules/@stdlib/repl/lib/completer.js @@ -163,12 +163,7 @@ function completer( repl ) { return clbk( null, [ res, line ] ); } debug( 'Attempting to complete an incomplete expression.' ); - try { - line = completeExpression( res, repl._context, line ); - } catch ( err ) { - debug( 'Unable to complete incomplete expression. Error: %s', err.message ); - return clbk( null, [ res, line ] ); - } + line = completeExpression( res, repl._context, line ); res = normalize( res ); debug( 'Results: %s', res.join( ', ' ) ); return clbk( null, [ res, line ] ); From dd4bc3c1e30499402ed9ad9c09e555d17088da83 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 15:02:51 -0700 Subject: [PATCH 28/37] fix: handle additional completion edge cases --- lib/node_modules/@stdlib/repl/lib/complete_expression.js | 5 +++++ .../@stdlib/repl/lib/complete_walk_find_last.js | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_expression.js b/lib/node_modules/@stdlib/repl/lib/complete_expression.js index 9d718c546004..f60938f90d37 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_expression.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_expression.js @@ -25,6 +25,7 @@ var logger = require( 'debug' ); var parse = require( 'acorn-loose' ).parse; var objectKeys = require( '@stdlib/utils/keys' ); var trim = require( '@stdlib/string/trim' ); +var trimRight = require( '@stdlib/string/right-trim' ); var hasOwnProp = require( '@stdlib/assert/has-own-property' ); var propertyNamesIn = require( '@stdlib/utils/property-names-in' ); var filterByPrefix = require( './filter_by_prefix.js' ); @@ -204,6 +205,10 @@ function complete( out, context, expression ) { } // Case: `foo.bar<|>` else { + // Case: `foo.bar <|>` + if ( trimRight( expression ) !== expression ) { + return ''; + } filter = node.property.name; } debug( 'Property auto-completion. Filter: %s', filter ); diff --git a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js index 388f81571430..c94fa416eb08 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js @@ -378,6 +378,10 @@ function walk( node ) { // eslint-disable-line max-lines-per-function node = node.handler.body; break; case 'TemplateLiteral': + if ( node.expressions.length === 0 ) { + FLG = false; + break; + } node = node.expressions[ node.expressions.length-1 ]; break; case 'SpreadElement': @@ -386,6 +390,10 @@ function walk( node ) { // eslint-disable-line max-lines-per-function break; case 'ObjectExpression': // `{ 'a': 1, ...<|>` || `{ 'a': 1, ...foo<|>` + if ( node.properties.length === 0 ) { + FLG = false; + break; + } node = node.properties[ node.properties.length-1 ]; break; default: From 9b0f547124f0959acae01888eb81c83e054a41a5 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 15:06:01 -0700 Subject: [PATCH 29/37] docs: add comments --- lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js index c94fa416eb08..8553ac5b020b 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_walk_find_last.js @@ -378,6 +378,7 @@ function walk( node ) { // eslint-disable-line max-lines-per-function node = node.handler.body; break; case 'TemplateLiteral': + // ``<|> if ( node.expressions.length === 0 ) { FLG = false; break; @@ -389,11 +390,12 @@ function walk( node ) { // eslint-disable-line max-lines-per-function node = node.argument; break; case 'ObjectExpression': - // `{ 'a': 1, ...<|>` || `{ 'a': 1, ...foo<|>` + // `{<|>` if ( node.properties.length === 0 ) { FLG = false; break; } + // `{ 'a': 1, ...<|>` || `{ 'a': 1, ...foo<|>` node = node.properties[ node.properties.length-1 ]; break; default: From 3872cd5f85ed66244f0ba8b250c169c21450cc6d Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 15:13:54 -0700 Subject: [PATCH 30/37] refactor: remove obsolete logic --- .../@stdlib/repl/lib/completer_preview.js | 13 ++++--------- lib/node_modules/@stdlib/repl/lib/main.js | 6 +++--- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 5aaa068914e3..c5c3179a5a6f 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -26,7 +26,6 @@ var readline = require( 'readline' ); var logger = require( 'debug' ); var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var repeat = require( '@stdlib/string/repeat' ); -var RE_WHITESPACE = require( '@stdlib/regexp/whitespace' ).REGEXP; var longestCommonPrefix = require( './longest_common_prefix.js' ); @@ -181,15 +180,10 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'clear', function clear() * @name onKeypress * @memberof PreviewCompleter.prototype * @param {string} data - input data +* @param {Object} key - key object * @returns {void} */ -setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress( data ) { - // When a user enters whitespace characters, assume that this means the beginning of new input and remove the existing completion preview (if present)... - if ( RE_WHITESPACE.test( data ) ) { - debug( 'Line ends with a trailing whitespace. Unable to display a preview completion...' ); - this.clear(); - return; - } +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onKeypress() { this._completer( this._rli.line, this._onCompletions ); }); @@ -198,10 +192,11 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onK * * @name beforeKeypress * @memberof PreviewCompleter.prototype +* @param {string} data - input data * @param {Object} key - key object * @returns {void} */ -setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function beforeKeypress( key ) { +setNonEnumerableReadOnly( PreviewCompleter.prototype, 'beforeKeypress', function beforeKeypress( data, key ) { if ( !key || this._preview === '' ) { return; } diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index b24c4033bdc0..e002c41c260f 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -289,7 +289,7 @@ function REPL( options ) { * @param {Object} key - key object */ function beforeKeypress( data, key ) { - previewCompleter.beforeKeypress( key ); + previewCompleter.beforeKeypress( data, key ); ttyWrite.call( self._rli, data, key ); } @@ -300,8 +300,8 @@ function REPL( options ) { * @param {string} data - input data * @param {Object} key - key object */ - function onKeypress( data ) { - previewCompleter.onKeypress( data ); + function onKeypress( data, key ) { + previewCompleter.onKeypress( data, key ); } /** From 206ce025dadb200b7c9995d581964ec2627a6dd9 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Sat, 16 Mar 2024 15:23:38 -0700 Subject: [PATCH 31/37] fix: handle trailing whitespace for top-level identifiers --- lib/node_modules/@stdlib/repl/lib/complete_expression.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_expression.js b/lib/node_modules/@stdlib/repl/lib/complete_expression.js index f60938f90d37..64b664344fad 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_expression.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_expression.js @@ -94,6 +94,10 @@ function complete( out, context, expression ) { } // Case: `foo<|>` (completing an identifier at the top-level) if ( node.type === 'ExpressionStatement' && node.expression.type === 'Identifier' ) { + // Case: `conso <|>` + if ( trimRight( expression ) !== expression ) { + return ''; + } filter = node.expression.name; debug( 'Identifier auto-completion. Filter: %s', filter ); out = filterByPrefix( out, RESERVED_KEYWORDS_COMMON, filter ); From 8bbb5d1679d418b554796b0e633a9658f7c372c2 Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Mon, 18 Mar 2024 10:33:51 +0100 Subject: [PATCH 32/37] refactor: clean up repl tests Extract the logic for setting up the repl with the debug streams into a separate fixture. Also, add comments to the tests. --- .../@stdlib/repl/test/fixtures/repl_debug.js | 92 ++++++ .../repl/test/test.completer_preview.js | 276 +++++++++--------- 2 files changed, 228 insertions(+), 140 deletions(-) create mode 100644 lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js diff --git a/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js b/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js new file mode 100644 index 000000000000..13ada80a2697 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js @@ -0,0 +1,92 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2024 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* eslint-disable no-new */ + +'use strict'; + +// MODULES // + +var InspectStreamSink = require( '@stdlib/streams/node/inspect-sink' ); +var REPL = require( '@stdlib/repl' ); + + +// MAIN // + +/** +* Creates a REPL instance connected to debugging IO streams. +* +* Takes an writable stream as a parameter and passes the input written to that +* stream to the repl instance, as if the user was typing it. +* As the commands are evaluated, any output written by the repl will be stored as +* strings in the array that this function returns. +* +* @param {Writable} inputStream - input stream passed to the repl instance +* @param {Object} opts - additional options passed to the repl +* @returns {Array} - array of strings in which the repl output is stored +* +* @example +* var inputStream; +* var outputs; +* inputStream = new DebugStream({ +* 'name': 'repl-input-stream' +* }); +* +* // Create a repl taking input from inputStream and storing the output in outputs. +* outputs = replDebug( inputStream, { 'isTTY': true } ); + +* // Simulate writing 'console.log( "hello" )' to the repl and pressing enter. +* inputStream.write( 'console.log( "hello" )\n' ); +* // outputs[outputs.length - 1] equals 'hello\n' +*/ +function replDebug( inputStream, opts ) { + var outputStream; + var outputs; + + // Create the initially empty array where the REPL output is stored: + outputs = []; + + // Create the output stream, passing a function which will log the outputs. + outputStream = new InspectStreamSink( outputLog ); + + // If no additional options are passed, assign opts to empty object... + if ( opts === undefined ) { + opts = {}; + } + + // Pass the debug input streams to the repl, along any additional options. + opts[ 'input' ] = inputStream; + opts[ 'output' ] = outputStream; + + new REPL( opts ); + + return outputs; + + /** + * Adds the content written to the output stream to the array containing the outputs. + * + * @private + * @param {string} chunk - the new content written to the output stream + * @returns {void} + */ + function outputLog( chunk ) { + outputs.push( chunk.toString() ); + } +} + +module.exports = replDebug; diff --git a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js index de5b11170fc0..73ce3aa4340a 100644 --- a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js +++ b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js @@ -21,229 +21,225 @@ // MODULES // var tape = require( 'tape' ); -var InspectStream = require( '@stdlib/streams/node/inspect' ); -var noop = require( '@stdlib/utils/noop' ); +var DebugStream = require( '@stdlib/streams/node/debug' ); var previewCompleter = require( './../lib/completer_preview.js' ); -var REPL = require( './../lib' ); + + +// FIXTURES // + +var replDebug = require( './fixtures/repl_debug.js' ); // TESTS // -tape( 'main export is an object', function test( t ) { +tape( 'main export is a function', function test( t ) { t.ok( true, __filename ); - t.strictEqual( typeof previewCompleter, 'function', 'main export is an object' ); + t.strictEqual( typeof previewCompleter, 'function', 'main export is a function' ); t.strictEqual( typeof previewCompleter().beforeKeypress, 'function' ); t.strictEqual( typeof previewCompleter().onKeypress, 'function' ); t.end(); }); tape( 'Preview completion is shown', function test( t ) { - var outputStream; var inputStream; var outputs; - var opts; - var r; - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - outputs = []; - function outputLog(chunk) { - outputs.push(chunk.toString()); - } - - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write('conso'); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + // Write the beginning of the variable's name: + inputStream.write( 'abcdef' ); + + // Check that the completion 'gh' is written to the console... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); + + // Check that the cursor is moved back two positions, to where it was originally... t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - r.close(); t.end(); }); -tape( 'Preview completion is shown when word is not first in the line', function test( t ) { - var outputStream; +tape( 'Preview completion is shown even with other words at the beginning of the line', function test( t ) { var inputStream; var outputs; - var opts; - var r; - - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write('this is a prefix console.ta'); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); + + // Write some other words before the beginning of the variable's name: + inputStream.write('this is a prefix abcde'); - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mble\x1b[0m' ); + // Check that the completion "fgh" is shown, even with the extra text at the beginning of the line... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mfgh\x1b[0m' ); t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); - r.close(); t.end(); }); -tape( 'Preview completion is shown when the cursor moves to the left', function test( t ) { - var outputStream; +tape( 'Preview completion is shown even after cursor is moved', function test( t ) { var inputStream; var outputs; - var opts; - var r; - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } - - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write( 'console.l' ); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); + + // Write the beginning of the variable's name: + inputStream.write( 'abcdef' ); + + // Check that the preview "gh" is shown... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); + + // Check that the cursor is moved back to its orginal position... + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - // Move cursor to the left + // Move cursor to the left by one position: inputStream.write( '\x1b[1D' ); - t.strictEqual( outputs[ outputs.length - 6 ], '\x1b[90mog\x1b[0m' ); - t.strictEqual( outputs[ outputs.length - 5 ], '\x1b[2D' ); - t.strictEqual( outputs[ outputs.length - 4 ], '\x1b[1D' ); + + // Check that the cursor is moved forward, to the end of the line t.strictEqual( outputs[ outputs.length - 3 ], '\x1b[1C' ); - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mog\x1b[0m' ); + + // Check that the preview "gh" is shown... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); + + // Check that the cursor is moved back 3 positions, to where it was originally... t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); - r.close(); t.end(); }); -tape( 'Preview is erased when user types something else', function test( t ) { - var outputStream; +tape( 'Preview is erased when new input makes the preview invalid', function test( t ) { var inputStream; var outputs; - var opts; - var r; - - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write( 'conso' ); - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); + + // Write the beginning of the variable's name: + inputStream.write( 'abcdef' ); + + // Check that the preview is shown... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); + + // Write a character that does not correspond to the variable's name: inputStream.write( 'o' ); - // Preview is replaced with empty spaces + // Check that preview is replaced with empty spaces... t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - r.close(); t.end(); }); tape( 'Preview is filled in when user moves cursor into it', function test( t ) { - var outputStream; var inputStream; var outputs; - var opts; - var r; - - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write( 'conso' ); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); + + // Write the beginning of the variable's name: + inputStream.write( 'abcdef' ); + + // Move cursor to the right by one space, which moves it into the preview: inputStream.write( '\x1b[1C' ); - t.strictEqual( outputs[ outputs.length - 1 ], 'le' ); - r.close(); + + // Check the preview 'gh' was filled in... + t.strictEqual( outputs[ outputs.length - 1 ], 'gh' ); t.end(); }); -tape( 'Preview is filled in when user presses return', function test( t ) { - var outputStream; +tape( 'Preview is filled in when user executes line', function test( t ) { var inputStream; var outputs; - var opts; - var r; - - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); + }); + + // Simulate writing 'console.l' and pressing submitting the line: inputStream.write( 'console.l\n' ); + + // Check that the completion 'og' was filled in... t.strictEqual( outputs[ outputs.length - 3 ], 'og' ); + + // Check the output is that of executing the line 'console.log'... t.strictEqual( outputs[ outputs.length - 1 ].trim(), 'Out[1]: [Function: log]' ); - r.close(); t.end(); }); -tape( 'Preview is erased after user presses space', function test( t ) { - var outputStream; +tape( 'Preview is erased after user enters white space', function test( t ) { var inputStream; var outputs; - var opts; - var r; - inputStream = new InspectStream( noop ); - outputStream = new InspectStream( outputLog ); + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); - outputs = []; - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } - - opts = { - 'input': inputStream, - 'output': outputStream, + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { 'isTTY': true - }; - r = new REPL( opts ); - inputStream.write( 'conso' ); - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mle\x1b[0m' ); + }); + + // Declare a variable with an unique name, to prevent collisions with other packages: + inputStream.write( 'var abcdefgh = 1\n' ); + + // Write the beginning of the variable's name: + inputStream.write( 'abcdef' ); + + // Check that the preview is shown... + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); + + // Write a white space character inputStream.write( ' ' ); + + // Check that the preview 'gh' was erased (replaced with white space) after the user entered white space... t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); - r.close(); t.end(); -} ); +}); From d1c0d6e639903953496706a4516fac65f0ac97af Mon Sep 17 00:00:00 2001 From: Tudor Stefan Pagu Date: Mon, 18 Mar 2024 19:06:00 +0100 Subject: [PATCH 33/37] feat: show common prefix of completion candidates If there are multiple completion candidates, but they all have some non-empty common prefix, that prefix will now be shown as a preview. This works well with the tab completion which also fills in the common prefix when the user presses tab. --- .../@stdlib/repl/lib/completer_preview.js | 23 +++++++++---- .../repl/test/test.completer_preview.js | 32 +++++++++++++++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index c5c3179a5a6f..5a47dba9d0bd 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -91,6 +91,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun * @returns {void} */ function clbk( error, completions ) { + var completionCandidate; var prefix; var list; var line; @@ -107,16 +108,26 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun self.clear(); return; } - // If multiple completions are possible, do not display a completion preview... + + // If there are multiple possible completions, check if they have a common prefix... if ( list.length > 1 ) { - debug( 'Unable to display a completion preview. Multiple completion preview candidates.' ); - self.clear(); - return; + completionCandidate = longestCommonPrefix( list ); + + // If the completion candidates have no common prefix, no completion preview can be displayed... + if ( completionCandidate === '' ) { + debug( 'Unable to display a completion preview. Completion candidates have no common prefix.' ); + return; + } + } + // If there is only one possible completion, set it as the completion candidate... + else { + completionCandidate = list[ 0 ]; } + // Extract the completion preview substring: line = completions[ 1 ]; - prefix = longestCommonPrefix( list[ 0 ], line ); - self._preview = list[ 0 ].substring( prefix.length ); + prefix = longestCommonPrefix( completionCandidate, line ); + self._preview = completionCandidate.substring( prefix.length ); // If the substring is empty, nothing to display... if ( self._preview === '' ) { diff --git a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js index 73ce3aa4340a..0ce2c0b85424 100644 --- a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js +++ b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js @@ -243,3 +243,35 @@ tape( 'Preview is erased after user enters white space', function test( t ) { t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); t.end(); }); + +tape( 'Common prefix of completion candidates is shown', function test( t ) { + var inputStream; + var inputString; + var outputs; + + inputStream = new DebugStream({ + 'name': 'repl-input-stream' + }); + + // Create a repl which takes input from inputStream and which stores the output in outputs + outputs = replDebug( inputStream, { + 'isTTY': true + }); + + // Simulate declaring two variables with a long common prefix... + inputString = 'var test_var_name_1 = 1\n' + + 'var test_var_name_2 = 1\n'; + + // Write to the repl and pass a callback which is called after the input is fully processed... + inputStream.write( inputString, function check() { + // Begin writing a prefix common to both variable names: + inputStream.write('test_var_'); + + // Verify that the common prefix 'name_' is shown + t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mname_\x1b[0m' ); + + // Verify that the cursor is sent back to its original position + t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[5D' ); + t.end(); + }); +}); From bc416a8e5b1acd9184b61636b794f7a23bbf71d3 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 18 Mar 2024 21:06:14 -0700 Subject: [PATCH 34/37] refactor: inline operations and update comments --- .../@stdlib/repl/lib/completer_preview.js | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 5a47dba9d0bd..527033962b77 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -26,7 +26,7 @@ var readline = require( 'readline' ); var logger = require( 'debug' ); var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' ); var repeat = require( '@stdlib/string/repeat' ); -var longestCommonPrefix = require( './longest_common_prefix.js' ); +var commonPrefix = require( './longest_common_prefix.js' ); // VARIABLES // @@ -91,10 +91,8 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun * @returns {void} */ function clbk( error, completions ) { - var completionCandidate; var prefix; var list; - var line; var N; // Check whether we encountered an error when generating completions... @@ -108,26 +106,16 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun self.clear(); return; } + // Resolve a common prefix from the completion results: + prefix = commonPrefix( list ); // e.g., [ 'back', 'background', 'backward' ] => 'back' - // If there are multiple possible completions, check if they have a common prefix... - if ( list.length > 1 ) { - completionCandidate = longestCommonPrefix( list ); - - // If the completion candidates have no common prefix, no completion preview can be displayed... - if ( completionCandidate === '' ) { - debug( 'Unable to display a completion preview. Completion candidates have no common prefix.' ); - return; - } - } - // If there is only one possible completion, set it as the completion candidate... - else { - completionCandidate = list[ 0 ]; + // If the completion candidates do not have a common prefix, no completion preview to display, as we do not have a criteria for choosing one candidate over another... + if ( prefix === '' ) { + debug( 'Unable to display a completion preview. Completion candidates have no common prefix.' ); + return; } - - // Extract the completion preview substring: - line = completions[ 1 ]; - prefix = longestCommonPrefix( completionCandidate, line ); - self._preview = completionCandidate.substring( prefix.length ); + // Extract the completion preview substring (e.g., if the current line is 'ba', preview should be 'ck'): + self._preview = prefix.substring( commonPrefix( prefix, completions[ 1 ] ).length ); // eslint-disable-line max-len // If the substring is empty, nothing to display... if ( self._preview === '' ) { From 7a6590084a04a61a3df8235aa9c817637e70cae0 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 18 Mar 2024 21:59:53 -0700 Subject: [PATCH 35/37] refactor: rename debug namespace --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 527033962b77..503de9508810 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -31,7 +31,7 @@ var commonPrefix = require( './longest_common_prefix.js' ); // VARIABLES // -var debug = logger( 'repl:preview_completer' ); +var debug = logger( 'repl:completer:preview' ); // MAIN // From 273c2b9f77f8940099c74e737cb9c33ee5f86598 Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 25 Mar 2024 00:43:33 -0700 Subject: [PATCH 36/37] docs: update comment --- lib/node_modules/@stdlib/repl/lib/completer_preview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 503de9508810..59d471159610 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -124,7 +124,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun } debug( 'Completion preview: %s', self._preview ); - // Compute the number of character until the end of the line from the current cursor position: + // Compute the number of characters until the end of the line from the current cursor position: N = self._rli.line.length - self._rli.cursor; // Move the cursor to the end of the line: From 2721cba4c234352c75fd6ece973df0a0ccf946bb Mon Sep 17 00:00:00 2001 From: Athan Reines Date: Mon, 25 Mar 2024 16:43:26 -0700 Subject: [PATCH 37/37] test: refactor tests and move to an 'integration' folder --- .../@stdlib/repl/lib/completer_preview.js | 2 +- .../@stdlib/repl/test/fixtures/repl.js | 149 +++++++ .../@stdlib/repl/test/fixtures/repl_debug.js | 92 ---- .../integration/test.completion_previews.js | 408 ++++++++++++++++++ .../repl/test/test.completer_preview.js | 277 ------------ 5 files changed, 558 insertions(+), 370 deletions(-) create mode 100644 lib/node_modules/@stdlib/repl/test/fixtures/repl.js delete mode 100644 lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js create mode 100644 lib/node_modules/@stdlib/repl/test/integration/test.completion_previews.js delete mode 100644 lib/node_modules/@stdlib/repl/test/test.completer_preview.js diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 59d471159610..59d4ae1efdcb 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -131,7 +131,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, '_completionCallback', fun readline.moveCursor( self._ostream, N ); // Append the completion preview to the current line (using ASCII color escape codes for displaying grey text): - self._ostream.write( '\x1b[90m' + self._preview + '\x1b[0m' ); + self._ostream.write( '\u001b[90m' + self._preview + '\u001b[0m' ); // Move the cursor back to previous position: readline.moveCursor( self._ostream, -self._preview.length-N ); diff --git a/lib/node_modules/@stdlib/repl/test/fixtures/repl.js b/lib/node_modules/@stdlib/repl/test/fixtures/repl.js new file mode 100644 index 000000000000..7e702c4f0378 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/test/fixtures/repl.js @@ -0,0 +1,149 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2024 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var isFunction = require( '@stdlib/assert/is-function' ); +var inspectSinkStream = require( '@stdlib/streams/node/inspect-sink' ); +var assign = require( '@stdlib/object/assign' ); +var format = require( '@stdlib/string/format' ); +var REPL = require( './../../lib' ); + + +// FUNCTIONS // + +/** +* Returns default options. +* +* @private +* @returns {Object} default options +* +* @example +* var o = defaults(); +* // returns {...} +*/ +function defaults() { + return { + 'isTTY': true, + 'sandbox': true, + 'timeout': 10000, + 'welcome': '', + 'quiet': true + }; +} + + +// MAIN // + +/** +* Returns a REPL instance connected to debugging IO streams. +* +* @private +* @param {Options} options - REPL options +* @param {WritableStream} options.input - input stream +* @param {Callback} clbk - callback to invoke upon closing a REPL +* @throws {Error} must provide an input stream +* @throws {TypeError} second argument must be a function +* @returns {REPL} REPL instance +* +* @example +* var DebugStream = require( '@stdlib/streams/node/debug' ); +* +* // Define a callback for receiving REPL input on close: +* function onClose( error, data ) { +* if ( error ) { +* return console.error( error.message ); +* } +* console.log( data.join( '\n' ) ); +* } +* +* // Create a stream for writing REPL input: +* var istream = new DebugStream({ +* 'name': 'repl-input-stream' +* }); +* +* // Create a test REPL instance: +* var opts = { +* 'input': istream +* }; +* var repl = mock( opts, onClose ); +* +* // Simulate a user entering a command in the REPL: +* istream.write( '2+2\n' ); +* +* // Close the REPL: +* repl.close(); +*/ +function mock( options, clbk ) { + var opts; + var data; + var repl; + + if ( !isFunction( clbk ) ) { + throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', clbk ) ); + } + opts = assign( defaults(), options ); + if ( !opts.input ) { + throw new Error( 'invalid argument. Options argument must specify an input stream.' ); + } + // If we were not provided an output stream, create a default output stream... + if ( !opts.output ) { + opts.output = inspectSinkStream( onWrite ); + } + // Initialize an array for storing streamed data: + data = []; + + // Create a new REPL instance: + repl = new REPL( opts ); + + // Add a listener for when the REPL exits: + repl.on( 'exit', onExit ); + + // Return the REPL instance: + return repl; + + /** + * Callback invoked upon receiving streamed data. + * + * @private + * @param {(Buffer|string)} chunk - data + * @returns {void} + */ + function onWrite( chunk ) { + data.push( chunk.toString() ); + } + + /** + * Callback invoked when a REPL exits. + * + * @private + */ + function onExit() { + if ( isFunction( opts.output.end ) ) { + opts.output.end(); + } + clbk( null, data ); + } +} + + +// EXPORTS // + +module.exports = mock; diff --git a/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js b/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js deleted file mode 100644 index 13ada80a2697..000000000000 --- a/lib/node_modules/@stdlib/repl/test/fixtures/repl_debug.js +++ /dev/null @@ -1,92 +0,0 @@ -/** -* @license Apache-2.0 -* -* Copyright (c) 2024 The Stdlib Authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -/* eslint-disable no-new */ - -'use strict'; - -// MODULES // - -var InspectStreamSink = require( '@stdlib/streams/node/inspect-sink' ); -var REPL = require( '@stdlib/repl' ); - - -// MAIN // - -/** -* Creates a REPL instance connected to debugging IO streams. -* -* Takes an writable stream as a parameter and passes the input written to that -* stream to the repl instance, as if the user was typing it. -* As the commands are evaluated, any output written by the repl will be stored as -* strings in the array that this function returns. -* -* @param {Writable} inputStream - input stream passed to the repl instance -* @param {Object} opts - additional options passed to the repl -* @returns {Array} - array of strings in which the repl output is stored -* -* @example -* var inputStream; -* var outputs; -* inputStream = new DebugStream({ -* 'name': 'repl-input-stream' -* }); -* -* // Create a repl taking input from inputStream and storing the output in outputs. -* outputs = replDebug( inputStream, { 'isTTY': true } ); - -* // Simulate writing 'console.log( "hello" )' to the repl and pressing enter. -* inputStream.write( 'console.log( "hello" )\n' ); -* // outputs[outputs.length - 1] equals 'hello\n' -*/ -function replDebug( inputStream, opts ) { - var outputStream; - var outputs; - - // Create the initially empty array where the REPL output is stored: - outputs = []; - - // Create the output stream, passing a function which will log the outputs. - outputStream = new InspectStreamSink( outputLog ); - - // If no additional options are passed, assign opts to empty object... - if ( opts === undefined ) { - opts = {}; - } - - // Pass the debug input streams to the repl, along any additional options. - opts[ 'input' ] = inputStream; - opts[ 'output' ] = outputStream; - - new REPL( opts ); - - return outputs; - - /** - * Adds the content written to the output stream to the array containing the outputs. - * - * @private - * @param {string} chunk - the new content written to the output stream - * @returns {void} - */ - function outputLog( chunk ) { - outputs.push( chunk.toString() ); - } -} - -module.exports = replDebug; diff --git a/lib/node_modules/@stdlib/repl/test/integration/test.completion_previews.js b/lib/node_modules/@stdlib/repl/test/integration/test.completion_previews.js new file mode 100644 index 000000000000..6cda82392747 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/test/integration/test.completion_previews.js @@ -0,0 +1,408 @@ +/** +* @license Apache-2.0 +* +* Copyright (c) 2024 The Stdlib Authors. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +'use strict'; + +// MODULES // + +var tape = require( 'tape' ); +var DebugStream = require( '@stdlib/streams/node/debug' ); +var trim = require( '@stdlib/string/trim' ); +var repl = require( './../fixtures/repl.js' ); + + +// TESTS // + +tape( 'main export is a function', function test( t ) { + t.ok( true, __filename ); + t.strictEqual( typeof repl, 'function', 'main export is a function' ); + t.end(); +}); + +tape( 'a REPL instance supports displaying a completion preview of user-defined variables', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion: + t.strictEqual( data[ data.length-2 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-1 ], '\u001b[2D', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance supports displaying a completion preview for common prefixes', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare variables with unique names having a common prefix: + istream.write( 'var test_var_name_beep_1 = 1;' ); + istream.write( 'var test_var_name_boop_1 = 1;\n' ); + + // Write the beginning of the common prefix in order to trigger a completion preview: + istream.write( 'test_var_' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'name_b' completion: + t.strictEqual( data[ data.length-2 ], '\u001b[90mname_b\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-1 ], '\u001b[6D', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance supports displaying a completion preview for recognized identifiers at the end of a line', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write a compound expression: + istream.write( 'abcdefgh; var x = 2 + 2; abcde' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'fgh' completion: + t.strictEqual( data[ data.length-2 ], '\u001b[90mfgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-1 ], '\u001b[3D', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance supports displaying a completion preview when a cursor is not at the end of the line', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Move cursor to backward by four column positions: + istream.write( '\u001b[4D' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion before moving the cursor: + t.strictEqual( data[ data.length-4 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-3 ], '\u001b[2D', 'returns expected value' ); + + // Check for an ANSI-escaped color-coded 'gh' completion after moving the cursor: + t.strictEqual( data[ data.length-2 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-1 ], '\u001b[2D', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance supports auto-completing a completion candidate by moving the cursor forward', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Move the cursor forward by one column position: + istream.write( '\u001b[1C' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion: + t.strictEqual( data[ data.length-3 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-2 ], '\u001b[2D', 'returns expected value' ); + + // Check that the completion preview was auto-completed: + t.strictEqual( data[ data.length-1 ], 'gh', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance supports auto-completing a completion preview and execution by pressing ENTER', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream, + 'inputPrompt': '> ', + 'outputPrompt': '' + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Simulate pressing ENTER: + istream.write( '\n' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion: + t.strictEqual( data[ data.length-5 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-4 ], '\u001b[2D', 'returns expected value' ); + + // Check that the completion preview was auto-completed prior to execution: + t.strictEqual( data[ data.length-3 ], 'gh', 'returns expected value' ); + + // Check that the expression was executed: + t.strictEqual( trim( data[ data.length-1 ] ), '1', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance does not display a completion preview when no completion candidate exists', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Write a character which invalidates the completion candidate: + istream.write( 'o' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion: + t.strictEqual( data[ data.length-5 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-4 ], '\u001b[2D', 'returns expected value' ); + + // Check for the character which invalidated a completion candidate: + t.strictEqual( data[ data.length-3 ], 'o', 'returns expected value' ); + + // Check that the completion preview is replaced with whitepsace: + t.strictEqual( data[ data.length-2 ], ' ', 'returns expected value' ); + + // Check that the cursor is returned to the position preceding the whitespace: + t.strictEqual( data[ data.length-1 ], '\u001b[2D', 'returns expected value' ); + + t.end(); + } +}); + +tape( 'a REPL instance does not display a completion preview once a user enters whitespace', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream, + 'inputPrompt': '> ', + 'outputPrompt': '' + }; + r = repl( opts, onClose ); + + // Declare a variable with an unique name in order to prevent namespace collisions: + istream.write( 'var abcdefgh = 1;\n' ); + + // Write the beginning of the variable's name in order to trigger a completion preview: + istream.write( 'abcdef' ); + + // Write whitespace to invalidate the completion candidate: + istream.write( ' ' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + // Check for an ANSI-escaped color-coded 'gh' completion: + t.strictEqual( data[ data.length-5 ], '\u001b[90mgh\u001b[0m', 'returns expected value' ); + + // Check that the cursor is positioned BEFORE the completion preview: + t.strictEqual( data[ data.length-4 ], '\u001b[2D', 'returns expected value' ); + + // Check for the whitespace which invalidated a completion candidate: + t.strictEqual( data[ data.length-3 ], ' ', 'returns expected value' ); + + // Check that the completion preview is replaced with whitepsace: + t.strictEqual( data[ data.length-2 ], ' ', 'returns expected value' ); + + // Check that the cursor is returned to the position preceding the whitespace: + t.strictEqual( data[ data.length-1 ], '\u001b[2D', 'returns expected value' ); + + t.end(); + } +}); diff --git a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js b/lib/node_modules/@stdlib/repl/test/test.completer_preview.js deleted file mode 100644 index 0ce2c0b85424..000000000000 --- a/lib/node_modules/@stdlib/repl/test/test.completer_preview.js +++ /dev/null @@ -1,277 +0,0 @@ -/** -* @license Apache-2.0 -* -* Copyright (c) 2024 The Stdlib Authors. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -'use strict'; - -// MODULES // - -var tape = require( 'tape' ); -var DebugStream = require( '@stdlib/streams/node/debug' ); -var previewCompleter = require( './../lib/completer_preview.js' ); - - -// FIXTURES // - -var replDebug = require( './fixtures/repl_debug.js' ); - - -// TESTS // - -tape( 'main export is a function', function test( t ) { - t.ok( true, __filename ); - t.strictEqual( typeof previewCompleter, 'function', 'main export is a function' ); - t.strictEqual( typeof previewCompleter().beforeKeypress, 'function' ); - t.strictEqual( typeof previewCompleter().onKeypress, 'function' ); - t.end(); -}); - -tape( 'Preview completion is shown', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write the beginning of the variable's name: - inputStream.write( 'abcdef' ); - - // Check that the completion 'gh' is written to the console... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); - - // Check that the cursor is moved back two positions, to where it was originally... - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - t.end(); -}); - -tape( 'Preview completion is shown even with other words at the beginning of the line', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write some other words before the beginning of the variable's name: - inputStream.write('this is a prefix abcde'); - - // Check that the completion "fgh" is shown, even with the extra text at the beginning of the line... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mfgh\x1b[0m' ); - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); - t.end(); -}); - -tape( 'Preview completion is shown even after cursor is moved', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write the beginning of the variable's name: - inputStream.write( 'abcdef' ); - - // Check that the preview "gh" is shown... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); - - // Check that the cursor is moved back to its orginal position... - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - - // Move cursor to the left by one position: - inputStream.write( '\x1b[1D' ); - - // Check that the cursor is moved forward, to the end of the line - t.strictEqual( outputs[ outputs.length - 3 ], '\x1b[1C' ); - - // Check that the preview "gh" is shown... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); - - // Check that the cursor is moved back 3 positions, to where it was originally... - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[3D' ); - t.end(); -}); - -tape( 'Preview is erased when new input makes the preview invalid', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write the beginning of the variable's name: - inputStream.write( 'abcdef' ); - - // Check that the preview is shown... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - - // Write a character that does not correspond to the variable's name: - inputStream.write( 'o' ); - - // Check that preview is replaced with empty spaces... - t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[2D' ); - t.end(); -}); - -tape( 'Preview is filled in when user moves cursor into it', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write the beginning of the variable's name: - inputStream.write( 'abcdef' ); - - // Move cursor to the right by one space, which moves it into the preview: - inputStream.write( '\x1b[1C' ); - - // Check the preview 'gh' was filled in... - t.strictEqual( outputs[ outputs.length - 1 ], 'gh' ); - t.end(); -}); - -tape( 'Preview is filled in when user executes line', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Simulate writing 'console.l' and pressing submitting the line: - inputStream.write( 'console.l\n' ); - - // Check that the completion 'og' was filled in... - t.strictEqual( outputs[ outputs.length - 3 ], 'og' ); - - // Check the output is that of executing the line 'console.log'... - t.strictEqual( outputs[ outputs.length - 1 ].trim(), 'Out[1]: [Function: log]' ); - t.end(); -}); - -tape( 'Preview is erased after user enters white space', function test( t ) { - var inputStream; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Declare a variable with an unique name, to prevent collisions with other packages: - inputStream.write( 'var abcdefgh = 1\n' ); - - // Write the beginning of the variable's name: - inputStream.write( 'abcdef' ); - - // Check that the preview is shown... - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mgh\x1b[0m' ); - - // Write a white space character - inputStream.write( ' ' ); - - // Check that the preview 'gh' was erased (replaced with white space) after the user entered white space... - t.strictEqual( outputs[ outputs.length - 2 ], ' ' ); - t.end(); -}); - -tape( 'Common prefix of completion candidates is shown', function test( t ) { - var inputStream; - var inputString; - var outputs; - - inputStream = new DebugStream({ - 'name': 'repl-input-stream' - }); - - // Create a repl which takes input from inputStream and which stores the output in outputs - outputs = replDebug( inputStream, { - 'isTTY': true - }); - - // Simulate declaring two variables with a long common prefix... - inputString = 'var test_var_name_1 = 1\n' + - 'var test_var_name_2 = 1\n'; - - // Write to the repl and pass a callback which is called after the input is fully processed... - inputStream.write( inputString, function check() { - // Begin writing a prefix common to both variable names: - inputStream.write('test_var_'); - - // Verify that the common prefix 'name_' is shown - t.strictEqual( outputs[ outputs.length - 2 ], '\x1b[90mname_\x1b[0m' ); - - // Verify that the cursor is sent back to its original position - t.strictEqual( outputs[ outputs.length - 1 ], '\x1b[5D' ); - t.end(); - }); -});