@motionscript/code 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (65) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/NOTICE +49 -0
  3. package/README.md +148 -50
  4. package/dist/browser/THIRD_PARTY_LICENSES.txt +431 -0
  5. package/dist/browser/index.js +10 -7
  6. package/dist/browser/index.js.map +3 -3
  7. package/dist/browser/manifest.json +85 -3
  8. package/dist/code-range.d.ts +7 -1
  9. package/dist/code-range.d.ts.map +1 -1
  10. package/dist/code-range.js +9 -1
  11. package/dist/code-range.js.map +1 -1
  12. package/dist/diff.d.ts +14 -0
  13. package/dist/diff.d.ts.map +1 -1
  14. package/dist/diff.js +134 -2
  15. package/dist/diff.js.map +1 -1
  16. package/dist/engine.d.ts +2 -2
  17. package/dist/engine.d.ts.map +1 -1
  18. package/dist/engine.js +4 -4
  19. package/dist/engine.js.map +1 -1
  20. package/dist/highlight.d.ts +10 -2
  21. package/dist/highlight.d.ts.map +1 -1
  22. package/dist/highlight.js +40 -6
  23. package/dist/highlight.js.map +1 -1
  24. package/dist/layout.d.ts +1 -1
  25. package/dist/layout.d.ts.map +1 -1
  26. package/dist/layout.js.map +1 -1
  27. package/dist/measure-cache.d.ts +2 -2
  28. package/dist/measure-cache.d.ts.map +1 -1
  29. package/dist/measure-cache.js +0 -0
  30. package/dist/measure-cache.js.map +1 -1
  31. package/dist/node.d.ts +275 -281
  32. package/dist/node.d.ts.map +1 -1
  33. package/dist/node.js +634 -700
  34. package/dist/node.js.map +1 -1
  35. package/dist/nodes.d.ts +6 -9
  36. package/dist/nodes.d.ts.map +1 -1
  37. package/dist/nodes.js +7 -9
  38. package/dist/nodes.js.map +1 -1
  39. package/dist/render.d.ts +10 -3
  40. package/dist/render.d.ts.map +1 -1
  41. package/dist/render.js +60 -1
  42. package/dist/render.js.map +1 -1
  43. package/dist/style.d.ts +3 -3
  44. package/dist/style.d.ts.map +1 -1
  45. package/dist/style.js +10 -6
  46. package/dist/style.js.map +1 -1
  47. package/dist/tokens.d.ts.map +1 -1
  48. package/dist/tokens.js +5 -1
  49. package/dist/tokens.js.map +1 -1
  50. package/dist/transitions.d.ts +19 -1
  51. package/dist/transitions.d.ts.map +1 -1
  52. package/dist/transitions.js.map +1 -1
  53. package/package.json +7 -4
  54. package/src/code-range.ts +10 -1
  55. package/src/diff.ts +137 -2
  56. package/src/engine.ts +5 -5
  57. package/src/highlight.ts +54 -5
  58. package/src/layout.ts +1 -1
  59. package/src/measure-cache.ts +0 -0
  60. package/src/node.ts +767 -747
  61. package/src/nodes.ts +8 -10
  62. package/src/render.ts +71 -3
  63. package/src/style.ts +11 -7
  64. package/src/tokens.ts +5 -1
  65. package/src/transitions.ts +21 -1
package/src/nodes.ts CHANGED
@@ -4,15 +4,13 @@ import { Code } from "./node";
4
4
  * Every node type this package publishes.
5
5
  *
6
6
  * A host registers these by handing the array to an engine —
7
- * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
8
- * key. The decorator only *declares* that key: nothing is registered by the mere
9
- * act of importing a module, so a registry holds exactly what its owner asked
10
- * for and two engines in one process can differ about it.
7
+ * `new Engine(platform, { nodes: NODES })` — which reads each component's key.
8
+ * Defining a component only *declares* that key: nothing is registered by
9
+ * importing a module, so a registry holds exactly what its owner asked for.
11
10
  *
