feat(tui): @-mention autocomplete for file/folder context input#290
Merged
Conversation
Adds a session-scoped `AtPicker` that wraps ffs-search 0.1.3's FilePicker to power `@<path>` autocomplete in the chat input. Features: - Background filesystem scan + watcher rooted at session.working_dir - Fuzzy matching with typo resistance (via neo_frizbee inside ffs) - Frecency ranking persisted to ~/.jcode/cache/ffs/<repo-hash>/ - Mixed file/dir search; trailing `/` activates dirs-only drill-in mode - Lazy init via AtPickerSlot enum (Pending/Initialized/Failed) so we don't spawn threads in test-harness or replay modes Adds 8 unit tests covering: state machine, search filtering, dir-only mode, idempotent warm_up, multi-picker no-deadlock, and stable cache keys for distinct repos. Picker is wired into App via a new `at_picker: RefCell<AtPickerSlot>` field; consumers come in follow-up commits.
Implements Claude-Code-style @-mention UX:
- New `extract_at_token_at_cursor()` finds an active @-token at any
position in the input based on cursor location, not just end-of-input.
Supports quoted paths `@"path with spaces"` and unicode (CJK,
accents).
- `active_at_token()` (end-of-input only) is kept for backward compat
with existing tests.
- New `AtTokenMatch` struct exposes byte offsets so callers can do
exact in-place replacement.
- `command_suggestions()` now checks for an active @-token first; when
found, it returns ffs-search results formatted as `@path` for files
or `@path/` for folders. `/` and `$` namespaces fall through.
- `autocomplete()` (Tab) handles @-tokens via new
`try_apply_at_completion()` helper:
* file selection → insert `@path ` (trailing space, close token)
* folder selection → insert `@path/` (keep token open for drill-in)
* paths containing spaces → quoted form `@"..."` automatically
Replacement uses byte offsets from AtTokenMatch so mid-input mentions
preserve text on either side.
Adds 21 cursor-based detection tests covering: start/middle/end of
input, multiple consecutive tokens, email-like rejection, quoted
forms (closed/unclosed/cursor-mid-quote), unicode paths, slash
separators, whitespace boundaries.
Extends submission-time @-mention expansion to match Claude Code:
- @path/to/folder (with or without trailing /) now inlines a 1-level
directory listing instead of being treated as a missing file.
Listing format:
--- @folder/ (directory listing, N entries) ---
subdir1/
subdir2/
file_a.rs
file_b.rs
--- end @folder/ ---
Sort order: directories first, then files, alphabetical within each
group (matches Claude Code's claude-code-best/claude-code source).
Capped at AT_FOLDER_MAX_ENTRIES=1000 with explicit truncation note;
no file content is inlined for folder mentions.
- @path#L10 and @path#L10-20 line range syntax. Range is parsed off
the END of the token so paths containing # work. Invalid ranges
(zero, backwards, non-numeric) leave the token literal so the user
can fix the typo.
- @"path with spaces" quoted form now expands correctly. The token
is taken verbatim between quotes; trailing punctuation stripping
is bare-token-only.
The expansion loop is rewritten as a byte-index state machine for
cleaner handling of multiple mentions and quoted boundaries.
Adds 12 tests: quoted paths, single/multi-line ranges, range
clamping, invalid range fallback, folder listing sort order, folder
cap + truncation note, multiple mixed mentions, email-bystander
non-expansion, folder w/o trailing slash.
18 tasks
quangdang46
added a commit
that referenced
this pull request
May 24, 2026
After merging 13 PRs in sequence (#290 + #292-#303), two squash-merges collided on shared schema state: 1. crates/jcode-provider-metadata/src/catalog.rs: - Both VERTEX_AI (#283, prior) and BIGMODEL (#297) added entries. - Both #19 (#263 GitLab Duo, prior) and #297 declared array size, each thinking they were +1 from baseline. Net actual: +2 from baseline but final declared count was off by 1. - Fixed: OPENAI_COMPAT_PROFILES 35→36, LOGIN_PROVIDERS 48→49 to match actual entry count. 2. src/cli/commands/provider_setup.rs: - PR #299 added headers: BTreeMap<String, String> field to NamedProviderConfig but didn't update this constructor (which pre-dated #299 in master). - Fixed: added headers: BTreeMap::new() in the configure constructor. cargo check -p jcode --bin jcode now clean. No behavioral change beyond what each PR brought.
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.
Summary
Implements
@<path>autocomplete in the TUI input with Claude Code parity. Type@anywhere in chat → dropdown shows file/folder suggestions ranked by frecency; pick a folder to drill in, pick a file to inline its content on submit.What works
@triggers picker dropdown anywhere in input (cursor-based detection, not just end-of-input)@pathand inline full content on submit (cap: 200 KB)@path/and KEEP the dropdown open for drill-in; on submit, expand to a 1-level directory listing (cap: 1000 entries, dirs sorted before files)@"path with spaces"for paths containing spaces@file#L10and@file#L10-20user@example.comcorrectly skipped~/.jcode/cache/ffs/<repo-hash>/How it works
Three layers, one per commit:
AtPickerwrapsffs-search0.1.3. Background scan + filesystem watcher rooted atsession.working_dir, gitignore-aware via ffs'signoreintegration, fuzzy match vianeo_frizbee, frecency persisted via LMDB. LazyAtPickerSlotstate machine (Pending/Initialized/Failed) so we don't spawn threads in test/replay modes.Cursor-based token detection in
state_ui_input_helpers.rs. Newextract_at_token_at_cursor(input, cursor) → Option<AtTokenMatch>returns byte offsets so callers can do exact in-place replacement. Handles quoted paths, unicode (CJK, accents), multiple@s in one input. Thecommand_suggestions()entry point checks for an active@-token first; if found, suggestions come fromAtPicker::search()and the slash/dollar branches don't run. Theautocomplete()(Tab) handler does the in-place token replacement: file → trailing space (close), folder → trailing slash (drill-in), space-containing paths → quoted form.Submission-time expansion in
input.rs.expand_at_path_references()rewritten as a byte-index state machine. Folders inline a sorted listing (matches Claude Code's behavior — no recursive content inlining, just direct children with truncation note). Line ranges parsed off the end of the token so paths containing#work. Invalid ranges leave the token literal so the user can fix the typo.Spec source
UX choices match
claude-code-best/claude-codesource (verified, not docs):MAX_DIR_ENTRIES = 1000—src/utils/attachments.ts:1973applyDirectorySuggestion: file →' ', dir →'/'—src/hooks/useTypeahead.tsx:277-279readdir, no recursive content inlining —src/utils/attachments.ts:1968-1995MAX_SUGGESTIONS = 15—src/hooks/fileSuggestions.tsTests
41 new tests across 3 modules:
at_picker::testsstate_ui_input_helpers::dollar_token_tests(cursor section)input::at_path_testsPlus 26 pre-existing tests in those modules unchanged and passing.
Verification
cargo check --lib✅cargo check --tests✅ (compiles every test in the workspace, no regressions elsewhere)Out of scope (separate PRs)
@server:resource/path@agent-name@image.png=0.1.3)