v0.7.0 4c16dd8

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
69func (d *Daemon) advanceCopyScrollLocked(sess *session, ac *attachedClient) (bool, bool) {
70	rt := ac.overlays
71	motion := &rt.copyScroll
72	remaining := motion.remaining
73	if remaining == 0 {
74		return false, false
75	}
76	magnitude := remaining
77	if magnitude < 0 {
78		magnitude = -magnitude
79	}
80	step := (magnitude + 3) / 4
81	if d.clock.Now().Sub(motion.lastInput) >= copyScrollTail {
82		step = magnitude
83	}
84	if remaining < 0 {
85		step = -step
86	}
87	motion.remaining -= step
88	changed, exit := rt.moveCopyWheelLocked(step)
89	if !changed || exit {
90		rt.stopCopyScrollLocked()
91		return changed, exit
92	}
93	if motion.remaining != 0 {
94		mode := rt.copyMode
95		motion.timer.retain(d.clock, copyScrollFrame, func(timer ports.Timer) {
96			// Check presentation ownership before entering the copy lock. A
97			// callback never locks another overlay or a session under copyMu.
98			visible := ac.currentSession() == sess && !rt.promptActive() && !rt.paletteActive() &&
99				!rt.pickerClientActive() && !rt.noticesActive() && !rt.resizeModeActive()
100			rt.copyMu.Lock()
101			if rt.copyScroll.timer.timer != timer {
102				rt.copyMu.Unlock()
103				return
104			}
105			rt.copyScroll.timer.timer = nil
106			rt.copyScroll.timer.done = nil
107			if !visible || rt.copyMode != mode || rt.copySearch != nil {
108				rt.stopCopyScrollLocked()
109				rt.copyMu.Unlock()
110				return
111			}
112			changed, exit := d.advanceCopyScrollLocked(sess, ac)
113			rt.copyMu.Unlock()
114			if changed || exit {
115				d.invalidateRender(sess, ac, exit, "copy-scroll-animation")
116			}
117		})
118	}
119	return changed, exit
120}

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