# 输入、slash 命令与补全 · 每次按键背后

> 从一次按键到一条 slash 命令被识别、补全、执行，中间是一套动作注册表 + 模糊补全 + 两级命令查找。这一章看 grok-pager 的键位冒泡、slash 补全的即时重算与内联 ghost，以及命令的可见性门控。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/input-and-completions
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 输入、slash 命令与补全 · 每次按键背后

[TUI 架构那章](https://daiw.org/manual/grok-build/tui-architecture)里的 Action 从哪来？从你每一次按键。这一章看输入这条线：键位怎么路由、slash 命令怎么补全和执行。

## 键位：动作注册表 + 精确冒泡

`ActionRegistry` 是**键位/动作/提示的唯一真相源**——一个 `ActionDef` 列表，每个带 `id`、默认键、上下文 `When`(`Always` / `PromptFocused` / `ScrollbackFocused` / …)。三个消费者共用它：快捷键栏、命令面板、按键派发。这张表还按渲染模式分别构建(`defaults_for`)：同一个 `Ctrl+G`，minimal 模式下是用外部编辑器改 prompt，全屏下是开任务面板。键位目前仍是内置的，不能自定义。

键位**冒泡是显式的**：按下一个键，依次精确匹配 `PromptFocused|ScrollbackFocused` → `AgentScreen` → `Always`。还有个 vim-mode 门控：vim 关时，裸字母(`j`/`k`)的绑定被压掉，好让字母正常打进输入框。甚至连「立即发送」(打断当前 turn，马上发出这条消息)的组合键都随终端品牌变(`Ctrl+Enter` / `Ctrl+O` / `Ctrl+L`)——因为不同终端对 `Ctrl+Enter` 的编码不同。这些「不酷但真实」的适配，占了输入层不少代码。

## slash 补全：每次按键重算

slash 系统(`slash/`)是**同步**的(无 async)。每个输入框有个 `SlashController`，每敲一个键就 `refresh` 重算补全：用 `nucleo` 模糊匹配，按(模糊分、是否完整打出了命令名、MRU 最近使用、内置优先)排序；只敲一个 `/` 时则按分组列出——命令在前，其次内置技能，最后其他技能。下拉框一屏 8 行，超出可以滚动。

两个体验细节：

- **内联 ghost**:tab 补全的灰字提示，和下拉选中项**同一个排序器**算出来，保证「你看到的 ghost」就是「Tab 会补的」。
- **中途 slash**：不只是行首，句子中间的 `/command` token 也能识别补全。

## 命令：trait + 两级查找

每个命令实现 `SlashCommand` trait(`name`/`aliases`/`takes_args`/`visible`/`run`……)，返回一个 `CommandResult`，共 8 种：常见的有 `Action(...)`(异步工作统一表达成一个 Action，交回 dispatch 跑)、`InjectSkill{...}`、`Message`、`Error`、`QueueCommand`(走排队命令管道，如 `/compact`)和 `PassThrough`(交给 shell 当普通 prompt)。约 70 个内置命令，基本一命令一文件。

`CommandRegistry` 有个巧妙的**两级查找**：

- `get()`——应用**全部**门控(菜单语义，决定「列不列出来」)；
- `get_for_dispatch()`——只跳过 `menu_hidden`(一个**完整打出来的隐藏命令仍能执行**)，但保留硬门控。

可见性由一堆 setter 控制(`set_dashboard_visible`、`set_auto_mode_available`、`set_available_tools`……)，其中 `set_available_tools` 在工具集未知时 fail-closed。ACP 来的命令目录也同步进来；技能和内置命令重名时，由 shell 先改成限定名(如 `acme:login`)再下发，pager 这边敲后缀 `/login` 也能匹配到它。

## 曾经的项目选择器

早先的版本从**非项目目录**发第一条 prompt 时，会弹一个项目选择器(当前目录 + 最近 5 个项目目录 + 「别再问了」)。HEAD 已删掉这个功能：收集最近目录的逻辑挪到了 `recent_dirs.rs`，只给 Agent Dashboard 选工作位置用，`Effect::SetWorkingDir` 也改由 dashboard 切换位置时触发。

## 和《从 LLM 到 Coding Agent》对照

对照 [Claude Code 的 slash 命令](https://daiw.org/manual/claude-code/commands-session)：教学实现里输入就是读一行、`if startswith('/')`。grok 的增量是一整套**可测的动作注册表 + 精确键位冒泡 + 即时模糊补全 + 两级命令门控 + 终端品牌适配**。下一章看输入之外的另一半：agent 干活时，你怎么**看见**它改了什么。

---

上一篇：[终端里实时渲染 markdown](https://daiw.org/manual/grok-build/markdown-render) · 下一篇：[diff 与工具调用可视化](https://daiw.org/manual/grok-build/diff-visualization)
