Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add fuzzy matching algorithm for tab completions #1855

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
dc73372
feat: add fuzzy matching algorithm for tab completions
Snehil-Shah Mar 12, 2024
3cae6a7
refactor: fix function jsdoc
Snehil-Shah Mar 24, 2024
abe7525
Merge branch 'stdlib-js:develop' into repl-fuzzy
Snehil-Shah Mar 31, 2024
dcb6014
feat: rewrite the completer engine
Snehil-Shah Apr 2, 2024
bef5dd3
feat: improve fuzzy algorithm and sort by relevancy
Snehil-Shah Apr 2, 2024
0aee34d
feat: add support for highlighted completions in terminal mode
Snehil-Shah Apr 2, 2024
7efe1a7
fix: wrong completion previews displayed for fuzzy completion
Snehil-Shah Apr 2, 2024
b0ca5c1
fix: suggestions
Snehil-Shah Apr 3, 2024
7cc9270
feat: new tab completions UI with navigation
Snehil-Shah Apr 5, 2024
24da634
refactor: move `fuzzyMatch` to a module & don't fuzzy match for previews
Snehil-Shah Apr 5, 2024
6818055
fix: changes requested
Snehil-Shah Apr 5, 2024
95a72a4
Merge branch 'develop' into repl-fuzzy
Snehil-Shah Apr 5, 2024
0c8b11a
style: lint merged changes
Snehil-Shah Apr 5, 2024
6b7dc99
feat: add a setting to control fuzzy completions
Snehil-Shah Apr 5, 2024
237fb7f
fix: limit height of completions & fix completions with prefixes
Snehil-Shah Apr 5, 2024
576f402
feat: fuzzy completions only when no exact completions
Snehil-Shah Apr 6, 2024
89b5001
fix: bug in completer
Snehil-Shah Apr 6, 2024
c464944
test: add tests for tab completions
Snehil-Shah Apr 7, 2024
6d6d7c9
docs: fix test name and comments
Snehil-Shah Apr 8, 2024
2c15d15
fix: tune fuzzy algorithm
Snehil-Shah Apr 8, 2024
0e54cf5
docs: document setting
Snehil-Shah Apr 21, 2024
7128fac
Merge branch 'develop' into repl-fuzzy
Snehil-Shah Apr 25, 2024
fa22afb
fix: abnormal completer behavior
Snehil-Shah Apr 25, 2024
7b8074a
refactor: move flag to the completer engine namespace
Snehil-Shah Apr 25, 2024
cc2f3c5
refactor: abstract logics into private methods
Snehil-Shah Apr 26, 2024
85efce4
fix: make completer `SIGWINCH` aware
Snehil-Shah Apr 26, 2024
023c31a
test: update tests for TTY
Snehil-Shah Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: make completer SIGWINCH aware
Signed-off-by: Snehil Shah <snehilshah.989@gmail.com>
  • Loading branch information
Snehil-Shah committed Apr 26, 2024
commit 85efce4f9fe59b8b3fc49e6ed427ec8f74b5d301
69 changes: 62 additions & 7 deletions lib/node_modules/@stdlib/repl/lib/completer_engine.js
Original file line number Diff line number Diff line change
@@ -26,7 +26,8 @@ var readline = require( 'readline' );
var logger = require( 'debug' );
var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
var lowercase = require( '@stdlib/string/lowercase' );
var max = require( '@stdlib/stats/base/max' );
var maxInArray = require( '@stdlib/stats/base/max' );
var max = require( '@stdlib/math/base/special/max' );
var min = require( '@stdlib/math/base/special/min' );
var floor = require( '@stdlib/math/base/special/floor' );
var ceil = require( '@stdlib/math/base/special/ceil' );
@@ -43,6 +44,7 @@ var commonPrefix = require( './longest_common_prefix.js' );

var debug = logger( 'repl:completer:engine' );
var RE_ANSI = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g; // eslint-disable-line no-control-regex
var RESERVED_COMPLETER_ROWS = 2; // input prompt + empty line


// FUNCTIONS //
@@ -135,6 +137,7 @@ function CompleterEngine( repl, completer, ostream, ttyWrite ) {
* @private
* @name _completionCallback
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {Function} completion callback
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionCallback', function completionCallback() {
@@ -201,6 +204,13 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionCallback', func
}
debug( 'No auto-completion candidate, displaying all possible completions.' );

// Check if completions can be displayed...
if ( !self._isDisplayable() ) {
debug( 'TTY height too short. exiting completer...' );
self._rli.resume();
return;
}

// Display completions:
self._displayCompletions();
self._isNavigating = true;
@@ -216,6 +226,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionCallback', func
* @private
* @name _displayCompletions
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_displayCompletions', function displayCompletions() {
@@ -228,7 +239,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_displayCompletions', func
for ( i = 0; i < this._completionsList.length; i++ ) {
this._completionsLength.push( this._completionsList[ i ].length );
}
this._widthOfColumn = max( this._completionsLength.length, this._completionsLength, 1 ) + 4; // eslint-disable-line max-len
this._widthOfColumn = maxInArray( this._completionsLength.length, this._completionsLength, 1 ) + 4; // eslint-disable-line max-len

