新增: skills 视图与标题编辑测试(未落库功能批量入库)
This commit is contained in:
+228
-14
@@ -7,7 +7,7 @@ import tea "charm.land/bubbletea/v2"
|
||||
func (m *Model) handleKey(s string) (*Model, tea.Cmd) {
|
||||
// 编辑标题时优先走输入处理,避免 q/1/2/u 等被全局拦截
|
||||
if m.editTitleMode {
|
||||
return m.handleHistoryKey(s)
|
||||
return m.handleEditTitleInput(s)
|
||||
}
|
||||
|
||||
switch s {
|
||||
@@ -20,11 +20,11 @@ func (m *Model) handleKey(s string) (*Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tab 快捷切换: 1=工作空间 2=历史对话
|
||||
// Tab 快捷切换: 1=工作空间 2=历史对话 3=技能
|
||||
if s == "1" {
|
||||
m.editTitleMode = false
|
||||
m.editTitleBuf = ""
|
||||
if m.IsHistoryTab() {
|
||||
if m.IsHistoryTab() || m.IsSkillsTab() {
|
||||
m.activeGroup = 0
|
||||
m.resetCursor()
|
||||
m.wsFocus = 1
|
||||
@@ -40,7 +40,19 @@ func (m *Model) handleKey(s string) (*Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
if s == "3" {
|
||||
if !m.IsSkillsTab() {
|
||||
m.activeGroup = len(Groups) + 1 // SKILLS
|
||||
m.resetCursor()
|
||||
m.wsFocus = 1
|
||||
return m, m.onSkillsTabSwitch()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if m.IsSkillsTab() {
|
||||
return m.handleSkillsKey(s)
|
||||
}
|
||||
if m.IsHistoryTab() {
|
||||
return m.handleHistoryKey(s)
|
||||
}
|
||||
@@ -55,6 +67,140 @@ func (m *Model) onTabSwitch() tea.Cmd {
|
||||
return ScanSessionsCmd()
|
||||
}
|
||||
|
||||
func (m *Model) onSkillsTabSwitch() tea.Cmd {
|
||||
if m.Skills.Loaded {
|
||||
return nil
|
||||
}
|
||||
m.Skills.Scanning = true
|
||||
return ScanSkillsCmd()
|
||||
}
|
||||
|
||||
// --- 技能 tab 按键 ---
|
||||
|
||||
func (m *Model) handleSkillsKey(s string) (*Model, tea.Cmd) {
|
||||
switch s {
|
||||
case "up", "k":
|
||||
switch m.Skills.FocusPanel {
|
||||
case 0:
|
||||
m.moveSkillsSkill(-1)
|
||||
case 1:
|
||||
m.moveSkillsFile(-1)
|
||||
case 2:
|
||||
m.scrollSkillContent(-1)
|
||||
}
|
||||
case "down", "j":
|
||||
switch m.Skills.FocusPanel {
|
||||
case 0:
|
||||
m.moveSkillsSkill(1)
|
||||
case 1:
|
||||
m.moveSkillsFile(1)
|
||||
case 2:
|
||||
m.scrollSkillContent(1)
|
||||
}
|
||||
case "pgup":
|
||||
if m.Skills.FocusPanel == 2 {
|
||||
m.scrollSkillContent(-10)
|
||||
}
|
||||
case "pgdown":
|
||||
if m.Skills.FocusPanel == 2 {
|
||||
m.scrollSkillContent(10)
|
||||
}
|
||||
case "tab", "right", "l":
|
||||
// 进内容面板需已加载内容
|
||||
if m.Skills.FocusPanel < 1 {
|
||||
m.Skills.FocusPanel = 1
|
||||
m.autoLoadCurrentFile()
|
||||
} else if m.Skills.FocusPanel == 1 && m.Skills.ContentPath != "" {
|
||||
m.Skills.FocusPanel = 2
|
||||
}
|
||||
case "shift+tab", "left", "h":
|
||||
if m.Skills.FocusPanel > 0 {
|
||||
m.Skills.FocusPanel--
|
||||
}
|
||||
case "enter":
|
||||
switch m.Skills.FocusPanel {
|
||||
case 0:
|
||||
m.Skills.FocusPanel = 1
|
||||
m.Skills.FileCur = 0
|
||||
m.autoLoadCurrentFile()
|
||||
case 1:
|
||||
fn := m.currentFlatNode()
|
||||
if fn != nil {
|
||||
if fn.IsDir {
|
||||
fn.Expanded = !fn.Expanded
|
||||
flat := m.skillsFlattened()
|
||||
if m.Skills.FileCur >= len(flat) {
|
||||
m.Skills.FileCur = len(flat) - 1
|
||||
}
|
||||
} else {
|
||||
m.loadSkillContent(fn.Path)
|
||||
m.Skills.FocusPanel = 2 // 打开文件即聚焦内容,便于滚动
|
||||
}
|
||||
}
|
||||
}
|
||||
case "r", "f5":
|
||||
m.Skills.Loaded = false
|
||||
m.Skills.Scanning = true
|
||||
return m, ScanSkillsCmd()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// scrollSkillContent 滚动内容面板,边界由 View 层按实际高度再夹紧
|
||||
func (m *Model) scrollSkillContent(dir int) {
|
||||
m.Skills.ContentScroll += dir
|
||||
if m.Skills.ContentScroll < 0 {
|
||||
m.Skills.ContentScroll = 0
|
||||
}
|
||||
}
|
||||
|
||||
// autoLoadCurrentFile 若当前文件树光标停在文件上,自动加载其内容
|
||||
func (m *Model) autoLoadCurrentFile() {
|
||||
if fn := m.currentFlatNode(); fn != nil && !fn.IsDir {
|
||||
m.loadSkillContent(fn.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) moveSkillsSkill(dir int) {
|
||||
if len(m.Skills.Skills) == 0 {
|
||||
return
|
||||
}
|
||||
m.Skills.SkillCur += dir
|
||||
if m.Skills.SkillCur < 0 {
|
||||
m.Skills.SkillCur = len(m.Skills.Skills) - 1
|
||||
}
|
||||
if m.Skills.SkillCur >= len(m.Skills.Skills) {
|
||||
m.Skills.SkillCur = 0
|
||||
}
|
||||
m.Skills.FileCur = 0
|
||||
m.Skills.Content = ""
|
||||
m.Skills.ContentPath = ""
|
||||
}
|
||||
|
||||
func (m *Model) moveSkillsFile(dir int) {
|
||||
flat := m.skillsFlattened()
|
||||
if len(flat) == 0 {
|
||||
return
|
||||
}
|
||||
m.Skills.FileCur += dir
|
||||
if m.Skills.FileCur < 0 {
|
||||
m.Skills.FileCur = len(flat) - 1
|
||||
}
|
||||
if m.Skills.FileCur >= len(flat) {
|
||||
m.Skills.FileCur = 0
|
||||
}
|
||||
// 光标移到文件即自动加载内容;目录则清空内容区
|
||||
if fn := m.currentFlatNode(); fn != nil {
|
||||
if fn.IsDir {
|
||||
m.Skills.Content = ""
|
||||
m.Skills.ContentPath = ""
|
||||
m.Skills.ContentScroll = 0
|
||||
} else {
|
||||
m.loadSkillContent(fn.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Workspace 按键 ---
|
||||
|
||||
func (m *Model) handleWorkspaceKey(s string) (*Model, tea.Cmd) {
|
||||
@@ -86,11 +232,16 @@ func (m *Model) handleWorkspaceKey(s string) (*Model, tea.Cmd) {
|
||||
return m.launchByInput()
|
||||
}
|
||||
if m.wsFocus == 0 {
|
||||
if m.wsTabCur >= len(Groups) {
|
||||
if m.wsTabCur == len(Groups) {
|
||||
m.activeGroup = len(Groups)
|
||||
m.resetCursor()
|
||||
return m, m.onTabSwitch()
|
||||
}
|
||||
if m.wsTabCur == len(Groups)+1 {
|
||||
m.activeGroup = len(Groups) + 1
|
||||
m.resetCursor()
|
||||
return m, m.onSkillsTabSwitch()
|
||||
}
|
||||
m.activeGroup = m.wsTabCur
|
||||
m.wsFocus = 1
|
||||
} else {
|
||||
@@ -110,7 +261,7 @@ func (m *Model) resetCursor() {
|
||||
}
|
||||
|
||||
func (m *Model) moveGroupCursor(dir int) {
|
||||
total := len(Groups) + 1 // 含历史入口
|
||||
total := len(Groups) + 2 // 含历史对话入口和技能入口
|
||||
m.wsTabCur = (m.wsTabCur + dir + total) % total
|
||||
if m.wsTabCur < len(Groups) {
|
||||
m.activeGroup = m.wsTabCur
|
||||
@@ -174,7 +325,8 @@ func (m *Model) handleHistoryKey(s string) (*Model, tea.Cmd) {
|
||||
s := m.currentSession()
|
||||
m.editTitleMode = true
|
||||
m.editTitleBuf = s.DisplayTitle()
|
||||
m.history.FocusPanel = 1 // 确保右栏可见
|
||||
m.editTitleCursor = len([]rune(m.editTitleBuf))
|
||||
m.history.FocusPanel = 1
|
||||
}
|
||||
case "c":
|
||||
if m.currentSession() != nil && m.history.FocusPanel == 1 {
|
||||
@@ -192,25 +344,85 @@ func (m *Model) handleEditTitleInput(s string) (*Model, tea.Cmd) {
|
||||
case "esc":
|
||||
m.editTitleMode = false
|
||||
m.editTitleBuf = ""
|
||||
m.editTitleCursor = 0
|
||||
case "backspace":
|
||||
r := []rune(m.editTitleBuf)
|
||||
if len(r) > 0 {
|
||||
m.editTitleBuf = string(r[:len(r)-1])
|
||||
m.backspaceTitle()
|
||||
case "delete":
|
||||
m.deleteTitleChar()
|
||||
case "left", "h":
|
||||
if m.editTitleCursor > 0 {
|
||||
m.editTitleCursor--
|
||||
}
|
||||
case "right", "l":
|
||||
r := []rune(m.editTitleBuf)
|
||||
if m.editTitleCursor < len(r) {
|
||||
m.editTitleCursor++
|
||||
}
|
||||
case "home", "ctrl+a":
|
||||
m.editTitleCursor = 0
|
||||
case "end", "ctrl+e":
|
||||
m.editTitleCursor = len([]rune(m.editTitleBuf))
|
||||
case "space":
|
||||
m.insertTitleRune(' ')
|
||||
default:
|
||||
// 编辑状态下忽略导航/功能键,避免 up/down/tab 等被当作字符输入
|
||||
switch s {
|
||||
case "up", "down", "left", "right", "tab", "shift+tab":
|
||||
// 不处理
|
||||
case "up", "down", "tab", "shift+tab":
|
||||
default:
|
||||
if s != "" && s[0] >= 32 {
|
||||
m.editTitleBuf += s
|
||||
if len(s) == 1 && s[0] >= 32 {
|
||||
m.insertTitleRune(rune(s[0]))
|
||||
} else if len(s) > 1 {
|
||||
m.insertTitleString(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) insertTitleRune(r rune) {
|
||||
buf := []rune(m.editTitleBuf)
|
||||
pos := m.editTitleCursor
|
||||
if pos > len(buf) {
|
||||
pos = len(buf)
|
||||
}
|
||||
buf = append(buf[:pos], append([]rune{r}, buf[pos:]...)...)
|
||||
m.editTitleBuf = string(buf)
|
||||
m.editTitleCursor = pos + 1
|
||||
}
|
||||
|
||||
func (m *Model) insertTitleString(s string) {
|
||||
buf := []rune(m.editTitleBuf)
|
||||
pos := m.editTitleCursor
|
||||
if pos > len(buf) {
|
||||
pos = len(buf)
|
||||
}
|
||||
ins := []rune(s)
|
||||
buf = append(buf[:pos], append(ins, buf[pos:]...)...)
|
||||
m.editTitleBuf = string(buf)
|
||||
m.editTitleCursor = pos + len(ins)
|
||||
}
|
||||
|
||||
func (m *Model) backspaceTitle() {
|
||||
buf := []rune(m.editTitleBuf)
|
||||
if m.editTitleCursor > 0 && len(buf) > 0 {
|
||||
pos := m.editTitleCursor - 1
|
||||
if pos > len(buf) {
|
||||
pos = len(buf) - 1
|
||||
}
|
||||
buf = append(buf[:pos], buf[pos+1:]...)
|
||||
m.editTitleBuf = string(buf)
|
||||
m.editTitleCursor--
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) deleteTitleChar() {
|
||||
buf := []rune(m.editTitleBuf)
|
||||
pos := m.editTitleCursor
|
||||
if len(buf) > 0 && pos < len(buf) {
|
||||
buf = append(buf[:pos], buf[pos+1:]...)
|
||||
m.editTitleBuf = string(buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) handleForkInput(s string) (*Model, tea.Cmd) {
|
||||
switch s {
|
||||
case "enter":
|
||||
@@ -225,6 +437,8 @@ func (m *Model) handleForkInput(s string) (*Model, tea.Cmd) {
|
||||
default:
|
||||
if len(s) == 1 && s[0] >= 32 && s[0] < 127 {
|
||||
m.forkBuf += s
|
||||
} else if len(s) > 1 {
|
||||
m.forkBuf += s
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
|
||||
Reference in New Issue
Block a user