v0.7.1 ff50ee4

screen_modes.go RawHistoryFolder

1package vt
2
3import (
4	renderer "github.com/bnema/vev-vt/core"
5	"github.com/bnema/vev-vt/protocol/kittygraphics"
6)
7
8type screenState struct {
9	buffer              *buffer
10	row                 int
11	col                 int
12	style               renderer.Style
13	scrollTop           int
14	scrollBottom        int
15	savedCursor         cursorState
16	graphics            *screenGraphicsState
17	kittyPendingDisplay *kittygraphics.Controls
18	originMode          bool
19	insertMode          bool
20	kittyKeyboard       kittyKeyboardStack
21	charsets            [2]byte
22	shiftOut            bool
23}
24
25// SyncUpdateActive reports whether DEC private mode 2026 (synchronized update)
26// is currently enabled by the child process.
27func (s *Screen) SyncUpdateActive() bool { return s.syncUpdateActive }
28
29// BracketedPasteMode reports whether DEC private mode 2004 is currently enabled
30// by the child process.
31func (s *Screen) BracketedPasteMode() bool { return s.bracketedPaste }
32
33// AutoWrapMode reports whether DEC private mode 7 (DECAWM) is enabled.
34func (s *Screen) AutoWrapMode() bool { return s.autoWrapMode }
35
36// ApplicationCursorMode reports whether DEC private mode 1 (DECCKM) is enabled.
37func (s *Screen) ApplicationCursorMode() bool { return s.applicationCursorMode }
38
39// ColorSchemeMode reports whether DEC private mode 2031 is currently enabled.
40func (s *Screen) ColorSchemeMode() bool { return s.colorSchemeMode }
41
42// SetColorScheme updates the host color scheme and notifies subscribed child apps.
43func (s *Screen) SetColorScheme(light bool) {
44	if s.colorSchemeSet && s.colorSchemeLight == light {
45		return
46	}
47	s.colorSchemeSet = true
48	s.colorSchemeLight = light
49	if s.colorSchemeMode {
50		s.respond(s.colorSchemeReport())
51	}
52}
53
54// ClearColorScheme marks the host color scheme as unknown. Future child color
55// scheme queries are silent until SetColorScheme supplies a known value again.
56func (s *Screen) ClearColorScheme() {
57	s.colorSchemeSet = false
58}
59
60// ForceSyncEnd forcibly leaves DEC private mode 2026 (synchronized update).
61// Hosts use this as a safety valve if a child enters synchronized update mode
62// and never sends the matching end sequence.
63func (s *Screen) ForceSyncEnd() { s.syncUpdateActive = false }
64
65// SetDefaultColors sets the terminal default foreground/background colors used
66// to answer child OSC 10/11 color queries. Passing ok=false makes color queries
67// silent until known colors are supplied again.
68func (s *Screen) SetDefaultColors(fg, bg renderer.RGB, ok bool) {
69	s.defaultFG = fg
70	s.defaultBG = bg
71	s.defaultColorsKnown = ok
72}
73
74func (s *Screen) CursorRow() int { return s.Row }
75func (s *Screen) CursorCol() int { return s.Col }
76func (s *Screen) CursorVisible() bool {
77	return s.cursorVisible
78}
79func (s *Screen) CursorStyle() (int, bool) { return s.cursorStyle, s.cursorStyleSet }
80func (s *Screen) MouseMode() (int, bool)   { return s.mouseMode, s.mouseSGR }
81func (s *Screen) AltScreenActive() bool    { return s.alternate != nil }
82
83// TerminalTitle returns the latest title set by OSC 0 or OSC 2.
84func (s *Screen) TerminalTitle() string { return s.terminalTitle }
85
86func (s *Screen) respond(b []byte) {
87	if s.OnResponse != nil {
88		s.OnResponse(b)
89	}
90}
91
92func (s *Screen) colorSchemeReport() []byte {
93	if !s.colorSchemeSet {
94		return nil
95	}
96	if s.colorSchemeLight {
97		return []byte(ColorSchemeReportLight)
98	}
99	return []byte(ColorSchemeReportDark)
100}
101
102func (s *Screen) reset() {
103	s.buffer = s.newBuffer(s.frame.Width, s.frame.Height)
104	s.frame = s.buffer.frame
105	s.Row, s.Col = 0, 0
106	s.Style = renderer.DefaultStyle()
107	s.escapeBuf = s.escapeBuf[:0]
108	s.kittyDiscard = false
109	s.kittyDiscardEscaped = false
110	s.kittyPendingDisplay = nil
111	s.savedCursor = cursorState{}
112	s.alternate = nil
113	s.graphics = nil
114	s.originMode = false
115	s.insertMode = false
116	s.autoWrapMode = true
117	s.applicationCursorMode = false
118	s.cursorVisible = true
119	s.cursorStyle = 0
120	s.cursorStyleSet = false
121	s.mouseMode = 0
122	s.mouseSGR = false
123	s.bracketedPaste = false
124	s.kittyKeyboard = nil
125	s.colorSchemeMode = false
126	s.charsets = [2]byte{}
127	s.shiftOut = false
128	s.lastPrinted = 0
129	s.resetScrollRegion()
130	s.fullRedraw()
131}
132
133// softReset implements DECSTR (CSI ! p): it resets modes, rendition, margins,
134// cursor appearance, and character sets without touching screen contents or
135// the cursor position. Like xterm, the saved cursor becomes home with the
136// reset state.
137func (s *Screen) softReset() {
138	s.Style = renderer.DefaultStyle()
139	s.originMode = false
140	s.insertMode = false
141	s.autoWrapMode = true
142	s.applicationCursorMode = false
143	s.cursorVisible = true
144	s.cursorStyle = 0
145	s.cursorStyleSet = false
146	s.charsets = [2]byte{}
147	s.shiftOut = false
148	s.savedCursor = cursorState{style: s.Style, autoWrapMode: true, saved: true}
149	s.resetScrollRegion()
150}
151
152func (s *Screen) setScrollRegion(parts []int) {
153	if s.frame.Height == 0 {
154		return
155	}
156	top, bottom := 1, s.frame.Height
157	if len(parts) > 0 && parts[0] > 0 {
158		top = parts[0]
159	}
160	if len(parts) > 1 && parts[1] > 0 {
161		bottom = parts[1]
162	}
163	if top >= bottom {
164		s.resetScrollRegion()
165	} else {
166		s.scrollTop = clamp(top-1, 0, s.frame.Height-1)
167		s.scrollBottom = clamp(bottom-1, 0, s.frame.Height-1)
168		if s.scrollTop >= s.scrollBottom {
169			s.resetScrollRegion()
170		}
171	}
172	s.homeCursor()
173}
174
175func (s *Screen) resetScrollRegion() {
176	s.scrollTop = 0
177	if s.frame.Height > 0 {
178		s.scrollBottom = s.frame.Height - 1
179	} else {
180		s.scrollBottom = 0
181	}
182}
183
184func (s *Screen) setMode(private bool, parts []int, enabled bool) {
185	if !private {
186		for _, mode := range parts {
187			switch mode {
188			case 4:
189				s.insertMode = enabled
190			default:
191				// Other ANSI modes (for example LNM) are intentionally ignored
192				// until the screen model implements their observable behavior.
193				continue
194			}
195		}
196		return
197	}
198	for _, mode := range parts {
199		switch mode {
200		case 1:
201			s.applicationCursorMode = enabled
202		case 6:
203			s.originMode = enabled
204			s.homeCursor()
205		case 7:
206			s.autoWrapMode = enabled
207			if !enabled && s.frame.Width > 0 && s.Col >= s.frame.Width {
208				s.Col = s.frame.Width - 1
209			}
210		case 47, 1047, 1049:
211			if enabled {
212				s.enterAlternateScreen()
213			} else {
214				// Only 1049 saves and restores cursor state, which includes charsets.
215				s.exitAlternateScreen(mode == 1049)
216			}
217		case 2026:
218			s.syncUpdateActive = enabled
219		case 2031:
220			s.colorSchemeMode = enabled
221		case 25:
222			s.cursorVisible = enabled
223		case 1000, 1002, 1003:
224			if enabled {
225				s.mouseMode = mode
226			} else if s.mouseMode == mode {
227				s.mouseMode = 0
228			}
229		case 1006:
230			s.mouseSGR = enabled
231		case 2004:
232			s.bracketedPaste = enabled
233		case 1004, 1005:
234			// Trackable terminal modes that do not directly affect the current
235			// cell model yet. Consuming them prevents mode bytes from leaking.
236			continue
237		}
238	}
239}
240
241func (s *Screen) enterAlternateScreen() {
242	if s.alternate == nil {
243		s.alternate = &screenState{
244			buffer:              s.buffer.clone(),
245			row:                 s.Row,
246			col:                 s.Col,
247			style:               s.Style,
248			scrollTop:           s.scrollTop,
249			scrollBottom:        s.scrollBottom,
250			savedCursor:         s.savedCursor,
251			graphics:            s.graphics,
252			kittyPendingDisplay: s.kittyPendingDisplay,
253			originMode:          s.originMode,
254			insertMode:          s.insertMode,
255			kittyKeyboard:       s.kittyKeyboard,
256			charsets:            s.charsets,
257			shiftOut:            s.shiftOut,
258		}
259		// The main and alternate screens keep independent keyboard stacks.
260		s.kittyKeyboard = nil
261	}
262	s.graphics = nil
263	s.kittyPendingDisplay = nil
264	s.buffer = s.newBuffer(s.frame.Width, s.frame.Height)
265	s.frame = s.buffer.frame
266	s.Row, s.Col = 0, 0
267	s.Style = renderer.DefaultStyle()
268	s.savedCursor = cursorState{}
269	s.resetScrollRegion()
270	s.fullRedraw()
271}
272
273func (s *Screen) exitAlternateScreen(restoreCharsets bool) {
274	if s.alternate == nil {
275		return
276	}
277	state := s.alternate
278	s.buffer = state.buffer
279	s.frame = s.buffer.frame
280	s.Row = clamp(state.row, 0, s.frame.Height-1)
281	s.Col = clamp(state.col, 0, s.frame.Width-1)
282	s.Style = state.style
283	s.scrollTop = state.scrollTop
284	s.scrollBottom = state.scrollBottom
285	s.savedCursor = state.savedCursor
286	s.graphics = state.graphics
287	s.kittyPendingDisplay = state.kittyPendingDisplay
288	s.originMode = state.originMode
289	s.insertMode = state.insertMode
290	s.kittyKeyboard = state.kittyKeyboard
291	if restoreCharsets {
292		s.charsets = state.charsets
293		s.shiftOut = state.shiftOut
294	}
295	s.alternate = nil
296	s.fullRedraw()
297}

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