12
- * Listing class **values** is also what survives bundling. This package declares
13
- * `sideEffects: false`, which licenses a bundler to drop a module nothing
14
- * imports a value from, and a document names a node type by string — so
15
- * `import "./node"` for its side effect is exactly what gets shaken out, where an
16
- * array of bindings is a data dependency that cannot be.
11
+ * Listing the component **values** is also what survives bundling: this package
12
+ * declares `sideEffects: false`, and a document names a node type by string, so
13
+ * an import kept only for its side effect is exactly what gets shaken out.
17
14
  */
18
- export const NODES = [Code];
15
+ // Typed by name, so the declaration names each component rather than spelling out its type through core.
16
+ export const NODES: (typeof Code)[] = [Code];
package/src/render.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { RenderContext2D, Graphics2D, NormalizedColor, parseColor, lerpNumber } from "@motionscript/core";
1
+ import { RenderContext2D, Graphics2D, NormalizedColor, parseColor, lerpNumber } from "@motionscript/sdk";
2
2
  import type { CodeLayout, CodeMetrics } from "./layout";
3
3
  import type { IdLine } from "./tokens";
4
4
  import { windowProgress, moveProgress } from "./transitions";
5
- import type { TokenState, StructuralTransition } from "./transitions";
5
+ import type { TokenState, StructuralTransition, RowBand, RowsTransition } from "./transitions";
6
6
  import type { TokenAdvanceCache } from "./measure-cache";
7
7
 
8
8
  /** Colour of a token the grammar had no opinion about. */
@@ -63,8 +63,16 @@ export interface DrawCodeState {
63
63
  tokenStates: Map<number, TokenState>;
64
64
  /** Whether a highlight (persistent dim, or a highlight()/resetHighlight() cross-fade) is currently engaged. */
65
65
  highlightActive: boolean;
66
+ /** The settled row bands, and the change between two sets of them in flight, if any. */
67
+ rowBands?: RowBand[];
68
+ rowsEdit?: RowsTransition | null;
69
+ /** The node's laid-out width, which a band spans when it is wider than the block. */
70
+ boxW?: number;
66
71
  }
67
72
 
73
+ /** What a frame is drawn onto: somewhere to draw, and the face's measurements. A render context is one. */
74
+ export type CodeCanvas = Pick<RenderContext2D, "draw" | "measureText">;
75
+
68
76
  /**
69
77
  * Draw one frame of a code listing: the settled structure, or — mid
70
78
  * structural edit — the two structures it is interpolating between (what
@@ -72,7 +80,7 @@ export interface DrawCodeState {
72
80
  * fades/slides in where it lands). Pure over `state`; touches nothing but
73
81
  * `draw`.
74
82
  */
