@rohal12/spindle-lsp 0.15.9 → 0.15.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.
- package/dist/bin.js +1871 -1779
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -55,7 +55,7 @@ var VERSION;
|
|
|
55
55
|
var init_version = __esm({
|
|
56
56
|
"src/version.ts"() {
|
|
57
57
|
"use strict";
|
|
58
|
-
VERSION = false ? pkg.version : "0.15.
|
|
58
|
+
VERSION = false ? pkg.version : "0.15.10";
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
61
|
|
|
@@ -31245,6 +31245,7 @@ var init_text = __esm({
|
|
|
31245
31245
|
});
|
|
31246
31246
|
|
|
31247
31247
|
// src/core/parsing/passage-parser.ts
|
|
31248
|
+
import { createRequire as createCompilerRequire } from "node:module";
|
|
31248
31249
|
function parsePassageHeader(rawLine, lineNumber) {
|
|
31249
31250
|
const line = withoutCr(rawLine);
|
|
31250
31251
|
const bomLength = lineNumber === 0 && line.charCodeAt(0) === 65279 ? 1 : 0;
|
|
@@ -31276,7 +31277,8 @@ function parsePassageHeader(rawLine, lineNumber) {
|
|
|
31276
31277
|
try {
|
|
31277
31278
|
meta3 = JSON.parse(raw);
|
|
31278
31279
|
} catch {
|
|
31279
|
-
|
|
31280
|
+
const { parseTwee } = createCompilerRequire(import.meta.url)("@rohal12/twee-ts");
|
|
31281
|
+
if (parseTwee(line.slice(bomLength)).passages.length !== 1) return null;
|
|
31280
31282
|
}
|
|
31281
31283
|
}
|
|
31282
31284
|
const headerRange = {
|
|
@@ -31303,8 +31305,11 @@ function isReservedPassageName(name) {
|
|
|
31303
31305
|
function isScriptOrStylesheetPassage(passage) {
|
|
31304
31306
|
return passage.tags?.some((t) => CODE_PASSAGE_TAGS.has(t)) ?? false;
|
|
31305
31307
|
}
|
|
31308
|
+
function isRuntimePassage(passage) {
|
|
31309
|
+
return passage.name !== "StoryTitle" && passage.name !== "StoryData" && !isScriptOrStylesheetPassage(passage) && !passage.tags?.includes("Twine.private");
|
|
31310
|
+
}
|
|
31306
31311
|
function isMarkupPassage(passage) {
|
|
31307
|
-
return !(passage.name !== void 0 && NON_MARKUP_PASSAGES.has(passage.name))
|
|
31312
|
+
return isRuntimePassage(passage) && !(passage.name !== void 0 && NON_MARKUP_PASSAGES.has(passage.name));
|
|
31308
31313
|
}
|
|
31309
31314
|
var SPECIAL_PASSAGES, CODE_PASSAGE_TAGS, RESERVED_PASSAGE_NAMES, NON_MARKUP_PASSAGES;
|
|
31310
31315
|
var init_passage_parser = __esm({
|
|
@@ -31501,834 +31506,1506 @@ var init_passage_index = __esm({
|
|
|
31501
31506
|
}
|
|
31502
31507
|
});
|
|
31503
31508
|
|
|
31504
|
-
// src/core/
|
|
31505
|
-
function
|
|
31506
|
-
|
|
31507
|
-
|
|
31509
|
+
// src/core/parsing/argument-lexer.ts
|
|
31510
|
+
function lexArguments(source) {
|
|
31511
|
+
let from = 0;
|
|
31512
|
+
return splitArgs(source).map((text3) => {
|
|
31513
|
+
const start = source.indexOf(text3, from);
|
|
31514
|
+
from = start + text3.length;
|
|
31515
|
+
return classifyArgument(text3, start);
|
|
31516
|
+
});
|
|
31517
|
+
}
|
|
31518
|
+
function classifyArgument(text3, start = 0) {
|
|
31519
|
+
const base = { text: text3, start, end: start + text3.length };
|
|
31520
|
+
if (readQuoted(text3, 0)?.end === text3.length) return { ...base, type: 0 /* String */ };
|
|
31521
|
+
if (text3 === "null") return { ...base, type: 3 /* Null */ };
|
|
31522
|
+
if (text3 === "undefined") return { ...base, type: 10 /* Undefined */ };
|
|
31523
|
+
if (text3 === "true" || text3 === "false") return { ...base, type: 2 /* Boolean */ };
|
|
31524
|
+
if (text3 === "NaN") return { ...base, type: 9 /* NaN */ };
|
|
31525
|
+
if (text3 !== "" && !Number.isNaN(Number(text3))) return { ...base, type: 1 /* Number */ };
|
|
31526
|
+
if (/^[$_@%]\w+(?:\.\w+)*$/.test(text3)) {
|
|
31527
|
+
return { ...base, type: 4 /* Variable */, sigil: text3[0], path: text3.slice(1).split(".") };
|
|
31508
31528
|
}
|
|
31509
|
-
if (
|
|
31529
|
+
if (text3.startsWith("[[") && text3.endsWith("]]")) return { ...base, type: 5 /* Link */ };
|
|
31530
|
+
if (/^\[[<>]?img\[/i.test(text3) && text3.endsWith("]]")) return { ...base, type: 6 /* Image */ };
|
|
31531
|
+
return { ...base, type: /^[-\w]+$/.test(text3) ? 8 /* Bareword */ : 7 /* Expression */ };
|
|
31510
31532
|
}
|
|
31511
|
-
var
|
|
31512
|
-
|
|
31513
|
-
"src/core/workspace/macro-registry.ts"() {
|
|
31533
|
+
var init_argument_lexer = __esm({
|
|
31534
|
+
"src/core/parsing/argument-lexer.ts"() {
|
|
31514
31535
|
"use strict";
|
|
31515
31536
|
init_tooling();
|
|
31516
|
-
init_config_loader();
|
|
31517
|
-
MacroRegistry = class {
|
|
31518
|
-
macros = /* @__PURE__ */ new Map();
|
|
31519
|
-
/** Entries as they were before the current user config was applied (undefined = absent). */
|
|
31520
|
-
configSnapshot = /* @__PURE__ */ new Map();
|
|
31521
|
-
/** Tier 3: macros discovered from Story.defineMacro() calls, keyed by lowercase name. */
|
|
31522
|
-
discovered = /* @__PURE__ */ new Map();
|
|
31523
|
-
/** Called after the set of macros or their metadata changed. */
|
|
31524
|
-
onChange;
|
|
31525
|
-
/** Tier 4: user config entries, kept so they can be re-applied over discovered macros. */
|
|
31526
|
-
configEntries = /* @__PURE__ */ new Map();
|
|
31527
|
-
/**
|
|
31528
|
-
* Load the built-in macros: name, block, sub-macros, flags and typed
|
|
31529
|
-
* parameters, from Spindle's tooling API (the release the LSP is built on).
|
|
31530
|
-
* This is the base layer the other tiers overlay.
|
|
31531
|
-
*/
|
|
31532
|
-
loadBuiltins() {
|
|
31533
|
-
for (const m of builtinMacros) {
|
|
31534
|
-
this.macros.set(m.name.toLowerCase(), {
|
|
31535
|
-
name: m.name,
|
|
31536
|
-
block: m.block,
|
|
31537
|
-
subMacros: [...m.subMacros],
|
|
31538
|
-
storeVar: m.storeVar,
|
|
31539
|
-
interpolate: m.interpolate,
|
|
31540
|
-
merged: m.merged,
|
|
31541
|
-
source: m.source === "builtin" ? "builtin" : "user",
|
|
31542
|
-
parameterDefs: m.parameters
|
|
31543
|
-
});
|
|
31544
|
-
}
|
|
31545
|
-
}
|
|
31546
|
-
/**
|
|
31547
|
-
* Overlay supplement data onto the registry.
|
|
31548
|
-
* Supplements provide descriptions, parameters, children, parents, skipArgs.
|
|
31549
|
-
* The `container` field maps to `block` on MacroInfo.
|
|
31550
|
-
*/
|
|
31551
|
-
loadSupplements(supplements) {
|
|
31552
|
-
this.mergeEntries(supplements);
|
|
31553
|
-
this.onChange?.();
|
|
31554
|
-
}
|
|
31555
|
-
/**
|
|
31556
|
-
* Overlay user config onto the registry.
|
|
31557
|
-
* Same format as supplements; applied last so it wins.
|
|
31558
|
-
*
|
|
31559
|
-
* Replaces any previously loaded config: entries from the last call are
|
|
31560
|
-
* reverted first, so macros removed from the config disappear (or fall
|
|
31561
|
-
* back to their builtin/supplement definition).
|
|
31562
|
-
*/
|
|
31563
|
-
loadConfig(config2) {
|
|
31564
|
-
config2 = normalizeMacroEntries(config2);
|
|
31565
|
-
for (const [key, previous2] of this.configSnapshot) {
|
|
31566
|
-
if (previous2) {
|
|
31567
|
-
this.macros.set(key, previous2);
|
|
31568
|
-
} else {
|
|
31569
|
-
this.macros.delete(key);
|
|
31570
|
-
}
|
|
31571
|
-
}
|
|
31572
|
-
this.configSnapshot.clear();
|
|
31573
|
-
this.configEntries.clear();
|
|
31574
|
-
for (const rawKey of Object.keys(config2)) {
|
|
31575
|
-
const key = rawKey.toLowerCase();
|
|
31576
|
-
if (!this.configSnapshot.has(key)) {
|
|
31577
|
-
const existing = this.macros.get(key);
|
|
31578
|
-
this.configSnapshot.set(key, existing ? { ...existing } : void 0);
|
|
31579
|
-
}
|
|
31580
|
-
this.configEntries.set(key, { ...this.configEntries.get(key), ...config2[rawKey] });
|
|
31581
|
-
}
|
|
31582
|
-
this.mergeEntries(config2);
|
|
31583
|
-
this.onChange?.();
|
|
31584
|
-
}
|
|
31585
|
-
/**
|
|
31586
|
-
* Replace the set of macros discovered from Story.defineMacro() calls.
|
|
31587
|
-
* Previously discovered definitions that are no longer present are dropped,
|
|
31588
|
-
* restoring whatever the other tiers define for that name.
|
|
31589
|
-
*
|
|
31590
|
-
* Each sub-macro becomes a known macro that may only appear inside its
|
|
31591
|
-
* parent. Whether a macro is a block is the tooling API's reading of the
|
|
31592
|
-
* definition (`discoverMacros`).
|
|
31593
|
-
*/
|
|
31594
|
-
setDiscoveredMacros(macros) {
|
|
31595
|
-
this.discovered.clear();
|
|
31596
|
-
for (const m of macros) {
|
|
31597
|
-
this.discovered.set(m.name.toLowerCase(), {
|
|
31598
|
-
name: m.name,
|
|
31599
|
-
block: m.block,
|
|
31600
|
-
subMacros: m.subMacros,
|
|
31601
|
-
storeVar: m.storeVar,
|
|
31602
|
-
interpolate: m.interpolate,
|
|
31603
|
-
merged: m.merged,
|
|
31604
|
-
description: m.description,
|
|
31605
|
-
source: "user",
|
|
31606
|
-
parameterDefs: m.parameters
|
|
31607
|
-
});
|
|
31608
|
-
}
|
|
31609
|
-
for (const m of [...this.discovered.values()]) {
|
|
31610
|
-
for (const sub of m.subMacros ?? []) {
|
|
31611
|
-
const key = sub.toLowerCase();
|
|
31612
|
-
const existing = this.discovered.get(key);
|
|
31613
|
-
if (existing) {
|
|
31614
|
-
existing.parents = [...existing.parents ?? [], m.name];
|
|
31615
|
-
} else {
|
|
31616
|
-
this.discovered.set(key, { name: sub, block: false, subMacros: [], source: "user", parents: [m.name] });
|
|
31617
|
-
}
|
|
31618
|
-
}
|
|
31619
|
-
}
|
|
31620
|
-
this.onChange?.();
|
|
31621
|
-
}
|
|
31622
|
-
/** Add or replace a single macro entry. */
|
|
31623
|
-
addMacro(info) {
|
|
31624
|
-
const key = info.name.toLowerCase();
|
|
31625
|
-
const existing = this.macros.get(key);
|
|
31626
|
-
this.macros.set(key, {
|
|
31627
|
-
name: info.name,
|
|
31628
|
-
block: info.block ?? existing?.block ?? false,
|
|
31629
|
-
subMacros: info.subMacros ?? existing?.subMacros ?? [],
|
|
31630
|
-
source: info.source ?? existing?.source ?? "user",
|
|
31631
|
-
storeVar: info.storeVar ?? existing?.storeVar,
|
|
31632
|
-
interpolate: info.interpolate ?? existing?.interpolate,
|
|
31633
|
-
merged: info.merged ?? existing?.merged,
|
|
31634
|
-
description: info.description ?? existing?.description,
|
|
31635
|
-
parameters: info.parameters ?? existing?.parameters,
|
|
31636
|
-
parameterDefs: info.parameterDefs ?? existing?.parameterDefs,
|
|
31637
|
-
parameterDocs: info.parameterDocs ?? existing?.parameterDocs,
|
|
31638
|
-
children: info.children ?? existing?.children,
|
|
31639
|
-
parents: info.parents ?? existing?.parents,
|
|
31640
|
-
skipArgs: info.skipArgs ?? existing?.skipArgs
|
|
31641
|
-
});
|
|
31642
|
-
this.onChange?.();
|
|
31643
|
-
}
|
|
31644
|
-
/** Get a macro by name (case-insensitive). */
|
|
31645
|
-
getMacro(name) {
|
|
31646
|
-
return this.resolve(name.toLowerCase());
|
|
31647
|
-
}
|
|
31648
|
-
/** A project's explicit schema takes precedence over runtime signature metadata. */
|
|
31649
|
-
hasConfiguredParameters(name) {
|
|
31650
|
-
return this.configEntries.get(name.toLowerCase())?.parameters !== void 0;
|
|
31651
|
-
}
|
|
31652
|
-
/** Check if a macro is a block (container) macro. */
|
|
31653
|
-
isBlock(name) {
|
|
31654
|
-
return this.getMacro(name)?.block ?? false;
|
|
31655
|
-
}
|
|
31656
|
-
/** Check if a macro is a sub-macro (has parents). */
|
|
31657
|
-
isSubMacro(name) {
|
|
31658
|
-
const info = this.getMacro(name);
|
|
31659
|
-
return info?.parents != null && info.parents.length > 0;
|
|
31660
|
-
}
|
|
31661
|
-
/** The macros as the tooling API reads them (`passagePieces`, `validateStoryMarkup`). */
|
|
31662
|
-
toolingMacros() {
|
|
31663
|
-
return this.getAllMacros().map((m) => ({
|
|
31664
|
-
name: m.name,
|
|
31665
|
-
block: m.block,
|
|
31666
|
-
subMacros: m.subMacros,
|
|
31667
|
-
storeVar: m.storeVar,
|
|
31668
|
-
interpolate: m.interpolate,
|
|
31669
|
-
parameters: m.parameterDefs
|
|
31670
|
-
}));
|
|
31671
|
-
}
|
|
31672
|
-
/** Get all registered macros. */
|
|
31673
|
-
getAllMacros() {
|
|
31674
|
-
const keys = /* @__PURE__ */ new Set([...this.macros.keys(), ...this.discovered.keys()]);
|
|
31675
|
-
return Array.from(keys, (key) => this.resolve(key));
|
|
31676
|
-
}
|
|
31677
|
-
/**
|
|
31678
|
-
* Compose the effective entry for a lowercase key: builtins/supplements/
|
|
31679
|
-
* config (stored in `macros`), overlaid by discovered metadata, with user
|
|
31680
|
-
* config re-applied on top so it keeps precedence.
|
|
31681
|
-
*/
|
|
31682
|
-
resolve(key) {
|
|
31683
|
-
const base = this.macros.get(key);
|
|
31684
|
-
const found = this.discovered.get(key);
|
|
31685
|
-
if (!found) return base;
|
|
31686
|
-
const info = {
|
|
31687
|
-
name: found.name,
|
|
31688
|
-
block: found.block ?? false,
|
|
31689
|
-
subMacros: found.subMacros ?? [],
|
|
31690
|
-
source: "user",
|
|
31691
|
-
storeVar: found.storeVar,
|
|
31692
|
-
interpolate: found.interpolate,
|
|
31693
|
-
merged: found.merged,
|
|
31694
|
-
description: found.description,
|
|
31695
|
-
// A definition replaces the macro it names, parameters included
|
|
31696
|
-
parameterDefs: found.parameterDefs,
|
|
31697
|
-
parents: found.parents
|
|
31698
|
-
};
|
|
31699
|
-
const config2 = this.configEntries.get(key);
|
|
31700
|
-
if (config2) applySupplement(info, config2);
|
|
31701
|
-
return info;
|
|
31702
|
-
}
|
|
31703
|
-
/** Merge a set of supplement/config entries into the registry. */
|
|
31704
|
-
mergeEntries(entries) {
|
|
31705
|
-
for (const [rawKey, entry] of Object.entries(entries)) {
|
|
31706
|
-
const key = rawKey.toLowerCase();
|
|
31707
|
-
const existing = this.macros.get(key);
|
|
31708
|
-
if (existing) {
|
|
31709
|
-
applySupplement(existing, entry);
|
|
31710
|
-
} else {
|
|
31711
|
-
this.macros.set(key, {
|
|
31712
|
-
name: entry.name ?? rawKey,
|
|
31713
|
-
block: entry.container ?? false,
|
|
31714
|
-
subMacros: [],
|
|
31715
|
-
source: "user",
|
|
31716
|
-
description: entry.description,
|
|
31717
|
-
parameters: entry.parameters,
|
|
31718
|
-
parameterDocs: entry.parameterDocs,
|
|
31719
|
-
children: entry.children,
|
|
31720
|
-
parents: entry.parents,
|
|
31721
|
-
skipArgs: entry.skipArgs
|
|
31722
|
-
});
|
|
31723
|
-
}
|
|
31724
|
-
}
|
|
31725
|
-
}
|
|
31726
|
-
};
|
|
31727
31537
|
}
|
|
31728
31538
|
});
|
|
31729
31539
|
|
|
31730
|
-
// src/core/parsing/
|
|
31731
|
-
function
|
|
31732
|
-
|
|
31733
|
-
return variableReferences(markup.content, markup.doc.context.macros, { all: true }).map((ref2) => ({
|
|
31734
|
-
sigil: ref2.sigil,
|
|
31735
|
-
name: ref2.name,
|
|
31736
|
-
path: [ref2.name, ...ref2.path].join("."),
|
|
31737
|
-
start: ref2.start,
|
|
31738
|
-
end: ref2.end
|
|
31739
|
-
}));
|
|
31540
|
+
// src/core/parsing/parameter-validator.ts
|
|
31541
|
+
function isAlwaysArg(arg) {
|
|
31542
|
+
return arg.type === 4 /* Variable */ || arg.type === 7 /* Expression */;
|
|
31740
31543
|
}
|
|
31741
|
-
|
|
31742
|
-
|
|
31743
|
-
|
|
31744
|
-
init_tooling();
|
|
31544
|
+
function findParameterType(name) {
|
|
31545
|
+
for (const pt2 of parameterTypes) {
|
|
31546
|
+
if (pt2.name.includes(name)) return pt2;
|
|
31745
31547
|
}
|
|
31746
|
-
|
|
31747
|
-
|
|
31748
|
-
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
|
|
31752
|
-
|
|
31753
|
-
|
|
31754
|
-
|
|
31755
|
-
|
|
31756
|
-
|
|
31757
|
-
|
|
31758
|
-
|
|
31759
|
-
|
|
31760
|
-
|
|
31761
|
-
|
|
31762
|
-
|
|
31763
|
-
|
|
31764
|
-
|
|
31765
|
-
|
|
31766
|
-
let content2 = "";
|
|
31767
|
-
for (let i2 = 0; i2 < raw.length; i2++) {
|
|
31768
|
-
if (raw[i2] === "\r" && raw[i2 + 1] === "\n") continue;
|
|
31769
|
-
map2.push(bodyStart + i2);
|
|
31770
|
-
content2 += raw[i2];
|
|
31771
|
-
}
|
|
31772
|
-
map2.push(bodyEnd);
|
|
31773
|
-
this.content = content2;
|
|
31774
|
-
this.toDoc = Int32Array.from(map2);
|
|
31775
|
-
} else {
|
|
31776
|
-
this.content = raw;
|
|
31777
|
-
this.toDoc = null;
|
|
31778
|
-
}
|
|
31779
|
-
}
|
|
31780
|
-
doc;
|
|
31781
|
-
passage;
|
|
31782
|
-
bodyStart;
|
|
31783
|
-
bodyEnd;
|
|
31784
|
-
/** The body, LF-normalized: the text the tooling API analyzes. */
|
|
31785
|
-
content;
|
|
31786
|
-
/** Whether Spindle reads the body as story markup (see isMarkupPassage). */
|
|
31787
|
-
isMarkup;
|
|
31788
|
-
/** For each `content` offset, its offset in the document; null when the body has no CRLF. */
|
|
31789
|
-
toDoc;
|
|
31790
|
-
cachedTokens;
|
|
31791
|
-
cachedPairing;
|
|
31792
|
-
cachedPieces;
|
|
31793
|
-
cachedDeclarations;
|
|
31794
|
-
/** The document offset of `contentOffset`. */
|
|
31795
|
-
docOffset(contentOffset) {
|
|
31796
|
-
if (!this.toDoc) return this.bodyStart + contentOffset;
|
|
31797
|
-
return this.toDoc[Math.min(Math.max(contentOffset, 0), this.toDoc.length - 1)];
|
|
31798
|
-
}
|
|
31799
|
-
/**
|
|
31800
|
-
* The document offset just past the character before `contentEnd`: where an
|
|
31801
|
-
* exclusive end maps to. (`docOffset()` of an end that sits before a line
|
|
31802
|
-
* break would land behind the `\r` of a CRLF.)
|
|
31803
|
-
*/
|
|
31804
|
-
docEnd(contentEnd) {
|
|
31805
|
-
return contentEnd <= 0 ? this.docOffset(0) : this.docOffset(contentEnd - 1) + 1;
|
|
31806
|
-
}
|
|
31807
|
-
/** The `content` offset of a document offset (the nearest one at or after it). */
|
|
31808
|
-
contentOffset(docOffset) {
|
|
31809
|
-
if (!this.toDoc) return Math.min(Math.max(docOffset - this.bodyStart, 0), this.content.length);
|
|
31810
|
-
let low = 0;
|
|
31811
|
-
let high = this.toDoc.length - 1;
|
|
31812
|
-
while (low < high) {
|
|
31813
|
-
const mid = low + high >> 1;
|
|
31814
|
-
if (this.toDoc[mid] < docOffset) low = mid + 1;
|
|
31815
|
-
else high = mid;
|
|
31816
|
-
}
|
|
31817
|
-
return low;
|
|
31818
|
-
}
|
|
31819
|
-
/** The document position of `contentOffset`. */
|
|
31820
|
-
position(contentOffset) {
|
|
31821
|
-
return offsetToPosition(this.docOffset(contentOffset), this.doc.lineStarts);
|
|
31822
|
-
}
|
|
31823
|
-
/** The document range of `[start, end)` in `content` offsets. */
|
|
31824
|
-
range(start, end) {
|
|
31825
|
-
return offsetsToRange(this.docOffset(start), end > start ? this.docEnd(end) : this.docOffset(start), this.doc.lineStarts);
|
|
31826
|
-
}
|
|
31827
|
-
/** The flat tokens and the malformed tags; none for a passage that is not markup. */
|
|
31828
|
-
get tokenization() {
|
|
31829
|
-
return this.cachedTokens ??= this.isMarkup ? tokenizeMarkupTolerant(this.content) : { tokens: [], errors: [] };
|
|
31830
|
-
}
|
|
31831
|
-
get tokens() {
|
|
31832
|
-
return this.tokenization.tokens;
|
|
31833
|
-
}
|
|
31834
|
-
/** The tree of paired tokens, and what does not pair. */
|
|
31835
|
-
get pairing() {
|
|
31836
|
-
return this.cachedPairing ??= pairMarkup(this.tokens, {
|
|
31837
|
-
isBlock: (name) => this.doc.context.isBlock(name),
|
|
31838
|
-
source: this.content
|
|
31839
|
-
});
|
|
31840
|
-
}
|
|
31841
|
-
/** What the markup runs and names, and where (see `passagePieces`). */
|
|
31842
|
-
get pieces() {
|
|
31843
|
-
return this.cachedPieces ??= this.isMarkup ? passagePieces(this.content, this.doc.context.macros) : [];
|
|
31844
|
-
}
|
|
31845
|
-
/** The declarations of a StoryVariables / StoryTransients passage. */
|
|
31846
|
-
get declarations() {
|
|
31847
|
-
return this.cachedDeclarations ??= parseDeclarations(this.content, this.passage.name === "StoryTransients" ? "%" : "$");
|
|
31548
|
+
return null;
|
|
31549
|
+
}
|
|
31550
|
+
function lexFormat(formatString) {
|
|
31551
|
+
let pos = 0;
|
|
31552
|
+
const lexed = [];
|
|
31553
|
+
let iter = 0;
|
|
31554
|
+
for (; ; ) {
|
|
31555
|
+
if (++iter > LEX_LIMIT) throw new Error("Format lex iteration limit exceeded");
|
|
31556
|
+
if (pos >= formatString.length) break;
|
|
31557
|
+
const ch = formatString[pos];
|
|
31558
|
+
if (wsRe.test(ch)) {
|
|
31559
|
+
pos++;
|
|
31560
|
+
} else if (ch === "&") {
|
|
31561
|
+
pos++;
|
|
31562
|
+
const next = formatString[pos];
|
|
31563
|
+
if (next === "+") {
|
|
31564
|
+
pos++;
|
|
31565
|
+
lexed.push({ kind: 3 /* AndNext */ });
|
|
31566
|
+
} else {
|
|
31567
|
+
throw new Error(`Lex: Expected '+' after '&', found '${next ?? "EOF"}'`);
|
|
31848
31568
|
}
|
|
31849
|
-
}
|
|
31850
|
-
|
|
31851
|
-
|
|
31852
|
-
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
|
|
31859
|
-
|
|
31860
|
-
|
|
31861
|
-
});
|
|
31569
|
+
} else if (ch === "|") {
|
|
31570
|
+
pos++;
|
|
31571
|
+
const next = formatString[pos];
|
|
31572
|
+
if (next === "+") {
|
|
31573
|
+
pos++;
|
|
31574
|
+
lexed.push({ kind: 4 /* MaybeNext */ });
|
|
31575
|
+
} else if (next === void 0) {
|
|
31576
|
+
throw new Error("Lex: Expected content after '|'");
|
|
31577
|
+
} else if (wsRe.test(next)) {
|
|
31578
|
+
pos++;
|
|
31579
|
+
lexed.push({ kind: 5 /* Or */ });
|
|
31580
|
+
} else if (identStartRe.test(next) || quoteRe.test(next)) {
|
|
31581
|
+
lexed.push({ kind: 5 /* Or */ });
|
|
31582
|
+
} else {
|
|
31583
|
+
throw new Error(`Lex: Invalid character after '|': '${next}'`);
|
|
31862
31584
|
}
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
|
|
31867
|
-
|
|
31868
|
-
|
|
31869
|
-
|
|
31870
|
-
|
|
31871
|
-
|
|
31585
|
+
} else if (quoteRe.test(ch)) {
|
|
31586
|
+
const quoted = readQuoted(formatString, pos);
|
|
31587
|
+
if (!quoted) throw new Error("Lex: Unterminated string literal");
|
|
31588
|
+
pos = quoted.end;
|
|
31589
|
+
lexed.push({ kind: 1 /* Literal */, value: quoted.value });
|
|
31590
|
+
} else if (ch === "(" || ch === ")") {
|
|
31591
|
+
pos++;
|
|
31592
|
+
lexed.push({ kind: 2 /* Group */, open: ch === "(" });
|
|
31593
|
+
} else if (identStartRe.test(ch)) {
|
|
31594
|
+
const start = pos;
|
|
31595
|
+
pos++;
|
|
31596
|
+
while (pos < formatString.length) {
|
|
31597
|
+
if (++iter > LEX_LIMIT) throw new Error("Format lex identifier iteration limit exceeded");
|
|
31598
|
+
const c = formatString[pos];
|
|
31599
|
+
if (identStopRe.test(c) || c === void 0) break;
|
|
31600
|
+
if (identStartRe.test(c)) {
|
|
31601
|
+
pos++;
|
|
31602
|
+
} else if (digitRe.test(c)) {
|
|
31603
|
+
throw new Error(`Lex: Digit '${c}' not allowed in parameter type name`);
|
|
31604
|
+
} else {
|
|
31605
|
+
throw new Error(`Lex: Invalid character '${c}' in parameter type name`);
|
|
31872
31606
|
}
|
|
31873
|
-
return void 0;
|
|
31874
31607
|
}
|
|
31875
|
-
|
|
31876
|
-
|
|
31877
|
-
});
|
|
31878
|
-
|
|
31879
|
-
|
|
31880
|
-
|
|
31881
|
-
|
|
31882
|
-
|
|
31883
|
-
|
|
31884
|
-
const text3 = (header === -1 ? lines : lines.slice(0, header)).join("\n");
|
|
31885
|
-
const { declarations, errors } = parseDeclarations(text3.replace(/\r/g, " "), sigil);
|
|
31886
|
-
const problems = [];
|
|
31887
|
-
for (const error48 of errors) {
|
|
31888
|
-
if (error48.code === "duplicate-declaration") continue;
|
|
31889
|
-
const [start, end] = lineSpan(text3, error48.offset);
|
|
31890
|
-
if (isUnjudged(text3.slice(start, end))) continue;
|
|
31891
|
-
let message = `${passage}: ${error48.message}`;
|
|
31892
|
-
if (error48.code === "invalid-declaration" && COMMENT_START.test(text3.slice(error48.offset, error48.end))) {
|
|
31893
|
-
message += `. ${passage} has no comment syntax.`;
|
|
31894
|
-
} else if (!message.endsWith(".")) {
|
|
31895
|
-
message += ".";
|
|
31608
|
+
const ident = formatString.slice(start, pos);
|
|
31609
|
+
const pt2 = findParameterType(ident);
|
|
31610
|
+
if (pt2 === null) throw new Error(`Lex: Unknown parameter type '${ident}'`);
|
|
31611
|
+
lexed.push({ kind: 0 /* Type */, type: pt2 });
|
|
31612
|
+
} else if (ch === "." && formatString[pos + 1] === "." && formatString[pos + 2] === ".") {
|
|
31613
|
+
pos += 3;
|
|
31614
|
+
lexed.push({ kind: 6 /* Repeat */ });
|
|
31615
|
+
} else {
|
|
31616
|
+
throw new Error(`Lex: Unexpected character '${ch}'`);
|
|
31896
31617
|
}
|
|
31897
|
-
problems.push({ message, start, end });
|
|
31898
31618
|
}
|
|
31899
|
-
return
|
|
31900
|
-
}
|
|
31901
|
-
function isUnjudged(line) {
|
|
31902
|
-
return line.includes("\r") || line.includes("\xA0");
|
|
31903
|
-
}
|
|
31904
|
-
function lineSpan(text3, offset2) {
|
|
31905
|
-
const lineStart = text3.lastIndexOf("\n", offset2 - 1) + 1;
|
|
31906
|
-
const lineEnd = text3.indexOf("\n", offset2);
|
|
31907
|
-
const line = text3.slice(lineStart, lineEnd === -1 ? void 0 : lineEnd);
|
|
31908
|
-
const start = lineStart + line.length - line.trimStart().length;
|
|
31909
|
-
return [start, lineStart + line.trimEnd().length];
|
|
31619
|
+
return lexed;
|
|
31910
31620
|
}
|
|
31911
|
-
|
|
31912
|
-
|
|
31913
|
-
|
|
31914
|
-
|
|
31915
|
-
|
|
31916
|
-
|
|
31917
|
-
|
|
31621
|
+
function parseFormat(formatString) {
|
|
31622
|
+
const tokens2 = lexFormat(formatString);
|
|
31623
|
+
if (tokens2.length === 0) return null;
|
|
31624
|
+
let index = 0;
|
|
31625
|
+
let iter = 0;
|
|
31626
|
+
function infixBp(kind) {
|
|
31627
|
+
switch (kind) {
|
|
31628
|
+
case 5 /* Or */:
|
|
31629
|
+
return [9, 10];
|
|
31630
|
+
case 3 /* AndNext */:
|
|
31631
|
+
return [4, 5];
|
|
31632
|
+
case 4 /* MaybeNext */:
|
|
31633
|
+
return [2, 3];
|
|
31634
|
+
default:
|
|
31635
|
+
return null;
|
|
31636
|
+
}
|
|
31918
31637
|
}
|
|
31919
|
-
|
|
31920
|
-
|
|
31921
|
-
|
|
31922
|
-
|
|
31923
|
-
|
|
31924
|
-
|
|
31925
|
-
|
|
31638
|
+
function prefixBp(kind) {
|
|
31639
|
+
switch (kind) {
|
|
31640
|
+
case 4 /* MaybeNext */:
|
|
31641
|
+
return 3;
|
|
31642
|
+
case 6 /* Repeat */:
|
|
31643
|
+
return 8;
|
|
31644
|
+
default:
|
|
31645
|
+
throw new Error(`Parse: Unexpected prefix operator kind ${kind}`);
|
|
31646
|
+
}
|
|
31647
|
+
}
|
|
31648
|
+
function isPrefixOp(t) {
|
|
31649
|
+
return t.kind === 4 /* MaybeNext */ || t.kind === 6 /* Repeat */;
|
|
31650
|
+
}
|
|
31651
|
+
function isValue(t) {
|
|
31652
|
+
return t.kind === 0 /* Type */ || t.kind === 1 /* Literal */;
|
|
31653
|
+
}
|
|
31654
|
+
function exprBp(minBp) {
|
|
31655
|
+
if (++iter > PARSE_LIMIT) throw new Error("Format parse iteration limit exceeded");
|
|
31656
|
+
const cur = tokens2[index];
|
|
31657
|
+
if (!cur) throw new Error("Parse: Unexpected end of format string");
|
|
31658
|
+
index++;
|
|
31659
|
+
let lhs;
|
|
31660
|
+
if (isPrefixOp(cur)) {
|
|
31661
|
+
const rBp = prefixBp(cur.kind);
|
|
31662
|
+
const rhs = exprBp(rBp);
|
|
31663
|
+
lhs = { kind: cur.kind, right: rhs };
|
|
31664
|
+
} else if (cur.kind === 2 /* Group */) {
|
|
31665
|
+
const g = cur;
|
|
31666
|
+
if (!g.open) throw new Error("Parse: Unexpected closing parenthesis");
|
|
31667
|
+
lhs = exprBp(0);
|
|
31668
|
+
const closing = tokens2[index];
|
|
31669
|
+
if (!closing || closing.kind !== 2 /* Group */ || closing.open) {
|
|
31670
|
+
throw new Error("Parse: Expected closing parenthesis");
|
|
31671
|
+
}
|
|
31672
|
+
index++;
|
|
31673
|
+
} else if (isValue(cur)) {
|
|
31674
|
+
lhs = cur;
|
|
31675
|
+
} else {
|
|
31676
|
+
throw new Error("Parse: Expected value or prefix operator");
|
|
31677
|
+
}
|
|
31678
|
+
for (; ; ) {
|
|
31679
|
+
if (++iter > PARSE_LIMIT) throw new Error("Format parse operator iteration limit exceeded");
|
|
31680
|
+
const op = tokens2[index];
|
|
31681
|
+
if (!op) break;
|
|
31682
|
+
const bp = infixBp(op.kind);
|
|
31683
|
+
if (bp === null) break;
|
|
31684
|
+
const [lBp, rBp] = bp;
|
|
31685
|
+
if (lBp < minBp) break;
|
|
31686
|
+
index++;
|
|
31687
|
+
const rhs = exprBp(rBp);
|
|
31688
|
+
lhs = { kind: op.kind, left: lhs, right: rhs };
|
|
31689
|
+
}
|
|
31690
|
+
return lhs;
|
|
31691
|
+
}
|
|
31692
|
+
return exprBp(0);
|
|
31926
31693
|
}
|
|
31927
|
-
function
|
|
31928
|
-
|
|
31929
|
-
|
|
31930
|
-
|
|
31931
|
-
|
|
31694
|
+
function slotAccepts(leaf) {
|
|
31695
|
+
return (arg) => crawlValidate(leaf, [arg], 0).status === 2 /* Success */;
|
|
31696
|
+
}
|
|
31697
|
+
function describeFormat(format) {
|
|
31698
|
+
const cap = (list3) => list3.slice(0, DESCRIBE_ALTERNATIVE_LIMIT);
|
|
31699
|
+
const join5 = (lefts, rights) => cap(lefts.flatMap((l) => rights.map((r) => [...l, ...r])));
|
|
31700
|
+
switch (format.kind) {
|
|
31701
|
+
case 0 /* Type */:
|
|
31702
|
+
return [[{ label: format.type.name[0], optional: false, repeat: false, accepts: slotAccepts(format) }]];
|
|
31703
|
+
case 1 /* Literal */:
|
|
31704
|
+
return [[{ label: `'${format.value}'`, optional: false, repeat: false, accepts: slotAccepts(format) }]];
|
|
31705
|
+
case 3 /* AndNext */:
|
|
31706
|
+
return join5(describeFormat(format.left), describeFormat(format.right));
|
|
31707
|
+
case 4 /* MaybeNext */: {
|
|
31708
|
+
const rights = describeFormat(format.right).map(
|
|
31709
|
+
(seq) => seq.map((slot) => ({ ...slot, optional: true }))
|
|
31710
|
+
);
|
|
31711
|
+
return join5(format.left ? describeFormat(format.left) : [[]], rights);
|
|
31932
31712
|
}
|
|
31713
|
+
case 5 /* Or */:
|
|
31714
|
+
return cap([...describeFormat(format.left), ...describeFormat(format.right)]);
|
|
31715
|
+
case 6 /* Repeat */:
|
|
31716
|
+
return describeFormat(format.right).map(
|
|
31717
|
+
(seq) => seq.map((slot) => ({ ...slot, optional: true, repeat: true }))
|
|
31718
|
+
);
|
|
31933
31719
|
}
|
|
31934
|
-
return results;
|
|
31935
31720
|
}
|
|
31936
|
-
|
|
31937
|
-
|
|
31938
|
-
|
|
31721
|
+
function passageSlots(format) {
|
|
31722
|
+
const single = (passage) => ({ slots: [passage], variable: false, unsafe: false });
|
|
31723
|
+
switch (format.kind) {
|
|
31724
|
+
case 0 /* Type */:
|
|
31725
|
+
return single(format.type.name.includes("passage"));
|
|
31726
|
+
case 1 /* Literal */:
|
|
31727
|
+
return single(false);
|
|
31728
|
+
case 6 /* Repeat */: {
|
|
31729
|
+
const child = passageSlots(format.right);
|
|
31730
|
+
return { slots: [], variable: true, unsafe: child.unsafe || child.slots.includes(true), restFrom: 0 };
|
|
31731
|
+
}
|
|
31732
|
+
case 5 /* Or */:
|
|
31733
|
+
return mergePassageSlots(passageSlots(format.left), passageSlots(format.right));
|
|
31734
|
+
case 3 /* AndNext */:
|
|
31735
|
+
case 4 /* MaybeNext */: {
|
|
31736
|
+
const left = format.left ? passageSlots(format.left) : { slots: [], variable: false, unsafe: false };
|
|
31737
|
+
const right = passageSlots(format.right);
|
|
31738
|
+
return {
|
|
31739
|
+
slots: [...left.slots, ...right.slots],
|
|
31740
|
+
variable: left.variable || right.variable || format.kind === 4 /* MaybeNext */,
|
|
31741
|
+
restFrom: left.restFrom ?? (right.restFrom === void 0 ? void 0 : left.slots.length + right.restFrom),
|
|
31742
|
+
// A variable-width prefix can move every position that follows it.
|
|
31743
|
+
// Refuse rather than attributing an ordinary string to a passage.
|
|
31744
|
+
unsafe: left.unsafe || right.unsafe || left.variable && right.slots.length > 0 && (left.slots.includes(true) || right.slots.includes(true))
|
|
31745
|
+
};
|
|
31746
|
+
}
|
|
31747
|
+
}
|
|
31748
|
+
}
|
|
31749
|
+
function mergePassageSlots(left, right) {
|
|
31750
|
+
const repeatedConflict = (repeated, other) => repeated.restFrom !== void 0 && other.slots.some((passage, i2) => passage && i2 >= repeated.restFrom);
|
|
31751
|
+
return {
|
|
31752
|
+
slots: Array.from({ length: Math.max(left.slots.length, right.slots.length) }, (_2, i2) => left.slots[i2] === true || right.slots[i2] === true),
|
|
31753
|
+
variable: left.variable || right.variable || left.slots.length !== right.slots.length,
|
|
31754
|
+
restFrom: left.restFrom === void 0 ? right.restFrom : right.restFrom === void 0 ? left.restFrom : Math.min(left.restFrom, right.restFrom),
|
|
31755
|
+
unsafe: left.unsafe || right.unsafe || left.slots.some((passage, i2) => right.slots[i2] !== void 0 && passage !== right.slots[i2]) || repeatedConflict(left, right) || repeatedConflict(right, left)
|
|
31756
|
+
};
|
|
31757
|
+
}
|
|
31758
|
+
function makeError(status, message, argIndex, rank = 0) {
|
|
31759
|
+
return {
|
|
31760
|
+
status,
|
|
31761
|
+
rank,
|
|
31762
|
+
argIndex,
|
|
31763
|
+
errors: [{ message, argIndex }],
|
|
31764
|
+
warnings: []
|
|
31765
|
+
};
|
|
31766
|
+
}
|
|
31767
|
+
function makeSuccess(argIndex, rank, warnings = []) {
|
|
31768
|
+
return { status: 2 /* Success */, rank, argIndex, errors: [], warnings };
|
|
31769
|
+
}
|
|
31770
|
+
function isFail(s) {
|
|
31771
|
+
return s === 1 /* Failure */ || s === 0 /* NotFound */;
|
|
31772
|
+
}
|
|
31773
|
+
function crawlValidate(format, args2, startIndex, state) {
|
|
31774
|
+
const memo2 = /* @__PURE__ */ new Map();
|
|
31775
|
+
function append(left, right) {
|
|
31776
|
+
return { ...right, rank: left.rank + right.rank, warnings: left.warnings.concat(right.warnings) };
|
|
31777
|
+
}
|
|
31778
|
+
function follow(left, right) {
|
|
31779
|
+
return left.flatMap((matched) => isFail(matched.status) ? [matched] : crawl(right, matched.argIndex).map((result2) => append(matched, result2)));
|
|
31780
|
+
}
|
|
31781
|
+
function crawl(fmt, argIdx) {
|
|
31782
|
+
let positions = memo2.get(fmt);
|
|
31783
|
+
if (!positions) memo2.set(fmt, positions = /* @__PURE__ */ new Map());
|
|
31784
|
+
const cached2 = positions.get(argIdx);
|
|
31785
|
+
if (cached2) return cached2;
|
|
31786
|
+
if (positions.size >= VALIDATE_LIMIT) {
|
|
31787
|
+
throw new Error(`Validation iteration limit exceeded at argument ${argIdx}`);
|
|
31788
|
+
}
|
|
31789
|
+
const unique = /* @__PURE__ */ new Map();
|
|
31790
|
+
for (const result2 of match2(fmt, argIdx)) {
|
|
31791
|
+
const key = isFail(result2.status) ? "failure" : result2.argIndex;
|
|
31792
|
+
const previous2 = unique.get(key);
|
|
31793
|
+
if (!previous2 || key === "failure" && result2.rank > previous2.rank) unique.set(key, result2);
|
|
31794
|
+
}
|
|
31795
|
+
const results2 = [...unique.values()];
|
|
31796
|
+
positions.set(argIdx, results2);
|
|
31797
|
+
return results2;
|
|
31798
|
+
}
|
|
31799
|
+
function match2(fmt, argIdx) {
|
|
31800
|
+
switch (fmt.kind) {
|
|
31801
|
+
case 4 /* MaybeNext */: {
|
|
31802
|
+
const left = fmt.left ? crawl(fmt.left, argIdx) : [makeSuccess(argIdx, 0)];
|
|
31803
|
+
return [...follow(left, fmt.right), ...left];
|
|
31804
|
+
}
|
|
31805
|
+
case 3 /* AndNext */: {
|
|
31806
|
+
return follow(crawl(fmt.left, argIdx), fmt.right);
|
|
31807
|
+
}
|
|
31808
|
+
case 5 /* Or */: {
|
|
31809
|
+
return [...crawl(fmt.left, argIdx), ...crawl(fmt.right, argIdx)];
|
|
31810
|
+
}
|
|
31811
|
+
case 6 /* Repeat */: {
|
|
31812
|
+
const matches = [];
|
|
31813
|
+
const pending = [makeSuccess(argIdx, 0)];
|
|
31814
|
+
const reached = /* @__PURE__ */ new Set();
|
|
31815
|
+
while (pending.length > 0) {
|
|
31816
|
+
const matched = pending.pop();
|
|
31817
|
+
if (reached.has(matched.argIndex)) continue;
|
|
31818
|
+
reached.add(matched.argIndex);
|
|
31819
|
+
matches.push(matched);
|
|
31820
|
+
const items = crawl(fmt.right, matched.argIndex);
|
|
31821
|
+
for (let i2 = items.length - 1; i2 >= 0; i2--) {
|
|
31822
|
+
const item = items[i2];
|
|
31823
|
+
if (!isFail(item.status) && !reached.has(item.argIndex)) pending.push(append(matched, item));
|
|
31824
|
+
}
|
|
31825
|
+
}
|
|
31826
|
+
return matches.sort((left, right) => right.argIndex - left.argIndex);
|
|
31827
|
+
}
|
|
31828
|
+
case 1 /* Literal */: {
|
|
31829
|
+
const arg = args2[argIdx];
|
|
31830
|
+
if (arg === void 0) {
|
|
31831
|
+
return [makeError(0 /* NotFound */, `Expected literal '${fmt.value}' but no argument found`, argIdx)];
|
|
31832
|
+
}
|
|
31833
|
+
if (isAlwaysArg(arg)) {
|
|
31834
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
31835
|
+
}
|
|
31836
|
+
const argText = arg.type === 0 /* String */ ? readQuoted(arg.text, 0)?.value : arg.text;
|
|
31837
|
+
if (argText === fmt.value) {
|
|
31838
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
31839
|
+
}
|
|
31840
|
+
if (fmt.value === "null" && arg.type === 3 /* Null */ || fmt.value === "undefined" && arg.type === 10 /* Undefined */ || fmt.value === "true" && arg.type === 2 /* Boolean */ && arg.text === "true" || fmt.value === "false" && arg.type === 2 /* Boolean */ && arg.text === "false" || fmt.value === "NaN" && arg.type === 9 /* NaN */) {
|
|
31841
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
31842
|
+
}
|
|
31843
|
+
return [makeError(1 /* Failure */, `Expected literal '${fmt.value}'`, argIdx, CORRECT_TYPE_RANK)];
|
|
31844
|
+
}
|
|
31845
|
+
case 0 /* Type */: {
|
|
31846
|
+
const arg = args2[argIdx];
|
|
31847
|
+
const typeDef = fmt.type;
|
|
31848
|
+
if (arg === void 0) {
|
|
31849
|
+
return [makeError(
|
|
31850
|
+
0 /* NotFound */,
|
|
31851
|
+
`Expected type '${typeDef.name[0]}' but no argument found`,
|
|
31852
|
+
argIdx
|
|
31853
|
+
)];
|
|
31854
|
+
}
|
|
31855
|
+
if (isAlwaysArg(arg)) {
|
|
31856
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
31857
|
+
}
|
|
31858
|
+
const result2 = typeDef.validate(arg, args2, argIdx, state);
|
|
31859
|
+
if (result2 instanceof TypeError2) {
|
|
31860
|
+
return [{
|
|
31861
|
+
status: 1 /* Failure */,
|
|
31862
|
+
rank: 0,
|
|
31863
|
+
argIndex: argIdx,
|
|
31864
|
+
errors: [{ message: result2.message, argIndex: argIdx }],
|
|
31865
|
+
warnings: []
|
|
31866
|
+
}];
|
|
31867
|
+
}
|
|
31868
|
+
if (result2 instanceof TypeWarning) {
|
|
31869
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK, [
|
|
31870
|
+
{ message: result2.message, argIndex: argIdx }
|
|
31871
|
+
])];
|
|
31872
|
+
}
|
|
31873
|
+
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
31874
|
+
}
|
|
31875
|
+
}
|
|
31876
|
+
}
|
|
31877
|
+
const results = crawl(format, startIndex);
|
|
31878
|
+
return results.find((result2) => !isFail(result2.status) && result2.argIndex === args2.length) ?? results.reduce((best, result2) => result2.rank > best.rank ? result2 : best);
|
|
31879
|
+
}
|
|
31880
|
+
var TypeError2, TypeWarning, parameterTypes, LEX_LIMIT, wsRe, identStartRe, identStopRe, quoteRe, digitRe, PARSE_LIMIT, DESCRIBE_ALTERNATIVE_LIMIT, VALIDATE_LIMIT, CORRECT_TYPE_RANK, CORRECT_RANK, Parameters;
|
|
31881
|
+
var init_parameter_validator = __esm({
|
|
31882
|
+
"src/core/parsing/parameter-validator.ts"() {
|
|
31939
31883
|
"use strict";
|
|
31940
31884
|
init_tooling();
|
|
31941
|
-
|
|
31942
|
-
|
|
31943
|
-
|
|
31944
|
-
|
|
31945
|
-
init_declaration_check();
|
|
31946
|
-
BUILTIN_CONTEXT = { macros: builtinMacros, isBlock: isBlockMacro };
|
|
31947
|
-
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
31948
|
-
...Object.getOwnPropertyNames(Array.prototype),
|
|
31949
|
-
...Object.getOwnPropertyNames(Object.prototype),
|
|
31950
|
-
"at",
|
|
31951
|
-
"flat",
|
|
31952
|
-
"flatMap",
|
|
31953
|
-
"includes",
|
|
31954
|
-
"findLast",
|
|
31955
|
-
"findLastIndex",
|
|
31956
|
-
"toReversed",
|
|
31957
|
-
"toSorted",
|
|
31958
|
-
"toSpliced",
|
|
31959
|
-
"with"
|
|
31960
|
-
]);
|
|
31961
|
-
DeclarationSet = class {
|
|
31962
|
-
constructor(sigil) {
|
|
31963
|
-
this.sigil = sigil;
|
|
31885
|
+
init_argument_lexer();
|
|
31886
|
+
TypeError2 = class {
|
|
31887
|
+
constructor(message) {
|
|
31888
|
+
this.message = message;
|
|
31964
31889
|
}
|
|
31965
|
-
|
|
31966
|
-
|
|
31967
|
-
|
|
31968
|
-
|
|
31969
|
-
|
|
31970
|
-
/** Lines Spindle rejects. */
|
|
31971
|
-
problems = [];
|
|
31972
|
-
/** The names declared, with the range of sigil and name of the first. */
|
|
31973
|
-
names = /* @__PURE__ */ new Map();
|
|
31974
|
-
clear() {
|
|
31975
|
-
this.present = false;
|
|
31976
|
-
this.declared.clear();
|
|
31977
|
-
this.occurrences.length = 0;
|
|
31978
|
-
this.problems = [];
|
|
31979
|
-
this.names.clear();
|
|
31890
|
+
message;
|
|
31891
|
+
};
|
|
31892
|
+
TypeWarning = class {
|
|
31893
|
+
constructor(message) {
|
|
31894
|
+
this.message = message;
|
|
31980
31895
|
}
|
|
31981
|
-
|
|
31982
|
-
|
|
31983
|
-
|
|
31984
|
-
|
|
31985
|
-
|
|
31986
|
-
|
|
31987
|
-
|
|
31988
|
-
const { line, character } = offsetToPosition(offset2, lineStarts);
|
|
31989
|
-
return { line: contentStartLine + line, character };
|
|
31990
|
-
};
|
|
31991
|
-
for (const problem of problems) {
|
|
31992
|
-
this.problems.push({ message: problem.message, range: { start: position(problem.start), end: position(problem.end) } });
|
|
31896
|
+
message;
|
|
31897
|
+
};
|
|
31898
|
+
parameterTypes = [
|
|
31899
|
+
{
|
|
31900
|
+
name: ["bool", "boolean"],
|
|
31901
|
+
validate(arg) {
|
|
31902
|
+
return arg.type === 2 /* Boolean */ ? null : new TypeError2("Argument is not a boolean");
|
|
31993
31903
|
}
|
|
31994
|
-
|
|
31995
|
-
|
|
31996
|
-
|
|
31997
|
-
|
|
31998
|
-
|
|
31999
|
-
|
|
32000
|
-
|
|
32001
|
-
|
|
31904
|
+
},
|
|
31905
|
+
{
|
|
31906
|
+
name: ["null"],
|
|
31907
|
+
validate(arg) {
|
|
31908
|
+
return arg.type === 3 /* Null */ ? null : new TypeError2("Argument is not 'null'");
|
|
31909
|
+
}
|
|
31910
|
+
},
|
|
31911
|
+
{
|
|
31912
|
+
name: ["undefined"],
|
|
31913
|
+
validate(arg) {
|
|
31914
|
+
return arg.type === 10 /* Undefined */ ? null : new TypeError2("Argument is not 'undefined'");
|
|
31915
|
+
}
|
|
31916
|
+
},
|
|
31917
|
+
{
|
|
31918
|
+
name: ["number"],
|
|
31919
|
+
validate(arg) {
|
|
31920
|
+
return arg.type === 1 /* Number */ ? null : new TypeError2("Argument is not a number");
|
|
31921
|
+
}
|
|
31922
|
+
},
|
|
31923
|
+
{
|
|
31924
|
+
name: ["NaN"],
|
|
31925
|
+
validate(arg) {
|
|
31926
|
+
return arg.type === 9 /* NaN */ ? null : new TypeError2("Argument is not 'NaN'");
|
|
31927
|
+
}
|
|
31928
|
+
},
|
|
31929
|
+
{
|
|
31930
|
+
name: ["link"],
|
|
31931
|
+
validate(arg) {
|
|
31932
|
+
return arg.type === 5 /* Link */ ? null : new TypeError2("Argument is not a link");
|
|
31933
|
+
}
|
|
31934
|
+
},
|
|
31935
|
+
{
|
|
31936
|
+
name: ["image"],
|
|
31937
|
+
validate(arg) {
|
|
31938
|
+
return arg.type === 6 /* Image */ ? null : new TypeError2("Argument is not an image");
|
|
31939
|
+
}
|
|
31940
|
+
},
|
|
31941
|
+
{
|
|
31942
|
+
name: ["bareword"],
|
|
31943
|
+
validate(arg) {
|
|
31944
|
+
return arg.type === 8 /* Bareword */ ? null : new TypeError2("Argument is not a bareword");
|
|
31945
|
+
}
|
|
31946
|
+
},
|
|
31947
|
+
{
|
|
31948
|
+
name: ["string"],
|
|
31949
|
+
validate(arg) {
|
|
31950
|
+
return arg.type === 0 /* String */ ? null : new TypeError2("Argument is not a quoted string");
|
|
31951
|
+
}
|
|
31952
|
+
},
|
|
31953
|
+
{
|
|
31954
|
+
name: ["text"],
|
|
31955
|
+
validate(arg) {
|
|
31956
|
+
const t = arg.type;
|
|
31957
|
+
if (t === 8 /* Bareword */ || t === 0 /* String */ || t === 2 /* Boolean */ || t === 3 /* Null */ || t === 9 /* NaN */ || t === 1 /* Number */) {
|
|
31958
|
+
return null;
|
|
32002
31959
|
}
|
|
32003
|
-
|
|
32004
|
-
|
|
32005
|
-
|
|
31960
|
+
return new TypeError2("Argument is not text");
|
|
31961
|
+
}
|
|
31962
|
+
},
|
|
31963
|
+
{
|
|
31964
|
+
name: ["var"],
|
|
31965
|
+
validate(arg) {
|
|
31966
|
+
return arg.type === 4 /* Variable */ ? null : new TypeError2("Argument is not a variable");
|
|
31967
|
+
}
|
|
31968
|
+
},
|
|
31969
|
+
{
|
|
31970
|
+
// The story variable a form control binds to: `$name` or `"$name"`.
|
|
31971
|
+
// Spindle strips the quotes and the `$` sigil from the first argument
|
|
31972
|
+
// and reads the rest as a dot path into the story variables.
|
|
31973
|
+
name: ["receiver"],
|
|
31974
|
+
validate(arg) {
|
|
31975
|
+
let name;
|
|
31976
|
+
if (arg.type === 0 /* String */) {
|
|
31977
|
+
name = arg.text.slice(1, -1);
|
|
31978
|
+
} else if (arg.type === 8 /* Bareword */) {
|
|
31979
|
+
name = arg.text;
|
|
31980
|
+
}
|
|
31981
|
+
if (name === void 0) {
|
|
31982
|
+
return new TypeError2("Argument is not a variable receiver");
|
|
31983
|
+
}
|
|
31984
|
+
if (/^[_@%]/.test(name)) {
|
|
31985
|
+
return new TypeError2(`Variable receiver must be a story variable ($name), got '${name}'`);
|
|
31986
|
+
}
|
|
31987
|
+
if (!/^(?:\$\w|[A-Za-z_])[\w$]*(?:\.[\w$]+)*$/.test(name)) {
|
|
31988
|
+
return new TypeError2(`Argument is not a valid variable receiver: '${name}'`);
|
|
31989
|
+
}
|
|
31990
|
+
return null;
|
|
31991
|
+
}
|
|
31992
|
+
},
|
|
31993
|
+
{
|
|
31994
|
+
name: ["passage"],
|
|
31995
|
+
validate(arg, _args, _index, state) {
|
|
31996
|
+
let passageName;
|
|
31997
|
+
if (arg.type === 0 /* String */) {
|
|
31998
|
+
const target = passageTarget(arg.text);
|
|
31999
|
+
if (target.kind === "name") passageName = target.name;
|
|
32000
|
+
} else if (arg.type === 8 /* Bareword */) {
|
|
32001
|
+
passageName = arg.text;
|
|
32002
|
+
} else if (arg.type === 1 /* Number */) {
|
|
32003
|
+
passageName = arg.text;
|
|
32004
|
+
} else if (arg.type === 9 /* NaN */) {
|
|
32005
|
+
passageName = "NaN";
|
|
32006
|
+
}
|
|
32007
|
+
if (passageName !== void 0) {
|
|
32008
|
+
if (state && state.passages.length > 0) {
|
|
32009
|
+
if (!state.passages.includes(passageName)) {
|
|
32010
|
+
return new TypeWarning(`Nonexistent passage: "${passageName}"`);
|
|
32011
|
+
}
|
|
32012
|
+
}
|
|
32013
|
+
return null;
|
|
32014
|
+
}
|
|
32015
|
+
return new TypeError2("Argument is not an acceptable passage");
|
|
32006
32016
|
}
|
|
32007
32017
|
}
|
|
32008
|
-
|
|
32009
|
-
|
|
32010
|
-
|
|
32011
|
-
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
|
|
32015
|
-
|
|
32018
|
+
];
|
|
32019
|
+
LEX_LIMIT = 1e3;
|
|
32020
|
+
wsRe = /\s/;
|
|
32021
|
+
identStartRe = /[a-zA-Z]/;
|
|
32022
|
+
identStopRe = /[|&+\-\s)(]/;
|
|
32023
|
+
quoteRe = /['"]/;
|
|
32024
|
+
digitRe = /[0-9]/;
|
|
32025
|
+
PARSE_LIMIT = 2e3;
|
|
32026
|
+
DESCRIBE_ALTERNATIVE_LIMIT = 16;
|
|
32027
|
+
VALIDATE_LIMIT = 2e3;
|
|
32028
|
+
CORRECT_TYPE_RANK = 1;
|
|
32029
|
+
CORRECT_RANK = 2;
|
|
32030
|
+
Parameters = class {
|
|
32031
|
+
variants;
|
|
32032
|
+
constructor(variants) {
|
|
32033
|
+
this.variants = variants.map((s) => ({
|
|
32034
|
+
formatString: s,
|
|
32035
|
+
format: parseFormat(s)
|
|
32036
|
+
}));
|
|
32037
|
+
}
|
|
32016
32038
|
/**
|
|
32017
|
-
*
|
|
32018
|
-
*
|
|
32039
|
+
* Describe every argument position of every schema variant. Alternatives
|
|
32040
|
+
* (`|`) and optional positions (`|+`) become separate sequences; an empty
|
|
32041
|
+
* schema is the empty sequence. Variants whose format cannot be parsed
|
|
32042
|
+
* contribute nothing.
|
|
32019
32043
|
*/
|
|
32020
|
-
|
|
32021
|
-
|
|
32022
|
-
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
|
|
32026
|
-
|
|
32027
|
-
/** Forget StoryTransients declarations, e.g. after the passage was removed. */
|
|
32028
|
-
clearStoryTransients() {
|
|
32029
|
-
this.transients.clear();
|
|
32044
|
+
describe() {
|
|
32045
|
+
const sequences = [];
|
|
32046
|
+
for (const variant of this.variants) {
|
|
32047
|
+
if (variant.format === null) sequences.push([]);
|
|
32048
|
+
else sequences.push(...describeFormat(variant.format));
|
|
32049
|
+
}
|
|
32050
|
+
return sequences;
|
|
32030
32051
|
}
|
|
32031
32052
|
/**
|
|
32032
|
-
*
|
|
32033
|
-
*
|
|
32053
|
+
* Passage positions that have the same role in every schema alternative.
|
|
32054
|
+
* Unlike describe(), this walks the whole tree without truncating branches.
|
|
32055
|
+
* Repeated or conflicting passage roles return null so callers can refuse
|
|
32056
|
+
* an unsafe rename; actual argument spans and values remain Spindle's job.
|
|
32034
32057
|
*/
|
|
32035
|
-
|
|
32036
|
-
this.
|
|
32058
|
+
passagePositions() {
|
|
32059
|
+
const summary = this.variants.map((variant) => variant.format ? passageSlots(variant.format) : { slots: [], variable: false, unsafe: false }).reduce(mergePassageSlots, { slots: [], variable: false, unsafe: false });
|
|
32060
|
+
return summary.unsafe ? null : summary.slots.flatMap((passage, i2) => passage ? [i2] : []);
|
|
32037
32061
|
}
|
|
32038
32062
|
/**
|
|
32039
|
-
*
|
|
32040
|
-
*
|
|
32041
|
-
* passes the `markup` it reads the document with, which knows the project's
|
|
32042
|
-
* macros and widgets; without it the built-in macros decide.
|
|
32063
|
+
* Validate an array of Arg tokens against these parameter variants.
|
|
32064
|
+
* Selects the best-matching variant by rank.
|
|
32043
32065
|
*/
|
|
32044
|
-
|
|
32045
|
-
this.
|
|
32046
|
-
|
|
32047
|
-
|
|
32048
|
-
for (const passage of markup.passages) {
|
|
32049
|
-
for (const ref2 of collectVariableReferences2(passage)) {
|
|
32050
|
-
const usage = {
|
|
32051
|
-
uri,
|
|
32052
|
-
baseName: ref2.name,
|
|
32053
|
-
fullName: ref2.path,
|
|
32054
|
-
range: passage.range(ref2.start, ref2.end)
|
|
32055
|
-
};
|
|
32056
|
-
(ref2.sigil === "$" ? usages : transientUsages).push(usage);
|
|
32066
|
+
validate(args2, stateInfo) {
|
|
32067
|
+
if (this.variants.length === 0) {
|
|
32068
|
+
if (args2.length === 0) {
|
|
32069
|
+
return { variantIndex: null, errors: [], warnings: [] };
|
|
32057
32070
|
}
|
|
32071
|
+
return {
|
|
32072
|
+
variantIndex: null,
|
|
32073
|
+
errors: [{ message: "Expected no arguments", argIndex: 0 }],
|
|
32074
|
+
warnings: []
|
|
32075
|
+
};
|
|
32058
32076
|
}
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
32063
|
-
|
|
32064
|
-
this.
|
|
32065
|
-
|
|
32066
|
-
|
|
32067
|
-
|
|
32068
|
-
|
|
32069
|
-
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
|
|
32076
|
-
|
|
32077
|
-
|
|
32078
|
-
|
|
32079
|
-
|
|
32080
|
-
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
|
|
32084
|
-
for (const [sigil, usages, declarations] of [
|
|
32085
|
-
["$", this.usagesByUri.get(uri), this.variables.occurrences],
|
|
32086
|
-
["%", this.transientUsagesByUri.get(uri), this.transients.occurrences]
|
|
32087
|
-
]) {
|
|
32088
|
-
const candidates = (usages ?? []).map((u) => ({ name: u.baseName, range: u.range }));
|
|
32089
|
-
for (const declaration of declarations) {
|
|
32090
|
-
if (declaration.declarationUri === uri && declaration.declarationRange) candidates.push({ name: declaration.name, range: declaration.declarationRange });
|
|
32077
|
+
let bestIndex = null;
|
|
32078
|
+
let bestRank = -Infinity;
|
|
32079
|
+
let bestErrors = [];
|
|
32080
|
+
let bestWarnings = [];
|
|
32081
|
+
let bestIsClean = false;
|
|
32082
|
+
for (let i2 = 0; i2 < this.variants.length; i2++) {
|
|
32083
|
+
const variant = this.variants[i2];
|
|
32084
|
+
if (variant.format === null) {
|
|
32085
|
+
if (args2.length === 0) {
|
|
32086
|
+
if (!bestIsClean || 0 > bestRank) {
|
|
32087
|
+
bestIndex = i2;
|
|
32088
|
+
bestRank = 0;
|
|
32089
|
+
bestErrors = [];
|
|
32090
|
+
bestWarnings = [];
|
|
32091
|
+
bestIsClean = true;
|
|
32092
|
+
}
|
|
32093
|
+
} else {
|
|
32094
|
+
if (!bestIsClean && bestIndex === null) {
|
|
32095
|
+
bestIndex = i2;
|
|
32096
|
+
bestRank = -1;
|
|
32097
|
+
bestErrors = [{ message: "Expected no arguments", argIndex: 0 }];
|
|
32098
|
+
bestWarnings = [];
|
|
32099
|
+
}
|
|
32100
|
+
}
|
|
32101
|
+
continue;
|
|
32091
32102
|
}
|
|
32092
|
-
|
|
32093
|
-
|
|
32094
|
-
|
|
32095
|
-
|
|
32096
|
-
|
|
32103
|
+
const result2 = crawlValidate(variant.format, args2, 0, stateInfo);
|
|
32104
|
+
let errors = result2.errors;
|
|
32105
|
+
if (result2.errors.length === 0 && result2.argIndex < args2.length) {
|
|
32106
|
+
errors = [
|
|
32107
|
+
{
|
|
32108
|
+
message: `Too many arguments: expected ${result2.argIndex}, got ${args2.length}`,
|
|
32109
|
+
argIndex: result2.argIndex
|
|
32110
|
+
}
|
|
32111
|
+
];
|
|
32097
32112
|
}
|
|
32098
|
-
|
|
32099
|
-
|
|
32100
|
-
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32109
|
-
|
|
32110
|
-
|
|
32111
|
-
|
|
32112
|
-
|
|
32113
|
-
|
|
32114
|
-
|
|
32115
|
-
|
|
32116
|
-
|
|
32117
|
-
/**
|
|
32118
|
-
* Get `$var.member` / `%var.member` accesses in a document where `var` is
|
|
32119
|
-
* declared with an array literal default and `member` is not a property
|
|
32120
|
-
* that JavaScript arrays have (so the access always yields undefined).
|
|
32121
|
-
*/
|
|
32122
|
-
getArrayMemberAccesses(uri) {
|
|
32123
|
-
const results = [];
|
|
32124
|
-
const sources = [
|
|
32125
|
-
["$", this.usagesByUri.get(uri), this.variables.declared],
|
|
32126
|
-
["%", this.transientUsagesByUri.get(uri), this.transients.declared]
|
|
32127
|
-
];
|
|
32128
|
-
for (const [sigil, usages, declared] of sources) {
|
|
32129
|
-
for (const u of usages ?? []) {
|
|
32130
|
-
const member = u.fullName.split(".")[1];
|
|
32131
|
-
if (member === void 0) continue;
|
|
32132
|
-
if (declared.get(u.baseName)?.type !== "array") continue;
|
|
32133
|
-
if (ARRAY_MEMBERS.has(member) || /^(0|[1-9]\d*)$/.test(member) && Number(member) < 4294967295) continue;
|
|
32134
|
-
results.push({ sigil, name: u.baseName, member, range: u.range });
|
|
32113
|
+
const rank = result2.rank;
|
|
32114
|
+
const isClean = errors.length === 0;
|
|
32115
|
+
if (isClean && (!bestIsClean || rank > bestRank)) {
|
|
32116
|
+
bestIndex = i2;
|
|
32117
|
+
bestRank = rank;
|
|
32118
|
+
bestErrors = [];
|
|
32119
|
+
bestWarnings = result2.warnings;
|
|
32120
|
+
bestIsClean = true;
|
|
32121
|
+
} else if (!isClean && !bestIsClean && rank > bestRank) {
|
|
32122
|
+
bestIndex = i2;
|
|
32123
|
+
bestRank = rank;
|
|
32124
|
+
bestErrors = errors;
|
|
32125
|
+
bestWarnings = result2.warnings;
|
|
32126
|
+
} else if (bestIndex === null) {
|
|
32127
|
+
bestIndex = i2;
|
|
32128
|
+
bestRank = rank;
|
|
32129
|
+
bestErrors = errors;
|
|
32130
|
+
bestWarnings = result2.warnings;
|
|
32131
|
+
bestIsClean = isClean;
|
|
32135
32132
|
}
|
|
32136
32133
|
}
|
|
32137
|
-
return
|
|
32138
|
-
|
|
32139
|
-
|
|
32140
|
-
|
|
32141
|
-
|
|
32134
|
+
return {
|
|
32135
|
+
variantIndex: bestIndex,
|
|
32136
|
+
errors: bestErrors,
|
|
32137
|
+
warnings: bestWarnings
|
|
32138
|
+
};
|
|
32142
32139
|
}
|
|
32143
32140
|
/**
|
|
32144
|
-
*
|
|
32145
|
-
* declares that StoryVariables declares too.
|
|
32141
|
+
* Whether all variants are empty (accept no arguments).
|
|
32146
32142
|
*/
|
|
32147
|
-
|
|
32148
|
-
|
|
32149
|
-
|
|
32150
|
-
for (const [name, range2] of this.transients.names) {
|
|
32151
|
-
if (!this.variables.names.has(name)) continue;
|
|
32152
|
-
collisions.push({
|
|
32153
|
-
message: `StoryTransients: Variable "${name}" is already declared in StoryVariables. Names must be unique across scopes.`,
|
|
32154
|
-
range: range2
|
|
32155
|
-
});
|
|
32156
|
-
}
|
|
32157
|
-
}
|
|
32158
|
-
return [...this.transients.problems, ...collisions].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
32143
|
+
isEmpty() {
|
|
32144
|
+
if (this.variants.length === 0) return true;
|
|
32145
|
+
return !this.variants.some((v2) => v2.format !== null);
|
|
32159
32146
|
}
|
|
32160
32147
|
};
|
|
32161
32148
|
}
|
|
32162
32149
|
});
|
|
32163
32150
|
|
|
32164
|
-
// src/core/
|
|
32165
|
-
function
|
|
32166
|
-
|
|
32167
|
-
|
|
32168
|
-
if (piece.kind === "text") yield* piece.tokens;
|
|
32169
|
-
}
|
|
32170
|
-
}
|
|
32171
|
-
function* macroTokens(passage) {
|
|
32172
|
-
for (const token of markupTokens(passage)) {
|
|
32173
|
-
if (token.type === "macro") yield token;
|
|
32174
|
-
}
|
|
32175
|
-
}
|
|
32176
|
-
var init_tokens = __esm({
|
|
32177
|
-
"src/core/markup/tokens.ts"() {
|
|
32178
|
-
"use strict";
|
|
32179
|
-
}
|
|
32180
|
-
});
|
|
32181
|
-
|
|
32182
|
-
// src/core/markup/widgets.ts
|
|
32183
|
-
function widgetSources(passages, macros) {
|
|
32184
|
-
const sources = /* @__PURE__ */ new Map();
|
|
32185
|
-
for (const passage of passages) {
|
|
32186
|
-
if (!passage.isMarkup) continue;
|
|
32187
|
-
const { name, tags } = passage.passage;
|
|
32188
|
-
for (const definition2 of widgetDefinitions([{ name, tags, content: passage.content }], macros)) {
|
|
32189
|
-
sources.set(definition2, passage);
|
|
32190
|
-
}
|
|
32151
|
+
// src/core/workspace/macro-registry.ts
|
|
32152
|
+
function applySupplement(info, entry) {
|
|
32153
|
+
for (const field of ["description", "parameters", "parameterDocs", "children", "parents", "skipArgs"]) {
|
|
32154
|
+
if (entry[field] !== void 0) Object.assign(info, { [field]: entry[field] });
|
|
32191
32155
|
}
|
|
32192
|
-
|
|
32156
|
+
if (entry.container !== void 0) info.block = entry.container;
|
|
32193
32157
|
}
|
|
32194
|
-
var
|
|
32195
|
-
|
|
32196
|
-
|
|
32197
|
-
init_tooling();
|
|
32198
|
-
}
|
|
32199
|
-
});
|
|
32200
|
-
|
|
32201
|
-
// src/core/workspace/widget-registry.ts
|
|
32202
|
-
var WidgetRegistry;
|
|
32203
|
-
var init_widget_registry = __esm({
|
|
32204
|
-
"src/core/workspace/widget-registry.ts"() {
|
|
32158
|
+
var MacroRegistry;
|
|
32159
|
+
var init_macro_registry2 = __esm({
|
|
32160
|
+
"src/core/workspace/macro-registry.ts"() {
|
|
32205
32161
|
"use strict";
|
|
32206
32162
|
init_tooling();
|
|
32207
|
-
|
|
32208
|
-
|
|
32209
|
-
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32213
|
-
|
|
32214
|
-
|
|
32215
|
-
|
|
32163
|
+
init_parameter_validator();
|
|
32164
|
+
init_config_loader();
|
|
32165
|
+
MacroRegistry = class {
|
|
32166
|
+
macros = /* @__PURE__ */ new Map();
|
|
32167
|
+
/** Entries as they were before the current user config was applied (undefined = absent). */
|
|
32168
|
+
configSnapshot = /* @__PURE__ */ new Map();
|
|
32169
|
+
/** Tier 3: macros discovered from Story.defineMacro() calls, keyed by lowercase name. */
|
|
32170
|
+
discovered = /* @__PURE__ */ new Map();
|
|
32171
|
+
/** Called after the set of macros or their metadata changed. */
|
|
32172
|
+
onChange;
|
|
32173
|
+
/** Tier 4: user config entries, kept so they can be re-applied over discovered macros. */
|
|
32174
|
+
configEntries = /* @__PURE__ */ new Map();
|
|
32216
32175
|
/**
|
|
32217
|
-
*
|
|
32218
|
-
*
|
|
32219
|
-
*
|
|
32176
|
+
* Load the built-in macros: name, block, sub-macros, flags and typed
|
|
32177
|
+
* parameters, from Spindle's tooling API (the release the LSP is built on).
|
|
32178
|
+
* This is the base layer the other tiers overlay.
|
|
32220
32179
|
*/
|
|
32221
|
-
|
|
32222
|
-
|
|
32223
|
-
|
|
32224
|
-
|
|
32225
|
-
|
|
32226
|
-
|
|
32227
|
-
|
|
32228
|
-
|
|
32229
|
-
|
|
32230
|
-
|
|
32231
|
-
|
|
32232
|
-
|
|
32233
|
-
nameRange: markup.range(definition2.nameStart, definition2.nameEnd),
|
|
32234
|
-
block: definition2.block
|
|
32235
|
-
};
|
|
32236
|
-
const key = definition2.name.toLowerCase();
|
|
32237
|
-
if (effective.has(definition2)) this.widgets.set(key, widget);
|
|
32238
|
-
else if (definition2.closeStart === void 0) this.incomplete.set(key, widget);
|
|
32180
|
+
loadBuiltins() {
|
|
32181
|
+
for (const m of builtinMacros) {
|
|
32182
|
+
this.macros.set(m.name.toLowerCase(), {
|
|
32183
|
+
name: m.name,
|
|
32184
|
+
block: m.block,
|
|
32185
|
+
subMacros: [...m.subMacros],
|
|
32186
|
+
storeVar: m.storeVar,
|
|
32187
|
+
interpolate: m.interpolate,
|
|
32188
|
+
merged: m.merged,
|
|
32189
|
+
source: m.source === "builtin" ? "builtin" : "user",
|
|
32190
|
+
parameterDefs: m.parameters
|
|
32191
|
+
});
|
|
32239
32192
|
}
|
|
32240
32193
|
}
|
|
32241
|
-
/**
|
|
32242
|
-
|
|
32243
|
-
|
|
32244
|
-
|
|
32245
|
-
|
|
32246
|
-
|
|
32247
|
-
|
|
32248
|
-
|
|
32194
|
+
/**
|
|
32195
|
+
* Overlay supplement data onto the registry.
|
|
32196
|
+
* Supplements provide descriptions, parameters, children, parents, skipArgs.
|
|
32197
|
+
* The `container` field maps to `block` on MacroInfo.
|
|
32198
|
+
*/
|
|
32199
|
+
loadSupplements(supplements) {
|
|
32200
|
+
this.mergeEntries(supplements);
|
|
32201
|
+
this.onChange?.();
|
|
32249
32202
|
}
|
|
32250
|
-
/**
|
|
32251
|
-
|
|
32252
|
-
|
|
32253
|
-
|
|
32254
|
-
|
|
32255
|
-
|
|
32203
|
+
/**
|
|
32204
|
+
* Overlay user config onto the registry.
|
|
32205
|
+
* Same format as supplements; applied last so it wins.
|
|
32206
|
+
*
|
|
32207
|
+
* Replaces any previously loaded config: entries from the last call are
|
|
32208
|
+
* reverted first, so macros removed from the config disappear (or fall
|
|
32209
|
+
* back to their builtin/supplement definition).
|
|
32210
|
+
*/
|
|
32211
|
+
loadConfig(config2) {
|
|
32212
|
+
config2 = normalizeMacroEntries(config2);
|
|
32213
|
+
for (const [key, previous2] of this.configSnapshot) {
|
|
32214
|
+
if (previous2) {
|
|
32215
|
+
this.macros.set(key, previous2);
|
|
32216
|
+
} else {
|
|
32217
|
+
this.macros.delete(key);
|
|
32218
|
+
}
|
|
32219
|
+
}
|
|
32220
|
+
this.configSnapshot.clear();
|
|
32221
|
+
this.configEntries.clear();
|
|
32222
|
+
for (const rawKey of Object.keys(config2)) {
|
|
32223
|
+
const key = rawKey.toLowerCase();
|
|
32224
|
+
if (!this.configSnapshot.has(key)) {
|
|
32225
|
+
const existing = this.macros.get(key);
|
|
32226
|
+
this.configSnapshot.set(key, existing ? { ...existing } : void 0);
|
|
32227
|
+
}
|
|
32228
|
+
this.configEntries.set(key, { ...this.configEntries.get(key), ...config2[rawKey] });
|
|
32229
|
+
}
|
|
32230
|
+
this.mergeEntries(config2);
|
|
32231
|
+
this.onChange?.();
|
|
32256
32232
|
}
|
|
32257
32233
|
/**
|
|
32258
|
-
*
|
|
32259
|
-
*
|
|
32260
|
-
*
|
|
32261
|
-
*
|
|
32234
|
+
* Replace the set of macros discovered from Story.defineMacro() calls.
|
|
32235
|
+
* Previously discovered definitions that are no longer present are dropped,
|
|
32236
|
+
* restoring whatever the other tiers define for that name.
|
|
32237
|
+
*
|
|
32238
|
+
* Each sub-macro becomes a known macro that may only appear inside its
|
|
32239
|
+
* parent. Whether a macro is a block is the tooling API's reading of the
|
|
32240
|
+
* definition (`discoverMacros`).
|
|
32262
32241
|
*/
|
|
32263
|
-
|
|
32264
|
-
|
|
32265
|
-
for (const
|
|
32266
|
-
|
|
32267
|
-
|
|
32242
|
+
setDiscoveredMacros(macros) {
|
|
32243
|
+
this.discovered.clear();
|
|
32244
|
+
for (const m of macros) {
|
|
32245
|
+
this.discovered.set(m.name.toLowerCase(), {
|
|
32246
|
+
name: m.name,
|
|
32247
|
+
block: m.block,
|
|
32248
|
+
subMacros: m.subMacros,
|
|
32249
|
+
storeVar: m.storeVar,
|
|
32250
|
+
interpolate: m.interpolate,
|
|
32251
|
+
merged: m.merged,
|
|
32252
|
+
description: m.description,
|
|
32253
|
+
source: "user",
|
|
32254
|
+
parameterDefs: m.parameters
|
|
32255
|
+
});
|
|
32256
|
+
}
|
|
32257
|
+
for (const m of [...this.discovered.values()]) {
|
|
32258
|
+
for (const sub of m.subMacros ?? []) {
|
|
32259
|
+
const key = sub.toLowerCase();
|
|
32260
|
+
const existing = this.discovered.get(key);
|
|
32261
|
+
if (existing) {
|
|
32262
|
+
existing.parents = [...existing.parents ?? [], m.name];
|
|
32263
|
+
} else {
|
|
32264
|
+
this.discovered.set(key, { name: sub, block: false, subMacros: [], source: "user", parents: [m.name] });
|
|
32265
|
+
}
|
|
32268
32266
|
}
|
|
32269
32267
|
}
|
|
32270
|
-
this.
|
|
32268
|
+
this.onChange?.();
|
|
32271
32269
|
}
|
|
32272
|
-
/**
|
|
32273
|
-
|
|
32274
|
-
|
|
32270
|
+
/** Add or replace a single macro entry. */
|
|
32271
|
+
addMacro(info) {
|
|
32272
|
+
const key = info.name.toLowerCase();
|
|
32273
|
+
const existing = this.macros.get(key);
|
|
32274
|
+
this.macros.set(key, {
|
|
32275
|
+
name: info.name,
|
|
32276
|
+
block: info.block ?? existing?.block ?? false,
|
|
32277
|
+
subMacros: info.subMacros ?? existing?.subMacros ?? [],
|
|
32278
|
+
source: info.source ?? existing?.source ?? "user",
|
|
32279
|
+
storeVar: info.storeVar ?? existing?.storeVar,
|
|
32280
|
+
interpolate: info.interpolate ?? existing?.interpolate,
|
|
32281
|
+
merged: info.merged ?? existing?.merged,
|
|
32282
|
+
description: info.description ?? existing?.description,
|
|
32283
|
+
parameters: info.parameters ?? existing?.parameters,
|
|
32284
|
+
parameterDefs: info.parameterDefs ?? existing?.parameterDefs,
|
|
32285
|
+
parameterDocs: info.parameterDocs ?? existing?.parameterDocs,
|
|
32286
|
+
children: info.children ?? existing?.children,
|
|
32287
|
+
parents: info.parents ?? existing?.parents,
|
|
32288
|
+
skipArgs: info.skipArgs ?? existing?.skipArgs
|
|
32289
|
+
});
|
|
32290
|
+
this.onChange?.();
|
|
32275
32291
|
}
|
|
32276
|
-
/**
|
|
32277
|
-
|
|
32278
|
-
|
|
32279
|
-
|
|
32280
|
-
|
|
32292
|
+
/** Get a macro by name (case-insensitive). */
|
|
32293
|
+
getMacro(name) {
|
|
32294
|
+
return this.resolve(name.toLowerCase());
|
|
32295
|
+
}
|
|
32296
|
+
/** A project's explicit schema takes precedence over runtime signature metadata. */
|
|
32297
|
+
hasConfiguredParameters(name) {
|
|
32298
|
+
return this.configEntries.get(name.toLowerCase())?.parameters !== void 0;
|
|
32299
|
+
}
|
|
32300
|
+
/** Check if a macro is a block (container) macro. */
|
|
32301
|
+
isBlock(name) {
|
|
32302
|
+
return this.getMacro(name)?.block ?? false;
|
|
32303
|
+
}
|
|
32304
|
+
/** Check if a macro is a sub-macro (has parents). */
|
|
32305
|
+
isSubMacro(name) {
|
|
32306
|
+
const info = this.getMacro(name);
|
|
32307
|
+
return info?.parents != null && info.parents.length > 0;
|
|
32308
|
+
}
|
|
32309
|
+
/** The macros as the tooling API reads them (`passagePieces`, `validateStoryMarkup`). */
|
|
32310
|
+
toolingMacros() {
|
|
32311
|
+
return this.getAllMacros().map((m) => ({
|
|
32312
|
+
name: m.name,
|
|
32313
|
+
block: m.block,
|
|
32314
|
+
subMacros: m.subMacros,
|
|
32315
|
+
storeVar: m.storeVar,
|
|
32316
|
+
interpolate: m.interpolate,
|
|
32317
|
+
parameters: m.parameterDefs,
|
|
32318
|
+
widget: this.configuredToolingParameters(m.name)
|
|
32319
|
+
}));
|
|
32320
|
+
}
|
|
32321
|
+
/** Whether an invoked configured schema prevents a safe passage rename. */
|
|
32322
|
+
hasUnsafeConfiguredPassages(name) {
|
|
32323
|
+
const positions = this.configuredPassagePositions(name);
|
|
32324
|
+
if (positions === void 0) return false;
|
|
32325
|
+
if (positions === null) return true;
|
|
32326
|
+
const definitions = this.getMacro(name)?.parameterDefs;
|
|
32327
|
+
if (!definitions?.length) return false;
|
|
32328
|
+
const runtimePositions = definitions.flatMap((parameter, i2) => parameter.type === "passage" || parameter.holds === "passage" ? [i2] : []);
|
|
32329
|
+
if (definitions.some((parameter) => parameter.type === "options" || parameter.type === "names" || parameter.type === "text" || parameter.type === "separator")) {
|
|
32330
|
+
return positions.length > 0 || runtimePositions.length > 0;
|
|
32331
|
+
}
|
|
32332
|
+
return positions.length !== runtimePositions.length || positions.some((position, i2) => position !== runtimePositions[i2]);
|
|
32333
|
+
}
|
|
32334
|
+
configuredPassagePositions(name) {
|
|
32335
|
+
const parameters = this.configEntries.get(name.toLowerCase())?.parameters;
|
|
32336
|
+
if (parameters === void 0) return void 0;
|
|
32337
|
+
try {
|
|
32338
|
+
return new Parameters(parameters).passagePositions();
|
|
32339
|
+
} catch {
|
|
32340
|
+
return null;
|
|
32341
|
+
}
|
|
32342
|
+
}
|
|
32343
|
+
configuredToolingParameters(name) {
|
|
32344
|
+
const positions = this.configuredPassagePositions(name);
|
|
32345
|
+
if (!positions?.length || this.getMacro(name)?.parameterDefs?.length) return void 0;
|
|
32346
|
+
const params = Array.from({ length: positions.at(-1) + 1 }, (_2, i2) => `arg${i2}`);
|
|
32347
|
+
return { params, holds: Object.fromEntries(positions.map((i2) => [params[i2], "passage"])) };
|
|
32348
|
+
}
|
|
32349
|
+
/** Get all registered macros. */
|
|
32350
|
+
getAllMacros() {
|
|
32351
|
+
const keys = /* @__PURE__ */ new Set([...this.macros.keys(), ...this.discovered.keys()]);
|
|
32352
|
+
return Array.from(keys, (key) => this.resolve(key));
|
|
32353
|
+
}
|
|
32354
|
+
/**
|
|
32355
|
+
* Compose the effective entry for a lowercase key: builtins/supplements/
|
|
32356
|
+
* config (stored in `macros`), overlaid by discovered metadata, with user
|
|
32357
|
+
* config re-applied on top so it keeps precedence.
|
|
32358
|
+
*/
|
|
32359
|
+
resolve(key) {
|
|
32360
|
+
const base = this.macros.get(key);
|
|
32361
|
+
const found = this.discovered.get(key);
|
|
32362
|
+
if (!found) return base;
|
|
32363
|
+
const info = {
|
|
32364
|
+
name: found.name,
|
|
32365
|
+
block: found.block ?? false,
|
|
32366
|
+
subMacros: found.subMacros ?? [],
|
|
32367
|
+
source: "user",
|
|
32368
|
+
storeVar: found.storeVar,
|
|
32369
|
+
interpolate: found.interpolate,
|
|
32370
|
+
merged: found.merged,
|
|
32371
|
+
description: found.description,
|
|
32372
|
+
// A definition replaces the macro it names, parameters included
|
|
32373
|
+
parameterDefs: found.parameterDefs,
|
|
32374
|
+
parents: found.parents
|
|
32375
|
+
};
|
|
32376
|
+
const config2 = this.configEntries.get(key);
|
|
32377
|
+
if (config2) applySupplement(info, config2);
|
|
32378
|
+
return info;
|
|
32379
|
+
}
|
|
32380
|
+
/** Merge a set of supplement/config entries into the registry. */
|
|
32381
|
+
mergeEntries(entries) {
|
|
32382
|
+
for (const [rawKey, entry] of Object.entries(entries)) {
|
|
32383
|
+
const key = rawKey.toLowerCase();
|
|
32384
|
+
const existing = this.macros.get(key);
|
|
32385
|
+
if (existing) {
|
|
32386
|
+
applySupplement(existing, entry);
|
|
32387
|
+
} else {
|
|
32388
|
+
this.macros.set(key, {
|
|
32389
|
+
name: entry.name ?? rawKey,
|
|
32390
|
+
block: entry.container ?? false,
|
|
32391
|
+
subMacros: [],
|
|
32392
|
+
source: "user",
|
|
32393
|
+
description: entry.description,
|
|
32394
|
+
parameters: entry.parameters,
|
|
32395
|
+
parameterDocs: entry.parameterDocs,
|
|
32396
|
+
children: entry.children,
|
|
32397
|
+
parents: entry.parents,
|
|
32398
|
+
skipArgs: entry.skipArgs
|
|
32399
|
+
});
|
|
32400
|
+
}
|
|
32281
32401
|
}
|
|
32282
|
-
return false;
|
|
32283
32402
|
}
|
|
32284
32403
|
};
|
|
32285
32404
|
}
|
|
32286
32405
|
});
|
|
32287
32406
|
|
|
32288
|
-
// src/core/
|
|
32289
|
-
function
|
|
32290
|
-
|
|
32407
|
+
// src/core/parsing/executable-refs.ts
|
|
32408
|
+
function collectVariableReferences2(markup) {
|
|
32409
|
+
if (!markup.isMarkup) return [];
|
|
32410
|
+
return variableReferences(markup.content, markup.doc.context.macros, { all: true }).map((ref2) => ({
|
|
32411
|
+
sigil: ref2.sigil,
|
|
32412
|
+
name: ref2.name,
|
|
32413
|
+
path: [ref2.name, ...ref2.path].join("."),
|
|
32414
|
+
start: ref2.start,
|
|
32415
|
+
end: ref2.end
|
|
32416
|
+
}));
|
|
32291
32417
|
}
|
|
32292
|
-
var
|
|
32293
|
-
|
|
32294
|
-
"src/core/markup/markup-index.ts"() {
|
|
32418
|
+
var init_executable_refs = __esm({
|
|
32419
|
+
"src/core/parsing/executable-refs.ts"() {
|
|
32295
32420
|
"use strict";
|
|
32296
|
-
|
|
32297
|
-
MarkupIndex = class {
|
|
32298
|
-
constructor(sources) {
|
|
32299
|
-
this.sources = sources;
|
|
32300
|
-
}
|
|
32301
|
-
sources;
|
|
32302
|
-
cache = /* @__PURE__ */ new Map();
|
|
32303
|
-
context;
|
|
32304
|
-
/** The markup of document `uri`, or undefined if it is not open. */
|
|
32305
|
-
get(uri) {
|
|
32306
|
-
const text3 = this.sources.text(uri);
|
|
32307
|
-
if (text3 === void 0) return void 0;
|
|
32308
|
-
const kept3 = this.cache.get(uri);
|
|
32309
|
-
const passages = this.sources.passages(uri);
|
|
32310
|
-
if (kept3 && kept3.text === text3 && sameHeaders(kept3.passages.map((p) => p.passage), passages)) return kept3;
|
|
32311
|
-
this.context ??= this.sources.context();
|
|
32312
|
-
const fresh = new DocumentMarkup(uri, text3, passages, this.context);
|
|
32313
|
-
this.cache.set(uri, fresh);
|
|
32314
|
-
return fresh;
|
|
32315
|
-
}
|
|
32316
|
-
/** The macros or widgets changed: every document's markup is read again on demand. */
|
|
32317
|
-
invalidate() {
|
|
32318
|
-
this.cache.clear();
|
|
32319
|
-
this.context = void 0;
|
|
32320
|
-
}
|
|
32321
|
-
};
|
|
32421
|
+
init_tooling();
|
|
32322
32422
|
}
|
|
32323
32423
|
});
|
|
32324
32424
|
|
|
32325
|
-
// src/core/
|
|
32326
|
-
|
|
32327
|
-
|
|
32328
|
-
|
|
32329
|
-
|
|
32330
|
-
|
|
32331
|
-
|
|
32425
|
+
// src/core/markup/passage-markup.ts
|
|
32426
|
+
var PassageMarkup, DocumentMarkup;
|
|
32427
|
+
var init_passage_markup = __esm({
|
|
32428
|
+
"src/core/markup/passage-markup.ts"() {
|
|
32429
|
+
"use strict";
|
|
32430
|
+
init_tooling();
|
|
32431
|
+
init_passage_parser();
|
|
32432
|
+
init_text();
|
|
32433
|
+
PassageMarkup = class {
|
|
32434
|
+
constructor(doc, passage, bodyStart, bodyEnd) {
|
|
32435
|
+
this.doc = doc;
|
|
32436
|
+
this.passage = passage;
|
|
32437
|
+
this.bodyStart = bodyStart;
|
|
32438
|
+
this.bodyEnd = bodyEnd;
|
|
32439
|
+
const raw = doc.text.slice(bodyStart, bodyEnd);
|
|
32440
|
+
this.isMarkup = isMarkupPassage(passage);
|
|
32441
|
+
if (raw.includes("\r\n")) {
|
|
32442
|
+
const map2 = [];
|
|
32443
|
+
let content2 = "";
|
|
32444
|
+
for (let i2 = 0; i2 < raw.length; i2++) {
|
|
32445
|
+
if (raw[i2] === "\r" && raw[i2 + 1] === "\n") continue;
|
|
32446
|
+
map2.push(bodyStart + i2);
|
|
32447
|
+
content2 += raw[i2];
|
|
32448
|
+
}
|
|
32449
|
+
map2.push(bodyEnd);
|
|
32450
|
+
this.content = content2;
|
|
32451
|
+
this.toDoc = Int32Array.from(map2);
|
|
32452
|
+
} else {
|
|
32453
|
+
this.content = raw;
|
|
32454
|
+
this.toDoc = null;
|
|
32455
|
+
}
|
|
32456
|
+
}
|
|
32457
|
+
doc;
|
|
32458
|
+
passage;
|
|
32459
|
+
bodyStart;
|
|
32460
|
+
bodyEnd;
|
|
32461
|
+
/** The body, LF-normalized: the text the tooling API analyzes. */
|
|
32462
|
+
content;
|
|
32463
|
+
/** Whether Spindle reads the body as story markup (see isMarkupPassage). */
|
|
32464
|
+
isMarkup;
|
|
32465
|
+
/** For each `content` offset, its offset in the document; null when the body has no CRLF. */
|
|
32466
|
+
toDoc;
|
|
32467
|
+
cachedTokens;
|
|
32468
|
+
cachedPairing;
|
|
32469
|
+
cachedPieces;
|
|
32470
|
+
cachedDeclarations;
|
|
32471
|
+
/** The document offset of `contentOffset`. */
|
|
32472
|
+
docOffset(contentOffset) {
|
|
32473
|
+
if (!this.toDoc) return this.bodyStart + contentOffset;
|
|
32474
|
+
return this.toDoc[Math.min(Math.max(contentOffset, 0), this.toDoc.length - 1)];
|
|
32475
|
+
}
|
|
32476
|
+
/**
|
|
32477
|
+
* The document offset just past the character before `contentEnd`: where an
|
|
32478
|
+
* exclusive end maps to. (`docOffset()` of an end that sits before a line
|
|
32479
|
+
* break would land behind the `\r` of a CRLF.)
|
|
32480
|
+
*/
|
|
32481
|
+
docEnd(contentEnd) {
|
|
32482
|
+
return contentEnd <= 0 ? this.docOffset(0) : this.docOffset(contentEnd - 1) + 1;
|
|
32483
|
+
}
|
|
32484
|
+
/** The `content` offset of a document offset (the nearest one at or after it). */
|
|
32485
|
+
contentOffset(docOffset) {
|
|
32486
|
+
if (!this.toDoc) return Math.min(Math.max(docOffset - this.bodyStart, 0), this.content.length);
|
|
32487
|
+
let low = 0;
|
|
32488
|
+
let high = this.toDoc.length - 1;
|
|
32489
|
+
while (low < high) {
|
|
32490
|
+
const mid = low + high >> 1;
|
|
32491
|
+
if (this.toDoc[mid] < docOffset) low = mid + 1;
|
|
32492
|
+
else high = mid;
|
|
32493
|
+
}
|
|
32494
|
+
return low;
|
|
32495
|
+
}
|
|
32496
|
+
/** The document position of `contentOffset`. */
|
|
32497
|
+
position(contentOffset) {
|
|
32498
|
+
return offsetToPosition(this.docOffset(contentOffset), this.doc.lineStarts);
|
|
32499
|
+
}
|
|
32500
|
+
/** The document range of `[start, end)` in `content` offsets. */
|
|
32501
|
+
range(start, end) {
|
|
32502
|
+
return offsetsToRange(this.docOffset(start), end > start ? this.docEnd(end) : this.docOffset(start), this.doc.lineStarts);
|
|
32503
|
+
}
|
|
32504
|
+
/** The flat tokens and the malformed tags; none for a passage that is not markup. */
|
|
32505
|
+
get tokenization() {
|
|
32506
|
+
return this.cachedTokens ??= this.isMarkup ? tokenizeMarkupTolerant(this.content) : { tokens: [], errors: [] };
|
|
32507
|
+
}
|
|
32508
|
+
get tokens() {
|
|
32509
|
+
return this.tokenization.tokens;
|
|
32510
|
+
}
|
|
32511
|
+
/** The tree of paired tokens, and what does not pair. */
|
|
32512
|
+
get pairing() {
|
|
32513
|
+
return this.cachedPairing ??= pairMarkup(this.tokens, {
|
|
32514
|
+
isBlock: (name) => this.doc.context.isBlock(name),
|
|
32515
|
+
source: this.content
|
|
32516
|
+
});
|
|
32517
|
+
}
|
|
32518
|
+
/** What the markup runs and names, and where (see `passagePieces`). */
|
|
32519
|
+
get pieces() {
|
|
32520
|
+
return this.cachedPieces ??= this.isMarkup ? passagePieces(this.content, this.doc.context.macros) : [];
|
|
32521
|
+
}
|
|
32522
|
+
/** The declarations of a StoryVariables / StoryTransients passage. */
|
|
32523
|
+
get declarations() {
|
|
32524
|
+
return this.cachedDeclarations ??= parseDeclarations(this.content, this.passage.name === "StoryTransients" ? "%" : "$");
|
|
32525
|
+
}
|
|
32526
|
+
};
|
|
32527
|
+
DocumentMarkup = class {
|
|
32528
|
+
constructor(uri, text3, passages, context) {
|
|
32529
|
+
this.uri = uri;
|
|
32530
|
+
this.text = text3;
|
|
32531
|
+
this.context = context;
|
|
32532
|
+
this.lineStarts = buildLineStarts(text3);
|
|
32533
|
+
this.passages = passages.map((passage, i2) => {
|
|
32534
|
+
const bodyStart = this.lineStarts[passage.headerEnd.end.line + 1] ?? text3.length;
|
|
32535
|
+
const next = passages[i2 + 1];
|
|
32536
|
+
const bodyEnd = next ? this.lineStarts[next.range.start.line] : text3.length;
|
|
32537
|
+
return new PassageMarkup(this, passage, Math.min(bodyStart, bodyEnd), bodyEnd);
|
|
32538
|
+
});
|
|
32539
|
+
}
|
|
32540
|
+
uri;
|
|
32541
|
+
text;
|
|
32542
|
+
context;
|
|
32543
|
+
lineStarts;
|
|
32544
|
+
passages;
|
|
32545
|
+
/** The passage that contains `position`. */
|
|
32546
|
+
passageAt(position) {
|
|
32547
|
+
for (let i2 = this.passages.length - 1; i2 >= 0; i2--) {
|
|
32548
|
+
if (this.passages[i2].passage.range.start.line <= position.line) return this.passages[i2];
|
|
32549
|
+
}
|
|
32550
|
+
return void 0;
|
|
32551
|
+
}
|
|
32552
|
+
};
|
|
32553
|
+
}
|
|
32554
|
+
});
|
|
32555
|
+
|
|
32556
|
+
// src/core/workspace/declaration-check.ts
|
|
32557
|
+
function readDeclarations(content2, sigil) {
|
|
32558
|
+
const passage = PASSAGE[sigil];
|
|
32559
|
+
const lines = content2.replace(/\r\n/g, "\n").split("\n");
|
|
32560
|
+
const header = lines.findIndex((line) => /^?::/.test(line));
|
|
32561
|
+
const text3 = (header === -1 ? lines : lines.slice(0, header)).join("\n");
|
|
32562
|
+
const { declarations, errors } = parseDeclarations(text3.replace(/\r/g, " "), sigil);
|
|
32563
|
+
const problems = [];
|
|
32564
|
+
for (const error48 of errors) {
|
|
32565
|
+
if (error48.code === "duplicate-declaration") continue;
|
|
32566
|
+
const [start, end] = lineSpan(text3, error48.offset);
|
|
32567
|
+
if (isUnjudged(text3.slice(start, end))) continue;
|
|
32568
|
+
let message = `${passage}: ${error48.message}`;
|
|
32569
|
+
if (error48.code === "invalid-declaration" && COMMENT_START.test(text3.slice(error48.offset, error48.end))) {
|
|
32570
|
+
message += `. ${passage} has no comment syntax.`;
|
|
32571
|
+
} else if (!message.endsWith(".")) {
|
|
32572
|
+
message += ".";
|
|
32573
|
+
}
|
|
32574
|
+
problems.push({ message, start, end });
|
|
32575
|
+
}
|
|
32576
|
+
return { text: text3, declarations, problems: problems.sort((a, b) => a.start - b.start) };
|
|
32577
|
+
}
|
|
32578
|
+
function isUnjudged(line) {
|
|
32579
|
+
return line.includes("\r") || line.includes("\xA0");
|
|
32580
|
+
}
|
|
32581
|
+
function lineSpan(text3, offset2) {
|
|
32582
|
+
const lineStart = text3.lastIndexOf("\n", offset2 - 1) + 1;
|
|
32583
|
+
const lineEnd = text3.indexOf("\n", offset2);
|
|
32584
|
+
const line = text3.slice(lineStart, lineEnd === -1 ? void 0 : lineEnd);
|
|
32585
|
+
const start = lineStart + line.length - line.trimStart().length;
|
|
32586
|
+
return [start, lineStart + line.trimEnd().length];
|
|
32587
|
+
}
|
|
32588
|
+
var PASSAGE, COMMENT_START;
|
|
32589
|
+
var init_declaration_check = __esm({
|
|
32590
|
+
"src/core/workspace/declaration-check.ts"() {
|
|
32591
|
+
"use strict";
|
|
32592
|
+
init_tooling();
|
|
32593
|
+
PASSAGE = { "$": "StoryVariables", "%": "StoryTransients" };
|
|
32594
|
+
COMMENT_START = /^(?:\/\/|\/\*|<!--|#)/;
|
|
32595
|
+
}
|
|
32596
|
+
});
|
|
32597
|
+
|
|
32598
|
+
// src/core/workspace/variable-tracker.ts
|
|
32599
|
+
function standaloneMarkup(uri, text3) {
|
|
32600
|
+
const index = new PassageIndex();
|
|
32601
|
+
index.rebuild(uri, text3);
|
|
32602
|
+
return new DocumentMarkup(uri, text3, index.getPassagesInDocument(uri), BUILTIN_CONTEXT);
|
|
32603
|
+
}
|
|
32604
|
+
function usagesOf(byUri, name) {
|
|
32605
|
+
const results = [];
|
|
32606
|
+
for (const usages of byUri.values()) {
|
|
32607
|
+
for (const u of usages) {
|
|
32608
|
+
if (u.baseName === name) results.push({ uri: u.uri, range: u.range });
|
|
32609
|
+
}
|
|
32610
|
+
}
|
|
32611
|
+
return results;
|
|
32612
|
+
}
|
|
32613
|
+
var BUILTIN_CONTEXT, ARRAY_MEMBERS, DeclarationSet, VariableTracker;
|
|
32614
|
+
var init_variable_tracker = __esm({
|
|
32615
|
+
"src/core/workspace/variable-tracker.ts"() {
|
|
32616
|
+
"use strict";
|
|
32617
|
+
init_tooling();
|
|
32618
|
+
init_executable_refs();
|
|
32619
|
+
init_passage_markup();
|
|
32620
|
+
init_text();
|
|
32621
|
+
init_passage_index();
|
|
32622
|
+
init_declaration_check();
|
|
32623
|
+
BUILTIN_CONTEXT = { macros: builtinMacros, isBlock: isBlockMacro };
|
|
32624
|
+
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
32625
|
+
...Object.getOwnPropertyNames(Array.prototype),
|
|
32626
|
+
...Object.getOwnPropertyNames(Object.prototype),
|
|
32627
|
+
"at",
|
|
32628
|
+
"flat",
|
|
32629
|
+
"flatMap",
|
|
32630
|
+
"includes",
|
|
32631
|
+
"findLast",
|
|
32632
|
+
"findLastIndex",
|
|
32633
|
+
"toReversed",
|
|
32634
|
+
"toSorted",
|
|
32635
|
+
"toSpliced",
|
|
32636
|
+
"with"
|
|
32637
|
+
]);
|
|
32638
|
+
DeclarationSet = class {
|
|
32639
|
+
constructor(sigil) {
|
|
32640
|
+
this.sigil = sigil;
|
|
32641
|
+
}
|
|
32642
|
+
sigil;
|
|
32643
|
+
present = false;
|
|
32644
|
+
declared = /* @__PURE__ */ new Map();
|
|
32645
|
+
/** Every declaration, including earlier defaults for the same binding. */
|
|
32646
|
+
occurrences = [];
|
|
32647
|
+
/** Lines Spindle rejects. */
|
|
32648
|
+
problems = [];
|
|
32649
|
+
/** The names declared, with the range of sigil and name of the first. */
|
|
32650
|
+
names = /* @__PURE__ */ new Map();
|
|
32651
|
+
clear() {
|
|
32652
|
+
this.present = false;
|
|
32653
|
+
this.declared.clear();
|
|
32654
|
+
this.occurrences.length = 0;
|
|
32655
|
+
this.problems = [];
|
|
32656
|
+
this.names.clear();
|
|
32657
|
+
}
|
|
32658
|
+
/** Read the passage `content`, which starts at line `contentStartLine` of the document `uri`. */
|
|
32659
|
+
read(content2, contentStartLine, uri) {
|
|
32660
|
+
this.clear();
|
|
32661
|
+
this.present = true;
|
|
32662
|
+
const { text: text3, declarations, problems } = readDeclarations(content2, this.sigil);
|
|
32663
|
+
const lineStarts = buildLineStarts(text3);
|
|
32664
|
+
const position = (offset2) => {
|
|
32665
|
+
const { line, character } = offsetToPosition(offset2, lineStarts);
|
|
32666
|
+
return { line: contentStartLine + line, character };
|
|
32667
|
+
};
|
|
32668
|
+
for (const problem of problems) {
|
|
32669
|
+
this.problems.push({ message: problem.message, range: { start: position(problem.start), end: position(problem.end) } });
|
|
32670
|
+
}
|
|
32671
|
+
for (const declaration of declarations) {
|
|
32672
|
+
const { name, schema } = declaration;
|
|
32673
|
+
const declarationRange = { start: position(declaration.nameStart - 1), end: position(declaration.nameEnd) };
|
|
32674
|
+
const declared = { name, sigil: this.sigil, declarationRange };
|
|
32675
|
+
if (uri !== void 0) declared.declarationUri = uri;
|
|
32676
|
+
if (schema) {
|
|
32677
|
+
if (schema.type !== "null") declared.type = schema.type;
|
|
32678
|
+
if (schema.fields && schema.fields.size > 0) declared.fields = [...schema.fields.keys()];
|
|
32679
|
+
}
|
|
32680
|
+
this.declared.set(name, declared);
|
|
32681
|
+
this.occurrences.push(declared);
|
|
32682
|
+
if (!this.names.has(name)) this.names.set(name, declarationRange);
|
|
32683
|
+
}
|
|
32684
|
+
}
|
|
32685
|
+
};
|
|
32686
|
+
VariableTracker = class {
|
|
32687
|
+
variables = new DeclarationSet("$");
|
|
32688
|
+
transients = new DeclarationSet("%");
|
|
32689
|
+
/** Per-URI list of variable usages. */
|
|
32690
|
+
usagesByUri = /* @__PURE__ */ new Map();
|
|
32691
|
+
/** Per-URI list of transient variable usages. */
|
|
32692
|
+
transientUsagesByUri = /* @__PURE__ */ new Map();
|
|
32693
|
+
/**
|
|
32694
|
+
* Parse the StoryVariables passage content for declarations.
|
|
32695
|
+
* Each line like `$name = value` becomes a declaration.
|
|
32696
|
+
*/
|
|
32697
|
+
parseStoryVariables(content2, contentStartLine = 0, uri) {
|
|
32698
|
+
this.variables.read(content2, contentStartLine, uri);
|
|
32699
|
+
}
|
|
32700
|
+
/** Forget StoryVariables declarations, e.g. after the passage was removed. */
|
|
32701
|
+
clearStoryVariables() {
|
|
32702
|
+
this.variables.clear();
|
|
32703
|
+
}
|
|
32704
|
+
/** Forget StoryTransients declarations, e.g. after the passage was removed. */
|
|
32705
|
+
clearStoryTransients() {
|
|
32706
|
+
this.transients.clear();
|
|
32707
|
+
}
|
|
32708
|
+
/**
|
|
32709
|
+
* Parse the StoryTransients passage content for declarations.
|
|
32710
|
+
* Each line like `%name = value` becomes a declaration.
|
|
32711
|
+
*/
|
|
32712
|
+
parseStoryTransients(content2, contentStartLine = 0, uri) {
|
|
32713
|
+
this.transients.read(content2, contentStartLine, uri);
|
|
32714
|
+
}
|
|
32715
|
+
/**
|
|
32716
|
+
* Scan a document for variable usages: the `$` and `%` references in the
|
|
32717
|
+
* markup of its passages (see `collectVariableReferences`). The workspace
|
|
32718
|
+
* passes the `markup` it reads the document with, which knows the project's
|
|
32719
|
+
* macros and widgets; without it the built-in macros decide.
|
|
32720
|
+
*/
|
|
32721
|
+
scanDocument(uri, text3, markup = standaloneMarkup(uri, text3)) {
|
|
32722
|
+
this.removeDocument(uri);
|
|
32723
|
+
const usages = [];
|
|
32724
|
+
const transientUsages = [];
|
|
32725
|
+
for (const passage of markup.passages) {
|
|
32726
|
+
for (const ref2 of collectVariableReferences2(passage)) {
|
|
32727
|
+
const usage = {
|
|
32728
|
+
uri,
|
|
32729
|
+
baseName: ref2.name,
|
|
32730
|
+
fullName: ref2.path,
|
|
32731
|
+
range: passage.range(ref2.start, ref2.end)
|
|
32732
|
+
};
|
|
32733
|
+
(ref2.sigil === "$" ? usages : transientUsages).push(usage);
|
|
32734
|
+
}
|
|
32735
|
+
}
|
|
32736
|
+
if (usages.length > 0) this.usagesByUri.set(uri, usages);
|
|
32737
|
+
if (transientUsages.length > 0) this.transientUsagesByUri.set(uri, transientUsages);
|
|
32738
|
+
}
|
|
32739
|
+
/** Forget the usages recorded for a document, e.g. after it was deleted. */
|
|
32740
|
+
removeDocument(uri) {
|
|
32741
|
+
this.usagesByUri.delete(uri);
|
|
32742
|
+
this.transientUsagesByUri.delete(uri);
|
|
32743
|
+
}
|
|
32744
|
+
/** Get all declared variables. */
|
|
32745
|
+
getDeclared() {
|
|
32746
|
+
return this.variables.declared;
|
|
32747
|
+
}
|
|
32748
|
+
/** Get all usages of a variable by base name. */
|
|
32749
|
+
getUsages(name) {
|
|
32750
|
+
return usagesOf(this.usagesByUri, name);
|
|
32751
|
+
}
|
|
32752
|
+
/** Every declaration location of a story or transient binding, in source order. */
|
|
32753
|
+
getDeclarations(name, sigil) {
|
|
32754
|
+
const declarations = sigil === "$" ? this.variables : this.transients;
|
|
32755
|
+
return declarations.occurrences.flatMap((d) => d.name === name && d.declarationUri !== void 0 && d.declarationRange ? [{ uri: d.declarationUri, range: d.declarationRange }] : []);
|
|
32756
|
+
}
|
|
32757
|
+
/** The executable usage or declaration containing the cursor, in source coordinates. */
|
|
32758
|
+
referenceAt(uri, position) {
|
|
32759
|
+
const contains = (range2) => (position.line > range2.start.line || position.line === range2.start.line && position.character >= range2.start.character) && (position.line < range2.end.line || position.line === range2.end.line && position.character <= range2.end.character);
|
|
32760
|
+
let atEnd = null;
|
|
32761
|
+
for (const [sigil, usages, declarations] of [
|
|
32762
|
+
["$", this.usagesByUri.get(uri), this.variables.occurrences],
|
|
32763
|
+
["%", this.transientUsagesByUri.get(uri), this.transients.occurrences]
|
|
32764
|
+
]) {
|
|
32765
|
+
const candidates = (usages ?? []).map((u) => ({ name: u.baseName, range: u.range }));
|
|
32766
|
+
for (const declaration of declarations) {
|
|
32767
|
+
if (declaration.declarationUri === uri && declaration.declarationRange) candidates.push({ name: declaration.name, range: declaration.declarationRange });
|
|
32768
|
+
}
|
|
32769
|
+
for (const candidate of candidates) {
|
|
32770
|
+
if (!contains(candidate.range)) continue;
|
|
32771
|
+
const symbol2 = { sigil, ...candidate };
|
|
32772
|
+
if (position.line !== candidate.range.end.line || position.character < candidate.range.end.character) return symbol2;
|
|
32773
|
+
atEnd ??= symbol2;
|
|
32774
|
+
}
|
|
32775
|
+
}
|
|
32776
|
+
return atEnd;
|
|
32777
|
+
}
|
|
32778
|
+
/** Whether a StoryVariables passage has been parsed. */
|
|
32779
|
+
hasStoryVariables() {
|
|
32780
|
+
return this.variables.present;
|
|
32781
|
+
}
|
|
32782
|
+
/** Get all declared transient variables. */
|
|
32783
|
+
getDeclaredTransient() {
|
|
32784
|
+
return this.transients.declared;
|
|
32785
|
+
}
|
|
32786
|
+
/** Get all usages of a transient variable by base name. */
|
|
32787
|
+
getTransientUsages(name) {
|
|
32788
|
+
return usagesOf(this.transientUsagesByUri, name);
|
|
32789
|
+
}
|
|
32790
|
+
/** Whether a StoryTransients passage has been parsed. */
|
|
32791
|
+
hasStoryTransients() {
|
|
32792
|
+
return this.transients.present;
|
|
32793
|
+
}
|
|
32794
|
+
/**
|
|
32795
|
+
* Get `$var.member` / `%var.member` accesses in a document where `var` is
|
|
32796
|
+
* declared with an array literal default and `member` is not a property
|
|
32797
|
+
* that JavaScript arrays have (so the access always yields undefined).
|
|
32798
|
+
*/
|
|
32799
|
+
getArrayMemberAccesses(uri) {
|
|
32800
|
+
const results = [];
|
|
32801
|
+
const sources = [
|
|
32802
|
+
["$", this.usagesByUri.get(uri), this.variables.declared],
|
|
32803
|
+
["%", this.transientUsagesByUri.get(uri), this.transients.declared]
|
|
32804
|
+
];
|
|
32805
|
+
for (const [sigil, usages, declared] of sources) {
|
|
32806
|
+
for (const u of usages ?? []) {
|
|
32807
|
+
const member = u.fullName.split(".")[1];
|
|
32808
|
+
if (member === void 0) continue;
|
|
32809
|
+
if (declared.get(u.baseName)?.type !== "array") continue;
|
|
32810
|
+
if (ARRAY_MEMBERS.has(member) || /^(0|[1-9]\d*)$/.test(member) && Number(member) < 4294967295) continue;
|
|
32811
|
+
results.push({ sigil, name: u.baseName, member, range: u.range });
|
|
32812
|
+
}
|
|
32813
|
+
}
|
|
32814
|
+
return results;
|
|
32815
|
+
}
|
|
32816
|
+
/** StoryVariables lines that stop Spindle from starting. */
|
|
32817
|
+
getInvalidDeclarations() {
|
|
32818
|
+
return this.variables.problems;
|
|
32819
|
+
}
|
|
32820
|
+
/**
|
|
32821
|
+
* StoryTransients lines that stop Spindle from starting, and the names it
|
|
32822
|
+
* declares that StoryVariables declares too.
|
|
32823
|
+
*/
|
|
32824
|
+
getInvalidTransientDeclarations() {
|
|
32825
|
+
const collisions = [];
|
|
32826
|
+
if (this.variables.present) {
|
|
32827
|
+
for (const [name, range2] of this.transients.names) {
|
|
32828
|
+
if (!this.variables.names.has(name)) continue;
|
|
32829
|
+
collisions.push({
|
|
32830
|
+
message: `StoryTransients: Variable "${name}" is already declared in StoryVariables. Names must be unique across scopes.`,
|
|
32831
|
+
range: range2
|
|
32832
|
+
});
|
|
32833
|
+
}
|
|
32834
|
+
}
|
|
32835
|
+
return [...this.transients.problems, ...collisions].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
32836
|
+
}
|
|
32837
|
+
};
|
|
32838
|
+
}
|
|
32839
|
+
});
|
|
32840
|
+
|
|
32841
|
+
// src/core/markup/tokens.ts
|
|
32842
|
+
function* markupTokens(passage) {
|
|
32843
|
+
yield* passage.tokens;
|
|
32844
|
+
for (const piece of passage.pieces) {
|
|
32845
|
+
if (piece.kind === "text") yield* piece.tokens;
|
|
32846
|
+
}
|
|
32847
|
+
}
|
|
32848
|
+
function* macroTokens(passage) {
|
|
32849
|
+
for (const token of markupTokens(passage)) {
|
|
32850
|
+
if (token.type === "macro") yield token;
|
|
32851
|
+
}
|
|
32852
|
+
}
|
|
32853
|
+
var init_tokens = __esm({
|
|
32854
|
+
"src/core/markup/tokens.ts"() {
|
|
32855
|
+
"use strict";
|
|
32856
|
+
}
|
|
32857
|
+
});
|
|
32858
|
+
|
|
32859
|
+
// src/core/markup/widgets.ts
|
|
32860
|
+
function widgetSources(passages, macros) {
|
|
32861
|
+
const sources = /* @__PURE__ */ new Map();
|
|
32862
|
+
for (const passage of passages) {
|
|
32863
|
+
if (!passage.isMarkup) continue;
|
|
32864
|
+
const { name, tags } = passage.passage;
|
|
32865
|
+
for (const definition2 of widgetDefinitions([{ name, tags, content: passage.content }], macros)) {
|
|
32866
|
+
sources.set(definition2, passage);
|
|
32867
|
+
}
|
|
32868
|
+
}
|
|
32869
|
+
return sources;
|
|
32870
|
+
}
|
|
32871
|
+
var init_widgets = __esm({
|
|
32872
|
+
"src/core/markup/widgets.ts"() {
|
|
32873
|
+
"use strict";
|
|
32874
|
+
init_tooling();
|
|
32875
|
+
}
|
|
32876
|
+
});
|
|
32877
|
+
|
|
32878
|
+
// src/core/workspace/widget-registry.ts
|
|
32879
|
+
var WidgetRegistry;
|
|
32880
|
+
var init_widget_registry = __esm({
|
|
32881
|
+
"src/core/workspace/widget-registry.ts"() {
|
|
32882
|
+
"use strict";
|
|
32883
|
+
init_tooling();
|
|
32884
|
+
init_tokens();
|
|
32885
|
+
init_widgets();
|
|
32886
|
+
WidgetRegistry = class {
|
|
32887
|
+
/** Widgets keyed by lower-cased name; Spindle resolves widgets case-insensitively. */
|
|
32888
|
+
widgets = /* @__PURE__ */ new Map();
|
|
32889
|
+
/** Half-typed declarations remain discoverable in the editor, without replacing effective widgets. */
|
|
32890
|
+
incomplete = /* @__PURE__ */ new Map();
|
|
32891
|
+
/** Per-URI set of lower-cased macro names opened in that document. */
|
|
32892
|
+
invokedByUri = /* @__PURE__ */ new Map();
|
|
32893
|
+
/**
|
|
32894
|
+
* Read the widget definitions of `passages` (all of them, in workspace
|
|
32895
|
+
* order). Public tooling selects the closed definitions in runtime phase
|
|
32896
|
+
* order, preserving their identities so the original source is retained.
|
|
32897
|
+
*/
|
|
32898
|
+
scan(passages, macros) {
|
|
32899
|
+
this.widgets.clear();
|
|
32900
|
+
this.incomplete.clear();
|
|
32901
|
+
const sources = widgetSources(passages, [...macros]);
|
|
32902
|
+
const effective = new Set(effectiveWidgetDefinitions([...sources.keys()]));
|
|
32903
|
+
for (const [definition2, markup] of sources) {
|
|
32904
|
+
const widget = {
|
|
32905
|
+
tooling: definition2,
|
|
32906
|
+
name: definition2.name,
|
|
32907
|
+
params: definition2.params,
|
|
32908
|
+
uri: markup.passage.uri,
|
|
32909
|
+
range: markup.range(definition2.start, definition2.end),
|
|
32910
|
+
nameRange: markup.range(definition2.nameStart, definition2.nameEnd),
|
|
32911
|
+
block: definition2.block
|
|
32912
|
+
};
|
|
32913
|
+
const key = definition2.name.toLowerCase();
|
|
32914
|
+
if (effective.has(definition2)) this.widgets.set(key, widget);
|
|
32915
|
+
else if (definition2.closeStart === void 0) this.incomplete.set(key, widget);
|
|
32916
|
+
}
|
|
32917
|
+
}
|
|
32918
|
+
/** The effective definition, or an incomplete declaration while its first definition is being typed. */
|
|
32919
|
+
getWidget(name) {
|
|
32920
|
+
const key = name.toLowerCase();
|
|
32921
|
+
return this.widgets.get(key) ?? this.incomplete.get(key);
|
|
32922
|
+
}
|
|
32923
|
+
/** Get the effective registered definitions, excluding incomplete editor declarations. */
|
|
32924
|
+
getAllWidgets() {
|
|
32925
|
+
return Array.from(this.widgets.values());
|
|
32926
|
+
}
|
|
32927
|
+
/** The effective widget whose declared name contains the cursor. */
|
|
32928
|
+
definitionAt(uri, position) {
|
|
32929
|
+
return this.getAllWidgets().find((widget) => {
|
|
32930
|
+
const { start, end } = widget.nameRange;
|
|
32931
|
+
return widget.uri === uri && position.line === start.line && position.character >= start.character && position.character <= end.character;
|
|
32932
|
+
});
|
|
32933
|
+
}
|
|
32934
|
+
/**
|
|
32935
|
+
* Record the macros opened in a document (its tags and those in the labels
|
|
32936
|
+
* and attribute values that hold markup), replacing any previous record for
|
|
32937
|
+
* that URI. Spindle resolves widget names case-insensitively, so names are
|
|
32938
|
+
* stored lower-cased.
|
|
32939
|
+
*/
|
|
32940
|
+
recordInvocations(uri, passages) {
|
|
32941
|
+
const names = /* @__PURE__ */ new Set();
|
|
32942
|
+
for (const passage of passages) {
|
|
32943
|
+
for (const macro of macroTokens(passage)) {
|
|
32944
|
+
if (!macro.isClose) names.add(macro.name.toLowerCase());
|
|
32945
|
+
}
|
|
32946
|
+
}
|
|
32947
|
+
this.invokedByUri.set(uri, names);
|
|
32948
|
+
}
|
|
32949
|
+
/** Forget all recorded invocations. */
|
|
32950
|
+
clearInvocations() {
|
|
32951
|
+
this.invokedByUri.clear();
|
|
32952
|
+
}
|
|
32953
|
+
/** Whether any recorded document invokes the widget `{name}`. */
|
|
32954
|
+
isInvoked(name) {
|
|
32955
|
+
const key = name.toLowerCase();
|
|
32956
|
+
for (const names of this.invokedByUri.values()) {
|
|
32957
|
+
if (names.has(key)) return true;
|
|
32958
|
+
}
|
|
32959
|
+
return false;
|
|
32960
|
+
}
|
|
32961
|
+
};
|
|
32962
|
+
}
|
|
32963
|
+
});
|
|
32964
|
+
|
|
32965
|
+
// src/core/markup/markup-index.ts
|
|
32966
|
+
function sameHeaders(a, b) {
|
|
32967
|
+
return a.length === b.length && a.every((p, i2) => p === b[i2]);
|
|
32968
|
+
}
|
|
32969
|
+
var MarkupIndex;
|
|
32970
|
+
var init_markup_index = __esm({
|
|
32971
|
+
"src/core/markup/markup-index.ts"() {
|
|
32972
|
+
"use strict";
|
|
32973
|
+
init_passage_markup();
|
|
32974
|
+
MarkupIndex = class {
|
|
32975
|
+
constructor(sources) {
|
|
32976
|
+
this.sources = sources;
|
|
32977
|
+
}
|
|
32978
|
+
sources;
|
|
32979
|
+
cache = /* @__PURE__ */ new Map();
|
|
32980
|
+
context;
|
|
32981
|
+
/** The markup of document `uri`, or undefined if it is not open. */
|
|
32982
|
+
get(uri) {
|
|
32983
|
+
const text3 = this.sources.text(uri);
|
|
32984
|
+
if (text3 === void 0) return void 0;
|
|
32985
|
+
const kept3 = this.cache.get(uri);
|
|
32986
|
+
const passages = this.sources.passages(uri);
|
|
32987
|
+
if (kept3 && kept3.text === text3 && sameHeaders(kept3.passages.map((p) => p.passage), passages)) return kept3;
|
|
32988
|
+
this.context ??= this.sources.context();
|
|
32989
|
+
const fresh = new DocumentMarkup(uri, text3, passages, this.context);
|
|
32990
|
+
this.cache.set(uri, fresh);
|
|
32991
|
+
return fresh;
|
|
32992
|
+
}
|
|
32993
|
+
/** The macros or widgets changed: every document's markup is read again on demand. */
|
|
32994
|
+
invalidate() {
|
|
32995
|
+
this.cache.clear();
|
|
32996
|
+
this.context = void 0;
|
|
32997
|
+
}
|
|
32998
|
+
};
|
|
32999
|
+
}
|
|
33000
|
+
});
|
|
33001
|
+
|
|
33002
|
+
// src/core/workspace/spindle-version.ts
|
|
33003
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
33004
|
+
import { dirname as dirname2, join as join3, resolve as resolve4 } from "node:path";
|
|
33005
|
+
import { fileURLToPath } from "node:url";
|
|
33006
|
+
function parseSpindleVersion(text3) {
|
|
33007
|
+
const m = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?\s*$/.exec(text3);
|
|
33008
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : void 0;
|
|
32332
33009
|
}
|
|
32333
33010
|
function atLeast(version2, minimum) {
|
|
32334
33011
|
const min = parseSpindleVersion(minimum);
|
|
@@ -32924,1065 +33601,470 @@ var init_macro_supplements = __esm({
|
|
|
32924
33601
|
{ name: "variable", documentation: "Variable to watch for changes." }
|
|
32925
33602
|
]
|
|
32926
33603
|
},
|
|
32927
|
-
unwatch: {
|
|
32928
|
-
name: "unwatch",
|
|
32929
|
-
description: "Stops watching a variable.\n\nUsage: `{unwatch $var}`",
|
|
32930
|
-
parameters: ["...text"],
|
|
32931
|
-
parameterDocs: [
|
|
32932
|
-
{ name: "variable", documentation: "Variable to stop watching." }
|
|
32933
|
-
]
|
|
32934
|
-
},
|
|
32935
|
-
unless: {
|
|
32936
|
-
name: "unless",
|
|
32937
|
-
container: true,
|
|
32938
|
-
description: "Conditionally displays its contents when the expression is falsy. The inverse of `{if}`.\n\nUsage:\n\n```\n{unless condition}\n ...\n{/unless}\n```",
|
|
32939
|
-
parameters: ["...text"],
|
|
32940
|
-
parameterDocs: [
|
|
32941
|
-
{ name: "condition", documentation: "Expression; the contents show when it is falsy." }
|
|
32942
|
-
]
|
|
32943
|
-
}
|
|
32944
|
-
};
|
|
32945
|
-
}
|
|
32946
|
-
});
|
|
32947
|
-
|
|
32948
|
-
// src/core/workspace/workspace-model.ts
|
|
32949
|
-
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
32950
|
-
function storyInitMacros(content2) {
|
|
32951
|
-
return passagePieces(content2, builtinMacros).flatMap((piece) => piece.kind === "code" && piece.goal === "statements" ? discoverMacros(piece.code) : []);
|
|
32952
|
-
}
|
|
32953
|
-
var WorkspaceModel;
|
|
32954
|
-
var init_workspace_model = __esm({
|
|
32955
|
-
"src/core/workspace/workspace-model.ts"() {
|
|
32956
|
-
"use strict";
|
|
32957
|
-
init_tooling();
|
|
32958
|
-
init_document_store();
|
|
32959
|
-
init_passage_index();
|
|
32960
|
-
init_macro_registry2();
|
|
32961
|
-
init_variable_tracker();
|
|
32962
|
-
init_widget_registry();
|
|
32963
|
-
init_markup_index();
|
|
32964
|
-
init_macro_sources();
|
|
32965
|
-
init_story_format();
|
|
32966
|
-
init_spindle_version();
|
|
32967
|
-
init_macro_supplements();
|
|
32968
|
-
WorkspaceModel = class extends EventEmitter2 {
|
|
32969
|
-
documents;
|
|
32970
|
-
passages;
|
|
32971
|
-
_macros;
|
|
32972
|
-
get macros() {
|
|
32973
|
-
this.ensureCurrent();
|
|
32974
|
-
return this._macros;
|
|
32975
|
-
}
|
|
32976
|
-
_variables;
|
|
32977
|
-
get variables() {
|
|
32978
|
-
this.ensureCurrent();
|
|
32979
|
-
return this._variables;
|
|
32980
|
-
}
|
|
32981
|
-
_widgets;
|
|
32982
|
-
get widgets() {
|
|
32983
|
-
this.ensureCurrent();
|
|
32984
|
-
return this._widgets;
|
|
32985
|
-
}
|
|
32986
|
-
/** Every document's markup, read through Spindle's tooling API. */
|
|
32987
|
-
_markup;
|
|
32988
|
-
get markup() {
|
|
32989
|
-
this.ensureCurrent();
|
|
32990
|
-
return this._markup;
|
|
32991
|
-
}
|
|
32992
|
-
/** True after initialize() has completed (full workspace scan done). */
|
|
32993
|
-
initialized = false;
|
|
32994
|
-
/** The story format declared by StoryData, updated with the passages. */
|
|
32995
|
-
format = UNDECLARED_STORY_FORMAT;
|
|
32996
|
-
/**
|
|
32997
|
-
* The Spindle the project targets: the version installed under the
|
|
32998
|
-
* workspace root, else StoryData's `format-version`. Only a version below
|
|
32999
|
-
* {@link MINIMUM_SPINDLE_VERSION} matters (it is reported, not emulated).
|
|
33000
|
-
*/
|
|
33001
|
-
_target;
|
|
33002
|
-
get target() {
|
|
33003
|
-
this.ensureCurrent();
|
|
33004
|
-
return this._target;
|
|
33005
|
-
}
|
|
33006
|
-
workspaceRoot;
|
|
33007
|
-
/** The version installed under the workspace root, read at startup and on refresh(). */
|
|
33008
|
-
installedVersion;
|
|
33009
|
-
/** The `format-version` StoryData declares. */
|
|
33010
|
-
declaredVersion;
|
|
33011
|
-
analysisPending = false;
|
|
33012
|
-
debounceTimer = null;
|
|
33013
|
-
DEBOUNCE_MS = 200;
|
|
33014
|
-
/** Bound listeners for cleanup in dispose(). */
|
|
33015
|
-
onDocumentChanged;
|
|
33016
|
-
onDocumentOpened;
|
|
33017
|
-
onDocumentClosed;
|
|
33018
|
-
constructor(config2) {
|
|
33019
|
-
super();
|
|
33020
|
-
this.documents = new DocumentStore();
|
|
33021
|
-
this.passages = new PassageIndex();
|
|
33022
|
-
this._macros = new MacroRegistry();
|
|
33023
|
-
this.macros.onChange = () => this.markup?.invalidate();
|
|
33024
|
-
this._variables = new VariableTracker();
|
|
33025
|
-
this._widgets = new WidgetRegistry();
|
|
33026
|
-
this._markup = new MarkupIndex({
|
|
33027
|
-
text: (uri) => this.documents.getText(uri),
|
|
33028
|
-
passages: (uri) => this.passages.getPassagesInDocument(uri),
|
|
33029
|
-
context: () => ({
|
|
33030
|
-
macros: this.toolingMacros(),
|
|
33031
|
-
isBlock: (name) => this.isContainer(name)
|
|
33032
|
-
})
|
|
33033
|
-
});
|
|
33034
|
-
this.workspaceRoot = config2?.workspaceRoot;
|
|
33035
|
-
this.installedVersion = this.workspaceRoot ? readInstalledSpindleVersion(this.workspaceRoot) : void 0;
|
|
33036
|
-
this._target = resolveSpindleTarget(this.installedVersion);
|
|
33037
|
-
this.macros.loadBuiltins();
|
|
33038
|
-
this.macros.loadSupplements(macro_supplements_default);
|
|
33039
|
-
this.onDocumentChanged = (uri) => this.handleDocumentChange(uri);
|
|
33040
|
-
this.onDocumentOpened = (uri) => this.handleDocumentChange(uri);
|
|
33041
|
-
this.onDocumentClosed = (uri) => this.handleDocumentClose(uri);
|
|
33042
|
-
this.documents.on("documentChanged", this.onDocumentChanged);
|
|
33043
|
-
this.documents.on("documentOpened", this.onDocumentOpened);
|
|
33044
|
-
this.documents.on("documentClosed", this.onDocumentClosed);
|
|
33045
|
-
}
|
|
33046
|
-
/**
|
|
33047
|
-
* Initialize the workspace in CLI/batch mode.
|
|
33048
|
-
* Loads all file contents, builds indices, and emits 'modelReady'.
|
|
33049
|
-
*/
|
|
33050
|
-
initialize(fileContents) {
|
|
33051
|
-
this.documents.removeListener("documentOpened", this.onDocumentOpened);
|
|
33052
|
-
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
33053
|
-
for (const [uri, text3] of fileContents) {
|
|
33054
|
-
this.documents.open(uri, text3);
|
|
33055
|
-
}
|
|
33056
|
-
this.documents.on("documentOpened", this.onDocumentOpened);
|
|
33057
|
-
this.documents.on("documentChanged", this.onDocumentChanged);
|
|
33058
|
-
this.rebuildAll();
|
|
33059
|
-
this.initialized = true;
|
|
33060
|
-
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
33061
|
-
this.debounceTimer = null;
|
|
33062
|
-
this.emit("modelReady");
|
|
33063
|
-
}
|
|
33064
|
-
/**
|
|
33065
|
-
* Re-run cross-document analysis and schedule 'modelReady', e.g. after
|
|
33066
|
-
* the macro configuration changed without any document changing.
|
|
33067
|
-
*/
|
|
33068
|
-
refresh() {
|
|
33069
|
-
if (this.workspaceRoot) this.installedVersion = readInstalledSpindleVersion(this.workspaceRoot);
|
|
33070
|
-
this.analysisPending = true;
|
|
33071
|
-
this.ensureCurrent();
|
|
33072
|
-
this.scheduleModelReady();
|
|
33073
|
-
}
|
|
33074
|
-
/**
|
|
33075
|
-
* Whether `{name}` opens a container that needs a `{/name}` closing tag:
|
|
33076
|
-
* a block macro, or a block widget (one whose body renders `{@children}`).
|
|
33077
|
-
* Like Spindle's set of block macros, either source makes a name a block.
|
|
33078
|
-
*/
|
|
33079
|
-
isContainer(name) {
|
|
33080
|
-
return this.macros.isBlock(name) || (this.widgets.getWidget(name)?.block ?? false);
|
|
33081
|
-
}
|
|
33082
|
-
/**
|
|
33083
|
-
* Whether document `uri` declares any passage. A Twee document with no
|
|
33084
|
-
* `::` header holds no passage (the compiler ignores text outside one), so
|
|
33085
|
-
* there is no markup in it for any consumer to read.
|
|
33086
|
-
*/
|
|
33087
|
-
hasPassages(uri) {
|
|
33088
|
-
return this.passages.getPassagesInDocument(uri).length > 0;
|
|
33089
|
-
}
|
|
33090
|
-
/**
|
|
33091
|
-
* The story format named by the project's StoryData passage (trimmed), or
|
|
33092
|
-
* undefined if no StoryData passage names one.
|
|
33093
|
-
*/
|
|
33094
|
-
get storyFormat() {
|
|
33095
|
-
this.ensureCurrent();
|
|
33096
|
-
return this.format.name;
|
|
33097
|
-
}
|
|
33098
|
-
/**
|
|
33099
|
-
* Whether Spindle semantics apply to this project. False only if a
|
|
33100
|
-
* StoryData passage names another story format (SugarCube, Harlowe, ...)
|
|
33101
|
-
* and none names Spindle; a missing or unreadable StoryData counts as
|
|
33102
|
-
* Spindle.
|
|
33103
|
-
*/
|
|
33104
|
-
isSpindleProject() {
|
|
33105
|
-
this.ensureCurrent();
|
|
33106
|
-
return this.format.isSpindle;
|
|
33107
|
-
}
|
|
33108
|
-
/** Clean up listeners and timers. */
|
|
33109
|
-
dispose() {
|
|
33110
|
-
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
33111
|
-
this.documents.removeListener("documentOpened", this.onDocumentOpened);
|
|
33112
|
-
this.documents.removeListener("documentClosed", this.onDocumentClosed);
|
|
33113
|
-
if (this.debounceTimer) {
|
|
33114
|
-
clearTimeout(this.debounceTimer);
|
|
33115
|
-
this.debounceTimer = null;
|
|
33116
|
-
}
|
|
33117
|
-
this.removeAllListeners();
|
|
33118
|
-
}
|
|
33119
|
-
/** Index local headers immediately; defer expensive cross-document analysis. */
|
|
33120
|
-
handleDocumentChange(uri) {
|
|
33121
|
-
const text3 = this.documents.getText(uri);
|
|
33122
|
-
if (text3 !== void 0 && !isMacroSource(uri)) {
|
|
33123
|
-
this.passages.rebuild(uri, text3);
|
|
33124
|
-
}
|
|
33125
|
-
this.analysisPending = true;
|
|
33126
|
-
this.updateStoryFormat();
|
|
33127
|
-
this.emit("documentChanged", uri);
|
|
33128
|
-
this.emit("passagesUpdated", uri);
|
|
33129
|
-
this.scheduleModelReady();
|
|
33130
|
-
}
|
|
33131
|
-
/** Remove local indices immediately and schedule cross-document analysis. */
|
|
33132
|
-
handleDocumentClose(uri) {
|
|
33133
|
-
this.passages.remove(uri);
|
|
33134
|
-
this._variables.removeDocument(uri);
|
|
33135
|
-
this.analysisPending = true;
|
|
33136
|
-
this.updateStoryFormat();
|
|
33137
|
-
this.emit("documentClosed", uri);
|
|
33138
|
-
this.emit("passagesUpdated", uri);
|
|
33139
|
-
this.scheduleModelReady();
|
|
33140
|
-
}
|
|
33141
|
-
/** Rebuild all indices from scratch. */
|
|
33142
|
-
rebuildAll() {
|
|
33143
|
-
for (const uri of this.documents.getUris()) {
|
|
33144
|
-
const text3 = this.documents.getText(uri);
|
|
33145
|
-
if (text3 !== void 0 && !isMacroSource(uri)) {
|
|
33146
|
-
this.passages.rebuild(uri, text3);
|
|
33147
|
-
}
|
|
33148
|
-
}
|
|
33149
|
-
this.analysisPending = true;
|
|
33150
|
-
this.ensureCurrent();
|
|
33151
|
-
}
|
|
33152
|
-
/** Finish pending cross-document work once, before a feature reads the indices. */
|
|
33153
|
-
ensureCurrent() {
|
|
33154
|
-
if (!this.analysisPending) return;
|
|
33155
|
-
this.analysisPending = false;
|
|
33156
|
-
this.refreshDiscoveredMacros();
|
|
33157
|
-
this.cascade();
|
|
33158
|
-
this.updateStoryFormat();
|
|
33159
|
-
}
|
|
33160
|
-
/**
|
|
33161
|
-
* Re-read the story format from the StoryData passages after the passage
|
|
33162
|
-
* index changed, emitting 'storyFormatChanged' if it differs. Listeners
|
|
33163
|
-
* that read the analysis indices finish pending analysis through getters.
|
|
33164
|
-
*/
|
|
33165
|
-
updateStoryFormat() {
|
|
33166
|
-
const format = resolveStoryFormat(storyDataFormats(
|
|
33167
|
-
this.passages.getAllPassages(),
|
|
33168
|
-
(uri) => this.documents.getText(uri)
|
|
33169
|
-
));
|
|
33170
|
-
if (format.name === this.format.name && format.isSpindle === this.format.isSpindle) return;
|
|
33171
|
-
this.format = format;
|
|
33172
|
-
this.emit("storyFormatChanged", format);
|
|
33173
|
-
}
|
|
33174
|
-
/**
|
|
33175
|
-
* Re-run static macro discovery over the workspace and replace the
|
|
33176
|
-
* registry's discovered tier (dropping definitions whose source is gone).
|
|
33177
|
-
*
|
|
33178
|
-
* Sources scanned for Story.defineMacro({...}) calls:
|
|
33179
|
-
* - `{do}` blocks in StoryInit passages
|
|
33180
|
-
* - passages tagged `script` (Story JavaScript)
|
|
33181
|
-
* - JavaScript/TypeScript documents in the workspace
|
|
33182
|
-
*/
|
|
33183
|
-
refreshDiscoveredMacros() {
|
|
33184
|
-
const found = [];
|
|
33185
|
-
const init = [];
|
|
33186
|
-
for (const uri of this.documents.getUris().sort()) {
|
|
33187
|
-
const text3 = this.documents.getText(uri);
|
|
33188
|
-
if (!text3) continue;
|
|
33189
|
-
if (isMacroSource(uri)) {
|
|
33190
|
-
found.push(...discoverMacros(text3));
|
|
33191
|
-
continue;
|
|
33192
|
-
}
|
|
33193
|
-
let lines;
|
|
33194
|
-
for (const passage of this.passages.getPassagesInDocument(uri)) {
|
|
33195
|
-
const isStoryInit = passage.name === "StoryInit";
|
|
33196
|
-
const isScript = passage.tags?.includes("script") ?? false;
|
|
33197
|
-
if (!isStoryInit && !isScript) continue;
|
|
33198
|
-
lines ??= text3.split("\n");
|
|
33199
|
-
const content2 = lines.slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
|
|
33200
|
-
if (isScript) found.push(...discoverMacros(content2));
|
|
33201
|
-
else init.push(...storyInitMacros(content2));
|
|
33202
|
-
}
|
|
33203
|
-
}
|
|
33204
|
-
this.macros.setDiscoveredMacros([...found, ...init]);
|
|
33205
|
-
}
|
|
33206
|
-
/**
|
|
33207
|
-
* Cascade: rescan variables and widgets based on current passages.
|
|
33208
|
-
* Called after any passage index update.
|
|
33209
|
-
*
|
|
33210
|
-
* The markup of every document is read again (the macros may have changed),
|
|
33211
|
-
* the widgets are defined from it, and then the variable usages and widget
|
|
33212
|
-
* invocations are read from the same markup, which pairs its tags with the
|
|
33213
|
-
* widgets just defined.
|
|
33214
|
-
*/
|
|
33215
|
-
cascade() {
|
|
33216
|
-
this.markup.invalidate();
|
|
33217
|
-
this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
|
|
33218
|
-
this._target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
|
|
33219
|
-
const declarations = (name) => {
|
|
33220
|
-
const passage = this.passages.getPassage(name);
|
|
33221
|
-
return passage ? this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage) : void 0;
|
|
33222
|
-
};
|
|
33223
|
-
const variables = declarations("StoryVariables");
|
|
33224
|
-
if (variables) this.variables.parseStoryVariables(variables.content, variables.passage.headerEnd.end.line + 1, variables.doc.uri);
|
|
33225
|
-
else this.variables.clearStoryVariables();
|
|
33226
|
-
const transients = declarations("StoryTransients");
|
|
33227
|
-
if (transients) this.variables.parseStoryTransients(transients.content, transients.passage.headerEnd.end.line + 1, transients.doc.uri);
|
|
33228
|
-
else this.variables.clearStoryTransients();
|
|
33229
|
-
const readDocuments = () => this.documents.getUris().flatMap((uri) => {
|
|
33230
|
-
const markup = isMacroSource(uri) ? void 0 : this.markup.get(uri);
|
|
33231
|
-
return markup ? [markup] : [];
|
|
33232
|
-
});
|
|
33233
|
-
const markups = /* @__PURE__ */ new Map();
|
|
33234
|
-
for (const doc of readDocuments()) for (const passage of doc.passages) markups.set(passage.passage, passage);
|
|
33235
|
-
this.widgets.scan(
|
|
33236
|
-
this.passages.getAllPassages().flatMap((passage) => markups.get(passage) ?? []),
|
|
33237
|
-
this.macros.toolingMacros()
|
|
33238
|
-
);
|
|
33239
|
-
this.markup.invalidate();
|
|
33240
|
-
this.widgets.clearInvocations();
|
|
33241
|
-
for (const doc of readDocuments()) {
|
|
33242
|
-
this.variables.scanDocument(doc.uri, doc.text, doc);
|
|
33243
|
-
this.widgets.recordInvocations(doc.uri, doc.passages);
|
|
33244
|
-
}
|
|
33245
|
-
}
|
|
33246
|
-
/** All runtime metadata, with real macros taking precedence over widgets. */
|
|
33247
|
-
toolingMacros() {
|
|
33248
|
-
const macros = this.macros.toolingMacros();
|
|
33249
|
-
return [...macros, ...this.widgets.getAllWidgets().filter((widget) => !this.macros.getMacro(widget.name)).map((widget) => ({ name: widget.name, block: widget.block, subMacros: [], widget: widget.tooling }))];
|
|
33250
|
-
}
|
|
33251
|
-
/** Coalesce analysis and diagnostics across a burst of document changes. */
|
|
33252
|
-
scheduleModelReady() {
|
|
33253
|
-
if (this.debounceTimer) {
|
|
33254
|
-
clearTimeout(this.debounceTimer);
|
|
33255
|
-
}
|
|
33256
|
-
this.debounceTimer = setTimeout(() => {
|
|
33257
|
-
this.debounceTimer = null;
|
|
33258
|
-
this.ensureCurrent();
|
|
33259
|
-
this.emit("modelReady");
|
|
33260
|
-
}, this.DEBOUNCE_MS);
|
|
33261
|
-
}
|
|
33262
|
-
};
|
|
33263
|
-
}
|
|
33264
|
-
});
|
|
33265
|
-
|
|
33266
|
-
// src/core/diagnostic-codes.ts
|
|
33267
|
-
function getSeverity(code) {
|
|
33268
|
-
return severityMap[code];
|
|
33269
|
-
}
|
|
33270
|
-
var DiagnosticCode, severityMap;
|
|
33271
|
-
var init_diagnostic_codes = __esm({
|
|
33272
|
-
"src/core/diagnostic-codes.ts"() {
|
|
33273
|
-
"use strict";
|
|
33274
|
-
DiagnosticCode = {
|
|
33275
|
-
/** The Spindle the project targets is older than the oldest one supported. */
|
|
33276
|
-
UnsupportedSpindleVersion: "SP001",
|
|
33277
|
-
/** `unknown-macro`: a macro that is none of the built-in, configured or defined ones. */
|
|
33278
|
-
UndefinedMacro: "SP100",
|
|
33279
|
-
/** `unclosed-block`, `mismatched-closer`, `stray-closer` of a macro. */
|
|
33280
|
-
MalformedContainer: "SP101",
|
|
33281
|
-
/** Tags of an HTML element Spindle cannot read or pair. */
|
|
33282
|
-
MalformedElement: "SP102",
|
|
33283
|
-
/** A closing tag that closes no macro (`{/set}`), or is malformed (`{/}`, `{/if x}`). */
|
|
33284
|
-
IllegalClosingTag: "SP104",
|
|
33285
|
-
/** `unclosed-link`, `unclosed-expression`, `unclosed-macro`: a `[[`, `{$` or `{name` that is never ended. */
|
|
33286
|
-
UnclosedMarkup: "SP105",
|
|
33287
|
-
/** `code-syntax`: JavaScript that does not parse. */
|
|
33288
|
-
CodeSyntaxError: "SP106",
|
|
33289
|
-
/** `misplaced-branch` (`{else}` outside `{if}`), and a macro outside the parents it needs. */
|
|
33290
|
-
InvalidChildren: "SP107",
|
|
33291
|
-
/** A configured parameter schema rejects the arguments (macros Spindle declares nothing for). */
|
|
33292
|
-
ExpectedNoArguments: "SP108",
|
|
33293
|
-
/** `argument-error`: arguments that do not have their parameters' forms. */
|
|
33294
|
-
ParameterTypeError: "SP109",
|
|
33295
|
-
ParameterWarning: "SP110",
|
|
33296
|
-
TooManyArguments: "SP111",
|
|
33297
|
-
/** `unquoted-passage-name`: `{goto Kitchen}`, which throws a ReferenceError. */
|
|
33298
|
-
UnquotedPassageName: "SP113",
|
|
33299
|
-
ChildMaxExceeded: "SP114",
|
|
33300
|
-
ChildMinNotMet: "SP115",
|
|
33301
|
-
/** `undeclared-variable`. */
|
|
33302
|
-
UndeclaredVariable: "SP200",
|
|
33303
|
-
/** `primitive-field`: a field of a number, string or boolean. */
|
|
33304
|
-
PrimitiveFieldAccess: "SP201",
|
|
33305
|
-
NoStoryVariables: "SP202",
|
|
33306
|
-
/** `undeclared-transient`. */
|
|
33307
|
-
UndeclaredTransient: "SP203",
|
|
33308
|
-
TemporaryAssignedInLoop: "SP205",
|
|
33309
|
-
ArrayMemberAccess: "SP206",
|
|
33310
|
-
InvalidDeclaration: "SP207",
|
|
33311
|
-
/** `reserved-name`: a variable named `__proto__`. */
|
|
33312
|
-
ReservedVariableName: "SP208",
|
|
33313
|
-
/** `unknown-passage`. */
|
|
33314
|
-
BrokenPassageLink: "SP300",
|
|
33315
|
-
WidgetArgCountMismatch: "SP301",
|
|
33316
|
-
IncludeWidgetPassage: "SP302",
|
|
33317
|
-
UnusedWidget: "SP303",
|
|
33318
|
-
LinkRuntimeMismatch: "SP304",
|
|
33319
|
-
LiteralLinkInterpolation: "SP305",
|
|
33320
|
-
DeadEndPassage: "SP400",
|
|
33321
|
-
UnreachablePassage: "SP401",
|
|
33322
|
-
LineTooLong: "SP500"
|
|
33323
|
-
};
|
|
33324
|
-
severityMap = {
|
|
33325
|
-
SP001: "warning",
|
|
33326
|
-
SP100: "warning",
|
|
33327
|
-
SP101: "error",
|
|
33328
|
-
SP102: "error",
|
|
33329
|
-
SP104: "error",
|
|
33330
|
-
SP105: "error",
|
|
33331
|
-
SP106: "error",
|
|
33332
|
-
SP107: "error",
|
|
33333
|
-
SP108: "error",
|
|
33334
|
-
SP109: "error",
|
|
33335
|
-
SP110: "warning",
|
|
33336
|
-
SP111: "error",
|
|
33337
|
-
SP113: "error",
|
|
33338
|
-
SP114: "error",
|
|
33339
|
-
SP115: "error",
|
|
33340
|
-
SP200: "error",
|
|
33341
|
-
SP201: "error",
|
|
33342
|
-
SP202: "info",
|
|
33343
|
-
SP203: "warning",
|
|
33344
|
-
SP205: "warning",
|
|
33345
|
-
SP206: "warning",
|
|
33346
|
-
SP207: "error",
|
|
33347
|
-
SP208: "error",
|
|
33348
|
-
SP300: "warning",
|
|
33349
|
-
SP301: "warning",
|
|
33350
|
-
SP302: "warning",
|
|
33351
|
-
SP303: "hint",
|
|
33352
|
-
SP304: "warning",
|
|
33353
|
-
SP305: "warning",
|
|
33354
|
-
SP400: "hint",
|
|
33355
|
-
SP401: "hint",
|
|
33356
|
-
SP500: "warning"
|
|
33604
|
+
unwatch: {
|
|
33605
|
+
name: "unwatch",
|
|
33606
|
+
description: "Stops watching a variable.\n\nUsage: `{unwatch $var}`",
|
|
33607
|
+
parameters: ["...text"],
|
|
33608
|
+
parameterDocs: [
|
|
33609
|
+
{ name: "variable", documentation: "Variable to stop watching." }
|
|
33610
|
+
]
|
|
33611
|
+
},
|
|
33612
|
+
unless: {
|
|
33613
|
+
name: "unless",
|
|
33614
|
+
container: true,
|
|
33615
|
+
description: "Conditionally displays its contents when the expression is falsy. The inverse of `{if}`.\n\nUsage:\n\n```\n{unless condition}\n ...\n{/unless}\n```",
|
|
33616
|
+
parameters: ["...text"],
|
|
33617
|
+
parameterDocs: [
|
|
33618
|
+
{ name: "condition", documentation: "Expression; the contents show when it is falsy." }
|
|
33619
|
+
]
|
|
33620
|
+
}
|
|
33357
33621
|
};
|
|
33358
33622
|
}
|
|
33359
33623
|
});
|
|
33360
33624
|
|
|
33361
|
-
// src/core/
|
|
33362
|
-
|
|
33363
|
-
|
|
33364
|
-
|
|
33365
|
-
let high = view.sourceOffsets.length - 1;
|
|
33366
|
-
while (low < high) {
|
|
33367
|
-
const mid = Math.ceil((low + high) / 2);
|
|
33368
|
-
if (view.sourceOffsets[mid] <= offset2) low = mid;
|
|
33369
|
-
else high = mid - 1;
|
|
33370
|
-
}
|
|
33371
|
-
return low;
|
|
33372
|
-
}
|
|
33373
|
-
function textAt(passage, start, end) {
|
|
33374
|
-
return passage.pieces.filter((piece) => piece.kind === "text" && piece.offset <= start && pieceOffset(piece, piece.text.length) >= end).at(-1);
|
|
33375
|
-
}
|
|
33376
|
-
function sourceView(passage, start, end) {
|
|
33377
|
-
const owner = passage.pieces.filter((piece) => {
|
|
33378
|
-
const value = piece.kind === "text" ? piece.text : piece.kind === "code" ? piece.code : void 0;
|
|
33379
|
-
return value !== void 0 && piece.offset <= start && pieceOffset(piece, value.length) >= end;
|
|
33380
|
-
}).at(-1);
|
|
33381
|
-
if (owner?.kind === "code") return { ...owner, text: owner.code };
|
|
33382
|
-
return owner?.kind === "text" ? owner : { offset: 0, text: passage.content };
|
|
33383
|
-
}
|
|
33384
|
-
var init_source_view = __esm({
|
|
33385
|
-
"src/core/markup/source-view.ts"() {
|
|
33386
|
-
"use strict";
|
|
33387
|
-
init_tooling();
|
|
33388
|
-
}
|
|
33389
|
-
});
|
|
33390
|
-
|
|
33391
|
-
// src/core/parsing/argument-lexer.ts
|
|
33392
|
-
function lexArguments(source) {
|
|
33393
|
-
let from = 0;
|
|
33394
|
-
return splitArgs(source).map((text3) => {
|
|
33395
|
-
const start = source.indexOf(text3, from);
|
|
33396
|
-
from = start + text3.length;
|
|
33397
|
-
return classifyArgument(text3, start);
|
|
33398
|
-
});
|
|
33399
|
-
}
|
|
33400
|
-
function classifyArgument(text3, start = 0) {
|
|
33401
|
-
const base = { text: text3, start, end: start + text3.length };
|
|
33402
|
-
if (readQuoted(text3, 0)?.end === text3.length) return { ...base, type: 0 /* String */ };
|
|
33403
|
-
if (text3 === "null") return { ...base, type: 3 /* Null */ };
|
|
33404
|
-
if (text3 === "undefined") return { ...base, type: 10 /* Undefined */ };
|
|
33405
|
-
if (text3 === "true" || text3 === "false") return { ...base, type: 2 /* Boolean */ };
|
|
33406
|
-
if (text3 === "NaN") return { ...base, type: 9 /* NaN */ };
|
|
33407
|
-
if (text3 !== "" && !Number.isNaN(Number(text3))) return { ...base, type: 1 /* Number */ };
|
|
33408
|
-
if (/^[$_@%]\w+(?:\.\w+)*$/.test(text3)) {
|
|
33409
|
-
return { ...base, type: 4 /* Variable */, sigil: text3[0], path: text3.slice(1).split(".") };
|
|
33410
|
-
}
|
|
33411
|
-
if (text3.startsWith("[[") && text3.endsWith("]]")) return { ...base, type: 5 /* Link */ };
|
|
33412
|
-
if (/^\[[<>]?img\[/i.test(text3) && text3.endsWith("]]")) return { ...base, type: 6 /* Image */ };
|
|
33413
|
-
return { ...base, type: /^[-\w]+$/.test(text3) ? 8 /* Bareword */ : 7 /* Expression */ };
|
|
33625
|
+
// src/core/workspace/workspace-model.ts
|
|
33626
|
+
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
33627
|
+
function storyInitMacros(content2) {
|
|
33628
|
+
return passagePieces(content2, builtinMacros).flatMap((piece) => piece.kind === "code" && piece.goal === "statements" ? discoverMacros(piece.code) : []);
|
|
33414
33629
|
}
|
|
33415
|
-
var
|
|
33416
|
-
|
|
33630
|
+
var WorkspaceModel;
|
|
33631
|
+
var init_workspace_model = __esm({
|
|
33632
|
+
"src/core/workspace/workspace-model.ts"() {
|
|
33417
33633
|
"use strict";
|
|
33418
33634
|
init_tooling();
|
|
33419
|
-
|
|
33420
|
-
|
|
33421
|
-
|
|
33422
|
-
|
|
33423
|
-
|
|
33424
|
-
|
|
33425
|
-
|
|
33426
|
-
|
|
33427
|
-
|
|
33428
|
-
|
|
33429
|
-
|
|
33430
|
-
|
|
33431
|
-
|
|
33432
|
-
|
|
33433
|
-
|
|
33434
|
-
|
|
33435
|
-
|
|
33436
|
-
for (; ; ) {
|
|
33437
|
-
if (++iter > LEX_LIMIT) throw new Error("Format lex iteration limit exceeded");
|
|
33438
|
-
if (pos >= formatString.length) break;
|
|
33439
|
-
const ch = formatString[pos];
|
|
33440
|
-
if (wsRe.test(ch)) {
|
|
33441
|
-
pos++;
|
|
33442
|
-
} else if (ch === "&") {
|
|
33443
|
-
pos++;
|
|
33444
|
-
const next = formatString[pos];
|
|
33445
|
-
if (next === "+") {
|
|
33446
|
-
pos++;
|
|
33447
|
-
lexed.push({ kind: 3 /* AndNext */ });
|
|
33448
|
-
} else {
|
|
33449
|
-
throw new Error(`Lex: Expected '+' after '&', found '${next ?? "EOF"}'`);
|
|
33450
|
-
}
|
|
33451
|
-
} else if (ch === "|") {
|
|
33452
|
-
pos++;
|
|
33453
|
-
const next = formatString[pos];
|
|
33454
|
-
if (next === "+") {
|
|
33455
|
-
pos++;
|
|
33456
|
-
lexed.push({ kind: 4 /* MaybeNext */ });
|
|
33457
|
-
} else if (next === void 0) {
|
|
33458
|
-
throw new Error("Lex: Expected content after '|'");
|
|
33459
|
-
} else if (wsRe.test(next)) {
|
|
33460
|
-
pos++;
|
|
33461
|
-
lexed.push({ kind: 5 /* Or */ });
|
|
33462
|
-
} else if (identStartRe.test(next) || quoteRe.test(next)) {
|
|
33463
|
-
lexed.push({ kind: 5 /* Or */ });
|
|
33464
|
-
} else {
|
|
33465
|
-
throw new Error(`Lex: Invalid character after '|': '${next}'`);
|
|
33635
|
+
init_document_store();
|
|
33636
|
+
init_passage_index();
|
|
33637
|
+
init_macro_registry2();
|
|
33638
|
+
init_variable_tracker();
|
|
33639
|
+
init_widget_registry();
|
|
33640
|
+
init_markup_index();
|
|
33641
|
+
init_macro_sources();
|
|
33642
|
+
init_story_format();
|
|
33643
|
+
init_spindle_version();
|
|
33644
|
+
init_macro_supplements();
|
|
33645
|
+
WorkspaceModel = class extends EventEmitter2 {
|
|
33646
|
+
documents;
|
|
33647
|
+
passages;
|
|
33648
|
+
_macros;
|
|
33649
|
+
get macros() {
|
|
33650
|
+
this.ensureCurrent();
|
|
33651
|
+
return this._macros;
|
|
33466
33652
|
}
|
|
33467
|
-
|
|
33468
|
-
|
|
33469
|
-
|
|
33470
|
-
|
|
33471
|
-
lexed.push({ kind: 1 /* Literal */, value: quoted.value });
|
|
33472
|
-
} else if (ch === "(" || ch === ")") {
|
|
33473
|
-
pos++;
|
|
33474
|
-
lexed.push({ kind: 2 /* Group */, open: ch === "(" });
|
|
33475
|
-
} else if (identStartRe.test(ch)) {
|
|
33476
|
-
const start = pos;
|
|
33477
|
-
pos++;
|
|
33478
|
-
while (pos < formatString.length) {
|
|
33479
|
-
if (++iter > LEX_LIMIT) throw new Error("Format lex identifier iteration limit exceeded");
|
|
33480
|
-
const c = formatString[pos];
|
|
33481
|
-
if (identStopRe.test(c) || c === void 0) break;
|
|
33482
|
-
if (identStartRe.test(c)) {
|
|
33483
|
-
pos++;
|
|
33484
|
-
} else if (digitRe.test(c)) {
|
|
33485
|
-
throw new Error(`Lex: Digit '${c}' not allowed in parameter type name`);
|
|
33486
|
-
} else {
|
|
33487
|
-
throw new Error(`Lex: Invalid character '${c}' in parameter type name`);
|
|
33488
|
-
}
|
|
33653
|
+
_variables;
|
|
33654
|
+
get variables() {
|
|
33655
|
+
this.ensureCurrent();
|
|
33656
|
+
return this._variables;
|
|
33489
33657
|
}
|
|
33490
|
-
|
|
33491
|
-
|
|
33492
|
-
|
|
33493
|
-
|
|
33494
|
-
} else if (ch === "." && formatString[pos + 1] === "." && formatString[pos + 2] === ".") {
|
|
33495
|
-
pos += 3;
|
|
33496
|
-
lexed.push({ kind: 6 /* Repeat */ });
|
|
33497
|
-
} else {
|
|
33498
|
-
throw new Error(`Lex: Unexpected character '${ch}'`);
|
|
33499
|
-
}
|
|
33500
|
-
}
|
|
33501
|
-
return lexed;
|
|
33502
|
-
}
|
|
33503
|
-
function parseFormat(formatString) {
|
|
33504
|
-
const tokens2 = lexFormat(formatString);
|
|
33505
|
-
if (tokens2.length === 0) return null;
|
|
33506
|
-
let index = 0;
|
|
33507
|
-
let iter = 0;
|
|
33508
|
-
function infixBp(kind) {
|
|
33509
|
-
switch (kind) {
|
|
33510
|
-
case 5 /* Or */:
|
|
33511
|
-
return [9, 10];
|
|
33512
|
-
case 3 /* AndNext */:
|
|
33513
|
-
return [4, 5];
|
|
33514
|
-
case 4 /* MaybeNext */:
|
|
33515
|
-
return [2, 3];
|
|
33516
|
-
default:
|
|
33517
|
-
return null;
|
|
33518
|
-
}
|
|
33519
|
-
}
|
|
33520
|
-
function prefixBp(kind) {
|
|
33521
|
-
switch (kind) {
|
|
33522
|
-
case 4 /* MaybeNext */:
|
|
33523
|
-
return 3;
|
|
33524
|
-
case 6 /* Repeat */:
|
|
33525
|
-
return 8;
|
|
33526
|
-
default:
|
|
33527
|
-
throw new Error(`Parse: Unexpected prefix operator kind ${kind}`);
|
|
33528
|
-
}
|
|
33529
|
-
}
|
|
33530
|
-
function isPrefixOp(t) {
|
|
33531
|
-
return t.kind === 4 /* MaybeNext */ || t.kind === 6 /* Repeat */;
|
|
33532
|
-
}
|
|
33533
|
-
function isValue(t) {
|
|
33534
|
-
return t.kind === 0 /* Type */ || t.kind === 1 /* Literal */;
|
|
33535
|
-
}
|
|
33536
|
-
function exprBp(minBp) {
|
|
33537
|
-
if (++iter > PARSE_LIMIT) throw new Error("Format parse iteration limit exceeded");
|
|
33538
|
-
const cur = tokens2[index];
|
|
33539
|
-
if (!cur) throw new Error("Parse: Unexpected end of format string");
|
|
33540
|
-
index++;
|
|
33541
|
-
let lhs;
|
|
33542
|
-
if (isPrefixOp(cur)) {
|
|
33543
|
-
const rBp = prefixBp(cur.kind);
|
|
33544
|
-
const rhs = exprBp(rBp);
|
|
33545
|
-
lhs = { kind: cur.kind, right: rhs };
|
|
33546
|
-
} else if (cur.kind === 2 /* Group */) {
|
|
33547
|
-
const g = cur;
|
|
33548
|
-
if (!g.open) throw new Error("Parse: Unexpected closing parenthesis");
|
|
33549
|
-
lhs = exprBp(0);
|
|
33550
|
-
const closing = tokens2[index];
|
|
33551
|
-
if (!closing || closing.kind !== 2 /* Group */ || closing.open) {
|
|
33552
|
-
throw new Error("Parse: Expected closing parenthesis");
|
|
33658
|
+
_widgets;
|
|
33659
|
+
get widgets() {
|
|
33660
|
+
this.ensureCurrent();
|
|
33661
|
+
return this._widgets;
|
|
33553
33662
|
}
|
|
33554
|
-
|
|
33555
|
-
|
|
33556
|
-
|
|
33557
|
-
|
|
33558
|
-
|
|
33559
|
-
}
|
|
33560
|
-
for (; ; ) {
|
|
33561
|
-
if (++iter > PARSE_LIMIT) throw new Error("Format parse operator iteration limit exceeded");
|
|
33562
|
-
const op = tokens2[index];
|
|
33563
|
-
if (!op) break;
|
|
33564
|
-
const bp = infixBp(op.kind);
|
|
33565
|
-
if (bp === null) break;
|
|
33566
|
-
const [lBp, rBp] = bp;
|
|
33567
|
-
if (lBp < minBp) break;
|
|
33568
|
-
index++;
|
|
33569
|
-
const rhs = exprBp(rBp);
|
|
33570
|
-
lhs = { kind: op.kind, left: lhs, right: rhs };
|
|
33571
|
-
}
|
|
33572
|
-
return lhs;
|
|
33573
|
-
}
|
|
33574
|
-
return exprBp(0);
|
|
33575
|
-
}
|
|
33576
|
-
function slotAccepts(leaf) {
|
|
33577
|
-
return (arg) => crawlValidate(leaf, [arg], 0).status === 2 /* Success */;
|
|
33578
|
-
}
|
|
33579
|
-
function describeFormat(format) {
|
|
33580
|
-
const cap = (list3) => list3.slice(0, DESCRIBE_ALTERNATIVE_LIMIT);
|
|
33581
|
-
const join5 = (lefts, rights) => cap(lefts.flatMap((l) => rights.map((r) => [...l, ...r])));
|
|
33582
|
-
switch (format.kind) {
|
|
33583
|
-
case 0 /* Type */:
|
|
33584
|
-
return [[{ label: format.type.name[0], optional: false, repeat: false, accepts: slotAccepts(format) }]];
|
|
33585
|
-
case 1 /* Literal */:
|
|
33586
|
-
return [[{ label: `'${format.value}'`, optional: false, repeat: false, accepts: slotAccepts(format) }]];
|
|
33587
|
-
case 3 /* AndNext */:
|
|
33588
|
-
return join5(describeFormat(format.left), describeFormat(format.right));
|
|
33589
|
-
case 4 /* MaybeNext */: {
|
|
33590
|
-
const rights = describeFormat(format.right).map(
|
|
33591
|
-
(seq) => seq.map((slot) => ({ ...slot, optional: true }))
|
|
33592
|
-
);
|
|
33593
|
-
return join5(format.left ? describeFormat(format.left) : [[]], rights);
|
|
33594
|
-
}
|
|
33595
|
-
case 5 /* Or */:
|
|
33596
|
-
return cap([...describeFormat(format.left), ...describeFormat(format.right)]);
|
|
33597
|
-
case 6 /* Repeat */:
|
|
33598
|
-
return describeFormat(format.right).map(
|
|
33599
|
-
(seq) => seq.map((slot) => ({ ...slot, optional: true, repeat: true }))
|
|
33600
|
-
);
|
|
33601
|
-
}
|
|
33602
|
-
}
|
|
33603
|
-
function makeError(status, message, argIndex, rank = 0) {
|
|
33604
|
-
return {
|
|
33605
|
-
status,
|
|
33606
|
-
rank,
|
|
33607
|
-
argIndex,
|
|
33608
|
-
errors: [{ message, argIndex }],
|
|
33609
|
-
warnings: []
|
|
33610
|
-
};
|
|
33611
|
-
}
|
|
33612
|
-
function makeSuccess(argIndex, rank, warnings = []) {
|
|
33613
|
-
return { status: 2 /* Success */, rank, argIndex, errors: [], warnings };
|
|
33614
|
-
}
|
|
33615
|
-
function isFail(s) {
|
|
33616
|
-
return s === 1 /* Failure */ || s === 0 /* NotFound */;
|
|
33617
|
-
}
|
|
33618
|
-
function crawlValidate(format, args2, startIndex, state) {
|
|
33619
|
-
const memo2 = /* @__PURE__ */ new Map();
|
|
33620
|
-
function append(left, right) {
|
|
33621
|
-
return { ...right, rank: left.rank + right.rank, warnings: left.warnings.concat(right.warnings) };
|
|
33622
|
-
}
|
|
33623
|
-
function follow(left, right) {
|
|
33624
|
-
return left.flatMap((matched) => isFail(matched.status) ? [matched] : crawl(right, matched.argIndex).map((result2) => append(matched, result2)));
|
|
33625
|
-
}
|
|
33626
|
-
function crawl(fmt, argIdx) {
|
|
33627
|
-
let positions = memo2.get(fmt);
|
|
33628
|
-
if (!positions) memo2.set(fmt, positions = /* @__PURE__ */ new Map());
|
|
33629
|
-
const cached2 = positions.get(argIdx);
|
|
33630
|
-
if (cached2) return cached2;
|
|
33631
|
-
if (positions.size >= VALIDATE_LIMIT) {
|
|
33632
|
-
throw new Error(`Validation iteration limit exceeded at argument ${argIdx}`);
|
|
33633
|
-
}
|
|
33634
|
-
const unique = /* @__PURE__ */ new Map();
|
|
33635
|
-
for (const result2 of match2(fmt, argIdx)) {
|
|
33636
|
-
const key = isFail(result2.status) ? "failure" : result2.argIndex;
|
|
33637
|
-
const previous2 = unique.get(key);
|
|
33638
|
-
if (!previous2 || key === "failure" && result2.rank > previous2.rank) unique.set(key, result2);
|
|
33639
|
-
}
|
|
33640
|
-
const results2 = [...unique.values()];
|
|
33641
|
-
positions.set(argIdx, results2);
|
|
33642
|
-
return results2;
|
|
33643
|
-
}
|
|
33644
|
-
function match2(fmt, argIdx) {
|
|
33645
|
-
switch (fmt.kind) {
|
|
33646
|
-
case 4 /* MaybeNext */: {
|
|
33647
|
-
const left = fmt.left ? crawl(fmt.left, argIdx) : [makeSuccess(argIdx, 0)];
|
|
33648
|
-
return [...follow(left, fmt.right), ...left];
|
|
33663
|
+
/** Every document's markup, read through Spindle's tooling API. */
|
|
33664
|
+
_markup;
|
|
33665
|
+
get markup() {
|
|
33666
|
+
this.ensureCurrent();
|
|
33667
|
+
return this._markup;
|
|
33649
33668
|
}
|
|
33650
|
-
|
|
33651
|
-
|
|
33669
|
+
/** True after initialize() has completed (full workspace scan done). */
|
|
33670
|
+
initialized = false;
|
|
33671
|
+
/** The story format declared by StoryData, updated with the passages. */
|
|
33672
|
+
format = UNDECLARED_STORY_FORMAT;
|
|
33673
|
+
/**
|
|
33674
|
+
* The Spindle the project targets: the version installed under the
|
|
33675
|
+
* workspace root, else StoryData's `format-version`. Only a version below
|
|
33676
|
+
* {@link MINIMUM_SPINDLE_VERSION} matters (it is reported, not emulated).
|
|
33677
|
+
*/
|
|
33678
|
+
_target;
|
|
33679
|
+
get target() {
|
|
33680
|
+
this.ensureCurrent();
|
|
33681
|
+
return this._target;
|
|
33652
33682
|
}
|
|
33653
|
-
|
|
33654
|
-
|
|
33683
|
+
workspaceRoot;
|
|
33684
|
+
/** The version installed under the workspace root, read at startup and on refresh(). */
|
|
33685
|
+
installedVersion;
|
|
33686
|
+
/** The `format-version` StoryData declares. */
|
|
33687
|
+
declaredVersion;
|
|
33688
|
+
analysisPending = false;
|
|
33689
|
+
debounceTimer = null;
|
|
33690
|
+
DEBOUNCE_MS = 200;
|
|
33691
|
+
/** Bound listeners for cleanup in dispose(). */
|
|
33692
|
+
onDocumentChanged;
|
|
33693
|
+
onDocumentOpened;
|
|
33694
|
+
onDocumentClosed;
|
|
33695
|
+
constructor(config2) {
|
|
33696
|
+
super();
|
|
33697
|
+
this.documents = new DocumentStore();
|
|
33698
|
+
this.passages = new PassageIndex();
|
|
33699
|
+
this._macros = new MacroRegistry();
|
|
33700
|
+
this.macros.onChange = () => this.markup?.invalidate();
|
|
33701
|
+
this._variables = new VariableTracker();
|
|
33702
|
+
this._widgets = new WidgetRegistry();
|
|
33703
|
+
this._markup = new MarkupIndex({
|
|
33704
|
+
text: (uri) => this.documents.getText(uri),
|
|
33705
|
+
passages: (uri) => this.passages.getPassagesInDocument(uri),
|
|
33706
|
+
context: () => ({
|
|
33707
|
+
macros: this.toolingMacros(),
|
|
33708
|
+
isBlock: (name) => this.isContainer(name)
|
|
33709
|
+
})
|
|
33710
|
+
});
|
|
33711
|
+
this.workspaceRoot = config2?.workspaceRoot;
|
|
33712
|
+
this.installedVersion = this.workspaceRoot ? readInstalledSpindleVersion(this.workspaceRoot) : void 0;
|
|
33713
|
+
this._target = resolveSpindleTarget(this.installedVersion);
|
|
33714
|
+
this.macros.loadBuiltins();
|
|
33715
|
+
this.macros.loadSupplements(macro_supplements_default);
|
|
33716
|
+
this.onDocumentChanged = (uri) => this.handleDocumentChange(uri);
|
|
33717
|
+
this.onDocumentOpened = (uri) => this.handleDocumentChange(uri);
|
|
33718
|
+
this.onDocumentClosed = (uri) => this.handleDocumentClose(uri);
|
|
33719
|
+
this.documents.on("documentChanged", this.onDocumentChanged);
|
|
33720
|
+
this.documents.on("documentOpened", this.onDocumentOpened);
|
|
33721
|
+
this.documents.on("documentClosed", this.onDocumentClosed);
|
|
33655
33722
|
}
|
|
33656
|
-
|
|
33657
|
-
|
|
33658
|
-
|
|
33659
|
-
|
|
33660
|
-
|
|
33661
|
-
|
|
33662
|
-
|
|
33663
|
-
|
|
33664
|
-
|
|
33665
|
-
const items = crawl(fmt.right, matched.argIndex);
|
|
33666
|
-
for (let i2 = items.length - 1; i2 >= 0; i2--) {
|
|
33667
|
-
const item = items[i2];
|
|
33668
|
-
if (!isFail(item.status) && !reached.has(item.argIndex)) pending.push(append(matched, item));
|
|
33669
|
-
}
|
|
33723
|
+
/**
|
|
33724
|
+
* Initialize the workspace in CLI/batch mode.
|
|
33725
|
+
* Loads all file contents, builds indices, and emits 'modelReady'.
|
|
33726
|
+
*/
|
|
33727
|
+
initialize(fileContents) {
|
|
33728
|
+
this.documents.removeListener("documentOpened", this.onDocumentOpened);
|
|
33729
|
+
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
33730
|
+
for (const [uri, text3] of fileContents) {
|
|
33731
|
+
this.documents.open(uri, text3);
|
|
33670
33732
|
}
|
|
33671
|
-
|
|
33733
|
+
this.documents.on("documentOpened", this.onDocumentOpened);
|
|
33734
|
+
this.documents.on("documentChanged", this.onDocumentChanged);
|
|
33735
|
+
this.rebuildAll();
|
|
33736
|
+
this.initialized = true;
|
|
33737
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
33738
|
+
this.debounceTimer = null;
|
|
33739
|
+
this.emit("modelReady");
|
|
33672
33740
|
}
|
|
33673
|
-
|
|
33674
|
-
|
|
33675
|
-
|
|
33676
|
-
|
|
33677
|
-
|
|
33678
|
-
if (
|
|
33679
|
-
|
|
33680
|
-
|
|
33681
|
-
|
|
33682
|
-
if (argText === fmt.value) {
|
|
33683
|
-
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
33684
|
-
}
|
|
33685
|
-
if (fmt.value === "null" && arg.type === 3 /* Null */ || fmt.value === "undefined" && arg.type === 10 /* Undefined */ || fmt.value === "true" && arg.type === 2 /* Boolean */ && arg.text === "true" || fmt.value === "false" && arg.type === 2 /* Boolean */ && arg.text === "false" || fmt.value === "NaN" && arg.type === 9 /* NaN */) {
|
|
33686
|
-
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
33687
|
-
}
|
|
33688
|
-
return [makeError(1 /* Failure */, `Expected literal '${fmt.value}'`, argIdx, CORRECT_TYPE_RANK)];
|
|
33741
|
+
/**
|
|
33742
|
+
* Re-run cross-document analysis and schedule 'modelReady', e.g. after
|
|
33743
|
+
* the macro configuration changed without any document changing.
|
|
33744
|
+
*/
|
|
33745
|
+
refresh() {
|
|
33746
|
+
if (this.workspaceRoot) this.installedVersion = readInstalledSpindleVersion(this.workspaceRoot);
|
|
33747
|
+
this.analysisPending = true;
|
|
33748
|
+
this.ensureCurrent();
|
|
33749
|
+
this.scheduleModelReady();
|
|
33689
33750
|
}
|
|
33690
|
-
|
|
33691
|
-
|
|
33692
|
-
|
|
33693
|
-
|
|
33694
|
-
|
|
33695
|
-
|
|
33696
|
-
|
|
33697
|
-
argIdx
|
|
33698
|
-
)];
|
|
33699
|
-
}
|
|
33700
|
-
if (isAlwaysArg(arg)) {
|
|
33701
|
-
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
33702
|
-
}
|
|
33703
|
-
const result2 = typeDef.validate(arg, args2, argIdx, state);
|
|
33704
|
-
if (result2 instanceof TypeError2) {
|
|
33705
|
-
return [{
|
|
33706
|
-
status: 1 /* Failure */,
|
|
33707
|
-
rank: 0,
|
|
33708
|
-
argIndex: argIdx,
|
|
33709
|
-
errors: [{ message: result2.message, argIndex: argIdx }],
|
|
33710
|
-
warnings: []
|
|
33711
|
-
}];
|
|
33712
|
-
}
|
|
33713
|
-
if (result2 instanceof TypeWarning) {
|
|
33714
|
-
return [makeSuccess(argIdx + 1, CORRECT_RANK, [
|
|
33715
|
-
{ message: result2.message, argIndex: argIdx }
|
|
33716
|
-
])];
|
|
33717
|
-
}
|
|
33718
|
-
return [makeSuccess(argIdx + 1, CORRECT_RANK)];
|
|
33751
|
+
/**
|
|
33752
|
+
* Whether `{name}` opens a container that needs a `{/name}` closing tag:
|
|
33753
|
+
* a block macro, or a block widget (one whose body renders `{@children}`).
|
|
33754
|
+
* Like Spindle's set of block macros, either source makes a name a block.
|
|
33755
|
+
*/
|
|
33756
|
+
isContainer(name) {
|
|
33757
|
+
return this.macros.isBlock(name) || (this.widgets.getWidget(name)?.block ?? false);
|
|
33719
33758
|
}
|
|
33720
|
-
|
|
33721
|
-
|
|
33722
|
-
|
|
33723
|
-
|
|
33724
|
-
|
|
33725
|
-
|
|
33726
|
-
|
|
33727
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
33730
|
-
|
|
33731
|
-
|
|
33732
|
-
|
|
33733
|
-
this.
|
|
33759
|
+
/**
|
|
33760
|
+
* Whether document `uri` declares any passage. A Twee document with no
|
|
33761
|
+
* `::` header holds no passage (the compiler ignores text outside one), so
|
|
33762
|
+
* there is no markup in it for any consumer to read.
|
|
33763
|
+
*/
|
|
33764
|
+
hasPassages(uri) {
|
|
33765
|
+
return this.passages.getPassagesInDocument(uri).length > 0;
|
|
33766
|
+
}
|
|
33767
|
+
/**
|
|
33768
|
+
* The story format named by the project's StoryData passage (trimmed), or
|
|
33769
|
+
* undefined if no StoryData passage names one.
|
|
33770
|
+
*/
|
|
33771
|
+
get storyFormat() {
|
|
33772
|
+
this.ensureCurrent();
|
|
33773
|
+
return this.format.name;
|
|
33734
33774
|
}
|
|
33735
|
-
|
|
33736
|
-
|
|
33737
|
-
|
|
33738
|
-
|
|
33739
|
-
|
|
33775
|
+
/**
|
|
33776
|
+
* Whether Spindle semantics apply to this project. False only if a
|
|
33777
|
+
* StoryData passage names another story format (SugarCube, Harlowe, ...)
|
|
33778
|
+
* and none names Spindle; a missing or unreadable StoryData counts as
|
|
33779
|
+
* Spindle.
|
|
33780
|
+
*/
|
|
33781
|
+
isSpindleProject() {
|
|
33782
|
+
this.ensureCurrent();
|
|
33783
|
+
return this.format.isSpindle;
|
|
33740
33784
|
}
|
|
33741
|
-
|
|
33742
|
-
|
|
33743
|
-
|
|
33744
|
-
|
|
33745
|
-
|
|
33746
|
-
|
|
33747
|
-
|
|
33748
|
-
|
|
33749
|
-
},
|
|
33750
|
-
{
|
|
33751
|
-
name: ["null"],
|
|
33752
|
-
validate(arg) {
|
|
33753
|
-
return arg.type === 3 /* Null */ ? null : new TypeError2("Argument is not 'null'");
|
|
33754
|
-
}
|
|
33755
|
-
},
|
|
33756
|
-
{
|
|
33757
|
-
name: ["undefined"],
|
|
33758
|
-
validate(arg) {
|
|
33759
|
-
return arg.type === 10 /* Undefined */ ? null : new TypeError2("Argument is not 'undefined'");
|
|
33760
|
-
}
|
|
33761
|
-
},
|
|
33762
|
-
{
|
|
33763
|
-
name: ["number"],
|
|
33764
|
-
validate(arg) {
|
|
33765
|
-
return arg.type === 1 /* Number */ ? null : new TypeError2("Argument is not a number");
|
|
33766
|
-
}
|
|
33767
|
-
},
|
|
33768
|
-
{
|
|
33769
|
-
name: ["NaN"],
|
|
33770
|
-
validate(arg) {
|
|
33771
|
-
return arg.type === 9 /* NaN */ ? null : new TypeError2("Argument is not 'NaN'");
|
|
33772
|
-
}
|
|
33773
|
-
},
|
|
33774
|
-
{
|
|
33775
|
-
name: ["link"],
|
|
33776
|
-
validate(arg) {
|
|
33777
|
-
return arg.type === 5 /* Link */ ? null : new TypeError2("Argument is not a link");
|
|
33778
|
-
}
|
|
33779
|
-
},
|
|
33780
|
-
{
|
|
33781
|
-
name: ["image"],
|
|
33782
|
-
validate(arg) {
|
|
33783
|
-
return arg.type === 6 /* Image */ ? null : new TypeError2("Argument is not an image");
|
|
33784
|
-
}
|
|
33785
|
-
},
|
|
33786
|
-
{
|
|
33787
|
-
name: ["bareword"],
|
|
33788
|
-
validate(arg) {
|
|
33789
|
-
return arg.type === 8 /* Bareword */ ? null : new TypeError2("Argument is not a bareword");
|
|
33790
|
-
}
|
|
33791
|
-
},
|
|
33792
|
-
{
|
|
33793
|
-
name: ["string"],
|
|
33794
|
-
validate(arg) {
|
|
33795
|
-
return arg.type === 0 /* String */ ? null : new TypeError2("Argument is not a quoted string");
|
|
33796
|
-
}
|
|
33797
|
-
},
|
|
33798
|
-
{
|
|
33799
|
-
name: ["text"],
|
|
33800
|
-
validate(arg) {
|
|
33801
|
-
const t = arg.type;
|
|
33802
|
-
if (t === 8 /* Bareword */ || t === 0 /* String */ || t === 2 /* Boolean */ || t === 3 /* Null */ || t === 9 /* NaN */ || t === 1 /* Number */) {
|
|
33803
|
-
return null;
|
|
33804
|
-
}
|
|
33805
|
-
return new TypeError2("Argument is not text");
|
|
33806
|
-
}
|
|
33807
|
-
},
|
|
33808
|
-
{
|
|
33809
|
-
name: ["var"],
|
|
33810
|
-
validate(arg) {
|
|
33811
|
-
return arg.type === 4 /* Variable */ ? null : new TypeError2("Argument is not a variable");
|
|
33785
|
+
/** Clean up listeners and timers. */
|
|
33786
|
+
dispose() {
|
|
33787
|
+
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
33788
|
+
this.documents.removeListener("documentOpened", this.onDocumentOpened);
|
|
33789
|
+
this.documents.removeListener("documentClosed", this.onDocumentClosed);
|
|
33790
|
+
if (this.debounceTimer) {
|
|
33791
|
+
clearTimeout(this.debounceTimer);
|
|
33792
|
+
this.debounceTimer = null;
|
|
33812
33793
|
}
|
|
33813
|
-
|
|
33814
|
-
|
|
33815
|
-
|
|
33816
|
-
|
|
33817
|
-
|
|
33818
|
-
|
|
33819
|
-
|
|
33820
|
-
let name;
|
|
33821
|
-
if (arg.type === 0 /* String */) {
|
|
33822
|
-
name = arg.text.slice(1, -1);
|
|
33823
|
-
} else if (arg.type === 8 /* Bareword */) {
|
|
33824
|
-
name = arg.text;
|
|
33825
|
-
}
|
|
33826
|
-
if (name === void 0) {
|
|
33827
|
-
return new TypeError2("Argument is not a variable receiver");
|
|
33828
|
-
}
|
|
33829
|
-
if (/^[_@%]/.test(name)) {
|
|
33830
|
-
return new TypeError2(`Variable receiver must be a story variable ($name), got '${name}'`);
|
|
33831
|
-
}
|
|
33832
|
-
if (!/^(?:\$\w|[A-Za-z_])[\w$]*(?:\.[\w$]+)*$/.test(name)) {
|
|
33833
|
-
return new TypeError2(`Argument is not a valid variable receiver: '${name}'`);
|
|
33834
|
-
}
|
|
33835
|
-
return null;
|
|
33794
|
+
this.removeAllListeners();
|
|
33795
|
+
}
|
|
33796
|
+
/** Index local headers immediately; defer expensive cross-document analysis. */
|
|
33797
|
+
handleDocumentChange(uri) {
|
|
33798
|
+
const text3 = this.documents.getText(uri);
|
|
33799
|
+
if (text3 !== void 0 && !isMacroSource(uri)) {
|
|
33800
|
+
this.passages.rebuild(uri, text3);
|
|
33836
33801
|
}
|
|
33837
|
-
|
|
33838
|
-
|
|
33839
|
-
|
|
33840
|
-
|
|
33841
|
-
|
|
33842
|
-
|
|
33843
|
-
|
|
33844
|
-
|
|
33845
|
-
|
|
33846
|
-
|
|
33847
|
-
|
|
33848
|
-
|
|
33849
|
-
|
|
33850
|
-
|
|
33851
|
-
|
|
33852
|
-
|
|
33853
|
-
|
|
33854
|
-
|
|
33855
|
-
|
|
33856
|
-
|
|
33857
|
-
|
|
33858
|
-
|
|
33802
|
+
this.analysisPending = true;
|
|
33803
|
+
this.updateStoryFormat();
|
|
33804
|
+
this.emit("documentChanged", uri);
|
|
33805
|
+
this.emit("passagesUpdated", uri);
|
|
33806
|
+
this.scheduleModelReady();
|
|
33807
|
+
}
|
|
33808
|
+
/** Remove local indices immediately and schedule cross-document analysis. */
|
|
33809
|
+
handleDocumentClose(uri) {
|
|
33810
|
+
this.passages.remove(uri);
|
|
33811
|
+
this._variables.removeDocument(uri);
|
|
33812
|
+
this.analysisPending = true;
|
|
33813
|
+
this.updateStoryFormat();
|
|
33814
|
+
this.emit("documentClosed", uri);
|
|
33815
|
+
this.emit("passagesUpdated", uri);
|
|
33816
|
+
this.scheduleModelReady();
|
|
33817
|
+
}
|
|
33818
|
+
/** Rebuild all indices from scratch. */
|
|
33819
|
+
rebuildAll() {
|
|
33820
|
+
for (const uri of this.documents.getUris()) {
|
|
33821
|
+
const text3 = this.documents.getText(uri);
|
|
33822
|
+
if (text3 !== void 0 && !isMacroSource(uri)) {
|
|
33823
|
+
this.passages.rebuild(uri, text3);
|
|
33859
33824
|
}
|
|
33860
|
-
return new TypeError2("Argument is not an acceptable passage");
|
|
33861
33825
|
}
|
|
33826
|
+
this.analysisPending = true;
|
|
33827
|
+
this.ensureCurrent();
|
|
33862
33828
|
}
|
|
33863
|
-
|
|
33864
|
-
|
|
33865
|
-
|
|
33866
|
-
|
|
33867
|
-
|
|
33868
|
-
|
|
33869
|
-
|
|
33870
|
-
PARSE_LIMIT = 2e3;
|
|
33871
|
-
DESCRIBE_ALTERNATIVE_LIMIT = 16;
|
|
33872
|
-
VALIDATE_LIMIT = 2e3;
|
|
33873
|
-
CORRECT_TYPE_RANK = 1;
|
|
33874
|
-
CORRECT_RANK = 2;
|
|
33875
|
-
Parameters = class {
|
|
33876
|
-
variants;
|
|
33877
|
-
constructor(variants) {
|
|
33878
|
-
this.variants = variants.map((s) => ({
|
|
33879
|
-
formatString: s,
|
|
33880
|
-
format: parseFormat(s)
|
|
33881
|
-
}));
|
|
33829
|
+
/** Finish pending cross-document work once, before a feature reads the indices. */
|
|
33830
|
+
ensureCurrent() {
|
|
33831
|
+
if (!this.analysisPending) return;
|
|
33832
|
+
this.analysisPending = false;
|
|
33833
|
+
this.refreshDiscoveredMacros();
|
|
33834
|
+
this.cascade();
|
|
33835
|
+
this.updateStoryFormat();
|
|
33882
33836
|
}
|
|
33883
33837
|
/**
|
|
33884
|
-
*
|
|
33885
|
-
*
|
|
33886
|
-
*
|
|
33887
|
-
* contribute nothing.
|
|
33838
|
+
* Re-read the story format from the StoryData passages after the passage
|
|
33839
|
+
* index changed, emitting 'storyFormatChanged' if it differs. Listeners
|
|
33840
|
+
* that read the analysis indices finish pending analysis through getters.
|
|
33888
33841
|
*/
|
|
33889
|
-
|
|
33890
|
-
const
|
|
33891
|
-
|
|
33892
|
-
|
|
33893
|
-
|
|
33894
|
-
|
|
33895
|
-
|
|
33842
|
+
updateStoryFormat() {
|
|
33843
|
+
const format = resolveStoryFormat(storyDataFormats(
|
|
33844
|
+
this.passages.getAllPassages(),
|
|
33845
|
+
(uri) => this.documents.getText(uri)
|
|
33846
|
+
));
|
|
33847
|
+
if (format.name === this.format.name && format.isSpindle === this.format.isSpindle) return;
|
|
33848
|
+
this.format = format;
|
|
33849
|
+
this.emit("storyFormatChanged", format);
|
|
33896
33850
|
}
|
|
33897
33851
|
/**
|
|
33898
|
-
*
|
|
33899
|
-
*
|
|
33852
|
+
* Re-run static macro discovery over the workspace and replace the
|
|
33853
|
+
* registry's discovered tier (dropping definitions whose source is gone).
|
|
33854
|
+
*
|
|
33855
|
+
* Sources scanned for Story.defineMacro({...}) calls:
|
|
33856
|
+
* - `{do}` blocks in StoryInit passages
|
|
33857
|
+
* - passages tagged `script` (Story JavaScript)
|
|
33858
|
+
* - JavaScript/TypeScript documents in the workspace
|
|
33900
33859
|
*/
|
|
33901
|
-
|
|
33902
|
-
|
|
33903
|
-
|
|
33904
|
-
|
|
33905
|
-
|
|
33906
|
-
|
|
33907
|
-
|
|
33908
|
-
|
|
33909
|
-
warnings: []
|
|
33910
|
-
};
|
|
33911
|
-
}
|
|
33912
|
-
let bestIndex = null;
|
|
33913
|
-
let bestRank = -Infinity;
|
|
33914
|
-
let bestErrors = [];
|
|
33915
|
-
let bestWarnings = [];
|
|
33916
|
-
let bestIsClean = false;
|
|
33917
|
-
for (let i2 = 0; i2 < this.variants.length; i2++) {
|
|
33918
|
-
const variant = this.variants[i2];
|
|
33919
|
-
if (variant.format === null) {
|
|
33920
|
-
if (args2.length === 0) {
|
|
33921
|
-
if (!bestIsClean || 0 > bestRank) {
|
|
33922
|
-
bestIndex = i2;
|
|
33923
|
-
bestRank = 0;
|
|
33924
|
-
bestErrors = [];
|
|
33925
|
-
bestWarnings = [];
|
|
33926
|
-
bestIsClean = true;
|
|
33927
|
-
}
|
|
33928
|
-
} else {
|
|
33929
|
-
if (!bestIsClean && bestIndex === null) {
|
|
33930
|
-
bestIndex = i2;
|
|
33931
|
-
bestRank = -1;
|
|
33932
|
-
bestErrors = [{ message: "Expected no arguments", argIndex: 0 }];
|
|
33933
|
-
bestWarnings = [];
|
|
33934
|
-
}
|
|
33935
|
-
}
|
|
33860
|
+
refreshDiscoveredMacros() {
|
|
33861
|
+
const found = [];
|
|
33862
|
+
const init = [];
|
|
33863
|
+
for (const uri of this.documents.getUris().sort()) {
|
|
33864
|
+
const text3 = this.documents.getText(uri);
|
|
33865
|
+
if (!text3) continue;
|
|
33866
|
+
if (isMacroSource(uri)) {
|
|
33867
|
+
found.push(...discoverMacros(text3));
|
|
33936
33868
|
continue;
|
|
33937
33869
|
}
|
|
33938
|
-
|
|
33939
|
-
|
|
33940
|
-
|
|
33941
|
-
|
|
33942
|
-
|
|
33943
|
-
|
|
33944
|
-
|
|
33945
|
-
|
|
33946
|
-
|
|
33947
|
-
}
|
|
33948
|
-
const rank = result2.rank;
|
|
33949
|
-
const isClean = errors.length === 0;
|
|
33950
|
-
if (isClean && (!bestIsClean || rank > bestRank)) {
|
|
33951
|
-
bestIndex = i2;
|
|
33952
|
-
bestRank = rank;
|
|
33953
|
-
bestErrors = [];
|
|
33954
|
-
bestWarnings = result2.warnings;
|
|
33955
|
-
bestIsClean = true;
|
|
33956
|
-
} else if (!isClean && !bestIsClean && rank > bestRank) {
|
|
33957
|
-
bestIndex = i2;
|
|
33958
|
-
bestRank = rank;
|
|
33959
|
-
bestErrors = errors;
|
|
33960
|
-
bestWarnings = result2.warnings;
|
|
33961
|
-
} else if (bestIndex === null) {
|
|
33962
|
-
bestIndex = i2;
|
|
33963
|
-
bestRank = rank;
|
|
33964
|
-
bestErrors = errors;
|
|
33965
|
-
bestWarnings = result2.warnings;
|
|
33966
|
-
bestIsClean = isClean;
|
|
33870
|
+
let lines;
|
|
33871
|
+
for (const passage of this.passages.getPassagesInDocument(uri)) {
|
|
33872
|
+
const isStoryInit = passage.name === "StoryInit";
|
|
33873
|
+
const isScript = passage.tags?.includes("script") ?? false;
|
|
33874
|
+
if (!isStoryInit && !isScript) continue;
|
|
33875
|
+
lines ??= text3.split("\n");
|
|
33876
|
+
const content2 = lines.slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
|
|
33877
|
+
if (isScript) found.push(...discoverMacros(content2));
|
|
33878
|
+
else init.push(...storyInitMacros(content2));
|
|
33967
33879
|
}
|
|
33968
33880
|
}
|
|
33969
|
-
|
|
33970
|
-
variantIndex: bestIndex,
|
|
33971
|
-
errors: bestErrors,
|
|
33972
|
-
warnings: bestWarnings
|
|
33973
|
-
};
|
|
33881
|
+
this.macros.setDiscoveredMacros([...found, ...init]);
|
|
33974
33882
|
}
|
|
33975
33883
|
/**
|
|
33976
|
-
*
|
|
33884
|
+
* Cascade: rescan variables and widgets based on current passages.
|
|
33885
|
+
* Called after any passage index update.
|
|
33886
|
+
*
|
|
33887
|
+
* The markup of every document is read again (the macros may have changed),
|
|
33888
|
+
* the widgets are defined from it, and then the variable usages and widget
|
|
33889
|
+
* invocations are read from the same markup, which pairs its tags with the
|
|
33890
|
+
* widgets just defined.
|
|
33977
33891
|
*/
|
|
33978
|
-
|
|
33979
|
-
|
|
33980
|
-
|
|
33892
|
+
cascade() {
|
|
33893
|
+
this.markup.invalidate();
|
|
33894
|
+
this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
|
|
33895
|
+
this._target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
|
|
33896
|
+
const declarations = (name) => {
|
|
33897
|
+
const passage = this.passages.getPassage(name);
|
|
33898
|
+
return passage ? this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage) : void 0;
|
|
33899
|
+
};
|
|
33900
|
+
const variables = declarations("StoryVariables");
|
|
33901
|
+
if (variables) this.variables.parseStoryVariables(variables.content, variables.passage.headerEnd.end.line + 1, variables.doc.uri);
|
|
33902
|
+
else this.variables.clearStoryVariables();
|
|
33903
|
+
const transients = declarations("StoryTransients");
|
|
33904
|
+
if (transients) this.variables.parseStoryTransients(transients.content, transients.passage.headerEnd.end.line + 1, transients.doc.uri);
|
|
33905
|
+
else this.variables.clearStoryTransients();
|
|
33906
|
+
const readDocuments = () => this.documents.getUris().flatMap((uri) => {
|
|
33907
|
+
const markup = isMacroSource(uri) ? void 0 : this.markup.get(uri);
|
|
33908
|
+
return markup ? [markup] : [];
|
|
33909
|
+
});
|
|
33910
|
+
const markups = /* @__PURE__ */ new Map();
|
|
33911
|
+
for (const doc of readDocuments()) for (const passage of doc.passages) markups.set(passage.passage, passage);
|
|
33912
|
+
this.widgets.scan(
|
|
33913
|
+
this.passages.getAllPassages().flatMap((passage) => markups.get(passage) ?? []),
|
|
33914
|
+
this.macros.toolingMacros()
|
|
33915
|
+
);
|
|
33916
|
+
this.markup.invalidate();
|
|
33917
|
+
this.widgets.clearInvocations();
|
|
33918
|
+
for (const doc of readDocuments()) {
|
|
33919
|
+
this.variables.scanDocument(doc.uri, doc.text, doc);
|
|
33920
|
+
this.widgets.recordInvocations(doc.uri, doc.passages);
|
|
33921
|
+
}
|
|
33922
|
+
}
|
|
33923
|
+
/** All runtime metadata, with real macros taking precedence over widgets. */
|
|
33924
|
+
toolingMacros() {
|
|
33925
|
+
const macros = this.macros.toolingMacros();
|
|
33926
|
+
return [...macros, ...this.widgets.getAllWidgets().filter((widget) => !this.macros.getMacro(widget.name)).map((widget) => ({ name: widget.name, block: widget.block, subMacros: [], widget: widget.tooling }))];
|
|
33927
|
+
}
|
|
33928
|
+
/** Coalesce analysis and diagnostics across a burst of document changes. */
|
|
33929
|
+
scheduleModelReady() {
|
|
33930
|
+
if (this.debounceTimer) {
|
|
33931
|
+
clearTimeout(this.debounceTimer);
|
|
33932
|
+
}
|
|
33933
|
+
this.debounceTimer = setTimeout(() => {
|
|
33934
|
+
this.debounceTimer = null;
|
|
33935
|
+
this.ensureCurrent();
|
|
33936
|
+
this.emit("modelReady");
|
|
33937
|
+
}, this.DEBOUNCE_MS);
|
|
33981
33938
|
}
|
|
33982
33939
|
};
|
|
33983
33940
|
}
|
|
33984
33941
|
});
|
|
33985
33942
|
|
|
33943
|
+
// src/core/diagnostic-codes.ts
|
|
33944
|
+
function getSeverity(code) {
|
|
33945
|
+
return severityMap[code];
|
|
33946
|
+
}
|
|
33947
|
+
var DiagnosticCode, severityMap;
|
|
33948
|
+
var init_diagnostic_codes = __esm({
|
|
33949
|
+
"src/core/diagnostic-codes.ts"() {
|
|
33950
|
+
"use strict";
|
|
33951
|
+
DiagnosticCode = {
|
|
33952
|
+
/** The Spindle the project targets is older than the oldest one supported. */
|
|
33953
|
+
UnsupportedSpindleVersion: "SP001",
|
|
33954
|
+
/** `unknown-macro`: a macro that is none of the built-in, configured or defined ones. */
|
|
33955
|
+
UndefinedMacro: "SP100",
|
|
33956
|
+
/** `unclosed-block`, `mismatched-closer`, `stray-closer` of a macro. */
|
|
33957
|
+
MalformedContainer: "SP101",
|
|
33958
|
+
/** Tags of an HTML element Spindle cannot read or pair. */
|
|
33959
|
+
MalformedElement: "SP102",
|
|
33960
|
+
/** A closing tag that closes no macro (`{/set}`), or is malformed (`{/}`, `{/if x}`). */
|
|
33961
|
+
IllegalClosingTag: "SP104",
|
|
33962
|
+
/** `unclosed-link`, `unclosed-expression`, `unclosed-macro`: a `[[`, `{$` or `{name` that is never ended. */
|
|
33963
|
+
UnclosedMarkup: "SP105",
|
|
33964
|
+
/** `code-syntax`: JavaScript that does not parse. */
|
|
33965
|
+
CodeSyntaxError: "SP106",
|
|
33966
|
+
/** `misplaced-branch` (`{else}` outside `{if}`), and a macro outside the parents it needs. */
|
|
33967
|
+
InvalidChildren: "SP107",
|
|
33968
|
+
/** A configured parameter schema rejects the arguments (macros Spindle declares nothing for). */
|
|
33969
|
+
ExpectedNoArguments: "SP108",
|
|
33970
|
+
/** `argument-error`: arguments that do not have their parameters' forms. */
|
|
33971
|
+
ParameterTypeError: "SP109",
|
|
33972
|
+
ParameterWarning: "SP110",
|
|
33973
|
+
TooManyArguments: "SP111",
|
|
33974
|
+
/** `unquoted-passage-name`: `{goto Kitchen}`, which throws a ReferenceError. */
|
|
33975
|
+
UnquotedPassageName: "SP113",
|
|
33976
|
+
ChildMaxExceeded: "SP114",
|
|
33977
|
+
ChildMinNotMet: "SP115",
|
|
33978
|
+
/** `undeclared-variable`. */
|
|
33979
|
+
UndeclaredVariable: "SP200",
|
|
33980
|
+
/** `primitive-field`: a field of a number, string or boolean. */
|
|
33981
|
+
PrimitiveFieldAccess: "SP201",
|
|
33982
|
+
NoStoryVariables: "SP202",
|
|
33983
|
+
/** `undeclared-transient`. */
|
|
33984
|
+
UndeclaredTransient: "SP203",
|
|
33985
|
+
TemporaryAssignedInLoop: "SP205",
|
|
33986
|
+
ArrayMemberAccess: "SP206",
|
|
33987
|
+
InvalidDeclaration: "SP207",
|
|
33988
|
+
/** `reserved-name`: a variable named `__proto__`. */
|
|
33989
|
+
ReservedVariableName: "SP208",
|
|
33990
|
+
/** `unknown-passage`. */
|
|
33991
|
+
BrokenPassageLink: "SP300",
|
|
33992
|
+
WidgetArgCountMismatch: "SP301",
|
|
33993
|
+
IncludeWidgetPassage: "SP302",
|
|
33994
|
+
UnusedWidget: "SP303",
|
|
33995
|
+
LinkRuntimeMismatch: "SP304",
|
|
33996
|
+
LiteralLinkInterpolation: "SP305",
|
|
33997
|
+
DeadEndPassage: "SP400",
|
|
33998
|
+
UnreachablePassage: "SP401",
|
|
33999
|
+
LineTooLong: "SP500"
|
|
34000
|
+
};
|
|
34001
|
+
severityMap = {
|
|
34002
|
+
SP001: "warning",
|
|
34003
|
+
SP100: "warning",
|
|
34004
|
+
SP101: "error",
|
|
34005
|
+
SP102: "error",
|
|
34006
|
+
SP104: "error",
|
|
34007
|
+
SP105: "error",
|
|
34008
|
+
SP106: "error",
|
|
34009
|
+
SP107: "error",
|
|
34010
|
+
SP108: "error",
|
|
34011
|
+
SP109: "error",
|
|
34012
|
+
SP110: "warning",
|
|
34013
|
+
SP111: "error",
|
|
34014
|
+
SP113: "error",
|
|
34015
|
+
SP114: "error",
|
|
34016
|
+
SP115: "error",
|
|
34017
|
+
SP200: "error",
|
|
34018
|
+
SP201: "error",
|
|
34019
|
+
SP202: "info",
|
|
34020
|
+
SP203: "warning",
|
|
34021
|
+
SP205: "warning",
|
|
34022
|
+
SP206: "warning",
|
|
34023
|
+
SP207: "error",
|
|
34024
|
+
SP208: "error",
|
|
34025
|
+
SP300: "warning",
|
|
34026
|
+
SP301: "warning",
|
|
34027
|
+
SP302: "warning",
|
|
34028
|
+
SP303: "hint",
|
|
34029
|
+
SP304: "warning",
|
|
34030
|
+
SP305: "warning",
|
|
34031
|
+
SP400: "hint",
|
|
34032
|
+
SP401: "hint",
|
|
34033
|
+
SP500: "warning"
|
|
34034
|
+
};
|
|
34035
|
+
}
|
|
34036
|
+
});
|
|
34037
|
+
|
|
34038
|
+
// src/core/markup/source-view.ts
|
|
34039
|
+
function viewIndex(view, offset2) {
|
|
34040
|
+
if (!view.sourceOffsets) return offset2 - view.offset;
|
|
34041
|
+
let low = 0;
|
|
34042
|
+
let high = view.sourceOffsets.length - 1;
|
|
34043
|
+
while (low < high) {
|
|
34044
|
+
const mid = Math.ceil((low + high) / 2);
|
|
34045
|
+
if (view.sourceOffsets[mid] <= offset2) low = mid;
|
|
34046
|
+
else high = mid - 1;
|
|
34047
|
+
}
|
|
34048
|
+
return low;
|
|
34049
|
+
}
|
|
34050
|
+
function textAt(passage, start, end) {
|
|
34051
|
+
return passage.pieces.filter((piece) => piece.kind === "text" && piece.offset <= start && pieceOffset(piece, piece.text.length) >= end).at(-1);
|
|
34052
|
+
}
|
|
34053
|
+
function sourceView(passage, start, end) {
|
|
34054
|
+
const owner = passage.pieces.filter((piece) => {
|
|
34055
|
+
const value = piece.kind === "text" ? piece.text : piece.kind === "code" ? piece.code : void 0;
|
|
34056
|
+
return value !== void 0 && piece.offset <= start && pieceOffset(piece, value.length) >= end;
|
|
34057
|
+
}).at(-1);
|
|
34058
|
+
if (owner?.kind === "code") return { ...owner, text: owner.code };
|
|
34059
|
+
return owner?.kind === "text" ? owner : { offset: 0, text: passage.content };
|
|
34060
|
+
}
|
|
34061
|
+
var init_source_view = __esm({
|
|
34062
|
+
"src/core/markup/source-view.ts"() {
|
|
34063
|
+
"use strict";
|
|
34064
|
+
init_tooling();
|
|
34065
|
+
}
|
|
34066
|
+
});
|
|
34067
|
+
|
|
33986
34068
|
// src/core/parsing/link-runtime.ts
|
|
33987
34069
|
function quoteArg2(value) {
|
|
33988
34070
|
return `"${value.replace(/[\\"]/g, "\\$&")}"`;
|
|
@@ -34252,10 +34334,11 @@ function buildReport(documents, workspace) {
|
|
|
34252
34334
|
const passages = [];
|
|
34253
34335
|
for (const doc of documents) {
|
|
34254
34336
|
for (const passage of doc.passages) {
|
|
34255
|
-
if (
|
|
34337
|
+
if (isRuntimePassage(passage.passage)) passages.push(passage);
|
|
34256
34338
|
}
|
|
34257
34339
|
}
|
|
34258
34340
|
const markup = /* @__PURE__ */ new Map();
|
|
34341
|
+
const validationMacros = toolingMacros.map((macro) => workspace.macros.hasConfiguredParameters(macro.name) ? { ...macro, widget: void 0 } : macro);
|
|
34259
34342
|
const found = validateStoryMarkup(
|
|
34260
34343
|
passages.map((passage, index) => ({
|
|
34261
34344
|
name: passage.passage.name,
|
|
@@ -34263,7 +34346,7 @@ function buildReport(documents, workspace) {
|
|
|
34263
34346
|
tags: passage.passage.tags,
|
|
34264
34347
|
metadata: { "data-source-file": String(index) }
|
|
34265
34348
|
})),
|
|
34266
|
-
|
|
34349
|
+
validationMacros,
|
|
34267
34350
|
// Every malformed tag and pairing error, and the checks of the markup around them
|
|
34268
34351
|
{ tolerant: true }
|
|
34269
34352
|
);
|
|
@@ -34607,7 +34690,7 @@ function isReadInBody(passage, bodyStart, bodyEnd, name, macroStart, macroEnd) {
|
|
|
34607
34690
|
}
|
|
34608
34691
|
function argumentDiagnostics(passage, workspace) {
|
|
34609
34692
|
const diagnostics = [];
|
|
34610
|
-
const passageNames =
|
|
34693
|
+
const passageNames = workspace.passages.getAllPassages().filter(isRuntimePassage).map((candidate) => candidate.name);
|
|
34611
34694
|
for (const token of passage.isMarkup ? macroTokens(passage) : []) {
|
|
34612
34695
|
if (token.type !== "macro" || token.isClose) continue;
|
|
34613
34696
|
const info = workspace.macros.getMacro(token.name);
|
|
@@ -35135,12 +35218,12 @@ async function runCheck(args2) {
|
|
|
35135
35218
|
return 0;
|
|
35136
35219
|
}
|
|
35137
35220
|
const workspace = new WorkspaceModel({ workspaceRoot: commonDir });
|
|
35138
|
-
const workspaceContents = new Map(fileContents);
|
|
35139
|
-
await addProjectMacroSources(workspaceContents, commonDir);
|
|
35140
|
-
workspace.initialize(workspaceContents);
|
|
35141
35221
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
35142
35222
|
workspace.macros.loadConfig(projectConfig.macros);
|
|
35143
35223
|
}
|
|
35224
|
+
const workspaceContents = new Map(fileContents);
|
|
35225
|
+
await addProjectMacroSources(workspaceContents, commonDir);
|
|
35226
|
+
workspace.initialize(workspaceContents);
|
|
35144
35227
|
const diagOptions = {};
|
|
35145
35228
|
if (options.maxLineLength !== null) {
|
|
35146
35229
|
diagOptions.maxLineLength = options.maxLineLength;
|
|
@@ -69102,12 +69185,12 @@ async function createWorkspace(files, fileContents) {
|
|
|
69102
69185
|
const configRoot = findConfigRoot(commonDir);
|
|
69103
69186
|
const projectConfig = loadConfigFromDisk(configRoot);
|
|
69104
69187
|
const workspace = new WorkspaceModel({ workspaceRoot: configRoot });
|
|
69105
|
-
fileContents = new Map(fileContents);
|
|
69106
|
-
await addProjectMacroSources(fileContents, commonDir);
|
|
69107
|
-
workspace.initialize(fileContents);
|
|
69108
69188
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
69109
69189
|
workspace.macros.loadConfig(projectConfig.macros);
|
|
69110
69190
|
}
|
|
69191
|
+
fileContents = new Map(fileContents);
|
|
69192
|
+
await addProjectMacroSources(fileContents, commonDir);
|
|
69193
|
+
workspace.initialize(fileContents);
|
|
69111
69194
|
return workspace;
|
|
69112
69195
|
}
|
|
69113
69196
|
async function checkFiles(pattern, severity, cwd = process.cwd()) {
|
|
@@ -80456,6 +80539,15 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
80456
80539
|
if (workspace.passages.getPassage(newName)) {
|
|
80457
80540
|
throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: a passage with that name already exists.`).at(uri, symbol2.range);
|
|
80458
80541
|
}
|
|
80542
|
+
for (const documentUri of workspace.documents.getUris()) {
|
|
80543
|
+
const doc = workspace.markup.get(documentUri);
|
|
80544
|
+
if (!doc) continue;
|
|
80545
|
+
for (const head of documentMacroHeads(doc)) {
|
|
80546
|
+
if (!head.closing && workspace.macros.hasUnsafeConfiguredPassages(head.name)) {
|
|
80547
|
+
throw new RenameError(`Cannot rename: {${head.name}} has configured passage parameters whose positions are ambiguous, repeated or conflict with its runtime signature.`).at(documentUri, head.range);
|
|
80548
|
+
}
|
|
80549
|
+
}
|
|
80550
|
+
}
|
|
80459
80551
|
const refEdits = [];
|
|
80460
80552
|
for (const { uri: refUri, ref: ref2 } of findPassageRefs(symbol2.name, workspace)) {
|
|
80461
80553
|
try {
|