@motionscript/code 0.0.0-stage → 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +201 -0
- package/README.md +76 -3
- package/dist/browser/chunks/chunk-HET2RCSD.js +2 -0
- package/dist/browser/chunks/chunk-HET2RCSD.js.map +7 -0
- package/dist/browser/chunks/chunk-SYZUZB3M.js +2 -0
- package/dist/browser/chunks/chunk-SYZUZB3M.js.map +7 -0
- package/dist/browser/chunks/dist-3F5TQHM6.js +2 -0
- package/dist/browser/chunks/dist-3F5TQHM6.js.map +7 -0
- package/dist/browser/chunks/dist-3SIR23P4.js +2 -0
- package/dist/browser/chunks/dist-3SIR23P4.js.map +7 -0
- package/dist/browser/chunks/dist-AQRNACF3.js +2 -0
- package/dist/browser/chunks/dist-AQRNACF3.js.map +7 -0
- package/dist/browser/chunks/dist-B46VH2YP.js +2 -0
- package/dist/browser/chunks/dist-B46VH2YP.js.map +7 -0
- package/dist/browser/chunks/dist-FH6BHJ6A.js +7 -0
- package/dist/browser/chunks/dist-FH6BHJ6A.js.map +7 -0
- package/dist/browser/chunks/dist-FQDUBZKU.js +2 -0
- package/dist/browser/chunks/dist-FQDUBZKU.js.map +7 -0
- package/dist/browser/chunks/dist-GPNEVCJI.js +2 -0
- package/dist/browser/chunks/dist-GPNEVCJI.js.map +7 -0
- package/dist/browser/chunks/dist-LRSXSDHT.js +2 -0
- package/dist/browser/chunks/dist-LRSXSDHT.js.map +7 -0
- package/dist/browser/chunks/dist-N4CNBNHL.js +2 -0
- package/dist/browser/chunks/dist-N4CNBNHL.js.map +7 -0
- package/dist/browser/chunks/dist-OXJDTPQ6.js +2 -0
- package/dist/browser/chunks/dist-OXJDTPQ6.js.map +7 -0
- package/dist/browser/chunks/dist-SH7H4MRK.js +2 -0
- package/dist/browser/chunks/dist-SH7H4MRK.js.map +7 -0
- package/dist/browser/chunks/dist-UG3NTKWL.js +2 -0
- package/dist/browser/chunks/dist-UG3NTKWL.js.map +7 -0
- package/dist/browser/chunks/dist-W5ZXIMV7.js +2 -0
- package/dist/browser/chunks/dist-W5ZXIMV7.js.map +7 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/manifest.json +11 -0
- package/dist/code-range.d.ts +69 -0
- package/dist/code-range.d.ts.map +1 -0
- package/dist/code-range.js +124 -0
- package/dist/code-range.js.map +1 -0
- package/dist/diff.d.ts +42 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +179 -0
- package/dist/diff.js.map +1 -0
- package/dist/engine.d.ts +15 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +17 -0
- package/dist/engine.js.map +1 -0
- package/dist/highlight.d.ts +43 -0
- package/dist/highlight.d.ts.map +1 -0
- package/dist/highlight.js +243 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +77 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +74 -0
- package/dist/layout.js.map +1 -0
- package/dist/measure-cache.d.ts +38 -0
- package/dist/measure-cache.d.ts.map +1 -0
- package/dist/measure-cache.js +0 -0
- package/dist/measure-cache.js.map +1 -0
- package/dist/node.d.ts +293 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +733 -0
- package/dist/node.js.map +1 -0
- package/dist/nodes.d.ts +18 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +18 -0
- package/dist/nodes.js.map +1 -0
- package/dist/render.d.ts +28 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +223 -0
- package/dist/render.js.map +1 -0
- package/dist/style.d.ts +88 -0
- package/dist/style.d.ts.map +1 -0
- package/dist/style.js +203 -0
- package/dist/style.js.map +1 -0
- package/dist/tokens.d.ts +24 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +50 -0
- package/dist/tokens.js.map +1 -0
- package/dist/transitions.d.ts +115 -0
- package/dist/transitions.d.ts.map +1 -0
- package/dist/transitions.js +85 -0
- package/dist/transitions.js.map +1 -0
- package/package.json +82 -3
- package/registry.json +6 -0
- package/src/code-range.ts +156 -0
- package/src/diff.ts +216 -0
- package/src/engine.ts +21 -0
- package/src/highlight.ts +291 -0
- package/src/index.ts +3 -0
- package/src/layout.ts +157 -0
- package/src/measure-cache.ts +0 -0
- package/src/node.ts +834 -0
- package/src/nodes.ts +18 -0
- package/src/render.ts +247 -0
- package/src/style.ts +251 -0
- package/src/tokens.ts +75 -0
- package/src/transitions.ts +184 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC","sourcesContent":["export * from './node';\nexport * from './engine';\nexport { NODES } from \"./nodes\";\n"]}
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { InsetsResolved, MeasureContext2D, RenderContext2D } from "@motionscript/core";
|
|
2
|
+
import type { TokenAdvanceCache } from "./measure-cache.js";
|
|
3
|
+
import type { IdLine } from "./tokens.js";
|
|
4
|
+
/**
|
|
5
|
+
* Everything about a listing's geometry that is *not* its content — the type,
|
|
6
|
+
* the padding, whether there is a gutter. Split out from the node so a layout
|
|
7
|
+
* can be computed for a token structure the node is no longer holding: during a
|
|
8
|
+
* structural transition the frame is the interpolation of two of them, and the
|
|
9
|
+
* one that is going away is not reachable through `this` any more.
|
|
10
|
+
*/
|
|
11
|
+
export interface CodeMetrics {
|
|
12
|
+
fontSize: number;
|
|
13
|
+
fontFamily: string;
|
|
14
|
+
lineHeight: number;
|
|
15
|
+
letterSpacing: number;
|
|
16
|
+
padding: InsetsResolved;
|
|
17
|
+
showLineNumbers: boolean;
|
|
18
|
+
lineNumberGap: number;
|
|
19
|
+
}
|
|
20
|
+
/** Where one token sits: its LEFT edge, and the vertical CENTRE of its line's slot. */
|
|
21
|
+
export interface TokenBox {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A resolved listing geometry: every token placed, in the node's own space
|
|
27
|
+
* (origin at the block's centre, y-up).
|
|
28
|
+
*
|
|
29
|
+
* Positions are absolute rather than "a running sum plus a width scale", which
|
|
30
|
+
* is the whole point. A token's place in the *next* structure is knowable
|
|
31
|
+
* without drawing it, so a transition can move a token from where it was to
|
|
32
|
+
* where it will be instead of faking the motion by collapsing the advance of
|
|
33
|
+
* its neighbours — which is what made an insert look like a pile of glyphs at
|
|
34
|
+
* the left margin springing apart.
|
|
35
|
+
*/
|
|
36
|
+
export interface CodeLayout {
|
|
37
|
+
/** Token id → placement. */
|
|
38
|
+
tokens: Map<number, TokenBox>;
|
|
39
|
+
/** Vertical centre of each line's slot, by line index. */
|
|
40
|
+
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
|
+
*/
|
|
50
|
+
lineW: number[];
|
|
51
|
+
/** Line id → its index in this structure (i.e. its line *number* minus one). */
|
|
52
|
+
lineIndex: Map<number, number>;
|
|
53
|
+
gutter: number;
|
|
54
|
+
gutterGap: number;
|
|
55
|
+
/** Content size, no padding. */
|
|
56
|
+
innerW: number;
|
|
57
|
+
innerH: number;
|
|
58
|
+
/** Content size plus padding — what the block draws and measures as. */
|
|
59
|
+
blockW: number;
|
|
60
|
+
blockH: number;
|
|
61
|
+
/** Left edge of the code column (after padding and gutter). */
|
|
62
|
+
startX: number;
|
|
63
|
+
}
|
|
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
|
+
*/
|
|
68
|
+
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
|
+
*/
|
|
76
|
+
export declare function layoutCode(lines: IdLine[], m: CodeMetrics, cache: TokenAdvanceCache, scope: MeasureContext2D | RenderContext2D): CodeLayout;
|
|
77
|
+
//# sourceMappingURL=layout.d.ts.map
|
|
@@ -0,0 +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,oBAAoB,CAAC;AAC5F,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,uFAAuF;AACvF,MAAM,WAAW,QAAQ;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,UAAU;IACvB,4BAA4B;IAC5B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC9B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;;;;OAQG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gFAAgF;IAChF,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,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAOvD;AAwBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,CAAC,EAAE,WAAW,EACd,KAAK,EAAE,iBAAiB,EACxB,KAAK,EAAE,gBAAgB,GAAG,eAAe,GAC1C,UAAU,CA0CZ"}
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
*/
|
|
5
|
+
export function metricsSignature(m) {
|
|
6
|
+
const p = m.padding;
|
|
7
|
+
return [
|
|
8
|
+
m.fontSize, m.fontFamily, m.lineHeight, m.letterSpacing,
|
|
9
|
+
p.left, p.right, p.top, p.bottom,
|
|
10
|
+
m.showLineNumbers ? 1 : 0, m.lineNumberGap,
|
|
11
|
+
].join("|");
|
|
12
|
+
}
|
|
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
|
+
*/
|
|
18
|
+
function gutterGapOf(scope, m, cache) {
|
|
19
|
+
return cache.advance(scope, " ", m.fontSize, m.fontFamily, 0) * m.lineNumberGap;
|
|
20
|
+
}
|
|
21
|
+
/** Width of the line-number column for a structure of `lineCount` lines, gap included. */
|
|
22
|
+
function gutterOf(scope, m, cache, lineCount, gap) {
|
|
23
|
+
if (!m.showLineNumbers)
|
|
24
|
+
return 0;
|
|
25
|
+
const sample = String(Math.max(1, lineCount));
|
|
26
|
+
return cache.advance(scope, sample, m.fontSize, m.fontFamily, 0) + gap;
|
|
27
|
+
}
|
|
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
|
+
*/
|
|
35
|
+
export function layoutCode(lines, m, cache, scope) {
|
|
36
|
+
const lineH = m.fontSize * m.lineHeight;
|
|
37
|
+
const gutterGap = m.showLineNumbers ? gutterGapOf(scope, m, cache) : 0;
|
|
38
|
+
const gutter = gutterOf(scope, m, cache, lines.length, gutterGap);
|
|
39
|
+
const widths = new Array(lines.length);
|
|
40
|
+
let maxLineWidth = 0;
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
let w = 0;
|
|
43
|
+
for (const tok of lines[i].tokens) {
|
|
44
|
+
w += cache.advance(scope, tok.content, m.fontSize, m.fontFamily, m.letterSpacing);
|
|
45
|
+
}
|
|
46
|
+
widths[i] = w;
|
|
47
|
+
if (w > maxLineWidth)
|
|
48
|
+
maxLineWidth = w;
|
|
49
|
+
}
|
|
50
|
+
const innerW = maxLineWidth + gutter;
|
|
51
|
+
const innerH = lines.length * lineH;
|
|
52
|
+
const blockW = innerW + m.padding.left + m.padding.right;
|
|
53
|
+
const blockH = innerH + m.padding.top + m.padding.bottom;
|
|
54
|
+
const startX = -blockW / 2 + m.padding.left + gutter;
|
|
55
|
+
// y-up author space: the first line sits at the TOP of the block, so the
|
|
56
|
+
// cursor starts at the top edge (+half height, minus top padding) and steps
|
|
57
|
+
// DOWNWARD by subtracting a line height per row.
|
|
58
|
+
const startY = blockH / 2 - m.padding.top;
|
|
59
|
+
const tokens = new Map();
|
|
60
|
+
const lineY = new Array(lines.length);
|
|
61
|
+
const lineIndex = new Map();
|
|
62
|
+
for (let i = 0; i < lines.length; i++) {
|
|
63
|
+
const centerY = startY - i * lineH - lineH / 2;
|
|
64
|
+
lineY[i] = centerY;
|
|
65
|
+
lineIndex.set(lines[i].id, i);
|
|
66
|
+
let x = startX;
|
|
67
|
+
for (const tok of lines[i].tokens) {
|
|
68
|
+
tokens.set(tok.id, { x, y: centerY });
|
|
69
|
+
x += cache.advance(scope, tok.content, m.fontSize, m.fontFamily, m.letterSpacing);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { tokens, lineY, lineW: widths, lineIndex, gutter, gutterGap, innerW, innerH, blockW, blockH, startX };
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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/core\";\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/** Where one token sits: its 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 /** Token id → placement. */\n tokens: Map<number, TokenBox>;\n /** Vertical centre of each line's slot, by line index. */\n lineY: number[];\n /**\n * Width of each line's ink, by line index — the sum of its tokens' advances,\n * so a blank line is `0` and a short line is short.\n *\n * Returned rather than kept local because it is the only thing that knows a\n * listing is **ragged**: `innerW` is the longest line, and everything that\n * asks where the text actually is (hit testing, for one) would otherwise have\n * to treat the block as a rectangle the width of its longest row.\n */\n lineW: number[];\n /** Line id → its index in this structure (i.e. its line *number* minus one). */\n lineIndex: Map<number, number>;\n gutter: number;\n gutterGap: number;\n /** Content size, no padding. */\n innerW: number;\n innerH: number;\n /** Content size plus padding — what the block draws and measures as. */\n blockW: number;\n blockH: number;\n /** Left edge of the code column (after padding and gutter). */\n startX: number;\n}\n\n/**\n * Signature of everything a layout depends on other than the token structure\n * itself. A cached layout survives exactly as long as this is unchanged.\n */\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/**\n * Horizontal gap between the line-number column and the code text. Sized in\n * space-widths so it scales with the font, and measured with letterSpacing 0 —\n * line numbers and the gap don't carry the code's letter-spacing.\n */\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/** Width of the line-number column for a structure of `lineCount` lines, 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/**\n * Place every token of `lines`.\n *\n * Pure in (lines, metrics, font measurements), which is what lets a caller cache\n * one per structure rather than recomputing it per frame — a transition's two\n * endpoints are both fixed for its whole duration.\n */\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 author space: the first line sits at the TOP of the block, so the\n // cursor starts at the top edge (+half height, minus top padding) and steps\n // DOWNWARD by subtracting a line height per row.\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"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MeasureContext2D, RenderContext2D } from "@motionscript/core";
|
|
2
|
+
/**
|
|
3
|
+
* Memoizes text-advance measurements for the Code node.
|
|
4
|
+
*
|
|
5
|
+
* `scope.measureText` runs a full CanvasKit/Skia paragraph layout (shaping plus
|
|
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.
|
|
23
|
+
*/
|
|
24
|
+
export declare class TokenAdvanceCache {
|
|
25
|
+
private cache;
|
|
26
|
+
private sig;
|
|
27
|
+
/** Signature of the font inputs that, when changed, invalidate every entry. */
|
|
28
|
+
signature(fontSize: number, fontFamily: string): string;
|
|
29
|
+
/** Clear the cache if the font signature changed since the last frame. */
|
|
30
|
+
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
|
+
*/
|
|
36
|
+
advance(scope: MeasureContext2D | RenderContext2D, content: string, fontSize: number, fontFamily: string, letterSpacing: number): number;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=measure-cache.d.ts.map
|
|
@@ -0,0 +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,oBAAoB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,KAAK,CAA6B;IAC1C,OAAO,CAAC,GAAG,CAAM;IAEjB,+EAA+E;IAC/E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAIvD,0EAA0E;IAC1E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOvB;;;;OAIG;IACH,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;CAYZ"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-cache.js","sourceRoot":"","sources":["../src/measure-cache.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,iBAAiB;IAClB,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IAClC,GAAG,GAAG,EAAE,CAAC;IAEjB,+EAA+E;IAC/E,SAAS,CAAC,QAAgB,EAAE,UAAkB;QAC1C,OAAO,GAAG,QAAQ,IAAI,UAAU,EAAE,CAAC;IACvC,CAAC;IAED,0EAA0E;IAC1E,IAAI,CAAC,GAAW;QACZ,IAAI,GAAG,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACnB,CAAC;IACL,CAAC;IAED;;;;OAIG;IACH,OAAO,CACH,KAAyC,EACzC,OAAe,EACf,QAAgB,EAChB,UAAkB,EAClB,aAAqB;QAErB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,GAAG,OAAO,IAAI,aAAa,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QAClC,wEAAwE;QACxE,8DAA8D;QAC9D,iDAAiD;QACjD,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;QAC7F,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3B,OAAO,KAAK,CAAC;IACjB,CAAC;CACJ","sourcesContent":["import { MeasureContext2D, RenderContext2D } from \"@motionscript/core\";\r\n\r\n/**\r\n * Memoizes text-advance measurements for the Code node.\r\n *\r\n * `scope.measureText` runs a full CanvasKit/Skia paragraph layout (shaping plus\r\n * font object alloc/free) on every call — far too expensive to repeat for every\r\n * token on every frame. A token's advance only depends on (content, fontSize,\r\n * fontFamily, letterSpacing); content changes only at edit time and the font\r\n * inputs only change via props, so the same string measures to the same width\r\n * across the many static frames the player renders.\r\n *\r\n * fontSize + fontFamily fold into a `signature` checked once per frame: when it\r\n * changes the cache clears. letterSpacing folds into the per-entry key so the\r\n * letter-spaced token advances and the unspaced gutter advances coexist in one\r\n * map. The signature check (not the node's `set()`) is the invalidation point on\r\n * purpose — animated/bound font props (`to({ fontSize })`, `fontSize={() => …}`)\r\n * bypass `set()` but still flow through the per-frame signature comparison.\r\n *\r\n * NOTE: the two `measureText` implementations in packages/browser/src\r\n * (measure-scope.ts and render-context.ts) are uncached and shape+free fonts on\r\n * every call. A shared cache there would speed up all text nodes, but it touches\r\n * shared infra and cross-font correctness, so it's intentionally left out here.\r\n */\r\nexport class TokenAdvanceCache {\r\n private cache = new Map<string, number>();\r\n private sig = \"\";\r\n\r\n /** Signature of the font inputs that, when changed, invalidate every entry. */\r\n signature(fontSize: number, fontFamily: string): string {\r\n return `${fontSize}|${fontFamily}`;\r\n }\r\n\r\n /** Clear the cache if the font signature changed since the last frame. */\r\n sync(sig: string): void {\r\n if (sig !== this.sig) {\r\n this.cache.clear();\r\n this.sig = sig;\r\n }\r\n }\r\n\r\n /**\r\n * Advance width of `content`, measured once per unique (content,\r\n * letterSpacing) pair under the current font signature. Call `sync()` first\r\n * each frame so a font change is reflected.\r\n */\r\n advance(\r\n scope: MeasureContext2D | RenderContext2D,\r\n content: string,\r\n fontSize: number,\r\n fontFamily: string,\r\n letterSpacing: number,\r\n ): number {\r\n if (content.length === 0) return 0;\r\n const key = `${content}\u0000${letterSpacing}`;\r\n const hit = this.cache.get(key);\r\n if (hit !== undefined) return hit;\r\n // Only the advance width is cached: a code block's geometry is a column\r\n // of x offsets, and its line height comes from the node's own\r\n // `lineHeight` prop rather than from the shaper.\r\n const { width } = scope.measureText(content, fontSize, fontFamily, undefined, letterSpacing);\r\n this.cache.set(key, width);\r\n return width;\r\n }\r\n}\r\n"]}
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { RenderContext2D, RenderPass2D, Clip, EasingFunction, NodeConfig, Size2D, SizeConstraints, Node2D, Node2DProps, MeasureContext2D, InsetsResolved, InsetsProps, type AssetScope, type Command, type CommandArgs, type Node, type TweenStepper, type Vector2 } from "@motionscript/core";
|
|
2
|
+
import { CodeRange, type CodeDiffStrategy, type CodePhaseStrategy, CodeTheme } from "./engine.js";
|
|
3
|
+
/**
|
|
4
|
+
* A listing and nothing else — no paint slots, and no box behind the tokens.
|
|
5
|
+
*
|
|
6
|
+
* It did carry a `fill`/`overlay`/`stroke`/`shadow` set and the corner radius
|
|
7
|
+
* that shaped them, so a block could draw its own panel. That was one node doing
|
|
8
|
+
* two jobs, and the second one is a `Rect`'s: a listing set on a card is a `Code`
|
|
9
|
+
* inside a hugging `Rect`, which tracks the block's size for the same reason
|
|
10
|
+
* every other hugging container tracks its child's. What is left here is the
|
|
11
|
+
* listing — its source, how it is coloured, the face it is set in, and the space
|
|
12
|
+
* it keeps around itself.
|
|
13
|
+
*/
|
|
14
|
+
export interface CodeProps extends Node2DProps {
|
|
15
|
+
code: string;
|
|
16
|
+
language: string;
|
|
17
|
+
fontSize: number;
|
|
18
|
+
fontFamily: string;
|
|
19
|
+
/** A built-in/registered theme name (e.g. `'github-dark'`), or a {@link CodeHighlightStyle} object. */
|
|
20
|
+
theme: CodeTheme;
|
|
21
|
+
lineHeight: number;
|
|
22
|
+
letterSpacing: number;
|
|
23
|
+
showLineNumbers: boolean;
|
|
24
|
+
lineNumberGap: number;
|
|
25
|
+
padding: InsetsProps;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Construction-time-only options: pluggable engine behavior, set once when
|
|
29
|
+
* the node is created. Deliberately *not* part of {@link CodeProps} — every
|
|
30
|
+
* `@property` field accepts either a value or a zero-arg reactive binding
|
|
31
|
+
* (`value | (() => value)`, see `PropInputs`), and the node's own reactive
|
|
32
|
+
* write path (`_writeProp`) treats any function value as the latter. A
|
|
33
|
+
* strategy *is* a function, so it can't be told apart from a binding that
|
|
34
|
+
* computes one — it has to stay outside the reactive prop system entirely.
|
|
35
|
+
*/
|
|
36
|
+
export interface CodeStrategies {
|
|
37
|
+
/** Decides which token became which across an edit. Defaults to `defaultCodeDiff`. */
|
|
38
|
+
diffStrategy?: CodeDiffStrategy;
|
|
39
|
+
/** Decides when (in normalized transition time) each part of an edit happens. Defaults to `defaultCodePhases`. */
|
|
40
|
+
phaseStrategy?: CodePhaseStrategy;
|
|
41
|
+
}
|
|
42
|
+
export declare class Code extends Node2D<CodeProps> {
|
|
43
|
+
readonly code: string;
|
|
44
|
+
readonly language: string;
|
|
45
|
+
readonly fontFamily: string;
|
|
46
|
+
readonly theme: CodeTheme;
|
|
47
|
+
readonly fontSize: number;
|
|
48
|
+
readonly lineHeight: number;
|
|
49
|
+
readonly letterSpacing: number;
|
|
50
|
+
readonly showLineNumbers: boolean;
|
|
51
|
+
readonly lineNumberGap: number;
|
|
52
|
+
readonly padding: InsetsResolved;
|
|
53
|
+
/** The settled structure. During an edit, the structure the edit lands on. */
|
|
54
|
+
private tokenLines;
|
|
55
|
+
private tokenized;
|
|
56
|
+
private transitions;
|
|
57
|
+
private advanceCache;
|
|
58
|
+
private structureVersion;
|
|
59
|
+
private layoutCacheKey;
|
|
60
|
+
private layoutCache;
|
|
61
|
+
private highlightDimOpacity;
|
|
62
|
+
private highlightedIds;
|
|
63
|
+
/**
|
|
64
|
+
* Which token became which across an edit, and when each part of that
|
|
65
|
+
* edit happens. Construction-time-only (see {@link CodeStrategies}) —
|
|
66
|
+
* resolved once here rather than left as `?? defaultX` at every call site.
|
|
67
|
+
*/
|
|
68
|
+
private readonly diffStrategy;
|
|
69
|
+
private readonly phaseStrategy;
|
|
70
|
+
constructor(props: NodeConfig<Code, CodeProps> & CodeStrategies);
|
|
71
|
+
set(props: {
|
|
72
|
+
[K in keyof CodeProps]?: CodeProps[K] | (() => CodeProps[K]);
|
|
73
|
+
}): void;
|
|
74
|
+
private tokenize;
|
|
75
|
+
/**
|
|
76
|
+
* Swap in a new settled structure, invalidating the layout cache.
|
|
77
|
+
*
|
|
78
|
+
* The one place `tokenLines` is written, because the cache key is a version
|
|
79
|
+
* counter rather than a hash of the content — a write that skipped the bump
|
|
80
|
+
* would keep serving the previous frame's geometry for the new listing.
|
|
81
|
+
*/
|
|
82
|
+
private setLines;
|
|
83
|
+
/**
|
|
84
|
+
* The monospaced face this block measures and draws with, and the syntax
|
|
85
|
+
* grammar it tokenizes with.
|
|
86
|
+
*
|
|
87
|
+
* Both belong to *layout*: token x positions are measured against the face,
|
|
88
|
+
* and how many tokens there are depends on the grammar. Declared here, the
|
|
89
|
+
* grammar goes on the timeline as an ordinary asset: `AssetManager.loadAt`
|
|
90
|
+
* waits for it before the frame is laid out, so the first measurement is the
|
|
91
|
+
* right one. Nothing is freed on eviction — parsers are cheap to keep
|
|
92
|
+
* resident. The loader touches no node: `renderContent` re-tokenizes once
|
|
93
|
+
* the grammar is there.
|
|
94
|
+
*/
|
|
95
|
+
declareAssets(assets: AssetScope): void;
|
|
96
|
+
private metrics;
|
|
97
|
+
/** The structural edit currently in flight, if any. */
|
|
98
|
+
private activeEdit;
|
|
99
|
+
private settledLayout;
|
|
100
|
+
/**
|
|
101
|
+
* The one or two layouts this frame is drawn from.
|
|
102
|
+
*
|
|
103
|
+
* A settled frame has one. A frame inside an edit has two — where every
|
|
104
|
+
* token was, and where it is going — and the frame is a point between them.
|
|
105
|
+
* That is the whole reason an insert no longer piles its glyphs at the left
|
|
106
|
+
* margin: a token's destination is *computed*, not approximated by collapsing
|
|
107
|
+
* the advance of everything around it.
|
|
108
|
+
*
|
|
109
|
+
* Both endpoints are fixed for the edit's duration, so they are built once
|
|
110
|
+
* and cached on the transition rather than rebuilt per frame.
|
|
111
|
+
*/
|
|
112
|
+
private frameLayout;
|
|
113
|
+
measure(constraints: SizeConstraints, scope: MeasureContext2D): Partial<Size2D>;
|
|
114
|
+
protected renderContent(ctx: RenderPass2D): void;
|
|
115
|
+
protected renderSelf(ctx: RenderContext2D): void;
|
|
116
|
+
/** A listing holds a listing — `append`/`insert`/`erase` are how it grows. */
|
|
117
|
+
protected acceptsChild(_child: Node): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The node's laid-out rect as a clip outline, so `clip` and any backdrop
|
|
120
|
+
* effect are confined to the block rather than to nothing — a listing set on
|
|
121
|
+
* a card is exactly where a backdrop blur is asked for.
|
|
122
|
+
*/
|
|
123
|
+
protected clipSelf(): Clip;
|
|
124
|
+
/**
|
|
125
|
+
* A listing is grabbed by its **text**, line by ragged line, rather than by
|
|
126
|
+
* the rectangle the lines were laid out in.
|
|
127
|
+
*
|
|
128
|
+
* The default narrows a node's grab region to the outline it declares, and
|
|
129
|
+
* this node's outline is a box it no longer paints anything into. Code is
|
|
130
|
+
* mostly whitespace: a ten-line snippet with one long line is a rectangle
|
|
131
|
+
* that is largely empty, and in an editor every one of those empty pixels
|
|
132
|
+
* swallows a click meant for whatever sits behind or beneath it. Short of the
|
|
133
|
+
* text itself there is nothing there to have clicked.
|
|
134
|
+
*
|
|
135
|
+
* So the region is the union of the lines' own extents — each a line-height
|
|
136
|
+
* slot as wide as that line's ink — plus the gutter where line numbers are
|
|
137
|
+
* showing, since those are drawn glyphs too.
|
|
138
|
+
*
|
|
139
|
+
* Answered from the **cached** layout, which is what the last frame drew
|
|
140
|
+
* from, because a hit test arrives with no measurement scope and text cannot
|
|
141
|
+
* be measured without one. Where there is no usable cache — before the first
|
|
142
|
+
* frame, or mid-edit, when the geometry is an interpolation of two structures
|
|
143
|
+
* held on the transition — the box is the honest answer rather than a
|
|
144
|
+
* silhouette measured against stale metrics.
|
|
145
|
+
*/
|
|
146
|
+
protected hitTestSelf(local: Vector2, tolerance: number): boolean;
|
|
147
|
+
/**
|
|
148
|
+
* The layout a hit test may be answered from: the settled cache, and only
|
|
149
|
+
* while it still matches the node's current metrics.
|
|
150
|
+
*
|
|
151
|
+
* The key is the one {@link settledLayout} writes minus its scope-dependent
|
|
152
|
+
* half — every input to it (`metricsSignature`, the advance cache's own
|
|
153
|
+
* signature) is readable without a measurer, which is precisely what makes
|
|
154
|
+
* the check possible here.
|
|
155
|
+
*/
|
|
156
|
+
private hitLayout;
|
|
157
|
+
/**
|
|
158
|
+
* Append `code` to the end of the listing.
|
|
159
|
+
*
|
|
160
|
+
* Stated as *what the source becomes*; {@link editTo} works out the rest —
|
|
161
|
+
* which is why appending a line that closes a block re-highlights the lines
|
|
162
|
+
* above it, instead of colouring the new text as its own program.
|
|
163
|
+
*/
|
|
164
|
+
append(args: CommandArgs<{
|
|
165
|
+
code: string;
|
|
166
|
+
}> & {
|
|
167
|
+
duration: number;
|
|
168
|
+
}): Command<Record<string, never>>;
|
|
169
|
+
/** Insert `code` above the listing. */
|
|
170
|
+
prepend(args: CommandArgs<{
|
|
171
|
+
code: string;
|
|
172
|
+
}> & {
|
|
173
|
+
duration: number;
|
|
174
|
+
}): Command<Record<string, never>>;
|
|
175
|
+
/**
|
|
176
|
+
* Insert `code` at the given (line, col). Both are 1-indexed; col is the
|
|
177
|
+
* column BEFORE which the new content is inserted (col=1 means start of
|
|
178
|
+
* line). If `code` contains newlines, new lines are created in the middle
|
|
179
|
+
* of the existing line.
|
|
180
|
+
*/
|
|
181
|
+
insert(args: CommandArgs<{
|
|
182
|
+
position: [number, number];
|
|
183
|
+
code: string;
|
|
184
|
+
}> & {
|
|
185
|
+
duration: number;
|
|
186
|
+
}): Command<Record<string, never>>;
|
|
187
|
+
/**
|
|
188
|
+
* Erase the code in `codeRange`.
|
|
189
|
+
*
|
|
190
|
+
* Named `erase` rather than `remove` because a `Code` is also a node, and
|
|
191
|
+
* `Node.remove(child)` takes a child out of the tree. Two methods spelled
|
|
192
|
+
* the same on one object, doing entirely different things, is worse than one
|
|
193
|
+
* of them having a slightly less obvious name.
|
|
194
|
+
*
|
|
195
|
+
* A range that covers whole lines takes their line breaks with it, so the
|
|
196
|
+
* rows below close up; a range inside a line takes only the characters, and
|
|
197
|
+
* the rest of the line reflows around the hole.
|
|
198
|
+
*/
|
|
199
|
+
erase(args: CommandArgs<{
|
|
200
|
+
target: CodeRange;
|
|
201
|
+
}> & {
|
|
202
|
+
duration: number;
|
|
203
|
+
}): Command<Record<string, never>>;
|
|
204
|
+
/** Replace the code in `codeRange` with `replacement`. */
|
|
205
|
+
replace(args: CommandArgs<{
|
|
206
|
+
target: CodeRange;
|
|
207
|
+
replacement: string;
|
|
208
|
+
}> & {
|
|
209
|
+
duration: number;
|
|
210
|
+
}): Command<Record<string, never>>;
|
|
211
|
+
/**
|
|
212
|
+
* Highlight a range of code — or several at once (e.g.
|
|
213
|
+
* `CodeRanges.lines(2).word(5, 1, 3)`) — highlighted as one set: tokens
|
|
214
|
+
* within any of the ranges stay at opacity 1, tokens outside dim to
|
|
215
|
+
* `dim`. Persistent — call resetHighlight() to undo, or call highlight()
|
|
216
|
+
* again with a different range to cross-fade.
|
|
217
|
+
*/
|
|
218
|
+
highlight(args: CommandArgs<{
|
|
219
|
+
target: CodeRange | Iterable<CodeRange>;
|
|
220
|
+
dim?: number;
|
|
221
|
+
}> & {
|
|
222
|
+
duration: number;
|
|
223
|
+
}): Command<Record<string, never>>;
|
|
224
|
+
/**
|
|
225
|
+
* Fade all dimmed tokens back to opacity 1 and clear the persistent
|
|
226
|
+
* highlight state.
|
|
227
|
+
*/
|
|
228
|
+
resetHighlight(args: CommandArgs<Record<string, never>> & {
|
|
229
|
+
duration: number;
|
|
230
|
+
}): Command<Record<string, never>>;
|
|
231
|
+
/**
|
|
232
|
+
* Animate the listing to a new source.
|
|
233
|
+
*
|
|
234
|
+
* The engine behind every editing command, and behind `to({ code })`. Every
|
|
235
|
+
* edit is expressed as the source it produces, tokenized, and then *diffed*
|
|
236
|
+
* against what is on screen — so a token that survived the edit keeps its
|
|
237
|
+
* identity and simply travels to its new column, and only what genuinely
|
|
238
|
+
* changed is faded.
|
|
239
|
+
*
|
|
240
|
+
* The result runs in three phases (see `phaseStrategy`): what is leaving
|
|
241
|
+
* fades out, what stays reflows, and only then does what is new arrive.
|
|
242
|
+
* Playing them together is what made a replacement read as a cross-dissolve
|
|
243
|
+
* of two unrelated listings rather than as an edit being made.
|
|
244
|
+
*/
|
|
245
|
+
private editTo;
|
|
246
|
+
/**
|
|
247
|
+
* Put a dim transition on the stack, drive its progress, take it off and
|
|
248
|
+
* commit at the end.
|
|
249
|
+
*
|
|
250
|
+
* A {@link Command} rather than a generator, which is what makes a listing
|
|
251
|
+
* scrubbable: membership is a function of `t` (on the stack for `0 < t < 1`,
|
|
252
|
+
* off it outside), `settle(done)` replaces the `finally` and takes *which
|
|
253
|
+
* way*, and `progress` is assigned from `t` rather than accumulated.
|
|
254
|
+
*/
|
|
255
|
+
private runDim;
|
|
256
|
+
/**
|
|
257
|
+
* Route a tweened `code` through the diff engine.
|
|
258
|
+
*
|
|
259
|
+
* `to({ code })` is the form an author reaches for first — it is how every
|
|
260
|
+
* other prop is animated — and a bare string prop would otherwise land in the
|
|
261
|
+
* generic tween's discrete-snap bucket and simply cut at the end. Intercepted
|
|
262
|
+
* here it becomes the same three-phase edit the named commands produce, while
|
|
263
|
+
* the rest of the step's props tween alongside it untouched.
|
|
264
|
+
*/
|
|
265
|
+
_prepareStep(to: Partial<CodeProps>, duration: number, easing?: EasingFunction): TweenStepper;
|
|
266
|
+
/**
|
|
267
|
+
* Find every range matching the literal string `text` in the current
|
|
268
|
+
* source. Multi-line matches are supported.
|
|
269
|
+
*/
|
|
270
|
+
findAllRanges(text: string): CodeRange[];
|
|
271
|
+
/** Find the `index`th range matching `text`. Returns null if not found. */
|
|
272
|
+
findRangeAt(text: string, index: number): CodeRange | null;
|
|
273
|
+
/** Find the first range matching `text`. Returns null if not found. */
|
|
274
|
+
findFirstRange(text: string): CodeRange | null;
|
|
275
|
+
private joinedSource;
|
|
276
|
+
private lineLengths;
|
|
277
|
+
/** Character offset of a 1-indexed (line, col), clamped into the document. */
|
|
278
|
+
private offsetAt;
|
|
279
|
+
/**
|
|
280
|
+
* Resolve one or more CodeRanges to the set of token ids whose content
|
|
281
|
+
* overlaps any of them. Tokens that partially overlap are included.
|
|
282
|
+
*/
|
|
283
|
+
private tokenIdsInRanges;
|
|
284
|
+
/**
|
|
285
|
+
* Thin delegator: everything about *how* a frame is painted (settled or
|
|
286
|
+
* mid structural-edit, entry/exit motion, colour cross-fade) lives in
|
|
287
|
+
* `drawCode` (the engine's `render.ts`) as a pure function over plain
|
|
288
|
+
* state — this just gathers that state from the node's own instance
|
|
289
|
+
* fields and caches.
|
|
290
|
+
*/
|
|
291
|
+
protected drawSelf(draw: RenderContext2D): void;
|
|
292
|
+
}
|
|
293
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"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"}
|