@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,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 1: Measure Phase
|
|
3
|
+
*
|
|
4
|
+
* Handle fit-content nodes by measuring their intrinsic content size.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BoxProps, TeaNode, TextProps } from "@silvery/tea/types"
|
|
8
|
+
import { displayWidthAnsi, wrapText } from "../unicode"
|
|
9
|
+
import { getBorderSize, getPadding } from "./helpers"
|
|
10
|
+
import type { PipelineContext } from "./types"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handle fit-content nodes by measuring their intrinsic content size.
|
|
14
|
+
*
|
|
15
|
+
* Traverses the tree and for any node with width="fit-content" or
|
|
16
|
+
* height="fit-content", measures the content and sets the Yoga constraint.
|
|
17
|
+
*/
|
|
18
|
+
export function measurePhase(root: TeaNode, ctx?: PipelineContext): void {
|
|
19
|
+
traverseTree(root, (node) => {
|
|
20
|
+
// Skip nodes without Yoga (raw text nodes)
|
|
21
|
+
if (!node.layoutNode) return
|
|
22
|
+
|
|
23
|
+
const props = node.props as BoxProps
|
|
24
|
+
|
|
25
|
+
if (props.width === "fit-content" || props.height === "fit-content") {
|
|
26
|
+
// When height="fit-content" but width is fixed, pass the available width
|
|
27
|
+
// so text nodes can wrap and compute correct intrinsic height.
|
|
28
|
+
let availableWidth: number | undefined
|
|
29
|
+
if (props.height === "fit-content" && props.width !== "fit-content" && typeof props.width === "number") {
|
|
30
|
+
// Subtract padding and border from the fixed width to get content area width
|
|
31
|
+
const padding = getPadding(props)
|
|
32
|
+
availableWidth = props.width - padding.left - padding.right
|
|
33
|
+
if (props.borderStyle) {
|
|
34
|
+
const border = getBorderSize(props)
|
|
35
|
+
availableWidth -= border.left + border.right
|
|
36
|
+
}
|
|
37
|
+
if (availableWidth < 1) availableWidth = 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const intrinsicSize = measureIntrinsicSize(node, ctx, availableWidth)
|
|
41
|
+
|
|
42
|
+
if (props.width === "fit-content") {
|
|
43
|
+
node.layoutNode.setWidth(intrinsicSize.width)
|
|
44
|
+
}
|
|
45
|
+
if (props.height === "fit-content") {
|
|
46
|
+
node.layoutNode.setHeight(intrinsicSize.height)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Measure the intrinsic size of a node's content.
|
|
54
|
+
*
|
|
55
|
+
* For text nodes: measures the text width and line count.
|
|
56
|
+
* For box nodes: recursively measures children based on flex direction.
|
|
57
|
+
*
|
|
58
|
+
* @param availableWidth - When set, text nodes wrap at this width for height calculation.
|
|
59
|
+
* Used when a container has fixed width + fit-content height.
|
|
60
|
+
*/
|
|
61
|
+
function measureIntrinsicSize(
|
|
62
|
+
node: TeaNode,
|
|
63
|
+
ctx?: PipelineContext,
|
|
64
|
+
availableWidth?: number,
|
|
65
|
+
): {
|
|
66
|
+
width: number
|
|
67
|
+
height: number
|
|
68
|
+
} {
|
|
69
|
+
const props = node.props as BoxProps
|
|
70
|
+
|
|
71
|
+
// display="none" nodes have 0x0 intrinsic size
|
|
72
|
+
if (props.display === "none") {
|
|
73
|
+
return { width: 0, height: 0 }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (node.type === "silvery-text") {
|
|
77
|
+
const textProps = props as TextProps
|
|
78
|
+
const text = collectTextContent(node)
|
|
79
|
+
|
|
80
|
+
// Apply internal_transform if present (used by Transform component).
|
|
81
|
+
// The transform is applied per-line, which can change the width.
|
|
82
|
+
const transform = textProps.internal_transform
|
|
83
|
+
let lines: string[]
|
|
84
|
+
|
|
85
|
+
if (availableWidth !== undefined && availableWidth > 0 && isWrapEnabled(textProps.wrap)) {
|
|
86
|
+
// Wrap text at available width to compute correct height
|
|
87
|
+
lines = ctx ? ctx.measurer.wrapText(text, availableWidth, true, true) : wrapText(text, availableWidth, true, true)
|
|
88
|
+
} else {
|
|
89
|
+
lines = text.split("\n")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (transform) {
|
|
93
|
+
lines = lines.map((line, index) => transform(line, index))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const width = Math.max(...lines.map((line) => getTextWidth(line, ctx)))
|
|
97
|
+
return {
|
|
98
|
+
width,
|
|
99
|
+
height: lines.length,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// For boxes, measure based on flex direction
|
|
104
|
+
const isRow = props.flexDirection === "row" || props.flexDirection === "row-reverse"
|
|
105
|
+
|
|
106
|
+
let width = 0
|
|
107
|
+
let height = 0
|
|
108
|
+
|
|
109
|
+
let childCount = 0
|
|
110
|
+
for (const child of node.children) {
|
|
111
|
+
const childSize = measureIntrinsicSize(child, ctx, availableWidth)
|
|
112
|
+
childCount++
|
|
113
|
+
|
|
114
|
+
if (isRow) {
|
|
115
|
+
width += childSize.width
|
|
116
|
+
height = Math.max(height, childSize.height)
|
|
117
|
+
} else {
|
|
118
|
+
width = Math.max(width, childSize.width)
|
|
119
|
+
height += childSize.height
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Add gap between children
|
|
124
|
+
const gap = (props.gap as number) ?? 0
|
|
125
|
+
if (gap > 0 && childCount > 1) {
|
|
126
|
+
const totalGap = gap * (childCount - 1)
|
|
127
|
+
if (isRow) {
|
|
128
|
+
width += totalGap
|
|
129
|
+
} else {
|
|
130
|
+
height += totalGap
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add padding
|
|
135
|
+
const padding = getPadding(props)
|
|
136
|
+
width += padding.left + padding.right
|
|
137
|
+
height += padding.top + padding.bottom
|
|
138
|
+
|
|
139
|
+
// Add border
|
|
140
|
+
if (props.borderStyle) {
|
|
141
|
+
const border = getBorderSize(props)
|
|
142
|
+
width += border.left + border.right
|
|
143
|
+
height += border.top + border.bottom
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { width, height }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if text wrapping is enabled for a text node.
|
|
151
|
+
*/
|
|
152
|
+
function isWrapEnabled(wrap: TextProps["wrap"]): boolean {
|
|
153
|
+
return wrap === "wrap" || wrap === true || wrap === undefined
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Traverse tree in depth-first order.
|
|
158
|
+
*/
|
|
159
|
+
function traverseTree(node: TeaNode, callback: (node: TeaNode) => void): void {
|
|
160
|
+
callback(node)
|
|
161
|
+
for (const child of node.children) {
|
|
162
|
+
traverseTree(child, callback)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get text display width (accounting for wide characters and ANSI codes).
|
|
168
|
+
* Uses ANSI-aware width calculation to handle styled text.
|
|
169
|
+
*/
|
|
170
|
+
function getTextWidth(text: string, ctx?: PipelineContext): number {
|
|
171
|
+
if (ctx) return ctx.measurer.displayWidthAnsi(text)
|
|
172
|
+
return displayWidthAnsi(text)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Collect text content from a node and its children.
|
|
177
|
+
* Used for measuring Text nodes that have nested Text children.
|
|
178
|
+
* Applies internal_transform from child nodes to match render-text.ts behavior.
|
|
179
|
+
*/
|
|
180
|
+
function collectTextContent(node: TeaNode): string {
|
|
181
|
+
if (node.textContent !== undefined) {
|
|
182
|
+
return node.textContent
|
|
183
|
+
}
|
|
184
|
+
let result = ""
|
|
185
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
186
|
+
const child = node.children[i]!
|
|
187
|
+
let childText = collectTextContent(child)
|
|
188
|
+
// Apply internal_transform from virtual text nodes (nested Transform components).
|
|
189
|
+
// Matches render-text.ts collectPlainText: transform is applied to the full
|
|
190
|
+
// concatenated text of the child, with index = child position in parent's children array.
|
|
191
|
+
const childTransform = (child.props as TextProps).internal_transform
|
|
192
|
+
if (childTransform && childText.length > 0) {
|
|
193
|
+
childText = childTransform(childText, i)
|
|
194
|
+
}
|
|
195
|
+
result += childText
|
|
196
|
+
}
|
|
197
|
+
return result
|
|
198
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profiling counters for measure function performance analysis (dev only).
|
|
3
|
+
*
|
|
4
|
+
* Shared between @silvery/react/reconciler/nodes (where measure happens)
|
|
5
|
+
* and @silvery/term/pipeline/layout-phase (where stats are logged).
|
|
6
|
+
*
|
|
7
|
+
* Lives in @silvery/term to keep the @silvery/term barrel React-free.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const measureStats = {
|
|
11
|
+
calls: 0,
|
|
12
|
+
cacheHits: 0,
|
|
13
|
+
textCollects: 0,
|
|
14
|
+
displayWidthCalls: 0,
|
|
15
|
+
reset() {
|
|
16
|
+
this.calls = 0
|
|
17
|
+
this.cacheHits = 0
|
|
18
|
+
this.textCollects = 0
|
|
19
|
+
this.displayWidthCalls = 0
|
|
20
|
+
},
|
|
21
|
+
}
|