interface UnifiedSuggestion // ✅ 完美,不需要改动
interface CompletionContext // ✅ 完美,不需要改动 // 当前状态
const [suggestions, setSuggestions] // ✅ 保持
const [selectedIndex, setSelectedIndex] // ✅ 保持
const [isActive, setIsActive] // ✅ 保持
const lastTabContext = useRef() // ✅ 保持
// 需要添加的状态(最小化)
const tabState = useRef<TabState>() // 🆕 Tab按键状态getWordAtCursor()✅ 完美,不改generateCommandSuggestions()✅ 完美,不改generateAgentSuggestions()✅ 完美,不改generateFileSuggestions()✅ 完美,不改generateSuggestions()✅ 完美,不改- Tab处理逻辑 ❌ 需要重构
// 添加到文件顶部,与其他interface并列
interface TabState {
lastTabTime: number
consecutiveTabCount: number
lastPrefix: string
lastSuggestions: UnifiedSuggestion[]
}// 添加为独立的utility函数
const findCommonPrefix = (suggestions: UnifiedSuggestion[]): string => {
if (suggestions.length === 0) return ''
if (suggestions.length === 1) return suggestions[0].value
const values = suggestions.map(s => s.value)
let prefix = values[0]
for (let i = 1; i < values.length; i++) {
while (prefix && !values[i].startsWith(prefix)) {
prefix = prefix.slice(0, -1)
}
if (!prefix) break
}
return prefix
}将现有的Tab处理(185-237行)替换为新的智能处理:
// Handle Tab key - Terminal-compliant behavior
useInput(async (_, key) => {
if (!key.tab || key.shift) return false
const context = getWordAtCursor()
if (!context) return false
const now = Date.now()
const isDoubleTab = tabState.current &&
(now - tabState.current.lastTabTime) < 500 &&
tabState.current.lastPrefix === context.prefix
// 如果菜单已显示,Tab选择下一个
if (isActive && suggestions.length > 0) {
// 保持原有逻辑
const selected = suggestions[selectedIndex]
// ... 完成逻辑
return true
}
// 生成建议(只在需要时)
let currentSuggestions = suggestions
if (!isDoubleTab || suggestions.length === 0) {
currentSuggestions = await generateSuggestions(context)
}
// 决策树 - 完全符合终端行为
if (currentSuggestions.length === 0) {
// 无匹配:蜂鸣
return false
} else if (currentSuggestions.length === 1) {
// 唯一匹配:立即完成
completeWith(currentSuggestions[0], context)
resetTabState()
return true
} else {
// 多个匹配
const commonPrefix = findCommonPrefix(currentSuggestions)
if (commonPrefix.length > context.prefix.length) {
// 可以补全到公共前缀
partialComplete(commonPrefix, context)
updateTabState(now, context.prefix, currentSuggestions)
return true
} else if (isDoubleTab) {
// 第二次Tab:显示菜单
setSuggestions(currentSuggestions)
setIsActive(true)
setSelectedIndex(0)
return true
} else {
// 第一次Tab但无法补全:记录状态
updateTabState(now, context.prefix, currentSuggestions)
return false // 蜂鸣
}
}
})// 完成补全
const completeWith = useCallback((suggestion: UnifiedSuggestion, context: CompletionContext) => {
const completion = context.type === 'command' ? `/${suggestion.value} ` :
context.type === 'agent' ? `@${suggestion.value} ` :
suggestion.value
const newInput = input.slice(0, context.startPos) + completion + input.slice(context.endPos)
onInputChange(newInput)
setCursorOffset(context.startPos + completion.length)
}, [input, onInputChange, setCursorOffset])
// 部分补全
const partialComplete = useCallback((prefix: string, context: CompletionContext) => {
const newInput = input.slice(0, context.startPos) + prefix + input.slice(context.endPos)
onInputChange(newInput)
setCursorOffset(context.startPos + prefix.length)
}, [input, onInputChange, setCursorOffset])
// Tab状态管理
const updateTabState = useCallback((time: number, prefix: string, suggestions: UnifiedSuggestion[]) => {
tabState.current = {
lastTabTime: time,
consecutiveTabCount: (tabState.current?.consecutiveTabCount || 0) + 1,
lastPrefix: prefix,
lastSuggestions: suggestions
}
}, [])
const resetTabState = useCallback(() => {
tabState.current = null
}, [])- 添加
TabStateinterface - 添加
tabStateuseRef - 添加
findCommonPrefix函数 - 添加辅助函数
- 备份现有Tab处理代码
- 替换为新的决策树逻辑
- 测试所有场景
- 调整超时时间(500ms vs 300ms)
- 优化菜单显示格式
- 添加蜂鸣反馈(可选)
- 所有数据结构
- 所有生成函数
- 箭头键处理
- Effect清理逻辑
- 与PromptInput的接口
- Tab按键处理逻辑
- 新增4个小函数
- 新增1个状态ref
- 低风险:改动集中在一处
- 可回滚:逻辑独立,易于回滚
- 向后兼容:接口不变
# 文件: package.json, package-lock.json
输入: p[Tab]
期望: 补全到 "package"
输入: package[Tab][Tab]
期望: 显示菜单输入: READ[Tab]
期望: 补全到 "README.md"输入: src/[Tab]
期望: 可以继续Tab补全- 使用
useCallback包装所有函数 - 使用
as const断言类型 - 保持简洁的注释风格
- 动词开头:
completeWith,updateTabState - 布尔值:
isDoubleTab,isActive - 常量大写:
TAB_TIMEOUT
- 保持静默失败(符合现有风格)
- 使用 try-catch 包装文件操作
cat p[Tab]
▸ package.json # 立即显示菜单 ❌
package-lock.json
cat p[Tab]
cat package # 补全公共前缀 ✅
cat package[Tab][Tab]
package.json package-lock.json # 双Tab显示 ✅
这个方案:
- 最小化改动 - 90%代码不变
- 原子操作 - 可以一次性替换
- 风格一致 - 像原生代码
- 100%终端兼容 - 完全匹配bash行为
准备好实施了吗?