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

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 23

Tags 10

↑↓ moveenter openesc close