Skip to content

feat(cli): add fuzzy matching to the slash-command completion menu#3833

Merged
esengine merged 1 commit into
esengine:main-v2from
CnsMaple:fix/slash-fuzzy-completion
Jun 11, 2026
Merged

feat(cli): add fuzzy matching to the slash-command completion menu#3833
esengine merged 1 commit into
esengine:main-v2from
CnsMaple:fix/slash-fuzzy-completion

Conversation

@CnsMaple

Copy link
Copy Markdown
Contributor

🤖 Generated by AI (CnsMaple + Reasonix)

Summary

Replace the strict strings.HasPrefix filter in the chat-TUI slash-command
completion popup with a case-insensitive subsequence matcher, so typing a
partial token like /mdl surfaces /compact, /model, /memory and any
other command whose letters appear in order — not just commands that begin
with the typed prefix.

Why

Users naturally abbreviate or fat-finger slash commands (/compt,
/mdl, /skll, /brnch). The current matcher silently closes the menu on
anything that isn't an exact prefix, forcing the user to retype or look
the command up via /help. A subsequence match keeps the "type a few
letters, see the candidates" muscle memory from shells like fzf.

Scope

Inentionally limited to the live TUI completion popup. Typed-input
resolution (/name + Enter) and the model's slash_command tool still
use exact-name lookup, so the routing layer stays predictable. Both are
one-liner follow-ups if reviewers want them in the same change.

Implementation

  • internal/cli/complete.go
    • New fuzzyFilterSlash(items, query): returns the matched compItems
      with prefix hits ranked first, subsequence-only hits after, each
      group preserving the original slashItems() order. Empty query
      lists everything (same as before). Zero matches returns nil so
      updateCompletion falls through and closes the menu.
    • New subsequenceMatch(target, query): rune-level subsequence test
      on already-case-folded strings. O(n+m), no allocations beyond the
      rune slice.
    • updateCompletion slash branch: filterByPrefix(...)
      fuzzyFilterSlash(...). No other call sites touched.

Tests

8 new tests in internal/cli/complete_test.go:

  • TestFuzzyFilterSlashSubsequence/mdl hits /model, not /mcp
  • TestFuzzyFilterSlashPrefixFirst/mo ranks /model first
  • TestFuzzyFilterSlashCaseInsensitive/COMP hits /compact
  • TestFuzzyFilterSlashEmptyQueryMatchesAll — bare / lists everything
  • TestFuzzyFilterSlashNoMatchClosesMenu/xzqzqz closes the menu
  • TestFuzzyFilterSlashAppliesToCustomCommands — custom commands covered
  • TestFuzzyFilterSlashAcceptFillsInput — Tab/accept end-to-end
  • TestSubsequenceMatchUnit — direct matcher coverage incl. negatives

All 4 pre-existing slash-completion tests still pass. gofmt -l and
go vet ./internal/cli/... are clean. The 4 unrelated failures in
internal/cli/... (TestApplyMCPModeDropsLegacyTier,
TestModelRefsSkipsUnconfigured, TestOneLine, TestRenderRewindSmoke)
are pre-existing on main-v2 and were verified with git stash to be
unrelated to this change.

Risk

Low. The matcher is a strict superset of the previous prefix filter:
every input that matched before still matches, and the menu's
"open/close" contract is preserved (empty = list all, no match = close).
The accept/insert path is untouched, so Tab still fills the input with
the top-ranked hit's insert string.

Checklist

  • Rebased onto current upstream/main-v2 (was 51 commits behind)
  • gofmt -l internal/cli/... clean on the LF content git will push
  • go vet ./internal/cli/... clean
  • New + existing slash-completion tests pass
  • PR targets main-v2 per CONTRIBUTING.md

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development tui Terminal UI / CLI (internal/cli, internal/control) labels Jun 10, 2026
Replace the strict prefix filter in updateCompletion with a case-insensitive
subsequence matcher, so typing a partial slash token like /mdl surfaces
/compact, /model, /memory and other commands whose letters appear in order,
not just commands that start with the typed prefix.

The new matcher keeps the previous menu semantics for every input the old
filter already handled: an empty query (bare /) lists every command, and a
query that matches nothing closes the menu. Prefix hits rank first within
the result list, so /mo still puts /model on top and the rest of the
subsequence matches follow in their original slashItems order.

Scope is intentionally limited to the in-TUI completion popup; typed-input
resolution (/name + Enter) and the model's slash_command tool still use
exact-name lookup to keep the change reviewable.

Tested with new unit tests in complete_test.go covering subsequence
matching, prefix-first ranking, case insensitivity, empty query, no-match
menu close, custom command coverage, and end-to-end accept.
@CnsMaple CnsMaple force-pushed the fix/slash-fuzzy-completion branch from cebef87 to dec5906 Compare June 11, 2026 01:46

@esengine esengine left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed: the subsequence matcher is correct and minimal, prefix hits ranking ahead of subsequence-only hits is the right UX call, and the empty-query / no-match contracts deliberately mirror the old prefix filter so the menu open/close behavior is unchanged. Keeping typed-input resolution and the slash_command tool on exact lookup is exactly the scope I'd have asked for. Test coverage is thorough — the unit table on subsequenceMatch plus the end-to-end accept path is the right split. Merging, thanks!

@esengine esengine merged commit 559b22c into esengine:main-v2 Jun 11, 2026
13 checks passed
@CnsMaple CnsMaple deleted the fix/slash-fuzzy-completion branch June 11, 2026 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tui Terminal UI / CLI (internal/cli, internal/control) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants