- 会话分叉: 按 c 键从历史会话分叉,支持带方向提示 - 启动自动加载历史记录 - 增量扫描: 缓存内存化、目录 modTime 跳过、已删除条目裁剪 - 刷新按钮复用 onTabSwitch 单一入口
288 lines
5.5 KiB
Go
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
|
|
}
|
|
}
|
|
}
|