cat p[Tab]
# 🤔 思考:有 package.json, package-lock.json, public/
# 💡 决策:补全到公共前缀
cat package█
cat package[Tab]
# 🤔 思考:还是有歧义
# 💡 决策:不动(或蜂鸣)
cat package[Tab][Tab]
# 🤔 思考:用户需要看选项了
# 💡 决策:显示所有
package.json package-lock.json
cat package.j[Tab]
# 🤔 思考:唯一匹配!
# 💡 决策:直接补全
cat package.json█cat p[Tab]
# 😵 立即显示菜单
▸ package.json
package-lock.json
public/
# 用户:我只是想补全啊,不是要选择!按下 Tab
↓
有几个匹配?
↓
┌────────┼────────┐
0个 1个 多个
↓ ↓ ↓
蜂鸣 补全完成 有公共前缀?
↓
┌────┴────┐
有 无
↓ ↓
补全前缀 是第二次Tab?
↓
┌────┴────┐
是 否
↓ ↓
显示菜单 蜂鸣
- 最少按键: 能补全就补全,不要问
- 渐进显示: 只在需要时才显示选项
- 智能判断: 根据上下文做最合理的事
Tab = "帮我补全"
Tab Tab = "我需要看看有什么选项"
if (key.tab) {
if (suggestions.length > 1) {
showMenu() // ❌ 太早了!
}
}if (key.tab) {
const now = Date.now()
const isDoubleTab = (now - lastTabTime) < 300
if (suggestions.length === 0) {
beep()
} else if (suggestions.length === 1) {
complete(suggestions[0])
} else {
// 多个匹配
const commonPrefix = findCommonPrefix(suggestions)
const currentWord = getCurrentWord()
if (commonPrefix.length > currentWord.length) {
// 可以补全到公共前缀
complete(commonPrefix)
} else if (isDoubleTab) {
// 第二次Tab,显示菜单
showMenu()
} else {
// 第一次Tab,但没有可补全的
beep()
}
}
lastTabTime = now
}# 文件: README.md, README.txt, package.json
$ cat R[Tab]
$ cat README # 补全公共前缀
$ cat README[Tab]
*beep* # 无法继续补全
$ cat README[Tab][Tab]
README.md README.txt # 显示选项
$ cat README.m[Tab]
$ cat README.md # 唯一匹配,完成$ cd s[Tab]
$ cd src/ # 唯一匹配,补全+加斜杠
$ cd src/[Tab][Tab]
components/ hooks/ utils/ # 继续显示下级
$ cd src/c[Tab]
$ cd src/components/ # 继续补全| 操作 | 按键次数(现在) | 按键次数(改进后) | 节省 |
|---|---|---|---|
| 输入 package.json | 5次(p+Tab+↓+↓+Enter) | 3次(pa+Tab+.j+Tab) | 40% |
| 进入 src/components | 4次(s+Tab+Enter+c+Tab) | 2次(s+Tab+c+Tab) | 50% |
| 选择多个选项之一 | 3次(Tab+↓+Enter) | 4次(Tab+Tab+↓+Enter) | -33% |
平均效率提升:~30%
一个原则:Tab应该"尽力而为",而不是"立即放弃"
两个规则:
- 能补全就补全
- 不能补全才显示选项(且需要双击)
三个好处:
- 符合用户期望
- 减少操作次数
- 保持专注流程