v0.1.0 af61ae3

screen_modes.go RawHistoryFolder

1package vt
2
3import renderer "github.com/bnema/vev-vt/core"
4
5type screenState struct {
6	frame        renderer.Frame
7	buffer       *buffer
8	row          int
9	col          int
10	style        renderer.Style
11	scrollTop    int
12	scrollBottom int
13	savedCursor  cursorState
14	originMode   bool
15	insertMode   bool
16}
17
18// SyncUpdateActive reports whether DEC private mode 2026 (synchronized update)
19// is currently enabled by the child process.
20func (s *Screen) SyncUpdateActive() bool { return s.syncUpdateActive }
21
22// BracketedPasteMode reports whether DEC private mode 2004 is currently enabled
23// by the child process.
24func (s *Screen) BracketedPasteMode() bool { return s.bracketedPaste }
25
26// ColorSchemeMode reports whether DEC private mode 2031 is currently enabled.
27func (s *Screen) ColorSchemeMode() bool { return s.colorSchemeMode }
28
29// SetColorScheme updates the host color scheme and notifies subscribed child apps.
30func (s *Screen) SetColorScheme(light bool) {
31	if s.colorSchemeSet && s.colorSchemeLight == light {
32		return
33	}
34	s.colorSchemeSet = true
35	s.colorSchemeLight = light
36	if s.colorSchemeMode {
37		s.respond(s.colorSchemeReport())
38	}
39}
40
41// ClearColorScheme marks the host color scheme as unknown. Future child color
42// scheme queries are silent until SetColorScheme supplies a known value again.
43func (s *Screen) ClearColorScheme() {
44	s.colorSchemeSet = false
45}
46
47// ForceSyncEnd forcibly leaves DEC private mode 2026 (synchronized update).
48// Hosts use this as a safety valve if a child enters synchronized update mode
49// and never sends the matching end sequence.
50func (s *Screen) ForceSyncEnd() { s.syncUpdateActive = false }
51
52// SetDefaultColors sets the terminal default foreground/background colors used
53// to answer child OSC 10/11 color queries. Passing ok=false makes color queries
54// silent until known colors are supplied again.
55func (s *Screen) SetDefaultColors(fg, bg renderer.RGB, ok bool) {
56	s.defaultFG = fg
57	s.defaultBG = bg
58	s.defaultColorsKnown = ok
59}
60
61func (s *Screen) CursorRow() int { return s.Row }
62func (s *Screen) CursorCol() int { return s.Col }
63func (s *Screen) CursorVisible() bool {
64	return s.cursorVisible
65}
66func (s *Screen) CursorStyle() (int, bool) { return s.cursorStyle, s.cursorStyleSet }
67func (s *Screen) MouseMode() (int, bool)   { return s.mouseMode, s.mouseSGR }
68func (s *Screen) AltScreenActive() bool    { return s.alternate != nil }
69
70// TerminalTitle returns the latest title set by OSC 0 or OSC 2.
71func (s *Screen) TerminalTitle() string { return s.terminalTitle }
72
73func (s *Screen) respond(b []byte) {
74	if s.OnResponse != nil {
75		s.OnResponse(b)
76	}
77}
78
79func (s *Screen) colorSchemeReport() []byte {
80	if !s.colorSchemeSet {
81		return nil
82	}
83	if s.colorSchemeLight {
84		return []byte(ColorSchemeReportLight)
85	}
86	return []byte(ColorSchemeReportDark)
87}
88
89func (s *Screen) reset() {
90	s.buffer = s.newBuffer(s.Frame.Width, s.Frame.Height)
91	s.Frame = s.buffer.frame
92	s.Row, s.Col = 0, 0
93	s.Style = renderer.DefaultStyle()
94	s.escapeBuf = s.escapeBuf[:0]
95	s.savedCursor = cursorState{}
96	s.alternate = nil
97	s.originMode = false
98	s.insertMode = false
99	s.cursorVisible = true
100	s.cursorStyle = 0
101	s.cursorStyleSet = false
102	s.mouseMode = 0
103	s.mouseSGR = false
104	s.bracketedPaste = false
105	s.colorSchemeMode = false
106	s.resetScrollRegion()
107	s.fullRedraw()
108}
109
110func (s *Screen) setScrollRegion(parts []int) {
111	if s.Frame.Height == 0 {
112		return
113	}
114	top, bottom := 1, s.Frame.Height
115	if len(parts) > 0 && parts[0] > 0 {
116		top = parts[0]
117	}
118	if len(parts) > 1 && parts[1] > 0 {
119		bottom = parts[1]
120	}
121	if top >= bottom {
122		s.resetScrollRegion()
123	} else {
124		s.scrollTop = clamp(top-1, 0, s.Frame.Height-1)
125		s.scrollBottom = clamp(bottom-1, 0, s.Frame.Height-1)
126		if s.scrollTop >= s.scrollBottom {
127			s.resetScrollRegion()
128		}
129	}
130	s.homeCursor()
131}
132
133func (s *Screen) resetScrollRegion() {
134	s.scrollTop = 0
135	if s.Frame.Height > 0 {
136		s.scrollBottom = s.Frame.Height - 1
137	} else {
138		s.scrollBottom = 0
139	}
140}
141
142func (s *Screen) setMode(private bool, parts []int, enabled bool) {
143	if !private {
144		for _, mode := range parts {
145			switch mode {
146			case 4:
147				s.insertMode = enabled
148			default:
149				// Other ANSI modes (for example LNM) are intentionally ignored
150				// until the screen model implements their observable behavior.
151				continue
152			}
153		}
154		return
155	}
156	for _, mode := range parts {
157		switch mode {
158		case 6:
159			s.originMode = enabled
160			s.homeCursor()
161		case 47, 1047, 1049:
162			if enabled {
163				s.enterAlternateScreen()
164			} else {
165				s.exitAlternateScreen()
166			}
167		case 2026:
168			s.syncUpdateActive = enabled
169		case 2031:
170			s.colorSchemeMode = enabled
171		case 25:
172			s.cursorVisible = enabled
173		case 1000, 1002, 1003:
174			if enabled {
175				s.mouseMode = mode
176			} else if s.mouseMode == mode {
177				s.mouseMode = 0
178			}
179		case 1006:
180			s.mouseSGR = enabled
181		case 2004:
182			s.bracketedPaste = enabled
183		case 1, 1004, 1005:
184			// Trackable terminal modes that do not directly affect the current
185			// cell model yet. Consuming them prevents mode bytes from leaking.
186			continue
187		}
188	}
189}
190
191func (s *Screen) enterAlternateScreen() {
192	if s.alternate == nil {
193		s.alternate = &screenState{
194			frame:        cloneFrame(s.Frame),
195			buffer:       s.buffer.clone(),
196			row:          s.Row,
197			col:          s.Col,
198			style:        s.Style,
199			scrollTop:    s.scrollTop,
200			scrollBottom: s.scrollBottom,
201			savedCursor:  s.savedCursor,
202			originMode:   s.originMode,
203			insertMode:   s.insertMode,
204		}
205	}
206	s.buffer = s.newBuffer(s.Frame.Width, s.Frame.Height)
207	s.Frame = s.buffer.frame
208	s.Row, s.Col = 0, 0
209	s.Style = renderer.DefaultStyle()
210	s.savedCursor = cursorState{}
211	s.resetScrollRegion()
212	s.fullRedraw()
213}
214
215func (s *Screen) exitAlternateScreen() {
216	if s.alternate == nil {
217		return
218	}
219	state := s.alternate
220	s.buffer = state.buffer
221	if s.buffer == nil {
222		s.buffer = bufferFromFrame(cloneFrame(state.frame))
223		s.fillMissingRowIDs(s.buffer)
224	}
225	s.Frame = s.buffer.frame
226	s.Row = clamp(state.row, 0, s.Frame.Height-1)
227	s.Col = clamp(state.col, 0, s.Frame.Width-1)
228	s.Style = state.style
229	s.scrollTop = state.scrollTop
230	s.scrollBottom = state.scrollBottom
231	s.savedCursor = state.savedCursor
232	s.originMode = state.originMode
233	s.insertMode = state.insertMode
234	s.alternate = nil
235	s.fullRedraw()
236}

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 2

Tags 13

↑↓ moveenter openesc close