v0.38.1 969e52a

layout.go RawHistoryFolder

1package tui
2
3import "strings"
4
5const (
6	InputStylePlain = "plain"
7	InputStyleLines = "lines"
8	InputStyleBlock = "block"
9
10	StatusPositionAboveInput = "above_input"
11	StatusPositionBelowInput = "below_input"
12
13	WorkingPositionAboveInput = "above_input"
14	WorkingPositionBelowInput = "below_input"
15
16	SubagentPositionAboveInput = "above_input"
17	SubagentPositionBelowInput = "below_input"
18)
19
20func NormalizeInputStyle(v string) string {
21	switch strings.ToLower(strings.TrimSpace(v)) {
22	case InputStyleLines, "line":
23		return InputStyleLines
24	case InputStyleBlock, "bubble", "boxed", "box":
25		return InputStyleBlock
26	default:
27		return InputStylePlain
28	}
29}
30
31func NormalizeStatusPosition(v string) string {
32	switch strings.ToLower(strings.TrimSpace(v)) {
33	case StatusPositionBelowInput, "below", "bottom":
34		return StatusPositionBelowInput
35	default:
36		return StatusPositionAboveInput
37	}
38}
39
40func NormalizeWorkingPosition(v string) string {
41	switch strings.ToLower(strings.TrimSpace(v)) {
42	case WorkingPositionBelowInput, "below", "bottom":
43		return WorkingPositionBelowInput
44	default:
45		return WorkingPositionAboveInput
46	}
47}
48
49// NormalizeSubagentPosition returns the placement for live subagent activity.
50// A missing value keeps activity immediately below the input by default.
51func NormalizeSubagentPosition(v string) string {
52	switch strings.ToLower(strings.TrimSpace(v)) {
53	case SubagentPositionAboveInput, "above", "top":
54		return SubagentPositionAboveInput
55	default:
56		return SubagentPositionBelowInput
57	}
58}
59
60func InputLines(th Theme, lines []string, width int) []string {
61	if width < 1 {
62		width = 1
63	}
64	rule := th.FGColor(th.Muted, strings.Repeat("─", width))
65	out := make([]string, 0, len(lines)+2)
66	out = append(out, rule)
67	out = append(out, lines...)
68	out = append(out, rule)
69	return out
70}
71
72func InputBlock(th Theme, lines []string, width int) []string {
73	if width < 1 {
74		width = 1
75	}
76	bubbleW := width - 2
77	if bubbleW < 1 {
78		bubbleW = 1
79	}
80	bg := th.sgrBGColor(th.UserBubbleBG)
81	bar := bg + th.FGColor(th.Accent, "▌ ")
82	row := func(content string) string {
83		visible := visibleWidth(content)
84		if visible < bubbleW {
85			content += strings.Repeat(" ", bubbleW-visible)
86		}
87		body := bg + strings.ReplaceAll(content, reset, reset+bg) + reset
88		return bar + body
89	}
90	out := make([]string, 0, len(lines)+2)
91	out = append(out, row(""))
92	for _, line := range lines {
93		out = append(out, row(line))
94	}
95	out = append(out, row(""))
96	return out
97}
98
99// CursorColor returns OSC 12 to set the terminal cursor color. Unlike SGR
100// styling, the cursor is terminal-owned, so modal backdrops must set this
101// separately from their dimmed text rows.
102func CursorColor(color TerminalColor) string {
103	rgb, ok := rgbForTerminalColor(color)
104	if !ok {
105		return ""
106	}
107	return "\x1b]12;rgb:" + hexByte(rgb[0]) + "/" + hexByte(rgb[1]) + "/" + hexByte(rgb[2]) + "\x07"
108}
109
110func CursorColor256(index int) string { return CursorColor(Color256(index)) }
111
112func ResetCursorColor() string { return "\x1b]112\x07" }
113
114func CursorShapeBlock() string { return "\x1b[1 q" }
115
116func ResetCursorShape() string { return "\x1b[0 q" }
117
118func xterm256RGB(index int) (int, int, int) {
119	if index < 0 {
120		index = 0
121	}
122	if index > 255 {
123		index = 255
124	}
125	ansi := [16][3]int{
126		{0, 0, 0}, {128, 0, 0}, {0, 128, 0}, {128, 128, 0},
127		{0, 0, 128}, {128, 0, 128}, {0, 128, 128}, {192, 192, 192},
128		{128, 128, 128}, {255, 0, 0}, {0, 255, 0}, {255, 255, 0},
129		{0, 0, 255}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255},
130	}
131	if index < 16 {
132		c := ansi[index]
133		return c[0], c[1], c[2]
134	}
135	if index < 232 {
136		idx := index - 16
137		levels := [6]int{0, 95, 135, 175, 215, 255}
138		return levels[idx/36], levels[(idx/6)%6], levels[idx%6]
139	}
140	gray := 8 + (index-232)*10
141	return gray, gray, gray
142}
143
144func hexByte(v int) string {
145	const digits = "0123456789abcdef"
146	if v < 0 {
147		v = 0
148	}
149	if v > 255 {
150		v = 255
151	}
152	return string([]byte{digits[v>>4], digits[v&0xf]})
153}

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