Initial patterm project
This commit is contained in:
143
internal/app/sidebar.go
Normal file
143
internal/app/sidebar.go
Normal file
@@ -0,0 +1,143 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user