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}