main 5041353

vim_page_capture.go RawHistoryFolder

1package input
2
3import (
4	"github.com/bnema/dumber/internal/domain/vimkeys"
5	"github.com/bnema/puregotk/v4/gdk"
6)
7
8// PageKeyForwarder receives canonical vimkeys strings ("j", "J", "<Escape>")
9// while an in-page Vim interaction owns keyboard input.
10type PageKeyForwarder func(key string)
11
12// SetPageKeyCapture routes Vim Mode keys to fn until ClearPageKeyCapture or
13// Vim Mode exits. Pending sequences are discarded so they cannot complete
14// against keys meant for the page. The reset is silent: the pending listener
15// repaints the mode indicator, and a notification here would overwrite the
16// sub-mode label the caller shows for the interaction.
17func (h *KeyboardHandler) SetPageKeyCapture(fn PageKeyForwarder) {
18	h.resetPendingSequence(false)
19	h.mu.Lock()
20	h.pageKeyCapture = fn
21	h.mu.Unlock()
22}
23
24// ClearPageKeyCapture returns key handling to Vim Mode bindings.
25func (h *KeyboardHandler) ClearPageKeyCapture() {
26	h.mu.Lock()
27	h.pageKeyCapture = nil
28	h.mu.Unlock()
29}
30
31// PageKeyCaptureActive reports whether an in-page interaction owns keys.
32func (h *KeyboardHandler) PageKeyCaptureActive() bool {
33	h.mu.RLock()
34	defer h.mu.RUnlock()
35	return h.pageKeyCapture != nil
36}
37
38// isVimModeToggleKey reports whether the key is the configured Vim Mode
39// activation shortcut. While Vim Mode is active it exits the mode, so it must
40// stay with Go even when a page interaction owns every other key.
41func (h *KeyboardHandler) isVimModeToggleKey(keyval uint, state gdk.ModifierType) bool {
42	h.mu.RLock()
43	shortcuts := h.shortcuts
44	h.mu.RUnlock()
45	if shortcuts == nil {
46		return false
47	}
48	binding := KeyBinding{Keyval: normalizeKeyval(keyval), Modifiers: Modifier(state) & modifierMask}
49	action, found := shortcuts.Lookup(binding, ModeVim)
50	return found && action == ActionEnterVimMode
51}
52
53// forwardPageKey sends a Vim Mode key to the active page capture, except the
54// Vim Mode toggle shortcut, which always falls through. Escape is
55// forwarded like any other key: the page decides whether it steps back (visual
56// to caret) or ends the interaction, and reports the end so the capture is
57// released. A page that cannot be reached ends the capture through the
58// forwarder's error path, navigation, or leaving Vim Mode.
59func (h *KeyboardHandler) forwardPageKey(mode Mode, keyval uint, state gdk.ModifierType) bool {
60	if mode != ModeVim {
61		return false
62	}
63	h.mu.RLock()
64	forward := h.pageKeyCapture
65	h.mu.RUnlock()
66	if forward == nil {
67		return false
68	}
69	if isModifierKeyval(keyval) {
70		return true
71	}
72	if h.isVimModeToggleKey(keyval, state) {
73		// Escape hatch: a page can never swallow the shortcut that leaves Vim Mode.
74		return false
75	}
76	key, ok := KeyvalToVimKey(keyval, state)
77	if !ok {
78		return true
79	}
80	if key.Sym == "Esc" {
81		// The page must see a plain Escape whatever modifiers are held.
82		key.Mods = 0
83	}
84	forward(vimkeys.Sequence{key}.String())
85	// Captured keys bypass dispatchAction, so keep Vim Mode's timeout alive.
86	h.modal.ResetTimeout(h.ctx)
87	return true
88}
89
90func isModifierKeyval(keyval uint) bool {
91	switch keyval {
92	case uint(gdk.KEY_Shift_L), uint(gdk.KEY_Shift_R),
93		uint(gdk.KEY_Control_L), uint(gdk.KEY_Control_R),
94		uint(gdk.KEY_Alt_L), uint(gdk.KEY_Alt_R),
95		uint(gdk.KEY_Super_L), uint(gdk.KEY_Super_R),
96		uint(gdk.KEY_Caps_Lock), uint(gdk.KEY_ISO_Level3_Shift):
97		return true
98	default:
99		return false
100	}
101}

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 8

Tags 42

↑↓ moveenter openesc close