// Highlight completions if operating in "terminal" mode...
if ( this.repl._isTTY ) {
@@ -255,6 +266,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_displayCompletions', func
* @private
* @name _updateCompletions
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_updateCompletions', function updateCompletions() {
@@ -290,6 +302,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_updateCompletions', funct
* @private
* @name _drawOutput
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {number} rows - number of rows in output grid
* @param {number} columns - number of columns in output grid
* @returns {string} output string
@@ -307,11 +320,12 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_drawOutput', function dra
whitespaces = 0;
for ( i = 0; i < this._highlightedCompletions.length; i++ ) {
completion = this._highlightedCompletions[ i ];

// If completions start overflowing the maximum allowed rows, stop writing to output...
if ( i >= rows * columns ) {
break;
}
if ( lineIndex >= columns ) {
// If completions start overflowing the maximum allowed rows, stop writing to output...
if ( i >= rows * columns ) {
break;
}
// Reached end of column, enter next line:
output += '\r\n';
lineIndex = 0;
@@ -340,6 +354,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_drawOutput', function dra
* @private
* @name _highlightCompletions
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {Array<string>} array of highlighted completions
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_highlightCompletions', function highlightCompletions() {
@@ -397,6 +412,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_highlightCompletions', fu
*
* @name _completionColumns
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {number} number of columns
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionColumns', function completionColumns() {
@@ -408,23 +424,38 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionColumns', funct
*
* @name _completionRows
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {number} columns - number of columns in the completions output grid
* @returns {number} number of rows
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_completionRows', function completionRows( columns ) {
var maxRows = this.repl.viewportHeight() - 2; // two rows reserved for the input prompt and an empty line at the end of viewport
var maxRows = max( this.repl.viewportHeight() - RESERVED_COMPLETER_ROWS, 0 ); // eslint-disable-line max-len
var rows = ceil( this._completionsList.length / columns );

// Truncate number of completion rows to fit the viewport:
return min( rows, maxRows );
});

/**
* Checks whether content having a specified number of lines is unable to fit within the current viewport.
*
* @private
* @name _isDisplayable
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {boolean} boolean indicating whether content is "displayable"
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_isDisplayable', function isDisplayable() {
return this.repl.viewportHeight() > RESERVED_COMPLETER_ROWS;
});

/**
* Closes completer engine.
*
* @private
* @name _closeCompleter
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_closeCompleter', function closeCompleter() {
@@ -451,6 +482,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_closeCompleter', function
* @private
* @name _navigateUp
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {string} data - input data
* @param {Object} key - key object
* @returns {void}
@@ -479,6 +511,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_navigateUp', function nav
* @private
* @name _navigateDown
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, '_navigateDown', function navigateDown() {
@@ -501,6 +534,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_navigateDown', function n
* @private
* @name _navigateLeft
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {string} data - input data
* @param {Object} key - key object
* @returns {void}
@@ -525,6 +559,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_navigateLeft', function n
* @private
* @name _navigateRight
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {string} data - input data
* @param {Object} key - key object
* @returns {void}
@@ -548,6 +583,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, '_navigateRight', function
*
* @name beforeKeypress
* @memberof CompleterEngine.prototype
* @type {Function}
* @param {string} data - input data
* @param {Object} key - key object
* @returns {void}
@@ -613,6 +649,25 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'beforeKeypress', function
this._completer( this._inputLine, this._onCompletions, this._settings.fuzzyCompletions ); // eslint-disable-line max-len
});

/**
* Callback which should be invoked upon a "resize" event.
*
* @name onResize
* @memberof CompleterEngine.prototype
* @type {Function}
* @returns {void}
*/
setNonEnumerableReadOnly( CompleterEngine.prototype, 'onResize', function onResize() {
if ( !this._isNavigating ) {
return;
}
if ( !this._isDisplayable() ) {
this._closeCompleter();
return;
}
this._updateCompletions();
});


// EXPORTS //

1 change: 1 addition & 0 deletions lib/node_modules/@stdlib/repl/lib/main.js
Original file line number Diff line number Diff line change
@@ -277,7 +277,7 @@
setNonEnumerableReadOnly( this, '_ttyWrite', this._rli._ttyWrite );

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

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

GitHub Actions / Lint Changed Files

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

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

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

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

GitHub Actions / Lint Changed Files

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

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

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

GitHub Actions / Lint Changed Files

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

// Check whether to load and execute a JavaScript file (e.g., prior REPL history) upon startup...
if ( opts.load ) {
@@ -387,6 +387,7 @@
function onSIGWINCH() {
debug( 'Received a SIGWINCH event. Terminal was resized.' );
self._ostream.onResize();
self._completerEngine.onResize();
}

/**
@@ -447,7 +448,7 @@
// Update the internal command history buffer: [..., <id>, <cmd>, <success>, ...]
self._history.push( self._count, cmd, success );

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

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

GitHub Actions / Lint Changed Files

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

// TODO: if necessary, (asynchronously?) write the command and result to a log file (JSON serialization?)
}
Loading
Oops, something went wrong.