package internal import ( "charm.land/bubbletea/v2" ) // --- 按键分发 --- func (m *Model) handleKey(s string) (*Model, tea.Cmd) { switch s { case "q", "ctrl+c": return m, tea.Quit case "u": if m.update.Available && !m.update.Updating { m.update.Updating = true return m, SelfUpdateCmd(m.update.DownloadURL, m.update.SHA256, m.update.NewVersion) } } // Tab 快捷切换: 1=工作空间 2=历史对话 if s == "1" { if m.IsHistoryTab() { m.activeGroup = 0 m.resetCursor() m.wsFocus = 1 } return m, nil } if s == "2" { if !m.IsHistoryTab() { m.activeGroup = len(Groups) // HISTORY m.resetCursor() m.wsFocus = 1 return m, m.onTabSwitch() } return m, nil } if m.IsHistoryTab() { return m.handleHistoryKey(s) } return m.handleWorkspaceKey(s) } func (m *Model) onTabSwitch() tea.Cmd { if !m.IsHistoryTab() || m.history.Scanning { return nil } m.history.Scanning = true return ScanSessionsCmd() } // --- Workspace 按键 --- func (m *Model) handleWorkspaceKey(s string) (*Model, tea.Cmd) { switch s { case "left", "h": if m.wsFocus > 0 { m.wsFocus = 0 } case "right", "l": if m.wsFocus < 1 { m.wsFocus = 1 } case "tab", "shift+tab": m.wsFocus = (m.wsFocus + 1) % 2 case "up", "k": if m.wsFocus == 0 { m.moveGroupCursor(-1) } else { m.moveCursor(-1) } case "down", "j": if m.wsFocus == 0 { m.moveGroupCursor(1) } else { m.moveCursor(1) } case "enter": if m.inputBuf != "" { return m.launchByInput() } if m.wsFocus == 0 { if m.wsTabCur >= len(Groups) { m.activeGroup = len(Groups) m.resetCursor() return m, m.onTabSwitch() } m.activeGroup = m.wsTabCur m.wsFocus = 1 } else { return m.launchSelected() } default: if len(s) == 1 && s[0] >= '0' && s[0] <= '9' { m.inputBuf += s } } return m, nil } func (m *Model) resetCursor() { m.cursor = 0 m.inputBuf = "" } func (m *Model) moveGroupCursor(dir int) { total := len(Groups) + 1 // 含历史入口 m.wsTabCur = (m.wsTabCur + dir + total) % total if m.wsTabCur < len(Groups) { m.activeGroup = m.wsTabCur } m.resetCursor() } // --- HISTORY 按键 --- func (m *Model) handleHistoryKey(s string) (*Model, tea.Cmd) { if m.forkMode { return m.handleForkInput(s) } switch s { case "up", "k": if m.history.FocusPanel == 0 { m.moveHistoryDir(-1) } else { m.moveHistorySess(-1) } case "down", "j": if m.history.FocusPanel == 0 { m.moveHistoryDir(1) } else { m.moveHistorySess(1) } case "tab", "right", "l": if m.history.FocusPanel < 1 { m.history.FocusPanel = 1 } case "shift+tab", "left", "h": if m.history.FocusPanel > 0 { m.history.FocusPanel = 0 } case "enter": if m.history.FocusPanel == 0 { if m.history.DirCursor == len(m.history.Projects)+2 { m.activeGroup = 0 m.resetCursor() m.wsTabCur = 0 m.wsFocus = 1 } m.history.FocusPanel = 1 } else { return m.resumeSelected() } case "n": return m.newSessionFromHistory() case "r", "f5": if cmd := m.onTabSwitch(); cmd != nil { return m, cmd } case "f": m.toggleFavorite() case "c": if m.currentSession() != nil && m.history.FocusPanel == 1 { m.forkMode = true m.forkBuf = "" } } return m, nil } func (m *Model) handleForkInput(s string) (*Model, tea.Cmd) { switch s { case "enter": return m.executeFork() case "esc": m.forkMode = false m.forkBuf = "" case "backspace": if len(m.forkBuf) > 0 { m.forkBuf = m.forkBuf[:len(m.forkBuf)-1] } default: if len(s) == 1 && s[0] >= 32 && s[0] < 127 { m.forkBuf += s } } return m, nil } // --- 光标移动 --- func (m *Model) moveHistoryDir(dir int) { if len(m.history.Projects) == 0 { return } m.history.DirCursor += dir maxDir := len(m.history.Projects) + 3 if m.history.DirCursor < 0 { m.history.DirCursor = maxDir - 1 } if m.history.DirCursor >= maxDir { m.history.DirCursor = 0 } m.history.SessCursor = 0 } func (m *Model) moveHistorySess(dir int) { sessions := m.currentSessions() if len(sessions) == 0 { return } m.history.SessCursor += dir if m.history.SessCursor < 0 { m.history.SessCursor = len(sessions) - 1 } if m.history.SessCursor >= len(sessions) { m.history.SessCursor = 0 } } func (m *Model) currentSessions() []*Session { if m.history.DirCursor == 0 { var all []*Session for _, pd := range m.history.Projects { all = append(all, pd.Sessions...) } return all } if m.history.DirCursor == 1 { return collectFavorites(m.history.Projects, m.history.Favorites) } idx := m.history.DirCursor - 2 if idx >= len(m.history.Projects) { return nil } return m.history.Projects[idx].Sessions } func (m *Model) currentSession() *Session { sessions := m.currentSessions() if len(sessions) == 0 { return nil } if m.history.SessCursor >= len(sessions) { m.history.SessCursor = len(sessions) - 1 } return sessions[m.history.SessCursor] } func (m *Model) currentDirCwd() string { if m.history.DirCursor <= 1 { return "" } idx := m.history.DirCursor - 2 if idx >= len(m.history.Projects) { return "" } return m.history.Projects[idx].Dir } func (m *Model) moveCursor(dir int) { svcs := WorkspacesByGroup(Groups[m.activeGroup].Label) if len(svcs) == 0 { return } m.cursor += dir if m.cursor < 0 { m.cursor = len(svcs) - 1 } if m.cursor >= len(svcs) { m.cursor = 0 } } func (m *Model) toggleMultiSelect() { svcs := WorkspacesByGroup(Groups[m.activeGroup].Label) if m.cursor < len(svcs) { idx := svcs[m.cursor].Index if m.selected[idx] { delete(m.selected, idx) } else { m.selected[idx] = true } } }