@motionscript/code 0.1.0 → 0.1.1-alpha.1
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 +21 -0
- package/README.md +2 -2
- package/dist/browser/index.js +8 -8
- package/dist/browser/index.js.map +3 -3
- package/dist/browser/manifest.json +1 -1
- package/dist/code-range.d.ts +7 -47
- package/dist/code-range.d.ts.map +1 -1
- package/dist/code-range.js +8 -45
- package/dist/code-range.js.map +1 -1
- package/dist/diff.d.ts +5 -44
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +22 -69
- package/dist/diff.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -7
- package/dist/engine.js.map +1 -1
- package/dist/highlight.d.ts +13 -31
- package/dist/highlight.d.ts.map +1 -1
- package/dist/highlight.js +25 -87
- package/dist/highlight.js.map +1 -1
- package/dist/layout.d.ts +7 -26
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +5 -20
- package/dist/layout.js.map +1 -1
- package/dist/measure-cache.d.ts +5 -27
- package/dist/measure-cache.d.ts.map +1 -1
- package/dist/measure-cache.js +0 -0
- package/dist/measure-cache.js.map +1 -1
- package/dist/node.d.ts +4 -11
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +18 -41
- package/dist/node.js.map +1 -1
- package/dist/nodes.d.ts +0 -12
- package/dist/nodes.d.ts.map +1 -1
- package/dist/nodes.js +0 -12
- package/dist/nodes.js.map +1 -1
- package/dist/render.d.ts +5 -14
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +13 -54
- package/dist/render.js.map +1 -1
- package/dist/style.d.ts +6 -61
- package/dist/style.d.ts.map +1 -1
- package/dist/style.js +4 -40
- package/dist/style.js.map +1 -1
- package/dist/tokens.d.ts +1 -10
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +3 -38
- package/dist/tokens.js.map +1 -1
- package/dist/transitions.d.ts +16 -68
- package/dist/transitions.d.ts.map +1 -1
- package/dist/transitions.js +5 -17
- package/dist/transitions.js.map +1 -1
- package/package.json +4 -4
- package/src/code-range.ts +9 -53
- package/src/diff.ts +26 -92
- package/src/engine.ts +3 -7
- package/src/highlight.ts +25 -98
- package/src/layout.ts +10 -35
- package/src/measure-cache.ts +0 -0
- package/src/node.ts +21 -57
- package/src/nodes.ts +0 -12
- package/src/render.ts +17 -61
- package/src/style.ts +9 -64
- package/src/tokens.ts +4 -46
- package/src/transitions.ts +16 -72
package/dist/highlight.d.ts
CHANGED
|
@@ -5,44 +5,26 @@ import { CodeHighlightStyle, CodeTheme } from "./style.js";
|
|
|
5
5
|
* as a character at the end of a line, measured and drawn as a token.
|
|
6
6
|
*/
|
|
7
7
|
export declare function normalizeSource(code: string): string;
|
|
8
|
-
export declare const DEFAULT_THEMES: string[];
|
|
9
8
|
export declare const DEFAULT_LANGS: string[];
|
|
10
9
|
/** Register a custom {@link CodeHighlightStyle} so Code nodes can reference it by name. */
|
|
11
10
|
export declare function registerCodeTheme(style: CodeHighlightStyle): void;
|
|
12
|
-
/**
|
|
13
|
-
* Resolve a theme reference (a registered name, or a CodeHighlightStyle object)
|
|
14
|
-
* to a concrete style. Unknown names fall back to the Motion Canvas default so a
|
|
15
|
-
* stray theme string never throws, with one warning per name.
|
|
16
|
-
*/
|
|
11
|
+
/** An unknown theme name falls back to the default style with one warning, never a throw. */
|
|
17
12
|
export declare function resolveTheme(theme: CodeTheme): CodeHighlightStyle;
|
|
18
13
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* the node renders them as plain text, with one warning per language. A grammar
|
|
24
|
-
* whose import is rejected is reported the same way and is tried again by the
|
|
25
|
-
* next call.
|
|
26
|
-
*/
|
|
27
|
-
export declare function ensureHighlighter(_themes?: string[], langs?: string[]): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Load a syntax-highlight language into the shared highlighter. Exposed so a Code
|
|
30
|
-
* node's asset loader can run it on the timeline, and so apps can preload
|
|
31
|
-
* languages explicitly. Returns once the parser is ready (or immediately for an
|
|
32
|
-
* unsupported language). The `theme` arg is accepted for backwards compatibility.
|
|
14
|
+
* Resolves once every requested language's parser is loaded. An unknown language
|
|
15
|
+
* resolves without loading (`canHighlight` stays false and it draws as plain
|
|
16
|
+
* text); a grammar whose import rejects is reported the same way and retried by
|
|
17
|
+
* the next call. Each warns once.
|
|
33
18
|
*/
|
|
34
|
-
export declare function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export declare function
|
|
19
|
+
export declare function ensureHighlighter(langs?: string[]): Promise<void>;
|
|
20
|
+
/** Loads one language; resolves at once for an unsupported one. */
|
|
21
|
+
export declare function loadCodeLanguage(language: string): Promise<void>;
|
|
22
|
+
export declare function initSyntaxHighlighter(langs?: string[]): Promise<void>;
|
|
23
|
+
/** True once `language`'s parser has loaded. */
|
|
24
|
+
export declare function canHighlight(language: string): boolean;
|
|
38
25
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* {@link highlightAt} (per-run color), then breaks tokens at newlines so each
|
|
42
|
-
* output line is a row of single-line, single-color tokens.
|
|
43
|
-
*
|
|
44
|
-
* Callers must have checked {@link canHighlight} first; this returns a single
|
|
45
|
-
* uncolored line per source line if the parser isn't loaded.
|
|
26
|
+
* Per-line rows of single-line, single-color tokens. Without a loaded parser it
|
|
27
|
+
* returns one uncolored token per source line, so callers check {@link canHighlight}.
|
|
46
28
|
*/
|
|
47
29
|
export declare function highlightToTokens(code: string, language: string, theme?: CodeTheme): {
|
|
48
30
|
content: string;
|
package/dist/highlight.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,kBAAkB,EAClB,SAAS,EAGZ,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,kBAAkB,EAClB,SAAS,EAGZ,MAAM,SAAS,CAAC;AA6CjB;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAWD,eAAO,MAAM,aAAa,UAAiD,CAAC;AAI5E,2FAA2F;AAC3F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAEjE;AAED,6FAA6F;AAC7F,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,kBAAkB,CAUjE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,MAAM,EAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChF;AAED,mEAAmE;AACnE,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,qBAAqB,CAAC,KAAK,GAAE,MAAM,EAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEtD;AAkED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,SAAiC,GACzC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,CAsCzC"}
|
package/dist/highlight.js
CHANGED
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import { highlightTree } from "@lezer/highlight";
|
|
2
2
|
import { BUILTIN_THEMES, DefaultHighlightStyle, compileStyle, } from "./style.js";
|
|
3
|
-
|
|
4
|
-
* Lezer-backed syntax highlighting for the Code component, replacing the former
|
|
5
|
-
* Shiki wiring. This mirrors Motion Canvas's `LezerHighlighter`:
|
|
6
|
-
*
|
|
7
|
-
* - {@link prepareHighlight} parses code once and builds a `from:to` → color map
|
|
8
|
-
* by walking {@link highlightTree}.
|
|
9
|
-
* - {@link tokenizeCode} splits code at parse-tree leaf boundaries.
|
|
10
|
-
* - {@link highlightAt} resolves the color (and run length) for a character index.
|
|
11
|
-
*
|
|
12
|
-
* Parsers load lazily and asynchronously (dynamic import) so the bundle doesn't
|
|
13
|
-
* pull in every grammar. The node's asset loader awaits {@link ensureHighlighter}
|
|
14
|
-
* before tokenizing; until a parser resolves, callers fall back to plain text and
|
|
15
|
-
* re-tokenize once {@link canHighlight} returns true.
|
|
16
|
-
*/
|
|
17
|
-
/** Dynamic-import factories for each supported language → a Lezer {@link Parser}. */
|
|
3
|
+
// Dynamic imports keep the bundle from pulling in every grammar.
|
|
18
4
|
const LANGUAGE_LOADERS = {
|
|
19
5
|
typescript: async () => (await import("@lezer/javascript")).parser.configure({ dialect: "ts" }),
|
|
20
6
|
tsx: async () => (await import("@lezer/javascript")).parser.configure({ dialect: "ts jsx" }),
|
|
@@ -33,7 +19,6 @@ const LANGUAGE_LOADERS = {
|
|
|
33
19
|
xml: async () => (await import("@lezer/xml")).parser,
|
|
34
20
|
yaml: async () => (await import("@lezer/yaml")).parser,
|
|
35
21
|
};
|
|
36
|
-
// Aliases so common language strings resolve to a loader above.
|
|
37
22
|
const LANGUAGE_ALIASES = {
|
|
38
23
|
ts: "typescript",
|
|
39
24
|
js: "javascript",
|
|
@@ -66,25 +51,16 @@ function resolveLanguageKey(language) {
|
|
|
66
51
|
const key = language.toLowerCase();
|
|
67
52
|
return LANGUAGE_ALIASES[key] ?? key;
|
|
68
53
|
}
|
|
69
|
-
//
|
|
70
|
-
// concurrent Code nodes asking for the same language share one dynamic import.
|
|
54
|
+
// Keyed by resolved language, so concurrent nodes share one import.
|
|
71
55
|
const loadedParsers = new Map();
|
|
72
56
|
const inFlightLangs = new Map();
|
|
73
|
-
export const DEFAULT_THEMES = [DefaultHighlightStyle.name];
|
|
74
57
|
export const DEFAULT_LANGS = ["typescript", "javascript", "json", "python"];
|
|
75
|
-
// Theme registry, seeded with the built-ins (Motion Canvas default + GitHub
|
|
76
|
-
// dark/light). The `theme` prop is a string name or a CodeHighlightStyle object;
|
|
77
|
-
// apps can register additional styles by name and reference them by that name.
|
|
78
58
|
const themesByName = new Map(Object.entries(BUILTIN_THEMES));
|
|
79
59
|
/** Register a custom {@link CodeHighlightStyle} so Code nodes can reference it by name. */
|
|
80
60
|
export function registerCodeTheme(style) {
|
|
81
61
|
themesByName.set(style.name, style);
|
|
82
62
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Resolve a theme reference (a registered name, or a CodeHighlightStyle object)
|
|
85
|
-
* to a concrete style. Unknown names fall back to the Motion Canvas default so a
|
|
86
|
-
* stray theme string never throws, with one warning per name.
|
|
87
|
-
*/
|
|
63
|
+
/** An unknown theme name falls back to the default style with one warning, never a throw. */
|
|
88
64
|
export function resolveTheme(theme) {
|
|
89
65
|
if (typeof theme !== "string")
|
|
90
66
|
return theme;
|
|
@@ -94,20 +70,13 @@ export function resolveTheme(theme) {
|
|
|
94
70
|
warnOnce("theme", theme, `unknown theme "${theme}"; using "${DefaultHighlightStyle.name}". Built-in themes: ${Object.keys(BUILTIN_THEMES).join(", ")}; register others with registerCodeTheme.`);
|
|
95
71
|
return DefaultHighlightStyle;
|
|
96
72
|
}
|
|
97
|
-
/** Has `language` finished loading its parser? */
|
|
98
|
-
function isLanguageLoaded(language) {
|
|
99
|
-
return loadedParsers.has(resolveLanguageKey(language));
|
|
100
|
-
}
|
|
101
73
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* the node renders them as plain text, with one warning per language. A grammar
|
|
107
|
-
* whose import is rejected is reported the same way and is tried again by the
|
|
108
|
-
* next call.
|
|
74
|
+
* Resolves once every requested language's parser is loaded. An unknown language
|
|
75
|
+
* resolves without loading (`canHighlight` stays false and it draws as plain
|
|
76
|
+
* text); a grammar whose import rejects is reported the same way and retried by
|
|
77
|
+
* the next call. Each warns once.
|
|
109
78
|
*/
|
|
110
|
-
export function ensureHighlighter(
|
|
79
|
+
export function ensureHighlighter(langs = DEFAULT_LANGS) {
|
|
111
80
|
const jobs = [];
|
|
112
81
|
for (const lang of langs) {
|
|
113
82
|
const key = resolveLanguageKey(lang);
|
|
@@ -115,7 +84,6 @@ export function ensureHighlighter(_themes = DEFAULT_THEMES, langs = DEFAULT_LANG
|
|
|
115
84
|
continue;
|
|
116
85
|
const loader = LANGUAGE_LOADERS[key];
|
|
117
86
|
if (!loader) {
|
|
118
|
-
// Unsupported language; leave it unhighlighted, and say so.
|
|
119
87
|
if (!PLAIN_LANGUAGES.has(key)) {
|
|
120
88
|
warnOnce("language", key, `no grammar for language "${lang}"; drawing it as plain text. Supported: ${Object.keys(LANGUAGE_LOADERS).join(", ")} (aliases: ${Object.keys(LANGUAGE_ALIASES).join(", ")}).`);
|
|
121
89
|
}
|
|
@@ -139,31 +107,21 @@ export function ensureHighlighter(_themes = DEFAULT_THEMES, langs = DEFAULT_LANG
|
|
|
139
107
|
}
|
|
140
108
|
return jobs.length === 0 ? Promise.resolve() : Promise.all(jobs).then(() => undefined);
|
|
141
109
|
}
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
* languages explicitly. Returns once the parser is ready (or immediately for an
|
|
146
|
-
* unsupported language). The `theme` arg is accepted for backwards compatibility.
|
|
147
|
-
*/
|
|
148
|
-
export async function loadCodeLanguage(language, _theme) {
|
|
149
|
-
await ensureHighlighter(DEFAULT_THEMES, [language]);
|
|
110
|
+
/** Loads one language; resolves at once for an unsupported one. */
|
|
111
|
+
export async function loadCodeLanguage(language) {
|
|
112
|
+
await ensureHighlighter([language]);
|
|
150
113
|
}
|
|
151
|
-
export async function initSyntaxHighlighter(
|
|
152
|
-
await ensureHighlighter(
|
|
114
|
+
export async function initSyntaxHighlighter(langs = DEFAULT_LANGS) {
|
|
115
|
+
await ensureHighlighter(langs);
|
|
153
116
|
}
|
|
154
|
-
/** True once `language`
|
|
155
|
-
export function canHighlight(language
|
|
156
|
-
return
|
|
117
|
+
/** True once `language`'s parser has loaded. */
|
|
118
|
+
export function canHighlight(language) {
|
|
119
|
+
return loadedParsers.has(resolveLanguageKey(language));
|
|
157
120
|
}
|
|
158
121
|
function nodeId(from, to) {
|
|
159
122
|
return `${from}:${to}`;
|
|
160
123
|
}
|
|
161
|
-
/**
|
|
162
|
-
* Parse `code` and build the per-node color map. Mirrors Motion Canvas's
|
|
163
|
-
* `LezerHighlighter.prepare`: for each highlighted range, walk the subtree and
|
|
164
|
-
* record the color against every contained node's `from:to` id, so a later
|
|
165
|
-
* {@link highlightAt} lookup by `resolveInner` index finds it.
|
|
166
|
-
*/
|
|
124
|
+
/** Maps every node inside a highlighted range to its color by `from:to` id, for {@link highlightAt}. */
|
|
167
125
|
function prepareHighlight(code, language, style) {
|
|
168
126
|
const parser = loadedParsers.get(resolveLanguageKey(language));
|
|
169
127
|
if (!parser)
|
|
@@ -172,8 +130,7 @@ function prepareHighlight(code, language, style) {
|
|
|
172
130
|
const highlighter = compileStyle(style);
|
|
173
131
|
const colorLookup = new Map();
|
|
174
132
|
highlightTree(tree, highlighter, (from, to, classes) => {
|
|
175
|
-
//
|
|
176
|
-
const color = classes;
|
|
133
|
+
const color = classes; // the tag highlighter emits the color as the "class"
|
|
177
134
|
if (!color)
|
|
178
135
|
return;
|
|
179
136
|
const cursor = tree.cursorAt(from, 1);
|
|
@@ -181,30 +138,20 @@ function prepareHighlight(code, language, style) {
|
|
|
181
138
|
colorLookup.set(nodeId(cursor.from, cursor.to), color);
|
|
182
139
|
} while (cursor.next() && cursor.to <= to);
|
|
183
140
|
});
|
|
184
|
-
return { tree,
|
|
141
|
+
return { tree, colorLookup };
|
|
185
142
|
}
|
|
186
|
-
/**
|
|
187
|
-
* Resolve the color and run length for the token starting at `index`. Mirrors
|
|
188
|
-
* Motion Canvas's `LezerHighlighter.highlight`: resolve the innermost node, look
|
|
189
|
-
* up its color, and report how far the run extends (`skipAhead`).
|
|
190
|
-
*/
|
|
143
|
+
/** The color and run length (`skipAhead`) of the innermost node at `index`. */
|
|
191
144
|
function highlightAt(index, cache) {
|
|
192
145
|
const node = cache.tree.resolveInner(index, 1);
|
|
193
146
|
const color = cache.colorLookup.get(nodeId(node.from, node.to));
|
|
194
147
|
if (color) {
|
|
195
148
|
return { color, skipAhead: node.to - index };
|
|
196
149
|
}
|
|
197
|
-
//
|
|
150
|
+
// Uncolored: skip a whole leaf rather than re-resolving every character.
|
|
198
151
|
const skipAhead = node.firstChild ? 0 : node.to - index;
|
|
199
152
|
return { color: null, skipAhead };
|
|
200
153
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Split `code` into structural tokens at parse-tree leaf boundaries, including the
|
|
203
|
-
* gaps between leaves (whitespace/punctuation). Mirrors Motion Canvas's
|
|
204
|
-
* `LezerHighlighter.tokenize`; this segmentation is what the token-level
|
|
205
|
-
* append/insert/erase/replace animations diff against, so matching it keeps the
|
|
206
|
-
* animation behavior identical to Motion Canvas.
|
|
207
|
-
*/
|
|
154
|
+
/** Splits at parse-tree leaf boundaries, gaps between leaves included; the edit animations diff against this segmentation. */
|
|
208
155
|
function tokenizeCode(code, language) {
|
|
209
156
|
const parser = loadedParsers.get(resolveLanguageKey(language));
|
|
210
157
|
if (!parser)
|
|
@@ -227,13 +174,8 @@ function tokenizeCode(code, language) {
|
|
|
227
174
|
return tokens;
|
|
228
175
|
}
|
|
229
176
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* {@link highlightAt} (per-run color), then breaks tokens at newlines so each
|
|
233
|
-
* output line is a row of single-line, single-color tokens.
|
|
234
|
-
*
|
|
235
|
-
* Callers must have checked {@link canHighlight} first; this returns a single
|
|
236
|
-
* uncolored line per source line if the parser isn't loaded.
|
|
177
|
+
* Per-line rows of single-line, single-color tokens. Without a loaded parser it
|
|
178
|
+
* returns one uncolored token per source line, so callers check {@link canHighlight}.
|
|
237
179
|
*/
|
|
238
180
|
export function highlightToTokens(code, language, theme = DefaultHighlightStyle) {
|
|
239
181
|
code = normalizeSource(code);
|
|
@@ -242,18 +184,14 @@ export function highlightToTokens(code, language, theme = DefaultHighlightStyle)
|
|
|
242
184
|
const lines = [[]];
|
|
243
185
|
const pushLine = () => lines.push([]);
|
|
244
186
|
if (!cache) {
|
|
245
|
-
// Parser unavailable: one uncolored token per source line.
|
|
246
187
|
return code.split("\n").map(line => [{ content: line, color: style.defaultColor }]);
|
|
247
188
|
}
|
|
248
|
-
// Walk the structural tokens, coloring each by its starting index and
|
|
249
|
-
// splitting on newlines so no token spans rows.
|
|
250
189
|
let index = 0;
|
|
251
190
|
for (const token of tokenizeCode(code, language)) {
|
|
252
191
|
let offset = 0;
|
|
253
192
|
while (offset < token.length) {
|
|
254
193
|
const { color, skipAhead } = highlightAt(index + offset, cache);
|
|
255
|
-
//
|
|
256
|
-
// structural token and never across a newline (lines are drawn rows).
|
|
194
|
+
// A run never passes its structural token or a newline.
|
|
257
195
|
let runEnd = offset + Math.max(skipAhead, 1);
|
|
258
196
|
if (runEnd > token.length)
|
|
259
197
|
runEnd = token.length;
|
package/dist/highlight.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"highlight.js","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import { Parser, Tree } from \"@lezer/common\";\nimport { highlightTree } from \"@lezer/highlight\";\nimport {\n BUILTIN_THEMES,\n CodeHighlightStyle,\n CodeTheme,\n DefaultHighlightStyle,\n compileStyle,\n} from \"./style\";\n\n/**\n * Lezer-backed syntax highlighting for the Code component, replacing the former\n * Shiki wiring. This mirrors Motion Canvas's `LezerHighlighter`:\n *\n * - {@link prepareHighlight} parses code once and builds a `from:to` → color map\n * by walking {@link highlightTree}.\n * - {@link tokenizeCode} splits code at parse-tree leaf boundaries.\n * - {@link highlightAt} resolves the color (and run length) for a character index.\n *\n * Parsers load lazily and asynchronously (dynamic import) so the bundle doesn't\n * pull in every grammar. The node's asset loader awaits {@link ensureHighlighter}\n * before tokenizing; until a parser resolves, callers fall back to plain text and\n * re-tokenize once {@link canHighlight} returns true.\n */\n\n/** Dynamic-import factories for each supported language → a Lezer {@link Parser}. */\nconst LANGUAGE_LOADERS: Record<string, () => Promise<Parser>> = {\n typescript: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"ts\" }),\n tsx: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"ts jsx\" }),\n javascript: async () => (await import(\"@lezer/javascript\")).parser,\n jsx: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"jsx\" }),\n python: async () => (await import(\"@lezer/python\")).parser,\n json: async () => (await import(\"@lezer/json\")).parser,\n html: async () => (await import(\"@lezer/html\")).parser,\n css: async () => (await import(\"@lezer/css\")).parser,\n rust: async () => (await import(\"@lezer/rust\")).parser,\n go: async () => (await import(\"@lezer/go\")).parser,\n java: async () => (await import(\"@lezer/java\")).parser,\n cpp: async () => (await import(\"@lezer/cpp\")).parser,\n php: async () => (await import(\"@lezer/php\")).parser,\n markdown: async () => (await import(\"@lezer/markdown\")).parser,\n xml: async () => (await import(\"@lezer/xml\")).parser,\n yaml: async () => (await import(\"@lezer/yaml\")).parser,\n};\n\n// Aliases so common language strings resolve to a loader above.\nconst LANGUAGE_ALIASES: Record<string, string> = {\n ts: \"typescript\",\n js: \"javascript\",\n py: \"python\",\n \"c++\": \"cpp\",\n md: \"markdown\",\n yml: \"yaml\",\n shell: \"javascript\", // no shell grammar bundled; fall back rather than throw\n};\n\n/** Names that mean \"no grammar, on purpose\": drawn as plain text without a warning. */\nconst PLAIN_LANGUAGES = new Set([\"\", \"text\", \"plaintext\"]);\n\nconst warned = new Set<string>();\n\n/** One `console.warn` per `kind:name` for the life of the process, so a listing redrawn every frame says it once. */\nfunction warnOnce(kind: string, name: string, message: string): void {\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[Code] ${message}`);\n}\n\n/**\n * The source as the highlighter and the commands see it: lines joined by `\\n`.\n * A `\\r\\n` (or a lone `\\r`) from a file or a clipboard would otherwise survive\n * as a character at the end of a line, measured and drawn as a token.\n */\nexport function normalizeSource(code: string): string {\n return code.indexOf(\"\\r\") === -1 ? code : code.replace(/\\r\\n?/g, \"\\n\");\n}\n\nfunction resolveLanguageKey(language: string): string {\n const key = language.toLowerCase();\n return LANGUAGE_ALIASES[key] ?? key;\n}\n\n// Loaded parsers and in-flight loads, keyed by resolved language key, so\n// concurrent Code nodes asking for the same language share one dynamic import.\nconst loadedParsers = new Map<string, Parser>();\nconst inFlightLangs = new Map<string, Promise<Parser | null>>();\n\nexport const DEFAULT_THEMES = [DefaultHighlightStyle.name];\nexport const DEFAULT_LANGS = [\"typescript\", \"javascript\", \"json\", \"python\"];\n\n// Theme registry, seeded with the built-ins (Motion Canvas default + GitHub\n// dark/light). The `theme` prop is a string name or a CodeHighlightStyle object;\n// apps can register additional styles by name and reference them by that name.\nconst themesByName = new Map<string, CodeHighlightStyle>(Object.entries(BUILTIN_THEMES));\n\n/** Register a custom {@link CodeHighlightStyle} so Code nodes can reference it by name. */\nexport function registerCodeTheme(style: CodeHighlightStyle): void {\n themesByName.set(style.name, style);\n}\n\n/**\n * Resolve a theme reference (a registered name, or a CodeHighlightStyle object)\n * to a concrete style. Unknown names fall back to the Motion Canvas default so a\n * stray theme string never throws, with one warning per name.\n */\nexport function resolveTheme(theme: CodeTheme): CodeHighlightStyle {\n if (typeof theme !== \"string\") return theme;\n const style = themesByName.get(theme);\n if (style) return style;\n warnOnce(\n \"theme\",\n theme,\n `unknown theme \"${theme}\"; using \"${DefaultHighlightStyle.name}\". Built-in themes: ${Object.keys(BUILTIN_THEMES).join(\", \")}; register others with registerCodeTheme.`,\n );\n return DefaultHighlightStyle;\n}\n\n/** Has `language` finished loading its parser? */\nfunction isLanguageLoaded(language: string): boolean {\n return loadedParsers.has(resolveLanguageKey(language));\n}\n\n/**\n * Load the parsers for `langs` (themes are synchronous, so they're a no-op here\n * but kept in the signature for API parity with the former Shiki implementation).\n * Resolves once every requested language's parser is available. Unknown languages\n * resolve without loading anything — {@link canHighlight} stays false for them and\n * the node renders them as plain text, with one warning per language. A grammar\n * whose import is rejected is reported the same way and is tried again by the\n * next call.\n */\nexport function ensureHighlighter(\n _themes: string[] = DEFAULT_THEMES,\n langs: string[] = DEFAULT_LANGS,\n): Promise<void> {\n const jobs: Promise<unknown>[] = [];\n\n for (const lang of langs) {\n const key = resolveLanguageKey(lang);\n if (loadedParsers.has(key)) continue;\n\n const loader = LANGUAGE_LOADERS[key];\n if (!loader) {\n // Unsupported language; leave it unhighlighted, and say so.\n if (!PLAIN_LANGUAGES.has(key)) {\n warnOnce(\n \"language\",\n key,\n `no grammar for language \"${lang}\"; drawing it as plain text. Supported: ${Object.keys(LANGUAGE_LOADERS).join(\", \")} (aliases: ${Object.keys(LANGUAGE_ALIASES).join(\", \")}).`,\n );\n }\n continue;\n }\n\n let job = inFlightLangs.get(key);\n if (!job) {\n job = loader()\n .then(parser => {\n loadedParsers.set(key, parser);\n return parser;\n })\n .catch((error: unknown) => {\n warnOnce(\n \"load\",\n key,\n `could not load the \"${lang}\" grammar; drawing it as plain text: ${error instanceof Error ? error.message : String(error)}`,\n );\n return null;\n })\n .finally(() => inFlightLangs.delete(key));\n inFlightLangs.set(key, job);\n }\n jobs.push(job);\n }\n\n return jobs.length === 0 ? Promise.resolve() : Promise.all(jobs).then(() => undefined);\n}\n\n/**\n * Load a syntax-highlight language into the shared highlighter. Exposed so a Code\n * node's asset loader can run it on the timeline, and so apps can preload\n * languages explicitly. Returns once the parser is ready (or immediately for an\n * unsupported language). The `theme` arg is accepted for backwards compatibility.\n */\nexport async function loadCodeLanguage(language: string, _theme?: string): Promise<void> {\n await ensureHighlighter(DEFAULT_THEMES, [language]);\n}\n\nexport async function initSyntaxHighlighter(\n themes: string[] = DEFAULT_THEMES,\n langs: string[] = DEFAULT_LANGS,\n): Promise<void> {\n await ensureHighlighter(themes, langs);\n}\n\n/** True once `language` can be tokenized (its parser has loaded). */\nexport function canHighlight(language: string, _theme?: CodeTheme): boolean {\n return isLanguageLoaded(language);\n}\n\n/** A parsed-and-prepared highlight cache for one snapshot of code. */\ninterface HighlightCache {\n tree: Tree;\n code: string;\n // `${from}:${to}` node id → color, exactly as Motion Canvas keys it.\n colorLookup: Map<string, string>;\n}\n\nfunction nodeId(from: number, to: number): string {\n return `${from}:${to}`;\n}\n\n/**\n * Parse `code` and build the per-node color map. Mirrors Motion Canvas's\n * `LezerHighlighter.prepare`: for each highlighted range, walk the subtree and\n * record the color against every contained node's `from:to` id, so a later\n * {@link highlightAt} lookup by `resolveInner` index finds it.\n */\nfunction prepareHighlight(code: string, language: string, style: CodeHighlightStyle): HighlightCache | null {\n const parser = loadedParsers.get(resolveLanguageKey(language));\n if (!parser) return null;\n\n const tree = parser.parse(code);\n const highlighter = compileStyle(style);\n const colorLookup = new Map<string, string>();\n\n highlightTree(tree, highlighter, (from, to, classes) => {\n // tagHighlighter emits our color string as the \"class\".\n const color = classes;\n if (!color) return;\n const cursor = tree.cursorAt(from, 1);\n do {\n colorLookup.set(nodeId(cursor.from, cursor.to), color);\n } while (cursor.next() && cursor.to <= to);\n });\n\n return { tree, code, colorLookup };\n}\n\n/**\n * Resolve the color and run length for the token starting at `index`. Mirrors\n * Motion Canvas's `LezerHighlighter.highlight`: resolve the innermost node, look\n * up its color, and report how far the run extends (`skipAhead`).\n */\nfunction highlightAt(index: number, cache: HighlightCache): { color: string | null; skipAhead: number } {\n const node = cache.tree.resolveInner(index, 1);\n const color = cache.colorLookup.get(nodeId(node.from, node.to));\n if (color) {\n return { color, skipAhead: node.to - index };\n }\n // No color: skip past a childless (leaf) node so we don't re-resolve every char.\n const skipAhead = node.firstChild ? 0 : node.to - index;\n return { color: null, skipAhead };\n}\n\n/**\n * Split `code` into structural tokens at parse-tree leaf boundaries, including the\n * gaps between leaves (whitespace/punctuation). Mirrors Motion Canvas's\n * `LezerHighlighter.tokenize`; this segmentation is what the token-level\n * append/insert/erase/replace animations diff against, so matching it keeps the\n * animation behavior identical to Motion Canvas.\n */\nfunction tokenizeCode(code: string, language: string): string[] {\n const parser = loadedParsers.get(resolveLanguageKey(language));\n if (!parser) return [code];\n\n const tree = parser.parse(code);\n const cursor = tree.cursor();\n const tokens: string[] = [];\n let current = 0;\n\n do {\n if (!cursor.node.firstChild) {\n if (cursor.from > current) tokens.push(code.slice(current, cursor.from));\n if (cursor.from < cursor.to) tokens.push(code.slice(cursor.from, cursor.to));\n current = cursor.to;\n }\n } while (cursor.next());\n\n if (current < code.length) tokens.push(code.slice(current));\n return tokens;\n}\n\n/**\n * Tokenize `code` into per-line, colored tokens — the contract the rest of the\n * Code component consumes. Combines {@link tokenizeCode} (structural split) with\n * {@link highlightAt} (per-run color), then breaks tokens at newlines so each\n * output line is a row of single-line, single-color tokens.\n *\n * Callers must have checked {@link canHighlight} first; this returns a single\n * uncolored line per source line if the parser isn't loaded.\n */\nexport function highlightToTokens(\n code: string,\n language: string,\n theme: CodeTheme = DefaultHighlightStyle,\n): { content: string; color?: string }[][] {\n code = normalizeSource(code);\n const style = resolveTheme(theme);\n const cache = prepareHighlight(code, language, style);\n\n const lines: { content: string; color?: string }[][] = [[]];\n const pushLine = () => lines.push([]);\n\n if (!cache) {\n // Parser unavailable: one uncolored token per source line.\n return code.split(\"\\n\").map(line => [{ content: line, color: style.defaultColor }]);\n }\n\n // Walk the structural tokens, coloring each by its starting index and\n // splitting on newlines so no token spans rows.\n let index = 0;\n for (const token of tokenizeCode(code, language)) {\n let offset = 0;\n while (offset < token.length) {\n const { color, skipAhead } = highlightAt(index + offset, cache);\n // Extend the run as far as the highlight says, but never past this\n // structural token and never across a newline (lines are drawn rows).\n let runEnd = offset + Math.max(skipAhead, 1);\n if (runEnd > token.length) runEnd = token.length;\n const newlineRel = token.indexOf(\"\\n\", offset);\n if (newlineRel !== -1 && newlineRel < runEnd) runEnd = newlineRel;\n\n const slice = token.slice(offset, runEnd);\n if (slice.length > 0) {\n lines[lines.length - 1].push({ content: slice, color: color ?? style.defaultColor });\n }\n\n if (newlineRel === runEnd) {\n pushLine();\n runEnd += 1; // consume the newline itself\n }\n offset = runEnd;\n }\n index += token.length;\n }\n\n return lines;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"highlight.js","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"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","sourcesContent":["import { Parser, Tree } from \"@lezer/common\";\nimport { highlightTree } from \"@lezer/highlight\";\nimport {\n BUILTIN_THEMES,\n CodeHighlightStyle,\n CodeTheme,\n DefaultHighlightStyle,\n compileStyle,\n} from \"./style\";\n\n// Dynamic imports keep the bundle from pulling in every grammar.\nconst LANGUAGE_LOADERS: Record<string, () => Promise<Parser>> = {\n typescript: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"ts\" }),\n tsx: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"ts jsx\" }),\n javascript: async () => (await import(\"@lezer/javascript\")).parser,\n jsx: async () => (await import(\"@lezer/javascript\")).parser.configure({ dialect: \"jsx\" }),\n python: async () => (await import(\"@lezer/python\")).parser,\n json: async () => (await import(\"@lezer/json\")).parser,\n html: async () => (await import(\"@lezer/html\")).parser,\n css: async () => (await import(\"@lezer/css\")).parser,\n rust: async () => (await import(\"@lezer/rust\")).parser,\n go: async () => (await import(\"@lezer/go\")).parser,\n java: async () => (await import(\"@lezer/java\")).parser,\n cpp: async () => (await import(\"@lezer/cpp\")).parser,\n php: async () => (await import(\"@lezer/php\")).parser,\n markdown: async () => (await import(\"@lezer/markdown\")).parser,\n xml: async () => (await import(\"@lezer/xml\")).parser,\n yaml: async () => (await import(\"@lezer/yaml\")).parser,\n};\n\nconst LANGUAGE_ALIASES: Record<string, string> = {\n ts: \"typescript\",\n js: \"javascript\",\n py: \"python\",\n \"c++\": \"cpp\",\n md: \"markdown\",\n yml: \"yaml\",\n shell: \"javascript\", // no shell grammar bundled; fall back rather than throw\n};\n\n/** Names that mean \"no grammar, on purpose\": drawn as plain text without a warning. */\nconst PLAIN_LANGUAGES = new Set([\"\", \"text\", \"plaintext\"]);\n\nconst warned = new Set<string>();\n\n/** One `console.warn` per `kind:name` for the life of the process, so a listing redrawn every frame says it once. */\nfunction warnOnce(kind: string, name: string, message: string): void {\n const key = `${kind}:${name}`;\n if (warned.has(key)) return;\n warned.add(key);\n console.warn(`[Code] ${message}`);\n}\n\n/**\n * The source as the highlighter and the commands see it: lines joined by `\\n`.\n * A `\\r\\n` (or a lone `\\r`) from a file or a clipboard would otherwise survive\n * as a character at the end of a line, measured and drawn as a token.\n */\nexport function normalizeSource(code: string): string {\n return code.indexOf(\"\\r\") === -1 ? code : code.replace(/\\r\\n?/g, \"\\n\");\n}\n\nfunction resolveLanguageKey(language: string): string {\n const key = language.toLowerCase();\n return LANGUAGE_ALIASES[key] ?? key;\n}\n\n// Keyed by resolved language, so concurrent nodes share one import.\nconst loadedParsers = new Map<string, Parser>();\nconst inFlightLangs = new Map<string, Promise<Parser | null>>();\n\nexport const DEFAULT_LANGS = [\"typescript\", \"javascript\", \"json\", \"python\"];\n\nconst themesByName = new Map<string, CodeHighlightStyle>(Object.entries(BUILTIN_THEMES));\n\n/** Register a custom {@link CodeHighlightStyle} so Code nodes can reference it by name. */\nexport function registerCodeTheme(style: CodeHighlightStyle): void {\n themesByName.set(style.name, style);\n}\n\n/** An unknown theme name falls back to the default style with one warning, never a throw. */\nexport function resolveTheme(theme: CodeTheme): CodeHighlightStyle {\n if (typeof theme !== \"string\") return theme;\n const style = themesByName.get(theme);\n if (style) return style;\n warnOnce(\n \"theme\",\n theme,\n `unknown theme \"${theme}\"; using \"${DefaultHighlightStyle.name}\". Built-in themes: ${Object.keys(BUILTIN_THEMES).join(\", \")}; register others with registerCodeTheme.`,\n );\n return DefaultHighlightStyle;\n}\n\n/**\n * Resolves once every requested language's parser is loaded. An unknown language\n * resolves without loading (`canHighlight` stays false and it draws as plain\n * text); a grammar whose import rejects is reported the same way and retried by\n * the next call. Each warns once.\n */\nexport function ensureHighlighter(langs: string[] = DEFAULT_LANGS): Promise<void> {\n const jobs: Promise<unknown>[] = [];\n\n for (const lang of langs) {\n const key = resolveLanguageKey(lang);\n if (loadedParsers.has(key)) continue;\n\n const loader = LANGUAGE_LOADERS[key];\n if (!loader) {\n if (!PLAIN_LANGUAGES.has(key)) {\n warnOnce(\n \"language\",\n key,\n `no grammar for language \"${lang}\"; drawing it as plain text. Supported: ${Object.keys(LANGUAGE_LOADERS).join(\", \")} (aliases: ${Object.keys(LANGUAGE_ALIASES).join(\", \")}).`,\n );\n }\n continue;\n }\n\n let job = inFlightLangs.get(key);\n if (!job) {\n job = loader()\n .then(parser => {\n loadedParsers.set(key, parser);\n return parser;\n })\n .catch((error: unknown) => {\n warnOnce(\n \"load\",\n key,\n `could not load the \"${lang}\" grammar; drawing it as plain text: ${error instanceof Error ? error.message : String(error)}`,\n );\n return null;\n })\n .finally(() => inFlightLangs.delete(key));\n inFlightLangs.set(key, job);\n }\n jobs.push(job);\n }\n\n return jobs.length === 0 ? Promise.resolve() : Promise.all(jobs).then(() => undefined);\n}\n\n/** Loads one language; resolves at once for an unsupported one. */\nexport async function loadCodeLanguage(language: string): Promise<void> {\n await ensureHighlighter([language]);\n}\n\nexport async function initSyntaxHighlighter(langs: string[] = DEFAULT_LANGS): Promise<void> {\n await ensureHighlighter(langs);\n}\n\n/** True once `language`'s parser has loaded. */\nexport function canHighlight(language: string): boolean {\n return loadedParsers.has(resolveLanguageKey(language));\n}\n\ninterface HighlightCache {\n tree: Tree;\n colorLookup: Map<string, string>;\n}\n\nfunction nodeId(from: number, to: number): string {\n return `${from}:${to}`;\n}\n\n/** Maps every node inside a highlighted range to its color by `from:to` id, for {@link highlightAt}. */\nfunction prepareHighlight(code: string, language: string, style: CodeHighlightStyle): HighlightCache | null {\n const parser = loadedParsers.get(resolveLanguageKey(language));\n if (!parser) return null;\n\n const tree = parser.parse(code);\n const highlighter = compileStyle(style);\n const colorLookup = new Map<string, string>();\n\n highlightTree(tree, highlighter, (from, to, classes) => {\n const color = classes; // the tag highlighter emits the color as the \"class\"\n if (!color) return;\n const cursor = tree.cursorAt(from, 1);\n do {\n colorLookup.set(nodeId(cursor.from, cursor.to), color);\n } while (cursor.next() && cursor.to <= to);\n });\n\n return { tree, colorLookup };\n}\n\n/** The color and run length (`skipAhead`) of the innermost node at `index`. */\nfunction highlightAt(index: number, cache: HighlightCache): { color: string | null; skipAhead: number } {\n const node = cache.tree.resolveInner(index, 1);\n const color = cache.colorLookup.get(nodeId(node.from, node.to));\n if (color) {\n return { color, skipAhead: node.to - index };\n }\n // Uncolored: skip a whole leaf rather than re-resolving every character.\n const skipAhead = node.firstChild ? 0 : node.to - index;\n return { color: null, skipAhead };\n}\n\n/** Splits at parse-tree leaf boundaries, gaps between leaves included; the edit animations diff against this segmentation. */\nfunction tokenizeCode(code: string, language: string): string[] {\n const parser = loadedParsers.get(resolveLanguageKey(language));\n if (!parser) return [code];\n\n const tree = parser.parse(code);\n const cursor = tree.cursor();\n const tokens: string[] = [];\n let current = 0;\n\n do {\n if (!cursor.node.firstChild) {\n if (cursor.from > current) tokens.push(code.slice(current, cursor.from));\n if (cursor.from < cursor.to) tokens.push(code.slice(cursor.from, cursor.to));\n current = cursor.to;\n }\n } while (cursor.next());\n\n if (current < code.length) tokens.push(code.slice(current));\n return tokens;\n}\n\n/**\n * Per-line rows of single-line, single-color tokens. Without a loaded parser it\n * returns one uncolored token per source line, so callers check {@link canHighlight}.\n */\nexport function highlightToTokens(\n code: string,\n language: string,\n theme: CodeTheme = DefaultHighlightStyle,\n): { content: string; color?: string }[][] {\n code = normalizeSource(code);\n const style = resolveTheme(theme);\n const cache = prepareHighlight(code, language, style);\n\n const lines: { content: string; color?: string }[][] = [[]];\n const pushLine = () => lines.push([]);\n\n if (!cache) {\n return code.split(\"\\n\").map(line => [{ content: line, color: style.defaultColor }]);\n }\n\n let index = 0;\n for (const token of tokenizeCode(code, language)) {\n let offset = 0;\n while (offset < token.length) {\n const { color, skipAhead } = highlightAt(index + offset, cache);\n // A run never passes its structural token or a newline.\n let runEnd = offset + Math.max(skipAhead, 1);\n if (runEnd > token.length) runEnd = token.length;\n const newlineRel = token.indexOf(\"\\n\", offset);\n if (newlineRel !== -1 && newlineRel < runEnd) runEnd = newlineRel;\n\n const slice = token.slice(offset, runEnd);\n if (slice.length > 0) {\n lines[lines.length - 1].push({ content: slice, color: color ?? style.defaultColor });\n }\n\n if (newlineRel === runEnd) {\n pushLine();\n runEnd += 1; // consume the newline itself\n }\n offset = runEnd;\n }\n index += token.length;\n }\n\n return lines;\n}\n"]}
|
package/dist/layout.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface CodeMetrics {
|
|
|
17
17
|
showLineNumbers: boolean;
|
|
18
18
|
lineNumberGap: number;
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
20
|
+
/** A token's LEFT edge and the vertical CENTRE of its line's slot. */
|
|
21
21
|
export interface TokenBox {
|
|
22
22
|
x: number;
|
|
23
23
|
y: number;
|
|
@@ -34,44 +34,25 @@ export interface TokenBox {
|
|
|
34
34
|
* the left margin springing apart.
|
|
35
35
|
*/
|
|
36
36
|
export interface CodeLayout {
|
|
37
|
-
/** Token id → placement. */
|
|
38
37
|
tokens: Map<number, TokenBox>;
|
|
39
|
-
/**
|
|
38
|
+
/** By line index. */
|
|
40
39
|
lineY: number[];
|
|
41
|
-
/**
|
|
42
|
-
* Width of each line's ink, by line index — the sum of its tokens' advances,
|
|
43
|
-
* so a blank line is `0` and a short line is short.
|
|
44
|
-
*
|
|
45
|
-
* Returned rather than kept local because it is the only thing that knows a
|
|
46
|
-
* listing is **ragged**: `innerW` is the longest line, and everything that
|
|
47
|
-
* asks where the text actually is (hit testing, for one) would otherwise have
|
|
48
|
-
* to treat the block as a rectangle the width of its longest row.
|
|
49
|
-
*/
|
|
40
|
+
/** Ink width per line (blank is 0): the only record that a listing is ragged, which hit testing needs. */
|
|
50
41
|
lineW: number[];
|
|
51
|
-
/** Line id → its index in this structure (i.e. its line *number* minus one). */
|
|
52
42
|
lineIndex: Map<number, number>;
|
|
53
43
|
gutter: number;
|
|
54
44
|
gutterGap: number;
|
|
55
45
|
/** Content size, no padding. */
|
|
56
46
|
innerW: number;
|
|
57
47
|
innerH: number;
|
|
58
|
-
/** Content
|
|
48
|
+
/** Content plus padding. */
|
|
59
49
|
blockW: number;
|
|
60
50
|
blockH: number;
|
|
61
|
-
/** Left edge of the code column
|
|
51
|
+
/** Left edge of the code column, after padding and gutter. */
|
|
62
52
|
startX: number;
|
|
63
53
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Signature of everything a layout depends on other than the token structure
|
|
66
|
-
* itself. A cached layout survives exactly as long as this is unchanged.
|
|
67
|
-
*/
|
|
54
|
+
/** Everything a layout depends on besides the token structure; a cached layout lives while it is unchanged. */
|
|
68
55
|
export declare function metricsSignature(m: CodeMetrics): string;
|
|
69
|
-
/**
|
|
70
|
-
* Place every token of `lines`.
|
|
71
|
-
*
|
|
72
|
-
* Pure in (lines, metrics, font measurements), which is what lets a caller cache
|
|
73
|
-
* one per structure rather than recomputing it per frame — a transition's two
|
|
74
|
-
* endpoints are both fixed for its whole duration.
|
|
75
|
-
*/
|
|
56
|
+
/** Pure in (lines, metrics, measurements), so a caller caches one per structure. */
|
|
76
57
|
export declare function layoutCode(lines: IdLine[], m: CodeMetrics, cache: TokenAdvanceCache, scope: MeasureContext2D | RenderContext2D): CodeLayout;
|
|
77
58
|
//# sourceMappingURL=layout.d.ts.map
|
package/dist/layout.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,
|
|
1
|
+
{"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9B,qBAAqB;IACrB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,0GAA0G;IAC1G,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,+GAA+G;AAC/G,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAOvD;AAoBD,oFAAoF;AACpF,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,CAAC,EAAE,WAAW,EACd,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,gBAAgB,GAAG,eAAe,GAC1C,UAAU,CAwCZ"}
|
package/dist/layout.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Signature of everything a layout depends on other than the token structure
|
|
3
|
-
* itself. A cached layout survives exactly as long as this is unchanged.
|
|
4
|
-
*/
|
|
1
|
+
/** Everything a layout depends on besides the token structure; a cached layout lives while it is unchanged. */
|
|
5
2
|
export function metricsSignature(m) {
|
|
6
3
|
const p = m.padding;
|
|
7
4
|
return [
|
|
@@ -10,28 +7,18 @@ export function metricsSignature(m) {
|
|
|
10
7
|
m.showLineNumbers ? 1 : 0, m.lineNumberGap,
|
|
11
8
|
].join("|");
|
|
12
9
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Horizontal gap between the line-number column and the code text. Sized in
|
|
15
|
-
* space-widths so it scales with the font, and measured with letterSpacing 0 —
|
|
16
|
-
* line numbers and the gap don't carry the code's letter-spacing.
|
|
17
|
-
*/
|
|
10
|
+
/** In space-widths, measured with letterSpacing 0: line numbers and the gap don't carry the code's. */
|
|
18
11
|
function gutterGapOf(scope, m, cache) {
|
|
19
12
|
return cache.advance(scope, " ", m.fontSize, m.fontFamily, 0) * m.lineNumberGap;
|
|
20
13
|
}
|
|
21
|
-
/**
|
|
14
|
+
/** Gap included. */
|
|
22
15
|
function gutterOf(scope, m, cache, lineCount, gap) {
|
|
23
16
|
if (!m.showLineNumbers)
|
|
24
17
|
return 0;
|
|
25
18
|
const sample = String(Math.max(1, lineCount));
|
|
26
19
|
return cache.advance(scope, sample, m.fontSize, m.fontFamily, 0) + gap;
|
|
27
20
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Place every token of `lines`.
|
|
30
|
-
*
|
|
31
|
-
* Pure in (lines, metrics, font measurements), which is what lets a caller cache
|
|
32
|
-
* one per structure rather than recomputing it per frame — a transition's two
|
|
33
|
-
* endpoints are both fixed for its whole duration.
|
|
34
|
-
*/
|
|
21
|
+
/** Pure in (lines, metrics, measurements), so a caller caches one per structure. */
|
|
35
22
|
export function layoutCode(lines, m, cache, scope) {
|
|
36
23
|
const lineH = m.fontSize * m.lineHeight;
|
|
37
24
|
const gutterGap = m.showLineNumbers ? gutterGapOf(scope, m, cache) : 0;
|
|
@@ -52,9 +39,7 @@ export function layoutCode(lines, m, cache, scope) {
|
|
|
52
39
|
const blockW = innerW + m.padding.left + m.padding.right;
|
|
53
40
|
const blockH = innerH + m.padding.top + m.padding.bottom;
|
|
54
41
|
const startX = -blockW / 2 + m.padding.left + gutter;
|
|
55
|
-
// y-up
|
|
56
|
-
// cursor starts at the top edge (+half height, minus top padding) and steps
|
|
57
|
-
// DOWNWARD by subtracting a line height per row.
|
|
42
|
+
// y-up: the first line sits at the TOP, and each row steps down by a line height.
|
|
58
43
|
const startY = blockH / 2 - m.padding.top;
|
|
59
44
|
const tokens = new Map();
|
|
60
45
|
const lineY = new Array(lines.length);
|
package/dist/layout.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/layout.ts"],"names":[],"mappings":"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","sourcesContent":["import type { InsetsResolved, MeasureContext2D, RenderContext2D } from \"@motionscript/sdk\";\nimport type { TokenAdvanceCache } from \"./measure-cache\";\nimport type { IdLine } from \"./tokens\";\n\n/**\n * Everything about a listing's geometry that is *not* its content — the type,\n * the padding, whether there is a gutter. Split out from the node so a layout\n * can be computed for a token structure the node is no longer holding: during a\n * structural transition the frame is the interpolation of two of them, and the\n * one that is going away is not reachable through `this` any more.\n */\nexport interface CodeMetrics {\n fontSize: number;\n fontFamily: string;\n lineHeight: number;\n letterSpacing: number;\n padding: InsetsResolved;\n showLineNumbers: boolean;\n lineNumberGap: number;\n}\n\n/** A token's LEFT edge and the vertical CENTRE of its line's slot. */\nexport interface TokenBox {\n x: number;\n y: number;\n}\n\n/**\n * A resolved listing geometry: every token placed, in the node's own space\n * (origin at the block's centre, y-up).\n *\n * Positions are absolute rather than \"a running sum plus a width scale\", which\n * is the whole point. A token's place in the *next* structure is knowable\n * without drawing it, so a transition can move a token from where it was to\n * where it will be instead of faking the motion by collapsing the advance of\n * its neighbours — which is what made an insert look like a pile of glyphs at\n * the left margin springing apart.\n */\nexport interface CodeLayout {\n tokens: Map<number, TokenBox>;\n /** By line index. */\n lineY: number[];\n /** Ink width per line (blank is 0): the only record that a listing is ragged, which hit testing needs. */\n lineW: number[];\n lineIndex: Map<number, number>;\n gutter: number;\n gutterGap: number;\n /** Content size, no padding. */\n innerW: number;\n innerH: number;\n /** Content plus padding. */\n blockW: number;\n blockH: number;\n /** Left edge of the code column, after padding and gutter. */\n startX: number;\n}\n\n/** Everything a layout depends on besides the token structure; a cached layout lives while it is unchanged. */\nexport function metricsSignature(m: CodeMetrics): string {\n const p = m.padding;\n return [\n m.fontSize, m.fontFamily, m.lineHeight, m.letterSpacing,\n p.left, p.right, p.top, p.bottom,\n m.showLineNumbers ? 1 : 0, m.lineNumberGap,\n ].join(\"|\");\n}\n\n/** In space-widths, measured with letterSpacing 0: line numbers and the gap don't carry the code's. */\nfunction gutterGapOf(scope: MeasureContext2D | RenderContext2D, m: CodeMetrics, cache: TokenAdvanceCache): number {\n return cache.advance(scope, \" \", m.fontSize, m.fontFamily, 0) * m.lineNumberGap;\n}\n\n/** Gap included. */\nfunction gutterOf(\n scope: MeasureContext2D | RenderContext2D,\n m: CodeMetrics,\n cache: TokenAdvanceCache,\n lineCount: number,\n gap: number,\n): number {\n if (!m.showLineNumbers) return 0;\n const sample = String(Math.max(1, lineCount));\n return cache.advance(scope, sample, m.fontSize, m.fontFamily, 0) + gap;\n}\n\n/** Pure in (lines, metrics, measurements), so a caller caches one per structure. */\nexport function layoutCode(\n lines: IdLine[],\n m: CodeMetrics,\n cache: TokenAdvanceCache,\n scope: MeasureContext2D | RenderContext2D,\n): CodeLayout {\n const lineH = m.fontSize * m.lineHeight;\n const gutterGap = m.showLineNumbers ? gutterGapOf(scope, m, cache) : 0;\n const gutter = gutterOf(scope, m, cache, lines.length, gutterGap);\n\n const widths: number[] = new Array(lines.length);\n let maxLineWidth = 0;\n for (let i = 0; i < lines.length; i++) {\n let w = 0;\n for (const tok of lines[i].tokens) {\n w += cache.advance(scope, tok.content, m.fontSize, m.fontFamily, m.letterSpacing);\n }\n widths[i] = w;\n if (w > maxLineWidth) maxLineWidth = w;\n }\n\n const innerW = maxLineWidth + gutter;\n const innerH = lines.length * lineH;\n const blockW = innerW + m.padding.left + m.padding.right;\n const blockH = innerH + m.padding.top + m.padding.bottom;\n const startX = -blockW / 2 + m.padding.left + gutter;\n // y-up: the first line sits at the TOP, and each row steps down by a line height.\n const startY = blockH / 2 - m.padding.top;\n\n const tokens = new Map<number, TokenBox>();\n const lineY: number[] = new Array(lines.length);\n const lineIndex = new Map<number, number>();\n\n for (let i = 0; i < lines.length; i++) {\n const centerY = startY - i * lineH - lineH / 2;\n lineY[i] = centerY;\n lineIndex.set(lines[i].id, i);\n let x = startX;\n for (const tok of lines[i].tokens) {\n tokens.set(tok.id, { x, y: centerY });\n x += cache.advance(scope, tok.content, m.fontSize, m.fontFamily, m.letterSpacing);\n }\n }\n\n return { tokens, lineY, lineW: widths, lineIndex, gutter, gutterGap, innerW, innerH, blockW, blockH, startX };\n}\n"]}
|
package/dist/measure-cache.d.ts
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
import { MeasureContext2D, RenderContext2D } from "@motionscript/sdk";
|
|
2
2
|
/**
|
|
3
|
-
* Memoizes
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* font object alloc/free) on every call — far too expensive to repeat for every
|
|
7
|
-
* token on every frame. A token's advance only depends on (content, fontSize,
|
|
8
|
-
* fontFamily, letterSpacing); content changes only at edit time and the font
|
|
9
|
-
* inputs only change via props, so the same string measures to the same width
|
|
10
|
-
* across the many static frames the player renders.
|
|
11
|
-
*
|
|
12
|
-
* fontSize + fontFamily fold into a `signature` checked once per frame: when it
|
|
13
|
-
* changes the cache clears. letterSpacing folds into the per-entry key so the
|
|
14
|
-
* letter-spaced token advances and the unspaced gutter advances coexist in one
|
|
15
|
-
* map. The signature check (not the node's `set()`) is the invalidation point on
|
|
16
|
-
* purpose — animated/bound font props (`to({ fontSize })`, `fontSize={() => …}`)
|
|
17
|
-
* bypass `set()` but still flow through the per-frame signature comparison.
|
|
18
|
-
*
|
|
19
|
-
* NOTE: the two `measureText` implementations in packages/browser/src
|
|
20
|
-
* (measure-scope.ts and render-context.ts) are uncached and shape+free fonts on
|
|
21
|
-
* every call. A shared cache there would speed up all text nodes, but it touches
|
|
22
|
-
* shared infra and cross-font correctness, so it's intentionally left out here.
|
|
3
|
+
* Memoizes token advances: `measureText` runs a full paragraph layout, too costly
|
|
4
|
+
* per token per frame. The per-frame `signature` check, not the node's `set()`, is
|
|
5
|
+
* the invalidation point because animated or bound font props bypass `set()`.
|
|
23
6
|
*/
|
|
24
7
|
export declare class TokenAdvanceCache {
|
|
25
8
|
private cache;
|
|
26
9
|
private sig;
|
|
27
|
-
/** Signature of the font inputs that, when changed, invalidate every entry. */
|
|
28
10
|
signature(fontSize: number, fontFamily: string): string;
|
|
29
|
-
/**
|
|
11
|
+
/** Clears the cache when the font signature changed since the last frame. */
|
|
30
12
|
sync(sig: string): void;
|
|
31
|
-
/**
|
|
32
|
-
* Advance width of `content`, measured once per unique (content,
|
|
33
|
-
* letterSpacing) pair under the current font signature. Call `sync()` first
|
|
34
|
-
* each frame so a font change is reflected.
|
|
35
|
-
*/
|
|
13
|
+
/** Measured once per (content, letterSpacing) under the current signature; call `sync()` first each frame. */
|
|
36
14
|
advance(scope: MeasureContext2D | RenderContext2D, content: string, fontSize: number, fontFamily: string, letterSpacing: number): number;
|
|
37
15
|
}
|
|
38
16
|
//# sourceMappingURL=measure-cache.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure-cache.d.ts","sourceRoot":"","sources":["../src/measure-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEtE
|
|
1
|
+
{"version":3,"file":"measure-cache.d.ts","sourceRoot":"","sources":["../src/measure-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEtE;;;;GAIG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,GAAG,CAAM;IAEjB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAIvD,6EAA6E;IAC7E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOvB,8GAA8G;IAC9G,OAAO,CACH,KAAK,EAAE,gBAAgB,GAAG,eAAe,EACzC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,GACtB,MAAM;CAUZ"}
|
package/dist/measure-cache.js
CHANGED
|
Binary file
|