v0.8.1 1f54f61

capabilities_test.go RawHistoryFolder

1package terminalcap
2
3import (
4	"testing"
5
6	"github.com/stretchr/testify/require"
7)
8
9func TestColorCapabilities(t *testing.T) {
10	tests := []struct {
11		name    string
12		in      ColorCapabilities
13		wantRGB bool
14	}{
15		{name: "zero value is truecolor", in: ColorCapabilities{}, wantRGB: true},
16		{name: "truecolor", in: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, wantRGB: true},
17		{name: "ansi256", in: ColorCapabilities{Mode: ANSI256}, wantRGB: false},
18		{name: "ansi16", in: ColorCapabilities{Mode: ANSI16, Source: SourceForced}, wantRGB: false},
19		{name: "monochrome", in: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, wantRGB: false},
20	}
21	for _, tt := range tests {
22		t.Run(tt.name, func(t *testing.T) {
23			require.Equal(t, tt.wantRGB, tt.in.RGB())
24		})
25	}
26}
27
28func TestColorCapabilitiesValid(t *testing.T) {
29	tests := []struct {
30		name string
31		in   ColorCapabilities
32		want bool
33	}{
34		{name: "zero value", in: ColorCapabilities{}, want: true},
35		{name: "every mode and source", in: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, want: true},
36		{name: "unknown mode", in: ColorCapabilities{Mode: Monochrome + 1}},
37		{name: "unknown source", in: ColorCapabilities{Source: SourceForced + 1}},
38	}
39	for _, tt := range tests {
40		t.Run(tt.name, func(t *testing.T) {
41			require.Equal(t, tt.want, tt.in.Valid())
42		})
43	}
44}
45
46func TestResolve(t *testing.T) {
47	tests := []struct {
48		name     string
49		env      []string
50		declared ColorCapabilities
51		want     ColorCapabilities
52		wantApp  Application
53	}{
54		{name: "unknown claim falls back to detection", env: []string{"TERM=xterm-256color"}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
55		{name: "unknown truecolor claim falls back to detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
56		{name: "unknown claim with detected truecolor", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
57		{name: "declared truecolor overrides indexed detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
58		{name: "declared truecolor on unknown terminal", env: []string{"TERM=unknown"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}},
59		{name: "declared 256 overrides detected truecolor verbatim", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, declared: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
60		{name: "forced 16 colors", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, declared: ColorCapabilities{Mode: ANSI16, Source: SourceForced}, want: ColorCapabilities{Mode: ANSI16, Source: SourceForced}},
61		{name: "forced monochrome", env: nil, declared: ColorCapabilities{Mode: Monochrome, Source: SourceForced}, want: ColorCapabilities{Mode: Monochrome, Source: SourceForced}},
62		{name: "heuristic truecolor upgrades weaker detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, want: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}},
63		{name: "heuristic truecolor keeps stronger detection", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, want: ColorCapabilities{Mode: TrueColor, Source: SourceHeuristic}, wantApp: ApplicationKitty},
64		{name: "heuristic 16 colors falls back to detection", env: []string{"TERM=xterm-256color"}, declared: ColorCapabilities{Mode: ANSI16, Source: SourceHeuristic}, want: ColorCapabilities{Mode: ANSI256, Source: SourceDeclared}},
65		{name: "declared claim keeps detected application", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, declared: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, want: ColorCapabilities{Mode: TrueColor, Source: SourceDeclared}, wantApp: ApplicationKitty},
66	}
67	for _, tt := range tests {
68		t.Run(tt.name, func(t *testing.T) {
69			got := Resolve(tt.env, tt.declared)
70			require.Equal(t, tt.want, got.Color)
71			require.Equal(t, tt.wantApp, got.Application)
72			require.False(t, got.KittyGraphics)
73		})
74	}
75}
76
77func TestDetect(t *testing.T) {
78	tests := []struct {
79		name         string
80		env          []string
81		wantMode     ColorMode
82		wantSource   Source
83		wantApp      Application
84		wantGraphics bool
85	}{
86		{name: "declared truecolor", env: []string{"TERM=xterm-256color", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared},
87		{name: "declared 24bit", env: []string{"TERM=xterm-256color", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared},
88		{name: "direct terminfo entry", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared},
89		{name: "kitty signals infer truecolor", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty},
90		{name: "kitty environment with declared truecolor remains color-only", env: []string{"TERM=xterm-kitty", "COLORTERM=truecolor", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceDeclared, wantApp: ApplicationKitty},
91		{name: "256 color terminal remains a constrained attachment", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
92		{name: "unknown terminal is conservatively indexed", env: []string{"TERM=unknown"}, wantMode: ANSI256, wantSource: SourceUnknown},
93		{name: "empty TERM is unknown", env: nil, wantMode: ANSI256, wantSource: SourceUnknown},
94		{name: "TERM=screen is unknown", env: []string{"TERM=screen"}, wantMode: ANSI256, wantSource: SourceUnknown},
95		{name: "TERM=xterm is unknown", env: []string{"TERM=xterm"}, wantMode: ANSI256, wantSource: SourceUnknown},
96		{name: "screen-256color", env: []string{"TERM=screen-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
97		{name: "tmux-256color", env: []string{"TERM=tmux-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
98		{name: "TERM is case-insensitive and trimmed", env: []string{"TERM= LINUX "}, wantMode: ANSI16, wantSource: SourceDeclared},
99		{name: "dumb is monochrome", env: []string{"TERM=dumb"}, wantMode: Monochrome, wantSource: SourceDeclared},
100		{name: "vt52 is monochrome", env: []string{"TERM=vt52"}, wantMode: Monochrome, wantSource: SourceDeclared},
101		{name: "vt100 is monochrome", env: []string{"TERM=vt100"}, wantMode: Monochrome, wantSource: SourceDeclared},
102		{name: "vt102 is monochrome", env: []string{"TERM=vt102"}, wantMode: Monochrome, wantSource: SourceDeclared},
103		{name: "vt220 is monochrome", env: []string{"TERM=vt220"}, wantMode: Monochrome, wantSource: SourceDeclared},
104		{name: "-mono suffix is monochrome", env: []string{"TERM=xterm-mono"}, wantMode: Monochrome, wantSource: SourceDeclared},
105		{name: "-m suffix is monochrome", env: []string{"TERM=rxvt-m"}, wantMode: Monochrome, wantSource: SourceDeclared},
106		{name: "vt320 is not assumed monochrome", env: []string{"TERM=vt320"}, wantMode: ANSI256, wantSource: SourceUnknown},
107		{name: "linux console is 16 colors", env: []string{"TERM=linux"}, wantMode: ANSI16, wantSource: SourceDeclared},
108		{name: "ansi is 16 colors", env: []string{"TERM=ansi"}, wantMode: ANSI16, wantSource: SourceDeclared},
109		{name: "cons25 is 16 colors", env: []string{"TERM=cons25"}, wantMode: ANSI16, wantSource: SourceDeclared},
110		{name: "-16color suffix", env: []string{"TERM=xterm-16color"}, wantMode: ANSI16, wantSource: SourceDeclared},
111		{name: "-color suffix", env: []string{"TERM=xterm-color"}, wantMode: ANSI16, wantSource: SourceDeclared},
112		{name: "screen-color suffix", env: []string{"TERM=screen-color"}, wantMode: ANSI16, wantSource: SourceDeclared},
113		{name: "256color is not the -color suffix", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared},
114		{name: "COLORTERM truecolor beats linux", env: []string{"TERM=linux", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared},
115		{name: "COLORTERM 24bit beats dumb", env: []string{"TERM=dumb", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared},
116		{name: "direct terminfo beats mono suffix", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared},
117		{name: "unrelated COLORTERM does not change TERM rules", env: []string{"TERM=linux", "COLORTERM=yes"}, wantMode: ANSI16, wantSource: SourceDeclared},
118		{name: "kitty identity beats TERM rules", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty},
119		{name: "kitty environment behind a multiplexer is not graphics evidence", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, wantMode: ANSI256, wantSource: SourceDeclared, wantApp: ApplicationKitty},
120	}
121
122	for _, tt := range tests {
123		t.Run(tt.name, func(t *testing.T) {
124			got := Detect(tt.env)
125			require.Equal(t, tt.wantMode, got.Color.Mode)
126			require.Equal(t, tt.wantSource, got.Color.Source)
127			require.Equal(t, tt.wantApp, got.Application)
128			require.Equal(t, tt.wantGraphics, got.SupportsKittyGraphics())
129		})
130	}
131}
132
133func TestParseColorMode(t *testing.T) {
134	tests := []struct {
135		name     string
136		in       string
137		wantMode ColorMode
138		wantAuto bool
139		wantErr  bool
140	}{
141		{name: "empty is auto", in: "", wantAuto: true},
142		{name: "blank is auto", in: "  \t", wantAuto: true},
143		{name: "auto", in: "auto", wantAuto: true},
144		{name: "auto is case-insensitive", in: " AUTO ", wantAuto: true},
145		{name: "truecolor", in: "truecolor", wantMode: TrueColor},
146		{name: "truecolor mixed case", in: "TrueColor", wantMode: TrueColor},
147		{name: "256", in: "256", wantMode: ANSI256},
148		{name: "16", in: " 16 ", wantMode: ANSI16},
149		{name: "mono", in: "mono", wantMode: Monochrome},
150		{name: "mono upper case", in: "MONO", wantMode: Monochrome},
151		{name: "24bit is not accepted", in: "24bit", wantErr: true},
152		{name: "256color is not accepted", in: "256color", wantErr: true},
153		{name: "number out of set", in: "8", wantErr: true},
154		{name: "monochrome spelled out is not accepted", in: "monochrome", wantErr: true},
155		{name: "garbage", in: "rainbow", wantErr: true},
156	}
157	for _, tt := range tests {
158		t.Run(tt.name, func(t *testing.T) {
159			mode, auto, err := ParseColorMode(tt.in)
160			if tt.wantErr {
161				require.Error(t, err)
162				require.False(t, auto)
163				return
164			}
165			require.NoError(t, err)
166			require.Equal(t, tt.wantAuto, auto)
167			if !auto {
168				require.Equal(t, tt.wantMode, mode)
169			}
170		})
171	}
172}

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