@teammates/consolonia 0.2.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 +48 -0
- package/dist/__tests__/ansi.test.d.ts +1 -0
- package/dist/__tests__/ansi.test.js +520 -0
- package/dist/__tests__/chat-view.test.d.ts +4 -0
- package/dist/__tests__/chat-view.test.js +480 -0
- package/dist/__tests__/drawing.test.d.ts +4 -0
- package/dist/__tests__/drawing.test.js +426 -0
- package/dist/__tests__/input.test.d.ts +5 -0
- package/dist/__tests__/input.test.js +911 -0
- package/dist/__tests__/layout.test.d.ts +4 -0
- package/dist/__tests__/layout.test.js +689 -0
- package/dist/__tests__/pixel.test.d.ts +1 -0
- package/dist/__tests__/pixel.test.js +674 -0
- package/dist/__tests__/render.test.d.ts +1 -0
- package/dist/__tests__/render.test.js +400 -0
- package/dist/__tests__/styled.test.d.ts +4 -0
- package/dist/__tests__/styled.test.js +149 -0
- package/dist/__tests__/widgets.test.d.ts +5 -0
- package/dist/__tests__/widgets.test.js +924 -0
- package/dist/ansi/esc.d.ts +61 -0
- package/dist/ansi/esc.js +85 -0
- package/dist/ansi/output.d.ts +66 -0
- package/dist/ansi/output.js +192 -0
- package/dist/ansi/strip.d.ts +16 -0
- package/dist/ansi/strip.js +74 -0
- package/dist/app.d.ts +68 -0
- package/dist/app.js +297 -0
- package/dist/drawing/clip.d.ts +23 -0
- package/dist/drawing/clip.js +67 -0
- package/dist/drawing/context.d.ts +77 -0
- package/dist/drawing/context.js +275 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +63 -0
- package/dist/input/escape-matcher.d.ts +27 -0
- package/dist/input/escape-matcher.js +253 -0
- package/dist/input/events.d.ts +49 -0
- package/dist/input/events.js +17 -0
- package/dist/input/index.d.ts +15 -0
- package/dist/input/index.js +14 -0
- package/dist/input/matcher.d.ts +23 -0
- package/dist/input/matcher.js +14 -0
- package/dist/input/mouse-matcher.d.ts +27 -0
- package/dist/input/mouse-matcher.js +142 -0
- package/dist/input/paste-matcher.d.ts +23 -0
- package/dist/input/paste-matcher.js +104 -0
- package/dist/input/processor.d.ts +51 -0
- package/dist/input/processor.js +145 -0
- package/dist/input/raw-mode.d.ts +13 -0
- package/dist/input/raw-mode.js +24 -0
- package/dist/input/text-matcher.d.ts +14 -0
- package/dist/input/text-matcher.js +32 -0
- package/dist/layout/box.d.ts +33 -0
- package/dist/layout/box.js +92 -0
- package/dist/layout/column.d.ts +21 -0
- package/dist/layout/column.js +90 -0
- package/dist/layout/control.d.ts +73 -0
- package/dist/layout/control.js +215 -0
- package/dist/layout/row.d.ts +21 -0
- package/dist/layout/row.js +95 -0
- package/dist/layout/stack.d.ts +18 -0
- package/dist/layout/stack.js +64 -0
- package/dist/layout/types.d.ts +27 -0
- package/dist/layout/types.js +4 -0
- package/dist/pixel/background.d.ts +16 -0
- package/dist/pixel/background.js +16 -0
- package/dist/pixel/box-pattern.d.ts +38 -0
- package/dist/pixel/box-pattern.js +57 -0
- package/dist/pixel/buffer.d.ts +25 -0
- package/dist/pixel/buffer.js +51 -0
- package/dist/pixel/color.d.ts +48 -0
- package/dist/pixel/color.js +92 -0
- package/dist/pixel/foreground.d.ts +31 -0
- package/dist/pixel/foreground.js +64 -0
- package/dist/pixel/pixel.d.ts +21 -0
- package/dist/pixel/pixel.js +38 -0
- package/dist/pixel/symbol.d.ts +38 -0
- package/dist/pixel/symbol.js +192 -0
- package/dist/render/regions.d.ts +54 -0
- package/dist/render/regions.js +102 -0
- package/dist/render/render-target.d.ts +42 -0
- package/dist/render/render-target.js +118 -0
- package/dist/styled.d.ts +113 -0
- package/dist/styled.js +176 -0
- package/dist/widgets/border.d.ts +34 -0
- package/dist/widgets/border.js +121 -0
- package/dist/widgets/chat-view.d.ts +239 -0
- package/dist/widgets/chat-view.js +993 -0
- package/dist/widgets/interview.d.ts +87 -0
- package/dist/widgets/interview.js +187 -0
- package/dist/widgets/markdown.d.ts +87 -0
- package/dist/widgets/markdown.js +611 -0
- package/dist/widgets/panel.d.ts +19 -0
- package/dist/widgets/panel.js +35 -0
- package/dist/widgets/scroll-view.d.ts +43 -0
- package/dist/widgets/scroll-view.js +182 -0
- package/dist/widgets/styled-text.d.ts +38 -0
- package/dist/widgets/styled-text.js +183 -0
- package/dist/widgets/syntax.d.ts +37 -0
- package/dist/widgets/syntax.js +670 -0
- package/dist/widgets/text-input.d.ts +121 -0
- package/dist/widgets/text-input.js +618 -0
- package/dist/widgets/text.d.ts +34 -0
- package/dist/widgets/text.js +168 -0
- package/package.json +45 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape sequence constants and builder functions.
|
|
3
|
+
* All functions return raw escape strings — nothing is written to stdout.
|
|
4
|
+
*/
|
|
5
|
+
export declare const reset = "\u001B[0m";
|
|
6
|
+
export declare const bold = "\u001B[1m";
|
|
7
|
+
export declare const dim = "\u001B[2m";
|
|
8
|
+
export declare const italic = "\u001B[3m";
|
|
9
|
+
export declare const underline = "\u001B[4m";
|
|
10
|
+
export declare const strikethrough = "\u001B[9m";
|
|
11
|
+
export declare const boldOff = "\u001B[22m";
|
|
12
|
+
export declare const dimOff = "\u001B[22m";
|
|
13
|
+
export declare const italicOff = "\u001B[23m";
|
|
14
|
+
export declare const underlineOff = "\u001B[24m";
|
|
15
|
+
export declare const strikethroughOff = "\u001B[29m";
|
|
16
|
+
/** Set foreground to an RGB truecolor value. */
|
|
17
|
+
export declare function fg(r: number, g: number, b: number): string;
|
|
18
|
+
/** Set background to an RGB truecolor value. */
|
|
19
|
+
export declare function bg(r: number, g: number, b: number): string;
|
|
20
|
+
/** Reset foreground color to default. */
|
|
21
|
+
export declare const fgDefault = "\u001B[39m";
|
|
22
|
+
/** Reset background color to default. */
|
|
23
|
+
export declare const bgDefault = "\u001B[49m";
|
|
24
|
+
/** Move cursor to absolute position (0-based). */
|
|
25
|
+
export declare function moveTo(x: number, y: number): string;
|
|
26
|
+
/** Move cursor up by n lines. */
|
|
27
|
+
export declare function moveUp(n?: number): string;
|
|
28
|
+
/** Move cursor down by n lines. */
|
|
29
|
+
export declare function moveDown(n?: number): string;
|
|
30
|
+
/** Move cursor right by n columns. */
|
|
31
|
+
export declare function moveRight(n?: number): string;
|
|
32
|
+
/** Move cursor left by n columns. */
|
|
33
|
+
export declare function moveLeft(n?: number): string;
|
|
34
|
+
/** Save cursor position. */
|
|
35
|
+
export declare const saveCursor = "\u001B[s";
|
|
36
|
+
/** Restore cursor position. */
|
|
37
|
+
export declare const restoreCursor = "\u001B[u";
|
|
38
|
+
/** Hide the cursor. */
|
|
39
|
+
export declare const hideCursor = "\u001B[?25l";
|
|
40
|
+
/** Show the cursor. */
|
|
41
|
+
export declare const showCursor = "\u001B[?25h";
|
|
42
|
+
/** Switch to the alternate screen buffer. */
|
|
43
|
+
export declare const alternateScreenOn = "\u001B[?1049h";
|
|
44
|
+
/** Switch back from the alternate screen buffer. */
|
|
45
|
+
export declare const alternateScreenOff = "\u001B[?1049l";
|
|
46
|
+
/** Clear the entire screen. */
|
|
47
|
+
export declare const clearScreen = "\u001B[2J";
|
|
48
|
+
/** Erase from cursor to end of display. */
|
|
49
|
+
export declare const eraseDown = "\u001B[0J";
|
|
50
|
+
/** Erase the entire current line. */
|
|
51
|
+
export declare const eraseLine = "\u001B[2K";
|
|
52
|
+
/** Enable bracketed paste mode. */
|
|
53
|
+
export declare const bracketedPasteOn = "\u001B[?2004h";
|
|
54
|
+
/** Disable bracketed paste mode. */
|
|
55
|
+
export declare const bracketedPasteOff = "\u001B[?2004l";
|
|
56
|
+
/** Enable SGR mouse tracking (any-event tracking + SGR extended coordinates). */
|
|
57
|
+
export declare const mouseTrackingOn = "\u001B[?1003h\u001B[?1006h";
|
|
58
|
+
/** Disable SGR mouse tracking. */
|
|
59
|
+
export declare const mouseTrackingOff = "\u001B[?1006l\u001B[?1003l";
|
|
60
|
+
/** Set the terminal window title. */
|
|
61
|
+
export declare function setTitle(title: string): string;
|
package/dist/ansi/esc.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape sequence constants and builder functions.
|
|
3
|
+
* All functions return raw escape strings — nothing is written to stdout.
|
|
4
|
+
*/
|
|
5
|
+
const ESC = "\x1b[";
|
|
6
|
+
const OSC = "\x1b]";
|
|
7
|
+
// ── Text style ──────────────────────────────────────────────────────
|
|
8
|
+
export const reset = `${ESC}0m`;
|
|
9
|
+
export const bold = `${ESC}1m`;
|
|
10
|
+
export const dim = `${ESC}2m`;
|
|
11
|
+
export const italic = `${ESC}3m`;
|
|
12
|
+
export const underline = `${ESC}4m`;
|
|
13
|
+
export const strikethrough = `${ESC}9m`;
|
|
14
|
+
export const boldOff = `${ESC}22m`;
|
|
15
|
+
export const dimOff = `${ESC}22m`;
|
|
16
|
+
export const italicOff = `${ESC}23m`;
|
|
17
|
+
export const underlineOff = `${ESC}24m`;
|
|
18
|
+
export const strikethroughOff = `${ESC}29m`;
|
|
19
|
+
// ── RGB truecolor ───────────────────────────────────────────────────
|
|
20
|
+
/** Set foreground to an RGB truecolor value. */
|
|
21
|
+
export function fg(r, g, b) {
|
|
22
|
+
return `${ESC}38;2;${r};${g};${b}m`;
|
|
23
|
+
}
|
|
24
|
+
/** Set background to an RGB truecolor value. */
|
|
25
|
+
export function bg(r, g, b) {
|
|
26
|
+
return `${ESC}48;2;${r};${g};${b}m`;
|
|
27
|
+
}
|
|
28
|
+
/** Reset foreground color to default. */
|
|
29
|
+
export const fgDefault = `${ESC}39m`;
|
|
30
|
+
/** Reset background color to default. */
|
|
31
|
+
export const bgDefault = `${ESC}49m`;
|
|
32
|
+
// ── Cursor movement ─────────────────────────────────────────────────
|
|
33
|
+
/** Move cursor to absolute position (0-based). */
|
|
34
|
+
export function moveTo(x, y) {
|
|
35
|
+
return `${ESC}${y + 1};${x + 1}H`;
|
|
36
|
+
}
|
|
37
|
+
/** Move cursor up by n lines. */
|
|
38
|
+
export function moveUp(n = 1) {
|
|
39
|
+
return `${ESC}${n}A`;
|
|
40
|
+
}
|
|
41
|
+
/** Move cursor down by n lines. */
|
|
42
|
+
export function moveDown(n = 1) {
|
|
43
|
+
return `${ESC}${n}B`;
|
|
44
|
+
}
|
|
45
|
+
/** Move cursor right by n columns. */
|
|
46
|
+
export function moveRight(n = 1) {
|
|
47
|
+
return `${ESC}${n}C`;
|
|
48
|
+
}
|
|
49
|
+
/** Move cursor left by n columns. */
|
|
50
|
+
export function moveLeft(n = 1) {
|
|
51
|
+
return `${ESC}${n}D`;
|
|
52
|
+
}
|
|
53
|
+
/** Save cursor position. */
|
|
54
|
+
export const saveCursor = `${ESC}s`;
|
|
55
|
+
/** Restore cursor position. */
|
|
56
|
+
export const restoreCursor = `${ESC}u`;
|
|
57
|
+
/** Hide the cursor. */
|
|
58
|
+
export const hideCursor = `${ESC}?25l`;
|
|
59
|
+
/** Show the cursor. */
|
|
60
|
+
export const showCursor = `${ESC}?25h`;
|
|
61
|
+
// ── Screen ──────────────────────────────────────────────────────────
|
|
62
|
+
/** Switch to the alternate screen buffer. */
|
|
63
|
+
export const alternateScreenOn = `${ESC}?1049h`;
|
|
64
|
+
/** Switch back from the alternate screen buffer. */
|
|
65
|
+
export const alternateScreenOff = `${ESC}?1049l`;
|
|
66
|
+
/** Clear the entire screen. */
|
|
67
|
+
export const clearScreen = `${ESC}2J`;
|
|
68
|
+
/** Erase from cursor to end of display. */
|
|
69
|
+
export const eraseDown = `${ESC}0J`;
|
|
70
|
+
/** Erase the entire current line. */
|
|
71
|
+
export const eraseLine = `${ESC}2K`;
|
|
72
|
+
// ── Input modes ─────────────────────────────────────────────────────
|
|
73
|
+
/** Enable bracketed paste mode. */
|
|
74
|
+
export const bracketedPasteOn = `${ESC}?2004h`;
|
|
75
|
+
/** Disable bracketed paste mode. */
|
|
76
|
+
export const bracketedPasteOff = `${ESC}?2004l`;
|
|
77
|
+
/** Enable SGR mouse tracking (any-event tracking + SGR extended coordinates). */
|
|
78
|
+
export const mouseTrackingOn = `${ESC}?1003h${ESC}?1006h`;
|
|
79
|
+
/** Disable SGR mouse tracking. */
|
|
80
|
+
export const mouseTrackingOff = `${ESC}?1006l${ESC}?1003l`;
|
|
81
|
+
// ── Window ──────────────────────────────────────────────────────────
|
|
82
|
+
/** Set the terminal window title. */
|
|
83
|
+
export function setTitle(title) {
|
|
84
|
+
return `${OSC}0;${title}\x07`;
|
|
85
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffered ANSI output writer.
|
|
3
|
+
* Inspired by Consolonia's AnsiConsoleOutput.cs — tracks terminal state
|
|
4
|
+
* to emit minimal escape sequences when writing pixels.
|
|
5
|
+
*/
|
|
6
|
+
import type { Writable } from "node:stream";
|
|
7
|
+
import type { Color } from "../pixel/color.js";
|
|
8
|
+
import type { Pixel } from "../pixel/pixel.js";
|
|
9
|
+
/**
|
|
10
|
+
* Buffered ANSI writer that accumulates escape sequences in a string buffer
|
|
11
|
+
* and flushes them in a single write to the underlying stream.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AnsiOutput {
|
|
14
|
+
private readonly stream;
|
|
15
|
+
private buf;
|
|
16
|
+
private lastX;
|
|
17
|
+
private lastY;
|
|
18
|
+
private lastFgColor;
|
|
19
|
+
private lastBgColor;
|
|
20
|
+
private lastBold;
|
|
21
|
+
private lastItalic;
|
|
22
|
+
private lastUnderline;
|
|
23
|
+
private lastStrikethrough;
|
|
24
|
+
constructor(stream?: Writable);
|
|
25
|
+
/** Append raw text to the internal buffer. */
|
|
26
|
+
private write;
|
|
27
|
+
/** Move cursor to (x, y) if not already there. */
|
|
28
|
+
setCursor(x: number, y: number): void;
|
|
29
|
+
/** Emit hide-cursor sequence. */
|
|
30
|
+
hideCursor(): void;
|
|
31
|
+
/** Emit show-cursor sequence. */
|
|
32
|
+
showCursor(): void;
|
|
33
|
+
/**
|
|
34
|
+
* Write a single pixel at (x, y).
|
|
35
|
+
* Cursor is moved only if necessary. Colors and styles are set only when
|
|
36
|
+
* they differ from the previously written state.
|
|
37
|
+
*/
|
|
38
|
+
writePixel(x: number, y: number, pixel: Pixel): void;
|
|
39
|
+
/**
|
|
40
|
+
* Write a string starting at (x, y) with optional style overrides.
|
|
41
|
+
* Convenience wrapper that writes character-by-character is wasteful,
|
|
42
|
+
* so instead we set styles once and write the full text.
|
|
43
|
+
*/
|
|
44
|
+
writeText(x: number, y: number, text: string, style?: {
|
|
45
|
+
fgColor?: Color;
|
|
46
|
+
bgColor?: Color;
|
|
47
|
+
bold?: boolean;
|
|
48
|
+
italic?: boolean;
|
|
49
|
+
underline?: boolean;
|
|
50
|
+
strikethrough?: boolean;
|
|
51
|
+
}): void;
|
|
52
|
+
/** Write the accumulated buffer to the output stream and clear it. */
|
|
53
|
+
flush(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Prepare the terminal for full-screen TUI rendering:
|
|
56
|
+
* alternate screen, hide cursor, bracketed paste, mouse tracking.
|
|
57
|
+
*/
|
|
58
|
+
prepareTerminal(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Restore the terminal to its normal state:
|
|
61
|
+
* disable mouse, disable bracketed paste, show cursor, leave alternate screen.
|
|
62
|
+
*/
|
|
63
|
+
restoreTerminal(): void;
|
|
64
|
+
/** Reset all tracked state so the next write re-emits everything. */
|
|
65
|
+
private resetState;
|
|
66
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buffered ANSI output writer.
|
|
3
|
+
* Inspired by Consolonia's AnsiConsoleOutput.cs — tracks terminal state
|
|
4
|
+
* to emit minimal escape sequences when writing pixels.
|
|
5
|
+
*/
|
|
6
|
+
import * as esc from "./esc.js";
|
|
7
|
+
/** Sentinel value meaning "no color has been set yet". */
|
|
8
|
+
const NO_COLOR = { r: -1, g: -1, b: -1, a: -1 };
|
|
9
|
+
function colorsEqual(a, b) {
|
|
10
|
+
return a.r === b.r && a.g === b.g && a.b === b.b;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Buffered ANSI writer that accumulates escape sequences in a string buffer
|
|
14
|
+
* and flushes them in a single write to the underlying stream.
|
|
15
|
+
*/
|
|
16
|
+
export class AnsiOutput {
|
|
17
|
+
stream;
|
|
18
|
+
buf = "";
|
|
19
|
+
// ── Tracked state to avoid redundant escapes ──────────────────────
|
|
20
|
+
lastX = -1;
|
|
21
|
+
lastY = -1;
|
|
22
|
+
lastFgColor = NO_COLOR;
|
|
23
|
+
lastBgColor = NO_COLOR;
|
|
24
|
+
lastBold = false;
|
|
25
|
+
lastItalic = false;
|
|
26
|
+
lastUnderline = false;
|
|
27
|
+
lastStrikethrough = false;
|
|
28
|
+
constructor(stream) {
|
|
29
|
+
this.stream = stream ?? process.stdout;
|
|
30
|
+
}
|
|
31
|
+
// ── Low-level append ──────────────────────────────────────────────
|
|
32
|
+
/** Append raw text to the internal buffer. */
|
|
33
|
+
write(s) {
|
|
34
|
+
this.buf += s;
|
|
35
|
+
}
|
|
36
|
+
// ── Cursor ────────────────────────────────────────────────────────
|
|
37
|
+
/** Move cursor to (x, y) if not already there. */
|
|
38
|
+
setCursor(x, y) {
|
|
39
|
+
if (x !== this.lastX || y !== this.lastY) {
|
|
40
|
+
this.write(esc.moveTo(x, y));
|
|
41
|
+
this.lastX = x;
|
|
42
|
+
this.lastY = y;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Emit hide-cursor sequence. */
|
|
46
|
+
hideCursor() {
|
|
47
|
+
this.write(esc.hideCursor);
|
|
48
|
+
}
|
|
49
|
+
/** Emit show-cursor sequence. */
|
|
50
|
+
showCursor() {
|
|
51
|
+
this.write(esc.showCursor);
|
|
52
|
+
}
|
|
53
|
+
// ── Pixel writing ─────────────────────────────────────────────────
|
|
54
|
+
/**
|
|
55
|
+
* Write a single pixel at (x, y).
|
|
56
|
+
* Cursor is moved only if necessary. Colors and styles are set only when
|
|
57
|
+
* they differ from the previously written state.
|
|
58
|
+
*/
|
|
59
|
+
writePixel(x, y, pixel) {
|
|
60
|
+
this.setCursor(x, y);
|
|
61
|
+
const fgColor = pixel.foreground.color;
|
|
62
|
+
const bgColor = pixel.background.color;
|
|
63
|
+
const { bold: isBold, italic: isItalic, underline: isUnderline, strikethrough: isStrike, } = pixel.foreground;
|
|
64
|
+
// ── Style toggles (emit only on change) ─────────────────────
|
|
65
|
+
if (isBold !== this.lastBold) {
|
|
66
|
+
this.write(isBold ? esc.bold : esc.boldOff);
|
|
67
|
+
this.lastBold = isBold;
|
|
68
|
+
}
|
|
69
|
+
if (isItalic !== this.lastItalic) {
|
|
70
|
+
this.write(isItalic ? esc.italic : esc.italicOff);
|
|
71
|
+
this.lastItalic = isItalic;
|
|
72
|
+
}
|
|
73
|
+
if (isUnderline !== this.lastUnderline) {
|
|
74
|
+
this.write(isUnderline ? esc.underline : esc.underlineOff);
|
|
75
|
+
this.lastUnderline = isUnderline;
|
|
76
|
+
}
|
|
77
|
+
if (isStrike !== this.lastStrikethrough) {
|
|
78
|
+
this.write(isStrike ? esc.strikethrough : esc.strikethroughOff);
|
|
79
|
+
this.lastStrikethrough = isStrike;
|
|
80
|
+
}
|
|
81
|
+
// ── Foreground color ────────────────────────────────────────
|
|
82
|
+
if (fgColor.a > 0 && !colorsEqual(fgColor, this.lastFgColor)) {
|
|
83
|
+
this.write(esc.fg(fgColor.r, fgColor.g, fgColor.b));
|
|
84
|
+
this.lastFgColor = fgColor;
|
|
85
|
+
}
|
|
86
|
+
else if (fgColor.a === 0 &&
|
|
87
|
+
this.lastFgColor !== NO_COLOR &&
|
|
88
|
+
this.lastFgColor.a !== 0) {
|
|
89
|
+
this.write(esc.fgDefault);
|
|
90
|
+
this.lastFgColor = NO_COLOR;
|
|
91
|
+
}
|
|
92
|
+
// ── Background color ────────────────────────────────────────
|
|
93
|
+
if (bgColor.a > 0 && !colorsEqual(bgColor, this.lastBgColor)) {
|
|
94
|
+
this.write(esc.bg(bgColor.r, bgColor.g, bgColor.b));
|
|
95
|
+
this.lastBgColor = bgColor;
|
|
96
|
+
}
|
|
97
|
+
else if (bgColor.a === 0 &&
|
|
98
|
+
this.lastBgColor !== NO_COLOR &&
|
|
99
|
+
this.lastBgColor.a !== 0) {
|
|
100
|
+
this.write(esc.bgDefault);
|
|
101
|
+
this.lastBgColor = NO_COLOR;
|
|
102
|
+
}
|
|
103
|
+
// ── Character ───────────────────────────────────────────────
|
|
104
|
+
const ch = pixel.foreground.symbol.text;
|
|
105
|
+
this.write(ch);
|
|
106
|
+
this.lastX += pixel.foreground.symbol.width;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Write a string starting at (x, y) with optional style overrides.
|
|
110
|
+
* Convenience wrapper that writes character-by-character is wasteful,
|
|
111
|
+
* so instead we set styles once and write the full text.
|
|
112
|
+
*/
|
|
113
|
+
writeText(x, y, text, style) {
|
|
114
|
+
this.setCursor(x, y);
|
|
115
|
+
if (style) {
|
|
116
|
+
if (style.bold !== undefined && style.bold !== this.lastBold) {
|
|
117
|
+
this.write(style.bold ? esc.bold : esc.boldOff);
|
|
118
|
+
this.lastBold = style.bold;
|
|
119
|
+
}
|
|
120
|
+
if (style.italic !== undefined && style.italic !== this.lastItalic) {
|
|
121
|
+
this.write(style.italic ? esc.italic : esc.italicOff);
|
|
122
|
+
this.lastItalic = style.italic;
|
|
123
|
+
}
|
|
124
|
+
if (style.underline !== undefined &&
|
|
125
|
+
style.underline !== this.lastUnderline) {
|
|
126
|
+
this.write(style.underline ? esc.underline : esc.underlineOff);
|
|
127
|
+
this.lastUnderline = style.underline;
|
|
128
|
+
}
|
|
129
|
+
if (style.strikethrough !== undefined &&
|
|
130
|
+
style.strikethrough !== this.lastStrikethrough) {
|
|
131
|
+
this.write(style.strikethrough ? esc.strikethrough : esc.strikethroughOff);
|
|
132
|
+
this.lastStrikethrough = style.strikethrough;
|
|
133
|
+
}
|
|
134
|
+
if (style.fgColor && !colorsEqual(style.fgColor, this.lastFgColor)) {
|
|
135
|
+
this.write(esc.fg(style.fgColor.r, style.fgColor.g, style.fgColor.b));
|
|
136
|
+
this.lastFgColor = style.fgColor;
|
|
137
|
+
}
|
|
138
|
+
if (style.bgColor && !colorsEqual(style.bgColor, this.lastBgColor)) {
|
|
139
|
+
this.write(esc.bg(style.bgColor.r, style.bgColor.g, style.bgColor.b));
|
|
140
|
+
this.lastBgColor = style.bgColor;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
this.write(text);
|
|
144
|
+
this.lastX += text.length;
|
|
145
|
+
}
|
|
146
|
+
// ── Flush ─────────────────────────────────────────────────────────
|
|
147
|
+
/** Write the accumulated buffer to the output stream and clear it. */
|
|
148
|
+
flush() {
|
|
149
|
+
if (this.buf.length > 0) {
|
|
150
|
+
this.stream.write(this.buf);
|
|
151
|
+
this.buf = "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ── Terminal setup / teardown ─────────────────────────────────────
|
|
155
|
+
/**
|
|
156
|
+
* Prepare the terminal for full-screen TUI rendering:
|
|
157
|
+
* alternate screen, hide cursor, bracketed paste, mouse tracking.
|
|
158
|
+
*/
|
|
159
|
+
prepareTerminal() {
|
|
160
|
+
this.write(esc.alternateScreenOn);
|
|
161
|
+
this.write(esc.hideCursor);
|
|
162
|
+
this.write(esc.bracketedPasteOn);
|
|
163
|
+
this.write(esc.mouseTrackingOn);
|
|
164
|
+
this.write(esc.clearScreen);
|
|
165
|
+
this.flush();
|
|
166
|
+
this.resetState();
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Restore the terminal to its normal state:
|
|
170
|
+
* disable mouse, disable bracketed paste, show cursor, leave alternate screen.
|
|
171
|
+
*/
|
|
172
|
+
restoreTerminal() {
|
|
173
|
+
this.write(esc.reset);
|
|
174
|
+
this.write(esc.mouseTrackingOff);
|
|
175
|
+
this.write(esc.bracketedPasteOff);
|
|
176
|
+
this.write(esc.showCursor);
|
|
177
|
+
this.write(esc.alternateScreenOff);
|
|
178
|
+
this.flush();
|
|
179
|
+
this.resetState();
|
|
180
|
+
}
|
|
181
|
+
/** Reset all tracked state so the next write re-emits everything. */
|
|
182
|
+
resetState() {
|
|
183
|
+
this.lastX = -1;
|
|
184
|
+
this.lastY = -1;
|
|
185
|
+
this.lastFgColor = NO_COLOR;
|
|
186
|
+
this.lastBgColor = NO_COLOR;
|
|
187
|
+
this.lastBold = false;
|
|
188
|
+
this.lastItalic = false;
|
|
189
|
+
this.lastUnderline = false;
|
|
190
|
+
this.lastStrikethrough = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions for stripping and measuring ANSI-escaped strings.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Strip all ANSI escape codes from a string, returning only visible characters.
|
|
6
|
+
*/
|
|
7
|
+
export declare function stripAnsi(str: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Get the visible (non-ANSI) length of a string.
|
|
10
|
+
*/
|
|
11
|
+
export declare function visibleLength(str: string): number;
|
|
12
|
+
/**
|
|
13
|
+
* Truncate a string containing ANSI codes to a maximum number of visible characters.
|
|
14
|
+
* ANSI sequences are preserved up to the cut-off point.
|
|
15
|
+
*/
|
|
16
|
+
export declare function truncateAnsi(str: string, maxWidth: number): string;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Functions for stripping and measuring ANSI-escaped strings.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Regex matching all common ANSI escape sequences:
|
|
6
|
+
* - CSI sequences: ESC [ ... <letter>
|
|
7
|
+
* - OSC sequences: ESC ] ... BEL/ST
|
|
8
|
+
* - Simple two-byte escapes: ESC <char>
|
|
9
|
+
*/
|
|
10
|
+
const ANSI_RE =
|
|
11
|
+
// eslint-disable-next-line no-control-regex
|
|
12
|
+
/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07]*\x07|[()#][A-Za-z0-9]|[A-Za-z])/g;
|
|
13
|
+
/**
|
|
14
|
+
* Strip all ANSI escape codes from a string, returning only visible characters.
|
|
15
|
+
*/
|
|
16
|
+
export function stripAnsi(str) {
|
|
17
|
+
return str.replace(ANSI_RE, "");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the visible (non-ANSI) length of a string.
|
|
21
|
+
*/
|
|
22
|
+
export function visibleLength(str) {
|
|
23
|
+
return stripAnsi(str).length;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Truncate a string containing ANSI codes to a maximum number of visible characters.
|
|
27
|
+
* ANSI sequences are preserved up to the cut-off point.
|
|
28
|
+
*/
|
|
29
|
+
export function truncateAnsi(str, maxWidth) {
|
|
30
|
+
if (maxWidth <= 0)
|
|
31
|
+
return "";
|
|
32
|
+
let visible = 0;
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < str.length && visible < maxWidth) {
|
|
35
|
+
// Check for ESC
|
|
36
|
+
if (str.charCodeAt(i) === 0x1b) {
|
|
37
|
+
// Try to match a CSI sequence: ESC [ ... letter
|
|
38
|
+
if (i + 1 < str.length && str.charCodeAt(i + 1) === 0x5b) {
|
|
39
|
+
// 0x5b = '['
|
|
40
|
+
let j = i + 2;
|
|
41
|
+
while (j < str.length &&
|
|
42
|
+
str.charCodeAt(j) >= 0x20 &&
|
|
43
|
+
str.charCodeAt(j) <= 0x3f) {
|
|
44
|
+
j++;
|
|
45
|
+
}
|
|
46
|
+
if (j < str.length) {
|
|
47
|
+
j++; // consume the final letter
|
|
48
|
+
}
|
|
49
|
+
i = j;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Try to match an OSC sequence: ESC ] ... BEL
|
|
53
|
+
if (i + 1 < str.length && str.charCodeAt(i + 1) === 0x5d) {
|
|
54
|
+
// 0x5d = ']'
|
|
55
|
+
let j = i + 2;
|
|
56
|
+
while (j < str.length && str.charCodeAt(j) !== 0x07) {
|
|
57
|
+
j++;
|
|
58
|
+
}
|
|
59
|
+
if (j < str.length) {
|
|
60
|
+
j++; // consume BEL
|
|
61
|
+
}
|
|
62
|
+
i = j;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Simple two-byte escape
|
|
66
|
+
i += 2;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Visible character
|
|
70
|
+
visible++;
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
return str.slice(0, i);
|
|
74
|
+
}
|
package/dist/app.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App — the top-level shell that owns the entire terminal lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Creates and wires together all subsystems (PixelBuffer, AnsiOutput,
|
|
5
|
+
* RenderTarget, DrawingContext, InputProcessor) and drives the
|
|
6
|
+
* measure → arrange → render loop in response to input and resize events.
|
|
7
|
+
*/
|
|
8
|
+
import type { Control } from "./layout/control.js";
|
|
9
|
+
export interface AppOptions {
|
|
10
|
+
/** Root control to render. */
|
|
11
|
+
root: Control;
|
|
12
|
+
/** Use alternate screen buffer (default: true). */
|
|
13
|
+
alternateScreen?: boolean;
|
|
14
|
+
/** Enable mouse tracking (default: false). */
|
|
15
|
+
mouse?: boolean;
|
|
16
|
+
/** Terminal title (optional). */
|
|
17
|
+
title?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class App {
|
|
20
|
+
readonly root: Control;
|
|
21
|
+
private readonly _alternateScreen;
|
|
22
|
+
private readonly _mouse;
|
|
23
|
+
private readonly _title;
|
|
24
|
+
private _output;
|
|
25
|
+
private _buffer;
|
|
26
|
+
private _dirtyRegions;
|
|
27
|
+
private _renderTarget;
|
|
28
|
+
private _drawingContext;
|
|
29
|
+
private _processor;
|
|
30
|
+
private _events;
|
|
31
|
+
private _running;
|
|
32
|
+
private _resolve;
|
|
33
|
+
private _stdinListener;
|
|
34
|
+
private _resizeListener;
|
|
35
|
+
private _sigintListener;
|
|
36
|
+
private _renderScheduled;
|
|
37
|
+
constructor(options: AppOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Start the app — enters raw mode, sets up terminal, runs the event
|
|
40
|
+
* loop. Returns a promise that resolves when the app stops.
|
|
41
|
+
*/
|
|
42
|
+
run(): Promise<void>;
|
|
43
|
+
/** Stop the app — restores terminal and exits the event loop. */
|
|
44
|
+
stop(): void;
|
|
45
|
+
/** Force a full re-render. */
|
|
46
|
+
refresh(): void;
|
|
47
|
+
private _setup;
|
|
48
|
+
private _prepareTerminal;
|
|
49
|
+
private _restoreTerminal;
|
|
50
|
+
private _createRenderPipeline;
|
|
51
|
+
private _setupInput;
|
|
52
|
+
private _handleInput;
|
|
53
|
+
private _handleResize;
|
|
54
|
+
/**
|
|
55
|
+
* Schedule a render pass using setImmediate so multiple rapid events
|
|
56
|
+
* within the same tick coalesce into a single render.
|
|
57
|
+
*/
|
|
58
|
+
private _scheduleRender;
|
|
59
|
+
/** Perform a full measure → arrange → render cycle (used on init and resize). */
|
|
60
|
+
private _fullRender;
|
|
61
|
+
/** Perform an incremental render for dirty regions. */
|
|
62
|
+
private _renderFrame;
|
|
63
|
+
/** Recursively clear dirty flags on the entire control tree. */
|
|
64
|
+
private _clearDirty;
|
|
65
|
+
/** Run the initial render after setup. */
|
|
66
|
+
private _initialRender;
|
|
67
|
+
private _teardown;
|
|
68
|
+
}
|