copy_scroll_animation.go RawHistoryFolder

1package daemon
2
3import (
4	"time"
5
6	"github.com/bnema/vev/internal/ports"
7)
8
9const (
10	copyScrollFrame = 16 * time.Millisecond
11	copyScrollTail  = 120 * time.Millisecond
12)
13
14// copyMu owns the animation and its single cancellable overlay-input timer.
15// Easing distributes requested rows over frames; it never invents extra travel.
16type copyScrollAnimation struct {
17	remaining int
18	lastInput time.Time
19	timer     pendingByteTimer
20}
21
22func (rt *overlayRuntime) stopCopyScrollLocked() {
23	rt.copyScroll.timer.stop()
24	rt.copyScroll = copyScrollAnimation{}
25}
26
27func (d *Daemon) smoothCopyWheel(sess *session, ac *attachedClient, delta int) {
28	if delta == 0 {
29		return
30	}
31	rt := ac.overlays
32	if d.clock == nil || d.currentCopyConfig().ReduceMotion {
33		rt.copyMu.Lock()
34		rt.stopCopyScrollLocked()
35		rt.copyMu.Unlock()
36		d.copyWheel(sess, ac, delta)
37		return
38	}
39	rt.copyMu.Lock()
40	if rt.copyMode == nil || rt.copyDocument == nil || rt.copyPane == nil {
41		rt.copyMu.Unlock()
42		return
43	}
44	// Selection stays exact and immediate. Reversal cancels the old tail so
45	// the next frame always follows the user's latest direction.
46	if rt.copyMode.Selection().Enabled || rt.copyDocument.Len() <= rt.copyDocument.Height() {
47		rt.stopCopyScrollLocked()
48		rt.copyMu.Unlock()
49		d.copyWheel(sess, ac, delta)
50		return
51	}
52	if rt.copyScroll.remaining != 0 && (rt.copyScroll.remaining < 0) != (delta < 0) {
53		rt.stopCopyScrollLocked()
54	}
55	motion := &rt.copyScroll
56	limit := rt.copyDocument.Len()
57	motion.remaining += min(max(delta, -limit-motion.remaining), limit-motion.remaining)
58	motion.lastInput = d.clock.Now()
59	changed, exit := false, false
60	if motion.timer.timer == nil {
61		changed, exit = d.advanceCopyScrollLocked(sess, ac)
62	}
63	rt.copyMu.Unlock()
64	if changed || exit {
65		d.invalidateRender(sess, ac, exit, "copy-scroll-animation")
66	}
67}
68
69// advanceCopyScrollLocked requires ac.overlays.copyMu, not d.mu.
70func (d *Daemon) advanceCopyScrollLocked(sess *session, ac *attachedClient) (bool, bool) {
71	rt := ac.overlays
72	motion := &rt.copyScroll
73	remaining := motion.remaining
74	if remaining == 0 {
75		return false, false
76	}
77	magnitude := remaining
78	if magnitude < 0 {
79		magnitude = -magnitude
80	}
81	step := (magnitude + 3) / 4
82	if d.clock.Now().Sub(motion.lastInput) >= copyScrollTail {
83		step = magnitude
84	}
85	if remaining < 0 {
86		step = -step
87	}
88	motion.remaining -= step
89	changed, exit := rt.moveCopyWheelLocked(step)
90	if !changed || exit {
91		rt.stopCopyScrollLocked()
92		return changed, exit
93	}
94	if motion.remaining != 0 {
95		mode := rt.copyMode
96		motion.timer.retain(d.clock, copyScrollFrame, func(timer ports.Timer) {
97			// Check presentation ownership before entering the copy lock. A
98			// callback never locks another overlay or a session under copyMu.
99			visible := ac.currentSession() == sess && !rt.promptActive() && !rt.paletteActive() &&
100				!rt.pickerClientActive() && !rt.noticesActive() && !rt.resizeModeActive()
101			rt.copyMu.Lock()
102			if rt.copyScroll.timer.timer != timer {
103				rt.copyMu.Unlock()
104				return
105			}
106			rt.copyScroll.timer.timer = nil
107			rt.copyScroll.timer.done = nil
108			if !visible || rt.copyMode != mode || rt.copySearch != nil {
109				rt.stopCopyScrollLocked()
110				rt.copyMu.Unlock()
111				return
112			}
113			changed, exit := d.advanceCopyScrollLocked(sess, ac)
114			rt.copyMu.Unlock()
115			if changed || exit {
116				d.invalidateRender(sess, ac, exit, "copy-scroll-animation")
117			}
118		})
119	}
120	return changed, exit
121}

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 12

Tags 11

↑↓ moveenter openesc close