Go bindings for libghostty-vt.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #16 from mitchellh/push-lzsmurmoprsy

Extensive notes on concurrency

authored by

Mitchell Hashimoto and committed by
GitHub
29fdb313 d059439f

+127 -40
+27
doc.go
··· 20 20 // 21 21 // term.VTWrite([]byte("Hello, world!\r\n")) 22 22 // 23 + // # Concurrency 24 + // 25 + // Unless documented otherwise, exported handle types in this package are 26 + // not safe for concurrent use. Keep each [Terminal], [Formatter], 27 + // [KeyEncoder], [MouseEncoder], [KeyEvent], [MouseEvent], and borrowed 28 + // view confined to one goroutine at a time or protect it with your own 29 + // synchronization. 30 + // 31 + // [RenderState] is the main exception. Hold exclusive access to the 32 + // terminal while calling [RenderState.Update]. After Update returns, the 33 + // render state can be read without touching the terminal until the next 34 + // Update. Do not call Update concurrently with reads from the same 35 + // render state. 36 + // 37 + // Borrowed views such as [GridRef], [KittyGraphics], [KittyGraphicsImage], 38 + // and [Selection], plus raw pixel slices returned by Kitty graphics 39 + // accessors, are only valid until the next mutating terminal call. Read 40 + // and copy what you need before mutating the terminal again. 41 + // 42 + // Plain copied values such as [Cell], [Row], [Style], [ColorRGB], and 43 + // [Palette] are regular Go values and may be retained after the call 44 + // that produced them. 45 + // 23 46 // # Effects 24 47 // 25 48 // The terminal communicates side-effects back to the host through 26 49 // effect callbacks. Register them at creation time with functional 27 50 // options like [WithWritePty], [WithBell], and [WithEnquiry], or 28 51 // on a live terminal with [Terminal.SetEffectWritePty] and friends. 52 + // 53 + // Effect callbacks run synchronously during [Terminal.VTWrite]. They 54 + // must not call [Terminal.VTWrite] on the same terminal and should avoid 55 + // blocking for long periods. 29 56 // 30 57 // [WithWritePty] is the most common effect — it delivers data that 31 58 // the terminal wants to send back to the pty (e.g. query responses):
+9 -4
formatter.go
··· 161 161 162 162 // Formatter wraps a Ghostty formatter handle that can produce 163 163 // plain text, VT sequences, or HTML from a terminal's current state. 164 - // The terminal must outlive the formatter. 164 + // The formatter stores a borrowed reference to a terminal, so the 165 + // terminal must outlive the formatter and formatter calls must be 166 + // serialized with all other access to that terminal. 165 167 // 166 168 // Formatter implements io.WriterTo so formatted output can be written 167 169 // directly to any io.Writer. ··· 173 175 // NewFormatter creates a formatter for the given terminal's active screen. 174 176 // The terminal must outlive the formatter. The formatter captures a 175 177 // borrowed reference to the terminal and reads its current state on 176 - // each Format call. 178 + // each [Formatter.Format] call, so formatter calls must be serialized 179 + // with other access to the terminal. 177 180 func NewFormatter(t *Terminal, opts ...FormatterOption) (*Formatter, error) { 178 181 // Start with GHOSTTY_INIT_SIZED defaults; options only touch 179 182 // fields the caller explicitly sets. ··· 197 200 } 198 201 199 202 // Format runs the formatter and returns the output as a byte slice. 200 - // Each call reflects the terminal's current state at the time of the call. 201 - // The returned buffer is allocated by libghostty and copied into Go memory. 203 + // Each call reflects the terminal's current state at the time of the 204 + // call. Serialize Format with all other access to the underlying 205 + // terminal. The returned buffer is allocated by libghostty and copied 206 + // into Go memory. 202 207 func (f *Formatter) Format() ([]byte, error) { 203 208 var outPtr *C.uint8_t 204 209 var outLen C.size_t
+8 -3
grid_ref.go
··· 22 22 23 23 // GridRef is a resolved reference to a specific cell position in the 24 24 // terminal's internal page structure. Obtain a GridRef from 25 - // Terminal.GridRef, then extract cell or row data from it. 25 + // [Terminal.GridRef], then extract cell or row data from it. 26 26 // 27 - // A GridRef is only valid until the next update to the terminal 28 - // instance. Read and cache any needed information immediately. 27 + // A GridRef is a borrowed view into terminal internals, so callers 28 + // must use it under the same serialized access that protects the 29 + // owning terminal. Any later terminal operation may invalidate the 30 + // GridRef, even if it looks unrelated, so read and cache what you 31 + // need immediately. Values returned by its getter methods are copied 32 + // snapshots and may be retained after the GridRef itself becomes 33 + // invalid. 29 34 // C: GhosttyGridRef 30 35 type GridRef struct { 31 36 ref C.GhosttyGridRef
+4 -1
key_encoder.go
··· 12 12 13 13 // KeyEncoder encodes key events into terminal escape sequences, 14 14 // supporting both legacy encoding and the Kitty Keyboard Protocol. 15 + // It maintains mutable encoding options and is not safe for concurrent 16 + // use. 15 17 // 16 18 // Basic usage: 17 19 // 1. Create an encoder with NewKeyEncoder. ··· 156 158 // 157 159 // Note that the macOS option-as-alt option cannot be determined from 158 160 // terminal state and is reset to OptionAsAltFalse by this call. Use 159 - // SetOptOptionAsAlt afterward if needed. 161 + // SetOptOptionAsAlt afterward if needed. The caller must serialize 162 + // access to both the encoder and the terminal during this call. 160 163 func (enc *KeyEncoder) SetOptFromTerminal(t *Terminal) { 161 164 C.ghostty_key_encoder_setopt_from_terminal(enc.ptr, t.ptr) 162 165 }
+2 -1
key_event.go
··· 12 12 13 13 // KeyEvent is an opaque handle representing a keyboard input event 14 14 // containing information about the physical key pressed, modifiers, 15 - // and generated text. 15 + // and generated text. It is mutable and reusable, but not safe for 16 + // concurrent use. 16 17 // 17 18 // C: GhosttyKeyEvent 18 19 type KeyEvent struct {
+14 -6
kitty_graphics.go
··· 122 122 ) 123 123 124 124 // KittyGraphics is a handle to the Kitty graphics image storage 125 - // associated with a terminal's active screen. It is borrowed from 126 - // the terminal and remains valid until the next mutating terminal 127 - // call (e.g. VTWrite or Reset). 125 + // associated with a terminal's active screen. It is borrowed from the 126 + // terminal and remains valid until the next mutating terminal call 127 + // (for example [Terminal.VTWrite] or [Terminal.Reset]). Access to this 128 + // handle must be serialized with mutations of the owning terminal. 128 129 // 129 130 // C: GhosttyKittyGraphics 130 131 type KittyGraphics struct { ··· 133 134 134 135 // KittyGraphicsImage is a handle to a single Kitty graphics image. 135 136 // It is borrowed from the storage and remains valid until the next 136 - // mutating terminal call. 137 + // mutating terminal call. Access to this handle and any borrowed pixel 138 + // data derived from it must be serialized with mutations of the owning 139 + // terminal. 137 140 // 138 141 // C: GhosttyKittyGraphicsImage 139 142 type KittyGraphicsImage struct { ··· 143 146 // KittyGraphicsPlacementIterator iterates over placements in the 144 147 // Kitty graphics storage. It is independently owned and must be 145 148 // freed by calling Close, but the data it yields is only valid 146 - // while the underlying terminal is not mutated. 149 + // while the underlying terminal is not mutated. Access to the iterator 150 + // must therefore be serialized with mutations of the terminal that 151 + // produced it. 147 152 // 148 153 // C: GhosttyKittyGraphicsPlacementIterator 149 154 type KittyGraphicsPlacementIterator struct { ··· 207 212 KittyImageCompressionZlibDeflate KittyImageCompression = C.GHOSTTY_KITTY_IMAGE_COMPRESSION_ZLIB_DEFLATE 208 213 ) 209 214 210 - // Selection represents a grid selection range defined by two grid references. 215 + // Selection represents a grid selection range defined by two grid 216 + // references. Because Start and End are [GridRef] values, a Selection 217 + // returned by Kitty graphics helpers is also a borrowed view and is 218 + // invalidated by the next mutation of the owning terminal. 211 219 // 212 220 // C: GhosttySelection 213 221 type Selection struct {
+4 -1
mouse_encoder.go
··· 19 19 20 20 // MouseEncoder encodes mouse events into terminal escape sequences, 21 21 // supporting X10, UTF-8, SGR, URxvt, and SGR-Pixels mouse protocols. 22 + // It maintains mutable encoder state and is not safe for concurrent 23 + // use. 22 24 // 23 25 // Basic usage: 24 26 // 1. Create an encoder with NewMouseEncoder. ··· 182 184 183 185 // SetOptFromTerminal reads the terminal's current mouse tracking mode 184 186 // and output format and applies them to the encoder. It does not 185 - // modify size or any-button state. 187 + // modify size or any-button state. The caller must serialize access to 188 + // both the encoder and the terminal during this call. 186 189 func (enc *MouseEncoder) SetOptFromTerminal(t *Terminal) { 187 190 C.ghostty_mouse_encoder_setopt_from_terminal(enc.ptr, t.ptr) 188 191 }
+2 -1
mouse_event.go
··· 10 10 11 11 // MouseEvent is an opaque handle representing a normalized mouse 12 12 // input event containing action, button, modifiers, and surface-space 13 - // position. 13 + // position. It is mutable and reusable, but not safe for concurrent 14 + // use. 14 15 // 15 16 // C: GhosttyMouseEvent 16 17 type MouseEvent struct {
+14 -2
render_state.go
··· 13 13 // for repeated updates from a single terminal, only updating dirty 14 14 // regions of the screen. 15 15 // 16 + // A render state owns its own snapshot storage. Hold exclusive access 17 + // to the terminal while calling [RenderState.Update]. After Update 18 + // returns, the render state can be read without touching the terminal 19 + // until the next Update. Do not call Update concurrently with reads 20 + // from the same render state. 21 + // 22 + // Iterators populated from the render state are only valid until the 23 + // next Update, but copied values returned from their getter methods can 24 + // be retained. 25 + // 16 26 // Basic usage: 17 27 // 1. Create an empty render state with NewRenderState. 18 28 // 2. Update it from a terminal via Update whenever needed. ··· 96 106 } 97 107 98 108 // Update updates the render state from a terminal instance. This 99 - // consumes terminal/screen dirty state. The terminal must not be 100 - // used concurrently during this call. 109 + // consumes terminal/screen dirty state and is the only render-state 110 + // operation that touches the terminal. Hold exclusive access to the 111 + // terminal while this call is running, and do not read from the same 112 + // render state concurrently with Update. 101 113 func (rs *RenderState) Update(t *Terminal) error { 102 114 return resultError(C.ghostty_render_state_update(rs.ptr, t.ptr)) 103 115 }
+7 -4
render_state_cell.go
··· 48 48 49 49 // RenderStateRowCells iterates over cells in a render-state row. 50 50 // Create one with NewRenderStateRowCells, populate it via 51 - // RenderStateRowIterator.Cells, then advance with Next (or jump 52 - // with Select) and read data with getter methods. 51 + // [RenderStateRowIterator.Cells], then advance with 52 + // [RenderStateRowCells.Next] (or jump with [RenderStateRowCells.Select]) 53 + // and read data with getter methods. 53 54 // 54 55 // A single instance can be reused across rows to avoid repeated 55 - // allocation. Cell data is only valid until the next call to 56 - // RenderState.Update. 56 + // allocation. The cells view is only valid until the next call to 57 + // [RenderState.Update]. Do not use it while [RenderState.Update] may 58 + // run on the same render state. Extracted [Cell], [Style], color 59 + // values, and grapheme slices are copied values and may be retained. 57 60 // 58 61 // C: GhosttyRenderStateRowCells 59 62 type RenderStateRowCells struct {
+2 -2
render_state_data.go
··· 313 313 // from the render state. The iterator can then be advanced with Next 314 314 // and queried with getter methods. 315 315 // 316 - // The iterator can be reused across multiple calls. Row data is only 317 - // valid until the next call to Update. 316 + // The iterator can be reused across multiple calls. The iterator view 317 + // is only valid until the next call to [RenderState.Update]. 318 318 func (rs *RenderState) RowIterator(ri *RenderStateRowIterator) error { 319 319 return resultError(C.ghostty_render_state_get( 320 320 rs.ptr,
+7 -5
render_state_row.go
··· 35 35 36 36 // RenderStateRowIterator iterates over rows in a render state. 37 37 // Create one with NewRenderStateRowIterator, populate it via 38 - // RenderState.RowIterator, then advance with Next and read data 39 - // with getter methods. 38 + // [RenderState.RowIterator], then advance with [RenderStateRowIterator.Next] 39 + // and read data with getter methods. 40 40 // 41 - // Row data is only valid as long as the underlying render state 42 - // is not updated. It is unsafe to use row data after calling 43 - // RenderState.Update. 41 + // The iterator's current position is only valid as long as the 42 + // underlying render state is not updated. Do not use the iterator 43 + // while [RenderState.Update] may run on the same render state. 44 + // Extracted [Row] values are copied snapshots and may be retained after 45 + // the iterator itself becomes invalid. 44 46 // 45 47 // C: GhosttyRenderStateRowIterator 46 48 type RenderStateRowIterator struct {
+6 -2
screen.go
··· 99 99 ) 100 100 101 101 // Cell is a wrapper around an opaque terminal grid cell value. 102 - // Use getter methods to extract data from it. 102 + // Use getter methods to extract data from it. A Cell is a copied value 103 + // snapshot, not a borrowed handle, so it may be retained after the 104 + // [GridRef] or render-state iterator that produced it becomes invalid. 103 105 // C: GhosttyCell 104 106 type Cell struct { 105 107 c C.GhosttyCell 106 108 } 107 109 108 110 // Row is a wrapper around an opaque terminal grid row value. 109 - // Use getter methods to extract data from it. 111 + // Use getter methods to extract data from it. A Row is a copied value 112 + // snapshot, not a borrowed handle, so it may be retained after the 113 + // [GridRef] or render-state iterator that produced it becomes invalid. 110 114 // C: GhosttyRow 111 115 type Row struct { 112 116 c C.GhosttyRow
+5 -3
style.go
··· 60 60 UnderlineDashed = C.GHOSTTY_SGR_UNDERLINE_DASHED 61 61 ) 62 62 63 - // Style is a thin wrapper around the C GhosttyStyle. It provides 64 - // getter methods to access individual style attributes without 65 - // copying the entire struct upfront. 63 + // Style is a thin wrapper around the copied C GhosttyStyle value. It 64 + // provides getter methods to access individual style attributes 65 + // without copying the entire struct upfront. A Style is a value 66 + // snapshot and may be retained after the terminal, [GridRef], or 67 + // render-state iterator that produced it becomes invalid. 66 68 // C: GhosttyStyle 67 69 type Style struct { 68 70 c C.GhosttyStyle
+16 -5
terminal.go
··· 11 11 ) 12 12 13 13 // Terminal wraps a Ghostty VT terminal handle. 14 + // It is stateful, not safe for concurrent use, and not reentrant. 15 + // Serialize all calls that touch a terminal, including getters, 16 + // setters, [Terminal.VTWrite], [Terminal.Resize], [Terminal.Close], 17 + // and any borrowed handles derived from it. Effect callbacks run 18 + // synchronously during [Terminal.VTWrite]; they must not call 19 + // [Terminal.VTWrite] on the same terminal and should avoid blocking 20 + // for long periods. 14 21 // C: GhosttyTerminal 15 22 type Terminal struct { 16 23 ptr C.GhosttyTerminal ··· 281 288 282 289 // VTWrite feeds raw VT-encoded bytes through the terminal's parser, 283 290 // updating terminal state. Malformed input is handled gracefully and 284 - // will not cause an error. 291 + // will not cause an error. Effect callbacks run synchronously before 292 + // this call returns; they must not call [Terminal.VTWrite] on the same 293 + // terminal. 285 294 func (t *Terminal) VTWrite(data []byte) { 286 295 if len(data) == 0 { 287 296 return ··· 376 385 } 377 386 378 387 // KittyGraphics returns the Kitty graphics image storage for the 379 - // terminal's active screen. The returned handle is borrowed from 380 - // the terminal and remains valid until the next mutating call 381 - // (e.g. VTWrite or Reset). 388 + // terminal's active screen. The returned handle is borrowed from the 389 + // terminal and remains valid until the next mutating call (for example 390 + // [Terminal.VTWrite] or [Terminal.Reset]). Serialize use of the 391 + // returned handle with mutations of the terminal. 382 392 func (t *Terminal) KittyGraphics() (*KittyGraphics, error) { 383 393 var ptr C.GhosttyKittyGraphics 384 394 if err := resultError(C.ghostty_terminal_get( ··· 392 402 } 393 403 394 404 // GridRef resolves a point in the terminal grid to a grid reference. 395 - // The returned GridRef is only valid until the next terminal update. 405 + // The returned GridRef is borrowed from terminal internals and should 406 + // be used immediately; any later terminal operation may invalidate it. 396 407 // 397 408 // Lookups using PointTagActive and PointTagViewport are fast. 398 409 // PointTagScreen and PointTagHistory may be expensive for large