@fleetagent/pi-tui 0.1.9 → 0.2.1
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/README.md +47 -7
- package/dist/autocomplete.d.ts +17 -23
- package/dist/autocomplete.d.ts.map +1 -1
- package/dist/autocomplete.js +129 -195
- package/dist/autocomplete.js.map +1 -1
- package/dist/components/alt-screen-flash.d.ts +13 -0
- package/dist/components/alt-screen-flash.d.ts.map +1 -0
- package/dist/components/alt-screen-flash.js +37 -0
- package/dist/components/alt-screen-flash.js.map +1 -0
- package/dist/components/editor.d.ts +47 -4
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +615 -585
- package/dist/components/editor.js.map +1 -1
- package/dist/components/h-stack.d.ts +7 -0
- package/dist/components/h-stack.d.ts.map +1 -0
- package/dist/components/h-stack.js +43 -0
- package/dist/components/h-stack.js.map +1 -0
- package/dist/components/image.d.ts +4 -0
- package/dist/components/image.d.ts.map +1 -1
- package/dist/components/image.js +38 -51
- package/dist/components/image.js.map +1 -1
- package/dist/components/input.d.ts +9 -0
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +121 -147
- package/dist/components/input.js.map +1 -1
- package/dist/components/markdown.d.ts +34 -0
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +491 -429
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/scroll-view.d.ts +53 -0
- package/dist/components/scroll-view.d.ts.map +1 -0
- package/dist/components/scroll-view.js +173 -0
- package/dist/components/scroll-view.js.map +1 -0
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts +2 -0
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +31 -29
- package/dist/components/settings-list.js.map +1 -1
- package/dist/components/stack.d.ts +32 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +122 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/v-stack.d.ts +8 -0
- package/dist/components/v-stack.d.ts.map +1 -0
- package/dist/components/v-stack.js +27 -0
- package/dist/components/v-stack.js.map +1 -0
- package/dist/fuzzy.d.ts.map +1 -1
- package/dist/fuzzy.js +37 -46
- package/dist/fuzzy.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/keybindings.d.ts +54 -4
- package/dist/keybindings.d.ts.map +1 -1
- package/dist/keybindings.js +39 -4
- package/dist/keybindings.js.map +1 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +359 -479
- package/dist/keys.js.map +1 -1
- package/dist/kill-ring.d.ts +6 -4
- package/dist/kill-ring.d.ts.map +1 -1
- package/dist/kill-ring.js +0 -7
- package/dist/kill-ring.js.map +1 -1
- package/dist/latex.d.ts +10 -0
- package/dist/latex.d.ts.map +1 -0
- package/dist/latex.js +1406 -0
- package/dist/latex.js.map +1 -0
- package/dist/layout-node.d.ts +43 -0
- package/dist/layout-node.d.ts.map +1 -0
- package/dist/layout-node.js +6 -0
- package/dist/layout-node.js.map +1 -0
- package/dist/layout.d.ts +40 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +386 -0
- package/dist/layout.js.map +1 -0
- package/dist/stdin-buffer.d.ts +8 -0
- package/dist/stdin-buffer.d.ts.map +1 -1
- package/dist/stdin-buffer.js +116 -159
- package/dist/stdin-buffer.js.map +1 -1
- package/dist/terminal-image.d.ts +41 -19
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +171 -32
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +3 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +27 -23
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +114 -0
- package/dist/tui-alt-screen.d.ts.map +1 -0
- package/dist/tui-alt-screen.js +950 -0
- package/dist/tui-alt-screen.js.map +1 -0
- package/dist/tui-main-screen.d.ts +52 -0
- package/dist/tui-main-screen.d.ts.map +1 -0
- package/dist/tui-main-screen.js +511 -0
- package/dist/tui-main-screen.js.map +1 -0
- package/dist/tui.d.ts +112 -44
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +489 -699
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +29 -17
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +605 -528
- package/dist/utils.js.map +1 -1
- package/dist/word-navigation.d.ts.map +1 -1
- package/dist/word-navigation.js +37 -39
- package/dist/word-navigation.js.map +1 -1
- package/package.json +1 -1
package/dist/layout.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { allocateStackSizes, visibleStackEntries } from "./components/stack.js";
|
|
2
|
+
import { getLayoutNode } from "./layout-node.js";
|
|
3
|
+
import { cropKittyImageLine, getKittyImageMetadata, isImageLine } from "./terminal-image.js";
|
|
4
|
+
import { CURSOR_MARKER, compositeTuiLine } from "./tui.js";
|
|
5
|
+
import { extractAnsiCode, getGraphemeCellRange, sliceByColumn, visibleWidth } from "./utils.js";
|
|
6
|
+
const OSC133_ZONE_PREFIX = /^(?:\x1b\]133;[ABC](?:\x07|\x1b\\))+/;
|
|
7
|
+
function intersect(a, b) {
|
|
8
|
+
const x = Math.max(a.x, b.x);
|
|
9
|
+
const y = Math.max(a.y, b.y);
|
|
10
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
11
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
12
|
+
return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) };
|
|
13
|
+
}
|
|
14
|
+
function renderCached(context, component, width) {
|
|
15
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
16
|
+
let widths = context.renderCache.get(component);
|
|
17
|
+
if (!widths) {
|
|
18
|
+
widths = new Map();
|
|
19
|
+
context.renderCache.set(component, widths);
|
|
20
|
+
}
|
|
21
|
+
let lines = widths.get(safeWidth);
|
|
22
|
+
if (!lines) {
|
|
23
|
+
lines = component.render(safeWidth);
|
|
24
|
+
widths.set(safeWidth, lines);
|
|
25
|
+
}
|
|
26
|
+
return lines;
|
|
27
|
+
}
|
|
28
|
+
function measureHeight(context, component, width) {
|
|
29
|
+
return renderCached(context, component, width).length;
|
|
30
|
+
}
|
|
31
|
+
function measureWidth(context, component, width) {
|
|
32
|
+
return renderCached(context, component, width).reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
|
|
33
|
+
}
|
|
34
|
+
function withParent(box, parent) {
|
|
35
|
+
box.parent = parent;
|
|
36
|
+
return box;
|
|
37
|
+
}
|
|
38
|
+
function translateBox(box, deltaY) {
|
|
39
|
+
box.rect.y += deltaY;
|
|
40
|
+
for (const child of box.children)
|
|
41
|
+
translateBox(child, deltaY);
|
|
42
|
+
}
|
|
43
|
+
function updateClips(box, parentClip) {
|
|
44
|
+
box.clip = intersect(parentClip, box.rect);
|
|
45
|
+
for (const child of box.children)
|
|
46
|
+
updateClips(child, box.clip);
|
|
47
|
+
}
|
|
48
|
+
function layoutLeafComponent(request) {
|
|
49
|
+
const { context, component, x, y, width, height, clip } = request;
|
|
50
|
+
const lines = renderCached(context, component, width);
|
|
51
|
+
const allocatedHeight = height === undefined ? lines.length : Math.max(0, Math.floor(height));
|
|
52
|
+
let lineOffset = 0;
|
|
53
|
+
if (lines.length > allocatedHeight && allocatedHeight > 0) {
|
|
54
|
+
const cursorLine = lines.findIndex((line) => line.includes(CURSOR_MARKER));
|
|
55
|
+
if (cursorLine >= allocatedHeight)
|
|
56
|
+
lineOffset = cursorLine - allocatedHeight + 1;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
component,
|
|
60
|
+
rect: { x, y, width, height: allocatedHeight },
|
|
61
|
+
clip: intersect(clip, { x, y, width, height: allocatedHeight }),
|
|
62
|
+
children: [],
|
|
63
|
+
lines,
|
|
64
|
+
lineOffset,
|
|
65
|
+
layer: 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function layoutScrollComponent(request, node) {
|
|
69
|
+
const { context, component, x, y, width, height, clip } = request;
|
|
70
|
+
const previousScrollTop = node.state.scrollTop;
|
|
71
|
+
const contentWidth = node.state.getContentWidth(width);
|
|
72
|
+
const childBox = layoutComponent(context, node.component, x, y - previousScrollTop, contentWidth, undefined, clip);
|
|
73
|
+
const contentHeight = childBox.rect.height;
|
|
74
|
+
const viewportHeight = height === undefined ? contentHeight : Math.max(0, Math.floor(height));
|
|
75
|
+
node.state.updateLayout(contentHeight, viewportHeight, context.requestRender);
|
|
76
|
+
translateBox(childBox, previousScrollTop - node.state.scrollTop);
|
|
77
|
+
const scrollView = node.state;
|
|
78
|
+
if (node.state.primary || !context.primaryScrollView)
|
|
79
|
+
context.primaryScrollView = scrollView;
|
|
80
|
+
const rect = { x, y, width, height: viewportHeight };
|
|
81
|
+
const childClip = intersect(clip, rect);
|
|
82
|
+
const box = {
|
|
83
|
+
component,
|
|
84
|
+
rect,
|
|
85
|
+
clip: childClip,
|
|
86
|
+
children: [childBox],
|
|
87
|
+
scrollView,
|
|
88
|
+
scrollContentLines: renderCached(context, node.component, contentWidth),
|
|
89
|
+
layer: 0,
|
|
90
|
+
};
|
|
91
|
+
childBox.parent = box;
|
|
92
|
+
updateClips(childBox, childClip);
|
|
93
|
+
return box;
|
|
94
|
+
}
|
|
95
|
+
function layoutVerticalStack(request, node) {
|
|
96
|
+
const { context, component, x, y, width, height, clip } = request;
|
|
97
|
+
const entries = visibleStackEntries(node.entries, context.viewport);
|
|
98
|
+
const intrinsicHeights = entries.map((entry) => typeof entry.basis === "number" ? entry.basis : measureHeight(context, entry.component, width));
|
|
99
|
+
const sizes = allocateStackSizes(entries, intrinsicHeights, height, node.gap);
|
|
100
|
+
const gapTotal = Math.max(0, entries.length - 1) * node.gap;
|
|
101
|
+
const naturalHeight = sizes.reduce((sum, size) => sum + size, 0) + gapTotal;
|
|
102
|
+
const allocatedHeight = height === undefined ? naturalHeight : Math.max(0, Math.floor(height));
|
|
103
|
+
const rect = { x, y, width, height: allocatedHeight };
|
|
104
|
+
const box = {
|
|
105
|
+
component,
|
|
106
|
+
rect,
|
|
107
|
+
clip: intersect(clip, rect),
|
|
108
|
+
children: [],
|
|
109
|
+
layer: 0,
|
|
110
|
+
};
|
|
111
|
+
let childY = y;
|
|
112
|
+
for (let index = 0; index < entries.length; index++) {
|
|
113
|
+
box.children.push(withParent(layoutComponent(context, entries[index].component, x, childY, width, sizes[index], box.clip), box));
|
|
114
|
+
childY += sizes[index] + node.gap;
|
|
115
|
+
}
|
|
116
|
+
return box;
|
|
117
|
+
}
|
|
118
|
+
function layoutHorizontalStack(request, node) {
|
|
119
|
+
const { context, component, x, y, width, height, clip } = request;
|
|
120
|
+
const entries = visibleStackEntries(node.entries, context.viewport);
|
|
121
|
+
const intrinsicWidths = entries.map((entry) => typeof entry.basis === "number" ? entry.basis : measureWidth(context, entry.component, width));
|
|
122
|
+
const widths = allocateStackSizes(entries, intrinsicWidths, width, node.gap);
|
|
123
|
+
const intrinsicHeights = entries.map((entry, index) => measureHeight(context, entry.component, Math.max(1, widths[index])));
|
|
124
|
+
const allocatedHeight = height === undefined
|
|
125
|
+
? intrinsicHeights.reduce((max, childHeight) => Math.max(max, childHeight), 0)
|
|
126
|
+
: Math.max(0, height);
|
|
127
|
+
const rect = { x, y, width, height: allocatedHeight };
|
|
128
|
+
const box = {
|
|
129
|
+
component,
|
|
130
|
+
rect,
|
|
131
|
+
clip: intersect(clip, rect),
|
|
132
|
+
children: [],
|
|
133
|
+
layer: 0,
|
|
134
|
+
};
|
|
135
|
+
let childX = x;
|
|
136
|
+
for (let index = 0; index < entries.length; index++) {
|
|
137
|
+
const naturalChildHeight = intrinsicHeights[index];
|
|
138
|
+
const childHeight = node.align === "stretch" ? allocatedHeight : Math.min(allocatedHeight, naturalChildHeight);
|
|
139
|
+
let childY = y;
|
|
140
|
+
if (node.align === "center")
|
|
141
|
+
childY += Math.floor((allocatedHeight - childHeight) / 2);
|
|
142
|
+
else if (node.align === "end")
|
|
143
|
+
childY += allocatedHeight - childHeight;
|
|
144
|
+
const childWidth = widths[index];
|
|
145
|
+
if (childWidth === 0) {
|
|
146
|
+
box.children.push({
|
|
147
|
+
component: entries[index].component,
|
|
148
|
+
rect: { x: childX, y: childY, width: 0, height: childHeight },
|
|
149
|
+
clip: { x: childX, y: childY, width: 0, height: 0 },
|
|
150
|
+
children: [],
|
|
151
|
+
parent: box,
|
|
152
|
+
layer: 0,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
box.children.push(withParent(layoutComponent(context, entries[index].component, childX, childY, childWidth, childHeight, box.clip), box));
|
|
157
|
+
}
|
|
158
|
+
childX += childWidth + node.gap;
|
|
159
|
+
}
|
|
160
|
+
return box;
|
|
161
|
+
}
|
|
162
|
+
function layoutComponent(context, component, x, y, width, height, clip) {
|
|
163
|
+
const request = { context, component, x, y, width: Math.max(1, Math.floor(width)), height, clip };
|
|
164
|
+
const node = getLayoutNode(component);
|
|
165
|
+
if (!node)
|
|
166
|
+
return layoutLeafComponent(request);
|
|
167
|
+
switch (node.type) {
|
|
168
|
+
case "scroll":
|
|
169
|
+
return layoutScrollComponent(request, node);
|
|
170
|
+
case "vstack":
|
|
171
|
+
return layoutVerticalStack(request, node);
|
|
172
|
+
case "hstack":
|
|
173
|
+
return layoutHorizontalStack(request, node);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function styleScrollbarCell(line, column, totalWidth, style) {
|
|
177
|
+
if (isImageLine(line))
|
|
178
|
+
return line;
|
|
179
|
+
const graphemeRange = getGraphemeCellRange(line, column);
|
|
180
|
+
const start = graphemeRange?.start ?? column;
|
|
181
|
+
const end = graphemeRange?.end ?? column + 1;
|
|
182
|
+
const before = sliceByColumn(line, 0, start, true);
|
|
183
|
+
const target = sliceByColumn(line, start, end - start, true);
|
|
184
|
+
const after = sliceByColumn(line, end, Math.max(0, totalWidth - end), true);
|
|
185
|
+
let targetPrefix = "";
|
|
186
|
+
let targetIndex = 0;
|
|
187
|
+
while (targetIndex < target.length) {
|
|
188
|
+
const ansi = extractAnsiCode(target, targetIndex);
|
|
189
|
+
if (!ansi)
|
|
190
|
+
break;
|
|
191
|
+
targetPrefix += ansi.code;
|
|
192
|
+
targetIndex += ansi.length;
|
|
193
|
+
}
|
|
194
|
+
const targetText = target.slice(targetIndex) || " ".repeat(end - start);
|
|
195
|
+
const beforePadding = " ".repeat(Math.max(0, start - visibleWidth(before)));
|
|
196
|
+
return `${before}${beforePadding}${targetPrefix}${style(targetText)}${after}`;
|
|
197
|
+
}
|
|
198
|
+
export function getScrollbarGeometry(box) {
|
|
199
|
+
if (!box.scrollView?.isScrollbarVisible || box.rect.width <= 0 || box.rect.height <= 0)
|
|
200
|
+
return undefined;
|
|
201
|
+
const contentHeight = box.children[0]?.rect.height ?? box.scrollContentLines?.length ?? 0;
|
|
202
|
+
const trackHeight = box.rect.height;
|
|
203
|
+
const minThumbHeight = Math.min(2, trackHeight);
|
|
204
|
+
const thumbHeight = Math.max(minThumbHeight, Math.min(trackHeight, Math.round((trackHeight * trackHeight) / contentHeight)));
|
|
205
|
+
const maxScrollTop = Math.max(0, contentHeight - trackHeight);
|
|
206
|
+
const maxThumbTop = trackHeight - thumbHeight;
|
|
207
|
+
const thumbOffset = maxScrollTop === 0 ? 0 : Math.round((box.scrollView.scrollTop / maxScrollTop) * maxThumbTop);
|
|
208
|
+
const column = box.rect.x + box.rect.width - 1;
|
|
209
|
+
if (column < box.clip.x || column >= box.clip.x + box.clip.width)
|
|
210
|
+
return undefined;
|
|
211
|
+
return {
|
|
212
|
+
column,
|
|
213
|
+
trackTop: box.rect.y,
|
|
214
|
+
trackHeight,
|
|
215
|
+
thumbTop: box.rect.y + thumbOffset,
|
|
216
|
+
thumbHeight,
|
|
217
|
+
maxScrollTop,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function paintScrollbar(box, screen, totalWidth) {
|
|
221
|
+
const geometry = getScrollbarGeometry(box);
|
|
222
|
+
if (!geometry || !box.scrollView)
|
|
223
|
+
return;
|
|
224
|
+
for (let offset = 0; offset < geometry.thumbHeight; offset++) {
|
|
225
|
+
const row = geometry.thumbTop + offset;
|
|
226
|
+
if (row < box.clip.y || row >= box.clip.y + box.clip.height || row < 0 || row >= screen.length)
|
|
227
|
+
continue;
|
|
228
|
+
screen[row] = styleScrollbarCell(screen[row] ?? "", geometry.column, totalWidth, box.scrollView.scrollbarStyle);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function boxFillsViewportWidth(box, totalWidth) {
|
|
232
|
+
return box.rect.x === 0 && box.rect.width >= totalWidth && box.clip.x === 0 && box.clip.width >= totalWidth;
|
|
233
|
+
}
|
|
234
|
+
function cropBoxImageLine(line, row, box, screenHeight) {
|
|
235
|
+
const imageMetadata = getKittyImageMetadata(line);
|
|
236
|
+
if (!imageMetadata)
|
|
237
|
+
return line;
|
|
238
|
+
const clipBottom = Math.min(screenHeight, box.clip.y + box.clip.height);
|
|
239
|
+
const visibleRows = Math.min(imageMetadata.rows, clipBottom - row);
|
|
240
|
+
return visibleRows < imageMetadata.rows ? cropKittyImageLine(line, 0, visibleRows) : line;
|
|
241
|
+
}
|
|
242
|
+
function paintBoxImageLine(box, screen, row, line, totalWidth) {
|
|
243
|
+
if (boxFillsViewportWidth(box, totalWidth)) {
|
|
244
|
+
screen[row] = line;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (box.clip.x !== box.rect.x || box.clip.width !== box.rect.width)
|
|
248
|
+
return;
|
|
249
|
+
screen[row] = compositeTuiLine(screen[row] ?? "", line, box.rect.x, box.rect.width, totalWidth);
|
|
250
|
+
}
|
|
251
|
+
function paintBoxTextLine(box, screen, row, line, totalWidth) {
|
|
252
|
+
if (box.clip.width <= 0)
|
|
253
|
+
return;
|
|
254
|
+
// A full-width box painting onto an untouched row can retain the source line
|
|
255
|
+
// reference. Recomposition would rebuild the row through ANSI/grapheme
|
|
256
|
+
// segmentation on every frame. Keep over-wide sources on the clipping path
|
|
257
|
+
// so LayoutFrame rows remain viewport-bounded.
|
|
258
|
+
if (boxFillsViewportWidth(box, totalWidth) && !screen[row] && visibleWidth(line) <= totalWidth) {
|
|
259
|
+
screen[row] = line;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const sourceStart = Math.max(0, box.clip.x - box.rect.x);
|
|
263
|
+
const clippedLine = sliceByColumn(line, sourceStart, box.clip.width, true);
|
|
264
|
+
screen[row] = compositeTuiLine(screen[row] ?? "", clippedLine, box.clip.x, box.clip.width, totalWidth);
|
|
265
|
+
}
|
|
266
|
+
function paintBoxLine(box, screen, row, sourceLine, totalWidth) {
|
|
267
|
+
const line = cropBoxImageLine(sourceLine.replace(OSC133_ZONE_PREFIX, ""), row, box, screen.length);
|
|
268
|
+
if (isImageLine(line)) {
|
|
269
|
+
paintBoxImageLine(box, screen, row, line, totalWidth);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
paintBoxTextLine(box, screen, row, line, totalWidth);
|
|
273
|
+
}
|
|
274
|
+
function paintBoxLines(box, screen, totalWidth) {
|
|
275
|
+
if (!box.lines)
|
|
276
|
+
return;
|
|
277
|
+
const offset = box.lineOffset ?? 0;
|
|
278
|
+
const firstRow = Math.max(box.rect.y, box.clip.y, 0);
|
|
279
|
+
const lastRow = Math.min(box.rect.y + box.rect.height, box.clip.y + box.clip.height, screen.length);
|
|
280
|
+
for (let row = firstRow; row < lastRow; row++) {
|
|
281
|
+
const sourceLine = box.lines[offset + row - box.rect.y];
|
|
282
|
+
if (sourceLine !== undefined)
|
|
283
|
+
paintBoxLine(box, screen, row, sourceLine, totalWidth);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function paintScrolledKittyImagePlacement(request) {
|
|
287
|
+
const { box, screen, totalWidth, scrollTop, imageLine, imageRow, imageRows } = request;
|
|
288
|
+
const targetRow = Math.max(0, box.rect.y, box.clip.y);
|
|
289
|
+
const hiddenRows = scrollTop - imageRow + (targetRow - box.rect.y);
|
|
290
|
+
if (hiddenRows >= imageRows)
|
|
291
|
+
return;
|
|
292
|
+
const clipBottom = Math.min(screen.length, box.rect.y + box.rect.height, box.clip.y + box.clip.height);
|
|
293
|
+
const visibleRows = Math.min(clipBottom - targetRow, imageRows - hiddenRows);
|
|
294
|
+
if (visibleRows <= 0)
|
|
295
|
+
return;
|
|
296
|
+
const cropped = cropKittyImageLine(imageLine, hiddenRows, visibleRows);
|
|
297
|
+
if (boxFillsViewportWidth(box, totalWidth))
|
|
298
|
+
screen[targetRow] = cropped;
|
|
299
|
+
}
|
|
300
|
+
function paintScrolledKittyImage(box, screen, totalWidth) {
|
|
301
|
+
const scrollView = box.scrollView;
|
|
302
|
+
const contentLines = box.scrollContentLines;
|
|
303
|
+
if (!scrollView || !contentLines || scrollView.scrollTop <= 0 || box.rect.height <= 0)
|
|
304
|
+
return;
|
|
305
|
+
for (let imageRow = scrollView.scrollTop - 1; imageRow >= 0; imageRow--) {
|
|
306
|
+
const imageLine = contentLines[imageRow] ?? "";
|
|
307
|
+
const metadata = getKittyImageMetadata(imageLine);
|
|
308
|
+
if (metadata) {
|
|
309
|
+
paintScrolledKittyImagePlacement({
|
|
310
|
+
box,
|
|
311
|
+
screen,
|
|
312
|
+
totalWidth,
|
|
313
|
+
scrollTop: scrollView.scrollTop,
|
|
314
|
+
imageLine,
|
|
315
|
+
imageRow,
|
|
316
|
+
imageRows: metadata.rows,
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (imageLine !== "")
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function paintBox(box, screen, totalWidth) {
|
|
325
|
+
paintBoxLines(box, screen, totalWidth);
|
|
326
|
+
for (const child of box.children)
|
|
327
|
+
paintBox(child, screen, totalWidth);
|
|
328
|
+
paintScrolledKittyImage(box, screen, totalWidth);
|
|
329
|
+
paintScrollbar(box, screen, totalWidth);
|
|
330
|
+
}
|
|
331
|
+
export function renderLayoutFrame(root, width, height, requestRender) {
|
|
332
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
333
|
+
const safeHeight = Math.max(1, Math.floor(height));
|
|
334
|
+
const context = {
|
|
335
|
+
viewport: { width: safeWidth, height: safeHeight },
|
|
336
|
+
renderCache: new Map(),
|
|
337
|
+
requestRender,
|
|
338
|
+
primaryScrollView: undefined,
|
|
339
|
+
};
|
|
340
|
+
const rootBox = layoutComponent(context, root, 0, 0, safeWidth, safeHeight, {
|
|
341
|
+
x: 0,
|
|
342
|
+
y: 0,
|
|
343
|
+
width: safeWidth,
|
|
344
|
+
height: safeHeight,
|
|
345
|
+
});
|
|
346
|
+
const lines = Array.from({ length: safeHeight }, () => "");
|
|
347
|
+
paintBox(rootBox, lines, safeWidth);
|
|
348
|
+
return {
|
|
349
|
+
root: rootBox,
|
|
350
|
+
width: safeWidth,
|
|
351
|
+
height: safeHeight,
|
|
352
|
+
lines,
|
|
353
|
+
...(context.primaryScrollView === undefined ? {} : { primaryScrollView: context.primaryScrollView }),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
function containsPoint(rect, x, y) {
|
|
357
|
+
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
358
|
+
}
|
|
359
|
+
export function getScrollViewBox(frame, scrollView) {
|
|
360
|
+
const visit = (box) => {
|
|
361
|
+
if (box.scrollView === scrollView)
|
|
362
|
+
return box;
|
|
363
|
+
for (const child of box.children) {
|
|
364
|
+
const match = visit(child);
|
|
365
|
+
if (match)
|
|
366
|
+
return match;
|
|
367
|
+
}
|
|
368
|
+
return undefined;
|
|
369
|
+
};
|
|
370
|
+
return visit(frame.root);
|
|
371
|
+
}
|
|
372
|
+
export function getScrollViewsAt(frame, x, y) {
|
|
373
|
+
const result = [];
|
|
374
|
+
const visit = (box, depth) => {
|
|
375
|
+
if (!containsPoint(box.clip, x, y))
|
|
376
|
+
return;
|
|
377
|
+
if (box.scrollView && containsPoint(box.rect, x, y))
|
|
378
|
+
result.push({ scrollView: box.scrollView, depth });
|
|
379
|
+
for (const child of box.children)
|
|
380
|
+
visit(child, depth + 1);
|
|
381
|
+
};
|
|
382
|
+
visit(frame.root, 0);
|
|
383
|
+
result.sort((a, b) => b.depth - a.depth);
|
|
384
|
+
return result.map((entry) => entry.scrollView);
|
|
385
|
+
}
|
|
386
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,aAAa,EAAoE,MAAM,kBAAkB,CAAC;AACnH,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC7F,OAAO,EAAkB,aAAa,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhG,MAAM,kBAAkB,GAAG,sCAAsC,CAAC;AAwDlE,SAAS,SAAS,CAAC,CAAa,EAAE,CAAa,EAAc;IAC5D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;AAAA,CAChF;AAED,SAAS,YAAY,CAAC,OAAsB,EAAE,SAAoB,EAAE,KAAa,EAAY;IAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,IAAI,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;QACrC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED,SAAS,aAAa,CAAC,OAAsB,EAAE,SAAoB,EAAE,KAAa,EAAU;IAC3F,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC;AAAA,CACtD;AAED,SAAS,YAAY,CAAC,OAAsB,EAAE,SAAoB,EAAE,KAAa,EAAU;IAC1F,OAAO,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAAA,CAC3G;AAED,SAAS,UAAU,CAAC,GAAc,EAAE,MAAiB,EAAa;IACjE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;IACpB,OAAO,GAAG,CAAC;AAAA,CACX;AAED,SAAS,YAAY,CAAC,GAAc,EAAE,MAAc,EAAQ;IAC3D,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ;QAAE,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAAA,CAC9D;AAED,SAAS,WAAW,CAAC,GAAc,EAAE,UAAsB,EAAQ;IAClE,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ;QAAE,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;AAAA,CAC/D;AAYD,SAAS,mBAAmB,CAAC,OAA+B,EAAa;IACxE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAClE,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,eAAe,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9F,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,KAAK,CAAC,MAAM,GAAG,eAAe,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;QAC3E,IAAI,UAAU,IAAI,eAAe;YAAE,UAAU,GAAG,UAAU,GAAG,eAAe,GAAG,CAAC,CAAC;IAClF,CAAC;IACD,OAAO;QACN,SAAS;QACT,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE;QAC9C,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAC/D,QAAQ,EAAE,EAAE;QACZ,KAAK;QACL,UAAU;QACV,KAAK,EAAE,CAAC;KACR,CAAC;AAAA,CACF;AAED,SAAS,qBAAqB,CAAC,OAA+B,EAAE,IAAsB,EAAa;IAClG,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAClE,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;IAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC,GAAG,iBAAiB,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACnH,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;IAC3C,MAAM,cAAc,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9F,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,cAAc,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9E,YAAY,CAAC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAmB,CAAC;IAC5C,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB;QAAE,OAAO,CAAC,iBAAiB,GAAG,UAAU,CAAC;IAC7F,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACrD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,GAAG,GAAc;QACtB,SAAS;QACT,IAAI;QACJ,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,CAAC,QAAQ,CAAC;QACpB,UAAU;QACV,kBAAkB,EAAE,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC;QACvE,KAAK,EAAE,CAAC;KACR,CAAC;IACF,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IACtB,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACjC,OAAO,GAAG,CAAC;AAAA,CACX;AAED,SAAS,mBAAmB,CAAC,OAA+B,EAAE,IAAqB,EAAa;IAC/F,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAClE,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC9C,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAC9F,CAAC;IACF,MAAM,KAAK,GAAG,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;IAC5D,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC;IAC5E,MAAM,eAAe,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/F,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACtD,MAAM,GAAG,GAAc;QACtB,SAAS;QACT,IAAI;QACJ,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;QAC3B,QAAQ,EAAE,EAAE;QACZ,KAAK,EAAE,CAAC;KACR,CAAC;IACF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,GAAG,CAAC,QAAQ,CAAC,IAAI,CAChB,UAAU,CACT,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAE,CAAC,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAE,EAAE,GAAG,CAAC,IAAI,CAAC,EAC9F,GAAG,CACH,CACD,CAAC;QACF,MAAM,IAAI,KAAK,CAAC,KAAK,CAAE,GAAG,IAAI,CAAC,GAAG,CAAC;IACpC,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACX;AAED,SAAS,qBAAqB,CAAC,OAA+B,EAAE,IAAqB,EAAa;IACjG,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAClE,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC7C,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,CAC7F,CAAC;IACF,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7E,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACrD,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAE,CAAC,CAAC,CACpE,CAAC;IACF,MAAM,eAAe,GACpB,MAAM,KAAK,SAAS;QACnB,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACxB,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACtD,MAAM,GAAG,GAAc;QACtB,SAAS;QACT,IAAI;QACJ,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC;QAC3B,QAAQ,EAAE,EAAE;QACZ,KAAK,EAAE,CAAC;KACR,CAAC;IACF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,KAAK,CAAE,CAAC;QACpD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;QAC/G,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,IAAI,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,eAAe,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;aAClF,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK;YAAE,MAAM,IAAI,eAAe,GAAG,WAAW,CAAC;QACvE,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAE,CAAC;QAClC,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACjB,SAAS,EAAE,OAAO,CAAC,KAAK,CAAE,CAAC,SAAS;gBACpC,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE;gBAC7D,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;gBACnD,QAAQ,EAAE,EAAE;gBACZ,MAAM,EAAE,GAAG;gBACX,KAAK,EAAE,CAAC;aACR,CAAC,CAAC;QACJ,CAAC;aAAM,CAAC;YACP,GAAG,CAAC,QAAQ,CAAC,IAAI,CAChB,UAAU,CACT,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,EACtG,GAAG,CACH,CACD,CAAC;QACH,CAAC;QACD,MAAM,IAAI,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC;IACjC,CAAC;IACD,OAAO,GAAG,CAAC;AAAA,CACX;AAED,SAAS,eAAe,CACvB,OAAsB,EACtB,SAAoB,EACpB,CAAS,EACT,CAAS,EACT,KAAa,EACb,MAA0B,EAC1B,IAAgB,EACJ;IACZ,MAAM,OAAO,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAClG,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC/C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,QAAQ;YACZ,OAAO,qBAAqB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC7C,KAAK,QAAQ;YACZ,OAAO,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC3C,KAAK,QAAQ;YACZ,OAAO,qBAAqB,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;AAAA,CACD;AAED,SAAS,kBAAkB,CAAC,IAAY,EAAE,MAAc,EAAE,UAAkB,EAAE,KAA+B,EAAU;IACtH,IAAI,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEnC,MAAM,aAAa,GAAG,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzD,MAAM,KAAK,GAAG,aAAa,EAAE,KAAK,IAAI,MAAM,CAAC;IAC7C,MAAM,GAAG,GAAG,aAAa,EAAE,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAE5E,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,OAAO,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI;YAAE,MAAM;QACjB,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC;QAC1B,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC;IAC5B,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC,CAAC;IACxE,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC5E,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,KAAK,EAAE,CAAC;AAAA,CAC9E;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAc,EAAiC;IACnF,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,kBAAkB,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAEzG,MAAM,aAAa,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,kBAAkB,EAAE,MAAM,IAAI,CAAC,CAAC;IAC1F,MAAM,WAAW,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC;IACpC,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAC3B,cAAc,EACd,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,aAAa,CAAC,CAAC,CAC9E,CAAC;IACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;IAC9D,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IAC9C,MAAM,WAAW,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,GAAG,YAAY,CAAC,GAAG,WAAW,CAAC,CAAC;IACjH,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IAC/C,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAEnF,OAAO;QACN,MAAM;QACN,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,WAAW;QACX,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,WAAW;QAClC,WAAW;QACX,YAAY;KACZ,CAAC;AAAA,CACF;AAED,SAAS,cAAc,CAAC,GAAc,EAAE,MAAgB,EAAE,UAAkB,EAAQ;IACnF,MAAM,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC3C,IAAI,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO;IAEzC,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAAC;QACvC,IAAI,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,MAAM;YAAE,SAAS;QACzG,MAAM,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACjH,CAAC;AAAA,CACD;AAED,SAAS,qBAAqB,CAAC,GAAc,EAAE,UAAkB,EAAW;IAC3E,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC;AAAA,CAC5G;AAED,SAAS,gBAAgB,CAAC,IAAY,EAAE,GAAW,EAAE,GAAc,EAAE,YAAoB,EAAU;IAClG,MAAM,aAAa,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,GAAG,CAAC,CAAC;IACnE,OAAO,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CAC1F;AAED,SAAS,iBAAiB,CAAC,GAAc,EAAE,MAAgB,EAAE,GAAW,EAAE,IAAY,EAAE,UAAkB,EAAQ;IACjH,IAAI,qBAAqB,CAAC,GAAG,EAAE,UAAU,CAAC,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;QACnB,OAAO;IACR,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK;QAAE,OAAO;IAC3E,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AAAA,CAChG;AAED,SAAS,gBAAgB,CAAC,GAAc,EAAE,MAAgB,EAAE,GAAW,EAAE,IAAY,EAAE,UAAkB,EAAQ;IAChH,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;QAAE,OAAO;IAChC,6EAA6E;IAC7E,uEAAuE;IACvE,2EAA2E;IAC3E,+CAA+C;IAC/C,IAAI,qBAAqB,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,UAAU,EAAE,CAAC;QAChG,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;QACnB,OAAO;IACR,CAAC;IACD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3E,MAAM,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AAAA,CACvG;AAED,SAAS,YAAY,CAAC,GAAc,EAAE,MAAgB,EAAE,GAAW,EAAE,UAAkB,EAAE,UAAkB,EAAQ;IAClH,MAAM,IAAI,GAAG,gBAAgB,CAAC,UAAU,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACnG,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QACtD,OAAO;IACR,CAAC;IACD,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAAA,CACrD;AAED,SAAS,aAAa,CAAC,GAAc,EAAE,MAAgB,EAAE,UAAkB,EAAQ;IAClF,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO;IACvB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACpG,KAAK,IAAI,GAAG,GAAG,QAAQ,EAAE,GAAG,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;QAC/C,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACxD,IAAI,UAAU,KAAK,SAAS;YAAE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACtF,CAAC;AAAA,CACD;AAED,SAAS,gCAAgC,CAAC,OAAuC,EAAQ;IACxF,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IACvF,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,UAAU,IAAI,SAAS;QAAE,OAAO;IACpC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvG,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,GAAG,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC;IAC7E,IAAI,WAAW,IAAI,CAAC;QAAE,OAAO;IAC7B,MAAM,OAAO,GAAG,kBAAkB,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IACvE,IAAI,qBAAqB,CAAC,GAAG,EAAE,UAAU,CAAC;QAAE,MAAM,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC;AAAA,CACxE;AAED,SAAS,uBAAuB,CAAC,GAAc,EAAE,MAAgB,EAAE,UAAkB,EAAQ;IAC5F,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC;IAClC,MAAM,YAAY,GAAG,GAAG,CAAC,kBAAkB,CAAC;IAC5C,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,IAAI,UAAU,CAAC,SAAS,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO;IAC9F,KAAK,IAAI,QAAQ,GAAG,UAAU,CAAC,SAAS,GAAG,CAAC,EAAE,QAAQ,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC;QACzE,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,QAAQ,EAAE,CAAC;YACd,gCAAgC,CAAC;gBAChC,GAAG;gBACH,MAAM;gBACN,UAAU;gBACV,SAAS,EAAE,UAAU,CAAC,SAAS;gBAC/B,SAAS;gBACT,QAAQ;gBACR,SAAS,EAAE,QAAQ,CAAC,IAAI;aACxB,CAAC,CAAC;YACH,OAAO;QACR,CAAC;QACD,IAAI,SAAS,KAAK,EAAE;YAAE,OAAO;IAC9B,CAAC;AAAA,CACD;AAED,SAAS,QAAQ,CAAC,GAAc,EAAE,MAAgB,EAAE,UAAkB,EAAQ;IAC7E,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACtE,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACjD,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;AAAA,CACxC;AAED,MAAM,UAAU,iBAAiB,CAChC,IAAe,EACf,KAAa,EACb,MAAc,EACd,aAAyB,EACX;IACd,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACnD,MAAM,OAAO,GAAkB;QAC9B,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE;QAClD,WAAW,EAAE,IAAI,GAAG,EAAE;QACtB,aAAa;QACb,iBAAiB,EAAE,SAAS;KAC5B,CAAC;IACF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE;QAC3E,CAAC,EAAE,CAAC;QACJ,CAAC,EAAE,CAAC;QACJ,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,UAAU;KAClB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACpC,OAAO;QACN,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,UAAU;QAClB,KAAK;QACL,GAAG,CAAC,OAAO,CAAC,iBAAiB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC;KACpG,CAAC;AAAA,CACF;AAED,SAAS,aAAa,CAAC,IAAgB,EAAE,CAAS,EAAE,CAAS,EAAW;IACvE,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;AAAA,CACzF;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAkB,EAAE,UAAsB,EAAyB;IACnG,MAAM,KAAK,GAAG,CAAC,GAAc,EAAyB,EAAE,CAAC;QACxD,IAAI,GAAG,CAAC,UAAU,KAAK,UAAU;YAAE,OAAO,GAAG,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3B,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QACzB,CAAC;QACD,OAAO,SAAS,CAAC;IAAA,CACjB,CAAC;IACF,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAAA,CACzB;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAkB,EAAE,CAAS,EAAE,CAAS,EAAgB;IACxF,MAAM,MAAM,GAAqD,EAAE,CAAC;IACpE,MAAM,KAAK,GAAG,CAAC,GAAc,EAAE,KAAa,EAAQ,EAAE,CAAC;QACtD,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAAE,OAAO;QAC3C,IAAI,GAAG,CAAC,UAAU,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;QACxG,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ;YAAE,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAAA,CAC1D,CAAC;IACF,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACrB,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AAAA,CAC/C","sourcesContent":["import type { ScrollView } from \"./components/scroll-view.ts\";\nimport { allocateStackSizes, visibleStackEntries } from \"./components/stack.ts\";\nimport { getLayoutNode, type LayoutViewport, type ScrollLayoutNode, type StackLayoutNode } from \"./layout-node.ts\";\nimport { cropKittyImageLine, getKittyImageMetadata, isImageLine } from \"./terminal-image.ts\";\nimport { type Component, CURSOR_MARKER, compositeTuiLine } from \"./tui.ts\";\nimport { extractAnsiCode, getGraphemeCellRange, sliceByColumn, visibleWidth } from \"./utils.ts\";\n\nconst OSC133_ZONE_PREFIX = /^(?:\\x1b\\]133;[ABC](?:\\x07|\\x1b\\\\))+/;\n\nexport interface LayoutRect {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n}\n\nexport interface LayoutBox {\n\tcomponent: Component;\n\trect: LayoutRect;\n\tclip: LayoutRect;\n\tchildren: LayoutBox[];\n\tparent?: LayoutBox;\n\tlines?: readonly string[];\n\tlineOffset?: number;\n\tscrollView?: ScrollView;\n\tscrollContentLines?: readonly string[];\n\tlayer: number;\n}\n\nexport interface LayoutFrame {\n\troot: LayoutBox;\n\twidth: number;\n\theight: number;\n\tlines: string[];\n\tprimaryScrollView?: ScrollView;\n}\n\nexport interface ScrollbarGeometry {\n\tcolumn: number;\n\ttrackTop: number;\n\ttrackHeight: number;\n\tthumbTop: number;\n\tthumbHeight: number;\n\tmaxScrollTop: number;\n}\n\ninterface LayoutContext {\n\tviewport: LayoutViewport;\n\trenderCache: Map<Component, Map<number, string[]>>;\n\trequestRender: () => void;\n\tprimaryScrollView: ScrollView | undefined;\n}\n\ninterface ScrolledKittyImagePaintRequest {\n\tbox: LayoutBox;\n\tscreen: string[];\n\ttotalWidth: number;\n\tscrollTop: number;\n\timageLine: string;\n\timageRow: number;\n\timageRows: number;\n}\n\nfunction intersect(a: LayoutRect, b: LayoutRect): LayoutRect {\n\tconst x = Math.max(a.x, b.x);\n\tconst y = Math.max(a.y, b.y);\n\tconst right = Math.min(a.x + a.width, b.x + b.width);\n\tconst bottom = Math.min(a.y + a.height, b.y + b.height);\n\treturn { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) };\n}\n\nfunction renderCached(context: LayoutContext, component: Component, width: number): string[] {\n\tconst safeWidth = Math.max(1, Math.floor(width));\n\tlet widths = context.renderCache.get(component);\n\tif (!widths) {\n\t\twidths = new Map<number, string[]>();\n\t\tcontext.renderCache.set(component, widths);\n\t}\n\tlet lines = widths.get(safeWidth);\n\tif (!lines) {\n\t\tlines = component.render(safeWidth);\n\t\twidths.set(safeWidth, lines);\n\t}\n\treturn lines;\n}\n\nfunction measureHeight(context: LayoutContext, component: Component, width: number): number {\n\treturn renderCached(context, component, width).length;\n}\n\nfunction measureWidth(context: LayoutContext, component: Component, width: number): number {\n\treturn renderCached(context, component, width).reduce((max, line) => Math.max(max, visibleWidth(line)), 0);\n}\n\nfunction withParent(box: LayoutBox, parent: LayoutBox): LayoutBox {\n\tbox.parent = parent;\n\treturn box;\n}\n\nfunction translateBox(box: LayoutBox, deltaY: number): void {\n\tbox.rect.y += deltaY;\n\tfor (const child of box.children) translateBox(child, deltaY);\n}\n\nfunction updateClips(box: LayoutBox, parentClip: LayoutRect): void {\n\tbox.clip = intersect(parentClip, box.rect);\n\tfor (const child of box.children) updateClips(child, box.clip);\n}\n\ninterface ComponentLayoutRequest {\n\tcontext: LayoutContext;\n\tcomponent: Component;\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number | undefined;\n\tclip: LayoutRect;\n}\n\nfunction layoutLeafComponent(request: ComponentLayoutRequest): LayoutBox {\n\tconst { context, component, x, y, width, height, clip } = request;\n\tconst lines = renderCached(context, component, width);\n\tconst allocatedHeight = height === undefined ? lines.length : Math.max(0, Math.floor(height));\n\tlet lineOffset = 0;\n\tif (lines.length > allocatedHeight && allocatedHeight > 0) {\n\t\tconst cursorLine = lines.findIndex((line) => line.includes(CURSOR_MARKER));\n\t\tif (cursorLine >= allocatedHeight) lineOffset = cursorLine - allocatedHeight + 1;\n\t}\n\treturn {\n\t\tcomponent,\n\t\trect: { x, y, width, height: allocatedHeight },\n\t\tclip: intersect(clip, { x, y, width, height: allocatedHeight }),\n\t\tchildren: [],\n\t\tlines,\n\t\tlineOffset,\n\t\tlayer: 0,\n\t};\n}\n\nfunction layoutScrollComponent(request: ComponentLayoutRequest, node: ScrollLayoutNode): LayoutBox {\n\tconst { context, component, x, y, width, height, clip } = request;\n\tconst previousScrollTop = node.state.scrollTop;\n\tconst contentWidth = node.state.getContentWidth(width);\n\tconst childBox = layoutComponent(context, node.component, x, y - previousScrollTop, contentWidth, undefined, clip);\n\tconst contentHeight = childBox.rect.height;\n\tconst viewportHeight = height === undefined ? contentHeight : Math.max(0, Math.floor(height));\n\tnode.state.updateLayout(contentHeight, viewportHeight, context.requestRender);\n\ttranslateBox(childBox, previousScrollTop - node.state.scrollTop);\n\tconst scrollView = node.state as ScrollView;\n\tif (node.state.primary || !context.primaryScrollView) context.primaryScrollView = scrollView;\n\tconst rect = { x, y, width, height: viewportHeight };\n\tconst childClip = intersect(clip, rect);\n\tconst box: LayoutBox = {\n\t\tcomponent,\n\t\trect,\n\t\tclip: childClip,\n\t\tchildren: [childBox],\n\t\tscrollView,\n\t\tscrollContentLines: renderCached(context, node.component, contentWidth),\n\t\tlayer: 0,\n\t};\n\tchildBox.parent = box;\n\tupdateClips(childBox, childClip);\n\treturn box;\n}\n\nfunction layoutVerticalStack(request: ComponentLayoutRequest, node: StackLayoutNode): LayoutBox {\n\tconst { context, component, x, y, width, height, clip } = request;\n\tconst entries = visibleStackEntries(node.entries, context.viewport);\n\tconst intrinsicHeights = entries.map((entry) =>\n\t\ttypeof entry.basis === \"number\" ? entry.basis : measureHeight(context, entry.component, width),\n\t);\n\tconst sizes = allocateStackSizes(entries, intrinsicHeights, height, node.gap);\n\tconst gapTotal = Math.max(0, entries.length - 1) * node.gap;\n\tconst naturalHeight = sizes.reduce((sum, size) => sum + size, 0) + gapTotal;\n\tconst allocatedHeight = height === undefined ? naturalHeight : Math.max(0, Math.floor(height));\n\tconst rect = { x, y, width, height: allocatedHeight };\n\tconst box: LayoutBox = {\n\t\tcomponent,\n\t\trect,\n\t\tclip: intersect(clip, rect),\n\t\tchildren: [],\n\t\tlayer: 0,\n\t};\n\tlet childY = y;\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tbox.children.push(\n\t\t\twithParent(\n\t\t\t\tlayoutComponent(context, entries[index]!.component, x, childY, width, sizes[index]!, box.clip),\n\t\t\t\tbox,\n\t\t\t),\n\t\t);\n\t\tchildY += sizes[index]! + node.gap;\n\t}\n\treturn box;\n}\n\nfunction layoutHorizontalStack(request: ComponentLayoutRequest, node: StackLayoutNode): LayoutBox {\n\tconst { context, component, x, y, width, height, clip } = request;\n\tconst entries = visibleStackEntries(node.entries, context.viewport);\n\tconst intrinsicWidths = entries.map((entry) =>\n\t\ttypeof entry.basis === \"number\" ? entry.basis : measureWidth(context, entry.component, width),\n\t);\n\tconst widths = allocateStackSizes(entries, intrinsicWidths, width, node.gap);\n\tconst intrinsicHeights = entries.map((entry, index) =>\n\t\tmeasureHeight(context, entry.component, Math.max(1, widths[index]!)),\n\t);\n\tconst allocatedHeight =\n\t\theight === undefined\n\t\t\t? intrinsicHeights.reduce((max, childHeight) => Math.max(max, childHeight), 0)\n\t\t\t: Math.max(0, height);\n\tconst rect = { x, y, width, height: allocatedHeight };\n\tconst box: LayoutBox = {\n\t\tcomponent,\n\t\trect,\n\t\tclip: intersect(clip, rect),\n\t\tchildren: [],\n\t\tlayer: 0,\n\t};\n\tlet childX = x;\n\tfor (let index = 0; index < entries.length; index++) {\n\t\tconst naturalChildHeight = intrinsicHeights[index]!;\n\t\tconst childHeight = node.align === \"stretch\" ? allocatedHeight : Math.min(allocatedHeight, naturalChildHeight);\n\t\tlet childY = y;\n\t\tif (node.align === \"center\") childY += Math.floor((allocatedHeight - childHeight) / 2);\n\t\telse if (node.align === \"end\") childY += allocatedHeight - childHeight;\n\t\tconst childWidth = widths[index]!;\n\t\tif (childWidth === 0) {\n\t\t\tbox.children.push({\n\t\t\t\tcomponent: entries[index]!.component,\n\t\t\t\trect: { x: childX, y: childY, width: 0, height: childHeight },\n\t\t\t\tclip: { x: childX, y: childY, width: 0, height: 0 },\n\t\t\t\tchildren: [],\n\t\t\t\tparent: box,\n\t\t\t\tlayer: 0,\n\t\t\t});\n\t\t} else {\n\t\t\tbox.children.push(\n\t\t\t\twithParent(\n\t\t\t\t\tlayoutComponent(context, entries[index]!.component, childX, childY, childWidth, childHeight, box.clip),\n\t\t\t\t\tbox,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tchildX += childWidth + node.gap;\n\t}\n\treturn box;\n}\n\nfunction layoutComponent(\n\tcontext: LayoutContext,\n\tcomponent: Component,\n\tx: number,\n\ty: number,\n\twidth: number,\n\theight: number | undefined,\n\tclip: LayoutRect,\n): LayoutBox {\n\tconst request = { context, component, x, y, width: Math.max(1, Math.floor(width)), height, clip };\n\tconst node = getLayoutNode(component);\n\tif (!node) return layoutLeafComponent(request);\n\tswitch (node.type) {\n\t\tcase \"scroll\":\n\t\t\treturn layoutScrollComponent(request, node);\n\t\tcase \"vstack\":\n\t\t\treturn layoutVerticalStack(request, node);\n\t\tcase \"hstack\":\n\t\t\treturn layoutHorizontalStack(request, node);\n\t}\n}\n\nfunction styleScrollbarCell(line: string, column: number, totalWidth: number, style: (text: string) => string): string {\n\tif (isImageLine(line)) return line;\n\n\tconst graphemeRange = getGraphemeCellRange(line, column);\n\tconst start = graphemeRange?.start ?? column;\n\tconst end = graphemeRange?.end ?? column + 1;\n\tconst before = sliceByColumn(line, 0, start, true);\n\tconst target = sliceByColumn(line, start, end - start, true);\n\tconst after = sliceByColumn(line, end, Math.max(0, totalWidth - end), true);\n\n\tlet targetPrefix = \"\";\n\tlet targetIndex = 0;\n\twhile (targetIndex < target.length) {\n\t\tconst ansi = extractAnsiCode(target, targetIndex);\n\t\tif (!ansi) break;\n\t\ttargetPrefix += ansi.code;\n\t\ttargetIndex += ansi.length;\n\t}\n\tconst targetText = target.slice(targetIndex) || \" \".repeat(end - start);\n\tconst beforePadding = \" \".repeat(Math.max(0, start - visibleWidth(before)));\n\treturn `${before}${beforePadding}${targetPrefix}${style(targetText)}${after}`;\n}\n\nexport function getScrollbarGeometry(box: LayoutBox): ScrollbarGeometry | undefined {\n\tif (!box.scrollView?.isScrollbarVisible || box.rect.width <= 0 || box.rect.height <= 0) return undefined;\n\n\tconst contentHeight = box.children[0]?.rect.height ?? box.scrollContentLines?.length ?? 0;\n\tconst trackHeight = box.rect.height;\n\tconst minThumbHeight = Math.min(2, trackHeight);\n\tconst thumbHeight = Math.max(\n\t\tminThumbHeight,\n\t\tMath.min(trackHeight, Math.round((trackHeight * trackHeight) / contentHeight)),\n\t);\n\tconst maxScrollTop = Math.max(0, contentHeight - trackHeight);\n\tconst maxThumbTop = trackHeight - thumbHeight;\n\tconst thumbOffset = maxScrollTop === 0 ? 0 : Math.round((box.scrollView.scrollTop / maxScrollTop) * maxThumbTop);\n\tconst column = box.rect.x + box.rect.width - 1;\n\tif (column < box.clip.x || column >= box.clip.x + box.clip.width) return undefined;\n\n\treturn {\n\t\tcolumn,\n\t\ttrackTop: box.rect.y,\n\t\ttrackHeight,\n\t\tthumbTop: box.rect.y + thumbOffset,\n\t\tthumbHeight,\n\t\tmaxScrollTop,\n\t};\n}\n\nfunction paintScrollbar(box: LayoutBox, screen: string[], totalWidth: number): void {\n\tconst geometry = getScrollbarGeometry(box);\n\tif (!geometry || !box.scrollView) return;\n\n\tfor (let offset = 0; offset < geometry.thumbHeight; offset++) {\n\t\tconst row = geometry.thumbTop + offset;\n\t\tif (row < box.clip.y || row >= box.clip.y + box.clip.height || row < 0 || row >= screen.length) continue;\n\t\tscreen[row] = styleScrollbarCell(screen[row] ?? \"\", geometry.column, totalWidth, box.scrollView.scrollbarStyle);\n\t}\n}\n\nfunction boxFillsViewportWidth(box: LayoutBox, totalWidth: number): boolean {\n\treturn box.rect.x === 0 && box.rect.width >= totalWidth && box.clip.x === 0 && box.clip.width >= totalWidth;\n}\n\nfunction cropBoxImageLine(line: string, row: number, box: LayoutBox, screenHeight: number): string {\n\tconst imageMetadata = getKittyImageMetadata(line);\n\tif (!imageMetadata) return line;\n\tconst clipBottom = Math.min(screenHeight, box.clip.y + box.clip.height);\n\tconst visibleRows = Math.min(imageMetadata.rows, clipBottom - row);\n\treturn visibleRows < imageMetadata.rows ? cropKittyImageLine(line, 0, visibleRows) : line;\n}\n\nfunction paintBoxImageLine(box: LayoutBox, screen: string[], row: number, line: string, totalWidth: number): void {\n\tif (boxFillsViewportWidth(box, totalWidth)) {\n\t\tscreen[row] = line;\n\t\treturn;\n\t}\n\tif (box.clip.x !== box.rect.x || box.clip.width !== box.rect.width) return;\n\tscreen[row] = compositeTuiLine(screen[row] ?? \"\", line, box.rect.x, box.rect.width, totalWidth);\n}\n\nfunction paintBoxTextLine(box: LayoutBox, screen: string[], row: number, line: string, totalWidth: number): void {\n\tif (box.clip.width <= 0) return;\n\t// A full-width box painting onto an untouched row can retain the source line\n\t// reference. Recomposition would rebuild the row through ANSI/grapheme\n\t// segmentation on every frame. Keep over-wide sources on the clipping path\n\t// so LayoutFrame rows remain viewport-bounded.\n\tif (boxFillsViewportWidth(box, totalWidth) && !screen[row] && visibleWidth(line) <= totalWidth) {\n\t\tscreen[row] = line;\n\t\treturn;\n\t}\n\tconst sourceStart = Math.max(0, box.clip.x - box.rect.x);\n\tconst clippedLine = sliceByColumn(line, sourceStart, box.clip.width, true);\n\tscreen[row] = compositeTuiLine(screen[row] ?? \"\", clippedLine, box.clip.x, box.clip.width, totalWidth);\n}\n\nfunction paintBoxLine(box: LayoutBox, screen: string[], row: number, sourceLine: string, totalWidth: number): void {\n\tconst line = cropBoxImageLine(sourceLine.replace(OSC133_ZONE_PREFIX, \"\"), row, box, screen.length);\n\tif (isImageLine(line)) {\n\t\tpaintBoxImageLine(box, screen, row, line, totalWidth);\n\t\treturn;\n\t}\n\tpaintBoxTextLine(box, screen, row, line, totalWidth);\n}\n\nfunction paintBoxLines(box: LayoutBox, screen: string[], totalWidth: number): void {\n\tif (!box.lines) return;\n\tconst offset = box.lineOffset ?? 0;\n\tconst firstRow = Math.max(box.rect.y, box.clip.y, 0);\n\tconst lastRow = Math.min(box.rect.y + box.rect.height, box.clip.y + box.clip.height, screen.length);\n\tfor (let row = firstRow; row < lastRow; row++) {\n\t\tconst sourceLine = box.lines[offset + row - box.rect.y];\n\t\tif (sourceLine !== undefined) paintBoxLine(box, screen, row, sourceLine, totalWidth);\n\t}\n}\n\nfunction paintScrolledKittyImagePlacement(request: ScrolledKittyImagePaintRequest): void {\n\tconst { box, screen, totalWidth, scrollTop, imageLine, imageRow, imageRows } = request;\n\tconst targetRow = Math.max(0, box.rect.y, box.clip.y);\n\tconst hiddenRows = scrollTop - imageRow + (targetRow - box.rect.y);\n\tif (hiddenRows >= imageRows) return;\n\tconst clipBottom = Math.min(screen.length, box.rect.y + box.rect.height, box.clip.y + box.clip.height);\n\tconst visibleRows = Math.min(clipBottom - targetRow, imageRows - hiddenRows);\n\tif (visibleRows <= 0) return;\n\tconst cropped = cropKittyImageLine(imageLine, hiddenRows, visibleRows);\n\tif (boxFillsViewportWidth(box, totalWidth)) screen[targetRow] = cropped;\n}\n\nfunction paintScrolledKittyImage(box: LayoutBox, screen: string[], totalWidth: number): void {\n\tconst scrollView = box.scrollView;\n\tconst contentLines = box.scrollContentLines;\n\tif (!scrollView || !contentLines || scrollView.scrollTop <= 0 || box.rect.height <= 0) return;\n\tfor (let imageRow = scrollView.scrollTop - 1; imageRow >= 0; imageRow--) {\n\t\tconst imageLine = contentLines[imageRow] ?? \"\";\n\t\tconst metadata = getKittyImageMetadata(imageLine);\n\t\tif (metadata) {\n\t\t\tpaintScrolledKittyImagePlacement({\n\t\t\t\tbox,\n\t\t\t\tscreen,\n\t\t\t\ttotalWidth,\n\t\t\t\tscrollTop: scrollView.scrollTop,\n\t\t\t\timageLine,\n\t\t\t\timageRow,\n\t\t\t\timageRows: metadata.rows,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (imageLine !== \"\") return;\n\t}\n}\n\nfunction paintBox(box: LayoutBox, screen: string[], totalWidth: number): void {\n\tpaintBoxLines(box, screen, totalWidth);\n\tfor (const child of box.children) paintBox(child, screen, totalWidth);\n\tpaintScrolledKittyImage(box, screen, totalWidth);\n\tpaintScrollbar(box, screen, totalWidth);\n}\n\nexport function renderLayoutFrame(\n\troot: Component,\n\twidth: number,\n\theight: number,\n\trequestRender: () => void,\n): LayoutFrame {\n\tconst safeWidth = Math.max(1, Math.floor(width));\n\tconst safeHeight = Math.max(1, Math.floor(height));\n\tconst context: LayoutContext = {\n\t\tviewport: { width: safeWidth, height: safeHeight },\n\t\trenderCache: new Map(),\n\t\trequestRender,\n\t\tprimaryScrollView: undefined,\n\t};\n\tconst rootBox = layoutComponent(context, root, 0, 0, safeWidth, safeHeight, {\n\t\tx: 0,\n\t\ty: 0,\n\t\twidth: safeWidth,\n\t\theight: safeHeight,\n\t});\n\tconst lines = Array.from({ length: safeHeight }, () => \"\");\n\tpaintBox(rootBox, lines, safeWidth);\n\treturn {\n\t\troot: rootBox,\n\t\twidth: safeWidth,\n\t\theight: safeHeight,\n\t\tlines,\n\t\t...(context.primaryScrollView === undefined ? {} : { primaryScrollView: context.primaryScrollView }),\n\t};\n}\n\nfunction containsPoint(rect: LayoutRect, x: number, y: number): boolean {\n\treturn x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;\n}\n\nexport function getScrollViewBox(frame: LayoutFrame, scrollView: ScrollView): LayoutBox | undefined {\n\tconst visit = (box: LayoutBox): LayoutBox | undefined => {\n\t\tif (box.scrollView === scrollView) return box;\n\t\tfor (const child of box.children) {\n\t\t\tconst match = visit(child);\n\t\t\tif (match) return match;\n\t\t}\n\t\treturn undefined;\n\t};\n\treturn visit(frame.root);\n}\n\nexport function getScrollViewsAt(frame: LayoutFrame, x: number, y: number): ScrollView[] {\n\tconst result: Array<{ scrollView: ScrollView; depth: number }> = [];\n\tconst visit = (box: LayoutBox, depth: number): void => {\n\t\tif (!containsPoint(box.clip, x, y)) return;\n\t\tif (box.scrollView && containsPoint(box.rect, x, y)) result.push({ scrollView: box.scrollView, depth });\n\t\tfor (const child of box.children) visit(child, depth + 1);\n\t};\n\tvisit(frame.root, 0);\n\tresult.sort((a, b) => b.depth - a.depth);\n\treturn result.map((entry) => entry.scrollView);\n}\n"]}
|
package/dist/stdin-buffer.d.ts
CHANGED
|
@@ -40,6 +40,14 @@ export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
|
|
|
40
40
|
private pasteBuffer;
|
|
41
41
|
private pendingKittyPrintableCodepoint;
|
|
42
42
|
constructor(options?: StdinBufferOptions);
|
|
43
|
+
private clearPendingTimeout;
|
|
44
|
+
private decodeInputChunk;
|
|
45
|
+
private emitDataSequences;
|
|
46
|
+
private finishBracketedPasteIfComplete;
|
|
47
|
+
private continueBracketedPaste;
|
|
48
|
+
private beginBracketedPaste;
|
|
49
|
+
private emitCompleteBufferedSequences;
|
|
50
|
+
private scheduleIncompleteSequenceFlush;
|
|
43
51
|
process(data: string | Buffer): void;
|
|
44
52
|
private emitDataSequence;
|
|
45
53
|
flush(): string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdin-buffer.d.ts","sourceRoot":"","sources":["../src/stdin-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA6OtC,MAAM,MAAM,kBAAkB,GAAG;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,mBAAmB,CAAC;IACjE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAA8C;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,8BAA8B,CAAqB;IAE3D,YAAY,OAAO,GAAE,kBAAuB,EAG3C;IAEM,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoG1C;IAED,OAAO,CAAC,gBAAgB;IAWxB,KAAK,IAAI,MAAM,EAAE,CAchB;IAED,KAAK,IAAI,IAAI,CASZ;IAED,SAAS,IAAI,MAAM,CAElB;IAED,OAAO,IAAI,IAAI,CAEd;CACD","sourcesContent":["/**\n * StdinBuffer buffers input and emits complete sequences.\n *\n * This is necessary because stdin data events can arrive in partial chunks,\n * especially for escape sequences like mouse events. Without buffering,\n * partial sequences can be misinterpreted as regular keypresses.\n *\n * For example, the mouse SGR sequence `\\x1b[<35;20;5m` might arrive as:\n * - Event 1: `\\x1b`\n * - Event 2: `[<35`\n * - Event 3: `;20;5m`\n *\n * The buffer accumulates these until a complete sequence is detected.\n * Call the `process()` method to feed input data.\n *\n * Based on code from OpenTUI (https://github.com/anomalyco/opentui)\n * MIT License - Copyright (c) 2025 opentui\n */\n\nimport { EventEmitter } from \"events\";\n\nconst ESC = \"\\x1b\";\nconst BRACKETED_PASTE_START = \"\\x1b[200~\";\nconst BRACKETED_PASTE_END = \"\\x1b[201~\";\n\n/**\n * Check if a string is a complete escape sequence or needs more data\n */\nfunction isCompleteSequence(data: string): \"complete\" | \"incomplete\" | \"not-escape\" {\n\tif (!data.startsWith(ESC)) {\n\t\treturn \"not-escape\";\n\t}\n\n\tif (data.length === 1) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst afterEsc = data.slice(1);\n\n\t// CSI sequences: ESC [\n\tif (afterEsc.startsWith(\"[\")) {\n\t\t// Check for old-style mouse sequence: ESC[M + 3 bytes\n\t\tif (afterEsc.startsWith(\"[M\")) {\n\t\t\t// Old-style mouse needs ESC[M + 3 bytes = 6 total\n\t\t\treturn data.length >= 6 ? \"complete\" : \"incomplete\";\n\t\t}\n\t\treturn isCompleteCsiSequence(data);\n\t}\n\n\t// OSC sequences: ESC ]\n\tif (afterEsc.startsWith(\"]\")) {\n\t\treturn isCompleteOscSequence(data);\n\t}\n\n\t// DCS sequences: ESC P ... ESC \\ (includes XTVersion responses)\n\tif (afterEsc.startsWith(\"P\")) {\n\t\treturn isCompleteDcsSequence(data);\n\t}\n\n\t// APC sequences: ESC _ ... ESC \\ (includes Kitty graphics responses)\n\tif (afterEsc.startsWith(\"_\")) {\n\t\treturn isCompleteApcSequence(data);\n\t}\n\n\t// SS3 sequences: ESC O\n\tif (afterEsc.startsWith(\"O\")) {\n\t\t// ESC O followed by a single character\n\t\treturn afterEsc.length >= 2 ? \"complete\" : \"incomplete\";\n\t}\n\n\t// Meta key sequences: ESC followed by a single character\n\tif (afterEsc.length === 1) {\n\t\treturn \"complete\";\n\t}\n\n\t// Unknown escape sequence - treat as complete\n\treturn \"complete\";\n}\n\n/**\n * Check if CSI sequence is complete\n * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)\n */\nfunction isCompleteCsiSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}[`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// Need at least ESC [ and one more character\n\tif (data.length < 3) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst payload = data.slice(2);\n\n\t// CSI sequences end with a byte in the range 0x40-0x7E (@-~)\n\t// This includes all letters and several special characters\n\tconst lastChar = payload[payload.length - 1];\n\tconst lastCharCode = lastChar.charCodeAt(0);\n\n\tif (lastCharCode >= 0x40 && lastCharCode <= 0x7e) {\n\t\t// Special handling for SGR mouse sequences\n\t\t// Format: ESC[<B;X;Ym or ESC[<B;X;YM\n\t\tif (payload.startsWith(\"<\")) {\n\t\t\t// Must have format: <digits;digits;digits[Mm]\n\t\t\tconst mouseMatch = /^<\\d+;\\d+;\\d+[Mm]$/.test(payload);\n\t\t\tif (mouseMatch) {\n\t\t\t\treturn \"complete\";\n\t\t\t}\n\t\t\t// If it ends with M or m but doesn't match the pattern, still incomplete\n\t\t\tif (lastChar === \"M\" || lastChar === \"m\") {\n\t\t\t\t// Check if we have the right structure\n\t\t\t\tconst parts = payload.slice(1, -1).split(\";\");\n\t\t\t\tif (parts.length === 3 && parts.every((p) => /^\\d+$/.test(p))) {\n\t\t\t\t\treturn \"complete\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn \"incomplete\";\n\t\t}\n\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if OSC sequence is complete\n * OSC sequences: ESC ] ... ST (where ST is ESC \\ or BEL)\n */\nfunction isCompleteOscSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}]`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// OSC sequences end with ST (ESC \\) or BEL (\\x07)\n\tif (data.endsWith(`${ESC}\\\\`) || data.endsWith(\"\\x07\")) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if DCS (Device Control String) sequence is complete\n * DCS sequences: ESC P ... ST (where ST is ESC \\)\n * Used for XTVersion responses like ESC P >| ... ESC \\\n */\nfunction isCompleteDcsSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}P`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// DCS sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if APC (Application Program Command) sequence is complete\n * APC sequences: ESC _ ... ST (where ST is ESC \\)\n * Used for Kitty graphics responses like ESC _ G ... ESC \\\n */\nfunction isCompleteApcSequence(data: string): \"complete\" | \"incomplete\" {\n\tif (!data.startsWith(`${ESC}_`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// APC sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Split accumulated buffer into complete sequences\n */\nfunction parseUnmodifiedKittyPrintableCodepoint(sequence: string): number | undefined {\n\tconst match = sequence.match(/^\\x1b\\[(\\d+)(?::\\d*)?(?::\\d+)?u$/);\n\tif (!match) return undefined;\n\n\tconst codepoint = parseInt(match[1]!, 10);\n\treturn codepoint >= 32 ? codepoint : undefined;\n}\n\nfunction extractCompleteSequences(buffer: string): { sequences: string[]; remainder: string } {\n\tconst sequences: string[] = [];\n\tlet pos = 0;\n\n\twhile (pos < buffer.length) {\n\t\tconst remaining = buffer.slice(pos);\n\n\t\t// Try to extract a sequence starting at this position\n\t\tif (remaining.startsWith(ESC)) {\n\t\t\t// Find the end of this escape sequence\n\t\t\tlet seqEnd = 1;\n\t\t\twhile (seqEnd <= remaining.length) {\n\t\t\t\tconst candidate = remaining.slice(0, seqEnd);\n\t\t\t\tconst status = isCompleteSequence(candidate);\n\n\t\t\t\tif (status === \"complete\") {\n\t\t\t\t\t// WezTerm with enable_kitty_keyboard sends the Escape key press as a\n\t\t\t\t\t// raw '\\x1b' byte (simple text path in encode_kitty, ignoring\n\t\t\t\t\t// DISAMBIGUATE_ESCAPE_CODES) and the release as a full Kitty CSI-u\n\t\t\t\t\t// sequence. These arrive concatenated as '\\x1b\\x1b[27;...u'.\n\t\t\t\t\t// The buffer would normally treat '\\x1b\\x1b' as a complete meta-key\n\t\t\t\t\t// sequence (ESC + single char), leaving '[27;...u' to be typed as\n\t\t\t\t\t// plain text. If the character immediately following '\\x1b\\x1b'\n\t\t\t\t\t// would begin a new escape sequence, emit only the first ESC and\n\t\t\t\t\t// restart from the second.\n\t\t\t\t\tif (candidate === \"\\x1b\\x1b\") {\n\t\t\t\t\t\tconst nextChar = remaining[seqEnd];\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\tnextChar === \"[\" || // CSI\n\t\t\t\t\t\t\tnextChar === \"]\" || // OSC\n\t\t\t\t\t\t\tnextChar === \"O\" || // SS3\n\t\t\t\t\t\t\tnextChar === \"P\" || // DCS\n\t\t\t\t\t\t\tnextChar === \"_\" // APC\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tsequences.push(ESC);\n\t\t\t\t\t\t\tpos += 1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (status === \"incomplete\") {\n\t\t\t\t\tseqEnd++;\n\t\t\t\t} else {\n\t\t\t\t\t// Should not happen when starting with ESC\n\t\t\t\t\tsequences.push(candidate);\n\t\t\t\t\tpos += seqEnd;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (seqEnd > remaining.length) {\n\t\t\t\treturn { sequences, remainder: remaining };\n\t\t\t}\n\t\t} else {\n\t\t\t// Not an escape sequence - take a single character\n\t\t\tsequences.push(remaining[0]!);\n\t\t\tpos++;\n\t\t}\n\t}\n\n\treturn { sequences, remainder: \"\" };\n}\n\nexport type StdinBufferOptions = {\n\t/**\n\t * Maximum time to wait for sequence completion (default: 10ms)\n\t * After this time, the buffer is flushed even if incomplete\n\t */\n\ttimeout?: number;\n};\n\nexport type StdinBufferEventMap = {\n\tdata: [string];\n\tpaste: [string];\n};\n\n/**\n * Buffers stdin input and emits complete sequences via the 'data' event.\n * Handles partial escape sequences that arrive across multiple chunks.\n */\nexport class StdinBuffer extends EventEmitter<StdinBufferEventMap> {\n\tprivate buffer: string = \"\";\n\tprivate timeout: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly timeoutMs: number;\n\tprivate pasteMode: boolean = false;\n\tprivate pasteBuffer: string = \"\";\n\tprivate pendingKittyPrintableCodepoint: number | undefined;\n\n\tconstructor(options: StdinBufferOptions = {}) {\n\t\tsuper();\n\t\tthis.timeoutMs = options.timeout ?? 10;\n\t}\n\n\tpublic process(data: string | Buffer): void {\n\t\t// Clear any pending timeout\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\t// Handle high-byte conversion (for compatibility with parseKeypress)\n\t\t// If buffer has single byte > 127, convert to ESC + (byte - 128)\n\t\tlet str: string;\n\t\tif (Buffer.isBuffer(data)) {\n\t\t\tif (data.length === 1 && data[0]! > 127) {\n\t\t\t\tconst byte = data[0]! - 128;\n\t\t\t\tstr = `\\x1b${String.fromCharCode(byte)}`;\n\t\t\t} else {\n\t\t\t\tstr = data.toString();\n\t\t\t}\n\t\t} else {\n\t\t\tstr = data;\n\t\t}\n\n\t\tif (str.length === 0 && this.buffer.length === 0) {\n\t\t\tthis.emitDataSequence(\"\");\n\t\t\treturn;\n\t\t}\n\n\t\tthis.buffer += str;\n\n\t\tif (this.pasteMode) {\n\t\t\tthis.pasteBuffer += this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst startIndex = this.buffer.indexOf(BRACKETED_PASTE_START);\n\t\tif (startIndex !== -1) {\n\t\t\tif (startIndex > 0) {\n\t\t\t\tconst beforePaste = this.buffer.slice(0, startIndex);\n\t\t\t\tconst result = extractCompleteSequences(beforePaste);\n\t\t\t\tfor (const sequence of result.sequences) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\tthis.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);\n\t\t\tthis.pasteMode = true;\n\t\t\tthis.pasteBuffer = this.buffer;\n\t\t\tthis.buffer = \"\";\n\n\t\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\t\tif (endIndex !== -1) {\n\t\t\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\t\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\n\t\t\t\tthis.pasteMode = false;\n\t\t\t\tthis.pasteBuffer = \"\";\n\t\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\n\t\t\t\tthis.emit(\"paste\", pastedContent);\n\n\t\t\t\tif (remaining.length > 0) {\n\t\t\t\t\tthis.process(remaining);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst result = extractCompleteSequences(this.buffer);\n\t\tthis.buffer = result.remainder;\n\n\t\tfor (const sequence of result.sequences) {\n\t\t\tthis.emitDataSequence(sequence);\n\t\t}\n\n\t\tif (this.buffer.length > 0) {\n\t\t\tthis.timeout = setTimeout(() => {\n\t\t\t\tconst flushed = this.flush();\n\n\t\t\t\tfor (const sequence of flushed) {\n\t\t\t\t\tthis.emitDataSequence(sequence);\n\t\t\t\t}\n\t\t\t}, this.timeoutMs);\n\t\t}\n\t}\n\n\tprivate emitDataSequence(sequence: string): void {\n\t\tconst rawCodepoint = sequence.length === 1 ? sequence.codePointAt(0) : undefined;\n\t\tif (rawCodepoint !== undefined && rawCodepoint === this.pendingKittyPrintableCodepoint) {\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.pendingKittyPrintableCodepoint = parseUnmodifiedKittyPrintableCodepoint(sequence);\n\t\tthis.emit(\"data\", sequence);\n\t}\n\n\tflush(): string[] {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\n\t\tif (this.buffer.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst sequences = [this.buffer];\n\t\tthis.buffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\treturn sequences;\n\t}\n\n\tclear(): void {\n\t\tif (this.timeout) {\n\t\t\tclearTimeout(this.timeout);\n\t\t\tthis.timeout = null;\n\t\t}\n\t\tthis.buffer = \"\";\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t}\n\n\tgetBuffer(): string {\n\t\treturn this.buffer;\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear();\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stdin-buffer.d.ts","sourceRoot":"","sources":["../src/stdin-buffer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AA2NtC,MAAM,MAAM,kBAAkB,GAAG;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;CAChB,CAAC;AAEF;;;GAGG;AACH,qBAAa,WAAY,SAAQ,YAAY,CAAC,mBAAmB,CAAC;IACjE,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,OAAO,CAA8C;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,8BAA8B,CAAqB;IAE3D,YAAY,OAAO,GAAE,kBAAuB,EAG3C;IAED,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,8BAA8B;IAYtC,OAAO,CAAC,sBAAsB;IAM9B,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,6BAA6B;IAMrC,OAAO,CAAC,+BAA+B;IAKhC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqB1C;IAED,OAAO,CAAC,gBAAgB;IAWxB,KAAK,IAAI,MAAM,EAAE,CAWhB;IAED,KAAK,IAAI,IAAI,CAMZ;IAED,SAAS,IAAI,MAAM,CAElB;IAED,OAAO,IAAI,IAAI,CAEd;CACD","sourcesContent":["/**\n * StdinBuffer buffers input and emits complete sequences.\n *\n * This is necessary because stdin data events can arrive in partial chunks,\n * especially for escape sequences like mouse events. Without buffering,\n * partial sequences can be misinterpreted as regular keypresses.\n *\n * For example, the mouse SGR sequence `\\x1b[<35;20;5m` might arrive as:\n * - Event 1: `\\x1b`\n * - Event 2: `[<35`\n * - Event 3: `;20;5m`\n *\n * The buffer accumulates these until a complete sequence is detected.\n * Call the `process()` method to feed input data.\n *\n * Based on code from OpenTUI (https://github.com/anomalyco/opentui)\n * MIT License - Copyright (c) 2025 opentui\n */\n\nimport { EventEmitter } from \"events\";\n\nconst ESC = \"\\x1b\";\nconst BRACKETED_PASTE_START = \"\\x1b[200~\";\nconst BRACKETED_PASTE_END = \"\\x1b[201~\";\nconst ESCAPE_SEQUENCE_PREFIXES = new Set([\"[\", \"]\", \"O\", \"P\", \"_\"]);\n\ntype SequenceCompletion = \"complete\" | \"incomplete\";\ntype EscapeSequenceCompletion = SequenceCompletion | \"not-escape\";\n\n/**\n * Check if a string is a complete escape sequence or needs more data\n */\nfunction isCompleteSequence(data: string): EscapeSequenceCompletion {\n\tif (!data.startsWith(ESC)) {\n\t\treturn \"not-escape\";\n\t}\n\n\tif (data.length === 1) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst afterEsc = data.slice(1);\n\n\t// CSI sequences: ESC [\n\tif (afterEsc.startsWith(\"[\")) {\n\t\t// Check for old-style mouse sequence: ESC[M + 3 bytes\n\t\tif (afterEsc.startsWith(\"[M\")) {\n\t\t\t// Old-style mouse needs ESC[M + 3 bytes = 6 total\n\t\t\treturn data.length >= 6 ? \"complete\" : \"incomplete\";\n\t\t}\n\t\treturn isCompleteCsiSequence(data);\n\t}\n\n\t// OSC sequences: ESC ]\n\tif (afterEsc.startsWith(\"]\")) {\n\t\treturn isCompleteOscSequence(data);\n\t}\n\n\t// DCS sequences: ESC P ... ESC \\ (includes XTVersion responses)\n\tif (afterEsc.startsWith(\"P\")) {\n\t\treturn isCompleteDcsSequence(data);\n\t}\n\n\t// APC sequences: ESC _ ... ESC \\ (includes Kitty graphics responses)\n\tif (afterEsc.startsWith(\"_\")) {\n\t\treturn isCompleteApcSequence(data);\n\t}\n\n\t// SS3 sequences: ESC O\n\tif (afterEsc.startsWith(\"O\")) {\n\t\t// ESC O followed by a single character\n\t\treturn afterEsc.length >= 2 ? \"complete\" : \"incomplete\";\n\t}\n\n\t// Meta key sequences: ESC followed by a single character\n\tif (afterEsc.length === 1) {\n\t\treturn \"complete\";\n\t}\n\n\t// Unknown escape sequence - treat as complete\n\treturn \"complete\";\n}\n\n/**\n * Check if CSI sequence is complete\n * CSI sequences: ESC [ ... followed by a final byte (0x40-0x7E)\n */\nfunction getSgrMouseSequenceCompletion(payload: string, lastCharacter: string): SequenceCompletion {\n\tif (/^<\\d+;\\d+;\\d+[Mm]$/.test(payload)) return \"complete\";\n\tif (lastCharacter === \"M\" || lastCharacter === \"m\") {\n\t\tconst parts = payload.slice(1, -1).split(\";\");\n\t\tif (parts.length === 3 && parts.every((part) => /^\\d+$/.test(part))) return \"complete\";\n\t}\n\treturn \"incomplete\";\n}\n\nfunction isCompleteCsiSequence(data: string): SequenceCompletion {\n\tif (!data.startsWith(`${ESC}[`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// Need at least ESC [ and one more character\n\tif (data.length < 3) {\n\t\treturn \"incomplete\";\n\t}\n\n\tconst payload = data.slice(2);\n\n\t// CSI sequences end with a byte in the range 0x40-0x7E (@-~)\n\t// This includes all letters and several special characters\n\tconst lastChar = payload[payload.length - 1];\n\tconst lastCharCode = lastChar.charCodeAt(0);\n\n\tif (lastCharCode < 0x40 || lastCharCode > 0x7e) return \"incomplete\";\n\n\t// Special handling for SGR mouse sequences: ESC[<B;X;Ym or ESC[<B;X;YM\n\tif (payload.startsWith(\"<\")) return getSgrMouseSequenceCompletion(payload, lastChar);\n\n\treturn \"complete\";\n}\n\n/**\n * Check if OSC sequence is complete\n * OSC sequences: ESC ] ... ST (where ST is ESC \\ or BEL)\n */\nfunction isCompleteOscSequence(data: string): SequenceCompletion {\n\tif (!data.startsWith(`${ESC}]`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// OSC sequences end with ST (ESC \\) or BEL (\\x07)\n\tif (data.endsWith(`${ESC}\\\\`) || data.endsWith(\"\\x07\")) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if DCS (Device Control String) sequence is complete\n * DCS sequences: ESC P ... ST (where ST is ESC \\)\n * Used for XTVersion responses like ESC P >| ... ESC \\\n */\nfunction isCompleteDcsSequence(data: string): SequenceCompletion {\n\tif (!data.startsWith(`${ESC}P`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// DCS sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Check if APC (Application Program Command) sequence is complete\n * APC sequences: ESC _ ... ST (where ST is ESC \\)\n * Used for Kitty graphics responses like ESC _ G ... ESC \\\n */\nfunction isCompleteApcSequence(data: string): SequenceCompletion {\n\tif (!data.startsWith(`${ESC}_`)) {\n\t\treturn \"complete\";\n\t}\n\n\t// APC sequences end with ST (ESC \\)\n\tif (data.endsWith(`${ESC}\\\\`)) {\n\t\treturn \"complete\";\n\t}\n\n\treturn \"incomplete\";\n}\n\n/**\n * Split accumulated buffer into complete sequences\n */\nfunction parseUnmodifiedKittyPrintableCodepoint(sequence: string): number | undefined {\n\tconst match = sequence.match(/^\\x1b\\[(\\d+)(?::\\d*)?(?::\\d+)?u$/);\n\tif (!match) return undefined;\n\n\tconst codepoint = parseInt(match[1]!, 10);\n\treturn codepoint >= 32 ? codepoint : undefined;\n}\n\ninterface EscapeSequenceExtraction {\n\tcompletion: SequenceCompletion;\n\tsequence: string;\n\tconsumed: number;\n}\n\ninterface ExtractedSequences {\n\tsequences: string[];\n\tremainder: string;\n}\n\nfunction shouldSplitAdjacentEscapeSequence(candidate: string, nextCharacter: string | undefined): boolean {\n\treturn candidate === `${ESC}${ESC}` && nextCharacter !== undefined && ESCAPE_SEQUENCE_PREFIXES.has(nextCharacter);\n}\n\nfunction extractEscapeSequence(remaining: string): EscapeSequenceExtraction {\n\tlet sequenceEnd = 1;\n\twhile (sequenceEnd <= remaining.length) {\n\t\tconst candidate = remaining.slice(0, sequenceEnd);\n\t\tconst status = isCompleteSequence(candidate);\n\t\tif (status === \"incomplete\") {\n\t\t\tsequenceEnd++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (status === \"complete\" && shouldSplitAdjacentEscapeSequence(candidate, remaining[sequenceEnd])) {\n\t\t\treturn { completion: \"complete\", sequence: ESC, consumed: 1 };\n\t\t}\n\t\treturn { completion: \"complete\", sequence: candidate, consumed: sequenceEnd };\n\t}\n\treturn { completion: \"incomplete\", sequence: \"\", consumed: 0 };\n}\n\nfunction extractCompleteSequences(buffer: string): ExtractedSequences {\n\tconst sequences: string[] = [];\n\tlet position = 0;\n\n\twhile (position < buffer.length) {\n\t\tconst remaining = buffer.slice(position);\n\t\tif (!remaining.startsWith(ESC)) {\n\t\t\tsequences.push(remaining[0]!);\n\t\t\tposition++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst extraction = extractEscapeSequence(remaining);\n\t\tif (extraction.completion === \"incomplete\") return { sequences, remainder: remaining };\n\t\tsequences.push(extraction.sequence);\n\t\tposition += extraction.consumed;\n\t}\n\n\treturn { sequences, remainder: \"\" };\n}\n\nexport type StdinBufferOptions = {\n\t/**\n\t * Maximum time to wait for sequence completion (default: 10ms)\n\t * After this time, the buffer is flushed even if incomplete\n\t */\n\ttimeout?: number;\n};\n\nexport type StdinBufferEventMap = {\n\tdata: [string];\n\tpaste: [string];\n};\n\n/**\n * Buffers stdin input and emits complete sequences via the 'data' event.\n * Handles partial escape sequences that arrive across multiple chunks.\n */\nexport class StdinBuffer extends EventEmitter<StdinBufferEventMap> {\n\tprivate buffer: string = \"\";\n\tprivate timeout: ReturnType<typeof setTimeout> | null = null;\n\tprivate readonly timeoutMs: number;\n\tprivate pasteMode: boolean = false;\n\tprivate pasteBuffer: string = \"\";\n\tprivate pendingKittyPrintableCodepoint: number | undefined;\n\n\tconstructor(options: StdinBufferOptions = {}) {\n\t\tsuper();\n\t\tthis.timeoutMs = options.timeout ?? 10;\n\t}\n\n\tprivate clearPendingTimeout(): void {\n\t\tif (!this.timeout) return;\n\t\tclearTimeout(this.timeout);\n\t\tthis.timeout = null;\n\t}\n\n\tprivate decodeInputChunk(data: string | Buffer): string {\n\t\tif (!Buffer.isBuffer(data)) return data;\n\t\tif (data.length !== 1 || data[0]! <= 127) return data.toString();\n\t\treturn `${ESC}${String.fromCharCode(data[0]! - 128)}`;\n\t}\n\n\tprivate emitDataSequences(sequences: readonly string[]): void {\n\t\tfor (const sequence of sequences) this.emitDataSequence(sequence);\n\t}\n\n\tprivate finishBracketedPasteIfComplete(): void {\n\t\tconst endIndex = this.pasteBuffer.indexOf(BRACKETED_PASTE_END);\n\t\tif (endIndex === -1) return;\n\t\tconst pastedContent = this.pasteBuffer.slice(0, endIndex);\n\t\tconst remaining = this.pasteBuffer.slice(endIndex + BRACKETED_PASTE_END.length);\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\tthis.emit(\"paste\", pastedContent);\n\t\tif (remaining.length > 0) this.process(remaining);\n\t}\n\n\tprivate continueBracketedPaste(): void {\n\t\tthis.pasteBuffer += this.buffer;\n\t\tthis.buffer = \"\";\n\t\tthis.finishBracketedPasteIfComplete();\n\t}\n\n\tprivate beginBracketedPaste(startIndex: number): void {\n\t\tif (startIndex > 0) {\n\t\t\tconst beforePaste = this.buffer.slice(0, startIndex);\n\t\t\tthis.emitDataSequences(extractCompleteSequences(beforePaste).sequences);\n\t\t}\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\tthis.buffer = this.buffer.slice(startIndex + BRACKETED_PASTE_START.length);\n\t\tthis.pasteMode = true;\n\t\tthis.pasteBuffer = this.buffer;\n\t\tthis.buffer = \"\";\n\t\tthis.finishBracketedPasteIfComplete();\n\t}\n\n\tprivate emitCompleteBufferedSequences(): void {\n\t\tconst result = extractCompleteSequences(this.buffer);\n\t\tthis.buffer = result.remainder;\n\t\tthis.emitDataSequences(result.sequences);\n\t}\n\n\tprivate scheduleIncompleteSequenceFlush(): void {\n\t\tif (this.buffer.length === 0) return;\n\t\tthis.timeout = setTimeout(() => this.emitDataSequences(this.flush()), this.timeoutMs);\n\t}\n\n\tpublic process(data: string | Buffer): void {\n\t\tthis.clearPendingTimeout();\n\t\tconst chunk = this.decodeInputChunk(data);\n\t\tif (chunk.length === 0 && this.buffer.length === 0) {\n\t\t\tthis.emitDataSequence(\"\");\n\t\t\treturn;\n\t\t}\n\t\tthis.buffer += chunk;\n\n\t\tif (this.pasteMode) {\n\t\t\tthis.continueBracketedPaste();\n\t\t\treturn;\n\t\t}\n\t\tconst pasteStartIndex = this.buffer.indexOf(BRACKETED_PASTE_START);\n\t\tif (pasteStartIndex !== -1) {\n\t\t\tthis.beginBracketedPaste(pasteStartIndex);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.emitCompleteBufferedSequences();\n\t\tthis.scheduleIncompleteSequenceFlush();\n\t}\n\n\tprivate emitDataSequence(sequence: string): void {\n\t\tconst rawCodepoint = sequence.length === 1 ? sequence.codePointAt(0) : undefined;\n\t\tif (rawCodepoint !== undefined && rawCodepoint === this.pendingKittyPrintableCodepoint) {\n\t\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.pendingKittyPrintableCodepoint = parseUnmodifiedKittyPrintableCodepoint(sequence);\n\t\tthis.emit(\"data\", sequence);\n\t}\n\n\tflush(): string[] {\n\t\tthis.clearPendingTimeout();\n\n\t\tif (this.buffer.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst sequences = [this.buffer];\n\t\tthis.buffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t\treturn sequences;\n\t}\n\n\tclear(): void {\n\t\tthis.clearPendingTimeout();\n\t\tthis.buffer = \"\";\n\t\tthis.pasteMode = false;\n\t\tthis.pasteBuffer = \"\";\n\t\tthis.pendingKittyPrintableCodepoint = undefined;\n\t}\n\n\tgetBuffer(): string {\n\t\treturn this.buffer;\n\t}\n\n\tdestroy(): void {\n\t\tthis.clear();\n\t}\n}\n"]}
|