75
- export function drawCode(draw: RenderContext2D, state: DrawCodeState): void {
83
+ export function drawCode(draw: CodeCanvas, state: DrawCodeState): void {
76
84
  const { tokenLines, metrics, advanceCache, from, to, edit, tokenStates: dim } = state;
77
85
 
78
86
  const progress = edit ? edit.progress : 1;
@@ -87,6 +95,8 @@ export function drawCode(draw: RenderContext2D, state: DrawCodeState): void {
87
95
  // code text begins.
88
96
  const numberRight = -blockW / 2 + metrics.padding.left + gutter - gutterGap;
89
97
 
98
+ drawRowBands(draw, state, Math.max(blockW, state.boxW ?? 0), pMove);
99
+
90
100
  const drawNumber = (label: string, y: number, opacity: number): void => {
91
101
  if (opacity <= 0) return;
92
102
  const labelW = advanceCache.advance(draw, label, metrics.fontSize, metrics.fontFamily, 0);
@@ -201,6 +211,64 @@ export function drawCode(draw: RenderContext2D, state: DrawCodeState): void {
201
211
  if (metrics.showLineNumbers) drawLineNumbers(state, drawNumber, pMove, pIn, pOut);
202
212
  }
203
213
 
214
+ /** A band's top and bottom edge this frame, or `null` when neither of its rows is in the listing any more. */
215
+ function bandEdges(band: RowBand, state: DrawCodeState, pMove: number): { top: number; bottom: number } | null {
216
+ const { from, to, metrics } = state;
217
+ const half = (metrics.fontSize * metrics.lineHeight) / 2;
218
+ const centre = (id: number): number | null => {
219
+ const toIndex = to.lineIndex.get(id);
220
+ const fromIndex = from.lineIndex.get(id);
221
+ if (toIndex !== undefined && fromIndex !== undefined) return lerpNumber(from.lineY[fromIndex], to.lineY[toIndex], pMove);
222
+ if (toIndex !== undefined) return to.lineY[toIndex];
223
+ return fromIndex === undefined ? null : from.lineY[fromIndex];
224
+ };
225
+ const first = centre(band.startId);
226
+ const last = centre(band.endId);
227
+ if (first === null && last === null) return null;
228
+ return { top: (first ?? last as number) + half, bottom: (last ?? first as number) - half };
229
+ }
230
+
231
+ /**
232
+ * Draw the row bands, under the text and across the whole block. A band rides
233
+ * its rows through a structural edit; between two sets, bands paired by position
234
+ * slide and recolour while an unpaired one fades.
235
+ */
236
+ function drawRowBands(draw: CodeCanvas, state: DrawCodeState, width: number, pMove: number): void {
237
+ const { rowBands = [], rowsEdit = null } = state;
238
+ const paint = (top: number, bottom: number, color: NormalizedColor, radius: number, opacity: number): void => {
239
+ if (opacity <= 0 || top <= bottom) return;
240
+ draw.draw(new Graphics2D()
241
+ .rect({ x: 0, y: (top + bottom) / 2, width, height: top - bottom, cornerRadius: radius })
242
+ .fill([{ type: "solid", color, opacity }]));
243
+ };
244
+
245
+ if (!rowsEdit) {
246
+ for (const band of rowBands) {
247
+ const edges = bandEdges(band, state, pMove);
248
+ if (edges) paint(edges.top, edges.bottom, band.color, band.radius, 1);
249
+ }
250
+ return;
251
+ }
252
+
253
+ const p = rowsEdit.progress;
254
+ for (let i = 0; i < Math.max(rowsEdit.from.length, rowsEdit.to.length); i++) {
255
+ const was = rowsEdit.from[i];
256
+ const next = rowsEdit.to[i];
257
+ const a = was ? bandEdges(was, state, pMove) : null;
258
+ const b = next ? bandEdges(next, state, pMove) : null;
259
+ if (a && b) {
260
+ paint(
261
+ lerpNumber(a.top, b.top, p), lerpNumber(a.bottom, b.bottom, p),
262
+ lerpColor(was.color, next.color, p), lerpNumber(was.radius, next.radius, p), 1,
263
+ );
264
+ } else if (a) {
265
+ paint(a.top, a.bottom, was.color, was.radius, 1 - p);
266
+ } else if (b) {
267
+ paint(b.top, b.bottom, next.color, next.radius, p);
268
+ }
269
+ }
270
+ }
271
+
204
272
  /**
205
273
  * Draw the gutter — one number per *slot*, not one per row.
206
274
  *
package/src/style.ts CHANGED
@@ -22,7 +22,7 @@ export interface CodeStyleRule {
22
22
  * `@lezer/highlight`'s {@link tagHighlighter} resolves precedence.
23
23
  */
24
24
  export interface CodeHighlightStyle {
25
- /** Stable name, used as the highlighter cache key. */
25
+ /** Stable name, used to register the style and to refer to it from a `theme` prop. */
26
26
  name: string;
27
27
  /** Color applied to text with no matching tag. */
28
28
  defaultColor: string;
@@ -231,21 +231,25 @@ export type CodeThemeName = keyof typeof BUILTIN_THEMES;
231
231
  */
232
232
  export type CodeTheme = CodeThemeName | (string & {}) | CodeHighlightStyle;
233
233
 
234
- // Cache one compiled Highlighter per style name. Compiling builds a tag-rule
234
+ // Cache one compiled Highlighter per style *object*. Compiling builds a tag-rule
235
235
  // matcher; the parsed result is pure data so it's safe to share across Code nodes.
236
- const compiledByName = new Map<string, Highlighter>();
236
+ // Keyed by the object rather than its `name`: two styles may share a name (an
237
+ // inline theme, or one re-registered under the same name with new colours), and
238
+ // the second must not colour with the first one's rules. A WeakMap, so a style
239
+ // nothing refers to any more takes its matcher with it.
240
+ const compiledByStyle = new WeakMap<CodeHighlightStyle, Highlighter>();
237
241
 
238
242
  /**
239
243
  * Compile a {@link CodeHighlightStyle} into a `@lezer/highlight` {@link Highlighter}
240
- * whose "class" output is the literal color string for each tag. Cached by style
241
- * name so repeated lookups (every tokenize) don't rebuild the matcher.
244
+ * whose "class" output is the literal color string for each tag. Cached per style
245
+ * object so repeated lookups (every tokenize) don't rebuild the matcher.
242
246
  */
243
247
  export function compileStyle(style: CodeHighlightStyle): Highlighter {
244
- const cached = compiledByName.get(style.name);
248
+ const cached = compiledByStyle.get(style);
245
249
  if (cached) return cached;
246
250
  const highlighter = tagHighlighter(
247
251
  style.rules.map(rule => ({ tag: rule.tag, class: rule.color })),
248
252
  );
249
- compiledByName.set(style.name, highlighter);
253
+ compiledByStyle.set(style, highlighter);
250
254
  return highlighter;
251
255
  }
package/src/tokens.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { canHighlight, highlightToTokens } from "./highlight";
1
+ import { canHighlight, highlightToTokens, normalizeSource, resolveTheme } from "./highlight";
2
2
  import { CodeTheme } from "./style";
3
3
 
4
4
  // IdToken is our internal wrapper around a highlighter token that gives every
@@ -36,7 +36,11 @@ export function tokenizeCodeToIdLines(code: string, language: string, theme: Cod
36
36
  // Until the language parser has loaded (it streams in via the asset loader),
37
37
  // fall back to flat, uncolored lines instead of throwing on an unloaded
38
38
  // grammar. The node re-tokenizes once the load completes.
39
+ code = normalizeSource(code);
39
40
  if (!canHighlight(language, theme)) {
41
+ // Resolved for its warning: an unknown theme is reported whether or not
42
+ // the grammar has landed yet.
43
+ resolveTheme(theme);
40
44
  return code.split('\n').map(line => makeIdLine([makeIdToken(line, '#d1d5db')]));
41
45
  }
42
46
  return highlightToTokens(code, language, theme).map(line =>
@@ -1,3 +1,4 @@
1
+ import type { NormalizedColor } from "@motionscript/sdk";
1
2
  import type { CodeLayout } from "./layout";
2
3
  import { IdLine } from "./tokens";
3
4
 
@@ -77,7 +78,26 @@ export interface StructuralTransition {
77
78
  toLayout: CodeLayout | null;
78
79
  }
79
80
 
80
- export type CodeTransition = DimTransition | StructuralTransition;
81
+ /** A filled band behind whole rows: the first and last line it covers, by line id so it follows them through an edit, and its paint. */
82
+ export interface RowBand {
83
+ startId: number;
84
+ endId: number;
85
+ color: NormalizedColor;
86
+ radius: number;
87
+ }
88
+
89
+ /**
90
+ * One set of row bands becoming another. Bands at the same position in both
91
+ * lists travel and recolour; one with no counterpart fades.
92
+ */
93
+ export interface RowsTransition {
94
+ kind: "rows";
95
+ from: RowBand[];
96
+ to: RowBand[];
97
+ progress: number;
98
+ }
99
+
100
+ export type CodeTransition = DimTransition | StructuralTransition | RowsTransition;
81
101
 
82
102
  /**
83
103
  * Decides *when* (in normalized transition time) each part of a structural