v0.3.0 99dc266

session_geometry.go RawHistoryFolder

1package daemon
2
3import "github.com/bnema/vev/internal/domain"
4
5// sessionGeometrySource identifies the attachment whose latest attach,
6// resume, or resize claim controls shared PTY/layout geometry. claim is zero
7// only for legacy/headless test fixtures that construct attachment maps
8// directly instead of publishing them through the attachment registry.
9type sessionGeometrySource struct {
10	attachment *attachedClient
11	size       domain.Size
12	claim      uint64
13}
14
15// geometrySourceSnapshot returns the latest live attachment claim. The
16// attachment registry publishes geometryOwner under the session lock, and this
17// function reads the owner, claim, and claim size under that same lock. Later
18// revalidation at the layout publication fence is lock-free so a stale claim
19// can be rejected without taking session.mu.
20func (s *session) geometrySourceSnapshot() (sessionGeometrySource, bool) {
21	if s == nil {
22		return sessionGeometrySource{}, false
23	}
24
25	s.mu.Lock()
26	s.refreshGeometryOwnerLocked()
27	owner := s.geometryOwner.Load()
28	var size domain.Size
29	var claim uint64
30	if owner != nil {
31		size = owner.geometryClaimSizeSnapshot()
32		claim = owner.geometryClaim.Load()
33	}
34	s.mu.Unlock()
35
36	if owner == nil || !size.Valid() {
37		return sessionGeometrySource{}, false
38	}
39	return sessionGeometrySource{attachment: owner, size: size, claim: claim}, true
40}
41
42// geometryClaimTokenCurrent is deliberately lock-free. applySessionLayout invokes
43// its current callback while tab locks are held, so taking session.mu here
44// would reverse the session -> tab ordering used by layout mutation paths.
45func (s *session) geometryClaimTokenCurrent(attachment *attachedClient, claim uint64) bool {
46	if s == nil || attachment == nil || claim == 0 {
47		return true
48	}
49	return s.geometryOwner.Load() == attachment && attachment.geometryClaim.Load() == claim
50}
51
52func (s *session) geometryClaimCurrent(source sessionGeometrySource) bool {
53	return s.geometryClaimTokenCurrent(source.attachment, source.claim) &&
54		(source.attachment == nil || source.attachment.geometryClaimSizeSnapshot() == source.size)
55}
56
57// sessionGeometryViewport is called with a non-nil session by the daemon
58// geometry paths; it returns ok == false when no valid claim exists.
59func sessionGeometryViewport(sess *session) (domain.Size, sessionGeometrySource, bool) {
60	source, ok := sess.geometrySourceSnapshot()
61	if !ok {
62		return domain.Size{}, sessionGeometrySource{}, false
63	}
64	content := contentSize(source.size)
65	return domain.Size{Cols: content.Cols, Rows: content.Rows + tabChromeRows}, source, true
66}
67
68// recalculateSessionGeometry reconciles shared PTY/layout geometry with the
69// latest live attachment claim. An explicit source records a new claim before
70// the snapshot; attach and resume paths already claimed ownership when they
71// entered the attachment registry. If the owner detaches, unregistering the
72// attachment selects the most recently claimed remaining attachment.
73func (d *Daemon) recalculateSessionGeometry(sess *session, source *attachedClient) bool {
74	if sess == nil {
75		return false
76	}
77	if source != nil {
78		if _, ok := sess.claimGeometryOwner(source); !ok {
79			return false
80		}
81	}
82	want, owner, ok := sessionGeometryViewport(sess)
83	if !ok {
84		return false
85	}
86	current := func() bool { return sess.geometryClaimCurrent(owner) }
87	if contentSize(sess.fullViewportSize()) == contentSize(want) {
88		return false
89	}
90	failed, ok := d.applySessionLayout(sess, want, current, nil)
91	if ok && len(failed) != 0 {
92		seen := make(map[*tab]struct{}, len(failed))
93		for _, member := range failed {
94			if member.tab == nil {
95				continue
96			}
97			if _, exists := seen[member.tab]; exists {
98				continue
99			}
100			seen[member.tab] = struct{}{}
101			d.scheduleAcceptedTabLayoutRetry(sess, member.tab)
102		}
103	}
104	return ok
105}
106
107// recalculateSessionGeometryAndInvalidate publishes a shared render wake after
108// canonical geometry changes. A nil source uses the latest attach/resume/resize
109// claim already published by the attachment registry. The wake is asynchronous
110// so a slow peer cannot block a resize or detach.
111func (d *Daemon) recalculateSessionGeometryAndInvalidate(sess *session, source *attachedClient, producer string) bool {
112	if !d.recalculateSessionGeometry(sess, source) {
113		return false
114	}
115	d.invalidateRender(sess, nil, true, producer)
116	return true
117}
118
119func (d *Daemon) recalculateSessionGeometryAndInvalidateAsync(sess *session, producer string) {
120	if d == nil {
121		return
122	}
123	d.sessWg.Go(func() {
124		d.recalculateSessionGeometryAndInvalidate(sess, nil, producer)
125	})
126}

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