@alchemy.run/sigil 0.0.0-alpha.3 → 0.0.0-alpha.5

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.
Files changed (207) hide show
  1. package/README.md +23 -13
  2. package/THIRD_PARTY_NOTICES.md +70 -23
  3. package/dist/Text-D5HUf3Fj.d.ts +452 -0
  4. package/dist/ansi.d.ts +137 -143
  5. package/dist/ansi.js +87 -2
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
  11. package/dist/color-policy-SVj1pYTA.js +560 -0
  12. package/dist/color-profile-DHhQHY55.js +36 -0
  13. package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-Bh4yGP6w.d.ts +186 -0
  18. package/dist/detect-BuTXtY6e.js +373 -0
  19. package/dist/{devtools-BhYGjb7h.js → devtools-DbthxoD1.js} +22 -23
  20. package/dist/env-YVw64yZS.js +9 -0
  21. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  22. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  23. package/dist/index-Bmc2tRPk.d.ts +21 -0
  24. package/dist/index.d.ts +235 -754
  25. package/dist/index.js +1362 -3001
  26. package/dist/osc-CCH7xDoS.js +71 -0
  27. package/dist/osc-Cn0fw77g.d.ts +23 -0
  28. package/dist/paint-C19minOS.d.ts +81 -0
  29. package/dist/query-vaIeGOkH.d.ts +152 -0
  30. package/dist/router.d.ts +392 -0
  31. package/dist/router.js +709 -0
  32. package/dist/sample-Cqw1bjUL.js +445 -0
  33. package/dist/screen-BOh__-65.js +324 -0
  34. package/dist/screen-BReKIheE.d.ts +40 -0
  35. package/dist/screen.d.ts +5 -0
  36. package/dist/screen.js +5 -0
  37. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  38. package/dist/serialize-BTkAZgw1.js +79 -0
  39. package/dist/session-BJmzX2NJ.js +664 -0
  40. package/dist/sgr-BhwaWAJB.js +246 -0
  41. package/dist/store-CgrG9K4y.d.ts +72 -0
  42. package/dist/string-width-CijQwpIk.js +69 -0
  43. package/dist/strip-BvU4toXG.js +6 -0
  44. package/dist/terminal.d.ts +118 -0
  45. package/dist/terminal.js +2 -0
  46. package/dist/tokenize-AjqbvtiT.js +1242 -0
  47. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  48. package/dist/truncate-D31fhU6i.js +562 -0
  49. package/dist/use-focus-C2sciZOo.js +1335 -0
  50. package/package.json +43 -17
  51. package/src/ansi/chalk.ts +12 -53
  52. package/src/ansi/cursor.ts +2 -4
  53. package/src/ansi/east-asian-width.ts +44 -0
  54. package/src/ansi/escapes.ts +18 -26
  55. package/src/ansi/graphemes.ts +8 -0
  56. package/src/ansi/hyperlink.ts +44 -0
  57. package/src/ansi/index.ts +14 -14
  58. package/src/ansi/osc.ts +77 -0
  59. package/src/ansi/sgr.ts +2 -5
  60. package/src/ansi/slice.ts +1 -1
  61. package/src/ansi/string-width.ts +91 -206
  62. package/src/ansi/strip.ts +13 -33
  63. package/src/ansi/tokenize.ts +96 -169
  64. package/src/ansi/truncate.ts +5 -3
  65. package/src/ansi/wrap.ts +13 -14
  66. package/src/ansi-tokenizer.ts +1 -1
  67. package/src/capabilities/color-policy.ts +34 -0
  68. package/src/capabilities/detect.ts +594 -0
  69. package/src/capabilities/index.ts +37 -0
  70. package/src/capabilities/query.ts +657 -0
  71. package/src/capabilities/store.ts +379 -0
  72. package/src/color/index.ts +3 -0
  73. package/src/color/paint.ts +169 -0
  74. package/src/color/palette.ts +48 -0
  75. package/src/color/sample.ts +323 -0
  76. package/src/color.ts +1 -0
  77. package/src/components/AnsiText.tsx +42 -0
  78. package/src/components/App.tsx +109 -22
  79. package/src/components/BackgroundContext.ts +2 -3
  80. package/src/components/Box.tsx +43 -60
  81. package/src/components/CursorContext.ts +1 -1
  82. package/src/components/ErrorBoundary.tsx +1 -1
  83. package/src/components/ErrorOverview.tsx +8 -7
  84. package/src/components/Hyperlink.tsx +56 -0
  85. package/src/components/Spacer.tsx +1 -1
  86. package/src/components/Static.tsx +1 -1
  87. package/src/components/StderrContext.ts +0 -2
  88. package/src/components/StdinContext.ts +0 -1
  89. package/src/components/StdoutContext.ts +1 -3
  90. package/src/components/TerminalOscContext.ts +25 -0
  91. package/src/components/Text.tsx +23 -46
  92. package/src/components/Transform.tsx +2 -2
  93. package/src/cursor-position.ts +1 -1
  94. package/src/devtools.ts +84 -24
  95. package/src/dom.ts +11 -8
  96. package/src/env.ts +12 -0
  97. package/src/get-max-width.ts +1 -1
  98. package/src/global.d.ts +6 -4
  99. package/src/{boxes.ts → glyphs.ts} +17 -18
  100. package/src/hooks/use-animation.ts +1 -1
  101. package/src/hooks/use-app.ts +1 -1
  102. package/src/hooks/use-box-metrics.ts +1 -1
  103. package/src/hooks/use-capabilities.ts +73 -0
  104. package/src/hooks/use-cursor.ts +3 -3
  105. package/src/hooks/use-focus-manager.ts +1 -1
  106. package/src/hooks/use-focus.ts +5 -6
  107. package/src/hooks/use-input.ts +4 -4
  108. package/src/hooks/use-is-screen-reader-enabled.ts +1 -1
  109. package/src/hooks/use-paste.ts +2 -2
  110. package/src/hooks/use-stderr.ts +1 -1
  111. package/src/hooks/use-stdin.ts +1 -1
  112. package/src/hooks/use-stdout.ts +1 -1
  113. package/src/hooks/use-terminal-osc.ts +59 -0
  114. package/src/hooks/use-window-size.ts +2 -2
  115. package/src/index.ts +88 -44
  116. package/src/ink.tsx +620 -745
  117. package/src/input-parser.ts +1 -1
  118. package/src/instances.ts +1 -1
  119. package/src/kitty-keyboard.ts +128 -0
  120. package/src/measure-element.ts +1 -1
  121. package/src/measure-text.ts +2 -2
  122. package/src/{render-node-to-output.ts → paint-tree.ts} +78 -53
  123. package/src/parse-keypress.ts +4 -5
  124. package/src/parse-stack-line.ts +0 -1
  125. package/src/patch-console.ts +44 -0
  126. package/src/reconciler.ts +36 -62
  127. package/src/render-background.ts +37 -16
  128. package/src/render-border.ts +97 -64
  129. package/src/render-frame.ts +83 -0
  130. package/src/render-to-string.ts +24 -9
  131. package/src/render.ts +24 -16
  132. package/src/router/components.tsx +343 -0
  133. package/src/router/context.ts +41 -0
  134. package/src/router/history.ts +194 -0
  135. package/src/router/hooks.tsx +391 -0
  136. package/src/router/index.ts +34 -0
  137. package/src/router/matcher.ts +571 -0
  138. package/src/sanitize-ansi.ts +1 -1
  139. package/src/screen/ansi.ts +184 -0
  140. package/src/screen/canvas.ts +137 -0
  141. package/src/screen/cell.ts +138 -0
  142. package/src/screen/color-profile.ts +47 -0
  143. package/src/screen/geometry.ts +9 -0
  144. package/src/screen/index.ts +6 -0
  145. package/src/screen/screen.ts +272 -0
  146. package/src/screen/serialize.ts +129 -0
  147. package/src/screen.ts +1 -0
  148. package/src/semantic-text-style.ts +118 -0
  149. package/src/signal-exit.ts +4 -5
  150. package/src/squash-text-nodes.ts +4 -7
  151. package/src/stream.ts +2 -4
  152. package/src/structured-text.ts +321 -0
  153. package/src/styles.ts +16 -17
  154. package/src/terminal/index.ts +2 -0
  155. package/src/terminal/inline-presenter.ts +120 -0
  156. package/src/terminal/input.ts +86 -0
  157. package/src/terminal/render-scheduler.ts +37 -0
  158. package/src/terminal/screen-presenter.ts +188 -0
  159. package/src/terminal/session.ts +407 -0
  160. package/src/terminal-size.ts +9 -8
  161. package/src/terminal.ts +1 -0
  162. package/src/testing/browser.ts +588 -0
  163. package/src/testing/emulators.ts +205 -0
  164. package/src/testing/explorer-app/index.html +12 -0
  165. package/src/testing/explorer-app/main.ts +381 -0
  166. package/src/testing/explorer-app/style.css +194 -0
  167. package/src/testing/explorer-app/tsconfig.json +15 -0
  168. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  169. package/src/testing/index.ts +26 -0
  170. package/src/testing/keys.ts +56 -0
  171. package/src/testing/live.ts +85 -0
  172. package/src/testing/matchers.ts +70 -0
  173. package/src/testing/public.ts +94 -0
  174. package/src/testing/terminal.ts +349 -0
  175. package/src/testing/vitest.ts +157 -0
  176. package/src/transform-adapter.ts +14 -0
  177. package/src/types.ts +1 -6
  178. package/src/utils.ts +2 -2
  179. package/src/wrap-text.ts +4 -4
  180. package/src/yoga/config.ts +2 -2
  181. package/src/yoga/core/absoluteLayout.ts +15 -15
  182. package/src/yoga/core/baseline.ts +3 -3
  183. package/src/yoga/core/cache.ts +4 -4
  184. package/src/yoga/core/calculateLayout.ts +33 -27
  185. package/src/yoga/core/config.ts +1 -1
  186. package/src/yoga/core/flexLine.ts +3 -3
  187. package/src/yoga/core/helpers.ts +3 -3
  188. package/src/yoga/core/layoutResults.ts +4 -4
  189. package/src/yoga/core/node.ts +16 -16
  190. package/src/yoga/core/pixelGrid.ts +4 -4
  191. package/src/yoga/core/style.ts +11 -11
  192. package/src/yoga/core/types.ts +2 -2
  193. package/src/yoga/index.ts +6 -6
  194. package/src/yoga/node.ts +8 -8
  195. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  196. package/dist/truncate-CBiyyZzw.js +0 -2156
  197. package/src/ansi/supports-color.ts +0 -213
  198. package/src/ansi/widest-line.ts +0 -12
  199. package/src/auto-bind.ts +0 -41
  200. package/src/colorize.ts +0 -60
  201. package/src/devtools-window-polyfill.ts +0 -73
  202. package/src/indent-string.ts +0 -16
  203. package/src/is-in-ci.ts +0 -7
  204. package/src/log-update.ts +0 -370
  205. package/src/output.ts +0 -308
  206. package/src/renderer.ts +0 -73
  207. package/src/write-synchronized.ts +0 -9
