picker_composition.go RawHistoryFolder

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

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