feat/data-dnd e05cb15

repeat.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"time"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10// A held key repeats the moves it runs in the compositor: a navigation bind
11// (focus, move, resize) or an overview key (h/j/k/l, arrows). Clients repeat
12// their own keys; these never reach a client. The pace is the compositor's,
13// slower than typing so each step can be followed: the first repeat after
14// repeatDelay, then one every repeatInterval. With animations on, each step
15// retargets the running motion, so a held key slides on smoothly.
16const (
17	repeatDelay    = 250 * time.Millisecond
18	repeatInterval = 125 * time.Millisecond
19)
20
21// keyRepeat is the held key that repeats, if any.
22type keyRepeat struct {
23	// held is the physical key (heldKey); mods the modifiers it was
24	// pressed with: a change stops the repeat.
25	held string
26	mods ports.Mods
27	// action is the bind it repeats; key the overview key, when action is
28	// empty.
29	action Action
30	key    ports.KeyEvent
31	// overview is whether the overview was open at the press: opening or
32	// closing it stops the repeat.
33	overview bool
34	timer    ports.Timer
35}
36
37// repeats reports whether a held key repeats bind a: moves only, never an
38// action that toggles, opens, closes or spawns.
39func repeats(a Action) bool {
40	if _, _, ok := ResizeArg(a); ok {
41		return true
42	}
43	switch a {
44	case ActionFocusColumnLeft, ActionFocusColumnRight,
45		ActionFocusWindowUp, ActionFocusWindowDown,
46		ActionMoveColumnLeft, ActionMoveColumnRight,
47		ActionMoveWindowUp, ActionMoveWindowDown,
48		ActionConsumeOrExpelLeft, ActionConsumeOrExpelRight,
49		ActionFocusWorkspacePrev, ActionFocusWorkspaceNext,
50		ActionMoveWorkspacePrev, ActionMoveWorkspaceNext,
51		ActionMoveColumnToWorkspacePrev, ActionMoveColumnToWorkspaceNext,
52		ActionMoveWindowToWorkspacePrev, ActionMoveWindowToWorkspaceNext:
53		return true
54	}
55	return false
56}
57
58// overviewRepeats reports whether a held overview key repeats: the moves,
59// not Return or Escape.
60func overviewRepeats(key ports.KeyEvent) bool {
61	switch keyName(key.Keysym) {
62	case "h", "j", "k", "l", "Left", "Right", "Up", "Down":
63		return true
64	}
65	return false
66}
67
68// startRepeat repeats action, or the overview key when action is empty,
69// while key stays held with its modifiers.
70func (c *Core) startRepeat(key ports.KeyEvent, action Action) {
71	c.stopRepeat()
72	r := &c.repeat
73	r.held, r.mods, r.action, r.key = heldKey(key), key.Mods, action, key
74	r.overview = c.cur().mon.ov.open
75	r.timer = newPortTimer(c.opts.Clock, repeatDelay)
76	c.repeatC = r.timer.C()
77}
78
79// stopRepeat ends the repeat, if any.
80func (c *Core) stopRepeat() {
81	if c.repeat.timer != nil {
82		c.repeat.timer.Stop()
83	}
84	c.repeat, c.repeatC = keyRepeat{}, nil
85}
86
87// repeatKey stops the repeat on a key event that ends it: another key
88// pressed, the held key released, or the modifiers changed.
89func (c *Core) repeatKey(key ports.KeyEvent) {
90	r := &c.repeat
91	if r.timer == nil {
92		return
93	}
94	if key.Pressed || heldKey(key) == r.held || key.Mods != r.mods {
95		c.stopRepeat()
96	}
97}
98
99// repeatLive reports whether the held key still runs what it ran, and stops
100// the repeat when not: the overview opened or closed, or a launcher or a
101// menu took the keyboard. A bind also stops when a window inhibits the
102// shortcuts; overview keys run before inhibitors, as on a press.
103func (c *Core) repeatLive() bool {
104	r := c.repeat
105	if r.timer == nil {
106		return false
107	}
108	if c.cur().mon.ov.open != r.overview || c.overviewKeyboardTaken() ||
109		r.action != "" && c.keyboard.inhibited(c.keyboardFocus()) {
110		c.stopRepeat()
111		return false
112	}
113	return true
114}
115
116// repeatTick runs one repeat of the held key, if still live, and arms the
117// next.
118func (c *Core) repeatTick(ctx context.Context) error {
119	if !c.repeatLive() {
120		return nil
121	}
122	r, mon := c.repeat, c.cur().mon
123	c.inputActive = true
124	defer func() { c.inputActive = false }()
125	if r.action == "" {
126		now := c.now()
127		shots := c.snapshot(now)
128		mon.overviewKey(r.key)
129		c.transition(shots, now)
130		if err := c.workspaceVisible(ctx, true); err != nil {
131			return err
132		}
133		c.repeat.timer.Reset(repeatInterval)
134		return c.publish(ctx)
135	}
136	c.repeat.timer.Reset(repeatInterval)
137	return c.runBind(ctx, r.action)
138}

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