v0.2.0 13c6a90

inhibit_test.go RawHistoryFolder

1package core_test
2
3import (
4	"context"
5	"testing"
6	"time"
7
8	"github.com/bnema/neferwl/internal/adapters/config"
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) {
34	cfg := config.Defaults()
35	input := make(chan ports.InputEvent, 4)
36	client := make(chan ports.ClientEvent, 4)
37	commands := make(chan ports.ClientCommand, 64)
38	scenes := make(chan []ports.Scene, 1)
39	spawn := make(chan ports.SpawnRequest, 4)
40	state := make(chan ports.State, 1)
41	output := make(chan ports.OutputEvent, 1)
42	c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn, State: state})
43	if err != nil {
44		t.Fatal(err)
45	}
46	ctx, cancel := context.WithCancel(context.Background())
47	done := make(chan error, 1)
48	go func() { done <- c.Run(ctx) }()
49	defer func() { cancel(); receive(t, done) }()
50	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
51	scene(t, scenes)
52	client <- ports.WindowMapped{ID: 1}
53	scene(t, scenes)
54	client <- ports.ShortcutsInhibit{Window: 1, Active: true}
55	if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1, Active: true}) {
56		t.Fatal(v)
57	}
58	bound := ports.KeyEvent{Keysym: "Return", Mods: ports.ModSuper, Pressed: true}
59	input <- bound
60	if v := commandOf[ports.ForwardKey](t, commands); v != (ports.ForwardKey{ID: 1, Key: bound}) {
61		t.Fatal(v)
62	}
63	select {
64	case r := <-spawn:
65		t.Fatalf("bind ran: %v", r)
66	case <-time.After(30 * time.Millisecond):
67	}
68	// A new window takes focus: the inhibitor goes inactive, binds run.
69	client <- ports.WindowMapped{ID: 2}
70	if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1}) {
71		t.Fatal(v)
72	}
73	input <- bound
74	receive(t, spawn)
75	// Idle inhibitors show in the state.
76	client <- ports.IdleInhibit{Window: 2, Active: true}
77	deadline := time.After(2 * time.Second)
78	for {
79		select {
80		case st := <-state:
81			for _, w := range st.Windows {
82				if w.ID == 2 && w.IdleInhibit {
83					return
84				}
85			}
86		case <-deadline:
87			t.Fatal("idle inhibit not in state")
88		}
89	}
90}
91
92// A key whose press ran a bind before inhibiting began is not released to
93// the window: it never saw the press.
94func TestShortcutsInhibitDropsBoundRelease(t *testing.T) {
95	cfg := config.Defaults()
96	input := make(chan ports.InputEvent, 4)
97	client := make(chan ports.ClientEvent, 4)
98	commands := make(chan ports.ClientCommand, 64)
99	scenes := make(chan []ports.Scene, 1)
100	spawn := make(chan ports.SpawnRequest, 4)
101	output := make(chan ports.OutputEvent, 1)
102	c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn})
103	if err != nil {
104		t.Fatal(err)
105	}
106	ctx, cancel := context.WithCancel(context.Background())
107	done := make(chan error, 1)
108	go func() { done <- c.Run(ctx) }()
109	defer func() { cancel(); receive(t, done) }()
110	output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
111	scene(t, scenes)
112	client <- ports.WindowMapped{ID: 1}
113	scene(t, scenes)
114	press := ports.KeyEvent{Keysym: "Return", Keycode: 36, Mods: ports.ModSuper, Pressed: true}
115	input <- press
116	receive(t, spawn)
117	client <- ports.ShortcutsInhibit{Window: 1, Active: true}
118	commandOf[ports.ShortcutsInhibitState](t, commands)
119	release := press
120	release.Pressed = false
121	input <- release
122	other := ports.KeyEvent{Keysym: "a", Keycode: 38, Pressed: true}
123	input <- other
124	if v := commandOf[ports.ForwardKey](t, commands); v.Key != other {
125		t.Fatalf("forwarded %+v before %+v", v.Key, other)
126	}
127}

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