1ch 0.1.0 → 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/README.md +87 -185
- package/dist/index.d.ts +280 -15
- package/dist/index.js +650 -83
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -14,6 +14,23 @@ function preserveTableTag(source, target) {
|
|
|
14
14
|
if (toTaggedLine(source)[TABLE_TAG]) toTaggedLine(target)[TABLE_TAG] = true;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// src/termui/scroll-tag.ts
|
|
18
|
+
var SCROLL_KEY = /* @__PURE__ */ Symbol("scrollRegion");
|
|
19
|
+
function toTagged(line) {
|
|
20
|
+
return line;
|
|
21
|
+
}
|
|
22
|
+
function markScrollRegion(line, meta) {
|
|
23
|
+
toTagged(line)[SCROLL_KEY] = meta;
|
|
24
|
+
return line;
|
|
25
|
+
}
|
|
26
|
+
function getScrollRegionMeta(line) {
|
|
27
|
+
return toTagged(line)[SCROLL_KEY];
|
|
28
|
+
}
|
|
29
|
+
function preserveScrollTag(source, target) {
|
|
30
|
+
const meta = toTagged(source)[SCROLL_KEY];
|
|
31
|
+
if (meta) toTagged(target)[SCROLL_KEY] = meta;
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
// src/termui/base.ts
|
|
18
35
|
function normalizedWidth(width) {
|
|
19
36
|
if (!Number.isFinite(width)) return 0;
|
|
@@ -90,6 +107,11 @@ function padLine(line, w) {
|
|
|
90
107
|
if (used === w) return line;
|
|
91
108
|
return [...line, { text: " ".repeat(w - used), noSelect: true }];
|
|
92
109
|
}
|
|
110
|
+
function truncate(text, width, ellipsis = "\u2026") {
|
|
111
|
+
if (text.length <= width) return text;
|
|
112
|
+
if (width <= ellipsis.length) return ellipsis.slice(0, width);
|
|
113
|
+
return text.slice(0, width - ellipsis.length) + ellipsis;
|
|
114
|
+
}
|
|
93
115
|
function wrapText(text, w) {
|
|
94
116
|
if (w <= 0) return [""];
|
|
95
117
|
if (text.length <= w) return [text];
|
|
@@ -113,20 +135,27 @@ function wrapText(text, w) {
|
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
// src/termui/layout.ts
|
|
116
|
-
function borderTop(w,
|
|
138
|
+
function borderTop(w, opts) {
|
|
117
139
|
const width = normalizedWidth(w);
|
|
118
140
|
if (width <= 0) return [];
|
|
119
|
-
if (width === 1) return [{ text: "\u250C", color, noSelect: true }];
|
|
141
|
+
if (width === 1) return [{ text: "\u250C", color: opts?.color, noSelect: true }];
|
|
142
|
+
const { title, color, action } = opts ?? {};
|
|
143
|
+
const innerWidth = width - 2;
|
|
144
|
+
const titleBudget = innerWidth - (action ? action.label.length + 2 : 0) - 4;
|
|
145
|
+
let titleStr = "";
|
|
120
146
|
if (title) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
const titleStr = "\u2500 " + safeTitle + " ";
|
|
124
|
-
const remaining = Math.max(0, width - 2 - titleStr.length);
|
|
125
|
-
return [
|
|
126
|
-
{ text: "\u250C" + titleStr + hl(remaining) + "\u2510", color, noSelect: true }
|
|
127
|
-
];
|
|
147
|
+
const safeTitle = title.length > titleBudget ? title.slice(0, Math.max(0, titleBudget)) : title;
|
|
148
|
+
titleStr = safeTitle.length > 0 ? "\u2500 " + safeTitle + " " : "";
|
|
128
149
|
}
|
|
129
|
-
|
|
150
|
+
const fillWidth = Math.max(0, innerWidth - titleStr.length - (action ? action.label.length + 2 : 0));
|
|
151
|
+
if (!action) {
|
|
152
|
+
return [{ text: "\u250C" + titleStr + hl(fillWidth) + "\u2510", color, noSelect: true }];
|
|
153
|
+
}
|
|
154
|
+
return [
|
|
155
|
+
{ text: "\u250C" + titleStr + hl(fillWidth), color, noSelect: true },
|
|
156
|
+
{ text: " " + action.label + " ", color: action.color ?? color, onClick: action.onClick, noSelect: true },
|
|
157
|
+
{ text: "\u2510", color, noSelect: true }
|
|
158
|
+
];
|
|
130
159
|
}
|
|
131
160
|
function borderBottom(w, color) {
|
|
132
161
|
const width = normalizedWidth(w);
|
|
@@ -148,7 +177,7 @@ function box(content, opts) {
|
|
|
148
177
|
const inner = width - 2;
|
|
149
178
|
const contentBlock = content(inner);
|
|
150
179
|
const result = [];
|
|
151
|
-
result.push(borderTop(width, opts?.title, bc));
|
|
180
|
+
result.push(borderTop(width, { title: opts?.title, color: bc, action: opts?.action }));
|
|
152
181
|
for (const line of contentBlock) {
|
|
153
182
|
result.push([
|
|
154
183
|
{ text: "\u2502", color: bc, noSelect: true },
|
|
@@ -205,11 +234,128 @@ function vstack(...children) {
|
|
|
205
234
|
return result;
|
|
206
235
|
};
|
|
207
236
|
}
|
|
208
|
-
function separator(color) {
|
|
209
|
-
return (w) =>
|
|
237
|
+
function separator(color, char) {
|
|
238
|
+
return (w) => {
|
|
239
|
+
const c = char ?? "\u2500";
|
|
240
|
+
return [[{ text: c.repeat(normalizedWidth(w)), color, noSelect: true }]];
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function blank(count = 1) {
|
|
244
|
+
return (w) => {
|
|
245
|
+
const line = [" ".repeat(normalizedWidth(w))];
|
|
246
|
+
const result = [];
|
|
247
|
+
for (let i = 0; i < count; i++) result.push(line);
|
|
248
|
+
return result;
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function divider(label, opts) {
|
|
252
|
+
return (w) => {
|
|
253
|
+
const width = normalizedWidth(w);
|
|
254
|
+
const c = opts?.char ?? "\u2500";
|
|
255
|
+
const padded = ` ${label} `;
|
|
256
|
+
if (padded.length >= width) {
|
|
257
|
+
const lc = opts?.labelColor ?? opts?.color;
|
|
258
|
+
return [[lc ? { text: padded.slice(0, width), color: lc } : padded.slice(0, width)]];
|
|
259
|
+
}
|
|
260
|
+
const remaining = width - padded.length;
|
|
261
|
+
const leftLen = Math.floor(remaining / 2);
|
|
262
|
+
const rightLen = remaining - leftLen;
|
|
263
|
+
const lineColor = opts?.color;
|
|
264
|
+
const labelColor = opts?.labelColor ?? opts?.color;
|
|
265
|
+
const line = [
|
|
266
|
+
{ text: c.repeat(leftLen), color: lineColor, noSelect: true },
|
|
267
|
+
labelColor ? { text: padded, color: labelColor } : padded,
|
|
268
|
+
{ text: c.repeat(rightLen), color: lineColor, noSelect: true }
|
|
269
|
+
];
|
|
270
|
+
return [line];
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function grid(cells, opts) {
|
|
274
|
+
return (w) => {
|
|
275
|
+
const width = normalizedWidth(w);
|
|
276
|
+
const cols = Math.max(1, opts.cols);
|
|
277
|
+
const gap = normalizedWidth(opts.gap ?? 1);
|
|
278
|
+
const rowGap = normalizedWidth(opts.rowGap ?? 0);
|
|
279
|
+
const totalGap = gap * (cols - 1);
|
|
280
|
+
const cellWidth = Math.max(1, Math.floor((width - totalGap) / cols));
|
|
281
|
+
const gapStr = gap > 0 ? " ".repeat(gap) : "";
|
|
282
|
+
const result = [];
|
|
283
|
+
for (let rowStart = 0; rowStart < cells.length; rowStart += cols) {
|
|
284
|
+
if (rowStart > 0 && rowGap > 0) {
|
|
285
|
+
const emptyLine = [" ".repeat(width)];
|
|
286
|
+
for (let g = 0; g < rowGap; g++) result.push(emptyLine);
|
|
287
|
+
}
|
|
288
|
+
const rowCells = cells.slice(rowStart, rowStart + cols);
|
|
289
|
+
const blocks = rowCells.map((cell) => cell(cellWidth));
|
|
290
|
+
const maxLines = Math.max(0, ...blocks.map((b) => b.length));
|
|
291
|
+
for (let i = 0; i < maxLines; i++) {
|
|
292
|
+
const line = [];
|
|
293
|
+
for (let j = 0; j < cols; j++) {
|
|
294
|
+
if (j > 0 && gapStr) line.push(gapStr);
|
|
295
|
+
const block = blocks[j];
|
|
296
|
+
if (block && block[i]) {
|
|
297
|
+
line.push(...padLine(block[i], cellWidth));
|
|
298
|
+
} else {
|
|
299
|
+
line.push(" ".repeat(cellWidth));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
result.push(padLine(line, width));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function scroll(content, opts) {
|
|
309
|
+
return (w) => {
|
|
310
|
+
const width = normalizedWidth(w);
|
|
311
|
+
const maxH = normalizedWidth(opts.maxHeight);
|
|
312
|
+
const contentW = Math.max(1, width - 1);
|
|
313
|
+
const fullBlock = content(contentW);
|
|
314
|
+
const totalLines = fullBlock.length;
|
|
315
|
+
if (totalLines <= maxH) {
|
|
316
|
+
return fullBlock.map((line) => padLine(line, width));
|
|
317
|
+
}
|
|
318
|
+
const maxOffset = Math.max(0, totalLines - maxH);
|
|
319
|
+
const offset = Math.min(maxOffset, Math.max(0, opts.offset ?? 0));
|
|
320
|
+
const visible = fullBlock.slice(offset, offset + maxH);
|
|
321
|
+
const thumbHeight = Math.max(1, Math.round(maxH / totalLines * maxH));
|
|
322
|
+
const thumbStart = Math.round(offset / maxOffset * (maxH - thumbHeight));
|
|
323
|
+
const result = [];
|
|
324
|
+
for (let i = 0; i < visible.length; i++) {
|
|
325
|
+
const inThumb = i >= thumbStart && i < thumbStart + thumbHeight;
|
|
326
|
+
const scrollChar = inThumb ? "\u2588" : "\u2591";
|
|
327
|
+
const line = [
|
|
328
|
+
...padLine(visible[i], contentW),
|
|
329
|
+
{ text: scrollChar, color: opts.scrollbarColor, noSelect: true }
|
|
330
|
+
];
|
|
331
|
+
if (opts.onScroll) {
|
|
332
|
+
markScrollRegion(line, { onScroll: opts.onScroll, totalLines, maxHeight: maxH, offset });
|
|
333
|
+
}
|
|
334
|
+
result.push(line);
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
};
|
|
210
338
|
}
|
|
211
|
-
function
|
|
212
|
-
return (w) =>
|
|
339
|
+
function padding(content, opts) {
|
|
340
|
+
return (w) => {
|
|
341
|
+
const width = normalizedWidth(w);
|
|
342
|
+
const t = normalizedWidth(opts?.top ?? opts?.y ?? 0);
|
|
343
|
+
const b = normalizedWidth(opts?.bottom ?? opts?.y ?? 0);
|
|
344
|
+
const l = normalizedWidth(opts?.left ?? opts?.x ?? 0);
|
|
345
|
+
const r = normalizedWidth(opts?.right ?? opts?.x ?? 0);
|
|
346
|
+
const innerW = Math.max(1, width - l - r);
|
|
347
|
+
const block = content(innerW);
|
|
348
|
+
const result = [];
|
|
349
|
+
const emptyLine = [" ".repeat(width)];
|
|
350
|
+
for (let i = 0; i < t; i++) result.push(emptyLine);
|
|
351
|
+
const leftPad = l > 0 ? " ".repeat(l) : "";
|
|
352
|
+
for (const line of block) {
|
|
353
|
+
const padded = leftPad ? [leftPad, ...line] : line;
|
|
354
|
+
result.push(padLine(padded, width));
|
|
355
|
+
}
|
|
356
|
+
for (let i = 0; i < b; i++) result.push(emptyLine);
|
|
357
|
+
return result;
|
|
358
|
+
};
|
|
213
359
|
}
|
|
214
360
|
|
|
215
361
|
// src/termui/table.ts
|
|
@@ -311,7 +457,8 @@ function table(columns, data, opts) {
|
|
|
311
457
|
const text = padStr + pad(cellLine, innerW, col.align === "right") + padStr;
|
|
312
458
|
const color = typeof col.color === "function" ? col.color(row[col.key], row) : col.color;
|
|
313
459
|
const dim = typeof col.dim === "function" ? col.dim(row[col.key], row) : col.dim;
|
|
314
|
-
|
|
460
|
+
const cellClick = typeof col.onClick === "function" ? () => col.onClick(row[col.key], row) : void 0;
|
|
461
|
+
dataLine.push({ text, color: color || void 0, dim: dim || void 0, onClick: cellClick, cell: i });
|
|
315
462
|
dataLine.push({ text: "\u2502", color: bc, noSelect: true });
|
|
316
463
|
}
|
|
317
464
|
result.push(dataLine);
|
|
@@ -384,6 +531,92 @@ function badge(label, opts) {
|
|
|
384
531
|
onClick: opts?.onClick
|
|
385
532
|
};
|
|
386
533
|
}
|
|
534
|
+
function kv(pairs, opts) {
|
|
535
|
+
return (w) => {
|
|
536
|
+
const width = normalizedWidth(w);
|
|
537
|
+
const sep = opts?.separator ?? " ";
|
|
538
|
+
const maxKey = opts?.keyWidth ?? Math.max(0, ...pairs.map((p) => p.key.length));
|
|
539
|
+
const result = [];
|
|
540
|
+
for (const pair of pairs) {
|
|
541
|
+
const keyText = pad(pair.key, maxKey);
|
|
542
|
+
const keyColor = pair.keyColor ?? opts?.keyColor;
|
|
543
|
+
const keySeg = keyColor ? { text: keyText, color: keyColor } : keyText;
|
|
544
|
+
if (typeof pair.value === "string") {
|
|
545
|
+
const valColor = pair.valueColor ?? opts?.valueColor;
|
|
546
|
+
const valSeg = valColor || pair.onClick ? { text: pair.value, color: valColor, onClick: pair.onClick } : pair.value;
|
|
547
|
+
result.push(padLine([keySeg, sep, valSeg], width));
|
|
548
|
+
} else {
|
|
549
|
+
result.push(padLine([keySeg, sep, ...pair.value], width));
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function callout(content, opts) {
|
|
556
|
+
return (w) => {
|
|
557
|
+
const width = normalizedWidth(w);
|
|
558
|
+
const accent = opts?.accent ?? opts?.color;
|
|
559
|
+
const innerWidth = Math.max(1, width - 2);
|
|
560
|
+
let block;
|
|
561
|
+
if (typeof content === "string") {
|
|
562
|
+
const wrapped = wrapText(content, innerWidth);
|
|
563
|
+
block = wrapped.map((line) => {
|
|
564
|
+
const seg = opts?.color ? [{ text: line, color: opts.color }] : [line];
|
|
565
|
+
return padLine(seg, innerWidth);
|
|
566
|
+
});
|
|
567
|
+
} else {
|
|
568
|
+
block = content(innerWidth);
|
|
569
|
+
}
|
|
570
|
+
const result = [];
|
|
571
|
+
if (opts?.label) {
|
|
572
|
+
const labelLine = [
|
|
573
|
+
{ text: "\u2590", color: accent, noSelect: true },
|
|
574
|
+
" ",
|
|
575
|
+
{ text: opts.label, color: accent, bold: true }
|
|
576
|
+
];
|
|
577
|
+
result.push(padLine(labelLine, width));
|
|
578
|
+
}
|
|
579
|
+
for (const line of block) {
|
|
580
|
+
result.push([
|
|
581
|
+
{ text: "\u2590", color: accent, noSelect: true },
|
|
582
|
+
" ",
|
|
583
|
+
...padLine(line, innerWidth)
|
|
584
|
+
]);
|
|
585
|
+
}
|
|
586
|
+
return result;
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function checkbox(checked, label, opts) {
|
|
590
|
+
return (w) => {
|
|
591
|
+
const width = normalizedWidth(w);
|
|
592
|
+
const check = checked ? "\u2713" : " ";
|
|
593
|
+
const boxSeg = {
|
|
594
|
+
text: `[${check}]`,
|
|
595
|
+
color: opts?.checkColor ?? opts?.color,
|
|
596
|
+
onClick: opts?.onClick
|
|
597
|
+
};
|
|
598
|
+
const labelSeg = opts?.onClick ? { text: ` ${label}`, color: opts.color, onClick: opts.onClick } : opts?.color ? { text: ` ${label}`, color: opts.color } : ` ${label}`;
|
|
599
|
+
return [padLine([boxSeg, labelSeg], width)];
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
function inputField(value, opts) {
|
|
603
|
+
return (w) => {
|
|
604
|
+
const totalWidth = normalizedWidth(opts?.width ?? w);
|
|
605
|
+
const innerWidth = Math.max(1, totalWidth - 2);
|
|
606
|
+
const bc = opts?.borderColor;
|
|
607
|
+
const focused = opts?.focused ?? false;
|
|
608
|
+
const display = value || opts?.placeholder || "";
|
|
609
|
+
const isPlaceholder = !value && opts?.placeholder;
|
|
610
|
+
const textTruncated = display.length > innerWidth ? display.slice(display.length - innerWidth) : display;
|
|
611
|
+
const padded = pad(textTruncated + (focused ? "\u2588" : ""), innerWidth);
|
|
612
|
+
const line = [
|
|
613
|
+
{ text: focused ? "\u2502" : "[", color: bc, noSelect: true },
|
|
614
|
+
isPlaceholder ? { text: padded, dim: true, color: opts?.color, onClick: opts?.onClick } : opts?.color || opts?.onClick ? { text: padded, color: opts?.color, onClick: opts?.onClick } : padded,
|
|
615
|
+
{ text: focused ? "\u2502" : "]", color: bc, noSelect: true }
|
|
616
|
+
];
|
|
617
|
+
return [padLine(line, w)];
|
|
618
|
+
};
|
|
619
|
+
}
|
|
387
620
|
|
|
388
621
|
// src/termui/json.ts
|
|
389
622
|
function fromJsonNode(node) {
|
|
@@ -447,7 +680,7 @@ function tree(data, opts) {
|
|
|
447
680
|
const line = [];
|
|
448
681
|
if (prefix) line.push({ text: prefix, color: opts?.branchColor ?? opts?.color, noSelect: true });
|
|
449
682
|
line.push({ text: connector, color: opts?.branchColor ?? opts?.color, noSelect: true });
|
|
450
|
-
line.push({ text: node.label, color: hasKids ? opts?.color : opts?.leafColor ?? opts?.color });
|
|
683
|
+
line.push({ text: node.label, color: hasKids ? opts?.color : opts?.leafColor ?? opts?.color, onClick: node.onClick });
|
|
451
684
|
result.push(padLine(line, w));
|
|
452
685
|
if (hasKids) {
|
|
453
686
|
walk(node.children, prefix + (last ? " " : "\u2502 "));
|
|
@@ -464,9 +697,13 @@ function list(items, opts) {
|
|
|
464
697
|
const ordered = opts?.ordered ?? false;
|
|
465
698
|
const digits = String(items.length).length;
|
|
466
699
|
for (let i = 0; i < items.length; i++) {
|
|
700
|
+
const item = items[i];
|
|
701
|
+
const text = typeof item === "string" ? item : item.text;
|
|
702
|
+
const itemColor = typeof item === "string" ? opts?.color : item.color ?? opts?.color;
|
|
703
|
+
const itemClick = typeof item === "string" ? void 0 : item.onClick;
|
|
467
704
|
const prefix = ordered ? ` ${String(i + 1).padStart(digits, " ")}. ` : " * ";
|
|
468
705
|
const indent = " ".repeat(prefix.length);
|
|
469
|
-
const wrapped = wrapText(
|
|
706
|
+
const wrapped = wrapText(text, w - prefix.length);
|
|
470
707
|
for (let j = 0; j < wrapped.length; j++) {
|
|
471
708
|
const line = [];
|
|
472
709
|
if (j === 0) {
|
|
@@ -474,7 +711,7 @@ function list(items, opts) {
|
|
|
474
711
|
} else {
|
|
475
712
|
line.push(indent);
|
|
476
713
|
}
|
|
477
|
-
line.push(
|
|
714
|
+
line.push(itemColor || itemClick ? { text: wrapped[j], color: itemColor, onClick: itemClick } : wrapped[j]);
|
|
478
715
|
result.push(padLine(line, w));
|
|
479
716
|
}
|
|
480
717
|
}
|
|
@@ -482,6 +719,9 @@ function list(items, opts) {
|
|
|
482
719
|
};
|
|
483
720
|
}
|
|
484
721
|
|
|
722
|
+
// src/termui/code-diff.ts
|
|
723
|
+
import { createPatch } from "diff";
|
|
724
|
+
|
|
485
725
|
// src/tokenize.ts
|
|
486
726
|
function rules(pairs) {
|
|
487
727
|
return pairs.map(([pattern, type]) => ({
|
|
@@ -1023,6 +1263,7 @@ function tokenColor(type, theme) {
|
|
|
1023
1263
|
function code(source, opts) {
|
|
1024
1264
|
const t = opts?.theme ?? dark;
|
|
1025
1265
|
const tokens = tokenize(source, opts?.language);
|
|
1266
|
+
const copyAction = opts?.copyable !== false ? { label: "cp", onClick: () => navigator.clipboard.writeText(source), color: t.semantic.frameMuted } : void 0;
|
|
1026
1267
|
return box(
|
|
1027
1268
|
lines(
|
|
1028
1269
|
tokens.length > 0 ? tokens.map(
|
|
@@ -1031,11 +1272,12 @@ function code(source, opts) {
|
|
|
1031
1272
|
),
|
|
1032
1273
|
{
|
|
1033
1274
|
title: opts?.title ?? (opts?.language ? `code:${opts.language}` : "code"),
|
|
1034
|
-
borderColor: opts?.borderColor ?? t.markdown.codeBorder
|
|
1275
|
+
borderColor: opts?.borderColor ?? t.markdown.codeBorder,
|
|
1276
|
+
action: opts?.action ?? copyAction
|
|
1035
1277
|
}
|
|
1036
1278
|
);
|
|
1037
1279
|
}
|
|
1038
|
-
function
|
|
1280
|
+
function unifiedDiff(value, opts) {
|
|
1039
1281
|
const t = opts?.theme ?? dark;
|
|
1040
1282
|
const addC = opts?.addColor ?? t.semantic.success;
|
|
1041
1283
|
const remC = opts?.removeColor ?? t.semantic.danger;
|
|
@@ -1049,6 +1291,72 @@ function diff(value, opts) {
|
|
|
1049
1291
|
});
|
|
1050
1292
|
});
|
|
1051
1293
|
}
|
|
1294
|
+
function computeDiff(before, after, opts) {
|
|
1295
|
+
const patch = createPatch("", before, after, "", "", { context: opts?.context ?? 3 });
|
|
1296
|
+
const hunks = patch.replace(/\r\n/g, "\n").split("\n").filter(
|
|
1297
|
+
(l) => !l.startsWith("Index:") && !l.startsWith("====") && !l.startsWith("---") && !l.startsWith("+++")
|
|
1298
|
+
).join("\n").trim();
|
|
1299
|
+
return unifiedDiff(hunks, opts);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// src/termui/chart.ts
|
|
1303
|
+
function fmtNum(n) {
|
|
1304
|
+
if (Math.abs(n) >= 1e3) return (n / 1e3).toFixed(1) + "k";
|
|
1305
|
+
if (Number.isInteger(n)) return String(n);
|
|
1306
|
+
return n.toFixed(1);
|
|
1307
|
+
}
|
|
1308
|
+
function chart(data, opts) {
|
|
1309
|
+
return (w) => {
|
|
1310
|
+
const width = normalizedWidth(w);
|
|
1311
|
+
const height = opts?.height ?? 8;
|
|
1312
|
+
const color = opts?.color;
|
|
1313
|
+
const axisColor = opts?.axisColor;
|
|
1314
|
+
if (data.length === 0 || width < 4 || height < 2) return [padLine([], width)];
|
|
1315
|
+
const min = Math.min(...data);
|
|
1316
|
+
const max = Math.max(...data);
|
|
1317
|
+
const range = max - min || 1;
|
|
1318
|
+
const minLabel = fmtNum(min);
|
|
1319
|
+
const maxLabel = fmtNum(max);
|
|
1320
|
+
const labelW = Math.max(minLabel.length, maxLabel.length);
|
|
1321
|
+
const axisW = labelW + 2;
|
|
1322
|
+
const plotW = Math.max(1, width - axisW);
|
|
1323
|
+
const points = [];
|
|
1324
|
+
if (data.length === 1) {
|
|
1325
|
+
for (let i = 0; i < plotW; i++) points.push(data[0]);
|
|
1326
|
+
} else {
|
|
1327
|
+
for (let i = 0; i < plotW; i++) {
|
|
1328
|
+
const idx = i / (plotW - 1) * (data.length - 1);
|
|
1329
|
+
const lo = Math.floor(idx);
|
|
1330
|
+
const hi = Math.min(lo + 1, data.length - 1);
|
|
1331
|
+
const t = idx - lo;
|
|
1332
|
+
points.push(data[lo] * (1 - t) + data[hi] * t);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
const rows = points.map((v) => Math.round((v - min) / range * (height - 1)));
|
|
1336
|
+
const result = [];
|
|
1337
|
+
for (let row = height - 1; row >= 0; row--) {
|
|
1338
|
+
const line = [];
|
|
1339
|
+
let label = "";
|
|
1340
|
+
if (row === height - 1) label = maxLabel;
|
|
1341
|
+
else if (row === 0) label = minLabel;
|
|
1342
|
+
line.push({ text: label.padStart(labelW) + " \u2502", color: axisColor, noSelect: true });
|
|
1343
|
+
let text = "";
|
|
1344
|
+
for (let col = 0; col < plotW; col++) {
|
|
1345
|
+
const val = rows[col];
|
|
1346
|
+
if (val === row) text += "\u2584";
|
|
1347
|
+
else if (opts?.fill && val > row) text += "\u2588";
|
|
1348
|
+
else text += " ";
|
|
1349
|
+
}
|
|
1350
|
+
line.push(color ? { text, color } : text);
|
|
1351
|
+
result.push(padLine(line, width));
|
|
1352
|
+
}
|
|
1353
|
+
const xAxis = [
|
|
1354
|
+
{ text: " ".repeat(labelW) + " \u2514" + "\u2500".repeat(plotW), color: axisColor, noSelect: true }
|
|
1355
|
+
];
|
|
1356
|
+
result.push(padLine(xAxis, width));
|
|
1357
|
+
return result;
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1052
1360
|
|
|
1053
1361
|
// src/document/common.ts
|
|
1054
1362
|
function headingLevelKey(level) {
|
|
@@ -1386,16 +1694,23 @@ function renderMarkdownBlock(block, theme, overrides) {
|
|
|
1386
1694
|
};
|
|
1387
1695
|
case "separator":
|
|
1388
1696
|
return separator(overrides?.separator ?? md.separator);
|
|
1389
|
-
case "code":
|
|
1697
|
+
case "code": {
|
|
1698
|
+
const codeSource = block.lines.join("\n");
|
|
1390
1699
|
return box(
|
|
1391
1700
|
lines(
|
|
1392
1701
|
block.lines.length > 0 ? block.lines.map((line) => [{ text: line || " ", color: overrides?.codeText ?? md.codeText }]) : [[{ text: " ", color: overrides?.codeEmpty ?? md.codeEmpty, dim: true }]]
|
|
1393
1702
|
),
|
|
1394
1703
|
{
|
|
1395
1704
|
title: block.language ? `code:${block.language}` : "code",
|
|
1396
|
-
borderColor: overrides?.codeBorder ?? md.codeBorder
|
|
1705
|
+
borderColor: overrides?.codeBorder ?? md.codeBorder,
|
|
1706
|
+
action: {
|
|
1707
|
+
label: "cp",
|
|
1708
|
+
onClick: () => navigator.clipboard.writeText(codeSource),
|
|
1709
|
+
color: theme.semantic.frameMuted
|
|
1710
|
+
}
|
|
1397
1711
|
}
|
|
1398
1712
|
);
|
|
1713
|
+
}
|
|
1399
1714
|
}
|
|
1400
1715
|
}
|
|
1401
1716
|
function fromMarkdown(markdown, opts) {
|
|
@@ -2048,9 +2363,6 @@ function asNumber(value) {
|
|
|
2048
2363
|
function asBoolean(value) {
|
|
2049
2364
|
return typeof value === "boolean" ? value : void 0;
|
|
2050
2365
|
}
|
|
2051
|
-
function asStringArray(value) {
|
|
2052
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
2053
|
-
}
|
|
2054
2366
|
function asNumberArray(value) {
|
|
2055
2367
|
if (!Array.isArray(value)) return void 0;
|
|
2056
2368
|
const out = [];
|
|
@@ -2068,6 +2380,13 @@ function asRecordArray(value) {
|
|
|
2068
2380
|
}
|
|
2069
2381
|
return out;
|
|
2070
2382
|
}
|
|
2383
|
+
function asBoxAction(value) {
|
|
2384
|
+
if (!isRecord4(value)) return void 0;
|
|
2385
|
+
const label = asString(value.label);
|
|
2386
|
+
const onClick = typeof value.onClick === "function" ? value.onClick : void 0;
|
|
2387
|
+
if (!label || !onClick) return void 0;
|
|
2388
|
+
return { label, onClick, color: asString(value.color) };
|
|
2389
|
+
}
|
|
2071
2390
|
function isMarkdownOverrides(value) {
|
|
2072
2391
|
if (!isRecord4(value)) return false;
|
|
2073
2392
|
if (value.headingMarker !== void 0 && typeof value.headingMarker !== "string") return false;
|
|
@@ -2309,40 +2628,51 @@ function compileRaw(p, children) {
|
|
|
2309
2628
|
])
|
|
2310
2629
|
);
|
|
2311
2630
|
}
|
|
2312
|
-
function
|
|
2631
|
+
function compileSpanSegments(children, inherited) {
|
|
2313
2632
|
const segments = [];
|
|
2314
2633
|
for (const child of children) {
|
|
2315
2634
|
if (child.type === "t-text") {
|
|
2316
|
-
|
|
2635
|
+
const text = child.text ?? "";
|
|
2636
|
+
if (inherited && (inherited.color || inherited.bg || inherited.bold !== void 0 || inherited.dim !== void 0 || inherited.inverted !== void 0 || inherited.blink !== void 0 || inherited.onClick)) {
|
|
2637
|
+
segments.push({ text, ...inherited });
|
|
2638
|
+
} else {
|
|
2639
|
+
segments.push(text);
|
|
2640
|
+
}
|
|
2317
2641
|
continue;
|
|
2318
2642
|
}
|
|
2319
2643
|
if (child.type === "t-span") {
|
|
2320
2644
|
const cp = child.props;
|
|
2321
|
-
const
|
|
2322
|
-
const
|
|
2323
|
-
const
|
|
2324
|
-
const
|
|
2325
|
-
const
|
|
2326
|
-
const
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2645
|
+
const color = asString(cp.color) ?? inherited?.color;
|
|
2646
|
+
const bg = asString(cp.bg) ?? inherited?.bg;
|
|
2647
|
+
const bold = asBoolean(cp.bold) ?? inherited?.bold;
|
|
2648
|
+
const dim = asBoolean(cp.dim) ?? inherited?.dim;
|
|
2649
|
+
const inverted = asBoolean(cp.inverted) ?? inherited?.inverted;
|
|
2650
|
+
const blink = asBoolean(cp.blink) ?? inherited?.blink;
|
|
2651
|
+
const onClick = (typeof cp.onClick === "function" ? cp.onClick : void 0) ?? inherited?.onClick;
|
|
2652
|
+
const style = { color, bg, bold, dim, inverted, blink, onClick };
|
|
2653
|
+
if (cp.text) {
|
|
2654
|
+
const text = asString(cp.text);
|
|
2655
|
+
if (color || bg || bold !== void 0 || dim !== void 0 || inverted !== void 0 || blink !== void 0 || onClick) {
|
|
2656
|
+
segments.push({ text, ...style });
|
|
2657
|
+
} else {
|
|
2658
|
+
segments.push(text);
|
|
2659
|
+
}
|
|
2660
|
+
} else if (child.children.length > 0) {
|
|
2661
|
+
segments.push(...compileSpanSegments(child.children, style));
|
|
2662
|
+
}
|
|
2663
|
+
} else {
|
|
2664
|
+
const text = extractTermText(child.children);
|
|
2665
|
+
if (inherited && (inherited.color || inherited.bg || inherited.bold !== void 0 || inherited.dim !== void 0 || inherited.inverted !== void 0 || inherited.blink !== void 0 || inherited.onClick)) {
|
|
2666
|
+
segments.push({ text, ...inherited });
|
|
2338
2667
|
} else {
|
|
2339
2668
|
segments.push(text);
|
|
2340
2669
|
}
|
|
2341
|
-
} else {
|
|
2342
|
-
segments.push(extractTermText(child.children));
|
|
2343
2670
|
}
|
|
2344
2671
|
}
|
|
2345
|
-
return
|
|
2672
|
+
return segments;
|
|
2673
|
+
}
|
|
2674
|
+
function compileLine(children) {
|
|
2675
|
+
return lines([compileSpanSegments(children)]);
|
|
2346
2676
|
}
|
|
2347
2677
|
function compileTermNode(node, theme, mdOverrides) {
|
|
2348
2678
|
if (node.type === "t-text") {
|
|
@@ -2368,13 +2698,25 @@ function compileTermNode(node, theme, mdOverrides) {
|
|
|
2368
2698
|
const gap = asNumber(p.gap) ?? 0;
|
|
2369
2699
|
return box(stackWithGap(ch, gap), {
|
|
2370
2700
|
title: asString(p.title),
|
|
2371
|
-
borderColor: asString(p.borderColor) ?? theme.semantic.border
|
|
2701
|
+
borderColor: asString(p.borderColor) ?? theme.semantic.border,
|
|
2702
|
+
action: asBoxAction(p.action)
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
case "t-pad": {
|
|
2706
|
+
const ch = compileChildLayouts(node.children, theme, mdOverrides);
|
|
2707
|
+
return padding(ch.length > 0 ? stackWithGap(ch, 0) : blank(), {
|
|
2708
|
+
x: asNumber(p.x),
|
|
2709
|
+
y: asNumber(p.y),
|
|
2710
|
+
top: asNumber(p.top),
|
|
2711
|
+
bottom: asNumber(p.bottom),
|
|
2712
|
+
left: asNumber(p.left),
|
|
2713
|
+
right: asNumber(p.right)
|
|
2372
2714
|
});
|
|
2373
2715
|
}
|
|
2374
2716
|
case "t-separator":
|
|
2375
|
-
return separator(asString(p.color) ?? theme.semantic.border);
|
|
2717
|
+
return separator(asString(p.color) ?? theme.semantic.border, asString(p.char));
|
|
2376
2718
|
case "t-blank":
|
|
2377
|
-
return blank();
|
|
2719
|
+
return blank(asNumber(p.lines) ?? 1);
|
|
2378
2720
|
case "t-raw":
|
|
2379
2721
|
return compileRaw(p, node.children);
|
|
2380
2722
|
case "t-line":
|
|
@@ -2447,10 +2789,20 @@ function compileTermNode(node, theme, mdOverrides) {
|
|
|
2447
2789
|
language: asString(p.language),
|
|
2448
2790
|
title: asString(p.title),
|
|
2449
2791
|
borderColor: asString(p.borderColor) ?? theme.markdown.codeBorder,
|
|
2450
|
-
theme
|
|
2792
|
+
theme,
|
|
2793
|
+
copyable: asBoolean(p.copyable),
|
|
2794
|
+
action: asBoxAction(p.action)
|
|
2451
2795
|
});
|
|
2452
2796
|
case "t-diff":
|
|
2453
|
-
return
|
|
2797
|
+
return unifiedDiff(asString(p.value) ?? "", {
|
|
2798
|
+
addColor: asString(p.addColor),
|
|
2799
|
+
removeColor: asString(p.removeColor),
|
|
2800
|
+
metaColor: asString(p.metaColor),
|
|
2801
|
+
theme
|
|
2802
|
+
});
|
|
2803
|
+
case "t-diff-compute":
|
|
2804
|
+
return computeDiff(asString(p.before) ?? "", asString(p.after) ?? "", {
|
|
2805
|
+
context: asNumber(p.context),
|
|
2454
2806
|
addColor: asString(p.addColor),
|
|
2455
2807
|
removeColor: asString(p.removeColor),
|
|
2456
2808
|
metaColor: asString(p.metaColor),
|
|
@@ -2490,7 +2842,7 @@ function compileTermNode(node, theme, mdOverrides) {
|
|
|
2490
2842
|
};
|
|
2491
2843
|
}
|
|
2492
2844
|
case "t-list":
|
|
2493
|
-
return list(
|
|
2845
|
+
return list(Array.isArray(p.items) ? p.items : [], {
|
|
2494
2846
|
ordered: asBoolean(p.ordered),
|
|
2495
2847
|
bulletColor: asString(p.bulletColor),
|
|
2496
2848
|
color: asString(p.color)
|
|
@@ -2499,6 +2851,8 @@ function compileTermNode(node, theme, mdOverrides) {
|
|
|
2499
2851
|
const label = asString(p.label) ?? "";
|
|
2500
2852
|
const btnColor = asString(p.color);
|
|
2501
2853
|
const btnBg = asString(p.bg);
|
|
2854
|
+
const btnBold = asBoolean(p.bold);
|
|
2855
|
+
const btnDim = asBoolean(p.dim);
|
|
2502
2856
|
const onClick = typeof p.onClick === "function" ? p.onClick : void 0;
|
|
2503
2857
|
const resolvedBtnColor = btnColor ?? theme.components.button.fg;
|
|
2504
2858
|
const resolvedBtnBg = btnBg ?? theme.components.button.bg;
|
|
@@ -2508,11 +2862,90 @@ function compileTermNode(node, theme, mdOverrides) {
|
|
|
2508
2862
|
const rp = Math.max(0, w - lp - decorated.length);
|
|
2509
2863
|
const segments = [];
|
|
2510
2864
|
if (lp > 0) segments.push(" ".repeat(lp));
|
|
2511
|
-
segments.push({ text: decorated, color: resolvedBtnColor, bg: resolvedBtnBg, onClick });
|
|
2865
|
+
segments.push({ text: decorated, color: resolvedBtnColor, bg: resolvedBtnBg, bold: btnBold, dim: btnDim, onClick });
|
|
2512
2866
|
if (rp > 0) segments.push(" ".repeat(rp));
|
|
2513
2867
|
return [segments];
|
|
2514
2868
|
};
|
|
2515
2869
|
}
|
|
2870
|
+
case "t-badge": {
|
|
2871
|
+
const label = asString(p.label) ?? "";
|
|
2872
|
+
const badgeColor = asString(p.color) ?? theme.components.badge.fg;
|
|
2873
|
+
const badgeBg = asString(p.bg) ?? theme.components.badge.bg;
|
|
2874
|
+
const onClick = typeof p.onClick === "function" ? p.onClick : void 0;
|
|
2875
|
+
return lines([[{ text: ` ${label} `, color: badgeColor, bg: badgeBg, onClick }]]);
|
|
2876
|
+
}
|
|
2877
|
+
case "t-divider":
|
|
2878
|
+
return divider(asString(p.label) ?? "", {
|
|
2879
|
+
color: asString(p.color) ?? theme.semantic.border,
|
|
2880
|
+
char: asString(p.char),
|
|
2881
|
+
labelColor: asString(p.labelColor)
|
|
2882
|
+
});
|
|
2883
|
+
case "t-kv":
|
|
2884
|
+
return kv(Array.isArray(p.pairs) ? p.pairs : [], {
|
|
2885
|
+
separator: asString(p.separator),
|
|
2886
|
+
keyColor: asString(p.keyColor),
|
|
2887
|
+
valueColor: asString(p.valueColor),
|
|
2888
|
+
keyWidth: asNumber(p.keyWidth)
|
|
2889
|
+
});
|
|
2890
|
+
case "t-callout": {
|
|
2891
|
+
const ch = compileChildLayouts(node.children, theme, mdOverrides);
|
|
2892
|
+
const text = asString(p.text);
|
|
2893
|
+
const content = text ?? (ch.length > 0 ? stackWithGap(ch, 0) : blank());
|
|
2894
|
+
return callout(content, {
|
|
2895
|
+
color: asString(p.color),
|
|
2896
|
+
accent: asString(p.accent),
|
|
2897
|
+
label: asString(p.label)
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
case "t-chart":
|
|
2901
|
+
return chart(
|
|
2902
|
+
Array.isArray(p.data) ? p.data.filter((v) => typeof v === "number") : [],
|
|
2903
|
+
{
|
|
2904
|
+
height: asNumber(p.height),
|
|
2905
|
+
color: asString(p.color),
|
|
2906
|
+
axisColor: asString(p.axisColor) ?? theme.semantic.border,
|
|
2907
|
+
fill: asBoolean(p.fill)
|
|
2908
|
+
}
|
|
2909
|
+
);
|
|
2910
|
+
case "t-scroll": {
|
|
2911
|
+
const ch = compileChildLayouts(node.children, theme, mdOverrides);
|
|
2912
|
+
const content = ch.length > 0 ? stackWithGap(ch, 0) : blank();
|
|
2913
|
+
const onScroll = typeof p.onScroll === "function" ? p.onScroll : void 0;
|
|
2914
|
+
return scroll(content, {
|
|
2915
|
+
maxHeight: asNumber(p.maxHeight) ?? 10,
|
|
2916
|
+
offset: asNumber(p.offset),
|
|
2917
|
+
scrollbarColor: asString(p.scrollbarColor) ?? theme.semantic.border,
|
|
2918
|
+
onScroll
|
|
2919
|
+
});
|
|
2920
|
+
}
|
|
2921
|
+
case "t-grid": {
|
|
2922
|
+
const ch = compileChildLayouts(node.children, theme, mdOverrides);
|
|
2923
|
+
return grid(ch, {
|
|
2924
|
+
cols: asNumber(p.cols) ?? 2,
|
|
2925
|
+
gap: asNumber(p.gap),
|
|
2926
|
+
rowGap: asNumber(p.rowGap)
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
case "t-checkbox": {
|
|
2930
|
+
const checked = asBoolean(p.checked) ?? false;
|
|
2931
|
+
const label = asString(p.label) ?? "";
|
|
2932
|
+
const onChange = typeof p.onChange === "function" ? p.onChange : void 0;
|
|
2933
|
+
const onClick = typeof p.onClick === "function" ? p.onClick : onChange ? () => onChange(!checked) : void 0;
|
|
2934
|
+
return checkbox(checked, label, {
|
|
2935
|
+
color: asString(p.color),
|
|
2936
|
+
checkColor: asString(p.checkColor),
|
|
2937
|
+
onClick
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
case "t-input":
|
|
2941
|
+
return inputField(asString(p.value) ?? "", {
|
|
2942
|
+
placeholder: asString(p.placeholder),
|
|
2943
|
+
width: asNumber(p.width),
|
|
2944
|
+
focused: asBoolean(p.focused),
|
|
2945
|
+
color: asString(p.color),
|
|
2946
|
+
borderColor: asString(p.borderColor) ?? theme.semantic.border,
|
|
2947
|
+
onClick: typeof p.onClick === "function" ? p.onClick : void 0
|
|
2948
|
+
});
|
|
2516
2949
|
default: {
|
|
2517
2950
|
const ch = compileChildLayouts(node.children, theme, mdOverrides);
|
|
2518
2951
|
return ch.length > 0 ? stackWithGap(ch, 0) : null;
|
|
@@ -2745,6 +3178,7 @@ function reconcileLine(prevLine, nextLine) {
|
|
|
2745
3178
|
reconciled[i] = prevSeg !== void 0 && sameSegment(prevSeg, nextSeg) ? prevSeg : nextSeg;
|
|
2746
3179
|
}
|
|
2747
3180
|
preserveTableTag(nextLine, reconciled);
|
|
3181
|
+
preserveScrollTag(nextLine, reconciled);
|
|
2748
3182
|
return reconciled;
|
|
2749
3183
|
}
|
|
2750
3184
|
function reconcileBlock(prevBlock, nextBlock) {
|
|
@@ -2869,31 +3303,71 @@ var TermTable = memo(
|
|
|
2869
3303
|
return true;
|
|
2870
3304
|
}
|
|
2871
3305
|
);
|
|
3306
|
+
var TermScrollRegion = memo(function TermScrollRegion2({ lines: lines2, meta, theme }) {
|
|
3307
|
+
return /* @__PURE__ */ jsx2(
|
|
3308
|
+
"div",
|
|
3309
|
+
{
|
|
3310
|
+
className: "termui-scroll-region",
|
|
3311
|
+
onWheel: (e) => {
|
|
3312
|
+
e.preventDefault();
|
|
3313
|
+
const step = e.deltaY > 0 ? 1 : -1;
|
|
3314
|
+
const maxOffset = Math.max(0, meta.totalLines - meta.maxHeight);
|
|
3315
|
+
const newOffset = Math.min(maxOffset, Math.max(0, meta.offset + step));
|
|
3316
|
+
if (newOffset !== meta.offset) meta.onScroll(newOffset);
|
|
3317
|
+
},
|
|
3318
|
+
children: lines2.map((line, i) => /* @__PURE__ */ jsx2(TermLine, { line, theme }, i))
|
|
3319
|
+
}
|
|
3320
|
+
);
|
|
3321
|
+
});
|
|
2872
3322
|
var TermBlock = memo(
|
|
2873
3323
|
function TermBlock2({ block, theme }) {
|
|
2874
3324
|
const elements = [];
|
|
2875
3325
|
let tableLines = null;
|
|
2876
3326
|
let tableStart = 0;
|
|
3327
|
+
let scrollLines = null;
|
|
3328
|
+
let scrollStart = 0;
|
|
3329
|
+
let scrollMeta = null;
|
|
2877
3330
|
const flushTable = () => {
|
|
2878
3331
|
if (tableLines) {
|
|
2879
3332
|
elements.push(/* @__PURE__ */ jsx2(TermTable, { lines: tableLines, theme }, `t${tableStart}`));
|
|
2880
3333
|
tableLines = null;
|
|
2881
3334
|
}
|
|
2882
3335
|
};
|
|
3336
|
+
const flushScroll = () => {
|
|
3337
|
+
if (scrollLines && scrollMeta) {
|
|
3338
|
+
elements.push(
|
|
3339
|
+
/* @__PURE__ */ jsx2(TermScrollRegion, { lines: scrollLines, meta: scrollMeta, theme }, `s${scrollStart}`)
|
|
3340
|
+
);
|
|
3341
|
+
scrollLines = null;
|
|
3342
|
+
scrollMeta = null;
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
2883
3345
|
for (let i = 0; i < block.length; i++) {
|
|
2884
3346
|
const line = block[i];
|
|
3347
|
+
const sm = getScrollRegionMeta(line);
|
|
2885
3348
|
if (isTableLine(line)) {
|
|
3349
|
+
flushScroll();
|
|
2886
3350
|
if (!tableLines) {
|
|
2887
3351
|
tableLines = [];
|
|
2888
3352
|
tableStart = i;
|
|
2889
3353
|
}
|
|
2890
3354
|
tableLines.push(line);
|
|
3355
|
+
} else if (sm) {
|
|
3356
|
+
flushTable();
|
|
3357
|
+
if (!scrollLines) {
|
|
3358
|
+
scrollLines = [];
|
|
3359
|
+
scrollStart = i;
|
|
3360
|
+
scrollMeta = sm;
|
|
3361
|
+
}
|
|
3362
|
+
scrollLines.push(line);
|
|
2891
3363
|
} else {
|
|
2892
3364
|
flushTable();
|
|
3365
|
+
flushScroll();
|
|
2893
3366
|
elements.push(/* @__PURE__ */ jsx2(TermLine, { line, theme }, i));
|
|
2894
3367
|
}
|
|
2895
3368
|
}
|
|
2896
3369
|
flushTable();
|
|
3370
|
+
flushScroll();
|
|
2897
3371
|
return /* @__PURE__ */ jsx2(Fragment, { children: elements });
|
|
2898
3372
|
},
|
|
2899
3373
|
(prev, next) => prev.block === next.block && prev.theme === next.theme
|
|
@@ -3014,21 +3488,24 @@ function TermDocument({
|
|
|
3014
3488
|
}
|
|
3015
3489
|
|
|
3016
3490
|
// src/term-react.tsx
|
|
3017
|
-
import { createElement } from "react";
|
|
3491
|
+
import { createElement, useCallback, useEffect as useEffect3, useState as useState2 } from "react";
|
|
3018
3492
|
function TVStack({ children, gap }) {
|
|
3019
3493
|
return createElement("t-vstack", { gap }, children);
|
|
3020
3494
|
}
|
|
3021
3495
|
function THStack({ children, gap, widths }) {
|
|
3022
3496
|
return createElement("t-hstack", { gap, widths }, children);
|
|
3023
3497
|
}
|
|
3024
|
-
function TBox({ children, title, borderColor, gap }) {
|
|
3025
|
-
return createElement("t-box", { title, borderColor, gap }, children);
|
|
3498
|
+
function TBox({ children, title, borderColor, gap, action }) {
|
|
3499
|
+
return createElement("t-box", { title, borderColor, gap, action }, children);
|
|
3500
|
+
}
|
|
3501
|
+
function TSeparator({ color, char }) {
|
|
3502
|
+
return createElement("t-separator", { color, char });
|
|
3026
3503
|
}
|
|
3027
|
-
function
|
|
3028
|
-
return createElement("t-
|
|
3504
|
+
function TBlank({ lines: lines2 } = {}) {
|
|
3505
|
+
return createElement("t-blank", { lines: lines2 });
|
|
3029
3506
|
}
|
|
3030
|
-
function
|
|
3031
|
-
return createElement("t-
|
|
3507
|
+
function TBadge({ label, color, bg, onClick }) {
|
|
3508
|
+
return createElement("t-badge", { label, color, bg, onClick });
|
|
3032
3509
|
}
|
|
3033
3510
|
function TRaw({ children, text, color, bg, bold, dim, blink, inverted, onClick }) {
|
|
3034
3511
|
return createElement(
|
|
@@ -3090,25 +3567,31 @@ function THtml({ value, themeOverrides }) {
|
|
|
3090
3567
|
function TLine({ children }) {
|
|
3091
3568
|
return createElement("t-line", {}, children);
|
|
3092
3569
|
}
|
|
3093
|
-
function TSpan({ children, text, color, bg, bold, dim, inverted, blink }) {
|
|
3570
|
+
function TSpan({ children, text, color, bg, bold, dim, inverted, blink, onClick }) {
|
|
3094
3571
|
return createElement(
|
|
3095
3572
|
"t-span",
|
|
3096
|
-
{ text, color, bg, bold, dim, inverted, blink },
|
|
3573
|
+
{ text, color, bg, bold, dim, inverted, blink, onClick },
|
|
3097
3574
|
children
|
|
3098
3575
|
);
|
|
3099
3576
|
}
|
|
3577
|
+
function TPad({ children, x, y, top, bottom, left, right }) {
|
|
3578
|
+
return createElement("t-pad", { x, y, top, bottom, left, right }, children);
|
|
3579
|
+
}
|
|
3100
3580
|
function TLayout({ layout }) {
|
|
3101
3581
|
return createElement("t-layout", { layout });
|
|
3102
3582
|
}
|
|
3103
3583
|
function TTree({ data, color, branchColor, leafColor }) {
|
|
3104
3584
|
return createElement("t-tree", { data, color, branchColor, leafColor });
|
|
3105
3585
|
}
|
|
3106
|
-
function TCode({ code: code2, language, title, borderColor }) {
|
|
3107
|
-
return createElement("t-code", { code: code2, language, title, borderColor });
|
|
3586
|
+
function TCode({ code: code2, language, title, borderColor, action, copyable }) {
|
|
3587
|
+
return createElement("t-code", { code: code2, language, title, borderColor, action, copyable });
|
|
3108
3588
|
}
|
|
3109
|
-
function
|
|
3589
|
+
function TUnifiedDiff({ value, addColor, removeColor, metaColor }) {
|
|
3110
3590
|
return createElement("t-diff", { value, addColor, removeColor, metaColor });
|
|
3111
3591
|
}
|
|
3592
|
+
function TDiffCompute({ before, after, context, addColor, removeColor, metaColor }) {
|
|
3593
|
+
return createElement("t-diff-compute", { before, after, context, addColor, removeColor, metaColor });
|
|
3594
|
+
}
|
|
3112
3595
|
function TBar({ label, value, max, color }) {
|
|
3113
3596
|
return createElement("t-bar", { label, value, max, color });
|
|
3114
3597
|
}
|
|
@@ -3118,8 +3601,70 @@ function TSpark({ data, color }) {
|
|
|
3118
3601
|
function TList({ items, ordered, bulletColor, color }) {
|
|
3119
3602
|
return createElement("t-list", { items, ordered, bulletColor, color });
|
|
3120
3603
|
}
|
|
3121
|
-
function TButton({ label, onClick, color, bg }) {
|
|
3122
|
-
return createElement("t-button", { label, onClick, color, bg });
|
|
3604
|
+
function TButton({ label, onClick, color, bg, bold, dim }) {
|
|
3605
|
+
return createElement("t-button", { label, onClick, color, bg, bold, dim });
|
|
3606
|
+
}
|
|
3607
|
+
function TDivider({ label, color, char, labelColor }) {
|
|
3608
|
+
return createElement("t-divider", { label, color, char, labelColor });
|
|
3609
|
+
}
|
|
3610
|
+
function TKV({ pairs, separator: separator2, keyColor, valueColor, keyWidth }) {
|
|
3611
|
+
return createElement("t-kv", { pairs, separator: separator2, keyColor, valueColor, keyWidth });
|
|
3612
|
+
}
|
|
3613
|
+
function TCallout({ children, text, color, accent, label }) {
|
|
3614
|
+
return createElement("t-callout", { text, color, accent, label }, children);
|
|
3615
|
+
}
|
|
3616
|
+
function TChart({ data, height, color, axisColor, fill }) {
|
|
3617
|
+
return createElement("t-chart", { data, height, color, axisColor, fill });
|
|
3618
|
+
}
|
|
3619
|
+
function TScroll({ children, maxHeight, offset: controlledOffset, onScroll: externalOnScroll, scrollbarColor }) {
|
|
3620
|
+
const [internalOffset, setInternalOffset] = useState2(0);
|
|
3621
|
+
const isControlled = controlledOffset !== void 0;
|
|
3622
|
+
const offset = isControlled ? controlledOffset : internalOffset;
|
|
3623
|
+
const handleScroll = useCallback((newOffset) => {
|
|
3624
|
+
if (!isControlled) setInternalOffset(newOffset);
|
|
3625
|
+
externalOnScroll?.(newOffset);
|
|
3626
|
+
}, [isControlled, externalOnScroll]);
|
|
3627
|
+
return createElement("t-scroll", { maxHeight, offset, scrollbarColor, onScroll: handleScroll }, children);
|
|
3628
|
+
}
|
|
3629
|
+
function TGrid({ children, cols, gap, rowGap }) {
|
|
3630
|
+
return createElement("t-grid", { cols, gap, rowGap }, children);
|
|
3631
|
+
}
|
|
3632
|
+
function TCheckbox({ checked, label, color, checkColor, onChange, onClick }) {
|
|
3633
|
+
return createElement("t-checkbox", { checked, label, color, checkColor, onChange, onClick });
|
|
3634
|
+
}
|
|
3635
|
+
function TInput({ value, onChange, onSubmit, placeholder, width, focused: controlledFocused, color, borderColor, onClick }) {
|
|
3636
|
+
const [internalFocused, setInternalFocused] = useState2(false);
|
|
3637
|
+
const focused = controlledFocused ?? internalFocused;
|
|
3638
|
+
useEffect3(() => {
|
|
3639
|
+
if (!focused || !onChange) return;
|
|
3640
|
+
const handler = (e) => {
|
|
3641
|
+
if (e.key === "Escape") {
|
|
3642
|
+
setInternalFocused(false);
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
if (e.key === "Enter") {
|
|
3646
|
+
setInternalFocused(false);
|
|
3647
|
+
onSubmit?.(value);
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
if (e.key === "Backspace") {
|
|
3651
|
+
e.preventDefault();
|
|
3652
|
+
onChange(value.slice(0, -1));
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
|
3656
|
+
e.preventDefault();
|
|
3657
|
+
onChange(value + e.key);
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
document.addEventListener("keydown", handler);
|
|
3661
|
+
return () => document.removeEventListener("keydown", handler);
|
|
3662
|
+
}, [focused, value, onChange, onSubmit]);
|
|
3663
|
+
const handleClick = () => {
|
|
3664
|
+
setInternalFocused(true);
|
|
3665
|
+
onClick?.();
|
|
3666
|
+
};
|
|
3667
|
+
return createElement("t-input", { value, placeholder, width, focused, color, borderColor, onClick: handleClick });
|
|
3123
3668
|
}
|
|
3124
3669
|
function renderTermReact(tree2, opts) {
|
|
3125
3670
|
const resolvedTheme = opts?.theme ?? resolveTheme(opts?.themeSpec ?? defaultThemeSpec, opts?.mode ?? "dark");
|
|
@@ -3134,27 +3679,27 @@ function renderTermReact(tree2, opts) {
|
|
|
3134
3679
|
}
|
|
3135
3680
|
|
|
3136
3681
|
// src/hooks.ts
|
|
3137
|
-
import { useState as
|
|
3682
|
+
import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
3138
3683
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3139
3684
|
function useSpinner(ms = 80) {
|
|
3140
|
-
const [frame, setFrame] =
|
|
3141
|
-
|
|
3685
|
+
const [frame, setFrame] = useState3(0);
|
|
3686
|
+
useEffect4(() => {
|
|
3142
3687
|
const id = setInterval(() => setFrame((v) => (v + 1) % FRAMES.length), ms);
|
|
3143
3688
|
return () => clearInterval(id);
|
|
3144
3689
|
}, [ms]);
|
|
3145
3690
|
return FRAMES[frame % FRAMES.length];
|
|
3146
3691
|
}
|
|
3147
3692
|
function useTick(ms = 500) {
|
|
3148
|
-
const [tick, setTick] =
|
|
3149
|
-
|
|
3693
|
+
const [tick, setTick] = useState3(0);
|
|
3694
|
+
useEffect4(() => {
|
|
3150
3695
|
const id = setInterval(() => setTick((v) => v + 1), ms);
|
|
3151
3696
|
return () => clearInterval(id);
|
|
3152
3697
|
}, [ms]);
|
|
3153
3698
|
return tick;
|
|
3154
3699
|
}
|
|
3155
3700
|
function useTermWidth(ref, fallback = 80) {
|
|
3156
|
-
const [width, setWidth] =
|
|
3157
|
-
|
|
3701
|
+
const [width, setWidth] = useState3(fallback);
|
|
3702
|
+
useEffect4(() => {
|
|
3158
3703
|
const el = ref.current;
|
|
3159
3704
|
if (!el) return;
|
|
3160
3705
|
const m = document.createElement("span");
|
|
@@ -3175,12 +3720,12 @@ function useTermWidth(ref, fallback = 80) {
|
|
|
3175
3720
|
function useStreamingText(text, lerp = 0.08) {
|
|
3176
3721
|
const visibleRef = useRef2(0);
|
|
3177
3722
|
const prevTextRef = useRef2("");
|
|
3178
|
-
const [sliceEnd, setSliceEnd] =
|
|
3723
|
+
const [sliceEnd, setSliceEnd] = useState3(0);
|
|
3179
3724
|
if (!text.startsWith(prevTextRef.current)) {
|
|
3180
3725
|
visibleRef.current = text.length;
|
|
3181
3726
|
}
|
|
3182
3727
|
prevTextRef.current = text;
|
|
3183
|
-
|
|
3728
|
+
useEffect4(() => {
|
|
3184
3729
|
const target = text.length;
|
|
3185
3730
|
if (visibleRef.current >= target) {
|
|
3186
3731
|
setSliceEnd(target);
|
|
@@ -3192,13 +3737,13 @@ function useStreamingText(text, lerp = 0.08) {
|
|
|
3192
3737
|
const dt = (now - lastTime) / 16.67;
|
|
3193
3738
|
lastTime = now;
|
|
3194
3739
|
const current = visibleRef.current;
|
|
3195
|
-
const
|
|
3196
|
-
if (
|
|
3740
|
+
const diff = target - current;
|
|
3741
|
+
if (diff < 0.5) {
|
|
3197
3742
|
visibleRef.current = target;
|
|
3198
3743
|
setSliceEnd(target);
|
|
3199
3744
|
return;
|
|
3200
3745
|
}
|
|
3201
|
-
const step = Math.max(1,
|
|
3746
|
+
const step = Math.max(1, diff * lerp * dt);
|
|
3202
3747
|
visibleRef.current = Math.min(current + step, target);
|
|
3203
3748
|
const next = Math.floor(visibleRef.current);
|
|
3204
3749
|
setSliceEnd((prev) => prev !== next ? next : prev);
|
|
@@ -3210,20 +3755,30 @@ function useStreamingText(text, lerp = 0.08) {
|
|
|
3210
3755
|
return text.slice(0, sliceEnd);
|
|
3211
3756
|
}
|
|
3212
3757
|
export {
|
|
3758
|
+
TBadge,
|
|
3213
3759
|
TBar,
|
|
3214
3760
|
TBlank,
|
|
3215
3761
|
TBox,
|
|
3216
3762
|
TButton,
|
|
3763
|
+
TCallout,
|
|
3764
|
+
TChart,
|
|
3765
|
+
TCheckbox,
|
|
3217
3766
|
TCode,
|
|
3218
|
-
|
|
3767
|
+
TDiffCompute,
|
|
3768
|
+
TDivider,
|
|
3769
|
+
TGrid,
|
|
3219
3770
|
THStack,
|
|
3220
3771
|
THtml,
|
|
3772
|
+
TInput,
|
|
3221
3773
|
TJson,
|
|
3774
|
+
TKV,
|
|
3222
3775
|
TLayout,
|
|
3223
3776
|
TLine,
|
|
3224
3777
|
TList,
|
|
3225
3778
|
TMarkdown,
|
|
3779
|
+
TPad,
|
|
3226
3780
|
TRaw,
|
|
3781
|
+
TScroll,
|
|
3227
3782
|
TSeparator,
|
|
3228
3783
|
TSpan,
|
|
3229
3784
|
TSpark,
|
|
@@ -3232,6 +3787,7 @@ export {
|
|
|
3232
3787
|
TTable,
|
|
3233
3788
|
TTabs,
|
|
3234
3789
|
TTree,
|
|
3790
|
+
TUnifiedDiff,
|
|
3235
3791
|
TVStack,
|
|
3236
3792
|
TermDocument,
|
|
3237
3793
|
TermThemeProvider,
|
|
@@ -3241,28 +3797,37 @@ export {
|
|
|
3241
3797
|
blank,
|
|
3242
3798
|
box,
|
|
3243
3799
|
builtinThemes,
|
|
3800
|
+
callout,
|
|
3801
|
+
chart,
|
|
3802
|
+
checkbox,
|
|
3244
3803
|
code,
|
|
3804
|
+
computeDiff,
|
|
3245
3805
|
dark,
|
|
3246
3806
|
defaultThemeSpec,
|
|
3247
|
-
|
|
3807
|
+
divider,
|
|
3248
3808
|
fromDocument,
|
|
3249
3809
|
fromHtml,
|
|
3250
3810
|
fromJson,
|
|
3251
3811
|
fromJsonNode,
|
|
3252
3812
|
fromMarkdown,
|
|
3813
|
+
grid,
|
|
3253
3814
|
hl,
|
|
3254
3815
|
hstack,
|
|
3816
|
+
inputField,
|
|
3255
3817
|
isTableLine,
|
|
3818
|
+
kv,
|
|
3256
3819
|
light,
|
|
3257
3820
|
lines,
|
|
3258
3821
|
list,
|
|
3259
3822
|
pad,
|
|
3260
3823
|
padLine,
|
|
3824
|
+
padding,
|
|
3261
3825
|
parseThemeSpec,
|
|
3262
3826
|
preserveTableTag,
|
|
3263
3827
|
renderTermReact,
|
|
3264
3828
|
resolveTheme,
|
|
3265
3829
|
resolveThemeMode,
|
|
3830
|
+
scroll,
|
|
3266
3831
|
separator,
|
|
3267
3832
|
spark,
|
|
3268
3833
|
statusbar,
|
|
@@ -3272,6 +3837,8 @@ export {
|
|
|
3272
3837
|
tokenColor,
|
|
3273
3838
|
tokenize,
|
|
3274
3839
|
tree,
|
|
3840
|
+
truncate,
|
|
3841
|
+
unifiedDiff,
|
|
3275
3842
|
useSpinner,
|
|
3276
3843
|
useStreamingText,
|
|
3277
3844
|
useTermTheme,
|