model.go RawHistoryFolder

1package prompt
2
3import (
4	renderer "github.com/bnema/vev-vt"
5	"github.com/bnema/vev/internal/domain"
6	"github.com/bnema/vev/internal/usecase/ui"
7)
8
9// RenderStyles contains daemon-supplied semantic chrome roles. The zero value
10// is intentionally not used by RenderStyled; isolated Render callers retain
11// the historic neutral defaults.
12type RenderStyles struct {
13	Base      renderer.Style
14	Selection renderer.Style
15}
16
17type Model struct {
18	title  string
19	text   ui.TextInput
20	errMsg string
21}
22
23func New(title, initial string) *Model {
24	m := &Model{title: title}
25	m.text.SetValue(initial)
26	return m
27}
28
29func (m *Model) Insert(r rune) {
30	if m == nil {
31		return
32	}
33	m.errMsg = ""
34	m.text.Insert(r)
35}
36
37func (m *Model) Backspace() {
38	if m == nil {
39		return
40	}
41	m.errMsg = ""
42	m.text.Backspace()
43}
44
45func (m *Model) Value() string {
46	if m == nil {
47		return ""
48	}
49	return m.text.Value()
50}
51
52// promptInputPrefix leads the value line on row 0 of the rendered frame.
53const promptInputPrefix = "> "
54
55// Caret is the value caret column on row 0 of a Render frame of size inner
56// (see ui.InputCaret).
57func (m *Model) Caret(inner domain.Size) (int, bool) {
58	return ui.InputCaret(inner, promptInputPrefix, m.Value())
59}
60
61func (m *Model) Title() string {
62	if m == nil {
63		return ""
64	}
65	return m.title
66}
67
68func (m *Model) SetError(msg string) {
69	if m != nil {
70		m.errMsg = msg
71	}
72}
73
74func (m *Model) Render(inner domain.Size, accentStyle ...renderer.Style) renderer.Frame {
75	base := renderer.DefaultStyle()
76	selection := base
77	selection.Inverse = true
78	if len(accentStyle) > 0 {
79		selection = accentStyle[0]
80	}
81	return m.RenderStyled(inner, RenderStyles{Base: base, Selection: selection})
82}
83
84// RenderStyled renders with explicit chrome roles captured by the daemon.
85func (m *Model) RenderStyled(inner domain.Size, styles RenderStyles) renderer.Frame {
86	frame := renderer.NewFrame(max(inner.Cols, 0), max(inner.Rows, 0))
87	if frame.Width == 0 || frame.Height == 0 {
88		return frame
89	}
90	ui.FillRect(frame, domain.Rect{Width: frame.Width, Height: frame.Height}, renderer.Cell{Rune: ' ', Style: styles.Base})
91	ui.DrawInputLine(frame, 0, promptInputPrefix, m.Value(), styles.Base)
92	if frame.Height > 1 && m != nil && m.errMsg != "" {
93		ui.DrawText(frame, 0, 1, frame.Width, m.errMsg, styles.Selection)
94	}
95	return frame
96}

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