v0.8.1 1f54f61

capabilities.go RawHistoryFolder

1// Package terminalcap owns pure terminal capability values and detection policy.
2package terminalcap
3
4import (
5	"fmt"
6	"strings"
7)
8
9// ColorMode is the color output mode selected for one attachment.
10type ColorMode uint8
11
12const (
13	// TrueColor is the zero value so manually constructed attachments retain the
14	// historical renderer behavior; live Hello paths always use the detector.
15	TrueColor ColorMode = iota
16	ANSI256
17	ANSI16
18	Monochrome
19)
20
21// Valid reports whether m is one of the defined color modes.
22func (m ColorMode) Valid() bool { return m <= Monochrome }
23
24// ColorCapabilities is the color output capability selected for one attachment
25// and how confidently it was selected.
26type ColorCapabilities struct {
27	Mode   ColorMode
28	Source Source
29}
30
31// Valid reports whether both the mode and the source are defined values.
32func (c ColorCapabilities) Valid() bool { return c.Mode.Valid() && c.Source.Valid() }
33
34// RGB reports whether the attachment can receive RGB ANSI output.
35func (c ColorCapabilities) RGB() bool { return c.Mode == TrueColor }
36
37// Source records how confidently a terminal capability was selected.
38type Source uint8
39
40const (
41	SourceUnknown Source = iota
42	SourceHeuristic
43	SourceDeclared
44	// SourceForced is an explicit user override that detection must not
45	// second-guess and that never warrants a downgrade notice.
46	SourceForced
47)
48
49// Valid reports whether s is one of the defined sources.
50func (s Source) Valid() bool { return s <= SourceForced }
51
52// Application identifies a known terminal application when its environment
53// provides a trustworthy origin signal.
54type Application uint8
55
56const (
57	ApplicationUnknown Application = iota
58	ApplicationKitty
59)
60
61// Capabilities describes the output features selected for one client attachment.
62type Capabilities struct {
63	Color         ColorCapabilities
64	Application   Application
65	KittyGraphics bool
66}
67
68// SupportsKittyGraphics reports whether the active outer-terminal probe
69// accepted the Kitty graphics protocol. Environment detection never sets it.
70func (c Capabilities) SupportsKittyGraphics() bool { return c.KittyGraphics }
71
72// Detect derives conservative attachment capabilities from a client environment.
73func Detect(env []string) Capabilities {
74	values := environmentValues(env)
75	term := strings.ToLower(strings.TrimSpace(values["TERM"]))
76	colorTerm := strings.ToLower(strings.TrimSpace(values["COLORTERM"]))
77	caps := Capabilities{Color: ColorCapabilities{Mode: ANSI256}}
78
79	if values["KITTY_WINDOW_ID"] != "" || values["KITTY_PID"] != "" || values["KITTY_LISTEN_ON"] != "" {
80		caps.Application = ApplicationKitty
81	}
82
83	kittyIdentity := term == "xterm-kitty" && caps.Application == ApplicationKitty
84	switch colorTerm {
85	case "truecolor", "24bit":
86		caps.Color.Mode = TrueColor
87		caps.Color.Source = SourceDeclared
88	}
89	if term == "xterm-direct" || strings.HasSuffix(term, "-direct") {
90		caps.Color.Mode = TrueColor
91		caps.Color.Source = SourceDeclared
92	}
93	if kittyIdentity {
94		if caps.Color.Source == SourceUnknown {
95			caps.Color.Source = SourceHeuristic
96		}
97		caps.Color.Mode = TrueColor
98		return caps
99	}
100	if caps.Color.Source == SourceDeclared {
101		return caps
102	}
103	if mode, ok := termColorMode(term); ok {
104		caps.Color.Mode = mode
105		caps.Color.Source = SourceDeclared
106	}
107	return caps
108}
109
110// termColorMode classifies a lowercase TERM value that carries no truecolor
111// signal. It is deliberately conservative: only well-known terminfo names map
112// to a constrained mode, and anything else reports ok=false so the caller keeps
113// its unverified 256-color default. The rules apply in this order:
114//
115//   - monochrome: dumb, vt52, vt100, vt102, vt220, or a name ending in -m or -mono;
116//   - 256 colors: any name containing "256color";
117//   - 16 colors: linux, ansi, cons25, or a name ending in -16color or -color.
118func termColorMode(term string) (ColorMode, bool) {
119	switch term {
120	case "dumb", "vt52", "vt100", "vt102", "vt220":
121		return Monochrome, true
122	case "linux", "ansi", "cons25":
123		return ANSI16, true
124	}
125	switch {
126	case strings.HasSuffix(term, "-m"), strings.HasSuffix(term, "-mono"):
127		return Monochrome, true
128	case strings.Contains(term, "256color"):
129		return ANSI256, true
130	case strings.HasSuffix(term, "-16color"), strings.HasSuffix(term, "-color"):
131		return ANSI16, true
132	}
133	return 0, false
134}
135
136// ParseColorMode parses a user color override: auto, truecolor, 256, 16, or
137// mono, case-insensitively and ignoring surrounding space. An empty value is
138// auto. When auto is true the caller must use detection and mode is
139// meaningless; an unknown value returns an error.
140func ParseColorMode(value string) (mode ColorMode, auto bool, err error) {
141	switch strings.ToLower(strings.TrimSpace(value)) {
142	case "", "auto":
143		return TrueColor, true, nil
144	case "truecolor":
145		return TrueColor, false, nil
146	case "256":
147		return ANSI256, false, nil
148	case "16":
149		return ANSI16, false, nil
150	case "mono":
151		return Monochrome, false, nil
152	}
153	return TrueColor, false, fmt.Errorf("invalid color mode %q (want auto, truecolor, 256, 16, or mono)", value)
154}
155
156func environmentValues(env []string) map[string]string {
157	values := make(map[string]string, len(env))
158	for _, entry := range env {
159		key, value, ok := strings.Cut(entry, "=")
160		if ok {
161			values[key] = value
162		}
163	}
164	return values
165}
166
167// Resolve selects attachment capabilities from the client environment and the
168// client's color claim. A declared or forced client color is used verbatim. A
169// heuristic truecolor claim upgrades a weaker daemon-side detection, because
170// the daemon may not see the client environment that produced the inference.
171// Any other claim falls back to environment detection.
172func Resolve(env []string, declared ColorCapabilities) Capabilities {
173	caps := Detect(env)
174	switch {
175	case declared.Source == SourceDeclared || declared.Source == SourceForced:
176		caps.Color = declared
177	case declared.Source == SourceHeuristic && declared.RGB() && !caps.Color.RGB():
178		caps.Color = declared
179	}
180	return caps
181}

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 12

Tags 11

↑↓ moveenter openesc close