bireactive 0.2.4 → 0.3.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/dist/animation/anim.js +4 -0
- package/dist/coll.d.ts +7 -7
- package/dist/core/cell.d.ts +89 -66
- package/dist/core/cell.js +642 -401
- package/dist/core/index.d.ts +4 -14
- package/dist/core/index.js +4 -14
- package/dist/core/lenses/aggregates.d.ts +1 -1
- package/dist/core/lenses/aggregates.js +4 -3
- package/dist/core/lenses/closed-form-policies.js +6 -6
- package/dist/core/lenses/decompositions.js +3 -3
- package/dist/core/lenses/domain-aggregates.js +5 -5
- package/dist/core/lenses/geometry.d.ts +1 -1
- package/dist/core/lenses/geometry.js +6 -7
- package/dist/core/lenses/memory.d.ts +2 -2
- package/dist/core/lenses/memory.js +3 -3
- package/dist/core/lenses/typed-factor.js +4 -3
- package/dist/core/traits.d.ts +1 -0
- package/dist/core/values/box.js +7 -7
- package/dist/core/values/color.js +5 -5
- package/dist/core/values/field.d.ts +70 -0
- package/dist/core/values/field.js +230 -0
- package/dist/core/values/gpu.d.ts +4 -2
- package/dist/core/values/gpu.js +11 -4
- package/dist/core/values/matrix.js +7 -7
- package/dist/core/values/num.d.ts +1 -1
- package/dist/core/values/num.js +1 -1
- package/dist/core/values/pose.js +4 -4
- package/dist/core/values/range.js +6 -6
- package/dist/core/values/template.d.ts +1 -1
- package/dist/core/values/template.js +2 -1
- package/dist/core/values/transform.js +7 -7
- package/dist/core/values/tri.js +3 -3
- package/dist/core/values/vec.js +8 -12
- package/dist/ext/timeline.js +2 -2
- package/dist/formats/cst.d.ts +127 -0
- package/dist/formats/cst.js +280 -0
- package/dist/formats/edn.d.ts +2 -0
- package/dist/formats/edn.js +301 -0
- package/dist/formats/index.d.ts +6 -0
- package/dist/formats/index.js +8 -0
- package/dist/formats/json.d.ts +2 -0
- package/dist/formats/json.js +332 -0
- package/dist/formats/lens.d.ts +8 -0
- package/dist/formats/lens.js +54 -0
- package/dist/formats/toml.d.ts +2 -0
- package/dist/formats/toml.js +526 -0
- package/dist/formats/yaml.d.ts +2 -0
- package/dist/formats/yaml.js +661 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/learn/data.d.ts +49 -0
- package/dist/learn/data.js +181 -0
- package/dist/learn/index.d.ts +3 -0
- package/dist/learn/index.js +6 -0
- package/dist/learn/lens-net.d.ts +63 -0
- package/dist/learn/lens-net.js +219 -0
- package/dist/learn/mlp.d.ts +77 -0
- package/dist/learn/mlp.js +292 -0
- package/dist/propagators/csp.d.ts +13 -0
- package/dist/propagators/csp.js +52 -0
- package/dist/propagators/flex.d.ts +31 -0
- package/dist/propagators/flex.js +189 -0
- package/dist/propagators/graph.d.ts +73 -0
- package/dist/propagators/graph.js +543 -0
- package/dist/propagators/index.d.ts +8 -6
- package/dist/propagators/index.js +15 -6
- package/dist/propagators/lattice.d.ts +45 -0
- package/dist/propagators/lattice.js +113 -0
- package/dist/propagators/layout.d.ts +1 -27
- package/dist/propagators/layout.js +6 -175
- package/dist/propagators/numeric.d.ts +17 -0
- package/dist/propagators/numeric.js +93 -0
- package/dist/propagators/solver.d.ts +51 -0
- package/dist/propagators/solver.js +175 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +3 -0
- package/dist/schema/lens.d.ts +121 -0
- package/dist/schema/lens.js +429 -0
- package/dist/shapes/annular-sector.js +4 -4
- package/dist/shapes/button.js +1 -1
- package/dist/shapes/circle.js +1 -1
- package/dist/shapes/handle.js +2 -2
- package/dist/shapes/label.js +1 -1
- package/dist/shapes/layout.js +2 -2
- package/dist/shapes/rect.js +7 -7
- package/dist/shapes/shape.js +8 -8
- package/dist/web/diagram.js +2 -2
- package/package.json +1 -1
- package/dist/propagators/network.d.ts +0 -52
- package/dist/propagators/network.js +0 -185
- package/dist/propagators/propagator.d.ts +0 -12
- package/dist/propagators/propagator.js +0 -16
- package/dist/propagators/range.d.ts +0 -45
- package/dist/propagators/range.js +0 -147
- package/dist/propagators/relations.d.ts +0 -60
- package/dist/propagators/relations.js +0 -343
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** Abstract value shared by all formats (the hub type). */
|
|
2
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
3
|
+
export interface JsonObject {
|
|
4
|
+
[key: string]: JsonValue;
|
|
5
|
+
}
|
|
6
|
+
export type Scalar = null | boolean | number | string;
|
|
7
|
+
export interface ParseError {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export type Node = ObjectNode | ArrayNode | ScalarNode | ErrorNode;
|
|
13
|
+
export interface ObjectNode {
|
|
14
|
+
kind: "object";
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
entries: Entry[];
|
|
18
|
+
/** Adapter-private layout info (flow vs block, section paths, …). */
|
|
19
|
+
meta?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface ArrayNode {
|
|
22
|
+
kind: "array";
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
items: Node[];
|
|
26
|
+
meta?: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface ScalarNode {
|
|
29
|
+
kind: "scalar";
|
|
30
|
+
start: number;
|
|
31
|
+
end: number;
|
|
32
|
+
value: Scalar;
|
|
33
|
+
}
|
|
34
|
+
/** Unparseable region. Never written into; skipped by the differ. */
|
|
35
|
+
export interface ErrorNode {
|
|
36
|
+
kind: "error";
|
|
37
|
+
start: number;
|
|
38
|
+
end: number;
|
|
39
|
+
}
|
|
40
|
+
/** Object entry. `key === undefined` marks a garbage region recovered
|
|
41
|
+
* inside an object (its node is an ErrorNode). */
|
|
42
|
+
export interface Entry {
|
|
43
|
+
key: string | undefined;
|
|
44
|
+
start: number;
|
|
45
|
+
end: number;
|
|
46
|
+
node: Node;
|
|
47
|
+
meta?: unknown;
|
|
48
|
+
}
|
|
49
|
+
export interface ParseResult {
|
|
50
|
+
tree: Node;
|
|
51
|
+
errors: ParseError[];
|
|
52
|
+
}
|
|
53
|
+
export interface TextEdit {
|
|
54
|
+
start: number;
|
|
55
|
+
end: number;
|
|
56
|
+
text: string;
|
|
57
|
+
}
|
|
58
|
+
/** Diff op against a CST. `depth` is the nesting depth of the target
|
|
59
|
+
* node/object (root = 0); adapters derive indentation from it. A
|
|
60
|
+
* replace carries its context: the object `entry` it is the value of,
|
|
61
|
+
* or the `container` array and `index` it sits at. */
|
|
62
|
+
export type Op = {
|
|
63
|
+
type: "replace";
|
|
64
|
+
node: Node;
|
|
65
|
+
value: JsonValue;
|
|
66
|
+
depth: number;
|
|
67
|
+
entry?: Entry;
|
|
68
|
+
container?: Node;
|
|
69
|
+
index?: number;
|
|
70
|
+
} | {
|
|
71
|
+
type: "insert";
|
|
72
|
+
obj: ObjectNode;
|
|
73
|
+
key: string;
|
|
74
|
+
value: JsonValue;
|
|
75
|
+
depth: number;
|
|
76
|
+
/** Last surviving entry to anchor after (deleted entries are not
|
|
77
|
+
* safe anchors — their spans vanish in the same merge). */
|
|
78
|
+
after?: Entry;
|
|
79
|
+
} | {
|
|
80
|
+
type: "delete";
|
|
81
|
+
obj: ObjectNode;
|
|
82
|
+
entry: Entry;
|
|
83
|
+
depth: number;
|
|
84
|
+
};
|
|
85
|
+
/** A concrete syntax: tolerant parser, canonical printer, op → edits.
|
|
86
|
+
* `opToEdit` returns `null` for an op the syntax can't express in
|
|
87
|
+
* place (caller falls back to a full reprint when the doc is clean). */
|
|
88
|
+
export interface FormatAdapter {
|
|
89
|
+
name: string;
|
|
90
|
+
parse(text: string): ParseResult;
|
|
91
|
+
print(value: JsonValue): string;
|
|
92
|
+
opToEdit(op: Op, text: string): TextEdit[] | null;
|
|
93
|
+
}
|
|
94
|
+
export declare function isObject(v: JsonValue): v is JsonObject;
|
|
95
|
+
export declare function deepEqual(a: JsonValue, b: JsonValue): boolean;
|
|
96
|
+
/** Recovered abstract value of a CST. Error regions are dropped;
|
|
97
|
+
* duplicate keys last-write-wins. Error nodes become `null` (callers
|
|
98
|
+
* only rely on `valueOf` when the parse reported zero errors). */
|
|
99
|
+
export declare function valueOf(node: Node): JsonValue;
|
|
100
|
+
/** Structural equality between a CST subtree and an abstract value.
|
|
101
|
+
* Error nodes are never equal to anything. */
|
|
102
|
+
export declare function nodeEquals(node: Node, value: JsonValue): boolean;
|
|
103
|
+
export declare function subtreeHasError(node: Node): boolean;
|
|
104
|
+
/** Collect ops that bring `node` in line with `theirs`. */
|
|
105
|
+
export declare function diffOps(node: Node, theirs: JsonValue, base: JsonValue | undefined, depth: number, ops: Op[], entry?: Entry, container?: Node, index?: number): void;
|
|
106
|
+
/** True if any edit touches an error span (insertions count when they
|
|
107
|
+
* land strictly inside one). */
|
|
108
|
+
export declare function editsBlocked(edits: TextEdit[], errors: ParseError[]): boolean;
|
|
109
|
+
/** Apply non-overlapping edits (overlapping later edits are dropped).
|
|
110
|
+
* Equal-start insertions apply in op order. */
|
|
111
|
+
export declare function applyEdits(text: string, edits: TextEdit[]): string;
|
|
112
|
+
/** Result of absorbing an external value into existing concrete text. */
|
|
113
|
+
export interface MergeResult {
|
|
114
|
+
text: string;
|
|
115
|
+
/** Ops that could not be applied (blocked by errors or unsupported). */
|
|
116
|
+
skipped: number;
|
|
117
|
+
}
|
|
118
|
+
/** Merge `theirs` into `text` by surgical edits around error regions.
|
|
119
|
+
* Falls back to a full canonical reprint only when an op is
|
|
120
|
+
* inexpressible in place AND the document is error-free. */
|
|
121
|
+
export declare function mergeText(adapter: FormatAdapter, text: string, tree: Node, errors: ParseError[], theirs: JsonValue, base: JsonValue | undefined): MergeResult;
|
|
122
|
+
/** 1-based line/column of a byte offset (for error display). */
|
|
123
|
+
export declare function lineColOf(text: string, offset: number): {
|
|
124
|
+
line: number;
|
|
125
|
+
col: number;
|
|
126
|
+
};
|
|
127
|
+
export declare const indentOf: (depth: number) => string;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// cst.ts — shared concrete-syntax machinery for the format lenses.
|
|
2
|
+
//
|
|
3
|
+
// Every format adapter (JSON, YAML, TOML, EDN) parses text into the same
|
|
4
|
+
// CST shape: nodes with byte spans, error regions as first-class nodes.
|
|
5
|
+
// The backward direction of a format lens is always a set of SURGICAL
|
|
6
|
+
// SPAN EDITS computed here: a three-way diff (mine = the CST, theirs =
|
|
7
|
+
// the new abstract value, base = the last value both sides agreed on)
|
|
8
|
+
// yields ops, each op maps to text edits via the adapter, and any edit
|
|
9
|
+
// touching an error span is dropped — so external writes flow "around"
|
|
10
|
+
// a user's in-progress syntax errors instead of trampling them. The
|
|
11
|
+
// same machinery is what preserves formatting, comments, and cursor
|
|
12
|
+
// position in the error-free case; errors merely mark spans unwritable.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Value helpers
|
|
15
|
+
export function isObject(v) {
|
|
16
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
export function deepEqual(a, b) {
|
|
19
|
+
if (a === b)
|
|
20
|
+
return true;
|
|
21
|
+
if (Array.isArray(a)) {
|
|
22
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
23
|
+
return false;
|
|
24
|
+
for (let i = 0; i < a.length; i++)
|
|
25
|
+
if (!deepEqual(a[i], b[i]))
|
|
26
|
+
return false;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (isObject(a)) {
|
|
30
|
+
if (!isObject(b))
|
|
31
|
+
return false;
|
|
32
|
+
const ka = Object.keys(a);
|
|
33
|
+
const kb = Object.keys(b);
|
|
34
|
+
if (ka.length !== kb.length)
|
|
35
|
+
return false;
|
|
36
|
+
for (const k of ka) {
|
|
37
|
+
if (!(k in b) || !deepEqual(a[k], b[k]))
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
/** Recovered abstract value of a CST. Error regions are dropped;
|
|
45
|
+
* duplicate keys last-write-wins. Error nodes become `null` (callers
|
|
46
|
+
* only rely on `valueOf` when the parse reported zero errors). */
|
|
47
|
+
export function valueOf(node) {
|
|
48
|
+
switch (node.kind) {
|
|
49
|
+
case "scalar":
|
|
50
|
+
return node.value;
|
|
51
|
+
case "array":
|
|
52
|
+
return node.items.filter(it => it.kind !== "error").map(valueOf);
|
|
53
|
+
case "object": {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const e of node.entries) {
|
|
56
|
+
if (e.key === undefined || e.node.kind === "error")
|
|
57
|
+
continue;
|
|
58
|
+
out[e.key] = valueOf(e.node);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
case "error":
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Structural equality between a CST subtree and an abstract value.
|
|
67
|
+
* Error nodes are never equal to anything. */
|
|
68
|
+
export function nodeEquals(node, value) {
|
|
69
|
+
switch (node.kind) {
|
|
70
|
+
case "error":
|
|
71
|
+
return false;
|
|
72
|
+
case "scalar":
|
|
73
|
+
return node.value === value;
|
|
74
|
+
case "array": {
|
|
75
|
+
if (!Array.isArray(value) || node.items.length !== value.length)
|
|
76
|
+
return false;
|
|
77
|
+
for (let i = 0; i < value.length; i++) {
|
|
78
|
+
if (!nodeEquals(node.items[i], value[i]))
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
case "object": {
|
|
84
|
+
if (!isObject(value))
|
|
85
|
+
return false;
|
|
86
|
+
const last = lastEntries(node);
|
|
87
|
+
if (last.size !== Object.keys(value).length)
|
|
88
|
+
return false;
|
|
89
|
+
for (const [k, e] of last) {
|
|
90
|
+
if (!(k in value) || !nodeEquals(e.node, value[k]))
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
// Garbage entries make the object not-equal (they're divergence).
|
|
94
|
+
for (const e of node.entries)
|
|
95
|
+
if (e.key === undefined)
|
|
96
|
+
return false;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function subtreeHasError(node) {
|
|
102
|
+
switch (node.kind) {
|
|
103
|
+
case "error":
|
|
104
|
+
return true;
|
|
105
|
+
case "scalar":
|
|
106
|
+
return false;
|
|
107
|
+
case "array":
|
|
108
|
+
return node.items.some(subtreeHasError);
|
|
109
|
+
case "object":
|
|
110
|
+
return node.entries.some(e => e.key === undefined || subtreeHasError(e.node));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Keyed entries, duplicates resolved to the last occurrence. */
|
|
114
|
+
function lastEntries(node) {
|
|
115
|
+
const m = new Map();
|
|
116
|
+
for (const e of node.entries)
|
|
117
|
+
if (e.key !== undefined)
|
|
118
|
+
m.set(e.key, e);
|
|
119
|
+
return m;
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Three-way diff
|
|
123
|
+
//
|
|
124
|
+
// mine = the CST (possibly containing error regions / local divergence)
|
|
125
|
+
// theirs = the new hub value to absorb
|
|
126
|
+
// base = the value this document last agreed with the hub on
|
|
127
|
+
//
|
|
128
|
+
// Per subtree: if theirs == base, the other side didn't touch it — keep
|
|
129
|
+
// mine (this is what preserves valid-but-unpropagated local edits while
|
|
130
|
+
// the doc is broken elsewhere). Error subtrees are never written.
|
|
131
|
+
/** Collect ops that bring `node` in line with `theirs`. */
|
|
132
|
+
export function diffOps(node, theirs, base, depth, ops, entry, container, index) {
|
|
133
|
+
if (base !== undefined && deepEqual(theirs, base))
|
|
134
|
+
return;
|
|
135
|
+
if (node.kind === "error")
|
|
136
|
+
return;
|
|
137
|
+
if (nodeEquals(node, theirs))
|
|
138
|
+
return;
|
|
139
|
+
if (node.kind === "object" && isObject(theirs) && node.entries.length > 0) {
|
|
140
|
+
const last = lastEntries(node);
|
|
141
|
+
const baseObj = base !== undefined && isObject(base) ? base : undefined;
|
|
142
|
+
const keep = [];
|
|
143
|
+
const dels = [];
|
|
144
|
+
let hasGarbage = false;
|
|
145
|
+
for (const e of node.entries) {
|
|
146
|
+
if (e.key === undefined) {
|
|
147
|
+
hasGarbage = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (last.get(e.key) !== e)
|
|
151
|
+
continue; // shadowed duplicate
|
|
152
|
+
if (e.key in theirs) {
|
|
153
|
+
keep.push(e);
|
|
154
|
+
}
|
|
155
|
+
else if ((baseObj === undefined || e.key in baseObj) && !subtreeHasError(e.node)) {
|
|
156
|
+
// Theirs deleted it (or we have no base info — trust theirs).
|
|
157
|
+
dels.push(e);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
keep.push(e); // mine added it (or it's broken) — keep
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Everything keyed is going away: a wholesale replace is both
|
|
164
|
+
// simpler and the only safe option (inserts can't anchor on
|
|
165
|
+
// entries whose spans are being deleted).
|
|
166
|
+
if (keep.length === 0 && !hasGarbage && dels.length > 0) {
|
|
167
|
+
ops.push({ type: "replace", node, value: theirs, depth, entry, container, index });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
for (const e of dels)
|
|
171
|
+
ops.push({ type: "delete", obj: node, entry: e, depth });
|
|
172
|
+
for (const e of keep) {
|
|
173
|
+
if (!(e.key in theirs))
|
|
174
|
+
continue; // kept-mine divergence — no target
|
|
175
|
+
diffOps(e.node, theirs[e.key], baseObj?.[e.key], depth + 1, ops, e, node);
|
|
176
|
+
}
|
|
177
|
+
let after;
|
|
178
|
+
for (const e of keep)
|
|
179
|
+
if (after === undefined || e.end > after.end)
|
|
180
|
+
after = e;
|
|
181
|
+
for (const k of Object.keys(theirs)) {
|
|
182
|
+
if (last.has(k))
|
|
183
|
+
continue;
|
|
184
|
+
if (baseObj !== undefined && k in baseObj && deepEqual(theirs[k], baseObj[k]))
|
|
185
|
+
continue;
|
|
186
|
+
ops.push({ type: "insert", obj: node, key: k, value: theirs[k], depth, after });
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (node.kind === "array" && Array.isArray(theirs) && node.items.length === theirs.length) {
|
|
191
|
+
const baseArr = Array.isArray(base) && base.length === theirs.length ? base : undefined;
|
|
192
|
+
for (let i = 0; i < theirs.length; i++) {
|
|
193
|
+
diffOps(node.items[i], theirs[i], baseArr?.[i], depth + 1, ops, undefined, node, i);
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Shape change (or empty object getting members): whole-node replace,
|
|
198
|
+
// unless the subtree holds an error region — then it stays stale.
|
|
199
|
+
if (subtreeHasError(node))
|
|
200
|
+
return;
|
|
201
|
+
ops.push({ type: "replace", node, value: theirs, depth, entry, container, index });
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Edit application
|
|
205
|
+
function spansIntersect(aStart, aEnd, bStart, bEnd) {
|
|
206
|
+
return aStart < bEnd && bStart < aEnd;
|
|
207
|
+
}
|
|
208
|
+
/** True if any edit touches an error span (insertions count when they
|
|
209
|
+
* land strictly inside one). */
|
|
210
|
+
export function editsBlocked(edits, errors) {
|
|
211
|
+
for (const e of edits) {
|
|
212
|
+
for (const err of errors) {
|
|
213
|
+
if (e.start === e.end) {
|
|
214
|
+
if (err.start < e.start && e.start < err.end)
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
else if (spansIntersect(e.start, e.end, err.start, err.end)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/** Apply non-overlapping edits (overlapping later edits are dropped).
|
|
225
|
+
* Equal-start insertions apply in op order. */
|
|
226
|
+
export function applyEdits(text, edits) {
|
|
227
|
+
const sorted = edits
|
|
228
|
+
.map((e, i) => ({ e, i }))
|
|
229
|
+
.sort((a, b) => a.e.start - b.e.start || a.e.end - b.e.end || a.i - b.i);
|
|
230
|
+
let out = "";
|
|
231
|
+
let pos = 0;
|
|
232
|
+
for (const { e } of sorted) {
|
|
233
|
+
if (e.start < pos)
|
|
234
|
+
continue; // overlap — drop
|
|
235
|
+
out += text.slice(pos, e.start) + e.text;
|
|
236
|
+
pos = e.end;
|
|
237
|
+
}
|
|
238
|
+
return out + text.slice(pos);
|
|
239
|
+
}
|
|
240
|
+
/** Merge `theirs` into `text` by surgical edits around error regions.
|
|
241
|
+
* Falls back to a full canonical reprint only when an op is
|
|
242
|
+
* inexpressible in place AND the document is error-free. */
|
|
243
|
+
export function mergeText(adapter, text, tree, errors, theirs, base) {
|
|
244
|
+
const ops = [];
|
|
245
|
+
diffOps(tree, theirs, base, 0, ops);
|
|
246
|
+
if (ops.length === 0)
|
|
247
|
+
return { text, skipped: 0 };
|
|
248
|
+
const edits = [];
|
|
249
|
+
let skipped = 0;
|
|
250
|
+
for (const op of ops) {
|
|
251
|
+
const e = adapter.opToEdit(op, text);
|
|
252
|
+
if (e === null) {
|
|
253
|
+
if (errors.length === 0)
|
|
254
|
+
return { text: adapter.print(theirs), skipped: 0 };
|
|
255
|
+
skipped++;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (editsBlocked(e, errors)) {
|
|
259
|
+
skipped++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
edits.push(...e);
|
|
263
|
+
}
|
|
264
|
+
return { text: applyEdits(text, edits), skipped };
|
|
265
|
+
}
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// Misc shared helpers
|
|
268
|
+
/** 1-based line/column of a byte offset (for error display). */
|
|
269
|
+
export function lineColOf(text, offset) {
|
|
270
|
+
let line = 1;
|
|
271
|
+
let lineStart = 0;
|
|
272
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
273
|
+
if (text[i] === "\n") {
|
|
274
|
+
line++;
|
|
275
|
+
lineStart = i + 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { line, col: offset - lineStart + 1 };
|
|
279
|
+
}
|
|
280
|
+
export const indentOf = (depth) => " ".repeat(depth);
|