@@ -0,0 +1,184 @@
1
+ import { stringWidth } from "#/ansi/string-width.ts";
2
+ import {
3
+ type AnsiCode,
4
+ type StyledChar,
5
+ styledCharsFromTokens,
6
+ tokenize,
7
+ } from "#/ansi/tokenize.ts";
8
+ import {
9
+ cellAttributes,
10
+ createCell,
11
+ type Cell,
12
+ type CellStyle,
13
+ type Color,
14
+ type Hyperlink,
15
+ type UnderlineStyle,
16
+ } from "#/screen/cell.ts";
17
+
18
+ const ESC = "\u001B";
19
+ const BEL = "\u0007";
20
+ const C1_ST = "\u009C";
21
+ const LINK_PREFIX = `${ESC}]8;`;
22
+
23
+ type ParsedStyle = {
24
+ foreground?: Color;
25
+ background?: Color;
26
+ underlineColor?: Color;
27
+ underline: UnderlineStyle;
28
+ attributes: number;
29
+ hyperlink?: Hyperlink;
30
+ };
31
+
32
+ /** Converts the renderer's canonical ANSI state into backend-neutral cells. */
33
+ export function cellsFromAnsi(text: string): Cell[] {
34
+ return styledCharsFromTokens(tokenize(text)).map((character) => cellFromStyledChar(character));
35
+ }
36
+
37
+ export function cellFromStyledChar(
38
+ character: StyledChar,
39
+ width = stringWidth(character.value),
40
+ ): Cell {
41
+ const parsed = parseStyles(character.styles);
42
+ const style: CellStyle = {
43
+ ...(parsed.foreground ? { foreground: parsed.foreground } : {}),
44
+ ...(parsed.background ? { background: parsed.background } : {}),
45
+ ...(parsed.underlineColor ? { underlineColor: parsed.underlineColor } : {}),
46
+ underline: parsed.underline,
47
+ attributes: parsed.attributes,
48
+ };
49
+
50
+ return createCell(character.value, Math.max(1, width), style, parsed.hyperlink);
51
+ }
52
+
53
+ function parseStyles(codes: readonly AnsiCode[]): ParsedStyle {
54
+ let foreground: Color | undefined;
55
+ let background: Color | undefined;
56
+ let underlineColor: Color | undefined;
57
+ let underline: UnderlineStyle = "none";
58
+ let attributes = cellAttributes.none;
59
+ let hyperlink: Hyperlink | undefined;
60
+
61
+ for (const { code } of codes) {
62
+ if (code.startsWith(LINK_PREFIX)) {
63
+ hyperlink = parseHyperlink(code);
64
+ continue;
65
+ }
66
+
67
+ if (!code.startsWith(`${ESC}[`) || !code.endsWith("m")) {
68
+ continue;
69
+ }
70
+
71
+ const parameters = code.slice(2, -1);
72
+ const primary = Number.parseInt(parameters, 10);
73
+
74
+ if (primary >= 30 && primary <= 37) {
75
+ foreground = { model: "indexed", index: primary - 30, encoding: "ansi16" };
76
+ } else if (primary >= 90 && primary <= 97) {
77
+ foreground = { model: "indexed", index: primary - 90 + 8, encoding: "ansi16" };
78
+ } else if (primary >= 40 && primary <= 47) {
79
+ background = { model: "indexed", index: primary - 40, encoding: "ansi16" };
80
+ } else if (primary >= 100 && primary <= 107) {
81
+ background = { model: "indexed", index: primary - 100 + 8, encoding: "ansi16" };
82
+ } else if (primary === 38) {
83
+ foreground = parseExtendedColor(parameters);
84
+ } else if (primary === 48) {
85
+ background = parseExtendedColor(parameters);
86
+ } else if (primary === 58) {
87
+ underlineColor = parseExtendedColor(parameters);
88
+ } else if (primary === 1) {
89
+ attributes += cellAttributes.bold;
90
+ } else if (primary === 2) {
91
+ attributes += cellAttributes.faint;
92
+ } else if (primary === 3) {
93
+ attributes += cellAttributes.italic;
94
+ } else if (primary === 5) {
95
+ attributes += cellAttributes.blink;
96
+ } else if (primary === 6) {
97
+ attributes += cellAttributes.rapidBlink;
98
+ } else if (primary === 7) {
99
+ attributes += cellAttributes.inverse;
100
+ } else if (primary === 8) {
101
+ attributes += cellAttributes.hidden;
102
+ } else if (primary === 9) {
103
+ attributes += cellAttributes.strikethrough;
104
+ } else if (primary === 4) {
105
+ underline = parseUnderline(parameters);
106
+ }
107
+ }
108
+
109
+ return {
110
+ ...(foreground ? { foreground } : {}),
111
+ ...(background ? { background } : {}),
112
+ ...(underlineColor ? { underlineColor } : {}),
113
+ underline,
114
+ attributes,
115
+ ...(hyperlink ? { hyperlink } : {}),
116
+ };
117
+ }
118
+
119
+ function parseExtendedColor(parameters: string): Color | undefined {
120
+ const parts = parameters.split(parameters.includes(":") ? ":" : ";");
121
+ const model = parts[1];
122
+
123
+ if (model === "5") {
124
+ const index = Number(parts[2]);
125
+ return Number.isInteger(index) && index >= 0 && index <= 255
126
+ ? { model: "indexed", index, encoding: "ansi256" }
127
+ : undefined;
128
+ }
129
+
130
+ if (model !== "2") {
131
+ return undefined;
132
+ }
133
+
134
+ const channels = parts.slice(-3).map(Number);
135
+ if (channels.length !== 3 || channels.some((channel) => !isByte(channel))) {
136
+ return undefined;
137
+ }
138
+
139
+ return {
140
+ model: "rgb",
141
+ red: channels[0]!,
142
+ green: channels[1]!,
143
+ blue: channels[2]!,
144
+ alpha: 255,
145
+ };
146
+ }
147
+
148
+ function parseUnderline(parameters: string): UnderlineStyle {
149
+ const variant = parameters.includes(":") ? Number(parameters.split(":").at(-1)) : 1;
150
+ return (
151
+ (
152
+ {
153
+ 0: "none",
154
+ 1: "single",
155
+ 2: "double",
156
+ 3: "curly",
157
+ 4: "dotted",
158
+ 5: "dashed",
159
+ } as const
160
+ )[variant] ?? "single"
161
+ );
162
+ }
163
+
164
+ function parseHyperlink(code: string): Hyperlink | undefined {
165
+ let body = code.slice(LINK_PREFIX.length);
166
+ if (body.endsWith(`${ESC}\\`)) {
167
+ body = body.slice(0, -2);
168
+ } else if (body.endsWith(BEL) || body.endsWith(C1_ST)) {
169
+ body = body.slice(0, -1);
170
+ }
171
+
172
+ const separator = body.indexOf(";");
173
+ if (separator < 0) {
174
+ return undefined;
175
+ }
176
+
177
+ const parameters = body.slice(0, separator);
178
+ const url = body.slice(separator + 1);
179
+ return parameters ? { url, parameters } : { url };
180
+ }
181
+
182
+ function isByte(value: number): boolean {
183
+ return Number.isInteger(value) && value >= 0 && value <= 255;
184
+ }
@@ -0,0 +1,137 @@
1
+ import { sliceAnsi } from "#/ansi/slice.ts";
2
+ import { stringWidth } from "#/ansi/string-width.ts";
3
+ import type { StyledChar } from "#/ansi/tokenize.ts";
4
+ import type { PaintContext } from "#/color/paint.ts";
5
+ import { cellFromStyledChar, Screen, type Cell, type ColorProfile } from "#/screen/index.ts";
6
+ import type { AnsiTransformer } from "#/transform-adapter.ts";
7
+ import { transformAnsiLine } from "#/transform-adapter.ts";
8
+
9
+ type Options = {
10
+ width: number;
11
+ height: number;
12
+ colorProfile?: ColorProfile;
13
+ paintContext?: PaintContext;
14
+ };
15
+ type Clip = {
16
+ x1: number | undefined;
17
+ x2: number | undefined;
18
+ y1: number | undefined;
19
+ y2: number | undefined;
20
+ };
21
+
22
+ /** Immediate structured drawing canvas with one explicit ANSI compatibility adapter. */
23
+ export class Canvas {
24
+ width: number;
25
+ height: number;
26
+ readonly paintContext: PaintContext;
27
+ readonly #screen: Screen;
28
+ readonly #clips: Clip[] = [];
29
+
30
+ constructor(options: Options) {
31
+ this.width = options.width;
32
+ this.height = options.height;
33
+ this.paintContext = { profile: options.colorProfile ?? "truecolor", ...options.paintContext };
34
+ this.#screen = new Screen(this.width, this.height);
35
+ }
36
+
37
+ writeCells(x: number, y: number, lines: readonly (readonly Cell[])[]): void {
38
+ const clip = this.#clips.at(-1);
39
+ for (const [rowOffset, line] of lines.entries()) {
40
+ const currentY = y + rowOffset;
41
+ if (!this.#insideY(currentY, clip)) continue;
42
+ let currentX = x;
43
+ for (const cell of line) {
44
+ const endX = currentX + cell.width;
45
+ const clipped =
46
+ (clip?.x1 !== undefined && currentX < clip.x1) ||
47
+ (clip?.x2 !== undefined && endX > clip.x2);
48
+ if (!clipped) this.#composeNativeCell(currentX, currentY, cell);
49
+ currentX = endX;
50
+ }
51
+ }
52
+ }
53
+
54
+ writeAnsi(
55
+ x: number,
56
+ y: number,
57
+ text: string,
58
+ options: { transformers: AnsiTransformer[] },
59
+ ): void {
60
+ if (!text) return;
61
+ let lines = text.split("\n");
62
+ const clip = this.#clips.at(-1);
63
+ if (clip?.y1 !== undefined && y < clip.y1) {
64
+ lines = lines.slice(clip.y1 - y);
65
+ y = clip.y1;
66
+ }
67
+ if (clip?.y2 !== undefined) lines = lines.slice(0, Math.max(0, clip.y2 - y));
68
+ for (const [lineIndex, original] of lines.entries()) {
69
+ const currentY = y + lineIndex;
70
+ if (!this.#insideY(currentY, clip)) continue;
71
+ let line = original;
72
+ let currentX = x;
73
+ if (clip?.x1 !== undefined && currentX < clip.x1) {
74
+ line = sliceAnsi(line, clip.x1 - currentX);
75
+ currentX = clip.x1;
76
+ }
77
+ if (clip?.x2 !== undefined) line = sliceAnsi(line, 0, Math.max(0, clip.x2 - currentX));
78
+ for (const character of transformAnsiLine(line, lineIndex, options.transformers)) {
79
+ const width = Math.max(1, stringWidth(character.value));
80
+ if (clip?.x2 !== undefined && currentX + width > clip.x2) break;
81
+ this.#writeCompatibilityCell(currentX, currentY, character, width);
82
+ currentX += width;
83
+ }
84
+ }
85
+ }
86
+
87
+ clip(clip: Clip): void {
88
+ this.#clips.push(clip);
89
+ }
90
+ unclip(): void {
91
+ this.#clips.pop();
92
+ }
93
+
94
+ finish(): Screen {
95
+ return this.#screen;
96
+ }
97
+
98
+ #insideY(y: number, clip: Clip | undefined): boolean {
99
+ return (
100
+ y >= 0 &&
101
+ y < this.#screen.height &&
102
+ (clip?.y1 === undefined || y >= clip.y1) &&
103
+ (clip?.y2 === undefined || y < clip.y2)
104
+ );
105
+ }
106
+
107
+ #composeNativeCell(x: number, y: number, cell: Cell): void {
108
+ if (x >= this.#screen.width) {
109
+ return;
110
+ }
111
+ this.#screen.composeCell(x, y, {
112
+ content: { grapheme: cell.grapheme, width: cell.width },
113
+ foreground: cell.reset?.foreground ? null : (cell.style.foreground ?? null),
114
+ background: cell.reset?.background ? null : cell.style.background,
115
+ underlineColor: cell.style.underlineColor ?? null,
116
+ underline: cell.style.underline,
117
+ attributes: cell.style.attributes,
118
+ hyperlink: cell.hyperlink ?? null,
119
+ });
120
+ }
121
+
122
+ #writeCompatibilityCell(x: number, y: number, character: StyledChar, width: number): void {
123
+ if (x >= this.#screen.width) {
124
+ return;
125
+ }
126
+ const cell = cellFromStyledChar(character, width);
127
+ this.#screen.composeCell(x, y, {
128
+ content: { grapheme: cell.grapheme, width: cell.width },
129
+ foreground: cell.style.foreground ?? null,
130
+ background: cell.style.background,
131
+ underlineColor: cell.style.underlineColor ?? null,
132
+ underline: cell.style.underline,
133
+ attributes: cell.style.attributes,
134
+ hyperlink: cell.hyperlink ?? null,
135
+ });
136
+ }
137
+ }
@@ -0,0 +1,138 @@
1
+ /**
2
+ A terminal color before it is converted to an output profile.
3
+ */
4
+ export type Color = RgbColor | IndexedColor;
5
+
6
+ export type RgbColor = {
7
+ readonly model: "rgb";
8
+ readonly red: number;
9
+ readonly green: number;
10
+ readonly blue: number;
11
+ readonly alpha: number;
12
+ };
13
+
14
+ export type IndexedColor = {
15
+ readonly model: "indexed";
16
+ readonly index: number;
17
+ /** Preserve an explicit palette encoding when compatibility requires it. */
18
+ readonly encoding?: "ansi16" | "ansi256";
19
+ };
20
+
21
+ /**
22
+ Bit flags for terminal text attributes. Multiple attributes may be combined.
23
+ */
24
+ export const cellAttributes = {
25
+ none: 0,
26
+ bold: 1 << 0,
27
+ faint: 1 << 1,
28
+ italic: 1 << 2,
29
+ blink: 1 << 3,
30
+ rapidBlink: 1 << 4,
31
+ inverse: 1 << 5,
32
+ hidden: 1 << 6,
33
+ strikethrough: 1 << 7,
34
+ } as const;
35
+
36
+ export type UnderlineStyle = "none" | "single" | "double" | "curly" | "dotted" | "dashed";
37
+
38
+ export type CellStyle = {
39
+ readonly foreground?: Color;
40
+ readonly background?: Color;
41
+ readonly underlineColor?: Color;
42
+ readonly underline: UnderlineStyle;
43
+ readonly attributes: number;
44
+ };
45
+
46
+ export type Hyperlink = {
47
+ readonly url: string;
48
+ readonly parameters?: string;
49
+ };
50
+
51
+ export type CellContent = {
52
+ readonly grapheme: string;
53
+ readonly width: number;
54
+ };
55
+
56
+ /**
57
+ A compositing operation for one screen position.
58
+
59
+ An absent field is transparent and preserves the destination channel. `null`
60
+ explicitly resets a color or hyperlink to the terminal default. Supplying
61
+ `content` paints a grapheme; `{grapheme: " ", width: 1}` is therefore an
62
+ explicit blank, while an absent patch paints nothing at all.
63
+ */
64
+ export type CellPatch = {
65
+ readonly content?: CellContent;
66
+ readonly foreground?: Color | null;
67
+ readonly background?: Color | null;
68
+ readonly underlineColor?: Color | null;
69
+ readonly underline?: UnderlineStyle;
70
+ readonly attributes?: number;
71
+ readonly hyperlink?: Hyperlink | null;
72
+ };
73
+
74
+ /**
75
+ One grapheme cluster in a terminal screen. A width of zero is reserved for the
76
+ continuation columns of a wide grapheme and is created only by `Screen`.
77
+ */
78
+ export type Cell = {
79
+ readonly grapheme: string;
80
+ readonly width: number;
81
+ readonly style: CellStyle;
82
+ readonly hyperlink?: Hyperlink;
83
+ /** Composition intent consumed when this cell is drawn over another surface. */
84
+ readonly reset?: { readonly foreground?: boolean; readonly background?: boolean };
85
+ };
86
+
87
+ export const emptyCellStyle: CellStyle = {
88
+ underline: "none",
89
+ attributes: cellAttributes.none,
90
+ };
91
+
92
+ export const emptyCell: Cell = {
93
+ grapheme: " ",
94
+ width: 1,
95
+ style: emptyCellStyle,
96
+ };
97
+
98
+ export const createCell = (
99
+ grapheme: string,
100
+ width: number,
101
+ style: CellStyle = emptyCellStyle,
102
+ hyperlink?: Hyperlink,
103
+ reset?: Cell["reset"],
104
+ ): Cell => {
105
+ if (grapheme.length === 0) {
106
+ throw new Error("A cell must contain one grapheme cluster");
107
+ }
108
+
109
+ if (!Number.isInteger(width) || width < 1) {
110
+ throw new Error("A cell width must be a positive integer");
111
+ }
112
+
113
+ return {
114
+ grapheme,
115
+ width,
116
+ style,
117
+ ...(hyperlink ? { hyperlink } : {}),
118
+ ...(reset ? { reset } : {}),
119
+ };
120
+ };
121
+
122
+ export const cellsEqual = (left: Cell | undefined, right: Cell | undefined): boolean => {
123
+ if (left === right) return true;
124
+ if (!left || !right || left.grapheme !== right.grapheme || left.width !== right.width) {
125
+ return false;
126
+ }
127
+
128
+ const leftStyle = left.style;
129
+ const rightStyle = right.style;
130
+ return (
131
+ leftStyle.attributes === rightStyle.attributes &&
132
+ leftStyle.underline === rightStyle.underline &&
133
+ JSON.stringify(leftStyle.foreground) === JSON.stringify(rightStyle.foreground) &&
134
+ JSON.stringify(leftStyle.background) === JSON.stringify(rightStyle.background) &&
135
+ JSON.stringify(leftStyle.underlineColor) === JSON.stringify(rightStyle.underlineColor) &&
136
+ JSON.stringify(left.hyperlink) === JSON.stringify(right.hyperlink)
137
+ );
138
+ };
@@ -0,0 +1,47 @@
1
+ import { ansi256ToAnsi, rgbToAnsi, rgbToAnsi256 } from "#/ansi/sgr.ts";
2
+ import type { ColorSupportLevel } from "#/capabilities/detect.ts";
3
+ import type { Color } from "#/screen/cell.ts";
4
+
5
+ /** The color model emitted by a terminal serializer. */
6
+ export type ColorProfile = "none" | "ansi16" | "ansi256" | "truecolor";
7
+
8
+ export function colorProfileFromLevel(level: ColorSupportLevel): ColorProfile {
9
+ return (["none", "ansi16", "ansi256", "truecolor"] as const)[level];
10
+ }
11
+
12
+ /**
13
+ Quantizes a semantic color for an output profile. `undefined` means the
14
+ terminal default and is also the result for the no-color profile.
15
+ */
16
+ export function quantizeColor(color: Color | undefined, profile: ColorProfile): Color | undefined {
17
+ if (!color || profile === "none") {
18
+ return undefined;
19
+ }
20
+
21
+ if (profile === "truecolor") {
22
+ return color;
23
+ }
24
+
25
+ if (profile === "ansi256") {
26
+ if (color.model === "indexed") {
27
+ return color;
28
+ }
29
+
30
+ return {
31
+ model: "indexed",
32
+ index: rgbToAnsi256(color.red, color.green, color.blue),
33
+ encoding: "ansi256",
34
+ };
35
+ }
36
+
37
+ const ansiCode =
38
+ color.model === "indexed"
39
+ ? ansi256ToAnsi(color.index)
40
+ : rgbToAnsi(color.red, color.green, color.blue);
41
+
42
+ return { model: "indexed", index: ansiCodeToPaletteIndex(ansiCode), encoding: "ansi16" };
43
+ }
44
+
45
+ function ansiCodeToPaletteIndex(code: number): number {
46
+ return code >= 90 ? code - 90 + 8 : code - 30;
47
+ }
@@ -0,0 +1,9 @@
1
+ export type Point = {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ };
5
+
6
+ export type Rect = Point & {
7
+ readonly width: number;
8
+ readonly height: number;
9
+ };
@@ -0,0 +1,6 @@
1
+ export * from "#/screen/ansi.ts";
2
+ export * from "#/screen/cell.ts";
3
+ export * from "#/screen/color-profile.ts";
4
+ export * from "#/screen/geometry.ts";
5
+ export * from "#/screen/serialize.ts";
6
+ export { Screen, type Line } from "#/screen/screen.ts";