输入、slash 命令与补全 · 每次按键背后
从一次按键到一条 slash 命令被识别、补全、执行,中间是一套动作注册表 + 模糊补全 + 两级命令查找。这一章看 grok-pager 的键位冒泡、slash 补全的即时重算与内联 ghost,以及命令的可见性门控。
输入、slash 命令与补全 · 每次按键背后
TUI 架构那章里的 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:不只是行首,句子中间的
/commandtoken 也能识别补全。
命令: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 命令:教学实现里输入就是读一行、if startswith('/')。grok 的增量是一整套可测的动作注册表 + 精确键位冒泡 + 即时模糊补全 + 两级命令门控 + 终端品牌适配。下一章看输入之外的另一半:agent 干活时,你怎么看见它改了什么。
上一篇:终端里实时渲染 markdown · 下一篇:diff 与工具调用可视化