feat(search): suggest closest files (fuzzy) when no match#73
Merged
Conversation
When the file filter returns no results, fall back to a "did you mean" list of the 5 closest file names by Levenshtein distance instead of showing an empty picker. Distance is computed against each file's base name (case-insensitive) so short queries are not penalized by long directory prefixes. The logic lives in a dependency-free, unit-tested module. Closes #54 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds @vscode/vsce as a dev dependency and a `package` script so the VSIX can be built with a single command, without publishing. Documents the flow in the README. Also tightens .vscodeignore to keep editor undo files and husky hooks out of the package, and ignores the generated *.vsix output in git. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace plain Levenshtein with a subsequence-first ranker that also handles abbreviations (e.g. "btn" -> Button.tsx, "uc" -> UserController.ts), falling back to Damerau-Levenshtein edit distance for substitution typos. Cap the fallback at 50k candidates (MAX_FALLBACK_CANDIDATES) so the one-shot scan stays bounded on very large workspaces regardless of repo size (~190ms worst case at 2M files, ~30ms typical). Rename the module to closest-match.ts to reflect its broader scope. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes #54.
When the file filter returns no matching files, the picker used to show an empty list. Now it falls back to a "did you mean" list of the 5 closest file names, with a placeholder that explains the fallback:
How
New dependency-free, unit-tested module
src/closest-match.tswith a hybrid ranker, because a query lands in this fallback for one of two reasons that fail differently under any single metric:btn→Button.tsx,uc→UserController.ts) — ranked by a fuzzy subsequence scorer that rewards contiguous runs, word boundaries, and camelCase humps. Plain edit distance would penalize the gaps and bury these.bztton→Button.tsx) — a substitution breaks a subsequence entirely, so when nothing matches as a subsequence we fall back to Damerau-Levenshtein edit distance (transpositions likebuttno→buttoncount as one edit).Matching is case-insensitive and against each file's base name, so a short query isn't penalized by long directory prefixes.
extension.tswiring: in the input-filter path, when the substring filter yields zero matches, show the ranked suggestions instead of an empty picker.showQuickPickgained an optionalplaceHolderfor the fallback message.Performance at scale
The fallback is one-shot (fires on Enter, not per keystroke) over the already-cached, in-memory file list. It's capped at
MAX_FALLBACK_CANDIDATES = 50_000so the worst case is constant regardless of repo size (100k / 1M / 10M+ files). Measured on a synthetic 2M-file workspace: ~30ms typical (abbreviation hit) and ~190ms absolute worst case (pure typo forcing a full edit-distance pass). The cap is a module constant, deliberately not a user setting (it's a safety guard, not a preference;searchCountLimitis the user-facing knob).Tests
test/closest-match.test.tscovers transposition distance, subsequence null/scoring, abbreviation + camelCase ranking, the typo fallback, base-name comparison, and the candidate cap. All tests pass (npm test).Also in this PR
npm run packagescript (+@vscode/vscedev dep) to build the VSIX locally without publishing, documented briefly in the README..vscodeignore/.gitignoretidy-up so the packaged VSIX and git tree stay clean.🤖 Generated with Claude Code