v0.5.0 31affde

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

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