feat: add directory/file path completion for terminal input#2879
feat: add directory/file path completion for terminal input#2879wenshao wants to merge 8 commits intoQwenLM:mainfrom
Conversation
📋 Review SummaryThis PR implements path auto-completion for the terminal input, adding file and directory completion functionality inspired by Claude Code. The implementation includes a well-structured LRU-cached directory scanning system with proper React hook integration. Overall, this is a solid implementation with good test coverage, though there are a few areas that could be improved for robustness and maintainability. 🔍 General Feedback
🎯 Specific Feedback🟡 High
🟢 Medium
🔵 Low
✅ Highlights
|
Implement path auto-completion inspired by Claude Code's directoryCompletion.ts. When user types a path-like token (/, ./, ../, ~/), trigger file/directory completion with LRU-cached directory scanning and .gitignore filtering. - New module: utils/directoryCompletion.ts with SimpleLRUCache, path parsing, directory scanning, and completion generation - New hook: usePathCompletion.ts with 100ms debounce and abort controller - Modified: useCommandCompletion.tsx to add PATH completion mode with SLASH/PATH disambiguation for absolute paths - 21 unit tests covering all path completion scenarios Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
7d454c7 to
9611029
Compare
|
Thanks for the thorough review! Here are my responses to the feedback: 🟡 HighWindows forward-slash paths in
🟢 MediumSimpleLRUCache O(n) access: This is intentional — the class is named
PATH mode only on Test 🔵 Low
Magic number I'll address the actionable items in follow-up commits shortly. |
- Use os.homedir() instead of process.env for home directory expansion - Extract magic number 100 to named constant MAX_SCAN_RESULTS - Remove redundant 'enabled' field from UsePathCompletionReturn - Add comment explaining cursorRow === 0 restriction for PATH mode Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
✅ Review feedback addressedPushed a follow-up commit with the following fixes:
All 48 tests pass, zero new TypeScript errors. |
- Preserve ./ prefix in completion values (was incorrectly stripped) - Remove bare ~, ., .. from path-like token detection (produced wrong results) - Fix bare / falling into PATH mode instead of SLASH mode when commands exist - Include symlinks in scanDirectoryForPaths results - Simplify usePathCompletion to return void (align with useAtCompletion pattern) - Strengthen tests to verify actual mode selection via mock call assertions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds filesystem path auto-completion to the CLI input, integrating it alongside existing @ and / completion modes and adding a small directory-scan cache to keep suggestions responsive.
Changes:
- Introduces a
directoryCompletionutility for parsing path-like tokens, scanning directories, and generating suggestions (with caching). - Adds a
usePathCompletionhook (debounced, abortable) and wires it intouseCommandCompletionvia a newCompletionMode.PATH. - Adds/updates tests to cover parsing/scanning/completion and PATH-mode detection/precedence.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/ui/utils/directoryCompletion.ts | New completion core: path parsing, directory scanning, LRU+TTL caches, suggestion generation. |
| packages/cli/src/ui/utils/directoryCompletion.test.ts | Unit tests for parsing/token detection/scanning/completion behavior. |
| packages/cli/src/ui/hooks/usePathCompletion.ts | New hook to fetch path suggestions with debounce + cancellation. |
| packages/cli/src/ui/hooks/usePathCompletion.test.ts | Placeholder tests (doesn’t actually exercise the hook). |
| packages/cli/src/ui/hooks/useCommandCompletion.tsx | Adds PATH completion mode, slash-vs-path disambiguation, and PATH insertion behavior (no trailing space). |
| packages/cli/src/ui/hooks/useCommandCompletion.test.ts | Adds PATH-mode detection tests and mocks usePathCompletion. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wenshao
left a comment
There was a problem hiding this comment.
Summary
这个 PR 为 CLI 输入新增了文件/目录路径补全,并扩展了 slash command 与 path completion 的分流逻辑。整体实现思路清晰、测试覆盖也不错;在我经过多轮无方向审计和反向验证后,最终保留 2 个非阻塞但值得修正的问题。
Findings
Suggestion
-
packages/cli/src/ui/hooks/useCommandCompletion.tsx:121-138- What's wrong — 当前逻辑把
slashCommands.length === 0视为应进入SLASH模式的条件之一。但slashCommands本身是异步加载的,初始化阶段会短暂为空,因此在启动早期输入/home、/tmp/foo这类绝对路径时,会先被判到SLASH分支,而不是PATH。 - Why it matters — 这会让路径补全在启动初期表现不稳定:用户输入的是明显的文件系统路径,但本应出现的路径候选会被暂时抑制,直到命令列表加载完成后才恢复正常。
- Suggested fix — 不要仅因为
slashCommands为空就进入SLASH模式。更稳妥的做法是:仅在首个 token 明确匹配已加载命令时进入SLASH,否则让明显的绝对路径(如/foo、/usr/local)走PATH;或者引入显式的commandsLoaded状态,在命令尚未加载完成时避免这个兜底判定。
- What's wrong — 当前逻辑把
-
packages/cli/src/ui/utils/directoryCompletion.ts:236-239- What's wrong —
scanDirectoryForPaths()当前把所有isSymbolicLink()条目统一归类为file,没有区分“指向目录的 symlink”和普通文件 symlink。 - Why it matters — 在 monorepo、workspace、挂载目录等场景中,目录 symlink 很常见。当前实现会让这类目录补全结果不带
/,用户接受补全后也无法继续补全其子路径,实际体验会像“这个目录不能展开”。 - Suggested fix — 对 symlink 再做一次目标类型判定;如果目标是目录,应按
directory处理并在补全结果中追加/。如果不想在热路径上增加额外 IO,至少应评估是否需要保留当前行为,并补充相应注释说明取舍。
- What's wrong —
Verdict
Comment — 没有阻塞合并的 critical 问题,但建议修正以上两个点以提升路径补全的一致性和真实项目目录结构下的可用性。
— gpt-5.4
Verify that path completion does not add trailing space after autocomplete (unlike AT/SLASH mode), which allows users to continue navigating deeper into directories. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Windows backslash path support to isPathLikeToken (~\, .\, ..\) - Clear suggestions/loading state when usePathCompletion is disabled to prevent stale UI state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
更正:上一条整体 review comment 误用了 summary review 形式,请忽略,不作为本次 review 结论。 本次 review 以刚发布的两条 inline comments 为准。 — gpt-5.4 |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Resolve symlink target type via stat() so symlinks to directories show as directories with trailing / and allow continued navigation - Use toCodePoints() instead of [...str].length for consistency with existing ASCII fast path - Fix test: use vi.spyOn(process, 'cwd') instead of direct assignment - Add tests for symlink-to-directory and broken symlink handling Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Map.set() on an existing key preserves its original insertion position, which would cause frequently-updated entries to be evicted prematurely. Delete-then-reinsert to maintain correct LRU ordering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
wenshao
left a comment
There was a problem hiding this comment.
🔍 第一轮代码审查
总体评价
代码质量良好,架构清晰,遵循了现有 hook 模式。所有 159 个测试通过。以下是我发现的问题:
🟡 建议改进
1. completionEnd 使用 toCodePoints 但 handleAutocomplete 用 buffer.lines 偏移量做替换 — 潜在的 Unicode 不一致
文件: useCommandCompletion.tsx 第 148-151 行
if (isPathLikeToken(firstToken)) {
return {
completionMode: CompletionMode.PATH,
query: firstToken,
completionStart: 0,
completionEnd: toCodePoints(firstToken).length, // ← code point 长度
};
}但 handleAutocomplete 中用的是 logicalPosToOffset,而 logicalPosToOffset 内部基于 toCodePoints 计算偏移量。所以 completionEnd 必须是 code point 长度才能正确。当前实现是正确的,但建议加个注释说明 completionEnd 的语义是 code point 长度(而非 UTF-16 字符串长度),与其他模式保持一致。
2. parsePartialPath 中 basename 对含空格路径的处理
文件: directoryCompletion.ts 第 155 行
const prefix = basename(partialPath);如果用户输入 ./my file,basename 会正确返回 my file。但如果用户输入 ./my\ file(shell 风格转义),basename 返回 my\ file 而非 my file。在 CLI 场景中这可能导致搜索不到。这虽然不是 bug(当前其他补全也不处理转义),但值得注意为 future work。
3. getDirectoryCompletions 没有被使用
文件: directoryCompletion.ts 第 241-261 行
getDirectoryCompletions 函数被导出并有测试覆盖,但实际代码中没有任何调用方。只有 getPathCompletions 被 usePathCompletion 调用。建议要么移除,要么在 PR 描述中说明它是为将来 directory-only 模式预留的。
4. scanDirectory 函数也是死代码
同上,scanDirectory 只被 getDirectoryCompletions 调用,而后者未被使用。scanDirectoryForPaths 才是实际被使用的函数。
5. 缓存是模块级单例
directoryCache 和 pathCache 是模块级变量。这意味着:
- 不同 CLI 实例共享同一个缓存(虽然 CLI 通常只有一个实例)
- 测试之间如果忘记调用
clearPathCache()会造成污染 - 缓存没有 size 属性暴露,无法监控使用情况
建议在 PR 描述中说明为什么选择模块级缓存而非实例级缓存(例如:简化 hook 接口、避免重复扫描)。
6. usePathCompletion 的 AbortController 实际上没有被使用
文件: usePathCompletion.ts 第 72-80 行
const controller = new AbortController();
abortRef.current = controller;
try {
const results = await getPathCompletions(query, { ... });
if (!controller.signal.aborted) { ... }AbortController 被创建并赋值给 abortRef,但 abort() 从未在任何地方被调用。当组件卸载时,cleanup 函数确实调用了 abortRef.current.abort(),但这是因为 setTimeout 已经被 clearTimeout 取消了,abort() 实际上已经来不及了。
关键问题是:在 debounce timer 内部创建的 AbortController,其 abort() 只在 cleanup 中调用,但 cleanup 同时也 clearTimeout 了 timer。所以要么 timer 被取消(永远不执行),要么 timer 已经在执行(abort() 来不及取消异步操作)。
getPathCompletions 本身也不接受 AbortSignal,所以即使调用了 abort() 也不会取消文件系统扫描。
建议:要么移除这个无效的 AbortController,要么将 signal 传入 getPathCompletions 以支持真正的取消。
🟢 小建议
7. 测试中 usePathCompletion.test.ts 只是占位符
注释说 "The hook uses setTimeout debounce which causes OOM in jsdom test environment"。两个测试实际上只是重新导入了 directoryCompletion.ts 的函数做基本验证,并没有真正测试 hook 的行为。不过通过 useCommandCompletion.test.ts 中的集成测试做了间接覆盖,这可以接受。
8. SLASH/PATH 歧义的 afterSlash === "" 条件
const isSlashMode =
slashCommands.length === 0 ||
afterSlash === "" ||
slashCommands.some(...);当用户输入恰好是 / 时,afterSlash 为空字符串,会进入 SLASH 模式。这是正确行为。但如果 slashCommands 为空数组(比如没有注册任何斜杠命令),afterSlash === "" 会让 / 进入 SLASH 模式而非 PATH 模式。这是有意为之的向后兼容设计(注释中说明了),但值得在代码中多一行注释。
9. getPathCompletions 中 lastSlash 和 lastSep 的计算
const lastSlash = partialPath.lastIndexOf("/");
const lastSep = partialPath.lastIndexOf(sep);
const lastSeparatorPos = Math.max(lastSlash, lastSep);在 macOS/Linux 上 sep === "/", 所以 lastSlash === lastSep,Math.max 冗余但不影响正确性。Windows 上 sep === "\\",此时两个值才不同。当前实现是跨平台安全的。
wenshao
left a comment
There was a problem hiding this comment.
🔍 代码审查 — glm-5.1
整体质量良好,架构遵循现有 hook 模式。以下是需要关注的行内问题:
wenshao
left a comment
There was a problem hiding this comment.
🔍 第二轮反向审查 — glm-5.1
从攻击者/边界条件视角重新审查,发现以下新问题:
🔴 关键问题:PATH 模式下绝对路径使用了错误的 SuggestionsDisplay 布局
InputPrompt.tsx 中 mode 的计算逻辑基于 buffer.text.startsWith('/')。当 PATH 模式激活且用户输入 /etc/nginx 这样的绝对路径时,mode 被设为 'slash',导致建议列表使用斜杠命令布局(label 最多占 50% 宽度),路径建议被截断。
修复建议:将 completionMode 暴露在 useCommandCompletion 返回值中,然后在 InputPrompt.tsx 中根据 completionMode === CompletionMode.PATH 来决定 mode。
(此问题在 InputPrompt.tsx 中,不在本 PR 变更范围内,但与本 PR 功能直接相关)
wenshao
left a comment
There was a problem hiding this comment.
🏁 第三轮最终审查总结 — glm-5.1
经过三轮审查(正向 → 反向边界 → 最终确认),以下是完整的问题清单:
🔴 必须修复(2 项)
-
completionMode未暴露:UseCommandCompletionReturn不含completionMode,导致InputPrompt.tsx无法区分 PATH 和 SLASH 模式。绝对路径(如/etc/nginx)在 SuggestionsDisplay 中使用了错误的'slash'布局。→ 在返回值中添加completionMode字段 -
死代码:
scanDirectory+getDirectoryCompletions+DirectoryEntry类型 +directoryCache均无调用方。应移除或明确标注为 future work
🟡 建议修复(3 项)
homedir()空值守卫:homedir()可能返回undefined,导致join(undefined, ...)抛出 TypeErrorAbortController无效:debounce timer 内的AbortController无法实际取消操作(getPathCompletions不接受AbortSignal)。建议简化或真正传递 signal- Windows
C:/正则:/^[a-zA-Z]:\\/不匹配C:/foo。改为/^[a-zA-Z]:[\\/]/
🟢 小改进(4 项)
- 模块级缓存应添加注释说明设计理由
completionEnd是 code point 长度,建议注释说明afterSlash === ""的向后兼容设计意图建议注释说明usePathCompletion.test.ts是占位符,后续 PR 应补充 hook 级测试
✅ 确认无问题的方面
- Unicode 路径处理正确
handleAutocomplete正确保留行尾内容- SLASH/PATH 消歧逻辑正确(
/./、/../正常落入 PATH 模式) toCodePoints/logicalPosToOffset偏移量一致- 测试全部通过(159 tests)
- 许可证头正确
总体评价:功能实现扎实,建议先修复 2 个 🔴 问题后合并。
- Add completionMode to UseCommandCompletionReturn so consumers (e.g., InputPrompt) can distinguish PATH vs SLASH mode for correct SuggestionsDisplay layout - Fix Windows path regex to accept both C:\ and C:/ separators Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Background
Users frequently need to reference file paths in prompts (e.g.
src/components/auth/LoginForm.tsx). Currently they must type full paths manually, which is error-prone and slow in large projects with deep directory structures.Related: Item-18: Directory/File Path Completion
Before / After
@prefix triggers file completion/,./,../,~/as path prefixes/home/user/...misidentified as a slash command — no suggestions shownImplementation
.gitignore-aware filteringCompletionMode.PATH; resolves SLASH/PATH conflict so/homeis no longer treated as a slash commandChanges
utils/directoryCompletion.ts(+348)hooks/usePathCompletion.ts(+167)hooks/useCommandCompletion.tsx(+57/-8)Tests
Manual verification: Launch the CLI, type
./sr— you should see suggestions likesrc/. Press Tab to accept and continue completing the next directory level.