v0.1.38 d3a4767

theme.go RawHistoryFolder

1// Package tui is a small terminal UI: raw-mode input, differential
2// line renderer, multi-line editor, and chat view. No external TUI
3// framework; just ANSI escape codes.
4package tui
5
6import "strings"
7
8// ANSI 256-color palette used by zot. Defined as numeric codes so we
9// can swap themes without changing any render code.
10type Theme struct {
11	FG          int
12	Muted       int
13	Accent      int
14	User        int // label color for the user role
15	Assistant   int // label color for the zot role
16	Tool        int
17	ToolOut     int
18	Error       int
19	Warning     int
20	Spinner     int // spinner + funny working line
21	SelectionBG int // background for highlighted rows
22	SelectionFG int // foreground for highlighted rows
23}
24
25var Dark = Theme{
26	FG:          253,
27	Muted:       244,
28	Accent:      111, // soft blue
29	User:        180, // warm tan
30	Assistant:   117, // bright cyan — the zot label color
31	Tool:        114, // green
32	ToolOut:     245,
33	Error:       203,
34	Warning:     214,
35	Spinner:     183, // soft purple
36	SelectionBG: 24,  // deep blue background
37	SelectionFG: 231, // near-white foreground
38}
39
40var Light = Theme{
41	FG:          236,
42	Muted:       244,
43	Accent:      33,
44	User:        94,
45	Assistant:   31, // deep cyan
46	Tool:        28,
47	ToolOut:     240,
48	Error:       160,
49	Warning:     166,
50	Spinner:     91,  // purple
51	SelectionBG: 153, // light blue
52	SelectionFG: 232, // near-black
53}
54
55// FG256 wraps s in foreground color c using ANSI 256-color SGR.
56func (t Theme) FG256(c int, s string) string {
57	return sgrFG(c) + s + reset
58}
59
60// BG256 wraps s in background color c using ANSI 256-color SGR.
61// Useful when the visible cell needs a coloured fill but the
62// underlying character should be a regular space (so mouse-copy
63// from the terminal yields whitespace instead of a glyph).
64func (t Theme) BG256(c int, s string) string {
65	return sgrBG(c) + s + reset
66}
67
68// AccentBar returns a 2-cell-wide leader: a coloured half-block
69// glyph followed by a plain space gutter. Used as the speaker-label
70// prefix in the chat ("▌ you", "▌ zot") and as the editor prompt so
71// the bar reads consistently across the UI.
72func (t Theme) AccentBar(c int) string {
73	return t.FG256(c, "▌ ")
74}
75
76// Highlight paints s with the theme's selection colors (foreground +
77// background). The caller is responsible for padding s to the desired
78// width; styling alone does not extend the background past content.
79func (t Theme) Highlight(s string) string {
80	return sgrFG(t.SelectionFG) + sgrBG(t.SelectionBG) + s + reset
81}
82
83// PadHighlight styles s and extends the selection background to the
84// full terminal width so the highlight is a full row, not just a
85// rectangle around the text.
86func (t Theme) PadHighlight(s string, width int) string {
87	visible := visibleWidth(s)
88	if visible < width {
89		s += strings.Repeat(" ", width-visible)
90	}
91	return sgrFG(t.SelectionFG) + sgrBG(t.SelectionBG) + s + reset
92}
93
94// Bold wraps s in bold SGR.
95func Bold(s string) string { return "\x1b[1m" + s + "\x1b[22m" }
96
97// Dim wraps s in dim SGR.
98func Dim(s string) string { return "\x1b[2m" + s + "\x1b[22m" }
99
100// Italic wraps s in italic SGR.
101func Italic(s string) string { return "\x1b[3m" + s + "\x1b[23m" }
102
103const reset = "\x1b[0m"
104
105func sgrFG(c int) string { return "\x1b[38;5;" + itoa(c) + "m" }
106func sgrBG(c int) string { return "\x1b[48;5;" + itoa(c) + "m" }
107
108// small itoa to avoid pulling strconv into this hot path twice.
109func itoa(n int) string {
110	if n == 0 {
111		return "0"
112	}
113	buf := [4]byte{}
114	i := len(buf)
115	for n > 0 {
116		i--
117		buf[i] = byte('0' + n%10)
118		n /= 10
119	}
120	return string(buf[i:])
121}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close