Skip to content

feat(search): suggest closest files (fuzzy) when no match#73

Merged
dawsbot merged 3 commits into
masterfrom
feat/levenshtein-suggestions
Jul 7, 2026
Merged

feat(search): suggest closest files (fuzzy) when no match#73
dawsbot merged 3 commits into
masterfrom
feat/levenshtein-suggestions

Conversation

@dawsbot

@dawsbot dawsbot commented Jul 6, 2026

Copy link
Copy Markdown
Owner

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:

No files match "buton.tsx". Showing the 5 closest names.

How

New dependency-free, unit-tested module src/closest-match.ts with a hybrid ranker, because a query lands in this fallback for one of two reasons that fail differently under any single metric:

  1. Abbreviation / subsequence (btnButton.tsx, ucUserController.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.
  2. Substitution typo (bzttonButton.tsx) — a substitution breaks a subsequence entirely, so when nothing matches as a subsequence we fall back to Damerau-Levenshtein edit distance (transpositions like buttnobutton count 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.ts wiring: in the input-filter path, when the substring filter yields zero matches, show the ranked suggestions instead of an empty picker. showQuickPick gained an optional placeHolder for 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_000 so 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; searchCountLimit is the user-facing knob).

Tests

test/closest-match.test.ts covers 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 package script (+ @vscode/vsce dev dep) to build the VSIX locally without publishing, documented briefly in the README.
  • .vscodeignore / .gitignore tidy-up so the packaged VSIX and git tree stay clean.

🤖 Generated with Claude Code

dawsbot and others added 3 commits July 6, 2026 17:23
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>
@dawsbot dawsbot changed the title feat(search): suggest closest files via Levenshtein when no match feat(search): suggest closest files (fuzzy) when no match Jul 7, 2026
@dawsbot dawsbot merged commit 9e32690 into master Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Perform Lavenstein distance recommendations when glob match is empty

1 participant