@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.
- package/package.json +54 -0
- package/src/adapters/canvas-adapter.ts +356 -0
- package/src/adapters/dom-adapter.ts +452 -0
- package/src/adapters/flexily-zero-adapter.ts +368 -0
- package/src/adapters/terminal-adapter.ts +305 -0
- package/src/adapters/yoga-adapter.ts +370 -0
- package/src/ansi/ansi.ts +251 -0
- package/src/ansi/constants.ts +76 -0
- package/src/ansi/detection.ts +441 -0
- package/src/ansi/hyperlink.ts +38 -0
- package/src/ansi/index.ts +201 -0
- package/src/ansi/patch-console.ts +159 -0
- package/src/ansi/sgr-codes.ts +34 -0
- package/src/ansi/storybook.ts +209 -0
- package/src/ansi/term.ts +724 -0
- package/src/ansi/types.ts +202 -0
- package/src/ansi/underline.ts +156 -0
- package/src/ansi/utils.ts +65 -0
- package/src/ansi-sanitize.ts +509 -0
- package/src/app.ts +571 -0
- package/src/bound-term.ts +94 -0
- package/src/bracketed-paste.ts +75 -0
- package/src/browser-renderer.ts +174 -0
- package/src/buffer.ts +1984 -0
- package/src/clipboard.ts +74 -0
- package/src/cursor-query.ts +85 -0
- package/src/device-attrs.ts +228 -0
- package/src/devtools.ts +123 -0
- package/src/dom/index.ts +194 -0
- package/src/errors.ts +39 -0
- package/src/focus-reporting.ts +48 -0
- package/src/hit-registry-core.ts +228 -0
- package/src/hit-registry.ts +176 -0
- package/src/index.ts +458 -0
- package/src/input.ts +119 -0
- package/src/inspector.ts +155 -0
- package/src/kitty-detect.ts +95 -0
- package/src/kitty-manager.ts +160 -0
- package/src/layout-engine.ts +296 -0
- package/src/layout.ts +26 -0
- package/src/measurer.ts +74 -0
- package/src/mode-query.ts +106 -0
- package/src/mouse-events.ts +419 -0
- package/src/mouse.ts +83 -0
- package/src/non-tty.ts +223 -0
- package/src/osc-markers.ts +32 -0
- package/src/osc-palette.ts +169 -0
- package/src/output.ts +406 -0
- package/src/pane-manager.ts +248 -0
- package/src/pipeline/CLAUDE.md +587 -0
- package/src/pipeline/content-phase-adapter.ts +976 -0
- package/src/pipeline/content-phase.ts +1765 -0
- package/src/pipeline/helpers.ts +42 -0
- package/src/pipeline/index.ts +416 -0
- package/src/pipeline/layout-phase.ts +686 -0
- package/src/pipeline/measure-phase.ts +198 -0
- package/src/pipeline/measure-stats.ts +21 -0
- package/src/pipeline/output-phase.ts +2593 -0
- package/src/pipeline/render-box.ts +343 -0
- package/src/pipeline/render-helpers.ts +243 -0
- package/src/pipeline/render-text.ts +1255 -0
- package/src/pipeline/types.ts +161 -0
- package/src/pipeline.ts +29 -0
- package/src/pixel-size.ts +119 -0
- package/src/render-adapter.ts +179 -0
- package/src/renderer.ts +1330 -0
- package/src/runtime/create-app.tsx +1845 -0
- package/src/runtime/create-buffer.ts +18 -0
- package/src/runtime/create-runtime.ts +325 -0
- package/src/runtime/diff.ts +56 -0
- package/src/runtime/event-handlers.ts +254 -0
- package/src/runtime/index.ts +119 -0
- package/src/runtime/keys.ts +8 -0
- package/src/runtime/layout.ts +164 -0
- package/src/runtime/run.tsx +318 -0
- package/src/runtime/term-provider.ts +399 -0
- package/src/runtime/terminal-lifecycle.ts +246 -0
- package/src/runtime/tick.ts +219 -0
- package/src/runtime/types.ts +210 -0
- package/src/scheduler.ts +723 -0
- package/src/screenshot.ts +57 -0
- package/src/scroll-region.ts +69 -0
- package/src/scroll-utils.ts +97 -0
- package/src/term-def.ts +267 -0
- package/src/terminal-caps.ts +5 -0
- package/src/terminal-colors.ts +216 -0
- package/src/termtest.ts +224 -0
- package/src/text-sizing.ts +109 -0
- package/src/toolbelt/index.ts +72 -0
- package/src/unicode.ts +1763 -0
- package/src/xterm/index.ts +491 -0
- package/src/xterm/xterm-provider.ts +204 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flexily Layout Engine Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps Flexily to implement the LayoutEngine interface.
|
|
5
|
+
* Uses the default zero-allocation algorithm from flexily.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ALIGN_AUTO,
|
|
10
|
+
ALIGN_BASELINE,
|
|
11
|
+
ALIGN_CENTER,
|
|
12
|
+
ALIGN_FLEX_END,
|
|
13
|
+
ALIGN_FLEX_START,
|
|
14
|
+
ALIGN_SPACE_AROUND,
|
|
15
|
+
ALIGN_SPACE_BETWEEN,
|
|
16
|
+
ALIGN_SPACE_EVENLY,
|
|
17
|
+
ALIGN_STRETCH,
|
|
18
|
+
DIRECTION_LTR,
|
|
19
|
+
DISPLAY_FLEX,
|
|
20
|
+
DISPLAY_NONE,
|
|
21
|
+
EDGE_ALL,
|
|
22
|
+
EDGE_BOTTOM,
|
|
23
|
+
EDGE_HORIZONTAL,
|
|
24
|
+
EDGE_LEFT,
|
|
25
|
+
EDGE_RIGHT,
|
|
26
|
+
EDGE_TOP,
|
|
27
|
+
EDGE_VERTICAL,
|
|
28
|
+
// Constants
|
|
29
|
+
FLEX_DIRECTION_COLUMN,
|
|
30
|
+
FLEX_DIRECTION_COLUMN_REVERSE,
|
|
31
|
+
FLEX_DIRECTION_ROW,
|
|
32
|
+
FLEX_DIRECTION_ROW_REVERSE,
|
|
33
|
+
Node as FlexilyNode,
|
|
34
|
+
GUTTER_ALL,
|
|
35
|
+
GUTTER_COLUMN,
|
|
36
|
+
GUTTER_ROW,
|
|
37
|
+
JUSTIFY_CENTER,
|
|
38
|
+
JUSTIFY_FLEX_END,
|
|
39
|
+
JUSTIFY_FLEX_START,
|
|
40
|
+
JUSTIFY_SPACE_AROUND,
|
|
41
|
+
JUSTIFY_SPACE_BETWEEN,
|
|
42
|
+
JUSTIFY_SPACE_EVENLY,
|
|
43
|
+
MEASURE_MODE_AT_MOST,
|
|
44
|
+
MEASURE_MODE_EXACTLY,
|
|
45
|
+
MEASURE_MODE_UNDEFINED,
|
|
46
|
+
OVERFLOW_HIDDEN,
|
|
47
|
+
OVERFLOW_SCROLL,
|
|
48
|
+
OVERFLOW_VISIBLE,
|
|
49
|
+
POSITION_TYPE_ABSOLUTE,
|
|
50
|
+
POSITION_TYPE_RELATIVE,
|
|
51
|
+
POSITION_TYPE_STATIC,
|
|
52
|
+
WRAP_NO_WRAP,
|
|
53
|
+
WRAP_WRAP,
|
|
54
|
+
WRAP_WRAP_REVERSE,
|
|
55
|
+
} from "flexily"
|
|
56
|
+
|
|
57
|
+
import type {
|
|
58
|
+
AlignValue,
|
|
59
|
+
DirectionValue,
|
|
60
|
+
DisplayValue,
|
|
61
|
+
EdgeValue,
|
|
62
|
+
FlexDirectionValue,
|
|
63
|
+
GutterValue,
|
|
64
|
+
JustifyValue,
|
|
65
|
+
LayoutConstants,
|
|
66
|
+
LayoutEngine,
|
|
67
|
+
LayoutNode,
|
|
68
|
+
MeasureFunc,
|
|
69
|
+
MeasureMode,
|
|
70
|
+
MeasureModeValue,
|
|
71
|
+
OverflowValue,
|
|
72
|
+
PositionTypeValue,
|
|
73
|
+
WrapValue,
|
|
74
|
+
} from "../layout-engine"
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// Flexily Zero Node Adapter
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Wraps a Flexily zero-alloc node to implement LayoutNode interface.
|
|
82
|
+
* Since Flexily already has a Yoga-compatible API, this is mostly delegation.
|
|
83
|
+
*/
|
|
84
|
+
class FlexilyZeroNodeAdapter implements LayoutNode {
|
|
85
|
+
private node: FlexilyNode
|
|
86
|
+
|
|
87
|
+
constructor(node: FlexilyNode) {
|
|
88
|
+
this.node = node
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Get the underlying Flexily node (for tree operations) */
|
|
92
|
+
getFlexilyNode(): FlexilyNode {
|
|
93
|
+
return this.node
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Tree operations
|
|
97
|
+
insertChild(child: LayoutNode, index: number): void {
|
|
98
|
+
const flexilyChild = (child as FlexilyZeroNodeAdapter).getFlexilyNode()
|
|
99
|
+
this.node.insertChild(flexilyChild, index)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
removeChild(child: LayoutNode): void {
|
|
103
|
+
const flexilyChild = (child as FlexilyZeroNodeAdapter).getFlexilyNode()
|
|
104
|
+
this.node.removeChild(flexilyChild)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
free(): void {
|
|
108
|
+
this.node.free()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Measure function
|
|
112
|
+
setMeasureFunc(measureFunc: MeasureFunc): void {
|
|
113
|
+
this.node.setMeasureFunc((width, widthMode, height, heightMode) => {
|
|
114
|
+
const widthModeStr = this.measureModeToString(widthMode)
|
|
115
|
+
const heightModeStr = this.measureModeToString(heightMode)
|
|
116
|
+
return measureFunc(width, widthModeStr, height, heightModeStr)
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Dirty tracking - forces layout recalculation
|
|
121
|
+
markDirty(): void {
|
|
122
|
+
this.node.markDirty()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private measureModeToString(mode: number): MeasureMode {
|
|
126
|
+
if (mode === MEASURE_MODE_EXACTLY) return "exactly"
|
|
127
|
+
if (mode === MEASURE_MODE_AT_MOST) return "at-most"
|
|
128
|
+
return "undefined"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Dimension setters
|
|
132
|
+
setWidth(value: number): void {
|
|
133
|
+
this.node.setWidth(value)
|
|
134
|
+
}
|
|
135
|
+
setWidthPercent(value: number): void {
|
|
136
|
+
this.node.setWidthPercent(value)
|
|
137
|
+
}
|
|
138
|
+
setWidthAuto(): void {
|
|
139
|
+
this.node.setWidthAuto()
|
|
140
|
+
}
|
|
141
|
+
setHeight(value: number): void {
|
|
142
|
+
this.node.setHeight(value)
|
|
143
|
+
}
|
|
144
|
+
setHeightPercent(value: number): void {
|
|
145
|
+
this.node.setHeightPercent(value)
|
|
146
|
+
}
|
|
147
|
+
setHeightAuto(): void {
|
|
148
|
+
this.node.setHeightAuto()
|
|
149
|
+
}
|
|
150
|
+
setMinWidth(value: number): void {
|
|
151
|
+
this.node.setMinWidth(value)
|
|
152
|
+
}
|
|
153
|
+
setMinWidthPercent(value: number): void {
|
|
154
|
+
this.node.setMinWidthPercent(value)
|
|
155
|
+
}
|
|
156
|
+
setMinHeight(value: number): void {
|
|
157
|
+
this.node.setMinHeight(value)
|
|
158
|
+
}
|
|
159
|
+
setMinHeightPercent(value: number): void {
|
|
160
|
+
this.node.setMinHeightPercent(value)
|
|
161
|
+
}
|
|
162
|
+
setMaxWidth(value: number): void {
|
|
163
|
+
this.node.setMaxWidth(value)
|
|
164
|
+
}
|
|
165
|
+
setMaxWidthPercent(value: number): void {
|
|
166
|
+
this.node.setMaxWidthPercent(value)
|
|
167
|
+
}
|
|
168
|
+
setMaxHeight(value: number): void {
|
|
169
|
+
this.node.setMaxHeight(value)
|
|
170
|
+
}
|
|
171
|
+
setMaxHeightPercent(value: number): void {
|
|
172
|
+
this.node.setMaxHeightPercent(value)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Flex properties
|
|
176
|
+
setFlexGrow(value: number): void {
|
|
177
|
+
this.node.setFlexGrow(value)
|
|
178
|
+
}
|
|
179
|
+
setFlexShrink(value: number): void {
|
|
180
|
+
this.node.setFlexShrink(value)
|
|
181
|
+
}
|
|
182
|
+
setFlexBasis(value: number): void {
|
|
183
|
+
this.node.setFlexBasis(value)
|
|
184
|
+
}
|
|
185
|
+
setFlexBasisPercent(value: number): void {
|
|
186
|
+
this.node.setFlexBasisPercent(value)
|
|
187
|
+
}
|
|
188
|
+
setFlexBasisAuto(): void {
|
|
189
|
+
this.node.setFlexBasisAuto()
|
|
190
|
+
}
|
|
191
|
+
setFlexDirection(direction: number): void {
|
|
192
|
+
this.node.setFlexDirection(direction)
|
|
193
|
+
}
|
|
194
|
+
setFlexWrap(wrap: number): void {
|
|
195
|
+
this.node.setFlexWrap(wrap)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Alignment
|
|
199
|
+
setAlignItems(align: number): void {
|
|
200
|
+
this.node.setAlignItems(align)
|
|
201
|
+
}
|
|
202
|
+
setAlignSelf(align: number): void {
|
|
203
|
+
this.node.setAlignSelf(align)
|
|
204
|
+
}
|
|
205
|
+
setAlignContent(align: number): void {
|
|
206
|
+
this.node.setAlignContent(align)
|
|
207
|
+
}
|
|
208
|
+
setJustifyContent(justify: number): void {
|
|
209
|
+
this.node.setJustifyContent(justify)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Spacing
|
|
213
|
+
setPadding(edge: number, value: number): void {
|
|
214
|
+
this.node.setPadding(edge, value)
|
|
215
|
+
}
|
|
216
|
+
setMargin(edge: number, value: number): void {
|
|
217
|
+
this.node.setMargin(edge, value)
|
|
218
|
+
}
|
|
219
|
+
setBorder(edge: number, value: number): void {
|
|
220
|
+
this.node.setBorder(edge, value)
|
|
221
|
+
}
|
|
222
|
+
setGap(gutter: number, value: number): void {
|
|
223
|
+
this.node.setGap(gutter, value)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Display & Position
|
|
227
|
+
setDisplay(display: number): void {
|
|
228
|
+
this.node.setDisplay(display)
|
|
229
|
+
}
|
|
230
|
+
setPositionType(positionType: number): void {
|
|
231
|
+
this.node.setPositionType(positionType)
|
|
232
|
+
}
|
|
233
|
+
setPosition(edge: number, value: number): void {
|
|
234
|
+
this.node.setPosition(edge, value)
|
|
235
|
+
}
|
|
236
|
+
setPositionPercent(edge: number, value: number): void {
|
|
237
|
+
this.node.setPositionPercent(edge, value)
|
|
238
|
+
}
|
|
239
|
+
setOverflow(overflow: number): void {
|
|
240
|
+
this.node.setOverflow(overflow)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Aspect Ratio
|
|
244
|
+
setAspectRatio(value: number): void {
|
|
245
|
+
this.node.setAspectRatio(value)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Layout calculation
|
|
249
|
+
calculateLayout(width: number, height: number, direction?: number): void {
|
|
250
|
+
this.node.calculateLayout(width, height, direction ?? DIRECTION_LTR)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Layout results
|
|
254
|
+
getComputedLeft(): number {
|
|
255
|
+
return this.node.getComputedLeft()
|
|
256
|
+
}
|
|
257
|
+
getComputedTop(): number {
|
|
258
|
+
return this.node.getComputedTop()
|
|
259
|
+
}
|
|
260
|
+
getComputedWidth(): number {
|
|
261
|
+
return this.node.getComputedWidth()
|
|
262
|
+
}
|
|
263
|
+
getComputedHeight(): number {
|
|
264
|
+
return this.node.getComputedHeight()
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ============================================================================
|
|
269
|
+
// Flexily Zero Layout Engine
|
|
270
|
+
// ============================================================================
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Layout engine implementation using Flexily zero-allocation variant.
|
|
274
|
+
* Optimized for high-frequency layout with reduced GC pressure.
|
|
275
|
+
*/
|
|
276
|
+
export class FlexilyZeroLayoutEngine implements LayoutEngine {
|
|
277
|
+
private _constants: LayoutConstants = {
|
|
278
|
+
// Flex Direction (cast from Flexily's plain numbers to branded types)
|
|
279
|
+
FLEX_DIRECTION_COLUMN: FLEX_DIRECTION_COLUMN as FlexDirectionValue,
|
|
280
|
+
FLEX_DIRECTION_COLUMN_REVERSE: FLEX_DIRECTION_COLUMN_REVERSE as FlexDirectionValue,
|
|
281
|
+
FLEX_DIRECTION_ROW: FLEX_DIRECTION_ROW as FlexDirectionValue,
|
|
282
|
+
FLEX_DIRECTION_ROW_REVERSE: FLEX_DIRECTION_ROW_REVERSE as FlexDirectionValue,
|
|
283
|
+
|
|
284
|
+
// Wrap
|
|
285
|
+
WRAP_NO_WRAP: WRAP_NO_WRAP as WrapValue,
|
|
286
|
+
WRAP_WRAP: WRAP_WRAP as WrapValue,
|
|
287
|
+
WRAP_WRAP_REVERSE: WRAP_WRAP_REVERSE as WrapValue,
|
|
288
|
+
|
|
289
|
+
// Align
|
|
290
|
+
ALIGN_AUTO: ALIGN_AUTO as AlignValue,
|
|
291
|
+
ALIGN_FLEX_START: ALIGN_FLEX_START as AlignValue,
|
|
292
|
+
ALIGN_CENTER: ALIGN_CENTER as AlignValue,
|
|
293
|
+
ALIGN_FLEX_END: ALIGN_FLEX_END as AlignValue,
|
|
294
|
+
ALIGN_STRETCH: ALIGN_STRETCH as AlignValue,
|
|
295
|
+
ALIGN_BASELINE: ALIGN_BASELINE as AlignValue,
|
|
296
|
+
ALIGN_SPACE_BETWEEN: ALIGN_SPACE_BETWEEN as AlignValue,
|
|
297
|
+
ALIGN_SPACE_AROUND: ALIGN_SPACE_AROUND as AlignValue,
|
|
298
|
+
ALIGN_SPACE_EVENLY: ALIGN_SPACE_EVENLY as AlignValue,
|
|
299
|
+
|
|
300
|
+
// Justify
|
|
301
|
+
JUSTIFY_FLEX_START: JUSTIFY_FLEX_START as JustifyValue,
|
|
302
|
+
JUSTIFY_CENTER: JUSTIFY_CENTER as JustifyValue,
|
|
303
|
+
JUSTIFY_FLEX_END: JUSTIFY_FLEX_END as JustifyValue,
|
|
304
|
+
JUSTIFY_SPACE_BETWEEN: JUSTIFY_SPACE_BETWEEN as JustifyValue,
|
|
305
|
+
JUSTIFY_SPACE_AROUND: JUSTIFY_SPACE_AROUND as JustifyValue,
|
|
306
|
+
JUSTIFY_SPACE_EVENLY: JUSTIFY_SPACE_EVENLY as JustifyValue,
|
|
307
|
+
|
|
308
|
+
// Edge
|
|
309
|
+
EDGE_LEFT: EDGE_LEFT as EdgeValue,
|
|
310
|
+
EDGE_TOP: EDGE_TOP as EdgeValue,
|
|
311
|
+
EDGE_RIGHT: EDGE_RIGHT as EdgeValue,
|
|
312
|
+
EDGE_BOTTOM: EDGE_BOTTOM as EdgeValue,
|
|
313
|
+
EDGE_HORIZONTAL: EDGE_HORIZONTAL as EdgeValue,
|
|
314
|
+
EDGE_VERTICAL: EDGE_VERTICAL as EdgeValue,
|
|
315
|
+
EDGE_ALL: EDGE_ALL as EdgeValue,
|
|
316
|
+
|
|
317
|
+
// Gutter
|
|
318
|
+
GUTTER_COLUMN: GUTTER_COLUMN as GutterValue,
|
|
319
|
+
GUTTER_ROW: GUTTER_ROW as GutterValue,
|
|
320
|
+
GUTTER_ALL: GUTTER_ALL as GutterValue,
|
|
321
|
+
|
|
322
|
+
// Display
|
|
323
|
+
DISPLAY_FLEX: DISPLAY_FLEX as DisplayValue,
|
|
324
|
+
DISPLAY_NONE: DISPLAY_NONE as DisplayValue,
|
|
325
|
+
|
|
326
|
+
// Position Type
|
|
327
|
+
POSITION_TYPE_STATIC: POSITION_TYPE_STATIC as PositionTypeValue,
|
|
328
|
+
POSITION_TYPE_RELATIVE: POSITION_TYPE_RELATIVE as PositionTypeValue,
|
|
329
|
+
POSITION_TYPE_ABSOLUTE: POSITION_TYPE_ABSOLUTE as PositionTypeValue,
|
|
330
|
+
|
|
331
|
+
// Overflow
|
|
332
|
+
OVERFLOW_VISIBLE: OVERFLOW_VISIBLE as OverflowValue,
|
|
333
|
+
OVERFLOW_HIDDEN: OVERFLOW_HIDDEN as OverflowValue,
|
|
334
|
+
OVERFLOW_SCROLL: OVERFLOW_SCROLL as OverflowValue,
|
|
335
|
+
|
|
336
|
+
// Direction
|
|
337
|
+
DIRECTION_LTR: DIRECTION_LTR as DirectionValue,
|
|
338
|
+
|
|
339
|
+
// Measure Mode
|
|
340
|
+
MEASURE_MODE_UNDEFINED: MEASURE_MODE_UNDEFINED as MeasureModeValue,
|
|
341
|
+
MEASURE_MODE_EXACTLY: MEASURE_MODE_EXACTLY as MeasureModeValue,
|
|
342
|
+
MEASURE_MODE_AT_MOST: MEASURE_MODE_AT_MOST as MeasureModeValue,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
createNode(): LayoutNode {
|
|
346
|
+
return new FlexilyZeroNodeAdapter(FlexilyNode.create())
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
get constants(): LayoutConstants {
|
|
350
|
+
return this._constants
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
get name(): string {
|
|
354
|
+
return "flexily-zero"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// Initialization Helper
|
|
360
|
+
// ============================================================================
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Create a Flexily zero-allocation layout engine.
|
|
364
|
+
* Unlike Yoga, Flexily doesn't require async initialization.
|
|
365
|
+
*/
|
|
366
|
+
export function createFlexilyZeroEngine(): FlexilyZeroLayoutEngine {
|
|
367
|
+
return new FlexilyZeroLayoutEngine()
|
|
368
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Render Adapter
|
|
3
|
+
*
|
|
4
|
+
* Implements the RenderAdapter interface for terminal output.
|
|
5
|
+
* Uses character cells as units, ANSI codes for styling.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type Color, TerminalBuffer } from "../buffer"
|
|
9
|
+
import { outputPhase } from "../pipeline/output-phase"
|
|
10
|
+
import type {
|
|
11
|
+
BorderChars,
|
|
12
|
+
RenderAdapter,
|
|
13
|
+
RenderBuffer,
|
|
14
|
+
RenderStyle,
|
|
15
|
+
TextMeasureResult,
|
|
16
|
+
TextMeasureStyle,
|
|
17
|
+
TextMeasurer,
|
|
18
|
+
} from "../render-adapter"
|
|
19
|
+
import { type Measurer, displayWidth } from "../unicode"
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Border Characters
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const BORDER_CHARS: Record<string, BorderChars> = {
|
|
26
|
+
single: {
|
|
27
|
+
topLeft: "┌",
|
|
28
|
+
topRight: "┐",
|
|
29
|
+
bottomLeft: "└",
|
|
30
|
+
bottomRight: "┘",
|
|
31
|
+
horizontal: "─",
|
|
32
|
+
vertical: "│",
|
|
33
|
+
},
|
|
34
|
+
double: {
|
|
35
|
+
topLeft: "╔",
|
|
36
|
+
topRight: "╗",
|
|
37
|
+
bottomLeft: "╚",
|
|
38
|
+
bottomRight: "╝",
|
|
39
|
+
horizontal: "═",
|
|
40
|
+
vertical: "║",
|
|
41
|
+
},
|
|
42
|
+
round: {
|
|
43
|
+
topLeft: "╭",
|
|
44
|
+
topRight: "╮",
|
|
45
|
+
bottomLeft: "╰",
|
|
46
|
+
bottomRight: "╯",
|
|
47
|
+
horizontal: "─",
|
|
48
|
+
vertical: "│",
|
|
49
|
+
},
|
|
50
|
+
bold: {
|
|
51
|
+
topLeft: "┏",
|
|
52
|
+
topRight: "┓",
|
|
53
|
+
bottomLeft: "┗",
|
|
54
|
+
bottomRight: "┛",
|
|
55
|
+
horizontal: "━",
|
|
56
|
+
vertical: "┃",
|
|
57
|
+
},
|
|
58
|
+
singleDouble: {
|
|
59
|
+
topLeft: "╓",
|
|
60
|
+
topRight: "╖",
|
|
61
|
+
bottomLeft: "╙",
|
|
62
|
+
bottomRight: "╜",
|
|
63
|
+
horizontal: "─",
|
|
64
|
+
vertical: "║",
|
|
65
|
+
},
|
|
66
|
+
doubleSingle: {
|
|
67
|
+
topLeft: "╒",
|
|
68
|
+
topRight: "╕",
|
|
69
|
+
bottomLeft: "╘",
|
|
70
|
+
bottomRight: "╛",
|
|
71
|
+
horizontal: "═",
|
|
72
|
+
vertical: "│",
|
|
73
|
+
},
|
|
74
|
+
classic: {
|
|
75
|
+
topLeft: "+",
|
|
76
|
+
topRight: "+",
|
|
77
|
+
bottomLeft: "+",
|
|
78
|
+
bottomRight: "+",
|
|
79
|
+
horizontal: "-",
|
|
80
|
+
vertical: "|",
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Terminal Measurer
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
/** Create a terminal text measurer, optionally using an explicit width measurer. */
|
|
89
|
+
export function createTerminalMeasurer(measurer?: Measurer): TextMeasurer {
|
|
90
|
+
const dw = measurer ? measurer.displayWidth.bind(measurer) : displayWidth
|
|
91
|
+
return {
|
|
92
|
+
measureText(text: string, _style?: TextMeasureStyle): TextMeasureResult {
|
|
93
|
+
return { width: dw(text), height: 1 }
|
|
94
|
+
},
|
|
95
|
+
getLineHeight(_style?: TextMeasureStyle): number {
|
|
96
|
+
return 1
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Default terminal measurer (uses module-level displayWidth / scoped measurer). */
|
|
102
|
+
export const terminalMeasurer: TextMeasurer = createTerminalMeasurer()
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// Terminal Render Buffer
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Wraps TerminalBuffer to implement the RenderBuffer interface.
|
|
110
|
+
*/
|
|
111
|
+
export class TerminalRenderBuffer implements RenderBuffer {
|
|
112
|
+
private buffer: TerminalBuffer
|
|
113
|
+
private dw: (text: string) => number
|
|
114
|
+
|
|
115
|
+
constructor(width: number, height: number, measurer?: Measurer) {
|
|
116
|
+
this.buffer = new TerminalBuffer(width, height)
|
|
117
|
+
this.dw = measurer ? measurer.displayWidth.bind(measurer) : displayWidth
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get width(): number {
|
|
121
|
+
return this.buffer.width
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get height(): number {
|
|
125
|
+
return this.buffer.height
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the underlying TerminalBuffer for output phase.
|
|
130
|
+
*/
|
|
131
|
+
getTerminalBuffer(): TerminalBuffer {
|
|
132
|
+
return this.buffer
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fillRect(x: number, y: number, width: number, height: number, style: RenderStyle): void {
|
|
136
|
+
const cellStyle = this.convertStyle(style)
|
|
137
|
+
for (let row = y; row < y + height; row++) {
|
|
138
|
+
for (let col = x; col < x + width; col++) {
|
|
139
|
+
if (this.buffer.inBounds(col, row)) {
|
|
140
|
+
this.buffer.setCell(col, row, {
|
|
141
|
+
char: " ",
|
|
142
|
+
...cellStyle,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
drawText(x: number, y: number, text: string, style: RenderStyle): void {
|
|
150
|
+
const cellStyle = this.convertStyle(style)
|
|
151
|
+
let col = x
|
|
152
|
+
for (const char of text) {
|
|
153
|
+
if (!this.buffer.inBounds(col, y)) break
|
|
154
|
+
const charWidth = this.dw(char)
|
|
155
|
+
this.buffer.setCell(col, y, {
|
|
156
|
+
char,
|
|
157
|
+
...cellStyle,
|
|
158
|
+
wide: charWidth > 1,
|
|
159
|
+
})
|
|
160
|
+
// Mark continuation cells for wide characters
|
|
161
|
+
for (let i = 1; i < charWidth; i++) {
|
|
162
|
+
if (this.buffer.inBounds(col + i, y)) {
|
|
163
|
+
this.buffer.setCell(col + i, y, {
|
|
164
|
+
char: "",
|
|
165
|
+
...cellStyle,
|
|
166
|
+
continuation: true,
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
col += charWidth
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
drawChar(x: number, y: number, char: string, style: RenderStyle): void {
|
|
175
|
+
if (this.buffer.inBounds(x, y)) {
|
|
176
|
+
this.buffer.setCell(x, y, {
|
|
177
|
+
char,
|
|
178
|
+
...this.convertStyle(style),
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
inBounds(x: number, y: number): boolean {
|
|
184
|
+
return this.buffer.inBounds(x, y)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private convertStyle(style: RenderStyle): {
|
|
188
|
+
fg: Color
|
|
189
|
+
bg: Color
|
|
190
|
+
underlineColor: Color
|
|
191
|
+
attrs: {
|
|
192
|
+
bold?: boolean
|
|
193
|
+
dim?: boolean
|
|
194
|
+
italic?: boolean
|
|
195
|
+
underline?: boolean
|
|
196
|
+
underlineStyle?: "single" | "double" | "curly" | "dotted" | "dashed" | false
|
|
197
|
+
strikethrough?: boolean
|
|
198
|
+
inverse?: boolean
|
|
199
|
+
}
|
|
200
|
+
} {
|
|
201
|
+
return {
|
|
202
|
+
fg: this.parseColor(style.fg),
|
|
203
|
+
bg: this.parseColor(style.bg),
|
|
204
|
+
underlineColor: this.parseColor(style.attrs?.underlineColor),
|
|
205
|
+
attrs: {
|
|
206
|
+
bold: style.attrs?.bold,
|
|
207
|
+
dim: style.attrs?.dim,
|
|
208
|
+
italic: style.attrs?.italic,
|
|
209
|
+
underline: style.attrs?.underline,
|
|
210
|
+
underlineStyle: style.attrs?.underlineStyle,
|
|
211
|
+
strikethrough: style.attrs?.strikethrough,
|
|
212
|
+
inverse: style.attrs?.inverse,
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Parse a color string to the Color type used by TerminalBuffer.
|
|
219
|
+
*/
|
|
220
|
+
private parseColor(color: string | undefined): Color {
|
|
221
|
+
if (!color) return null
|
|
222
|
+
|
|
223
|
+
// Hex color
|
|
224
|
+
if (color.startsWith("#")) {
|
|
225
|
+
const hex = color.slice(1)
|
|
226
|
+
if (hex.length === 6) {
|
|
227
|
+
return {
|
|
228
|
+
r: Number.parseInt(hex.slice(0, 2), 16),
|
|
229
|
+
g: Number.parseInt(hex.slice(2, 4), 16),
|
|
230
|
+
b: Number.parseInt(hex.slice(4, 6), 16),
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (hex.length === 3) {
|
|
234
|
+
return {
|
|
235
|
+
r: Number.parseInt(hex[0]! + hex[0]!, 16),
|
|
236
|
+
g: Number.parseInt(hex[1]! + hex[1]!, 16),
|
|
237
|
+
b: Number.parseInt(hex[2]! + hex[2]!, 16),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// RGB color
|
|
243
|
+
const rgbMatch = color.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
|
|
244
|
+
if (rgbMatch) {
|
|
245
|
+
return {
|
|
246
|
+
r: Number.parseInt(rgbMatch[1]!, 10),
|
|
247
|
+
g: Number.parseInt(rgbMatch[2]!, 10),
|
|
248
|
+
b: Number.parseInt(rgbMatch[3]!, 10),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Named ANSI colors - map to 256-color indices
|
|
253
|
+
const namedColors: Record<string, number> = {
|
|
254
|
+
black: 0,
|
|
255
|
+
red: 1,
|
|
256
|
+
green: 2,
|
|
257
|
+
yellow: 3,
|
|
258
|
+
blue: 4,
|
|
259
|
+
magenta: 5,
|
|
260
|
+
cyan: 6,
|
|
261
|
+
white: 7,
|
|
262
|
+
gray: 8,
|
|
263
|
+
grey: 8,
|
|
264
|
+
brightblack: 8,
|
|
265
|
+
brightred: 9,
|
|
266
|
+
brightgreen: 10,
|
|
267
|
+
brightyellow: 11,
|
|
268
|
+
brightblue: 12,
|
|
269
|
+
brightmagenta: 13,
|
|
270
|
+
brightcyan: 14,
|
|
271
|
+
brightwhite: 15,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const normalized = color.toLowerCase().replace(/[^a-z]/g, "")
|
|
275
|
+
const index = namedColors[normalized]
|
|
276
|
+
if (index !== undefined) {
|
|
277
|
+
return index
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return null
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// Terminal Adapter
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
export const terminalAdapter: RenderAdapter = {
|
|
289
|
+
name: "terminal",
|
|
290
|
+
measurer: terminalMeasurer,
|
|
291
|
+
|
|
292
|
+
createBuffer(width: number, height: number): RenderBuffer {
|
|
293
|
+
return new TerminalRenderBuffer(width, height)
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
flush(buffer: RenderBuffer, prevBuffer: RenderBuffer | null): string {
|
|
297
|
+
const termBuffer = (buffer as TerminalRenderBuffer).getTerminalBuffer()
|
|
298
|
+
const prevTermBuffer = prevBuffer ? (prevBuffer as TerminalRenderBuffer).getTerminalBuffer() : null
|
|
299
|
+
return outputPhase(prevTermBuffer, termBuffer)
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
getBorderChars(style: string): BorderChars {
|
|
303
|
+
return BORDER_CHARS[style] ?? BORDER_CHARS.single!
|
|
304
|
+
},
|
|
305
|
+
}
|