picker_composition.go RawHistoryFolder

1package client
2
3import (
4	"time"
5
6	"github.com/bnema/vev/internal/domain"
7	"github.com/bnema/vev/internal/domain/terminalcap"
8	"github.com/bnema/vev/internal/ports"
9	"github.com/bnema/vev/internal/protocol"
10	"github.com/bnema/vev/internal/usecase/keys/kittykey"
11)
12
13// Picker is the composition seam over the client-owned picker.
14//
15// A composition must construct one picker with NewPicker, assign it once to
16// SupervisorConfig.Picker, and paint the bytes returned by Render and
17// RenderNotice. Its zero value is inert so optional composition remains safe,
18// but it does not provide a working picker.
19//
20// The remaining exported methods are the supervisor contract: ApplySnapshot,
21// TakeOp, ResolveKey, SetOwnsInput, OpsReady, and ConsumeTerminalRead. A
22// composition must never drive those methods itself.
23// Doing so can violate the supervisor's single-reader and single-input-owner
24// invariants or consume a pending user decision before the supervisor handles
25// it.
26//
27// Painting is allowed only while PickerPresentation reports true: the plain
28// picker, or the picker overlay over a live attachment whose output the
29// attachment foreground suppresses while the overlay is open. In
30// particular, PresentConnecting is not safe: once the attachment foreground is
31// admitted it owns the same terminal writer before MarkAttached or the initial
32// publication. Nothing serializes picker painting with that foreground.
33// The picker deliberately neither owns a terminal writer nor consumes the
34// terminal resize channel; the composition supplies its current geometry when
35// rendering.
36type Picker struct {
37	controller *pickerController
38}
39
40// Picker is the supervisor's picker seam: the value assigned to
41// SupervisorConfig.Picker.
42var _ pickerHost = (*Picker)(nil)
43
44// PickerPresentation reports whether composition-owned picker bytes may be
45// written for state. This is intentionally narrower than "not attached": an
46// admitted foreground can write while the public presentation is still
47// PresentConnecting.
48func PickerPresentation(state State) bool {
49	return state.Presentation == PresentPicker || state.Presentation == PresentAttachedPicker
50}
51
52// NewPicker composes one client-owned picker. A nil clock uses the system
53// clock, and a zero freshness uses the catalogue default. color is the
54// terminal's detected color capability (AttachmentEnvironment.Color); the
55// picker renders indexed colors without it.
56func NewPicker(clock ports.Clock, freshness time.Duration, color terminalcap.ColorCapabilities) *Picker {
57	return &Picker{controller: newPickerController(clock, freshness, color)}
58}
59
60// ApplySnapshot forwards a broker publication to the supervisor-owned picker.
61func (p *Picker) ApplySnapshot(snapshot ports.BrokerSnapshot) {
62	if p == nil || p.controller == nil {
63		return
64	}
65	p.controller.ApplySnapshot(snapshot)
66}
67
68// TakeOp returns and clears the pending picker operation for the supervisor.
69func (p *Picker) TakeOp() (pickerOp, string) {
70	if p == nil || p.controller == nil {
71		return pickerOp{}, ""
72	}
73	return p.controller.TakeOp()
74}
75
76// ResolveCommit resolves a committed key plus the exact tab it names.
77func (p *Picker) ResolveCommit(key string, base pickerResolveBase) (ports.BrokerOpenStreamRequest, attachmentTab, error) {
78	if p == nil || p.controller == nil {
79		return (*pickerController)(nil).ResolveCommit(key, base)
80	}
81	return p.controller.ResolveCommit(key, base)
82}
83
84// ResolveKill revalidates one kill key for the supervisor.
85func (p *Picker) ResolveKill(key string) (pickerKillTarget, error) {
86	if p == nil || p.controller == nil {
87		return (*pickerController)(nil).ResolveKill(key)
88	}
89	return p.controller.ResolveKill(key)
90}
91
92// SetCurrent names the attachment the picker is presented over.
93func (p *Picker) SetCurrent(current pickerCurrent) {
94	if p != nil && p.controller != nil {
95		p.controller.SetCurrent(current)
96	}
97}
98
99// PreviewRequest names the selected row's preview route and viewport.
100func (p *Picker) PreviewRequest(size domain.Size) (ports.BrokerPreviewRoute, protocol.RemotePreviewRequest, bool) {
101	if p == nil || p.controller == nil {
102		return ports.BrokerPreviewRoute{}, protocol.RemotePreviewRequest{}, false
103	}
104	return p.controller.PreviewRequest(size)
105}
106
107func (p *Picker) SetPreview(preview protocol.RemotePreview, state previewState) {
108	if p != nil && p.controller != nil {
109		p.controller.SetPreview(preview, state)
110	}
111}
112
113func (p *Picker) ClearPreview() {
114	if p != nil && p.controller != nil {
115		p.controller.ClearPreview()
116	}
117}
118
119// SetOwnsInput updates picker input ownership at an attachment boundary.
120func (p *Picker) SetOwnsInput(owns bool) {
121	if p == nil || p.controller == nil {
122		return
123	}
124	p.controller.SetOwnsInput(owns)
125}
126
127// Notify shows one bounded client-local notice on the picker.
128func (p *Picker) Notify(n domain.Notification) {
129	if p != nil && p.controller != nil {
130		p.controller.Notify(n)
131	}
132}
133
134func (p *Picker) noticeDeadline() (time.Time, bool) {
135	if p == nil || p.controller == nil {
136		return time.Time{}, false
137	}
138	return p.controller.noticeDeadline()
139}
140
141// invalidatePresentation forces the next frame to redraw the whole box.
142func (p *Picker) invalidatePresentation() {
143	if p != nil && p.controller != nil {
144		p.controller.invalidatePresentation()
145	}
146}
147
148// OpsReady returns the supervisor's coalescing operation wake channel.
149func (p *Picker) OpsReady() <-chan struct{} {
150	if p == nil || p.controller == nil {
151		return nil
152	}
153	return p.controller.OpsReady()
154}
155
156// ConsumeTerminalRead lets the supervisor offer one read to the picker.
157func (p *Picker) ConsumeTerminalRead(data []byte) bool {
158	if p == nil || p.controller == nil {
159		return false
160	}
161	// The picker decodes legacy keys; kitty keyboard sequences are
162	// translated back before it sees them.
163	return p.controller.ConsumeTerminalRead(kittykey.Translate(data, 0))
164}
165
166// Render returns the current picker frame for size, or nil when no catalogue
167// has been applied yet. A composition paints this payload when Render is
168// invoked and the reported presentation is PresentPicker.
169func (p *Picker) Render(size domain.Size) []byte {
170	if p == nil || p.controller == nil {
171		return nil
172	}
173	return p.controller.Render(size)
174}
175
176// RenderNotice returns the notice stack for size, including blanks for boxes
177// that went away, or nil when nothing changes on screen. A composition paints
178// it after the picker frame only while the supervisor reports PresentPicker.
179func (p *Picker) RenderNotice(size domain.Size) []byte {
180	if p == nil || p.controller == nil {
181		return nil
182	}
183	return p.controller.RenderNotice(size)
184}

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