inhibit_test.go RawHistoryFolder

1package core_test
2
3import (
4	"context"
5	"testing"
6	"testing/synctest"
7	"time"
8
9	"github.com/bnema/neferwl/internal/core"
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13// commandOf waits for the next command of type T, skipping others.
14func commandOf[T ports.ClientCommand](t *testing.T, commands <-chan ports.ClientCommand) T {
15	t.Helper()
16	deadline := time.After(2 * time.Second)
17	for {
18		select {
19		case c := <-commands:
20			if v, ok := c.(T); ok {
21				return v
22			}
23		case <-deadline:
24			var zero T
25			t.Fatalf("no %T", zero)
26			return zero
27		}
28	}
29}
30
31// A focused window inhibiting shortcuts gets bound keys; it stops when
32// it loses focus, and binds run again.
33func TestShortcutsInhibit(t *testing.T) { synctest.Test(t, shortcutsInhibit) }
34
35func shortcutsInhibit(t *testing.T) {
36	cfg := scrollDefaults()
37	input := make(chan ports.InputEvent, 4)
38	client := make(chan ports.ClientEvent, 4)
39	commands := make(chan ports.ClientCommand, 64)
40	scenes := make(chan []ports.Scene, 1)
41	spawn := make(chan ports.SpawnRequest, 4)
42	state := make(chan ports.State, 1)
43	output := make(chan ports.OutputEvent, 1)
44	c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn, State: state}, core.Options{})
45	if err != nil {
46		t.Fatal(err)
47	}
48	ctx, cancel := context.WithCancel(context.Background())
49	done := make(chan error, 1)
50	go func() { done <- c.Run(ctx) }()
51	defer func() { cancel(); receive(t, done) }()
52	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
53	scene(t, scenes)
54	client <- ports.WindowMapped{ID: 1}
55	scene(t, scenes)
56	client <- ports.ShortcutsInhibit{Window: 1, Active: true}
57	if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1, Active: true}) {
58		t.Fatal(v)
59	}
60	bound := ports.KeyEvent{Keysym: "Return", Mods: ports.ModSuper, Pressed: true}
61	input <- bound
62	if v := commandOf[ports.ForwardKey](t, commands); v != (ports.ForwardKey{ID: 1, Key: bound}) {
63		t.Fatal(v)
64	}
65	synctest.Wait()
66	select {
67	case r := <-spawn:
68		t.Fatalf("bind ran: %v", r)
69	default:
70	}
71	// A new window takes focus: the inhibitor goes inactive, binds run.
72	client <- ports.WindowMapped{ID: 2}
73	if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1}) {
74		t.Fatal(v)
75	}
76	input <- bound
77	receive(t, spawn)
78	// Idle inhibitors show in the state.
79	client <- ports.IdleInhibit{Window: 2, Active: true}
80	deadline := time.After(2 * time.Second)
81	for {
82		select {
83		case st := <-state:
84			for _, w := range st.Windows {
85				if w.ID == 2 && w.IdleInhibit {
86					return
87				}
88			}
89		case <-deadline:
90			t.Fatal("idle inhibit not in state")
91		}
92	}
93}
94
95// A key whose press ran a bind before inhibiting began is not released to
96// the window: it never saw the press.
97func TestShortcutsInhibitDropsBoundRelease(t *testing.T) {
98	cfg := scrollDefaults()
99	input := make(chan ports.InputEvent, 4)
100	client := make(chan ports.ClientEvent, 4)
101	commands := make(chan ports.ClientCommand, 64)
102	scenes := make(chan []ports.Scene, 1)
103	spawn := make(chan ports.SpawnRequest, 4)
104	output := make(chan ports.OutputEvent, 1)
105	c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn}, core.Options{})
106	if err != nil {
107		t.Fatal(err)
108	}
109	ctx, cancel := context.WithCancel(context.Background())
110	done := make(chan error, 1)
111	go func() { done <- c.Run(ctx) }()
112	defer func() { cancel(); receive(t, done) }()
113	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
114	scene(t, scenes)
115	client <- ports.WindowMapped{ID: 1}
116	scene(t, scenes)
117	press := ports.KeyEvent{Keysym: "Return", Keycode: 36, Mods: ports.ModSuper, Pressed: true}
118	input <- press
119	receive(t, spawn)
120	client <- ports.ShortcutsInhibit{Window: 1, Active: true}
121	commandOf[ports.ShortcutsInhibitState](t, commands)
122	release := press
123	release.Pressed = false
124	input <- release
125	other := ports.KeyEvent{Keysym: "a", Keycode: 38, Pressed: true}
126	input <- other
127	if v := commandOf[ports.ForwardKey](t, commands); v.Key != other {
128		t.Fatalf("forwarded %+v before %+v", v.Key, other)
129	}
130}

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 25

Tags 10

↑↓ moveenter openesc close