perf/html-runs 1e6ecdb

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

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