144 lines
3.2 KiB
Go
144 lines
3.2 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
sidebarCols = 28
|
|
statusRows = 1
|
|
)
|
|
|
|
// drawSidebar paints the right-rail session tree + scratchpad list.
|
|
// SPEC §4: the rail is the active session's child hierarchy on top and
|
|
// the scratchpad list (with preview) on the bottom.
|
|
//
|
|
// Implementation note: the PTY child's winsize is constrained to the
|
|
// computed main viewport, so the sidebar region is outside the child's
|
|
// cursor range. We can redraw freely without fighting the child for cells.
|
|
func (st *uiState) drawSidebar() {
|
|
st.mu.Lock()
|
|
palOpen := st.palette != nil
|
|
focus := st.focusedID
|
|
st.mu.Unlock()
|
|
if palOpen {
|
|
return
|
|
}
|
|
|
|
layout := st.layoutSnapshot()
|
|
if !layout.sidebarVisible || layout.hostRows < 4 {
|
|
return
|
|
}
|
|
left := int(layout.sidebarLeft)
|
|
width := int(layout.sidebarWidth) - 1
|
|
maxRow := int(layout.statusRow) - statusRows
|
|
|
|
var b strings.Builder
|
|
// Border column at left-1: a single vertical pipe.
|
|
for r := 1; r <= maxRow; r++ {
|
|
fmt.Fprintf(&b, "\x1b[%d;%dH\x1b[2m│\x1b[0m", r, left-1)
|
|
}
|
|
|
|
row := 1
|
|
writeLine := func(s string, style string) {
|
|
if row > maxRow {
|
|
return
|
|
}
|
|
if len(s) > width {
|
|
s = s[:width]
|
|
}
|
|
fmt.Fprintf(&b, "\x1b[%d;%dH%s%s\x1b[0m\x1b[K", row, left, style, padRight(s, width))
|
|
row++
|
|
}
|
|
|
|
writeLine(" Session tree", "\x1b[1m")
|
|
writeLine(strings.Repeat("─", width-1), "\x1b[2m")
|
|
|
|
children := visibleSessionTree(st.sess.Children(), focus)
|
|
if len(children) == 0 {
|
|
writeLine(" (empty)", "\x1b[2m")
|
|
}
|
|
for _, c := range children {
|
|
glyph := "◉"
|
|
marker := " "
|
|
if c.ID == focus {
|
|
marker = "▶ "
|
|
}
|
|
indent := ""
|
|
if c.ParentID != "" {
|
|
indent = " "
|
|
}
|
|
line := fmt.Sprintf(" %s%s%s %s", marker, indent, glyph, c.Name)
|
|
style := ""
|
|
if c.ID == focus {
|
|
style = "\x1b[1m"
|
|
}
|
|
writeLine(line, style)
|
|
}
|
|
|
|
// Scratchpads list — pick the most-recently-modified one as the
|
|
// preview target. SPEC §4.
|
|
var previewName string
|
|
if row+2 <= maxRow {
|
|
row++
|
|
writeLine(" Scratchpads", "\x1b[1m")
|
|
writeLine(strings.Repeat("─", width-1), "\x1b[2m")
|
|
entries, err := st.pads.List()
|
|
if err == nil {
|
|
if len(entries) == 0 {
|
|
writeLine(" (none)", "\x1b[2m")
|
|
}
|
|
var newest string
|
|
var newestTS string
|
|
for _, e := range entries {
|
|
if e.ModifiedAt > newestTS {
|
|
newestTS = e.ModifiedAt
|
|
newest = e.Name
|
|
}
|
|
}
|
|
previewName = newest
|
|
for _, e := range entries {
|
|
if row > maxRow {
|
|
break
|
|
}
|
|
marker := " "
|
|
style := ""
|
|
if e.Name == previewName {
|
|
marker = " ▸ "
|
|
style = "\x1b[1m"
|
|
}
|
|
writeLine(marker+e.Name, style)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Preview pane at the bottom of the rail. Reserve up to 8 rows.
|
|
if previewName != "" && row+2 <= maxRow {
|
|
row++
|
|
writeLine(strings.Repeat("─", width-1), "\x1b[2m")
|
|
writeLine(" "+previewName, "\x1b[1m")
|
|
content, _, err := st.pads.Read(previewName)
|
|
if err == nil {
|
|
for _, line := range strings.Split(content, "\n") {
|
|
if row > maxRow {
|
|
break
|
|
}
|
|
writeLine(" "+line, "\x1b[2m")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Blank-fill any rows the rail content didn't cover so stale
|
|
// content from a previous redraw doesn't linger.
|
|
for row <= maxRow {
|
|
writeLine("", "")
|
|
}
|
|
|
|
st.outMu.Lock()
|
|
// Save cursor; emit the sidebar; restore.
|
|
fmt.Fprintf(os.Stdout, "\x1b7%s\x1b8", b.String())
|
|
st.outMu.Unlock()
|
|
}
|