tui: highlight selected rows and accent frame in extension panel
Style ext panel rows that start with the selection markers (▸ / ●) using the theme's selection colors, pad them to full width, and keep ✓ glyphs in the tool color. Render the panel header and bottom rule in the accent color so panels stand out from regular dialogs.
- commit
- 25e7da2c0145b09b563e8f175b9770b3a85e3af1
- author
- patriceckhart · · 2026-05-17 11:48 UTC
- parent
- 1aea23e
- signature
- unsigned
- refs
- tag: v0.1.82
1 file · +23 -3
M internal/agent/modes/ext_panel_dialog.go +23 -3
| @@ -1,8 +1,11 @@ | |||
| 1 | 1 | package modes | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "strings" | |
| 5 | 6 | ||
| 7 | + | "github.com/mattn/go-runewidth" | |
| 8 | + | ||
| 6 | 9 | "github.com/patriceckhart/zot/internal/tui" | |
| 7 | 10 | ) | |
| 8 | 11 | ||
| @@ -56,14 +59,31 @@ func (d *extPanelDialog) Render(th tui.Theme, width int) []string { | |||
| 56 | 59 | if title == "" { | |
| 57 | 60 | title = d.ext | |
| 58 | 61 | } | |
| 59 | - | out := []string{frameHeader(th, title, width)} | |
| 62 | + | out := []string{frameHeaderColor(th, title, width, th.Accent)} | |
| 60 | 63 | for _, l := range d.lines { | |
| 61 | - | out = append(out, l) | |
| 64 | + | plain := stripANSIBytes(l) | |
| 65 | + | trimmed := strings.TrimLeft(plain, " ") | |
| 66 | + | selected := strings.HasPrefix(trimmed, "▸ ") || strings.HasPrefix(trimmed, "● ") | |
| 67 | + | out = append(out, styleExtPanelLine(th, plain, width, selected)) | |
| 62 | 68 | } | |
| 63 | 69 | if strings.TrimSpace(d.footer) != "" { | |
| 64 | 70 | out = append(out, "") | |
| 65 | 71 | out = append(out, th.FG256(th.Muted, d.footer)) | |
| 66 | 72 | } | |
| 67 | - | out = append(out, frameRule(th, width)) | |
| 73 | + | out = append(out, frameRuleColor(th, width, th.Accent)) | |
| 68 | 74 | return out | |
| 69 | 75 | } | |
| 76 | + | ||
| 77 | + | func styleExtPanelLine(th tui.Theme, plain string, width int, selected bool) string { | |
| 78 | + | if selected { | |
| 79 | + | if visible := runewidth.StringWidth(plain); visible < width { | |
| 80 | + | plain += strings.Repeat(" ", width-visible) | |
| 81 | + | } | |
| 82 | + | base := fmt.Sprintf("\x1b[38;5;%dm\x1b[48;5;%dm", th.SelectionFG, th.SelectionBG) | |
| 83 | + | green := fmt.Sprintf("\x1b[38;5;%dm\x1b[48;5;%dm", th.Tool, th.SelectionBG) | |
| 84 | + | return base + strings.ReplaceAll(plain, "✓", green+"✓"+base) + "\x1b[0m" | |
| 85 | + | } | |
| 86 | + | styled := th.FG256(th.Muted, plain) | |
| 87 | + | styled = strings.ReplaceAll(styled, "✓", th.FG256(th.Tool, "✓")) | |
| 88 | + | return styled | |
| 89 | + | } | |
| @@ -1,8 +1,11 @@ | |||||
| 1 | package modes | 1 | package modes | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "strings" | 5 | "strings" | ||
| 5 | 6 | ||||
| 7 | + | "github.com/mattn/go-runewidth" | |||
| 8 | + | ||||
| 6 | "github.com/patriceckhart/zot/internal/tui" | 9 | "github.com/patriceckhart/zot/internal/tui" | ||
| 7 | ) | 10 | ) | ||
| 8 | 11 | ||||
| @@ -56,14 +59,31 @@ func (d *extPanelDialog) Render(th tui.Theme, width int) []string { | |||||
| 56 | if title == "" { | 59 | if title == "" { | ||
| 57 | title = d.ext | 60 | title = d.ext | ||
| 58 | } | 61 | } | ||
| 59 | - | out := []string{frameHeader(th, title, width)} | 62 | + | out := []string{frameHeaderColor(th, title, width, th.Accent)} |
| 60 | for _, l := range d.lines { | 63 | for _, l := range d.lines { | ||
| 61 | - | out = append(out, l) | 64 | + | plain := stripANSIBytes(l) |
| 65 | + | trimmed := strings.TrimLeft(plain, " ") | |||
| 66 | + | selected := strings.HasPrefix(trimmed, "▸ ") || strings.HasPrefix(trimmed, "● ") | |||
| 67 | + | out = append(out, styleExtPanelLine(th, plain, width, selected)) | |||
| 62 | } | 68 | } | ||
| 63 | if strings.TrimSpace(d.footer) != "" { | 69 | if strings.TrimSpace(d.footer) != "" { | ||
| 64 | out = append(out, "") | 70 | out = append(out, "") | ||
| 65 | out = append(out, th.FG256(th.Muted, d.footer)) | 71 | out = append(out, th.FG256(th.Muted, d.footer)) | ||
| 66 | } | 72 | } | ||
| 67 | - | out = append(out, frameRule(th, width)) | 73 | + | out = append(out, frameRuleColor(th, width, th.Accent)) |
| 68 | return out | 74 | return out | ||
| 69 | } | 75 | } | ||
| 76 | + | ||||
| 77 | + | func styleExtPanelLine(th tui.Theme, plain string, width int, selected bool) string { | |||
| 78 | + | if selected { | |||
| 79 | + | if visible := runewidth.StringWidth(plain); visible < width { | |||
| 80 | + | plain += strings.Repeat(" ", width-visible) | |||
| 81 | + | } | |||
| 82 | + | base := fmt.Sprintf("\x1b[38;5;%dm\x1b[48;5;%dm", th.SelectionFG, th.SelectionBG) | |||
| 83 | + | green := fmt.Sprintf("\x1b[38;5;%dm\x1b[48;5;%dm", th.Tool, th.SelectionBG) | |||
| 84 | + | return base + strings.ReplaceAll(plain, "✓", green+"✓"+base) + "\x1b[0m" | |||
| 85 | + | } | |||
| 86 | + | styled := th.FG256(th.Muted, plain) | |||
| 87 | + | styled = strings.ReplaceAll(styled, "✓", th.FG256(th.Tool, "✓")) | |||
| 88 | + | return styled | |||
| 89 | + | } | |||