From beebe51d7bbdea1918d6de749e8edf062527ddf1 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Mon, 1 Jul 2024 22:26:53 +0000 Subject: [PATCH 1/7] feat: add fuzzy completions extension Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/README.md | 1 + .../@stdlib/repl/lib/complete_expression.js | 81 ++++++++-- .../@stdlib/repl/lib/complete_fs.js | 51 ++++++- .../@stdlib/repl/lib/complete_require.js | 94 +++++++++++- .../@stdlib/repl/lib/complete_settings.js | 28 +++- .../@stdlib/repl/lib/complete_tutorial.js | 28 +++- .../@stdlib/repl/lib/complete_workspace.js | 28 +++- .../@stdlib/repl/lib/completer.js | 40 ++--- .../@stdlib/repl/lib/completer_engine.js | 7 +- .../@stdlib/repl/lib/completer_preview.js | 2 +- lib/node_modules/@stdlib/repl/lib/defaults.js | 3 + .../@stdlib/repl/lib/filter_by_prefix.js | 25 +++- .../@stdlib/repl/lib/fuzzy_match.js | 140 ++++++++++++++++++ lib/node_modules/@stdlib/repl/lib/main.js | 1 + lib/node_modules/@stdlib/repl/lib/settings.js | 4 + .../repl/lib/sort_fuzzy_completions.js | 66 +++++++++ .../test/integration/test.completer_engine.js | 116 ++++++++++++++- 17 files changed, 674 insertions(+), 41 deletions(-) create mode 100644 lib/node_modules/@stdlib/repl/lib/fuzzy_match.js create mode 100644 lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js diff --git a/lib/node_modules/@stdlib/repl/README.md b/lib/node_modules/@stdlib/repl/README.md index 206a2b19097f..46d2672292b4 100644 --- a/lib/node_modules/@stdlib/repl/README.md +++ b/lib/node_modules/@stdlib/repl/README.md @@ -84,6 +84,7 @@ The function supports specifying the following settings: - **autoDeletePairs**: boolean indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes. Default: `true`. - **autoPage**: boolean indicating whether to automatically page return values having a display size exceeding the visible screen. When streams are TTY, the default is `true`; otherwise, the default is `false`. - **completionPreviews**: boolean indicating whether to display completion previews for auto-completion. When streams are TTY, the default is `true`; otherwise, the default is `false`. +- **fuzzyCompletions**: boolean indicating whether to include fuzzy results in TAB completions. Default: `true`. - **syntaxHighlighting**: boolean indicating whether to enable syntax highlighting of entered input expressions. When streams are TTY, the default is `true`; otherwise, the default is `false`. - **theme**: initial color theme for syntax highlighting. Default: `stdlib-ansi-basic`. diff --git a/lib/node_modules/@stdlib/repl/lib/complete_expression.js b/lib/node_modules/@stdlib/repl/lib/complete_expression.js index 769d7dd17bf0..d1ed03054fbe 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_expression.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_expression.js @@ -16,6 +16,8 @@ * limitations under the License. */ +/* eslint-disable max-statements */ + 'use strict'; // MODULES // @@ -32,6 +34,7 @@ var filterByPrefix = require( './filter_by_prefix.js' ); var findLast = require( './complete_walk_find_last.js' ); var resolveLocalScopes = require( './resolve_local_scopes.js' ); var resolveLocalScope = require( './resolve_local_scope.js' ); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); var RESERVED_KEYWORDS_COMMON = require( './reserved_keywords_common.js' ); @@ -52,9 +55,11 @@ var AOPTS = { * @param {Array} out - output array for storing completions * @param {Object} context - REPL context * @param {string} expression - expression to complete +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} filter */ -function complete( out, context, expression ) { +function complete( out, context, expression, isFuzzy ) { + var fuzzyResults = []; var filter; var script; var node; @@ -62,6 +67,7 @@ function complete( out, context, expression ) { var ast; var obj; var res; + var i; // Case: `<|>` (a command devoid of expressions/statements) if ( trim( expression ) === '' ) { @@ -100,9 +106,22 @@ function complete( out, context, expression ) { } filter = node.expression.name; debug( 'Identifier auto-completion. Filter: %s', filter ); - out = filterByPrefix( out, RESERVED_KEYWORDS_COMMON, filter ); - out = filterByPrefix( out, objectKeys( context ), filter ); - out = filterByPrefix( out, ast.locals, filter ); + out = filterByPrefix( out, RESERVED_KEYWORDS_COMMON, filter, false ); + out = filterByPrefix( out, objectKeys( context ), filter, false ); + out = filterByPrefix( out, ast.locals, filter, false ); + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + fuzzyResults = filterByPrefix( fuzzyResults, RESERVED_KEYWORDS_COMMON, filter, true ); // eslint-disable-line max-len + fuzzyResults = filterByPrefix( fuzzyResults, objectKeys( context ), filter, true ); // eslint-disable-line max-len + fuzzyResults = filterByPrefix( fuzzyResults, ast.locals, filter, true ); + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } // Find the identifier or member expression to be completed: @@ -177,7 +196,17 @@ function complete( out, context, expression ) { // Case: `foo['<|>` || `foo['bar<|>` if ( node.property.type === 'Literal' ) { filter = node.property.value.toString(); // handles numeric literals - out = filterByPrefix( out, propertyNamesIn( obj ), filter ); + out = filterByPrefix( out, propertyNamesIn( obj ), filter, false ); // eslint-disable-line max-len + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + fuzzyResults = filterByPrefix( fuzzyResults, propertyNamesIn( obj ), filter, true ); // eslint-disable-line max-len + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } // Case: `foo[<|>` || `foo[bar<|>` @@ -190,7 +219,17 @@ function complete( out, context, expression ) { else { filter = node.property.name; } - out = filterByPrefix( out, objectKeys( context ), filter ); + out = filterByPrefix( out, objectKeys( context ), filter, false ); // eslint-disable-line max-len + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + fuzzyResults = filterByPrefix( fuzzyResults, objectKeys( context ), filter, true ); // eslint-disable-line max-len + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } // Case: `foo[bar.<|>` || `foo[bar.beep<|>` || `foo[bar.beep.<|>` || `foo[bar[beep<|>` || etc @@ -216,15 +255,37 @@ function complete( out, context, expression ) { filter = node.property.name; } debug( 'Property auto-completion. Filter: %s', filter ); - out = filterByPrefix( out, propertyNamesIn( obj ), filter ); + out = filterByPrefix( out, propertyNamesIn( obj ), filter, false ); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + fuzzyResults = filterByPrefix( fuzzyResults, propertyNamesIn( obj ), filter, true ); // eslint-disable-line max-len + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } // Case: `foo<|>` (completing an identifier) filter = ( node.name === '✖' ) ? '' : node.name; debug( 'Identifier auto-completion. Filter: %s', filter ); - out = filterByPrefix( out, res.keywords, filter ); - out = filterByPrefix( out, objectKeys( context ), filter ); - out = filterByPrefix( out, resolveLocalScope( ast, node ), filter ); + out = filterByPrefix( out, res.keywords, filter, false ); + out = filterByPrefix( out, objectKeys( context ), filter, false ); + out = filterByPrefix( out, resolveLocalScope( ast, node ), filter, false ); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + fuzzyResults = filterByPrefix( fuzzyResults, res.keywords, filter, true ); + fuzzyResults = filterByPrefix( fuzzyResults, objectKeys( context ), filter, true ); // eslint-disable-line max-len + fuzzyResults = filterByPrefix( fuzzyResults, resolveLocalScope( ast, node ), filter, true ); // eslint-disable-line max-len + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_fs.js b/lib/node_modules/@stdlib/repl/lib/complete_fs.js index d4d70bef5f48..2cf36f2fde4d 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_fs.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_fs.js @@ -30,6 +30,8 @@ var readDir = require( '@stdlib/fs/read-dir' ).sync; var startsWith = require( '@stdlib/string/starts-with' ); var pathRegExp = require( './regexp_path.js' ); var fsAliasArgs = require( './fs_alias_args.js' ); +var fuzzyMatch = require( './fuzzy_match.js' ); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); // VARIABLES // @@ -50,13 +52,16 @@ var AOPTS = { * @param {string} expression - expression to complete * @param {string} alias - file system API alias * @param {string} path - path to complete +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} path filter */ -function complete( out, expression, alias, path ) { +function complete( out, expression, alias, path, isFuzzy ) { + var fuzzyResults = []; var filter; var subdir; var files; var stats; + var match; var args; var ast; var arg; @@ -128,6 +133,50 @@ function complete( out, expression, alias, path ) { continue; } } + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + // Start searching for fuzzy completions... + debug( 'Searching path for fuzzy completions...' ); + for ( i = 0; i < files.length; i++ ) { + f = files[ i ]; + match = fuzzyMatch( f, filter ); + if ( !match ) { + debug( '%s does not match fuzzy filter %s. Skipping...', f, filter ); + continue; + } + f = resolve( dir, f ); + debug( 'Examining path: %s', f ); + try { + stats = statSync( f ); + if ( stats.isDirectory() ) { + debug( 'Path resolves to a subdirectory.' ); + fuzzyResults.push({ + 'score': match.score, + 'completion': match.completion + '/' + }); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + } else if ( stats.isFile() ) { + debug( 'Path resolves to a file.' ); + fuzzyResults.push( match ); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + } else { + debug( 'Path resolves to neither a directory nor a file. Skipping path...' ); + continue; + } + } catch ( err ) { + debug( 'Error: %s', err.message ); + debug( 'Skipping path...' ); + continue; + } + } + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_require.js b/lib/node_modules/@stdlib/repl/lib/complete_require.js index 03888ac53a42..e6a7f36401f5 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_require.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_require.js @@ -16,6 +16,8 @@ * limitations under the License. */ +/* eslint-disable max-statements */ + 'use strict'; // MODULES // @@ -27,10 +29,12 @@ var readDir = require( '@stdlib/fs/read-dir' ).sync; var startsWith = require( '@stdlib/string/starts-with' ); var extname = require( '@stdlib/utils/extname' ); var cwd = require( '@stdlib/process/cwd' ); -var indexRegExp = require( './regexp_index.js' ); // eslint-disable-line stdlib/no-require-index +var indexRegExp = require( './regexp_index.js' ); var relativePathRegExp = require( './regexp_relative_require_path.js' ); var pathRegExp = require( './regexp_path.js' ); var contains = require( './contains.js' ); +var fuzzyMatch = require( './fuzzy_match.js' ); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); // VARIABLES // @@ -49,14 +53,17 @@ var RE_RELATIVE = relativePathRegExp(); * @param {string} path - path to complete * @param {Array} paths - module search paths * @param {Array} exts - supported `require` extensions +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} path filter */ -function complete( out, path, paths, exts ) { +function complete( out, path, paths, exts, isFuzzy ) { + var fuzzyResults = []; var filter; var sfiles; var subdir; var files; var stats; + var match; var dir; var ext; var re; @@ -157,6 +164,89 @@ function complete( out, path, paths, exts ) { } } } + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return filter; + } + // Start searching for fuzzy completions... + debug( 'Searching paths for fuzzy completions: %s', paths.join( ', ' ) ); + for ( i = 0; i < paths.length; i++ ) { + // Resolve the subdirectory path to a file system path: + dir = resolve( paths[ i ], subdir ); + debug( 'Resolved directory: %s', dir ); + + debug( 'Reading directory contents...' ); + files = readDir( dir ); + if ( files instanceof Error ) { + debug( 'Unable to read directory: %s. Error: %s', dir, files.message ); + continue; + } + for ( j = 0; j < files.length; j++ ) { + f = files[ j ]; + match = fuzzyMatch( f, filter ); + if ( !match ) { + debug( '%s does not fuzzy match filter %s. Skipping...', f, filter ); + continue; + } + f = resolve( dir, f ); + debug( 'Examining path: %s', f ); + try { + stats = statSync( f ); + if ( stats.isDirectory() ) { + debug( 'Path resolves to a subdirectory.' ); + fuzzyResults.push({ + 'score': match.score, + 'completion': match.completion + '/' + }); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + + debug( 'Reading subdirectory contents...' ); + sfiles = readDir( f ); + if ( sfiles instanceof Error ) { + debug( 'Unable to read subdirectory: %s. Error: %s', f, sfiles.message ); + continue; + } + for ( k = 0; k < sfiles.length; k++ ) { + if ( re.test( sfiles[ k ] ) ) { + // Since the subdirectory contains an `index` file, one can simply "require" the subdirectory, thus eliding the full file path: + debug( 'Subdirectory contains an `index` file.' ); + + fuzzyResults.push( match ); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + } else if ( sfiles[ k ] === 'package.json' ) { + // Since the subdirectory contains a `package.json` file, we **ASSUME** one can simply "require" the subdirectory, thus eliding the full file path (WARNING: we do NOT explicitly check that the main entry point actually exists!): + debug( 'Subdirectory contains a `package.json` file.' ); + + fuzzyResults.push( match ); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + } + } + } else if ( stats.isFile() ) { + debug( 'Path resolves to a file.' ); + ext = extname( files[ j ] ); + if ( contains( exts.length, exts, 1, 0, ext ) ) { + debug( 'File has supported extension: %s', ext ); + + fuzzyResults.push( match ); + debug( 'Found a fuzzy completion: %s', fuzzyResults[ fuzzyResults.length-1 ].completion ); + } + } else { + debug( 'Path resolves to neither a directory nor a file. Skipping path...' ); + continue; + } + } catch ( err ) { + debug( 'Error: %s', err.message ); + debug( 'Skipping path...' ); + continue; + } + } + } + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_settings.js b/lib/node_modules/@stdlib/repl/lib/complete_settings.js index 93672a1f4da7..8419c620cc9b 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_settings.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_settings.js @@ -23,6 +23,8 @@ var logger = require( 'debug' ); var parse = require( 'acorn-loose' ).parse; var startsWith = require( '@stdlib/string/starts-with' ); +var fuzzyMatch = require( './fuzzy_match.js' ); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); var settingsAliasArgs = require( './settings_alias_args.js' ); var SETTINGS_NAMES = require( './settings_names.js' ); @@ -46,9 +48,12 @@ var AOPTS = { * @param {string} expression - expression to complete * @param {string} alias - settings API alias * @param {string} value - value to complete +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} value filter */ -function complete( out, repl, expression, alias, value ) { +function complete( out, repl, expression, alias, value, isFuzzy ) { + var fuzzyResults = []; + var match; var args; var ast; var arg; @@ -82,6 +87,27 @@ function complete( out, repl, expression, alias, value ) { debug( '%s does not match filter %s. Skipping...', v, value ); } } + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return value; + } + debug( 'Fuzzy searching for completion candidates...' ); + for ( i = 0; i < SETTINGS_NAMES.length; i++ ) { + v = SETTINGS_NAMES[ i ]; + match = fuzzyMatch( v, value ); + if ( match ) { + debug( 'Found a fuzzy completion: %s', v ); + fuzzyResults.push( match ); + } else { + debug( '%s does not match fuzzy filter %s. Skipping...', v, value ); + } + } + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js index d66e1be6ca39..c23e79b1aa82 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js @@ -26,6 +26,8 @@ var startsWith = require( '@stdlib/string/starts-with' ); var objectKeys = require( '@stdlib/utils/keys' ); var tutorialAliasArgs = require( './tutorial_alias_args.js' ); var TUTORIALS = require( './repl_docs.js' ).tutorial; +var fuzzyMatch = require('./fuzzy_match.js'); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); // VARIABLES // @@ -48,9 +50,12 @@ var AOPTS = { * @param {string} expression - expression to complete * @param {string} alias - tutorial API alias * @param {string} value - value to complete +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} value filter */ -function complete( out, repl, expression, alias, value ) { +function complete( out, repl, expression, alias, value, isFuzzy ) { + var fuzzyResults; + var match; var args; var ast; var arg; @@ -84,6 +89,27 @@ function complete( out, repl, expression, alias, value ) { debug( '%s does not match filter %s. Skipping...', t, value ); } } + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return value; + } + debug( 'Fuzzy searching for completion candidates...' ); + for ( i = 0; i < TUTS.length; i++ ) { + t = TUTS[ i ]; + match = fuzzyMatch( t, value ); + if ( match ) { + debug( 'Found a fuzzy completion: %s', t ); + fuzzyResults.push( match ); + } else { + debug( '%s does not match fuzzy filter %s. Skipping...', t, value ); + } + } + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_workspace.js b/lib/node_modules/@stdlib/repl/lib/complete_workspace.js index d818c4884057..9378edbffeb6 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_workspace.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_workspace.js @@ -24,6 +24,8 @@ var logger = require( 'debug' ); var parse = require( 'acorn-loose' ).parse; var startsWith = require( '@stdlib/string/starts-with' ); var workspaceAliasArgs = require( './workspace_alias_args.js' ); +var fuzzyMatch = require( './fuzzy_match.js' ); +var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); // VARIABLES // @@ -45,9 +47,12 @@ var AOPTS = { * @param {string} expression - expression to complete * @param {string} alias - workspace API alias * @param {string} value - value to complete +* @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {string} value filter */ -function complete( out, repl, expression, alias, value ) { +function complete( out, repl, expression, alias, value, isFuzzy ) { + var fuzzyResults = []; + var match; var args; var ast; var arg; @@ -85,6 +90,27 @@ function complete( out, repl, expression, alias, value ) { debug( '%s does not match filter %s. Skipping...', w, value ); } } + out.sort(); + + // Only fuzzy search, when no exact candidates found... + if ( !isFuzzy || out.length !== 0 ) { + return value; + } + debug( 'Fuzzy searching for completion candidates...' ); + for ( i = 0; i < ws.length; i++ ) { + w = ws[ i ]; + match = fuzzyMatch( w, value ); + if ( match ) { + debug( 'Found a fuzzy completion: %s', w ); + fuzzyResults.push( match ); + } else { + debug( '%s does not match fuzzy filter %s. Skipping...', w, value ); + } + } + fuzzyResults = sortFuzzyCompletions( fuzzyResults ); + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/completer.js b/lib/node_modules/@stdlib/repl/lib/completer.js index ce393c6e5b43..488679b5cce8 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer.js +++ b/lib/node_modules/@stdlib/repl/lib/completer.js @@ -25,6 +25,8 @@ var logger = require( 'debug' ); var objectKeys = require( '@stdlib/utils/keys' ); var hasOwnProp = require( '@stdlib/assert/has-own-property' ); +var slice = require( '@stdlib/array/slice' ); +var startsWith = require( '@stdlib/string/starts-with' ); var fsRegExp = require( './regexp_fs_aliases.js' ); var requireRegExp = require( './regexp_require.js' ); var workspaceRegExp = require( './regexp_workspace.js' ); @@ -51,9 +53,10 @@ var debug = logger( 'repl:completer:callback' ); * * @private * @param {Array} list - completion list +* @param {boolean} isFuzzy - boolean indicating if completions are strictly fuzzy * @returns {Array} normalized completion list */ -function normalize( list ) { +function normalize( list, isFuzzy ) { var hash; var i; @@ -66,10 +69,12 @@ function normalize( list ) { } list = objectKeys( hash ); - // TODO: sort such that printed columns are in lexicographic order, not rows, similar to bash behavior! + // Limit fuzzy completions to 10... + if ( isFuzzy ) { + list = slice( list, 0, 10 ); + } - // Sort the values in lexicographic order: - list = list.sort(); + // TODO: sort such that printed columns are in lexicographic order, not rows, similar to bash behavior! return list; } @@ -93,9 +98,10 @@ function completer( repl ) { * @private * @param {string} line - current line * @param {Function} clbk - completion callback + * @param {boolean} isFuzzy - boolean indicating if the completions should be strictly fuzzy * @returns {void} */ - function complete( line, clbk ) { + function complete( line, clbk, isFuzzy ) { var match; var exts; var res; @@ -113,8 +119,8 @@ function completer( repl ) { exts = objectKeys( repl._context.require.extensions ); debug( 'Supported `require` filename extensions: %s', exts.join( ', ' ) ); - line = completeRequire( res, match[ 1 ], repl._context.module.paths, exts ); // eslint-disable-line max-len - res = normalize( res ); + line = completeRequire( res, match[ 1 ], repl._context.module.paths, exts, isFuzzy ); // eslint-disable-line max-len + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Completion filter: %s', line ); debug( 'Results: %s', res.join( ', ' ) ); @@ -128,8 +134,8 @@ function completer( repl ) { debug( 'Expression: %s', match[ 0 ] ); debug( 'File system API: %s', match[ 1 ] ); debug( 'Path to complete: %s', match[ 3 ] ); - line = completeFS( res, match[ 0 ], match[ 1 ], match[ 3 ] ); - res = normalize( res ); + line = completeFS( res, match[ 0 ], match[ 1 ], match[ 3 ], isFuzzy ); // eslint-disable-line max-len + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Completion filter: %s', line ); debug( 'Results: %s', res.join( ', ' ) ); @@ -143,8 +149,8 @@ function completer( repl ) { debug( 'Expression: %s', match[ 0 ] ); debug( 'Workspace API: %s', match[ 1 ] ); debug( 'Value to complete: %s', match[ 3 ] ); - line = completeWorkspace( res, repl, match[ 0 ], match[ 1 ], match[ 3 ] ); // eslint-disable-line max-len - res = normalize( res ); + line = completeWorkspace( res, repl, match[ 0 ], match[ 1 ], match[ 3 ], isFuzzy ); // eslint-disable-line max-len + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Completion filter: %s', line ); debug( 'Results: %s', res.join( ', ' ) ); @@ -158,8 +164,8 @@ function completer( repl ) { debug( 'Expression: %s', match[ 0 ] ); debug( 'Tutorial API: %s', match[ 1 ] ); debug( 'Value to complete: %s', match[ 3 ] ); - line = completeTutorial( res, repl, match[ 0 ], match[ 1 ], match[ 3 ] ); // eslint-disable-line max-len - res = normalize( res ); + line = completeTutorial( res, repl, match[ 0 ], match[ 1 ], match[ 3 ], isFuzzy ); // eslint-disable-line max-len + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Completion filter: %s', line ); debug( 'Results: %s', res.join( ', ' ) ); @@ -173,8 +179,8 @@ function completer( repl ) { debug( 'Expression: %s', match[ 0 ] ); debug( 'Settings API: %s', match[ 1 ] ); debug( 'Value to complete: %s', match[ 3 ] ); - line = completeSettings( res, repl, match[ 0 ], match[ 1 ], match[ 3 ] ); // eslint-disable-line max-len - res = normalize( res ); + line = completeSettings( res, repl, match[ 0 ], match[ 1 ], match[ 3 ], isFuzzy ); // eslint-disable-line max-len + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Completion filter: %s', line ); debug( 'Results: %s', res.join( ', ' ) ); @@ -187,8 +193,8 @@ function completer( repl ) { return clbk( null, [ res, line ] ); } debug( 'Attempting to complete an incomplete expression.' ); - line = completeExpression( res, repl._context, line ); - res = normalize( res ); + line = completeExpression( res, repl._context, line, isFuzzy ); + res = normalize( res, res[ 0 ] && !startsWith( res[ 0 ], line ) ); debug( 'Results: %s', res.join( ', ' ) ); return clbk( null, [ res, line ] ); } diff --git a/lib/node_modules/@stdlib/repl/lib/completer_engine.js b/lib/node_modules/@stdlib/repl/lib/completer_engine.js index 5de916695d2e..7f551ac961a5 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_engine.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_engine.js @@ -98,6 +98,9 @@ function CompleterEngine( repl, completer, ostream, ttyWrite ) { // Cache a reference to the REPL's multi-line handler: this._multiline = repl._multilineHandler; + // Cache a reference to REPL settings: + this._settings = repl._settings; + // Initialize a flag indicating whether a user is navigating TAB completions: this._isNavigating = false; @@ -672,7 +675,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'beforeKeypress', function // Pause the input stream before generating completions as the completer may be asynchronous: this._rli.pause(); - this._completer( this._inputLine, this._onCompletions ); + this._completer( this._inputLine, this._onCompletions, this._settings.fuzzyCompletions ); // eslint-disable-line max-len }); /** @@ -723,7 +726,7 @@ setNonEnumerableReadOnly( CompleterEngine.prototype, 'onKeypress', function onKe // Pause the input stream before generating completions as the completer may be asynchronous... this._rli.pause(); - this._completer( this._inputLine, this._onCompletions ); + this._completer( this._inputLine, this._onCompletions, this._settings.fuzzyCompletions ); // eslint-disable-line max-len }); /** diff --git a/lib/node_modules/@stdlib/repl/lib/completer_preview.js b/lib/node_modules/@stdlib/repl/lib/completer_preview.js index 2eae0a89ac4e..b711d2bd4df9 100644 --- a/lib/node_modules/@stdlib/repl/lib/completer_preview.js +++ b/lib/node_modules/@stdlib/repl/lib/completer_preview.js @@ -232,7 +232,7 @@ setNonEnumerableReadOnly( PreviewCompleter.prototype, 'onKeypress', function onK return; } try { - this._completer( this._rli.line, this._onCompletions ); + this._completer( this._rli.line, this._onCompletions, false ); } catch ( err ) { debug( 'Error: %s', err.message ); } diff --git a/lib/node_modules/@stdlib/repl/lib/defaults.js b/lib/node_modules/@stdlib/repl/lib/defaults.js index 8f07cbe2b011..1ddae30a7c84 100644 --- a/lib/node_modules/@stdlib/repl/lib/defaults.js +++ b/lib/node_modules/@stdlib/repl/lib/defaults.js @@ -92,6 +92,9 @@ function defaults() { // Flag indicating whether to enable the display of completion previews for auto-completion (note: default depends on whether TTY): 'completionPreviews': void 0, + // Flag indicating whether to enable fuzzy matching in TAB completions: + 'fuzzyCompletions': true, + // Flag indicating whether to enable syntax highlighting (note: default depends on whether TTY): 'syntaxHighlighting': void 0, diff --git a/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js b/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js index 73e014060627..e60ddc3b5d57 100644 --- a/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js +++ b/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js @@ -21,6 +21,7 @@ // MODULES // var startsWith = require( '@stdlib/string/starts-with' ); +var fuzzyMatch = require( './fuzzy_match.js' ); // MAIN // @@ -32,13 +33,29 @@ var startsWith = require( '@stdlib/string/starts-with' ); * @param {Array} out - output array * @param {Array} arr - source array * @param {string} str - string filter +* @param {boolean} isFuzzy - boolean indicating if the completions should be fuzzy * @returns {Array} output array */ -function filterByPrefix( out, arr, str ) { +function filterByPrefix( out, arr, str, isFuzzy ) { + var fuzzyResults = []; + var match; var i; - for ( i = 0; i < arr.length; i++ ) { - if ( startsWith( arr[ i ], str ) ) { - out.push( arr[ i ] ); + + if ( isFuzzy ) { + for ( i = 0; i < arr.length; i++ ) { + match = fuzzyMatch( arr[ i ], str ); + if ( match ) { + fuzzyResults.push( match ); + } + } + for ( i = 0; i < fuzzyResults.length; i++ ) { + out.push( fuzzyResults[ i ] ); + } + } else { + for ( i = 0; i < arr.length; i++ ) { + if ( startsWith( arr[ i ], str ) ) { + out.push( arr[ i ] ); + } } } return out; diff --git a/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js new file mode 100644 index 000000000000..1fc7c0361c61 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js @@ -0,0 +1,140 @@ +/** +* @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 startsWith = require( '@stdlib/string/starts-with' ); +var lowercase = require( '@stdlib/string/lowercase' ); + + +// VARIABLES // + +var PENALTIES = { + 'wrong_case': -1, // Penalty for a matching character that is in the wrong case + 'not_start': -4, // Penalty if the first letter of the completion and input are different + 'gap_between': -4, // Penalty for a gap between matching characters of input in the completion + 'gap_beginning': -1, // Penalty for a gap, before a character from input is encountered + 'missing_char': -19 // Penalty for a character in input that doesn't exist in the completion (to tackle possible spelling mistakes) +}; +var LENIENCY_FACTOR = 0.27; // Controls how lenient the algorithm is + + +// MAIN // + +/** +* Checks if the completion is strictly a fuzzy match for the input and returns a score representing the extent of the match. +* Based on a penalty based scoring system that charges a penalty for unfavorable characteristics in the completion string that make it less ideal of a match to the input string. +* +* @private +* @param {string} completion - completion string +* @param {string} input - input string +* @returns {boolean|Object} - false if not a fuzzy match. if a fuzzy match, an object with the completion and score (<=0) representing the extent of the match. Lower scores (negative) indicate a worse match. A score of 0 indicates a perfect match. +* +* @example +* var score = fuzzyMatch( 'pizza', 'zz' ); +* // returns { 'score': -6.0, 'completion': 'pizza' } +* +* score = fuzzyMatch( 'pizza', 'ab' ); +* // returns false +*/ +function fuzzyMatch( completion, input ) { + var lastMatchedIndex; + var foundFirstMatch; + var completionIndex; + var gapsBetween; + var inputIndex; + var worstScore; + var score; + + worstScore = input.length * PENALTIES.missing_char; + + // If the input is an exact prefix of the completion, reject it as it will be handled by the non-fuzzy logic... + if ( startsWith( completion, input ) ) { + return false; + } + // If the completion is shorter than the input, don't match... + if ( completion.length < input.length ) { + return false; + } + // If the input is just a single character, don't try to fuzzy match... + if ( input.length === 1 ) { + return false; + } + + score = 0; + inputIndex = 0; + completionIndex = 0; + lastMatchedIndex = -1; // buffer to store the index in the completion string where the last match was found + gapsBetween = 0; + foundFirstMatch = false; // flag that tracks if we have found a single character from the input in the completion, so that we can check for `gap_between` penalties + + // If the first character of the input and completion do not match, charge the penalty... + if ( lowercase( input[inputIndex] ) !== lowercase( completion[completionIndex] ) ) { // eslint-disable-line max-len + score += PENALTIES.not_start; + } + // Traverse the completion string looking for characters in the input (in the same order) + while ( inputIndex < input.length && completionIndex < completion.length ) { + if ( lowercase( input[inputIndex] ) === lowercase( completion[completionIndex] ) ) { // eslint-disable-line max-len + // If we find an input character match in the completion, check if the case doesn't match and charge a penalty accordingly... + if ( input[inputIndex] !== completion[completionIndex] ) { + score += PENALTIES.wrong_case; + } + foundFirstMatch = true; + lastMatchedIndex = completionIndex; + score += gapsBetween * PENALTIES.gap_between; + gapsBetween = 0; + inputIndex += 1; + } else if ( completionIndex + 1 === completion.length ) { + // Failed to find the input character in the completion string after full traversal, charge a penalty and check for the next character in the input string from the buffer index in the completion + score += PENALTIES.missing_char; + gapsBetween = 0; + inputIndex += 1; + completionIndex = lastMatchedIndex + 1; + } else if ( foundFirstMatch ) { + // If input and completion character do not match in this iteration and atleast one input character has been found before, track the gaps till the next match + gapsBetween += 1; + } else { + // If input and completion character do not match in the iteration and no input character is found yet, track the gaps till the first match + score += PENALTIES.gap_beginning; + } + completionIndex += 1; + + // Check for early rejections... + if ( score < LENIENCY_FACTOR * worstScore ) { + return false; + } + } + + // Charge penalty for the input characters that are still remaining but the completion string is fully checked for input characters (e.g., input: abcd, completion: xayzb, charge penalty for the remaining c and d ) + score += ( input.length - inputIndex ) * PENALTIES.missing_char; + + if ( score < LENIENCY_FACTOR * worstScore ) { + return false; + } + return { + 'score': score, + 'completion': completion + }; +} + + +// EXPORTS // + +module.exports = fuzzyMatch; diff --git a/lib/node_modules/@stdlib/repl/lib/main.js b/lib/node_modules/@stdlib/repl/lib/main.js index b63a95e23f01..6a5d15f9c120 100644 --- a/lib/node_modules/@stdlib/repl/lib/main.js +++ b/lib/node_modules/@stdlib/repl/lib/main.js @@ -103,6 +103,7 @@ var debug = logger( 'repl' ); * @param {boolean} [options.settings.autoDeletePairs=true] - boolean indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes * @param {boolean} [options.settings.autoPage] - boolean indicating whether to automatically page return values requiring a display size exceeding the visible screen * @param {boolean} [options.settings.completionPreviews] - boolean indicating whether to enable completion previews for auto-completion +* @param {boolean} [options.settings.fuzzyCompletions] - boolean indicating whether to include fuzzy results in TAB completions * @param {boolean} [options.settings.syntaxHighlighting] - boolean indicating whether to enable syntax highlighting * @param {string} [options.settings.theme] - initial color theme for syntax highlighting * @throws {Error} must provide valid options diff --git a/lib/node_modules/@stdlib/repl/lib/settings.js b/lib/node_modules/@stdlib/repl/lib/settings.js index abfb131a0d26..2648d20c614d 100644 --- a/lib/node_modules/@stdlib/repl/lib/settings.js +++ b/lib/node_modules/@stdlib/repl/lib/settings.js @@ -44,6 +44,10 @@ var SETTINGS = { 'desc': 'Enable the display of completion previews for auto-completion.', 'type': 'boolean' }, + 'fuzzyCompletions': { + 'desc': 'Include fuzzy results in TAB completions.', + 'type': 'boolean' + }, 'syntaxHighlighting': { 'desc': 'Enable syntax highlighting.', 'type': 'boolean' diff --git a/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js b/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js new file mode 100644 index 000000000000..c87ec7aadaf5 --- /dev/null +++ b/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js @@ -0,0 +1,66 @@ +/** +* @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'; + +// FUNCTIONS // + +/** +* Callback to sort the results array in descending order of score (lexicographically if scores are equal). +* +* @private +* @param {Object} a - first object representing a completion's fuzzy score +* @param {Object} b - second object representing a completion's fuzzy score +* @returns {number} - comparison result for array sorting +*/ +function sortClbk( a, b ) { + if ( b.score === a.score ) { + // If scores are equal, sort lexicographically... + return a.completion.localeCompare( b.completion ); + } + // Sort by score: + return b.score - a.score; +} + + +// MAIN // + +/** +* Sorts an array of completion objects and returns an array of sorted completion strings. +* +* @private +* @param {Array} completions - array of objects, each containing a 'score' and a 'completion' property +* @returns {Array} - array of sorted completion strings +*/ +function sortFuzzyCompletions( completions ) { + var out = []; + var i; + + // Sort and return the completions: + completions.sort( sortClbk ); + for ( i = 0; i < completions.length; i++ ) { + out.push( completions[ i ].completion ); + } + + return out; +} + + +// EXPORTS // + +module.exports = sortFuzzyCompletions; diff --git a/lib/node_modules/@stdlib/repl/test/integration/test.completer_engine.js b/lib/node_modules/@stdlib/repl/test/integration/test.completer_engine.js index 2429c5f2c477..b896eb90bddc 100644 --- a/lib/node_modules/@stdlib/repl/test/integration/test.completer_engine.js +++ b/lib/node_modules/@stdlib/repl/test/integration/test.completer_engine.js @@ -47,7 +47,8 @@ function defaultSettings() { 'autoClosePairs': false, 'completionPreviews': false, 'syntaxHighlighting': false, - 'autoPage': false + 'autoPage': false, + 'fuzzyCompletions': false }; } @@ -152,6 +153,67 @@ tape( 'a REPL instance supports displaying TAB completions of user-defined varia } }); +tape( 'a REPL instance supports displaying `fuzzy` TAB completions of user-defined variables', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream, + 'settings': defaultSettings(), + 'tty': { + 'rows': 100, + 'columns': 80 + } + }; + r = repl( opts, onClose ); + + // Declare variables with unique names in order to prevent namespace collisions: + istream.write( 'var pizza = 1;' ); + istream.write( 'var jazz = 2;' ); + + // Write the common substring of the variable names in order to generate fuzzy completions: + istream.write( 'zz' ); + + // Enable Fuzzy completions: + r.settings( 'fuzzyCompletions', true ); + + // Write TAB to display completions: + istream.write( '\t' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + var actual; + + if ( error ) { + t.fail( error.message ); + return; + } + + actual = extractCompletions( stripANSI( data[ data.length - 3 ] ) ); + + // Check for two fuzzy completions in the output: + t.strictEqual( actual.length, 2, 'returns expected value' ); + + // Check for the declared variables (sorted lexicographically) in the completions: + t.strictEqual( actual[ 0 ], 'jazz', 'returns expected value' ); + t.strictEqual( actual[ 1 ], 'pizza', 'returns expected value' ); + + // Check if the cursor is returned back to the prompt: + t.strictEqual( data[ data.length - 2 ], '\x1B[2A', 'returns expected value' ); + + t.end(); + } +}); + tape( 'a REPL instance supports hiding the completions panel upon pressing TAB again', function test( t ) { var istream; var opts; @@ -567,3 +629,55 @@ tape( 'a REPL instance supports auto-completing common object property prefixes t.end(); } }); + +tape( 'a REPL instance supports auto-completing a single fuzzy completion for the input when hitting TAB', function test( t ) { + var istream; + var opts; + var r; + + istream = new DebugStream({ + 'name': 'repl-input-stream' + }); + opts = { + 'input': istream, + 'settings': defaultSettings(), + 'tty': { + 'rows': 100, + 'columns': 80 + } + }; + r = repl( opts, onClose ); + + // Declare a variables with `zz` as a substring: + istream.write( 'var pizza = 1;' ); + + // Write the common substring of the variable names in order to generate a fuzzy completion: + istream.write( 'zz' ); + + // Enable Fuzzy completions: + r.settings( 'fuzzyCompletions', true ); + + // Write TAB to trigger auto completion: + istream.write( '\t' ); + + // Close the input stream: + istream.end(); + + // Close the REPL: + r.close(); + + function onClose( error, data ) { + if ( error ) { + t.fail( error.message ); + return; + } + + // Check if the completion prefix was cleared: + t.strictEqual( data[ data.length-2 ], '\b\b', 'returns expected value' ); + + // Check if the final completion was auto-inserted: + t.strictEqual( data[ data.length-1 ], 'pizza', 'returns expected value' ); + + t.end(); + } +}); From a1d31bc5ba17ea6cd191686f1db7967e5dd6f951 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Wed, 3 Jul 2024 11:50:22 +0000 Subject: [PATCH 2/7] fix: error when completing comments Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/complete_expression.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_expression.js b/lib/node_modules/@stdlib/repl/lib/complete_expression.js index d1ed03054fbe..8cef9f5d1ff3 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_expression.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_expression.js @@ -85,7 +85,9 @@ function complete( out, context, expression, isFuzzy ) { // Get the last program top-level AST "node": debug( 'Number of statements: %d', ast.body.length ); node = ast.body[ ast.body.length-1 ]; - + if ( !node ) { + return ''; + } // Check for an empty trailing "expression"... if ( node.end !== ast.end && From 4e149b5e0d7254f0356956d1697d382641780bcb Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Sat, 6 Jul 2024 19:42:40 +0000 Subject: [PATCH 3/7] style: fix tabs and spaces Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/defaults.js | 4 ++-- lib/node_modules/@stdlib/repl/lib/settings.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/defaults.js b/lib/node_modules/@stdlib/repl/lib/defaults.js index 6c9227e8a32c..95c2bba9b1ea 100644 --- a/lib/node_modules/@stdlib/repl/lib/defaults.js +++ b/lib/node_modules/@stdlib/repl/lib/defaults.js @@ -85,8 +85,8 @@ function defaults() { // Flag indicating whether to automatically delete adjacent matching brackets, parentheses, and quotes: 'autoDeletePairs': true, - - // Flag indicating whether to automatically disable bracketed-paste upon exiting the REPL (note: default depends on whether TTY): + + // Flag indicating whether to automatically disable bracketed-paste upon exiting the REPL (note: default depends on whether TTY): 'autoDisableBracketedPasteOnExit': void 0, // Flag indicating whether to enable automatically page return values requiring a display size exceeding the visible screen (note: default depends on whether TTY): diff --git a/lib/node_modules/@stdlib/repl/lib/settings.js b/lib/node_modules/@stdlib/repl/lib/settings.js index 19b7d7be201e..bff5e74fa507 100644 --- a/lib/node_modules/@stdlib/repl/lib/settings.js +++ b/lib/node_modules/@stdlib/repl/lib/settings.js @@ -54,7 +54,7 @@ var SETTINGS = { }, 'fuzzyCompletions': { 'desc': 'Include fuzzy results in TAB completions.', - 'type': 'boolean' + 'type': 'boolean' }, 'syntaxHighlighting': { 'desc': 'Enable syntax highlighting.', From cdadaa90a168ff816a0c65e90e1a8df0b4239a4a Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Tue, 10 Sep 2024 17:00:48 +0530 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: Philipp Burckhardt Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/complete_expression.js | 8 ++++---- lib/node_modules/@stdlib/repl/lib/complete_fs.js | 2 +- lib/node_modules/@stdlib/repl/lib/complete_require.js | 2 +- lib/node_modules/@stdlib/repl/lib/complete_settings.js | 2 +- lib/node_modules/@stdlib/repl/lib/complete_tutorial.js | 2 +- lib/node_modules/@stdlib/repl/lib/complete_workspace.js | 2 +- lib/node_modules/@stdlib/repl/lib/fuzzy_match.js | 2 +- .../@stdlib/repl/lib/sort_fuzzy_completions.js | 6 +++--- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_expression.js b/lib/node_modules/@stdlib/repl/lib/complete_expression.js index 8cef9f5d1ff3..7732989f7e22 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_expression.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_expression.js @@ -113,7 +113,7 @@ function complete( out, context, expression, isFuzzy ) { out = filterByPrefix( out, ast.locals, filter, false ); out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } @@ -200,7 +200,7 @@ function complete( out, context, expression, isFuzzy ) { filter = node.property.value.toString(); // handles numeric literals out = filterByPrefix( out, propertyNamesIn( obj ), filter, false ); // eslint-disable-line max-len - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } @@ -223,7 +223,7 @@ function complete( out, context, expression, isFuzzy ) { } out = filterByPrefix( out, objectKeys( context ), filter, false ); // eslint-disable-line max-len - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } @@ -259,7 +259,7 @@ function complete( out, context, expression, isFuzzy ) { debug( 'Property auto-completion. Filter: %s', filter ); out = filterByPrefix( out, propertyNamesIn( obj ), filter, false ); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_fs.js b/lib/node_modules/@stdlib/repl/lib/complete_fs.js index 2cf36f2fde4d..6bbd6252680e 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_fs.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_fs.js @@ -135,7 +135,7 @@ function complete( out, expression, alias, path, isFuzzy ) { } out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_require.js b/lib/node_modules/@stdlib/repl/lib/complete_require.js index e6a7f36401f5..2310db1642fb 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_require.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_require.js @@ -166,7 +166,7 @@ function complete( out, path, paths, exts, isFuzzy ) { } out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return filter; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_settings.js b/lib/node_modules/@stdlib/repl/lib/complete_settings.js index 8419c620cc9b..299b9ee3a296 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_settings.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_settings.js @@ -89,7 +89,7 @@ function complete( out, repl, expression, alias, value, isFuzzy ) { } out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js index c23e79b1aa82..a8967e1ae072 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js @@ -91,7 +91,7 @@ function complete( out, repl, expression, alias, value, isFuzzy ) { } out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/complete_workspace.js b/lib/node_modules/@stdlib/repl/lib/complete_workspace.js index 9378edbffeb6..a3b2ad82ddb7 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_workspace.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_workspace.js @@ -92,7 +92,7 @@ function complete( out, repl, expression, alias, value, isFuzzy ) { } out.sort(); - // Only fuzzy search, when no exact candidates found... + // Only fuzzy search when no exact candidates found... if ( !isFuzzy || out.length !== 0 ) { return value; } diff --git a/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js index 1fc7c0361c61..7a3d2d3f06b2 100644 --- a/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js +++ b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js @@ -45,7 +45,7 @@ var LENIENCY_FACTOR = 0.27; // Controls how lenient the algorithm is * @private * @param {string} completion - completion string * @param {string} input - input string -* @returns {boolean|Object} - false if not a fuzzy match. if a fuzzy match, an object with the completion and score (<=0) representing the extent of the match. Lower scores (negative) indicate a worse match. A score of 0 indicates a perfect match. +* @returns {(boolean|Object)} false if not a fuzzy match. If a fuzzy match, an object with the completion and score (<=0) representing the extent of the match. Lower scores (negative) indicate a worse match. A score of 0 indicates a perfect match. * * @example * var score = fuzzyMatch( 'pizza', 'zz' ); diff --git a/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js b/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js index c87ec7aadaf5..7ee15900fd42 100644 --- a/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js +++ b/lib/node_modules/@stdlib/repl/lib/sort_fuzzy_completions.js @@ -26,7 +26,7 @@ * @private * @param {Object} a - first object representing a completion's fuzzy score * @param {Object} b - second object representing a completion's fuzzy score -* @returns {number} - comparison result for array sorting +* @returns {number} comparison result for array sorting */ function sortClbk( a, b ) { if ( b.score === a.score ) { @@ -41,11 +41,11 @@ function sortClbk( a, b ) { // MAIN // /** -* Sorts an array of completion objects and returns an array of sorted completion strings. +* Sorts an array of completion objects in-place and returns an array of sorted completion strings. * * @private * @param {Array} completions - array of objects, each containing a 'score' and a 'completion' property -* @returns {Array} - array of sorted completion strings +* @returns {Array} array of sorted completion strings */ function sortFuzzyCompletions( completions ) { var out = []; From aa7b329efa422690006200aa78312312f9e09f95 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Wed, 11 Sep 2024 03:11:35 +0000 Subject: [PATCH 5/7] style: fix lint errors Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/complete_tutorial.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js index a8967e1ae072..c95397c3ac80 100644 --- a/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js +++ b/lib/node_modules/@stdlib/repl/lib/complete_tutorial.js @@ -26,7 +26,7 @@ var startsWith = require( '@stdlib/string/starts-with' ); var objectKeys = require( '@stdlib/utils/keys' ); var tutorialAliasArgs = require( './tutorial_alias_args.js' ); var TUTORIALS = require( './repl_docs.js' ).tutorial; -var fuzzyMatch = require('./fuzzy_match.js'); +var fuzzyMatch = require( './fuzzy_match.js' ); var sortFuzzyCompletions = require( './sort_fuzzy_completions.js' ); From 1916c1b36c482395f780fca4f33647c48a7d0288 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Wed, 11 Sep 2024 03:31:54 +0000 Subject: [PATCH 6/7] fix: remove unnecessary variable Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js b/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js index e60ddc3b5d57..f497dabf9e8e 100644 --- a/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js +++ b/lib/node_modules/@stdlib/repl/lib/filter_by_prefix.js @@ -37,7 +37,6 @@ var fuzzyMatch = require( './fuzzy_match.js' ); * @returns {Array} output array */ function filterByPrefix( out, arr, str, isFuzzy ) { - var fuzzyResults = []; var match; var i; @@ -45,12 +44,9 @@ function filterByPrefix( out, arr, str, isFuzzy ) { for ( i = 0; i < arr.length; i++ ) { match = fuzzyMatch( arr[ i ], str ); if ( match ) { - fuzzyResults.push( match ); + out.push( match ); } } - for ( i = 0; i < fuzzyResults.length; i++ ) { - out.push( fuzzyResults[ i ] ); - } } else { for ( i = 0; i < arr.length; i++ ) { if ( startsWith( arr[ i ], str ) ) { From a5cc4402999b13fbafbb7cd3d25f4e18dc145ec1 Mon Sep 17 00:00:00 2001 From: Snehil Shah Date: Wed, 11 Sep 2024 10:56:25 +0000 Subject: [PATCH 7/7] docs: move notes to `## Notes` block Signed-off-by: Snehil Shah --- lib/node_modules/@stdlib/repl/lib/fuzzy_match.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js index 7a3d2d3f06b2..bf5ff2908e53 100644 --- a/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js +++ b/lib/node_modules/@stdlib/repl/lib/fuzzy_match.js @@ -40,7 +40,18 @@ var LENIENCY_FACTOR = 0.27; // Controls how lenient the algorithm is /** * Checks if the completion is strictly a fuzzy match for the input and returns a score representing the extent of the match. -* Based on a penalty based scoring system that charges a penalty for unfavorable characteristics in the completion string that make it less ideal of a match to the input string. +* +* ## Notes +* +* - The algorithm is based on a penalty based scoring mechanism that charges a penalty for unfavorable characteristics in the completion string that make it less ideal of a match to the input string. +* +* - The algorithm takes the following variables into account: +* +* - Casing mismatch: Low penalty +* - First character doesn't match: Medium penalty +* - Gap between matching characters: Medium penalty +* - Gap before the input was encountered: Low penalty +* - Missing character: High penalty * * @private * @param {string} completion - completion string