Files
u-tabs/internal/key.go
绝尘 36aeef4bb7 新增: 会话分叉功能,优化增量扫描缓存
- 会话分叉: 按 c 键从历史会话分叉,支持带方向提示
- 启动自动加载历史记录
- 增量扫描: 缓存内存化、目录 modTime 跳过、已删除条目裁剪
- 刷新按钮复用 onTabSwitch 单一入口
2026-05-31 21:40:20 +08:00

288 lines
5.5 KiB
Go

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
}
}
}