@stainless-api/playgrounds 0.0.1-beta.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +23 -0
  3. package/eslint.config.js +2 -0
  4. package/package.json +69 -0
  5. package/src/Logs.tsx +216 -0
  6. package/src/Panel.tsx +21 -0
  7. package/src/PlaygroundPanelWrapper.tsx +5 -0
  8. package/src/build-py-types.ts +152 -0
  9. package/src/build-ts-types.ts +70 -0
  10. package/src/build.ts +97 -0
  11. package/src/codemirror/comlink.ts +698 -0
  12. package/src/codemirror/curl/curlconverter.vendor.js +7959 -0
  13. package/src/codemirror/curl.ts +108 -0
  14. package/src/codemirror/deps.ts +12 -0
  15. package/src/codemirror/fix-lsp-markdown.ts +50 -0
  16. package/src/codemirror/lsp.ts +87 -0
  17. package/src/codemirror/python/anser.ts +398 -0
  18. package/src/codemirror/python/pyodide.ts +180 -0
  19. package/src/codemirror/python.ts +160 -0
  20. package/src/codemirror/react.tsx +615 -0
  21. package/src/codemirror/sanitize-html.ts +12 -0
  22. package/src/codemirror/shiki.ts +65 -0
  23. package/src/codemirror/typescript/cdn-typescript.d.ts +1 -0
  24. package/src/codemirror/typescript/cdn-typescript.js +1 -0
  25. package/src/codemirror/typescript/console.ts +590 -0
  26. package/src/codemirror/typescript/get-signature.ts +94 -0
  27. package/src/codemirror/typescript/prettier-plugin-external-typescript.vendor.js +4968 -0
  28. package/src/codemirror/typescript/runner.ts +396 -0
  29. package/src/codemirror/typescript/special-info.ts +171 -0
  30. package/src/codemirror/typescript/worker.ts +292 -0
  31. package/src/codemirror/typescript.tsx +198 -0
  32. package/src/create.tsx +44 -0
  33. package/src/icon.tsx +21 -0
  34. package/src/index.ts +6 -0
  35. package/src/logs-context.ts +5 -0
  36. package/src/playground.css +359 -0
  37. package/src/sandbox-worker/in-frame.js +179 -0
  38. package/src/sandbox-worker/index.ts +202 -0
  39. package/src/use-storage.ts +54 -0
  40. package/src/util.ts +29 -0
  41. package/src/virtual-module.d.ts +45 -0
  42. package/src/vite-env.d.ts +1 -0
  43. package/test/get-signature.test.ts +73 -0
  44. package/test/use-storage.test.ts +60 -0
  45. package/tsconfig.json +11 -0
