Kode-cli/docs/ELEGANT_TAB_IMPROVEMENT_PLAN.md
CrazyBoyM 926df2cfaf feat: Ultra-redesign completion system with @mention integration
- Complete architectural overhaul of useUnifiedCompletion hook
- Unified state management: 8 separate states → single CompletionState interface
- Simplified core logic: getWordAtCursor 194 lines → 42 lines (78% reduction)
- Fixed infinite React update loops with ref-based input tracking
- Smart triggering mechanism replacing aggressive auto-completion
- Integrated @agent and @file mention system with system reminders
- Added comprehensive agent loading and mention processing
- Enhanced Tab/Arrow/Enter key handling with clean event management
- Maintained 100% functional compatibility across all completion types

Key improvements:
• File path completion (relative, absolute, ~expansion, @references)
• Slash command completion (/help, /model, etc.)
• Agent completion (@agent-xxx with intelligent descriptions)
• System command completion (PATH scanning with fallback)
• Terminal-style Tab cycling, Enter confirmation, Escape cancellation
• Preview mode with boundary calculation
• History navigation compatibility
• Empty directory handling with user feedback

Architecture: Event-driven @mention detection → system reminder injection → LLM tool usage
Performance: Eliminated 7-layer nested conditionals, reduced state synchronization issues
Reliability: Fixed maximum update depth exceeded warnings, stable state management
2025-08-21 01:21:12 +08:00

7.0 KiB
Raw Permalink Blame History

优雅的Tab补全改进计划

一、当前架构分析

核心数据结构(保持不变)

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处理逻辑 需要重构

二、最小化改动方案

1. 添加Tab状态跟踪新增数据结构

// 添加到文件顶部与其他interface并列
interface TabState {
  lastTabTime: number
  consecutiveTabCount: number
  lastPrefix: string
  lastSuggestions: UnifiedSuggestion[]
}

2. 添加公共前缀计算(纯函数,无副作用)

// 添加为独立的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
}

3. 重构Tab处理逻辑核心改动

将现有的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 // 蜂鸣
    }
  }
})

4. 添加辅助函数(与现有风格一致)

// 完成补全
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
}, [])

三、实施步骤

Phase 1: 基础设施(不影响现有功能)

  1. 添加 TabState interface
  2. 添加 tabState useRef
  3. 添加 findCommonPrefix 函数
  4. 添加辅助函数

Phase 2: 核心逻辑替换(原子操作)

  1. 备份现有Tab处理代码
  2. 替换为新的决策树逻辑
  3. 测试所有场景

Phase 3: 细节优化

  1. 调整超时时间500ms vs 300ms
  2. 优化菜单显示格式
  3. 添加蜂鸣反馈(可选)

四、影响评估

不变的部分90%

  • 所有数据结构
  • 所有生成函数
  • 箭头键处理
  • Effect清理逻辑
  • 与PromptInput的接口

改变的部分10%

  • Tab按键处理逻辑
  • 新增4个小函数
  • 新增1个状态ref

风险评估

  • 低风险:改动集中在一处
  • 可回滚:逻辑独立,易于回滚
  • 向后兼容:接口不变

五、测试场景

场景1: 多个文件补全

# 文件: package.json, package-lock.json
输入: p[Tab]
期望: 补全到 "package"
输入: package[Tab][Tab]
期望: 显示菜单

场景2: 唯一匹配

输入: READ[Tab]
期望: 补全到 "README.md"

场景3: 连续补全

输入: src/[Tab]
期望: 可以继续Tab补全

六、代码风格指南

保持一致性

  • 使用 useCallback 包装所有函数
  • 使用 as const 断言类型
  • 保持简洁的注释风格

命名规范

  • 动词开头:completeWith, updateTabState
  • 布尔值:isDoubleTab, isActive
  • 常量大写:TAB_TIMEOUT

错误处理

  • 保持静默失败(符合现有风格)
  • 使用 try-catch 包装文件操作

七、预期效果

Before

cat p[Tab]
▸ package.json     # 立即显示菜单 ❌
  package-lock.json

After

cat p[Tab]
cat package        # 补全公共前缀 ✅
cat package[Tab][Tab]
package.json  package-lock.json  # 双Tab显示 ✅

八、总结

这个方案:

  1. 最小化改动 - 90%代码不变
  2. 原子操作 - 可以一次性替换
  3. 风格一致 - 像原生代码
  4. 100%终端兼容 - 完全匹配bash行为

准备好实施了吗?