v0.2.0 6e0cddc

ime_focus_test.go RawHistoryFolder

1package ui
2
3import (
4	"reflect"
5	"testing"
6)
7
8type orderedIME struct {
9	calls  []string
10	active bool
11}
12
13func (i *orderedIME) Enable() {
14	if i.active {
15		panic("IME enable before disable")
16	}
17	i.active = true
18	i.calls = append(i.calls, "enable")
19}
20func (i *orderedIME) Disable() {
21	if !i.active {
22		panic("IME double disable")
23	}
24	i.active = false
25	i.calls = append(i.calls, "disable")
26}
27func (i *orderedIME) Surrounding(string, int, int)                  {}
28func (i *orderedIME) CursorRect(float64, float64, float64, float64) {}
29func (i *orderedIME) ContentType(bool, bool)                        {}
30
31type recordingIME struct {
32	surrounding []string
33	password    bool
34}
35
36func (i *recordingIME) Enable()  {}
37func (i *recordingIME) Disable() {}
38func (i *recordingIME) Surrounding(text string, _, _ int) {
39	i.surrounding = append(i.surrounding, text)
40}
41func (i *recordingIME) CursorRect(float64, float64, float64, float64) {}
42func (i *recordingIME) ContentType(password, _ bool)                  { i.password = password }
43
44func TestPasswordEditorNeverSendsSecretToIME(t *testing.T) {
45	r := newRuntime()
46	ime := &recordingIME{}
47	r.ime = ime
48	value := "hunter2"
49	view := func(f *Frame) { f.Root().Input("secret", &value, Key("secret"), Password(true)) }
50	r.Build(view)
51	r.route(key("Tab"))
52	r.Build(view)
53	if !ime.password {
54		t.Fatal("password content type not sent")
55	}
56	if len(ime.surrounding) == 0 {
57		t.Fatal("no surrounding text update")
58	}
59	for _, s := range ime.surrounding {
60		if s != "" {
61			t.Fatalf("surrounding text leaked %q", s)
62		}
63	}
64}
65
66func TestIMEFocusTransferDisablesBeforeEnable(t *testing.T) {
67	r := newRuntime()
68	ime := &orderedIME{}
69	r.ime = ime
70	a, b := "one", "two"
71	view := func(f *Frame) { root := f.Root(); root.Input("a", &a, Key("a")); root.Textarea("b", &b, Key("b")) }
72	r.Build(view)
73	r.route(key("Tab"))
74	r.Build(view)
75	r.route(key("Tab"))
76	r.Build(view)
77	r.route(key("Tab"))
78	r.Build(view)
79	if want := []string{"enable", "disable", "enable", "disable", "enable"}; !reflect.DeepEqual(ime.calls, want) {
80		t.Fatalf("focus transition calls %v want %v", ime.calls, want)
81	}
82	if !r.edits[idKey(r.Target(0))].IMEActive() || r.edits[idKey(r.Target(1))].IMEActive() {
83		t.Fatal("wrong active editor")
84	}
85}

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 12

↑↓ moveenter openesc close