新增: 会话分叉功能,优化增量扫描缓存
- 会话分叉: 按 c 键从历史会话分叉,支持带方向提示 - 启动自动加载历史记录 - 增量扫描: 缓存内存化、目录 modTime 跳过、已删除条目裁剪 - 刷新按钮复用 onTabSwitch 单一入口
This commit is contained in:
189
internal/view_ws.go
Normal file
189
internal/view_ws.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
"u-tabs/internal/style"
|
||||
)
|
||||
|
||||
// --- Workspace 三栏渲染 ---
|
||||
|
||||
func (m *Model) renderWorkspace() string {
|
||||
g := Groups[m.activeGroup]
|
||||
svcs := WorkspacesByGroup(g.Label)
|
||||
if len(svcs) == 0 {
|
||||
return style.SubtitleStyle.Render(" empty")
|
||||
}
|
||||
|
||||
avail := m.width - 8
|
||||
tabW := max(14, min(20, avail*18/100))
|
||||
listW := max(30, avail*45/100)
|
||||
detailW := avail - tabW - listW
|
||||
|
||||
tabCw := tabW - 2
|
||||
listCw := listW - 2
|
||||
detailCw := detailW - 2
|
||||
|
||||
listH := max(3, m.height-7)
|
||||
|
||||
tabLines := m.tabsColumnLines(tabCw, listH-1)
|
||||
svcLines := m.wsListLines(g, svcs, listCw, listH-1)
|
||||
detailLines := m.wsDetailLines(svcs, detailCw, listH-1)
|
||||
|
||||
maxRows := min(listH-1, max(max(len(tabLines), len(svcLines)), len(detailLines)))
|
||||
|
||||
tabBorderFg := style.BgPanel
|
||||
if m.wsFocus == 0 {
|
||||
tabBorderFg = style.Accent
|
||||
}
|
||||
listBorderFg := style.BgPanel
|
||||
if m.wsFocus == 1 {
|
||||
listBorderFg = style.Accent
|
||||
}
|
||||
|
||||
panelSty := lipgloss.NewStyle().Border(lipgloss.RoundedBorder())
|
||||
|
||||
tabBox := panelSty.BorderForeground(tabBorderFg).Width(tabW).Render(
|
||||
style.DetailTitle.Render("tabs") + "\n" + padLinesTo(tabLines, tabCw, maxRows))
|
||||
listBox := panelSty.BorderForeground(listBorderFg).Width(listW).Render(
|
||||
style.DetailTitle.Render(g.Label) + "\n" + padLinesTo(svcLines, listCw, maxRows))
|
||||
detailBox := panelSty.BorderForeground(style.BgPanel).Width(detailW).Render(
|
||||
style.DetailTitle.Render("detail") + "\n" + padLinesTo(detailLines, detailCw, maxRows))
|
||||
|
||||
return lipgloss.JoinHorizontal(lipgloss.Top, tabBox, " ", listBox, " ", detailBox)
|
||||
}
|
||||
|
||||
func (m *Model) tabsColumnLines(w, listH int) []string {
|
||||
var lines []string
|
||||
total := len(Groups) + 1
|
||||
start, end := viewport(m.wsTabCur, total, listH)
|
||||
|
||||
focused := m.wsFocus == 0
|
||||
|
||||
for i := start; i < end; i++ {
|
||||
if i == len(Groups) {
|
||||
renderNavEntry(&lines, w, "历史对话", "#7dcfff", m.wsTabCur == i, focused)
|
||||
continue
|
||||
}
|
||||
|
||||
g := Groups[i]
|
||||
label := g.Label
|
||||
gs := style.GroupStyles[g.Label]
|
||||
isActive := m.wsTabCur == i
|
||||
if isActive {
|
||||
line := " ▸ " + label
|
||||
line = padRightByWidth(truncateByWidth(line, w), w)
|
||||
if focused && gs.GetForeground() != (lipgloss.Color("")) {
|
||||
sty := lipgloss.NewStyle().Foreground(style.BgDark).Background(gs.GetForeground()).Bold(true)
|
||||
lines = append(lines, sty.Render(line))
|
||||
} else if gs.GetForeground() != (lipgloss.Color("")) {
|
||||
sty := lipgloss.NewStyle().Foreground(gs.GetForeground()).Bold(true)
|
||||
lines = append(lines, sty.Render(line))
|
||||
} else {
|
||||
lines = append(lines, style.SelStyle.Render(line))
|
||||
}
|
||||
} else {
|
||||
line := " " + label
|
||||
line = padRightByWidth(truncateByWidth(line, w), w)
|
||||
if gs.GetForeground() != (lipgloss.Color("")) {
|
||||
lines = append(lines, gs.Render(line))
|
||||
} else {
|
||||
lines = append(lines, style.NormStyle.Render(line))
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m *Model) wsListLines(g Group, svcs []Workspace, w, listH int) []string {
|
||||
start, end := viewport(m.cursor, len(svcs), listH)
|
||||
|
||||
maxTitleW := 0
|
||||
for _, ws := range svcs {
|
||||
if tw := stringWidth(ws.Title); tw > maxTitleW {
|
||||
maxTitleW = tw
|
||||
}
|
||||
}
|
||||
|
||||
var lines []string
|
||||
for i := start; i < end; i++ {
|
||||
ws := svcs[i]
|
||||
cur := " "
|
||||
if i == m.cursor {
|
||||
cur = "▸"
|
||||
}
|
||||
mark := " "
|
||||
if m.selected[ws.Index] {
|
||||
mark = "✓"
|
||||
}
|
||||
|
||||
paddedTitle := padRightByWidth(ws.Title, maxTitleW)
|
||||
|
||||
if i == m.cursor {
|
||||
prefix := cur + " " + mark + " " + fmt.Sprintf("%02d", ws.N) + " "
|
||||
remainW := max(10, w-stringWidth(prefix))
|
||||
text := truncateByWidth(paddedTitle+" "+ws.Prompt, remainW)
|
||||
sty := style.SelStyle
|
||||
if m.wsFocus != 1 {
|
||||
sty = lipgloss.NewStyle().Foreground(style.Accent).Bold(true)
|
||||
}
|
||||
lines = append(lines, sty.Render(padRightByWidth(truncateByWidth(prefix+text, w), w)))
|
||||
} else {
|
||||
markStr := " "
|
||||
if m.selected[ws.Index] {
|
||||
markStr = style.MarkStyle.Render("✓")
|
||||
}
|
||||
num := style.NumStyle.Render(fmt.Sprintf("%02d", ws.N))
|
||||
prefix := cur + " " + markStr + " " + num + " "
|
||||
remainW := max(10, w-stringWidth(prefix))
|
||||
text := truncateByWidth(paddedTitle+" "+ws.Prompt, remainW)
|
||||
lines = append(lines, style.NormStyle.Render(padRightByWidth(truncateByWidth(prefix+text, w), w)))
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m *Model) wsDetailLines(svcs []Workspace, w, listH int) []string {
|
||||
if m.cursor >= len(svcs) {
|
||||
return []string{lipgloss.NewStyle().Foreground(style.Dim).Render(fitWidth(" ← select to view", w))}
|
||||
}
|
||||
|
||||
ws := svcs[m.cursor]
|
||||
sepSty := lipgloss.NewStyle().Foreground(style.BgPanel)
|
||||
var lines []string
|
||||
|
||||
lines = append(lines, style.DetailTitle.Render(fitWidth(" "+ws.Title, w)))
|
||||
lines = append(lines, sepSty.Render(fitWidth(strings.Repeat("─", w), w)))
|
||||
|
||||
rows := []struct {
|
||||
key string
|
||||
val string
|
||||
sty lipgloss.Style
|
||||
}{
|
||||
{"dir", ws.Dir, style.ValStyle},
|
||||
{"no", fmt.Sprintf("%02d · %s", ws.N, ws.Group), style.NumStyle},
|
||||
{"desc", ws.Prompt, style.ValStyle},
|
||||
{"tech", ws.Tech, style.TechStyle},
|
||||
{"deploy", ws.Deploy, style.DeployStyle},
|
||||
}
|
||||
for _, r := range rows {
|
||||
line := fmt.Sprintf(" %-6s %s", r.key, r.sty.Render(r.val))
|
||||
lines = append(lines, style.NormStyle.Render(fitWidth(line, w)))
|
||||
}
|
||||
|
||||
lines = append(lines, sepSty.Render(fitWidth(strings.Repeat("─", w), w)))
|
||||
|
||||
hintCmd := "wt"
|
||||
if !isWindows {
|
||||
hintCmd = "bash"
|
||||
}
|
||||
lines = append(lines, lipgloss.NewStyle().Foreground(style.Accent).Italic(true).Render(fitWidth(" $ "+hintCmd+" → CC @"+ws.Title, w)))
|
||||
lines = append(lines, lipgloss.NewStyle().Foreground(style.Dim).Render(fitWidth(" [Enter]start [Space]multi", w)))
|
||||
|
||||
if len(lines) > listH {
|
||||
lines = lines[:listH]
|
||||
}
|
||||
return lines
|
||||
}
|
||||
Reference in New Issue
Block a user