@silvery/term 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/package.json +54 -0
  2. package/src/adapters/canvas-adapter.ts +356 -0
  3. package/src/adapters/dom-adapter.ts +452 -0
  4. package/src/adapters/flexily-zero-adapter.ts +368 -0
  5. package/src/adapters/terminal-adapter.ts +305 -0
  6. package/src/adapters/yoga-adapter.ts +370 -0
  7. package/src/ansi/ansi.ts +251 -0
  8. package/src/ansi/constants.ts +76 -0
  9. package/src/ansi/detection.ts +441 -0
  10. package/src/ansi/hyperlink.ts +38 -0
  11. package/src/ansi/index.ts +201 -0
  12. package/src/ansi/patch-console.ts +159 -0
  13. package/src/ansi/sgr-codes.ts +34 -0
  14. package/src/ansi/storybook.ts +209 -0
  15. package/src/ansi/term.ts +724 -0
  16. package/src/ansi/types.ts +202 -0
  17. package/src/ansi/underline.ts +156 -0
  18. package/src/ansi/utils.ts +65 -0
  19. package/src/ansi-sanitize.ts +509 -0
  20. package/src/app.ts +571 -0
  21. package/src/bound-term.ts +94 -0
  22. package/src/bracketed-paste.ts +75 -0
  23. package/src/browser-renderer.ts +174 -0
  24. package/src/buffer.ts +1984 -0
  25. package/src/clipboard.ts +74 -0
  26. package/src/cursor-query.ts +85 -0
  27. package/src/device-attrs.ts +228 -0
  28. package/src/devtools.ts +123 -0
  29. package/src/dom/index.ts +194 -0
  30. package/src/errors.ts +39 -0
  31. package/src/focus-reporting.ts +48 -0
  32. package/src/hit-registry-core.ts +228 -0
  33. package/src/hit-registry.ts +176 -0
  34. package/src/index.ts +458 -0
  35. package/src/input.ts +119 -0
  36. package/src/inspector.ts +155 -0
  37. package/src/kitty-detect.ts +95 -0
  38. package/src/kitty-manager.ts +160 -0
  39. package/src/layout-engine.ts +296 -0
  40. package/src/layout.ts +26 -0
  41. package/src/measurer.ts +74 -0
  42. package/src/mode-query.ts +106 -0
  43. package/src/mouse-events.ts +419 -0
  44. package/src/mouse.ts +83 -0
  45. package/src/non-tty.ts +223 -0
  46. package/src/osc-markers.ts +32 -0
  47. package/src/osc-palette.ts +169 -0
  48. package/src/output.ts +406 -0
  49. package/src/pane-manager.ts +248 -0
  50. package/src/pipeline/CLAUDE.md +587 -0
  51. package/src/pipeline/content-phase-adapter.ts +976 -0
  52. package/src/pipeline/content-phase.ts +1765 -0
  53. package/src/pipeline/helpers.ts +42 -0
  54. package/src/pipeline/index.ts +416 -0
  55. package/src/pipeline/layout-phase.ts +686 -0
  56. package/src/pipeline/measure-phase.ts +198 -0
  57. package/src/pipeline/measure-stats.ts +21 -0
  58. package/src/pipeline/output-phase.ts +2593 -0
  59. package/src/pipeline/render-box.ts +343 -0
  60. package/src/pipeline/render-helpers.ts +243 -0
  61. package/src/pipeline/render-text.ts +1255 -0
  62. package/src/pipeline/types.ts +161 -0
  63. package/src/pipeline.ts +29 -0
  64. package/src/pixel-size.ts +119 -0
  65. package/src/render-adapter.ts +179 -0
  66. package/src/renderer.ts +1330 -0
  67. package/src/runtime/create-app.tsx +1845 -0
  68. package/src/runtime/create-buffer.ts +18 -0
  69. package/src/runtime/create-runtime.ts +325 -0
  70. package/src/runtime/diff.ts +56 -0
  71. package/src/runtime/event-handlers.ts +254 -0
  72. package/src/runtime/index.ts +119 -0
  73. package/src/runtime/keys.ts +8 -0
  74. package/src/runtime/layout.ts +164 -0
  75. package/src/runtime/run.tsx +318 -0
  76. package/src/runtime/term-provider.ts +399 -0
  77. package/src/runtime/terminal-lifecycle.ts +246 -0
  78. package/src/runtime/tick.ts +219 -0
  79. package/src/runtime/types.ts +210 -0
  80. package/src/scheduler.ts +723 -0
  81. package/src/screenshot.ts +57 -0
  82. package/src/scroll-region.ts +69 -0
  83. package/src/scroll-utils.ts +97 -0
  84. package/src/term-def.ts +267 -0
  85. package/src/terminal-caps.ts +5 -0
  86. package/src/terminal-colors.ts +216 -0
  87. package/src/termtest.ts +224 -0
  88. package/src/text-sizing.ts +109 -0
  89. package/src/toolbelt/index.ts +72 -0
  90. package/src/unicode.ts +1763 -0
  91. package/src/xterm/index.ts +491 -0
  92. package/src/xterm/xterm-provider.ts +204 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Yoga Layout Engine Adapter