@@ -0,0 +1,108 @@
1
+ import runnerWorkerURL from './typescript/runner?worker&url';
2
+ import * as Comlink from './comlink';
3
+ import type { Language } from './react';
4
+ import { proxy, type Remote } from './comlink';
5
+ import type { Logger } from '../Logs';
6
+ import type { RunnerAPI } from './typescript/runner';
7
+ import { shell } from '@codemirror/legacy-modes/mode/shell';
8
+ import { StreamLanguage } from '@codemirror/language';
9
+
10
+ export async function createCurl(signal: AbortSignal, doc: string): Promise<Language> {
11
+ const { SandboxWorker } = await import('../sandbox-worker/index.js');
12
+ const { toJavaScript } = await import('./curl/curlconverter.vendor.js');
13
+ let runnerWorker: Worker;
14
+ let runnerAPI: Remote<RunnerAPI>;
15
+ const startRunner = () => {
16
+ if (runnerWorker) {
17
+ runnerWorker.terminate();
18
+ runnerAPI[Comlink.releaseProxy]();
19
+ }
20
+ runnerWorker = new SandboxWorker(runnerWorkerURL);
21
+ runnerAPI = Comlink.wrap<RunnerAPI>(runnerWorker);
22
+ runnerAPI.setLogger(
23
+ proxy((...args) => {
24
+ logger(...args);
25
+ }),
26
+ );
27
+ };
28
+ startRunner();
29
+
30
+ let logger: Logger;
31
+ return {
32
+ transport: undefined,
33
+ fileName: 'playground.sh',
34
+ extensions: [StreamLanguage.define(shell)],
35
+ doc,
36
+ setLogger(fn) {
37
+ logger = (log) => {
38
+ if (log.type === 'clear') {
39
+ fn(log);
40
+ } else {
41
+ fn({
42
+ ...log,
43
+ type: log.type === 'log' ? 'stdout' : log.type,
44
+ });
45
+ }
46
+ };
47
+ },
48
+ async expandLogHandle(handle) {
49
+ return await runnerAPI.expandLogHandle(handle);
50
+ },
51
+ async freeLogHandle(handle) {
52
+ return await runnerAPI.freeLogHandle(handle);
53
+ },
54
+ async setEnv(vars) {
55
+ return await runnerAPI.setEnv(vars);
56
+ },
57
+ async run(code, signal) {
58
+ signal.addEventListener('abort', startRunner);
59
+ try {
60
+ const js = `
61
+ async function responseToCurl(res) {
62
+ const blob = await res.blob();
63
+ const bytes = new Uint8Array(await blob.arrayBuffer())
64
+ let text;
65
+ try {
66
+ text = new TextDecoder('utf-8', { fatal: true }).decode(bytes)
67
+ } catch {
68
+ text = '[output was binary: ' + URL.createObjectURL(blob) + ']'
69
+ }
70
+ const after = ['color:var(--stl-color-foreground-reduced)', '', res.status, res.statusText]
71
+ const headers = [\`%c< %cHTTP %s %s\`,
72
+ ...[...res.headers.entries()].map((e) => {
73
+ after.push('color:var(--stl-color-foreground-reduced)')
74
+ after.push('')
75
+ after.push(e[0])
76
+ after.push('color:var(--stl-color-foreground-reduced)')
77
+ after.push('color:var(--stl-color-green-foreground)')
78
+ after.push(e[1])
79
+ after.push('')
80
+ return "\\n%c< %c%s%c: %c%s%c"
81
+ })].join("")
82
+ try {
83
+ return [headers + '\\n%c< %c\\n\\n%o', ...after, 'color:var(--stl-color-foreground-reduced)', '', JSON.parse(text)]
84
+ } catch {
85
+ return [headers + '\\n%c< %c\\n\\n%s', ...after, 'color:var(--stl-color-foreground-reduced)', '', text]
86
+ }
87
+ };
88
+ await (async fetch => {${toJavaScript(code)}})(async (...args) => {
89
+ const req = new Request(...args)
90
+ console.log('%c>%c %s %c%s', 'color:var(--stl-color-foreground-reduced)', '', req.method, 'color:var(--stl-color-green-foreground)', req.url)
91
+ console.log(...await responseToCurl(await fetch(req)))
92
+ })`;
93
+ await runnerAPI.run(js, true);
94
+ } catch (e) {
95
+ if (signal.aborted) {
96
+ logger({ type: 'error', parts: ['Aborted.'] });
97
+ return;
98
+ }
99
+ throw e;
100
+ } finally {
101
+ signal.removeEventListener('abort', startRunner);
102
+ }
103
+ },
104
+ async format() {
105
+ throw new Error('todo');
106
+ },
107
+ };
108
+ }
@@ -0,0 +1,12 @@
1
+ import './fix-lsp-markdown';
2
+ export * as view from '@codemirror/view';
3
+ export * as state from '@codemirror/state';
4
+ export * as language from '@codemirror/language';
5
+ export * as search from '@codemirror/search';
6
+ export * as autocomplete from '@codemirror/autocomplete';
7
+ export * as lint from '@codemirror/lint';
8
+ export * as commands from '@codemirror/commands';
9
+ export * as lsp from '@codemirror/lsp-client';
10
+ export { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
11
+ export { createLSPClient } from './lsp';
12
+ export { shikiHighlighter } from './shiki';
@@ -0,0 +1,50 @@
1
+ import { LSPPlugin } from '@codemirror/lsp-client';
2
+ import { Marked } from 'marked';
3
+ import { sanitizeHTML } from './sanitize-html';
4
+ import { highlighter } from './shiki';
5
+ // @ts-expect-error TODO: make independent from stl-starlight
6
+ import { HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
7
+ type MarkupKind = 'plaintext' | 'markdown';
8
+ interface MarkupContent {
9
+ /**
10
+ * The type of the Markup
11
+ */
12
+ kind: MarkupKind;
13
+ /**
14
+ * The content itself
15
+ */
16
+ value: string;
17
+ }
18
+ function escHTML(text: string) {
19
+ return text.replace(/[\n<&]/g, (ch) => (ch == '\n' ? '<br>' : ch == '<' ? '&lt;' : '&amp;'));
20
+ }
21
+
22
+ const marked = new Marked({
23
+ walkTokens(token) {
24
+ if (token.type != 'code') return;
25
+ token.escaped = true;
26
+ token.text = highlighter.codeToHtml(token.text, {
27
+ lang: token.lang,
28
+ themes: HIGHLIGHT_THEMES ?? {
29
+ light: 'github-light',
30
+ dark: 'github-dark',
31
+ },
32
+ structure: 'inline',
33
+ });
34
+ },
35
+ });
36
+
37
+ export function docToHTML(value: string | MarkupContent, defaultKind: MarkupKind = 'plaintext') {
38
+ let kind = defaultKind,
39
+ text = value;
40
+ if (typeof text != 'string') {
41
+ kind = text.kind;
42
+ text = text.value;
43
+ }
44
+ if (kind == 'plaintext') {
45
+ return escHTML(text);
46
+ } else {
47
+ return sanitizeHTML(marked.parse(text, { async: false }));
48
+ }
49
+ }
50
+ LSPPlugin.prototype.docToHTML = docToHTML;
@@ -0,0 +1,87 @@
1
+ import type * as lsp from 'vscode-languageserver-protocol';
2
+ import type { Transport } from '@codemirror/lsp-client';
3
+ import { sanitizeHTML } from './sanitize-html';
4
+
5
+ export interface WorkerTransportHooks {
6
+ onMessage?: (message: unknown) => void;
7
+ onSend?: (message: unknown) => unknown;
8
+ }
9
+
10
+ export interface WorkerTransportConfig {
11
+ worker: Worker;
12
+ hooks?: WorkerTransportHooks;
13
+ }
14
+
15
+ export function workerTransport({ worker, hooks = {} }: WorkerTransportConfig): Transport {
16
+ let handlers: ((value: string) => void)[] = [];
17
+
18
+ worker.onmessage = (e) => {
19
+ if (e.data.result?.items?.[0]?.sortText) {
20
+ e.data.result.items.forEach((e: { detail: string; sortText: string }) => {
21
+ const prevDetail = e.detail;
22
+ e.detail = '\0' + JSON.stringify({ detail: prevDetail, sortText: e.sortText });
23
+ });
24
+ }
25
+ hooks.onMessage?.(e.data);
26
+ const s = JSON.stringify(e.data);
27
+ for (const h of handlers) h(s);
28
+ };
29
+
30
+ return {
31
+ send(message: string) {
32
+ let j = JSON.parse(message);
33
+ if (hooks.onSend) {
34
+ const sent = hooks.onSend(j);
35
+ if (sent) {
36
+ j = sent;
37
+ } else {
38
+ return;
39
+ }
40
+ }
41
+ worker.postMessage(j);
42
+ },
43
+ subscribe(handler: (value: string) => void) {
44
+ handlers.push(handler);
45
+ },
46
+ unsubscribe(handler: (value: string) => void) {
47
+ handlers = handlers.filter((h) => h != handler);
48
+ if (handlers.length === 0) {
49
+ worker.terminate();
50
+ }
51
+ },
52
+ };
53
+ }
54
+
55
+ export interface LSPClientConfig {
56
+ rootUri: string;
57
+ onDiagnostics?: (diagnostics: lsp.Diagnostic[]) => void;
58
+ onNotification?: (method: string, params: unknown) => void;
59
+ }
60
+
61
+ export async function createLSPClient(
62
+ LSPClient: typeof import('@codemirror/lsp-client').LSPClient,
63
+ config: LSPClientConfig,
64
+ transport: Transport,
65
+ ) {
66
+ const { rootUri, onDiagnostics, onNotification } = config;
67
+
68
+ const client = new LSPClient({
69
+ rootUri,
70
+ unhandledNotification(_client: unknown, method: string, params: unknown) {
71
+ if (method === 'textDocument/publishDiagnostics' && onDiagnostics) {
72
+ const diagnosticsParams = params as lsp.PublishDiagnosticsParams;
73
+ onDiagnostics(diagnosticsParams.diagnostics);
74
+ } else if (onNotification) {
75
+ onNotification(method, params);
76
+ }
77
+ },
78
+ sanitizeHTML,
79
+ });
80
+ // @ts-expect-error accessing private field
81
+ client.timeout = 1000 * 60;
82
+ client.connect(transport);
83
+ await client.initializing;
84
+ // @ts-expect-error accessing private field
85
+ client.timeout = 3000;
86
+ return client;
87
+ }
@@ -0,0 +1,398 @@
1
+ // This file was originally written by @drudru (https://github.com/drudru/ansi_up), MIT, 2011
2
+
3
+ // Type definitions for Anser
4
+ // Project: https://github.com/IonicaBizau/anser
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ declare namespace Anser {
8
+ type DecorationName =
9
+ | 'bold'
10
+ | 'dim'
11
+ | 'italic'
12
+ | 'underline'
13
+ | 'blink'
14
+ | 'reverse'
15
+ | 'hidden'
16
+ | 'strikethrough';
17
+
18
+ export interface AnserJsonEntry {
19
+ /** The text. */
20
+ content: string;
21
+ /** The foreground color. */
22
+ fg: string;
23
+ /** The background color. */
24
+ bg: string;
25
+ /** `true` if a carriageReturn \r was fount at end of line. */
26
+ clearLine: boolean;
27
+ /** The decoration last declared before the text. */
28
+ decoration: null | DecorationName;
29
+ /** All decorations that apply to the text. */
30
+ decorations: Array<DecorationName>;
31
+ /** `true` if the colors were processed, `false` otherwise. */
32
+ was_processed: boolean;
33
+ }
34
+
35
+ export interface AnserOptions {
36
+ clearLine?: boolean;
37
+ }
38
+ }
39
+
40
+ const ANSI_COLORS = [
41
+ [
42
+ {
43
+ color: 'light-dark(var(--stl-color-foreground), var(--stl-color-foreground-muted))',
44
+ class: 'ansi-black',
45
+ },
46
+ { color: 'var(--stl-color-red)', class: 'ansi-red' },
47
+ { color: 'var(--stl-color-green)', class: 'ansi-green' },
48
+ { color: 'var(--stl-color-yellow)', class: 'ansi-yellow' },
49
+ { color: 'var(--stl-color-blue)', class: 'ansi-blue' },
50
+ { color: 'var(--stl-color-purple)', class: 'ansi-magenta' },
51
+ { color: 'var(--stl-color-cyan)', class: 'ansi-cyan' },
52
+ { color: 'var(--stl-color-foreground-reduced)', class: 'ansi-white' },
53
+ ],
54
+ [
55
+ { color: 'var(--stl-color-foreground-reduced)', class: 'ansi-bright-black' },
56
+ { color: 'var(--stl-color-red)', class: 'ansi-bright-red' },
57
+ { color: 'var(--stl-color-green)', class: 'ansi-bright-green' },
58
+ {
59
+ color: 'var(--stl-color-yellow)',
60
+ class: 'ansi-bright-yellow',
61
+ },
62
+ { color: 'var(--stl-color-blue)', class: 'ansi-bright-blue' },
63
+ {
64
+ color: 'var(--stl-color-purple)',
65
+ class: 'ansi-bright-magenta',
66
+ },
67
+ { color: 'var(--stl-color-cyan)', class: 'ansi-bright-cyan' },
68
+ {
69
+ color: 'light-dark(var(--stl-color-foreground-muted), var(--stl-color-foreground))',
70
+ class: 'ansi-bright-white',
71
+ },
72
+ ],
73
+ ];
74
+
75
+ class Anser {
76
+ fg: string | null;
77
+ bg: string | null;
78
+ decorations: Anser.DecorationName[];
79
+ PALETTE_COLORS?: string[];
80
+
81
+ /**
82
+ * Anser
83
+ * The `Anser` class.
84
+ *
85
+ * @name Anser
86
+ * @function
87
+ */
88
+ constructor() {
89
+ this.fg = this.bg = null;
90
+ this.decorations = [];
91
+ }
92
+
93
+ /**
94
+ * setupPalette
95
+ * Sets up the palette.
96
+ *
97
+ * @name setupPalette
98
+ * @function
99
+ */
100
+ setupPalette() {
101
+ this.PALETTE_COLORS = [];
102
+
103
+ // Index 0..15 : System color
104
+ for (let i = 0; i < 2; ++i) {
105
+ for (let j = 0; j < 8; ++j) {
106
+ this.PALETTE_COLORS.push(ANSI_COLORS[i]![j]!.color);
107
+ }
108
+ }
109
+
110
+ // Index 16..231 : RGB 6x6x6
111
+ // https://gist.github.com/jasonm23/2868981#file-xterm-256color-yaml
112
+ const levels = [0, 95, 135, 175, 215, 255];
113
+ const format = (r: number, g: number, b: number) =>
114
+ 'rgb(' + levels[r] + ', ' + levels[g] + ', ' + levels[b] + ')';
115
+ for (let r = 0; r < 6; ++r) {
116
+ for (let g = 0; g < 6; ++g) {
117
+ for (let b = 0; b < 6; ++b) {
118
+ this.PALETTE_COLORS.push(format(r, g, b));
119
+ }
120
+ }
121
+ }
122
+
123
+ // Index 232..255 : Grayscale
124
+ let level = 8;
125
+ for (let i = 0; i < 24; ++i, level += 10) {
126
+ this.PALETTE_COLORS.push(format(level, level, level));
127
+ }
128
+ }
129
+
130
+ /**
131
+ * ansiToJson
132
+ * Converts ANSI input into HTML output.
133
+ *
134
+ * @name ansiToJson
135
+ * @function
136
+ * @param {String} txt The input text.
137
+ * @param {Object} options The options passed ot the `process` method.
138
+ * @returns {String} The JSON output.
139
+ */
140
+ ansiToJson(txt: string): Anser.AnserJsonEntry[] {
141
+ return this.process(txt, { clearLine: false }, true);
142
+ }
143
+
144
+ /**
145
+ * process
146
+ * Processes the input.
147
+ *
148
+ * @name process
149
+ * @function
150
+ * @param {String} txt The input text.
151
+ * @param {Object} options An object passed to `processChunk` method, extended with:
152
+ * @param {Boolean} markup
153
+ */
154
+ process(txt: string, options: Anser.AnserOptions, markup?: boolean): Anser.AnserJsonEntry[] {
155
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
156
+ const self = this;
157
+ // eslint-disable-next-line no-control-regex
158
+ const raw_text_chunks = txt.split(/\x1B\[/);
159
+ const first_chunk = raw_text_chunks.shift()!; // the first chunk is not the result of the split
160
+
161
+ if (options === undefined || options === null) {
162
+ options = {};
163
+ }
164
+ options.clearLine = /\r/.test(txt); // check for Carriage Return
165
+ const color_chunks = raw_text_chunks.map((chunk) => this.processChunk(chunk, options, markup));
166
+
167
+ const first = self.processChunkJson('');
168
+ first.content = first_chunk;
169
+ first.clearLine = options.clearLine;
170
+ color_chunks.unshift(first);
171
+ return color_chunks;
172
+ }
173
+
174
+ /**
175
+ * processChunkJson
176
+ * Processes the current chunk into json output.
177
+ *
178
+ * @name processChunkJson
179
+ * @function
180
+ * @param {String} text The input text.
181
+ * @param {Object} options An object containing the following fields:
182
+ * @param {Boolean} markup If false, the colors will not be parsed.
183
+ * @return {Object} The result object:
184
+ *
185
+ * - `content` (String): The text.
186
+ * - `fg` (String|null): The foreground color.
187
+ * - `bg` (String|null): The background color.
188
+ * - `clearLine` (Boolean): `true` if a carriageReturn \r was fount at end of line.
189
+ * - `was_processed` (Boolean): `true` if the colors were processed, `false` otherwise.
190
+ */
191
+ processChunkJson(text: string, options?: Anser.AnserOptions, markup?: boolean): Anser.AnserJsonEntry {
192
+ // Are we using classes or styles?
193
+ options = typeof options == 'undefined' ? {} : options;
194
+
195
+ const result: Anser.AnserJsonEntry = {
196
+ content: text,
197
+ fg: '',
198
+ bg: '',
199
+ clearLine: options.clearLine ?? false,
200
+ decoration: null,
201
+ decorations: [],
202
+ was_processed: false,
203
+ };
204
+
205
+ // Each "chunk" is the text after the CSI (ESC + "[") and before the next CSI/EOF.
206
+ //
207
+ // This regex matches four groups within a chunk.
208
+ //
209
+ // The first and third groups match code type.
210
+ // We supported only SGR command. It has empty first group and "m" in third.
211
+ //
212
+ // The second group matches all of the number+semicolon command sequences
213
+ // before the "m" (or other trailing) character.
214
+ // These are the graphics or SGR commands.
215
+ //
216
+ // The last group is the text (including newlines) that is colored by
217
+ // the other group"s commands.
218
+ const matches = text.match(/^([!\x3c-\x3f]*)([\d;]*)([\x20-\x2c]*[\x40-\x7e])([\s\S]*)/m);
219
+
220
+ if (!matches) return result;
221
+
222
+ result.content = matches[4]!;
223
+ const numbers = matches[2]!.split(';');
224
+
225
+ // We currently support only "SGR" (Select Graphic Rendition)
226
+ // Simply ignore if not a SGR command.
227
+ if (matches[1] !== '' || matches[3] !== 'm') {
228
+ return result;
229
+ }
230
+
231
+ if (!markup) {
232
+ return result;
233
+ }
234
+
235
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
236
+ const self = this;
237
+
238
+ while (numbers.length > 0) {
239
+ const num_str = numbers.shift()!;
240
+ const num = parseInt(num_str);
241
+
242
+ if (isNaN(num) || num === 0) {
243
+ self.fg = self.bg = null;
244
+ self.decorations = [];
245
+ } else if (num === 1) {
246
+ self.decorations.push('bold');
247
+ } else if (num === 2) {
248
+ self.decorations.push('dim');
249
+ // Enable code 2 to get string
250
+ } else if (num === 3) {
251
+ self.decorations.push('italic');
252
+ } else if (num === 4) {
253
+ self.decorations.push('underline');
254
+ } else if (num === 5) {
255
+ self.decorations.push('blink');
256
+ } else if (num === 7) {
257
+ self.decorations.push('reverse');
258
+ } else if (num === 8) {
259
+ self.decorations.push('hidden');
260
+ // Enable code 9 to get strikethrough
261
+ } else if (num === 9) {
262
+ self.decorations.push('strikethrough');
263
+ /**
264
+ * Add several widely used style codes
265
+ * @see https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
266
+ */
267
+ } else if (num === 21) {
268
+ self.removeDecoration('bold');
269
+ } else if (num === 22) {
270
+ self.removeDecoration('bold');
271
+ self.removeDecoration('dim');
272
+ } else if (num === 23) {
273
+ self.removeDecoration('italic');
274
+ } else if (num === 24) {
275
+ self.removeDecoration('underline');
276
+ } else if (num === 25) {
277
+ self.removeDecoration('blink');
278
+ } else if (num === 27) {
279
+ self.removeDecoration('reverse');
280
+ } else if (num === 28) {
281
+ self.removeDecoration('hidden');
282
+ } else if (num === 29) {
283
+ self.removeDecoration('strikethrough');
284
+ } else if (num === 39) {
285
+ self.fg = null;
286
+ } else if (num === 49) {
287
+ self.bg = null;
288
+ // Foreground color
289
+ } else if (num >= 30 && num < 38) {
290
+ self.fg = ANSI_COLORS[0]![num % 10]!.color;
291
+ // Foreground bright color
292
+ } else if (num >= 90 && num < 98) {
293
+ self.fg = ANSI_COLORS[1]![num % 10]!.color;
294
+ // Background color
295
+ } else if (num >= 40 && num < 48) {
296
+ self.bg = ANSI_COLORS[0]![num % 10]!.color;
297
+ // Background bright color
298
+ } else if (num >= 100 && num < 108) {
299
+ self.bg = ANSI_COLORS[1]![num % 10]!.color;
300
+ } else if (num === 38 || num === 48) {
301
+ // extend color (38=fg, 48=bg)
302
+ const is_foreground = num === 38;
303
+ if (numbers.length >= 1) {
304
+ const mode = numbers.shift();
305
+ if (mode === '5' && numbers.length >= 1) {
306
+ // palette color
307
+ const palette_index = parseInt(numbers.shift()!);
308
+ if (palette_index >= 0 && palette_index <= 255) {
309
+ if (!this.PALETTE_COLORS) {
310
+ self.setupPalette();
311
+ }
312
+ if (is_foreground) {
313
+ self.fg = this.PALETTE_COLORS![palette_index]!;
314
+ } else {
315
+ self.bg = this.PALETTE_COLORS![palette_index]!;
316
+ }
317
+ }
318
+ } else if (mode === '2' && numbers.length >= 3) {
319
+ // true color
320
+ const r = parseInt(numbers.shift()!);
321
+ const g = parseInt(numbers.shift()!);
322
+ const b = parseInt(numbers.shift()!);
323
+ if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
324
+ const color = 'rgb(' + r + ', ' + g + ', ' + b + ')';
325
+ if (is_foreground) {
326
+ self.fg = color;
327
+ } else {
328
+ self.bg = color;
329
+ }
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ if (self.fg === null && self.bg === null && self.decorations.length === 0) {
337
+ return result;
338
+ } else {
339
+ result.fg = self.fg!;
340
+ result.bg = self.bg!;
341
+ result.decorations = self.decorations;
342
+ result.decoration = self.decorations.slice(-1).pop() || null;
343
+ result.was_processed = true;
344
+
345
+ return result;
346
+ }
347
+ }
348
+
349
+ /**
350
+ * processChunk
351
+ * Processes the current chunk of text.
352
+ *
353
+ * @name processChunk
354
+ * @function
355
+ * @param {String} text The input text.
356
+ * @param {Object} options An object containing the following fields:
357
+ *
358
+ * - `json` (Boolean): If `true`, the result will be an object.
359
+ * - `use_classes` (Boolean): If `true`, HTML classes will be appended to the HTML output.
360
+ *
361
+ * @param {Boolean} markup If false, the colors will not be parsed.
362
+ * @return {Object|String} The result (object if `json` is wanted back or string otherwise).
363
+ */
364
+ processChunk(text: string, options: Anser.AnserOptions, markup?: boolean): Anser.AnserJsonEntry {
365
+ options = options || {};
366
+ const jsonChunk = this.processChunkJson(text, options, markup ?? false);
367
+
368
+ // "reverse" decoration reverses foreground and background colors
369
+ jsonChunk.decorations = jsonChunk.decorations.filter((decoration) => {
370
+ if (decoration === 'reverse') {
371
+ // when reversing, missing colors are defaulted to black (bg) and white (fg)
372
+ if (!jsonChunk.fg) {
373
+ jsonChunk.fg = ANSI_COLORS[0]![7]!.color;
374
+ }
375
+ if (!jsonChunk.bg) {
376
+ jsonChunk.bg = ANSI_COLORS[0]![0]!.color;
377
+ }
378
+ const tmpFg = jsonChunk.fg;
379
+ jsonChunk.fg = jsonChunk.bg;
380
+ jsonChunk.bg = tmpFg;
381
+ return false;
382
+ }
383
+ return true;
384
+ });
385
+
386
+ return jsonChunk;
387
+ }
388
+
389
+ removeDecoration(decoration: Anser.DecorationName) {
390
+ const index = this.decorations.indexOf(decoration);
391
+
392
+ if (index >= 0) {
393
+ this.decorations.splice(index, 1);
394
+ }
395
+ }
396
+ }
397
+
398
+ export default Anser;