v0.4.0 0b9cfda

kittygraphics.go RawHistoryFolder

1package kittygraphics
2
3import (
4	"errors"
5	"math"
6	"sort"
7	"sync"
8
9	"github.com/bnema/vev-vt/graphics"
10)
11
12var (
13	ErrInvalidAPC        = errors.New("invalid kitty graphics APC")
14	ErrAPCTooLarge       = errors.New("kitty graphics APC is too large")
15	ErrAPCTruncated      = errors.New("truncated kitty graphics APC")
16	ErrInvalidCommand    = errors.New("invalid kitty graphics command")
17	ErrUnknownAction     = errors.New("unknown kitty graphics action")
18	ErrUnknownControl    = errors.New("unknown kitty graphics control")
19	ErrDuplicateControl  = errors.New("duplicate kitty graphics control")
20	ErrInvalidInteger    = errors.New("invalid kitty graphics integer")
21	ErrIntegerOverflow   = errors.New("kitty graphics integer overflow")
22	ErrInvalidBase64     = errors.New("invalid kitty graphics base64")
23	ErrInvalidPNG        = errors.New("invalid kitty graphics PNG")
24	ErrPayloadTooLarge   = errors.New("kitty graphics payload is too large")
25	ErrTooManyChunks     = errors.New("too many kitty graphics continuation chunks")
26	ErrInterleavedUpload = errors.New("interleaved kitty graphics upload")
27	ErrUnsupported       = errors.New("unsupported kitty graphics command")
28	ErrImageNotFound     = errors.New("kitty graphics image not found")
29	ErrPlacementNotFound = errors.New("kitty graphics placement not found")
30	ErrNoScene           = errors.New("kitty graphics scene is nil")
31)
32
33const (
34	// DefaultMaxAPCBytes bounds the complete framed APC, including its
35	// controls and encoded payload.
36	DefaultMaxAPCBytes = uint64(32 << 20)
37	// DefaultMaxPayloadBytes bounds encoded bytes in one image, before base64
38	// decoding. The graphics scene applies its own encoded-byte budget too.
39	DefaultMaxPayloadBytes = uint64(24 << 20)
40	DefaultMaxUploadBytes  = uint64(16 << 20)
41	DefaultMaxChunks       = uint64(1024)
42	DefaultMaxPixels       = uint64(128 << 20)
43	DefaultMaxDecodedBytes = uint64(256 << 20)
44	DefaultMaxDimension    = uint64(1 << 20)
45)
46
47// Limits bounds parser and adapter resource use. Zero values select bounded
48// defaults. The limits are independent from the graphics scene limits: both
49// layers enforce their budgets.
50type Limits struct {
51	MaxAPCBytes      uint64
52	MaxPayloadBytes  uint64
53	MaxUploadBytes   uint64
54	MaxChunks        uint64
55	MaxDecodedPixels uint64
56	MaxDecodedBytes  uint64
57	MaxDimension     uint64
58	MaxResponseBytes uint64
59	MaxImages        uint64
60	MaxPlacements    uint64
61}
62
63// Config is an alias retained for callers that use constructor terminology.
64type Config = Limits
65
66func normalizeLimits(l Limits) Limits {
67	if l.MaxAPCBytes == 0 {
68		l.MaxAPCBytes = DefaultMaxAPCBytes
69	}
70	if l.MaxPayloadBytes == 0 {
71		l.MaxPayloadBytes = DefaultMaxPayloadBytes
72	}
73	if l.MaxUploadBytes == 0 {
74		l.MaxUploadBytes = DefaultMaxUploadBytes
75	}
76	if l.MaxChunks == 0 {
77		l.MaxChunks = DefaultMaxChunks
78	}
79	if l.MaxDecodedPixels == 0 {
80		l.MaxDecodedPixels = DefaultMaxPixels
81	}
82	if l.MaxDecodedBytes == 0 {
83		l.MaxDecodedBytes = DefaultMaxDecodedBytes
84	}
85	if l.MaxDimension == 0 {
86		l.MaxDimension = DefaultMaxDimension
87	}
88	if l.MaxResponseBytes == 0 {
89		l.MaxResponseBytes = 4096
90	}
91	if l.MaxImages == 0 {
92		l.MaxImages = 1024
93	}
94	if l.MaxPlacements == 0 {
95		l.MaxPlacements = 65536
96	}
97	return l
98}
99
100// Action is the closed set of Kitty graphics actions understood by this
101// adapter. The underlying byte values are the protocol values.
102type Action byte
103
104const (
105	ActionTransmit        Action = 't'
106	ActionTransmitDisplay Action = 'T'
107	ActionPut             Action = 'p'
108	ActionQuery           Action = 'q'
109	ActionDelete          Action = 'd'
110	ActionFrame           Action = 'f'
111	ActionCompose         Action = 'a'
112
113	// Descriptive aliases used by callers that prefer the protocol names.
114	ActionTransmitAndDisplay = ActionTransmitDisplay
115	ActionDisplay            = ActionPut
116)
117
118func (a Action) Valid() bool {
119	switch a {
120	case ActionTransmit, ActionTransmitDisplay, ActionPut, ActionQuery, ActionDelete, ActionFrame, ActionCompose:
121		return true
122	default:
123		return false
124	}
125}
126
127func (a Action) String() string {
128	if !a.Valid() {
129		return "unknown"
130	}
131	return string([]byte{byte(a)})
132}
133
134// Format is the closed set of Kitty image formats supported by this adapter.
135type Format uint64
136
137const (
138	FormatRGB  Format = 24
139	FormatRGBA Format = 32
140	FormatPNG  Format = 100
141
142	FormatRGB24  = FormatRGB
143	FormatRGBA32 = FormatRGBA
144)
145
146func (f Format) Valid() bool { return f == FormatRGB || f == FormatRGBA || f == FormatPNG }
147
148// Quiet controls protocol response emission. Kitty uses q=1 for successful
149// operations only and q=2 for all responses.
150type Quiet uint8
151
152const (
153	QuietNever   Quiet = 0
154	QuietSuccess Quiet = 1
155	QuietAll     Quiet = 2
156
157	QuietNone       = QuietNever
158	QuietErrorsOnly = QuietSuccess
159	QuietEverything = QuietAll
160)
161
162// DeleteTarget is the closed set of targets accepted by a delete action.
163type DeleteTarget byte
164
165const (
166	DeleteImage       DeleteTarget = 'i'
167	DeleteImageNumber DeleteTarget = 'I'
168	DeleteCell        DeleteTarget = 'p'
169	DeleteAll         DeleteTarget = 'a'
170	DeleteAllImages   DeleteTarget = 'A'
171	DeleteCellAll     DeleteTarget = 'P'
172)
173
174// Transmission is the direct transmission mode. File, temporary-file and
175// shared-memory transmissions are intentionally not accepted by this adapter.
176type Transmission byte
177
178const TransmissionDirect Transmission = 'd'
179
180// ControlKey identifies a recognized Kitty control field.
181type ControlKey byte
182
183const (
184	ControlAction       ControlKey = 'a'
185	ControlCompression  ControlKey = 'o'
186	ControlFormat       ControlKey = 'f'
187	ControlImageID      ControlKey = 'i'
188	ControlImageNumber  ControlKey = 'I'
189	ControlMore         ControlKey = 'm'
190	ControlQuiet        ControlKey = 'q'
191	ControlWidth        ControlKey = 's'
192	ControlHeight       ControlKey = 'v'
193	ControlColumns      ControlKey = 'c'
194	ControlRows         ControlKey = 'r'
195	ControlX            ControlKey = 'x'
196	ControlY            ControlKey = 'y'
197	ControlSourceWidth  ControlKey = 'w'
198	ControlSourceHeight ControlKey = 'h'
199	ControlLayer        ControlKey = 'z'
200	ControlPlacementID  ControlKey = 'p'
201	ControlDelete       ControlKey = 'd'
202	ControlTransmission ControlKey = 't'
203	ControlCursor       ControlKey = 'C'
204	ControlSourceX      ControlKey = 'X'
205	ControlSourceY      ControlKey = 'Y'
206	ControlCellOffsetX  ControlKey = 'H'
207	ControlCellOffsetY  ControlKey = 'V'
208	ControlParent       ControlKey = 'P'
209)
210
211// Controls is the typed, closed representation of a Kitty control header.
212// Has* fields preserve the distinction between an omitted field and zero.
213type Controls struct {
214	Action          Action
215	HasAction       bool
216	Compression     uint64
217	HasCompression  bool
218	Format          Format
219	HasFormat       bool
220	ImageID         uint64
221	HasImageID      bool
222	ImageNumber     uint64
223	HasImageNumber  bool
224	More            uint64
225	HasMore         bool
226	Quiet           Quiet
227	HasQuiet        bool
228	Width           uint64
229	HasWidth        bool
230	Height          uint64
231	HasHeight       bool
232	Columns         uint64
233	HasColumns      bool
234	Rows            uint64
235	HasRows         bool
236	X               uint64
237	HasX            bool
238	Y               uint64
239	HasY            bool
240	SourceWidth     uint64
241	HasSourceWidth  bool
242	SourceHeight    uint64
243	HasSourceHeight bool
244	Layer           int64
245	HasLayer        bool
246	PlacementID     uint64
247	HasPlacementID  bool
248	Delete          DeleteTarget
249	HasDelete       bool
250	Transmission    Transmission
251	HasTransmission bool
252	Cursor          uint64
253	HasCursor       bool
254	SourceX         uint64
255	HasSourceX      bool
256	SourceY         uint64
257	HasSourceY      bool
258	CellOffsetX     int64
259	HasCellOffsetX  bool
260	CellOffsetY     int64
261	HasCellOffsetY  bool
262	Parent          uint64
263	HasParent       bool
264}
265
266// Command is one complete APC graphics command. Payload is the encoded
267// command payload and is copied from the parser input.
268type Command struct {
269	Controls Controls
270	Payload  []byte
271}
272
273// EventKind identifies parser output. Text events contain bytes which are not
274// Kitty graphics APCs; command events contain complete commands.
275type EventKind uint8
276
277const (
278	EventText EventKind = iota + 1
279	EventCommand
280	EventError
281)
282
283// Event is emitted by Parser. Text and command payloads are owned by the
284// event and remain valid after the next Feed call.
285type Event struct {
286	Kind    EventKind
287	Text    []byte
288	Command Command
289	Err     error
290}
291
292// ParserConfig is a descriptive alias for Limits.
293type ParserConfig = Limits
294
295// Parser incrementally recognizes ESC _ G APCs. It never interprets ordinary
296// text or other escape sequences and can be fed arbitrary byte-sized chunks.
297type Parser struct {
298	limits    Limits
299	candidate []byte
300	apc       []byte
301	inAPC     bool
302	escaped   bool
303	oversize  bool
304}
305
306// NewParser creates an incremental bounded parser.
307func NewParser(config ...Limits) *Parser {
308	var l Limits
309	if len(config) != 0 {
310		l = config[0]
311	}
312	return &Parser{limits: normalizeLimits(l)}
313}
314
315// Limits reports the parser limits.
316func (p *Parser) Limits() Limits {
317	if p == nil {
318		return Limits{}
319	}
320	return p.limits
321}
322
323// MaxInt64 is exported for callers constructing checked protocol geometry.
324const MaxInt64 = int64(math.MaxInt64)
325
326// MaxKittyID is the largest protocol image or placement identifier.
327const MaxKittyID = uint64(^uint32(0))
328
329// Session owns protocol-to-scene mappings. It is safe for one owner to call
330// Feed and query methods serially; the mutex also makes read-only mapping
331// access safe while a caller publishes a snapshot.
332type Session struct {
333	mu            sync.Mutex
334	scene         *graphics.Scene
335	limits        Limits
336	parser        *Parser
337	images        map[uint64]graphics.AssetID
338	imageNumbers  map[uint64]uint64
339	placements    map[placementKey]graphics.PlacementID
340	children      map[uint64]Child
341	nextChild     uint64
342	nextPlacement uint64
343	upload        *upload
344	origin        placementOrigin
345}
346
347// Adapter is an alias for the stateful Kitty graphics session.
348type Adapter = Session
349
350// Child describes a session-local image child and its graphics asset. Child
351// IDs are never sent to the terminal; they make the protocol mapping explicit
352// and stable for consumers.
353type Child struct {
354	ID      uint64
355	ImageID uint64
356	AssetID graphics.AssetID
357}
358
359// NewSession constructs an adapter over scene. A nil scene is allowed so the
360// parser can still be used for validation, but commands which mutate a scene
361// return ErrNoScene.
362func NewSession(scene *graphics.Scene, config ...Limits) *Session {
363	var l Limits
364	if len(config) != 0 {
365		l = config[0]
366	}
367	l = normalizeLimits(l)
368	return &Session{
369		scene:         scene,
370		limits:        l,
371		parser:        NewParser(l),
372		images:        make(map[uint64]graphics.AssetID),
373		imageNumbers:  make(map[uint64]uint64),
374		placements:    make(map[placementKey]graphics.PlacementID),
375		children:      make(map[uint64]Child),
376		nextChild:     1,
377		nextPlacement: 1,
378	}
379}
380
381// NewAdapter is an adapter-named constructor alias.
382func NewAdapter(scene *graphics.Scene, config ...Limits) *Session {
383	return NewSession(scene, config...)
384}
385
386// New is a concise constructor alias for NewSession.
387func New(scene *graphics.Scene, config ...Limits) *Session { return NewSession(scene, config...) }
388
389// Image returns the graphics asset mapped from a Kitty image ID.
390func (s *Session) Image(imageID uint64) (graphics.AssetID, bool) {
391	if s == nil {
392		return graphics.AssetID{}, false
393	}
394	s.mu.Lock()
395	defer s.mu.Unlock()
396	id, ok := s.images[imageID]
397	return id, ok
398}
399
400// Asset is an alias for Image.
401func (s *Session) Asset(imageID uint64) (graphics.AssetID, bool) { return s.Image(imageID) }
402
403// Placement returns the graphics placement mapped from one Kitty image and
404// placement-ID pair. Kitty placement IDs are scoped to their image ID.
405func (s *Session) Placement(imageID, placementID uint64) (graphics.PlacementID, bool) {
406	if s == nil {
407		return graphics.PlacementID{}, false
408	}
409	s.mu.Lock()
410	defer s.mu.Unlock()
411	id, ok := s.placements[placementKey{imageID: imageID, placementID: placementID}]
412	return id, ok
413}
414
415// PlacementID is an alias for Placement.
416func (s *Session) PlacementID(imageID, placementID uint64) (graphics.PlacementID, bool) {
417	return s.Placement(imageID, placementID)
418}
419
420// Child returns the oldest session child associated with an image ID.
421func (s *Session) Child(imageID uint64) (Child, bool) {
422	if s == nil {
423		return Child{}, false
424	}
425	s.mu.Lock()
426	defer s.mu.Unlock()
427	var selected Child
428	found := false
429	for _, child := range s.children {
430		if child.ImageID == imageID && (!found || child.ID < selected.ID) {
431			selected, found = child, true
432		}
433	}
434	return selected, found
435}
436
437// ChildByID returns a session child by its adapter-local ID.
438func (s *Session) ChildByID(childID uint64) (Child, bool) {
439	if s == nil {
440		return Child{}, false
441	}
442	s.mu.Lock()
443	defer s.mu.Unlock()
444	child, ok := s.children[childID]
445	return child, ok
446}
447
448// ChildID returns the adapter-local child ID associated with an image ID.
449func (s *Session) ChildID(imageID uint64) (uint64, bool) {
450	child, ok := s.Child(imageID)
451	if !ok {
452		return 0, false
453	}
454	return child.ID, true
455}
456
457// Children returns a stable copy of all current session children in child-ID
458// order.
459func (s *Session) Children() []Child {
460	if s == nil {
461		return nil
462	}
463	s.mu.Lock()
464	defer s.mu.Unlock()
465	children := make([]Child, 0, len(s.children))
466	for _, child := range s.children {
467		children = append(children, child)
468	}
469	sort.Slice(children, func(i, j int) bool { return children[i].ID < children[j].ID })
470	return children
471}

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