3
+ *
4
+ * Wraps yoga-wasm-web to implement the LayoutEngine interface.
5
+ */
6
+
7
+ import type {
8
+ Align,
9
+ Direction,
10
+ Display,
11
+ Edge,
12
+ FlexDirection,
13
+ Gutter,
14
+ Justify,
15
+ Overflow,
16
+ PositionType,
17
+ Wrap,
18
+ Yoga,
19
+ Node as YogaNode,
20
+ } from "yoga-wasm-web"
21
+ import type {
22
+ AlignValue,
23
+ DirectionValue,
24
+ DisplayValue,
25
+ EdgeValue,
26
+ FlexDirectionValue,
27
+ GutterValue,
28
+ JustifyValue,
29
+ LayoutConstants,
30
+ LayoutEngine,
31
+ LayoutNode,
32
+ MeasureFunc,
33
+ MeasureMode,
34
+ MeasureModeValue,
35
+ OverflowValue,
36
+ PositionTypeValue,
37
+ WrapValue,
38
+ } from "../layout-engine"
39
+
40
+ // ============================================================================
41
+ // Yoga Node Adapter
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Wraps a Yoga node to implement LayoutNode interface.
46
+ */
47
+ class YogaNodeAdapter implements LayoutNode {
48
+ private node: YogaNode
49
+ private yoga: Yoga
50
+ private hasMeasureFunc = false
51
+
52
+ constructor(node: YogaNode, yoga: Yoga) {
53
+ this.node = node
54
+ this.yoga = yoga
55
+ }
56
+
57
+ /** Get the underlying Yoga node (for tree operations) */
58
+ getYogaNode(): YogaNode {
59
+ return this.node
60
+ }
61
+
62
+ // Tree operations
63
+ insertChild(child: LayoutNode, index: number): void {
64
+ const yogaChild = (child as YogaNodeAdapter).getYogaNode()
65
+ this.node.insertChild(yogaChild, index)
66
+ }
67
+
68
+ removeChild(child: LayoutNode): void {
69
+ const yogaChild = (child as YogaNodeAdapter).getYogaNode()
70
+ this.node.removeChild(yogaChild)
71
+ }
72
+
73
+ free(): void {
74
+ this.node.free()
75
+ }
76
+
77
+ // Measure function
78
+ setMeasureFunc(measureFunc: MeasureFunc): void {
79
+ this.hasMeasureFunc = true
80
+ this.node.setMeasureFunc((width, widthMode, height, heightMode) => {
81
+ const widthModeStr = this.measureModeToString(widthMode)
82
+ const heightModeStr = this.measureModeToString(heightMode)
83
+ return measureFunc(width, widthModeStr, height, heightModeStr)
84
+ })
85
+ }
86
+
87
+ // Dirty tracking - forces layout recalculation
88
+ // Yoga only allows markDirty() on leaf nodes with measure functions
89
+ markDirty(): void {
90
+ if (this.hasMeasureFunc) {
91
+ this.node.markDirty()
92
+ }
93
+ }
94
+
95
+ private measureModeToString(mode: number): MeasureMode {
96
+ if (mode === this.yoga.MEASURE_MODE_EXACTLY) return "exactly"
97
+ if (mode === this.yoga.MEASURE_MODE_AT_MOST) return "at-most"
98
+ return "undefined"
99
+ }
100
+
101
+ // Dimension setters
102
+ setWidth(value: number): void {
103
+ this.node.setWidth(value)
104
+ }
105
+ setWidthPercent(value: number): void {
106
+ this.node.setWidthPercent(value)
107
+ }
108
+ setWidthAuto(): void {
109
+ this.node.setWidthAuto()
110
+ }
111
+ setHeight(value: number): void {
112
+ this.node.setHeight(value)
113
+ }
114
+ setHeightPercent(value: number): void {
115
+ this.node.setHeightPercent(value)
116
+ }
117
+ setHeightAuto(): void {
118
+ this.node.setHeightAuto()
119
+ }
120
+ setMinWidth(value: number): void {
121
+ this.node.setMinWidth(value)
122
+ }
123
+ setMinWidthPercent(value: number): void {
124
+ this.node.setMinWidthPercent(value)
125
+ }
126
+ setMinHeight(value: number): void {
127
+ this.node.setMinHeight(value)
128
+ }
129
+ setMinHeightPercent(value: number): void {
130
+ this.node.setMinHeightPercent(value)
131
+ }
132
+ setMaxWidth(value: number): void {
133
+ this.node.setMaxWidth(value)
134
+ }
135
+ setMaxWidthPercent(value: number): void {
136
+ this.node.setMaxWidthPercent(value)
137
+ }
138
+ setMaxHeight(value: number): void {
139
+ this.node.setMaxHeight(value)
140
+ }
141
+ setMaxHeightPercent(value: number): void {
142
+ this.node.setMaxHeightPercent(value)
143
+ }
144
+
145
+ // Flex properties
146
+ setFlexGrow(value: number): void {
147
+ this.node.setFlexGrow(value)
148
+ }
149
+ setFlexShrink(value: number): void {
150
+ this.node.setFlexShrink(value)
151
+ }
152
+ setFlexBasis(value: number): void {
153
+ this.node.setFlexBasis(value)
154
+ }
155
+ setFlexBasisPercent(value: number): void {
156
+ this.node.setFlexBasisPercent(value)
157
+ }
158
+ setFlexBasisAuto(): void {
159
+ this.node.setFlexBasisAuto()
160
+ }
161
+ setFlexDirection(direction: number): void {
162
+ // LayoutEngine uses plain numbers; Yoga uses branded FlexDirection type
163
+ this.node.setFlexDirection(direction as FlexDirection)
164
+ }
165
+ setFlexWrap(wrap: number): void {
166
+ // LayoutEngine uses plain numbers; Yoga uses branded Wrap type
167
+ this.node.setFlexWrap(wrap as Wrap)
168
+ }
169
+
170
+ // Alignment
171
+ setAlignItems(align: number): void {
172
+ // LayoutEngine uses plain numbers; Yoga uses branded Align type
173
+ this.node.setAlignItems(align as Align)
174
+ }
175
+ setAlignSelf(align: number): void {
176
+ // LayoutEngine uses plain numbers; Yoga uses branded Align type
177
+ this.node.setAlignSelf(align as Align)
178
+ }
179
+ setAlignContent(align: number): void {
180
+ // LayoutEngine uses plain numbers; Yoga uses branded Align type
181
+ this.node.setAlignContent(align as Align)
182
+ }
183
+ setJustifyContent(justify: number): void {
184
+ // LayoutEngine uses plain numbers; Yoga uses branded Justify type
185
+ this.node.setJustifyContent(justify as Justify)
186
+ }
187
+
188
+ // Spacing
189
+ setPadding(edge: number, value: number): void {
190
+ // LayoutEngine uses plain numbers; Yoga uses branded Edge type
191
+ this.node.setPadding(edge as Edge, value)
192
+ }
193
+ setMargin(edge: number, value: number): void {
194
+ // LayoutEngine uses plain numbers; Yoga uses branded Edge type
195
+ this.node.setMargin(edge as Edge, value)
196
+ }
197
+ setBorder(edge: number, value: number): void {
198
+ // LayoutEngine uses plain numbers; Yoga uses branded Edge type
199
+ this.node.setBorder(edge as Edge, value)
200
+ }
201
+ setGap(gutter: number, value: number): void {
202
+ // LayoutEngine uses plain numbers; Yoga uses branded Gutter type
203
+ this.node.setGap(gutter as Gutter, value)
204
+ }
205
+
206
+ // Display & Position
207
+ setDisplay(display: number): void {
208
+ // LayoutEngine uses plain numbers; Yoga uses branded Display type
209
+ this.node.setDisplay(display as Display)
210
+ }
211
+ setPositionType(positionType: number): void {
212
+ // LayoutEngine uses plain numbers; Yoga uses branded PositionType type
213
+ this.node.setPositionType(positionType as PositionType)
214
+ }
215
+ setPosition(edge: number, value: number): void {
216
+ // LayoutEngine uses plain numbers; Yoga uses branded Edge type
217
+ this.node.setPosition(edge as Edge, value)
218
+ }
219
+ setPositionPercent(edge: number, value: number): void {
220
+ // LayoutEngine uses plain numbers; Yoga uses branded Edge type
221
+ this.node.setPositionPercent(edge as Edge, value)
222
+ }
223
+ setOverflow(overflow: number): void {
224
+ // LayoutEngine uses plain numbers; Yoga uses branded Overflow type
225
+ this.node.setOverflow(overflow as Overflow)
226
+ }
227
+
228
+ // Aspect Ratio
229
+ setAspectRatio(value: number): void {
230
+ this.node.setAspectRatio(value)
231
+ }
232
+
233
+ // Layout calculation
234
+ calculateLayout(width: number, height: number, direction?: number): void {
235
+ // LayoutEngine uses plain numbers; Yoga uses branded Direction type
236
+ this.node.calculateLayout(width, height, (direction ?? this.yoga.DIRECTION_LTR) as Direction)
237
+ }
238
+
239
+ // Layout results
240
+ getComputedLeft(): number {
241
+ return this.node.getComputedLeft()
242
+ }
243
+ getComputedTop(): number {
244
+ return this.node.getComputedTop()
245
+ }
246
+ getComputedWidth(): number {
247
+ return this.node.getComputedWidth()
248
+ }
249
+ getComputedHeight(): number {
250
+ return this.node.getComputedHeight()
251
+ }
252
+ }
253
+
254
+ // ============================================================================
255
+ // Yoga Layout Engine
256
+ // ============================================================================
257
+
258
+ /**
259
+ * Layout engine implementation using Yoga (WASM).
260
+ */
261
+ export class YogaLayoutEngine implements LayoutEngine {
262
+ private yoga: Yoga
263
+ private _constants: LayoutConstants
264
+
265
+ constructor(yoga: Yoga) {
266
+ this.yoga = yoga
267
+ // Cast Yoga's branded types to our LayoutEngine branded types at the adapter boundary
268
+ this._constants = {
269
+ // Flex Direction
270
+ FLEX_DIRECTION_COLUMN: yoga.FLEX_DIRECTION_COLUMN as unknown as FlexDirectionValue,
271
+ FLEX_DIRECTION_COLUMN_REVERSE: yoga.FLEX_DIRECTION_COLUMN_REVERSE as unknown as FlexDirectionValue,
272
+ FLEX_DIRECTION_ROW: yoga.FLEX_DIRECTION_ROW as unknown as FlexDirectionValue,
273
+ FLEX_DIRECTION_ROW_REVERSE: yoga.FLEX_DIRECTION_ROW_REVERSE as unknown as FlexDirectionValue,
274
+
275
+ // Wrap
276
+ WRAP_NO_WRAP: yoga.WRAP_NO_WRAP as unknown as WrapValue,
277
+ WRAP_WRAP: yoga.WRAP_WRAP as unknown as WrapValue,
278
+ WRAP_WRAP_REVERSE: yoga.WRAP_WRAP_REVERSE as unknown as WrapValue,
279
+
280
+ // Align
281
+ ALIGN_AUTO: yoga.ALIGN_AUTO as unknown as AlignValue,
282
+ ALIGN_FLEX_START: yoga.ALIGN_FLEX_START as unknown as AlignValue,
283
+ ALIGN_CENTER: yoga.ALIGN_CENTER as unknown as AlignValue,
284
+ ALIGN_FLEX_END: yoga.ALIGN_FLEX_END as unknown as AlignValue,
285
+ ALIGN_STRETCH: yoga.ALIGN_STRETCH as unknown as AlignValue,
286
+ ALIGN_BASELINE: yoga.ALIGN_BASELINE as unknown as AlignValue,
287
+ ALIGN_SPACE_BETWEEN: yoga.ALIGN_SPACE_BETWEEN as unknown as AlignValue,
288
+ ALIGN_SPACE_AROUND: yoga.ALIGN_SPACE_AROUND as unknown as AlignValue,
289
+ ALIGN_SPACE_EVENLY: yoga.ALIGN_SPACE_EVENLY as unknown as AlignValue,
290
+
291
+ // Justify
292
+ JUSTIFY_FLEX_START: yoga.JUSTIFY_FLEX_START as unknown as JustifyValue,
293
+ JUSTIFY_CENTER: yoga.JUSTIFY_CENTER as unknown as JustifyValue,
294
+ JUSTIFY_FLEX_END: yoga.JUSTIFY_FLEX_END as unknown as JustifyValue,
295
+ JUSTIFY_SPACE_BETWEEN: yoga.JUSTIFY_SPACE_BETWEEN as unknown as JustifyValue,
296
+ JUSTIFY_SPACE_AROUND: yoga.JUSTIFY_SPACE_AROUND as unknown as JustifyValue,
297
+ JUSTIFY_SPACE_EVENLY: yoga.JUSTIFY_SPACE_EVENLY as unknown as JustifyValue,
298
+
299
+ // Edge
300
+ EDGE_LEFT: yoga.EDGE_LEFT as unknown as EdgeValue,
301
+ EDGE_TOP: yoga.EDGE_TOP as unknown as EdgeValue,
302
+ EDGE_RIGHT: yoga.EDGE_RIGHT as unknown as EdgeValue,
303
+ EDGE_BOTTOM: yoga.EDGE_BOTTOM as unknown as EdgeValue,
304
+ EDGE_HORIZONTAL: yoga.EDGE_HORIZONTAL as unknown as EdgeValue,
305
+ EDGE_VERTICAL: yoga.EDGE_VERTICAL as unknown as EdgeValue,
306
+ EDGE_ALL: yoga.EDGE_ALL as unknown as EdgeValue,
307
+
308
+ // Gutter
309
+ GUTTER_COLUMN: yoga.GUTTER_COLUMN as unknown as GutterValue,
310
+ GUTTER_ROW: yoga.GUTTER_ROW as unknown as GutterValue,
311
+ GUTTER_ALL: yoga.GUTTER_ALL as unknown as GutterValue,
312
+
313
+ // Display
314
+ DISPLAY_FLEX: yoga.DISPLAY_FLEX as unknown as DisplayValue,
315
+ DISPLAY_NONE: yoga.DISPLAY_NONE as unknown as DisplayValue,
316
+
317
+ // Position Type
318
+ POSITION_TYPE_STATIC: yoga.POSITION_TYPE_STATIC as unknown as PositionTypeValue,
319
+ POSITION_TYPE_RELATIVE: yoga.POSITION_TYPE_RELATIVE as unknown as PositionTypeValue,
320
+ POSITION_TYPE_ABSOLUTE: yoga.POSITION_TYPE_ABSOLUTE as unknown as PositionTypeValue,
321
+
322
+ // Overflow
323
+ OVERFLOW_VISIBLE: yoga.OVERFLOW_VISIBLE as unknown as OverflowValue,
324
+ OVERFLOW_HIDDEN: yoga.OVERFLOW_HIDDEN as unknown as OverflowValue,
325
+ OVERFLOW_SCROLL: yoga.OVERFLOW_SCROLL as unknown as OverflowValue,
326
+
327
+ // Direction
328
+ DIRECTION_LTR: yoga.DIRECTION_LTR as unknown as DirectionValue,
329
+
330
+ // Measure Mode
331
+ MEASURE_MODE_UNDEFINED: yoga.MEASURE_MODE_UNDEFINED as unknown as MeasureModeValue,
332
+ MEASURE_MODE_EXACTLY: yoga.MEASURE_MODE_EXACTLY as unknown as MeasureModeValue,
333
+ MEASURE_MODE_AT_MOST: yoga.MEASURE_MODE_AT_MOST as unknown as MeasureModeValue,
334
+ }
335
+ }
336
+
337
+ createNode(): LayoutNode {
338
+ return new YogaNodeAdapter(this.yoga.Node.create(), this.yoga)
339
+ }
340
+
341
+ get constants(): LayoutConstants {
342
+ return this._constants
343
+ }
344
+
345
+ get name(): string {
346
+ return "yoga"
347
+ }
348
+ }
349
+
350
+ // ============================================================================
351
+ // Initialization Helper
352
+ // ============================================================================
353
+
354
+ /**
355
+ * Create a Yoga layout engine from an initialized Yoga instance.
356
+ */
357
+ export function createYogaEngine(yoga: Yoga): YogaLayoutEngine {
358
+ return new YogaLayoutEngine(yoga)
359
+ }
360
+
361
+ /**
362
+ * Initialize Yoga and create a layout engine.
363
+ * Uses yoga-wasm-web/auto which automatically selects the right implementation.
364
+ */
365
+ export async function initYogaEngine(): Promise<YogaLayoutEngine> {
366
+ const { default: yoga } = (await import("yoga-wasm-web/auto")) as {
367
+ default: Yoga
368
+ }
369
+ return new YogaLayoutEngine(yoga)
370
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * ANSI terminal control helpers.
3
+ *
4
+ * Pure string-returning functions for terminal control sequences.
5
+ * No side effects, no stdout writes -- consumers compose and write.
6
+ *
7
+ * Covers: screen management, cursor control, scroll regions,
8
+ * mouse tracking, keyboard protocols, and bracketed paste.
9
+ *
10
+ * @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
11
+ * @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/
12
+ */
13
+
14
+ // =============================================================================
15
+ // Base Constants
16
+ // =============================================================================
17
+
18
+ /** Escape character (0x1B) */
19
+ const ESC = "\x1b"
20
+
21
+ /** Control Sequence Introducer: ESC [ */
22
+ const CSI = `${ESC}[`
23
+
24
+ /** Operating System Command: ESC ] */
25
+ const OSC = `${ESC}]`
26
+
27
+ /** Bell character (0x07) -- used as string terminator in OSC sequences */
28
+ const BEL = "\x07"
29
+
30
+ // =============================================================================
31
+ // Screen
32
+ // =============================================================================
33
+
34
+ /**
35
+ * Enter the alternate screen buffer (DEC private mode 1049).
36
+ * The alternate screen preserves the main scrollback buffer.
37
+ */
38
+ export function enterAltScreen(): string {
39
+ return `${CSI}?1049h`
40
+ }
41
+
42
+ /**
43
+ * Leave the alternate screen buffer and restore the main screen.
44
+ */
45
+ export function leaveAltScreen(): string {
46
+ return `${CSI}?1049l`
47
+ }
48
+
49
+ /**
50
+ * Clear the entire screen (ED 2 -- Erase in Display, all).
51
+ */
52
+ export function clearScreen(): string {
53
+ return `${CSI}2J`
54
+ }
55
+
56
+ /**
57
+ * Clear the current line (EL 2 -- Erase in Line, entire line).
58
+ */
59
+ export function clearLine(): string {
60
+ return `${CSI}2K`
61
+ }
62
+
63
+ // =============================================================================
64
+ // Cursor
65
+ // =============================================================================
66
+
67
+ /**
68
+ * Move cursor to an absolute position.
69
+ * Uses 0-indexed row/col; converts to 1-indexed CUP (Cursor Position).
70
+ */
71
+ export function cursorTo(row: number, col: number): string {
72
+ return `${CSI}${row + 1};${col + 1}H`
73
+ }
74
+
75
+ /**
76
+ * Move cursor to the home position (top-left, row 0, col 0).
77
+ */
78
+ export function cursorHome(): string {
79
+ return `${CSI}H`
80
+ }
81
+
82
+ /**
83
+ * Hide the cursor (DEC private mode 25, reset).
84
+ */
85
+ export function cursorHide(): string {
86
+ return `${CSI}?25l`
87
+ }
88
+
89
+ /**
90
+ * Show the cursor (DEC private mode 25, set).
91
+ */
92
+ export function cursorShow(): string {
93
+ return `${CSI}?25h`
94
+ }
95
+
96
+ /**
97
+ * Set cursor style via DECSCUSR (DEC Set Cursor Style).
98
+ *
99
+ * | Style | Code | Description |
100
+ * | ----------- | ---- | ------------------------ |
101
+ * | block | 2 | Steady block |
102
+ * | underline | 4 | Steady underline |
103
+ * | beam | 6 | Steady bar (vertical) |
104
+ *
105
+ * Steady variants are used (even codes). Blinking would be odd codes.
106
+ * Supported by: xterm, Ghostty, Kitty, WezTerm, iTerm2, Alacritty, foot.
107
+ */
108
+ export function cursorStyle(style: "block" | "underline" | "beam"): string {
109
+ const code = style === "block" ? 2 : style === "underline" ? 4 : 6
110
+ return `${CSI}${code} q`
111
+ }
112
+
113
+ // =============================================================================
114
+ // Terminal
115
+ // =============================================================================
116
+
117
+ /**
118
+ * Set the terminal window title using OSC 2 (window title only).
119
+ * Does not affect icon title. Widely supported.
120
+ */
121
+ export function setTitle(title: string): string {
122
+ return `${OSC}2;${title}${BEL}`
123
+ }
124
+
125
+ /**
126
+ * Enable mouse tracking.
127
+ *
128
+ * Enables three modes for full mouse support:
129
+ * - 1000: Basic button press/release reporting
130
+ * - 1002: Button-event tracking (drag events)
131
+ * - 1006: SGR extended coordinates (supports >223 columns)
132
+ */
133
+ export function enableMouse(): string {
134
+ return `${CSI}?1000h${CSI}?1002h${CSI}?1006h`
135
+ }
136
+
137
+ /**
138
+ * Disable mouse tracking.
139
+ *
140
+ * Disables in reverse order of enabling.
141
+ */
142
+ export function disableMouse(): string {
143
+ return `${CSI}?1006l${CSI}?1002l${CSI}?1000l`
144
+ }
145
+
146
+ /**
147
+ * Enable bracketed paste mode (DEC private mode 2004).
148
+ * Terminal wraps pasted text with markers so the app can distinguish
149
+ * paste from typed input.
150
+ */
151
+ export function enableBracketedPaste(): string {
152
+ return `${CSI}?2004h`
153
+ }
154
+
155
+ /**
156
+ * Disable bracketed paste mode.
157
+ */
158
+ export function disableBracketedPaste(): string {
159
+ return `${CSI}?2004l`
160
+ }
161
+
162
+ /**
163
+ * Enable synchronized update mode (DEC private mode 2026).
164
+ * Tells the terminal to batch output and paint atomically, preventing tearing.
165
+ * Supported by: Ghostty, Kitty, WezTerm, iTerm2, Foot, Alacritty 0.14+, tmux 3.2+.
166
+ * Terminals that don't support it safely ignore this sequence.
167
+ */
168
+ export function enableSyncUpdate(): string {
169
+ return `${CSI}?2026h`
170
+ }
171
+
172
+ /**
173
+ * Disable synchronized update mode.
174
+ * Sending this when not in sync mode is a harmless no-op.
175
+ */
176
+ export function disableSyncUpdate(): string {
177
+ return `${CSI}?2026l`
178
+ }
179
+
180
+ // =============================================================================
181
+ // Scroll
182
+ // =============================================================================
183
+
184
+ /**
185
+ * Set the terminal scroll region (DECSTBM -- DEC Set Top and Bottom Margins).
186
+ * Uses 0-indexed top/bottom; converts to 1-indexed for the terminal.
187
+ *
188
+ * Supported by most modern terminals: xterm, iTerm2, Kitty, Ghostty, WezTerm, etc.
189
+ */
190
+ export function setScrollRegion(top: number, bottom: number): string {
191
+ return `${CSI}${top + 1};${bottom + 1}r`
192
+ }
193
+
194
+ /**
195
+ * Reset the scroll region to the full terminal height.
196
+ */
197
+ export function resetScrollRegion(): string {
198
+ return `${CSI}r`
199
+ }
200
+
201
+ /**
202
+ * Scroll content up by N lines within the current scroll region (SU).
203
+ * New blank lines appear at the bottom.
204
+ */
205
+ export function scrollUp(n: number): string {
206
+ if (n <= 0) return ""
207
+ return `${CSI}${n}S`
208
+ }
209
+
210
+ /**
211
+ * Scroll content down by N lines within the current scroll region (SD).
212
+ * New blank lines appear at the top.
213
+ */
214
+ export function scrollDown(n: number): string {
215
+ if (n <= 0) return ""
216
+ return `${CSI}${n}T`
217
+ }
218
+
219
+ // =============================================================================
220
+ // Keyboard
221
+ // =============================================================================
222
+
223
+ /**
224
+ * Enable the Kitty keyboard protocol (push mode).
225
+ *
226
+ * Sends CSI > flags u to opt into the specified modes.
227
+ * Supported by: Ghostty, Kitty, WezTerm, foot. Ignored by unsupported terminals.
228
+ *
229
+ * Flags are a bitfield:
230
+ *
231
+ * | Flag | Bit | Description |
232
+ * | ---- | --- | ----------------------------------------- |
233
+ * | 1 | 0 | Disambiguate escape codes |
234
+ * | 2 | 1 | Report event types (press/repeat/release) |
235
+ * | 4 | 2 | Report alternate keys |
236
+ * | 8 | 3 | Report all keys as escape codes |
237
+ * | 16 | 4 | Report associated text |
238
+ *
239
+ * @param flags Bitfield of Kitty keyboard flags
240
+ */
241
+ export function enableKittyKeyboard(flags: number): string {
242
+ return `${CSI}>${flags}u`
243
+ }
244
+
245
+ /**
246
+ * Disable the Kitty keyboard protocol (pop mode stack).
247
+ * Sends CSI < u to restore the previous keyboard mode.
248
+ */
249
+ export function disableKittyKeyboard(): string {
250
+ return `${CSI}<u`
251
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ANSI escape code constants for extended terminal features.
3
+ *
4
+ * @see https://sw.kovidgoyal.net/kitty/underlines/
5
+ * @see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
6
+ */
7
+
8
+ // =============================================================================
9
+ // Extended Underline Styles (ISO 8613-6 / ECMA-48)
10
+ // =============================================================================
11
+
12
+ /**
13
+ * Extended underline style codes.
14
+ * Uses colon-separated parameters (ISO 8613-6): \x1b[4:Nm
15
+ */
16
+ export const UNDERLINE_CODES = {
17
+ /** No underline */
18
+ none: "\x1b[4:0m",
19
+ /** Standard single underline */
20
+ single: "\x1b[4:1m",
21
+ /** Double underline (two parallel lines) */
22
+ double: "\x1b[4:2m",
23
+ /** Curly/wavy underline (spell check style) */
24
+ curly: "\x1b[4:3m",
25
+ /** Dotted underline */
26
+ dotted: "\x1b[4:4m",
27
+ /** Dashed underline */
28
+ dashed: "\x1b[4:5m",
29
+ /** Reset extended underline (same as none) */
30
+ reset: "\x1b[4:0m",
31
+ } as const
32
+
33
+ // =============================================================================
34
+ // Standard Underline (Fallback)
35
+ // =============================================================================
36
+
37
+ /** Standard underline on (SGR 4) - works on all terminals */
38
+ export const UNDERLINE_STANDARD = "\x1b[4m"
39
+
40
+ /** Standard underline off (SGR 24) */
41
+ export const UNDERLINE_RESET_STANDARD = "\x1b[24m"
42
+
43
+ // =============================================================================
44
+ // Underline Color (SGR 58/59)
45
+ // =============================================================================
46
+
47
+ /**
48
+ * Reset underline color to default (SGR 59)
49
+ */
50
+ export const UNDERLINE_COLOR_RESET = "\x1b[59m"
51
+
52
+ /**
53
+ * Build underline color escape code for RGB values.
54
+ * Format: \x1b[58:2::r:g:bm (SGR 58 with RGB color space)
55
+ */
56
+ export function buildUnderlineColorCode(r: number, g: number, b: number): string {
57
+ return `\x1b[58:2::${r}:${g}:${b}m`
58
+ }
59
+
60
+ // =============================================================================
61
+ // Hyperlinks (OSC 8)
62
+ // =============================================================================
63
+
64
+ /** OSC 8 hyperlink start sequence */
65
+ export const HYPERLINK_START = "\x1b]8;;"
66
+
67
+ /** OSC 8 hyperlink end sequence (ST - String Terminator) */
68
+ export const HYPERLINK_END = "\x1b\\"
69
+
70
+ /**
71
+ * Build a hyperlink escape sequence.
72
+ * Format: \x1b]8;;<url>\x1b\\ <text> \x1b]8;;\x1b\\
73
+ */
74
+ export function buildHyperlink(text: string, url: string): string {
75
+ return `${HYPERLINK_START}${url}${HYPERLINK_END}${text}${HYPERLINK_START}${HYPERLINK_END}`
76
+ }