@svelterm/core 0.1.0 → 0.23.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/CHANGELOG.md +465 -0
- package/README.md +42 -29
- package/dist/src/cli/build.d.ts +13 -0
- package/dist/src/cli/build.js +119 -0
- package/dist/src/cli/bundle.d.ts +25 -0
- package/dist/src/cli/bundle.js +61 -0
- package/dist/src/cli/dev.d.ts +10 -0
- package/dist/src/cli/dev.js +152 -0
- package/dist/src/cli/devtools.d.ts +9 -0
- package/dist/src/cli/devtools.js +47 -0
- package/dist/src/cli/init.d.ts +8 -0
- package/dist/src/cli/init.js +153 -0
- package/dist/src/cli/main.d.ts +9 -0
- package/dist/src/cli/main.js +52 -0
- package/dist/src/cli/svt-bin.d.ts +2 -0
- package/dist/src/cli/svt-bin.js +6 -0
- package/dist/src/cli/svt.d.ts +14 -0
- package/dist/src/cli/svt.js +76 -0
- package/dist/src/components/text-buffer.js +8 -5
- package/dist/src/css/animation-runner.d.ts +15 -6
- package/dist/src/css/animation-runner.js +80 -29
- package/dist/src/css/animation.d.ts +12 -0
- package/dist/src/css/animation.js +21 -0
- package/dist/src/css/calc.js +4 -3
- package/dist/src/css/color.d.ts +19 -0
- package/dist/src/css/color.js +371 -62
- package/dist/src/css/compute.d.ts +31 -4
- package/dist/src/css/compute.js +273 -34
- package/dist/src/css/defaults.d.ts +1 -1
- package/dist/src/css/defaults.js +9 -0
- package/dist/src/css/easing.d.ts +9 -0
- package/dist/src/css/easing.js +95 -0
- package/dist/src/css/incremental.d.ts +1 -1
- package/dist/src/css/incremental.js +2 -2
- package/dist/src/css/interpolate.d.ts +13 -0
- package/dist/src/css/interpolate.js +41 -0
- package/dist/src/css/parser.js +59 -3
- package/dist/src/css/pseudo-elements.d.ts +9 -0
- package/dist/src/css/pseudo-elements.js +97 -0
- package/dist/src/css/selector.d.ts +17 -2
- package/dist/src/css/selector.js +128 -13
- package/dist/src/css/specificity.js +17 -6
- package/dist/src/css/values.d.ts +6 -1
- package/dist/src/css/values.js +13 -6
- package/dist/src/debug/context.d.ts +13 -0
- package/dist/src/debug/context.js +11 -0
- package/dist/src/debug/css.d.ts +12 -0
- package/dist/src/debug/css.js +28 -0
- package/dist/src/debug/dom.d.ts +17 -0
- package/dist/src/debug/dom.js +92 -0
- package/dist/src/devtools/DevTools.compiled.js +327 -0
- package/dist/src/devtools/DevTools.css.js +1 -0
- package/dist/src/devtools/client.d.ts +36 -0
- package/dist/src/devtools/client.js +76 -0
- package/dist/src/framelog.d.ts +54 -0
- package/dist/src/framelog.js +99 -0
- package/dist/src/headless.js +12 -4
- package/dist/src/index.d.ts +66 -3
- package/dist/src/index.js +610 -81
- package/dist/src/input/checkable.d.ts +8 -0
- package/dist/src/input/checkable.js +66 -0
- package/dist/src/input/details.d.ts +6 -0
- package/dist/src/input/details.js +34 -0
- package/dist/src/input/focus.d.ts +6 -0
- package/dist/src/input/focus.js +27 -9
- package/dist/src/input/keyboard.d.ts +2 -2
- package/dist/src/input/keyboard.js +32 -5
- package/dist/src/input/label.d.ts +8 -0
- package/dist/src/input/label.js +53 -0
- package/dist/src/input/modal.d.ts +9 -0
- package/dist/src/input/modal.js +28 -0
- package/dist/src/input/mouse.d.ts +2 -2
- package/dist/src/input/mouse.js +15 -2
- package/dist/src/input/select.d.ts +12 -0
- package/dist/src/input/select.js +63 -0
- package/dist/src/input/selection.d.ts +48 -0
- package/dist/src/input/selection.js +150 -0
- package/dist/src/layout/engine.d.ts +2 -0
- package/dist/src/layout/engine.js +1092 -142
- package/dist/src/layout/flex.js +4 -4
- package/dist/src/layout/size.js +3 -2
- package/dist/src/layout/text.d.ts +3 -2
- package/dist/src/layout/text.js +96 -17
- package/dist/src/layout/unicode.d.ts +20 -0
- package/dist/src/layout/unicode.js +121 -0
- package/dist/src/render/animation-clock.d.ts +57 -0
- package/dist/src/render/animation-clock.js +221 -0
- package/dist/src/render/ansi-text.d.ts +26 -0
- package/dist/src/render/ansi-text.js +131 -0
- package/dist/src/render/ansi.d.ts +18 -0
- package/dist/src/render/ansi.js +64 -19
- package/dist/src/render/border.js +166 -17
- package/dist/src/render/buffer.d.ts +1 -0
- package/dist/src/render/buffer.js +5 -2
- package/dist/src/render/clock.d.ts +35 -0
- package/dist/src/render/clock.js +67 -0
- package/dist/src/render/color-depth.d.ts +8 -0
- package/dist/src/render/color-depth.js +59 -0
- package/dist/src/render/context.d.ts +1 -0
- package/dist/src/render/context.js +17 -21
- package/dist/src/render/cursor-emit.d.ts +18 -0
- package/dist/src/render/cursor-emit.js +50 -0
- package/dist/src/render/diff.d.ts +12 -0
- package/dist/src/render/diff.js +120 -0
- package/dist/src/render/generation.d.ts +9 -0
- package/dist/src/render/generation.js +14 -0
- package/dist/src/render/graphics-layer.d.ts +27 -0
- package/dist/src/render/graphics-layer.js +86 -0
- package/dist/src/render/image.d.ts +27 -0
- package/dist/src/render/image.js +113 -0
- package/dist/src/render/incremental-paint.d.ts +7 -3
- package/dist/src/render/incremental-paint.js +52 -79
- package/dist/src/render/inline.d.ts +59 -0
- package/dist/src/render/inline.js +219 -0
- package/dist/src/render/kitty-graphics.d.ts +24 -0
- package/dist/src/render/kitty-graphics.js +58 -0
- package/dist/src/render/paint-text.js +68 -22
- package/dist/src/render/paint.d.ts +8 -1
- package/dist/src/render/paint.js +358 -31
- package/dist/src/render/png.d.ts +13 -0
- package/dist/src/render/png.js +145 -0
- package/dist/src/render/scrollbar.d.ts +8 -2
- package/dist/src/render/scrollbar.js +71 -14
- package/dist/src/render/snapshot.js +3 -1
- package/dist/src/renderer/default.d.ts +7 -0
- package/dist/src/renderer/default.js +11 -0
- package/dist/src/renderer/index.d.ts +8 -2
- package/dist/src/renderer/index.js +4 -2
- package/dist/src/renderer/node.d.ts +109 -0
- package/dist/src/renderer/node.js +165 -1
- package/dist/src/terminal/capabilities.d.ts +33 -0
- package/dist/src/terminal/capabilities.js +66 -0
- package/dist/src/terminal/clipboard.d.ts +9 -0
- package/dist/src/terminal/clipboard.js +39 -0
- package/dist/src/terminal/io.d.ts +82 -0
- package/dist/src/terminal/io.js +155 -0
- package/dist/src/terminal/screen.d.ts +3 -10
- package/dist/src/terminal/screen.js +5 -28
- package/dist/src/terminal/stdin-router.d.ts +8 -5
- package/dist/src/terminal/stdin-router.js +22 -11
- package/dist/src/utils/node-map.d.ts +24 -0
- package/dist/src/utils/node-map.js +75 -0
- package/dist/src/vite/config.d.ts +62 -0
- package/dist/src/vite/config.js +191 -0
- package/docs/compatibility.md +67 -0
- package/docs/debug/devtools.md +40 -0
- package/docs/debug/svt.md +50 -0
- package/docs/distribution.md +106 -0
- package/docs/elements.md +120 -0
- package/docs/getting-started.md +177 -0
- package/docs/guide/css.md +187 -0
- package/docs/guide/input.md +143 -0
- package/docs/guide/layout.md +171 -0
- package/docs/guide/theming.md +94 -0
- package/docs/how-it-works.md +115 -0
- package/docs/inline-mode.md +77 -0
- package/docs/layout.md +112 -0
- package/docs/motion.md +91 -0
- package/docs/reference/README.md +65 -0
- package/docs/reference/css/properties/border-corner.md +82 -0
- package/docs/reference/css/properties/border-style.md +168 -0
- package/docs/reference.md +227 -0
- package/docs/selectors.md +80 -0
- package/docs/terminal-css.md +149 -0
- package/docs/terminals.md +83 -0
- package/package.json +28 -7
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal ANSI/SGR parser for the <svt-ansi> passthrough element:
|
|
3
|
+
* pre-styled tool output (git diff, ls --color, build logs) renders as
|
|
4
|
+
* styled cells. SGR sequences apply; every other escape sequence is
|
|
5
|
+
* dropped. Content is `pre` — lines split on newline, no wrapping.
|
|
6
|
+
*/
|
|
7
|
+
const SGR_NAMES = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
|
|
8
|
+
/** Standard xterm palette entries 8–15 (the bright variants). */
|
|
9
|
+
const BRIGHT_HEX = ['#808080', '#ff0000', '#00ff00', '#ffff00', '#0000ff', '#ff00ff', '#00ffff', '#ffffff'];
|
|
10
|
+
const CUBE_LEVELS = [0, 95, 135, 175, 215, 255];
|
|
11
|
+
/** The xterm 256-palette entry as #rrggbb (16–255; 0–15 use names/brights). */
|
|
12
|
+
export function palette256(index) {
|
|
13
|
+
if (index < 8)
|
|
14
|
+
return SGR_NAMES[index];
|
|
15
|
+
if (index < 16)
|
|
16
|
+
return BRIGHT_HEX[index - 8];
|
|
17
|
+
if (index < 232) {
|
|
18
|
+
const n = index - 16;
|
|
19
|
+
const r = CUBE_LEVELS[Math.floor(n / 36)];
|
|
20
|
+
const g = CUBE_LEVELS[Math.floor(n / 6) % 6];
|
|
21
|
+
const b = CUBE_LEVELS[n % 6];
|
|
22
|
+
return hex(r, g, b);
|
|
23
|
+
}
|
|
24
|
+
const grey = 8 + (index - 232) * 10;
|
|
25
|
+
return hex(grey, grey, grey);
|
|
26
|
+
}
|
|
27
|
+
function hex(r, g, b) {
|
|
28
|
+
return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
|
|
29
|
+
}
|
|
30
|
+
const DEFAULT_STYLE = {
|
|
31
|
+
fg: 'default', bg: 'default',
|
|
32
|
+
bold: false, dim: false, italic: false,
|
|
33
|
+
underline: false, strikethrough: false, inverse: false,
|
|
34
|
+
};
|
|
35
|
+
const ESCAPE_RE = /\x1b(?:\[([0-9;]*)([a-zA-Z])|\][^\x07\x1b]*(?:\x07|\x1b\\)|[a-zA-Z=><])/;
|
|
36
|
+
/** Parse ANSI-styled text into lines of styled cells. */
|
|
37
|
+
export function parseAnsiText(text) {
|
|
38
|
+
const lines = [[]];
|
|
39
|
+
let style = { ...DEFAULT_STYLE };
|
|
40
|
+
let rest = text;
|
|
41
|
+
while (rest.length > 0) {
|
|
42
|
+
const match = ESCAPE_RE.exec(rest);
|
|
43
|
+
const plain = match ? rest.slice(0, match.index) : rest;
|
|
44
|
+
for (const char of plain) {
|
|
45
|
+
const line = lines[lines.length - 1];
|
|
46
|
+
if (char === '\n')
|
|
47
|
+
lines.push([]);
|
|
48
|
+
else if (char === '\r')
|
|
49
|
+
continue;
|
|
50
|
+
else if (char === '\t') {
|
|
51
|
+
const stop = (Math.floor(line.length / 8) + 1) * 8;
|
|
52
|
+
while (line.length < stop)
|
|
53
|
+
line.push({ char: ' ', ...style });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
line.push({ char, ...style });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!match)
|
|
60
|
+
break;
|
|
61
|
+
if (match[2] === 'm')
|
|
62
|
+
style = applySgr(style, match[1] ?? '');
|
|
63
|
+
rest = rest.slice(match.index + match[0].length);
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
function applySgr(style, params) {
|
|
68
|
+
const next = { ...style };
|
|
69
|
+
const codes = params === '' ? [0] : params.split(';').map(n => parseInt(n, 10) || 0);
|
|
70
|
+
for (let i = 0; i < codes.length; i++) {
|
|
71
|
+
const code = codes[i];
|
|
72
|
+
if (code === 0)
|
|
73
|
+
Object.assign(next, DEFAULT_STYLE);
|
|
74
|
+
else if (code === 1)
|
|
75
|
+
next.bold = true;
|
|
76
|
+
else if (code === 2)
|
|
77
|
+
next.dim = true;
|
|
78
|
+
else if (code === 3)
|
|
79
|
+
next.italic = true;
|
|
80
|
+
else if (code === 4)
|
|
81
|
+
next.underline = true;
|
|
82
|
+
else if (code === 7)
|
|
83
|
+
next.inverse = true;
|
|
84
|
+
else if (code === 9)
|
|
85
|
+
next.strikethrough = true;
|
|
86
|
+
else if (code === 22) {
|
|
87
|
+
next.bold = false;
|
|
88
|
+
next.dim = false;
|
|
89
|
+
}
|
|
90
|
+
else if (code === 23)
|
|
91
|
+
next.italic = false;
|
|
92
|
+
else if (code === 24)
|
|
93
|
+
next.underline = false;
|
|
94
|
+
else if (code === 27)
|
|
95
|
+
next.inverse = false;
|
|
96
|
+
else if (code === 29)
|
|
97
|
+
next.strikethrough = false;
|
|
98
|
+
else if (code >= 30 && code <= 37)
|
|
99
|
+
next.fg = SGR_NAMES[code - 30];
|
|
100
|
+
else if (code === 39)
|
|
101
|
+
next.fg = 'default';
|
|
102
|
+
else if (code >= 40 && code <= 47)
|
|
103
|
+
next.bg = SGR_NAMES[code - 40];
|
|
104
|
+
else if (code === 49)
|
|
105
|
+
next.bg = 'default';
|
|
106
|
+
else if (code >= 90 && code <= 97)
|
|
107
|
+
next.fg = BRIGHT_HEX[code - 90];
|
|
108
|
+
else if (code >= 100 && code <= 107)
|
|
109
|
+
next.bg = BRIGHT_HEX[code - 100];
|
|
110
|
+
else if (code === 38 || code === 48) {
|
|
111
|
+
const target = code === 38 ? 'fg' : 'bg';
|
|
112
|
+
if (codes[i + 1] === 5 && codes.length > i + 2) {
|
|
113
|
+
next[target] = palette256(codes[i + 2]);
|
|
114
|
+
i += 2;
|
|
115
|
+
}
|
|
116
|
+
else if (codes[i + 1] === 2 && codes.length > i + 4) {
|
|
117
|
+
next[target] = hex(codes[i + 2], codes[i + 3], codes[i + 4]);
|
|
118
|
+
i += 4;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return next;
|
|
123
|
+
}
|
|
124
|
+
/** Width/height of parsed content (pre semantics — longest line, no wrap). */
|
|
125
|
+
export function measureAnsiText(text) {
|
|
126
|
+
const lines = parseAnsiText(text);
|
|
127
|
+
return {
|
|
128
|
+
width: lines.reduce((max, line) => Math.max(max, line.length), 0),
|
|
129
|
+
height: lines.length,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ColorDepth } from '../terminal/capabilities.js';
|
|
2
|
+
export declare function setColorDepth(depth: ColorDepth): void;
|
|
3
|
+
export declare function getColorDepth(): ColorDepth;
|
|
1
4
|
export declare function moveTo(col: number, row: number): string;
|
|
2
5
|
export declare function clearScreen(): string;
|
|
3
6
|
export declare function hideCursor(): string;
|
|
@@ -10,6 +13,7 @@ export declare function dim(): string;
|
|
|
10
13
|
export declare function italic(): string;
|
|
11
14
|
export declare function underline(): string;
|
|
12
15
|
export declare function strikethrough(): string;
|
|
16
|
+
export declare function inverse(): string;
|
|
13
17
|
export declare function fgColor(color: string): string;
|
|
14
18
|
export declare function bgColor(color: string): string;
|
|
15
19
|
export declare function hyperlinkOpen(url: string): string;
|
|
@@ -17,7 +21,21 @@ export declare function hyperlinkClose(): string;
|
|
|
17
21
|
export declare function enableMouse(): string;
|
|
18
22
|
export declare function disableMouse(): string;
|
|
19
23
|
export declare function setCursorShape(shape: 'block' | 'underline' | 'bar'): string;
|
|
24
|
+
/** DECSCUSR 0 — the terminal's configured default cursor. */
|
|
25
|
+
export declare function resetCursorShape(): string;
|
|
26
|
+
/** Kitty keyboard protocol: push disambiguate-escape-codes mode. */
|
|
27
|
+
export declare function pushKittyKeyboard(): string;
|
|
28
|
+
/** Kitty keyboard protocol: pop our mode entry. */
|
|
29
|
+
export declare function popKittyKeyboard(): string;
|
|
20
30
|
export declare function enableBracketedPaste(): string;
|
|
21
31
|
export declare function disableBracketedPaste(): string;
|
|
32
|
+
/** DECSTBM — set the scroll region to rows [top, bottom] (1-based). */
|
|
33
|
+
export declare function setScrollRegion(top: number, bottom: number): string;
|
|
34
|
+
/** DECSTBM reset — scroll region back to the full screen. */
|
|
35
|
+
export declare function resetScrollRegion(): string;
|
|
36
|
+
/** IND — index: move down one line, scrolling the region at its bottom. */
|
|
37
|
+
export declare function index(): string;
|
|
38
|
+
/** RI — reverse index: move up one line, scrolling the region at its top. */
|
|
39
|
+
export declare function reverseIndex(): string;
|
|
22
40
|
export declare function beginSyncUpdate(): string;
|
|
23
41
|
export declare function endSyncUpdate(): string;
|
package/dist/src/render/ansi.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { quantizeTo256, quantizeTo16 } from './color-depth.js';
|
|
1
2
|
const ESC = '\x1b';
|
|
2
3
|
const CSI = `${ESC}[`;
|
|
4
|
+
// Colour depth applies to the whole process's output stream, set once
|
|
5
|
+
// after capability detection (default: truecolor, today's common case).
|
|
6
|
+
let colorDepth = 'truecolor';
|
|
7
|
+
export function setColorDepth(depth) {
|
|
8
|
+
colorDepth = depth;
|
|
9
|
+
}
|
|
10
|
+
export function getColorDepth() {
|
|
11
|
+
return colorDepth;
|
|
12
|
+
}
|
|
3
13
|
function expandHex(color) {
|
|
4
14
|
if (color.length === 4) {
|
|
5
15
|
return '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
|
|
@@ -42,31 +52,38 @@ export function underline() {
|
|
|
42
52
|
export function strikethrough() {
|
|
43
53
|
return `${CSI}9m`;
|
|
44
54
|
}
|
|
55
|
+
export function inverse() {
|
|
56
|
+
return `${CSI}7m`;
|
|
57
|
+
}
|
|
45
58
|
export function fgColor(color) {
|
|
46
|
-
|
|
47
|
-
if (code !== undefined)
|
|
48
|
-
return `${CSI}${code}m`;
|
|
49
|
-
if (color.startsWith('#')) {
|
|
50
|
-
const hex = expandHex(color);
|
|
51
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
52
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
53
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
54
|
-
return `${CSI}38;2;${r};${g};${b}m`;
|
|
55
|
-
}
|
|
56
|
-
return '';
|
|
59
|
+
return sgrColor(color, ANSI_FG, 38);
|
|
57
60
|
}
|
|
58
61
|
export function bgColor(color) {
|
|
59
|
-
|
|
62
|
+
return sgrColor(color, ANSI_BG, 48);
|
|
63
|
+
}
|
|
64
|
+
function sgrColor(color, names, extended) {
|
|
65
|
+
if (colorDepth === 'mono') {
|
|
66
|
+
// Colour is disabled; only default (the reset) still emits.
|
|
67
|
+
return color === 'default' ? `${CSI}${names.default}m` : '';
|
|
68
|
+
}
|
|
69
|
+
const code = names[color];
|
|
60
70
|
if (code !== undefined)
|
|
61
71
|
return `${CSI}${code}m`;
|
|
62
|
-
if (color.startsWith('#'))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
if (!color.startsWith('#'))
|
|
73
|
+
return '';
|
|
74
|
+
const hex = expandHex(color);
|
|
75
|
+
switch (colorDepth) {
|
|
76
|
+
case '256':
|
|
77
|
+
return `${CSI}${extended};5;${quantizeTo256(hex)}m`;
|
|
78
|
+
case '16':
|
|
79
|
+
return `${CSI}${names[quantizeTo16(hex)]}m`;
|
|
80
|
+
default: {
|
|
81
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
82
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
83
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
84
|
+
return `${CSI}${extended};2;${r};${g};${b}m`;
|
|
85
|
+
}
|
|
68
86
|
}
|
|
69
|
-
return '';
|
|
70
87
|
}
|
|
71
88
|
const ANSI_FG = {
|
|
72
89
|
black: 30, red: 31, green: 32, yellow: 33,
|
|
@@ -89,12 +106,40 @@ export function setCursorShape(shape) {
|
|
|
89
106
|
const code = shape === 'block' ? 2 : shape === 'underline' ? 4 : 6;
|
|
90
107
|
return `${CSI}${code} q`;
|
|
91
108
|
}
|
|
109
|
+
/** DECSCUSR 0 — the terminal's configured default cursor. */
|
|
110
|
+
export function resetCursorShape() {
|
|
111
|
+
return `${CSI}0 q`;
|
|
112
|
+
}
|
|
113
|
+
/** Kitty keyboard protocol: push disambiguate-escape-codes mode. */
|
|
114
|
+
export function pushKittyKeyboard() {
|
|
115
|
+
return `${CSI}>1u`;
|
|
116
|
+
}
|
|
117
|
+
/** Kitty keyboard protocol: pop our mode entry. */
|
|
118
|
+
export function popKittyKeyboard() {
|
|
119
|
+
return `${CSI}<u`;
|
|
120
|
+
}
|
|
92
121
|
export function enableBracketedPaste() {
|
|
93
122
|
return `${CSI}?2004h`;
|
|
94
123
|
}
|
|
95
124
|
export function disableBracketedPaste() {
|
|
96
125
|
return `${CSI}?2004l`;
|
|
97
126
|
}
|
|
127
|
+
/** DECSTBM — set the scroll region to rows [top, bottom] (1-based). */
|
|
128
|
+
export function setScrollRegion(top, bottom) {
|
|
129
|
+
return `${CSI}${top};${bottom}r`;
|
|
130
|
+
}
|
|
131
|
+
/** DECSTBM reset — scroll region back to the full screen. */
|
|
132
|
+
export function resetScrollRegion() {
|
|
133
|
+
return `${CSI}r`;
|
|
134
|
+
}
|
|
135
|
+
/** IND — index: move down one line, scrolling the region at its bottom. */
|
|
136
|
+
export function index() {
|
|
137
|
+
return `${ESC}D`;
|
|
138
|
+
}
|
|
139
|
+
/** RI — reverse index: move up one line, scrolling the region at its top. */
|
|
140
|
+
export function reverseIndex() {
|
|
141
|
+
return `${ESC}M`;
|
|
142
|
+
}
|
|
98
143
|
export function beginSyncUpdate() {
|
|
99
144
|
return `${CSI}?2026h`;
|
|
100
145
|
}
|
|
@@ -1,12 +1,96 @@
|
|
|
1
1
|
const BORDER_SETS = {
|
|
2
|
-
single: { topLeft: '┌', topRight: '┐', bottomLeft: '└', bottomRight: '┘', horizontal: '─', vertical: '│' },
|
|
3
|
-
double: { topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝', horizontal: '═', vertical: '║' },
|
|
4
|
-
rounded: { topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯', horizontal: '─', vertical: '│' },
|
|
5
|
-
heavy: { topLeft: '┏', topRight: '┓', bottomLeft: '┗', bottomRight: '┛', horizontal: '━', vertical: '┃' },
|
|
2
|
+
single: { topLeft: '┌', topRight: '┐', bottomLeft: '└', bottomRight: '┘', horizontal: '─', vertical: '│', teeLeft: '├', teeRight: '┤', teeTop: '┬', teeBottom: '┴', cross: '┼' },
|
|
3
|
+
double: { topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝', horizontal: '═', vertical: '║', teeLeft: '╠', teeRight: '╣', teeTop: '╦', teeBottom: '╩', cross: '╬' },
|
|
4
|
+
rounded: { topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯', horizontal: '─', vertical: '│', teeLeft: '├', teeRight: '┤', teeTop: '┬', teeBottom: '┴', cross: '┼' },
|
|
5
|
+
heavy: { topLeft: '┏', topRight: '┓', bottomLeft: '┗', bottomRight: '┛', horizontal: '━', vertical: '┃', teeLeft: '┣', teeRight: '┫', teeTop: '┳', teeBottom: '┻', cross: '╋' },
|
|
6
|
+
ascii: { topLeft: '+', topRight: '+', bottomLeft: '+', bottomRight: '+', horizontal: '-', vertical: '|', teeLeft: '+', teeRight: '+', teeTop: '+', teeBottom: '+', cross: '+' },
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Direction masks for box-drawing glyph merging. Each glyph is the set of
|
|
10
|
+
* directions its strokes point in; overlapping glyphs merge by unioning
|
|
11
|
+
* their masks (e.g. ┐ over └ → all four directions → ┼).
|
|
12
|
+
*/
|
|
13
|
+
const UP = 1, RIGHT = 2, DOWN = 4, LEFT = 8;
|
|
14
|
+
const GLYPH_MASKS = {};
|
|
15
|
+
for (const s of Object.values(BORDER_SETS)) {
|
|
16
|
+
GLYPH_MASKS[s.topLeft] = RIGHT | DOWN;
|
|
17
|
+
GLYPH_MASKS[s.topRight] = LEFT | DOWN;
|
|
18
|
+
GLYPH_MASKS[s.bottomLeft] = UP | RIGHT;
|
|
19
|
+
GLYPH_MASKS[s.bottomRight] = UP | LEFT;
|
|
20
|
+
GLYPH_MASKS[s.horizontal] = LEFT | RIGHT;
|
|
21
|
+
GLYPH_MASKS[s.vertical] = UP | DOWN;
|
|
22
|
+
GLYPH_MASKS[s.teeLeft] = UP | DOWN | RIGHT;
|
|
23
|
+
GLYPH_MASKS[s.teeRight] = UP | DOWN | LEFT;
|
|
24
|
+
GLYPH_MASKS[s.teeTop] = LEFT | RIGHT | DOWN;
|
|
25
|
+
GLYPH_MASKS[s.teeBottom] = LEFT | RIGHT | UP;
|
|
26
|
+
GLYPH_MASKS[s.cross] = UP | RIGHT | DOWN | LEFT;
|
|
27
|
+
}
|
|
28
|
+
function glyphForMask(chars, mask) {
|
|
29
|
+
switch (mask) {
|
|
30
|
+
case RIGHT | DOWN: return chars.topLeft;
|
|
31
|
+
case LEFT | DOWN: return chars.topRight;
|
|
32
|
+
case UP | RIGHT: return chars.bottomLeft;
|
|
33
|
+
case UP | LEFT: return chars.bottomRight;
|
|
34
|
+
case LEFT | RIGHT: return chars.horizontal;
|
|
35
|
+
case UP | DOWN: return chars.vertical;
|
|
36
|
+
case UP | DOWN | RIGHT: return chars.teeLeft;
|
|
37
|
+
case UP | DOWN | LEFT: return chars.teeRight;
|
|
38
|
+
case LEFT | RIGHT | DOWN: return chars.teeTop;
|
|
39
|
+
case LEFT | RIGHT | UP: return chars.teeBottom;
|
|
40
|
+
case UP | RIGHT | DOWN | LEFT: return chars.cross;
|
|
41
|
+
default: return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Merge a border glyph with whatever box-drawing character is already in the
|
|
46
|
+
* buffer cell, producing T-junctions and crosses where strokes meet. The new
|
|
47
|
+
* glyph's family (single/double/...) wins for the merged character.
|
|
48
|
+
*/
|
|
49
|
+
function mergeGlyph(buffer, cx, cy, newMask, chars) {
|
|
50
|
+
const fallback = glyphForMask(chars, newMask) ?? chars.cross;
|
|
51
|
+
const existing = buffer.getCell(cx, cy)?.char;
|
|
52
|
+
const existingMask = existing !== undefined ? GLYPH_MASKS[existing] : undefined;
|
|
53
|
+
if (existingMask === undefined)
|
|
54
|
+
return fallback;
|
|
55
|
+
return glyphForMask(chars, existingMask | newMask) ?? fallback;
|
|
56
|
+
}
|
|
57
|
+
const BLOCK_EDGES = {
|
|
58
|
+
'eighth-cell-inner': { top: '\u2581', bottom: '\u2594', left: '\u2595', right: '\u258F' },
|
|
59
|
+
'eighth-cell-outer': { top: '\u2594', bottom: '\u2581', left: '\u258F', right: '\u2595' },
|
|
60
|
+
'half-cell-inner': { top: '\u2584', bottom: '\u2580', left: '\u2590', right: '\u258C' },
|
|
61
|
+
'half-cell-outer': { top: '\u2580', bottom: '\u2584', left: '\u258C', right: '\u2590' },
|
|
62
|
+
'full-cell': { top: '\u2588', bottom: '\u2588', left: '\u2588', right: '\u2588' },
|
|
63
|
+
};
|
|
64
|
+
const BLOCK_CORNERS = {
|
|
65
|
+
// Inner-facing: strokes face the content area, so corner glyphs fill the
|
|
66
|
+
// inner quadrant relative to the cell (e.g. top-left corner fills lower-right).
|
|
67
|
+
'half-cell-inner': {
|
|
68
|
+
topLeft: '\u2597', // ▗ lower-right quadrant
|
|
69
|
+
topRight: '\u2596', // ▖ lower-left quadrant
|
|
70
|
+
bottomLeft: '\u259D', // ▝ upper-right quadrant
|
|
71
|
+
bottomRight: '\u2598', // ▘ upper-left quadrant
|
|
72
|
+
},
|
|
73
|
+
// Outer-facing: strokes face outward. Corner cells combine the full upper
|
|
74
|
+
// half (or lower half) with the full left half (or right half), forming
|
|
75
|
+
// three-quadrant L glyphs with just the diagonally-inner quadrant empty.
|
|
76
|
+
'half-cell-outer': {
|
|
77
|
+
topLeft: '\u259B', // ▛ TL+TR+BL (missing BR)
|
|
78
|
+
topRight: '\u259C', // ▜ TL+TR+BR (missing BL)
|
|
79
|
+
bottomLeft: '\u2599', // ▙ TL+BL+BR (missing TR)
|
|
80
|
+
bottomRight: '\u259F', // ▟ TR+BL+BR (missing TL)
|
|
81
|
+
},
|
|
82
|
+
'full-cell': {
|
|
83
|
+
topLeft: '\u2588', topRight: '\u2588', bottomLeft: '\u2588', bottomRight: '\u2588',
|
|
84
|
+
},
|
|
6
85
|
};
|
|
7
86
|
export function renderBorder(buffer, box, style) {
|
|
8
87
|
if (style.borderStyle === 'none')
|
|
9
88
|
return;
|
|
89
|
+
const blockEdges = BLOCK_EDGES[style.borderStyle];
|
|
90
|
+
if (blockEdges) {
|
|
91
|
+
renderBlockBorder(buffer, box, style, blockEdges);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
10
94
|
const chars = BORDER_SETS[style.borderStyle];
|
|
11
95
|
if (!chars)
|
|
12
96
|
return;
|
|
@@ -16,21 +100,25 @@ export function renderBorder(buffer, box, style) {
|
|
|
16
100
|
const right = style.borderRight;
|
|
17
101
|
const bottom = style.borderBottom;
|
|
18
102
|
const left = style.borderLeft;
|
|
19
|
-
// Corners
|
|
20
|
-
if (top && left)
|
|
21
|
-
buffer.setCell(x, y, { char: chars
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (bottom &&
|
|
27
|
-
buffer.setCell(x +
|
|
103
|
+
// Corners — merge into T-junctions or crosses when overlapping a sibling's border
|
|
104
|
+
if (top && left) {
|
|
105
|
+
buffer.setCell(x, y, { char: mergeGlyph(buffer, x, y, RIGHT | DOWN, chars), fg });
|
|
106
|
+
}
|
|
107
|
+
if (top && right) {
|
|
108
|
+
buffer.setCell(x + width - 1, y, { char: mergeGlyph(buffer, x + width - 1, y, LEFT | DOWN, chars), fg });
|
|
109
|
+
}
|
|
110
|
+
if (bottom && left) {
|
|
111
|
+
buffer.setCell(x, y + height - 1, { char: mergeGlyph(buffer, x, y + height - 1, UP | RIGHT, chars), fg });
|
|
112
|
+
}
|
|
113
|
+
if (bottom && right) {
|
|
114
|
+
buffer.setCell(x + width - 1, y + height - 1, { char: mergeGlyph(buffer, x + width - 1, y + height - 1, UP | LEFT, chars), fg });
|
|
115
|
+
}
|
|
28
116
|
// Top edge
|
|
29
117
|
if (top) {
|
|
30
118
|
const startCol = left ? x + 1 : x;
|
|
31
119
|
const endCol = right ? x + width - 1 : x + width;
|
|
32
120
|
for (let col = startCol; col < endCol; col++) {
|
|
33
|
-
buffer.setCell(col, y, { char: chars
|
|
121
|
+
buffer.setCell(col, y, { char: mergeGlyph(buffer, col, y, LEFT | RIGHT, chars), fg });
|
|
34
122
|
}
|
|
35
123
|
}
|
|
36
124
|
// Bottom edge
|
|
@@ -38,7 +126,7 @@ export function renderBorder(buffer, box, style) {
|
|
|
38
126
|
const startCol = left ? x + 1 : x;
|
|
39
127
|
const endCol = right ? x + width - 1 : x + width;
|
|
40
128
|
for (let col = startCol; col < endCol; col++) {
|
|
41
|
-
buffer.setCell(col, y + height - 1, { char: chars
|
|
129
|
+
buffer.setCell(col, y + height - 1, { char: mergeGlyph(buffer, col, y + height - 1, LEFT | RIGHT, chars), fg });
|
|
42
130
|
}
|
|
43
131
|
}
|
|
44
132
|
// Left edge
|
|
@@ -46,7 +134,7 @@ export function renderBorder(buffer, box, style) {
|
|
|
46
134
|
const startRow = top ? y + 1 : y;
|
|
47
135
|
const endRow = bottom ? y + height - 1 : y + height;
|
|
48
136
|
for (let row = startRow; row < endRow; row++) {
|
|
49
|
-
buffer.setCell(x, row, { char: chars
|
|
137
|
+
buffer.setCell(x, row, { char: mergeGlyph(buffer, x, row, UP | DOWN, chars), fg });
|
|
50
138
|
}
|
|
51
139
|
}
|
|
52
140
|
// Right edge
|
|
@@ -54,7 +142,68 @@ export function renderBorder(buffer, box, style) {
|
|
|
54
142
|
const startRow = top ? y + 1 : y;
|
|
55
143
|
const endRow = bottom ? y + height - 1 : y + height;
|
|
56
144
|
for (let row = startRow; row < endRow; row++) {
|
|
57
|
-
buffer.setCell(x + width - 1, row, { char: chars
|
|
145
|
+
buffer.setCell(x + width - 1, row, { char: mergeGlyph(buffer, x + width - 1, row, UP | DOWN, chars), fg });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Render a block-character border (eighth-cell-*, half-cell-*, full-cell).
|
|
151
|
+
* Corner cells are owned by the axis selected via border-corner: 'h' (default top/bottom),
|
|
152
|
+
* 'v' (sides), or 'none' (corners blank).
|
|
153
|
+
*/
|
|
154
|
+
function renderBlockBorder(buffer, box, style, edges) {
|
|
155
|
+
const fg = style.borderColor !== 'default' ? style.borderColor : undefined;
|
|
156
|
+
const { x, y, width, height } = box;
|
|
157
|
+
const top = style.borderTop;
|
|
158
|
+
const right = style.borderRight;
|
|
159
|
+
const bottom = style.borderBottom;
|
|
160
|
+
const left = style.borderLeft;
|
|
161
|
+
const corner = style.borderCorner;
|
|
162
|
+
// Corner ownership controls how far horizontal vs vertical edges extend.
|
|
163
|
+
// 'h' = top/bottom strokes extend through the full row, sides indent by 1
|
|
164
|
+
// 'v' = sides extend through the full column, top/bottom indent by 1
|
|
165
|
+
// 'none' = corners blank (or filled by dedicated corner glyphs, if available)
|
|
166
|
+
//
|
|
167
|
+
// eighth-cell-outer defaults to 'h' when 'none' is specified — there's no
|
|
168
|
+
// corner glyph in Block Elements for 1/8-thick L pieces, and with no
|
|
169
|
+
// extension the corners would be visibly missing from the outer frame.
|
|
170
|
+
const effectiveCorner = (corner === 'none' && style.borderStyle === 'eighth-cell-outer') ? 'h' : corner;
|
|
171
|
+
const hOwnsCorners = effectiveCorner === 'h';
|
|
172
|
+
const vOwnsCorners = effectiveCorner === 'v';
|
|
173
|
+
const corners = (effectiveCorner === 'none') ? BLOCK_CORNERS[style.borderStyle] : undefined;
|
|
174
|
+
const horizStart = (left && !hOwnsCorners) ? x + 1 : x;
|
|
175
|
+
const horizEnd = (right && !hOwnsCorners) ? x + width - 1 : x + width;
|
|
176
|
+
const vertStart = (top && !vOwnsCorners) ? y + 1 : y;
|
|
177
|
+
const vertEnd = (bottom && !vOwnsCorners) ? y + height - 1 : y + height;
|
|
178
|
+
// Place corner glyphs first so edge-paint loops don't overwrite them.
|
|
179
|
+
if (corners) {
|
|
180
|
+
if (top && left)
|
|
181
|
+
buffer.setCell(x, y, { char: corners.topLeft, fg });
|
|
182
|
+
if (top && right)
|
|
183
|
+
buffer.setCell(x + width - 1, y, { char: corners.topRight, fg });
|
|
184
|
+
if (bottom && left)
|
|
185
|
+
buffer.setCell(x, y + height - 1, { char: corners.bottomLeft, fg });
|
|
186
|
+
if (bottom && right)
|
|
187
|
+
buffer.setCell(x + width - 1, y + height - 1, { char: corners.bottomRight, fg });
|
|
188
|
+
}
|
|
189
|
+
if (top) {
|
|
190
|
+
for (let col = horizStart; col < horizEnd; col++) {
|
|
191
|
+
buffer.setCell(col, y, { char: edges.top, fg });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (bottom) {
|
|
195
|
+
for (let col = horizStart; col < horizEnd; col++) {
|
|
196
|
+
buffer.setCell(col, y + height - 1, { char: edges.bottom, fg });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (left) {
|
|
200
|
+
for (let row = vertStart; row < vertEnd; row++) {
|
|
201
|
+
buffer.setCell(x, row, { char: edges.left, fg });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (right) {
|
|
205
|
+
for (let row = vertStart; row < vertEnd; row++) {
|
|
206
|
+
buffer.setCell(x + width - 1, row, { char: edges.right, fg });
|
|
58
207
|
}
|
|
59
208
|
}
|
|
60
209
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const EMPTY_CELL = {
|
|
2
2
|
char: ' ', fg: 'default', bg: 'default',
|
|
3
3
|
bold: false, italic: false, underline: false,
|
|
4
|
-
strikethrough: false, dim: false,
|
|
4
|
+
strikethrough: false, dim: false, inverse: false,
|
|
5
5
|
};
|
|
6
6
|
export class CellBuffer {
|
|
7
7
|
width;
|
|
@@ -37,6 +37,7 @@ export class CellBuffer {
|
|
|
37
37
|
underline: cell.underline ?? existing.underline,
|
|
38
38
|
strikethrough: cell.strikethrough ?? existing.strikethrough,
|
|
39
39
|
dim: cell.dim ?? existing.dim,
|
|
40
|
+
inverse: cell.inverse ?? existing.inverse,
|
|
40
41
|
hyperlink: cell.hyperlink ?? existing.hyperlink,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -51,6 +52,7 @@ export class CellBuffer {
|
|
|
51
52
|
underline: style?.underline,
|
|
52
53
|
strikethrough: style?.strikethrough,
|
|
53
54
|
dim: style?.dim,
|
|
55
|
+
inverse: style?.inverse,
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
58
|
}
|
|
@@ -66,5 +68,6 @@ export function cellsEqual(a, b) {
|
|
|
66
68
|
return a.char === b.char && a.fg === b.fg && a.bg === b.bg
|
|
67
69
|
&& a.bold === b.bold && a.italic === b.italic
|
|
68
70
|
&& a.underline === b.underline && a.strikethrough === b.strikethrough
|
|
69
|
-
&& a.dim === b.dim && a.
|
|
71
|
+
&& a.dim === b.dim && a.inverse === b.inverse
|
|
72
|
+
&& a.hyperlink === b.hyperlink;
|
|
70
73
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clock seam for time-driven rendering (animations, transitions, and the
|
|
3
|
+
* scrollbar fade). Abstracts both the time source and the frame
|
|
4
|
+
* scheduler so tests can advance time and fire ticks deterministically
|
|
5
|
+
* instead of depending on `Date.now()` and real `setInterval`.
|
|
6
|
+
*/
|
|
7
|
+
export type ClockTimer = unknown;
|
|
8
|
+
export interface Clock {
|
|
9
|
+
now(): number;
|
|
10
|
+
setInterval(fn: () => void, ms: number): ClockTimer;
|
|
11
|
+
clearInterval(timer: ClockTimer): void;
|
|
12
|
+
}
|
|
13
|
+
/** The real clock: wall time and the platform timer. */
|
|
14
|
+
export declare const systemClock: Clock;
|
|
15
|
+
/** Wrap a bare time function in a Clock backed by the real scheduler. */
|
|
16
|
+
export declare function clockFromNow(now: () => number): Clock;
|
|
17
|
+
/**
|
|
18
|
+
* A controllable clock for tests: set the time directly, or `advance`
|
|
19
|
+
* it, which fires any registered intervals at each tick they cross.
|
|
20
|
+
*/
|
|
21
|
+
export declare class TestClock implements Clock {
|
|
22
|
+
private time;
|
|
23
|
+
private timers;
|
|
24
|
+
constructor(start?: number);
|
|
25
|
+
now(): number;
|
|
26
|
+
setInterval(fn: () => void, ms: number): ClockTimer;
|
|
27
|
+
clearInterval(timer: ClockTimer): void;
|
|
28
|
+
/** Jump to an absolute time without firing intervals. */
|
|
29
|
+
setTime(time: number): void;
|
|
30
|
+
/** Advance by `ms`, firing each interval at every tick it crosses. */
|
|
31
|
+
advance(ms: number): void;
|
|
32
|
+
/** Timers registered right now (for assertions). */
|
|
33
|
+
get activeTimers(): number;
|
|
34
|
+
private nextTimerBefore;
|
|
35
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clock seam for time-driven rendering (animations, transitions, and the
|
|
3
|
+
* scrollbar fade). Abstracts both the time source and the frame
|
|
4
|
+
* scheduler so tests can advance time and fire ticks deterministically
|
|
5
|
+
* instead of depending on `Date.now()` and real `setInterval`.
|
|
6
|
+
*/
|
|
7
|
+
/** The real clock: wall time and the platform timer. */
|
|
8
|
+
export const systemClock = {
|
|
9
|
+
now: () => Date.now(),
|
|
10
|
+
setInterval: (fn, ms) => setInterval(fn, ms),
|
|
11
|
+
clearInterval: (timer) => clearInterval(timer),
|
|
12
|
+
};
|
|
13
|
+
/** Wrap a bare time function in a Clock backed by the real scheduler. */
|
|
14
|
+
export function clockFromNow(now) {
|
|
15
|
+
return { now, setInterval: systemClock.setInterval, clearInterval: systemClock.clearInterval };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A controllable clock for tests: set the time directly, or `advance`
|
|
19
|
+
* it, which fires any registered intervals at each tick they cross.
|
|
20
|
+
*/
|
|
21
|
+
export class TestClock {
|
|
22
|
+
time;
|
|
23
|
+
timers = new Set();
|
|
24
|
+
constructor(start = 0) {
|
|
25
|
+
this.time = start;
|
|
26
|
+
}
|
|
27
|
+
now() {
|
|
28
|
+
return this.time;
|
|
29
|
+
}
|
|
30
|
+
setInterval(fn, ms) {
|
|
31
|
+
const timer = { fn, ms, next: this.time + ms };
|
|
32
|
+
this.timers.add(timer);
|
|
33
|
+
return timer;
|
|
34
|
+
}
|
|
35
|
+
clearInterval(timer) {
|
|
36
|
+
this.timers.delete(timer);
|
|
37
|
+
}
|
|
38
|
+
/** Jump to an absolute time without firing intervals. */
|
|
39
|
+
setTime(time) {
|
|
40
|
+
this.time = time;
|
|
41
|
+
}
|
|
42
|
+
/** Advance by `ms`, firing each interval at every tick it crosses. */
|
|
43
|
+
advance(ms) {
|
|
44
|
+
const target = this.time + ms;
|
|
45
|
+
while (true) {
|
|
46
|
+
const due = this.nextTimerBefore(target);
|
|
47
|
+
if (!due)
|
|
48
|
+
break;
|
|
49
|
+
this.time = due.next;
|
|
50
|
+
due.next += due.ms;
|
|
51
|
+
due.fn();
|
|
52
|
+
}
|
|
53
|
+
this.time = target;
|
|
54
|
+
}
|
|
55
|
+
/** Timers registered right now (for assertions). */
|
|
56
|
+
get activeTimers() {
|
|
57
|
+
return this.timers.size;
|
|
58
|
+
}
|
|
59
|
+
nextTimerBefore(target) {
|
|
60
|
+
let earliest = null;
|
|
61
|
+
for (const timer of this.timers) {
|
|
62
|
+
if (timer.next <= target && (!earliest || timer.next < earliest.next))
|
|
63
|
+
earliest = timer;
|
|
64
|
+
}
|
|
65
|
+
return earliest;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour quantization for terminals without truecolor: hex → the xterm
|
|
3
|
+
* 256-colour palette, or the nearest of the 16 base colours.
|
|
4
|
+
*/
|
|
5
|
+
/** Map #rrggbb to the nearest xterm 256-palette index (cube or grey ramp). */
|
|
6
|
+
export declare function quantizeTo256(hex: string): number;
|
|
7
|
+
/** Map #rrggbb to the nearest of the 16 base colour names. */
|
|
8
|
+
export declare function quantizeTo16(hex: string): string;
|