@rohal12/spindle-lsp 0.8.4 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1445 -420
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6303,11 +6303,15 @@ function parsePassageHeader(line, lineNumber) {
|
|
|
6303
6303
|
nameRange
|
|
6304
6304
|
};
|
|
6305
6305
|
}
|
|
6306
|
-
|
|
6306
|
+
function isScriptOrStylesheetPassage(passage) {
|
|
6307
|
+
return passage.tags?.some((t) => CODE_PASSAGE_TAGS.has(t)) ?? false;
|
|
6308
|
+
}
|
|
6309
|
+
var passageHeaderRegex, CODE_PASSAGE_TAGS;
|
|
6307
6310
|
var init_passage_parser = __esm({
|
|
6308
6311
|
"src/core/parsing/passage-parser.ts"() {
|
|
6309
6312
|
"use strict";
|
|
6310
6313
|
passageHeaderRegex = /^(::\s+)(.*?)(\[.*?\]\s*)?(\{.*?\}\s*)?\r?$/;
|
|
6314
|
+
CODE_PASSAGE_TAGS = /* @__PURE__ */ new Set(["script", "stylesheet"]);
|
|
6311
6315
|
}
|
|
6312
6316
|
});
|
|
6313
6317
|
|
|
@@ -6339,6 +6343,7 @@ var init_passage_index = __esm({
|
|
|
6339
6343
|
// will be extended below
|
|
6340
6344
|
},
|
|
6341
6345
|
headerEnd: header.headerRange,
|
|
6346
|
+
nameRange: header.nameRange,
|
|
6342
6347
|
uri,
|
|
6343
6348
|
tags: header.tags.length > 0 ? header.tags : void 0,
|
|
6344
6349
|
meta: header.meta
|
|
@@ -6443,6 +6448,12 @@ var init_macro_registry = __esm({
|
|
|
6443
6448
|
"use strict";
|
|
6444
6449
|
MacroRegistry = class {
|
|
6445
6450
|
macros = /* @__PURE__ */ new Map();
|
|
6451
|
+
/** Entries as they were before the current user config was applied (undefined = absent). */
|
|
6452
|
+
configSnapshot = /* @__PURE__ */ new Map();
|
|
6453
|
+
/** Tier 3: macros discovered from Story.defineMacro() calls, keyed by lowercase name. */
|
|
6454
|
+
discovered = /* @__PURE__ */ new Map();
|
|
6455
|
+
/** Tier 4: user config entries, kept so they can be re-applied over discovered macros. */
|
|
6456
|
+
configEntries = /* @__PURE__ */ new Map();
|
|
6446
6457
|
/**
|
|
6447
6458
|
* Load built-in macro metadata from @rohal12/spindle's macro-registry.json.
|
|
6448
6459
|
* This is the base layer that provides name, block, subMacros, flags, source.
|
|
@@ -6499,10 +6510,67 @@ var init_macro_registry = __esm({
|
|
|
6499
6510
|
/**
|
|
6500
6511
|
* Overlay user config onto the registry.
|
|
6501
6512
|
* Same format as supplements; applied last so it wins.
|
|
6513
|
+
*
|
|
6514
|
+
* Replaces any previously loaded config: entries from the last call are
|
|
6515
|
+
* reverted first, so macros removed from the config disappear (or fall
|
|
6516
|
+
* back to their builtin/supplement definition).
|
|
6502
6517
|
*/
|
|
6503
6518
|
loadConfig(config2) {
|
|
6519
|
+
for (const [key, previous] of this.configSnapshot) {
|
|
6520
|
+
if (previous) {
|
|
6521
|
+
this.macros.set(key, previous);
|
|
6522
|
+
} else {
|
|
6523
|
+
this.macros.delete(key);
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
this.configSnapshot.clear();
|
|
6527
|
+
this.configEntries.clear();
|
|
6528
|
+
for (const rawKey of Object.keys(config2)) {
|
|
6529
|
+
const key = rawKey.toLowerCase();
|
|
6530
|
+
if (!this.configSnapshot.has(key)) {
|
|
6531
|
+
const existing = this.macros.get(key);
|
|
6532
|
+
this.configSnapshot.set(key, existing ? { ...existing } : void 0);
|
|
6533
|
+
}
|
|
6534
|
+
this.configEntries.set(key, { ...this.configEntries.get(key), ...config2[rawKey] });
|
|
6535
|
+
}
|
|
6504
6536
|
this.mergeEntries(config2);
|
|
6505
6537
|
}
|
|
6538
|
+
/**
|
|
6539
|
+
* Replace the set of macros discovered from Story.defineMacro() calls.
|
|
6540
|
+
* Previously discovered definitions that are no longer present are dropped,
|
|
6541
|
+
* restoring whatever the other tiers define for that name.
|
|
6542
|
+
*
|
|
6543
|
+
* Mirrors Story.defineMacro(): a macro is a block when `block: true`, or
|
|
6544
|
+
* when it declares sub-macros and does not set `block: false`. Each
|
|
6545
|
+
* sub-macro becomes a known macro that may only appear inside its parent.
|
|
6546
|
+
*/
|
|
6547
|
+
setDiscoveredMacros(macros) {
|
|
6548
|
+
this.discovered.clear();
|
|
6549
|
+
for (const m of macros) {
|
|
6550
|
+
const subMacros = m.subMacros ?? [];
|
|
6551
|
+
this.discovered.set(m.name.toLowerCase(), {
|
|
6552
|
+
name: m.name,
|
|
6553
|
+
block: m.block === true || m.block !== false && subMacros.length > 0,
|
|
6554
|
+
subMacros,
|
|
6555
|
+
storeVar: m.storeVar,
|
|
6556
|
+
interpolate: m.interpolate,
|
|
6557
|
+
merged: m.merged,
|
|
6558
|
+
description: m.description,
|
|
6559
|
+
source: "user"
|
|
6560
|
+
});
|
|
6561
|
+
}
|
|
6562
|
+
for (const m of macros) {
|
|
6563
|
+
for (const sub of m.subMacros ?? []) {
|
|
6564
|
+
const key = sub.toLowerCase();
|
|
6565
|
+
const existing = this.discovered.get(key);
|
|
6566
|
+
if (existing) {
|
|
6567
|
+
existing.parents = [...existing.parents ?? [], m.name];
|
|
6568
|
+
} else {
|
|
6569
|
+
this.discovered.set(key, { name: sub, block: false, subMacros: [], source: "user", parents: [m.name] });
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6506
6574
|
/** Add or replace a single macro entry. */
|
|
6507
6575
|
addMacro(info) {
|
|
6508
6576
|
const key = info.name.toLowerCase();
|
|
@@ -6524,20 +6592,54 @@ var init_macro_registry = __esm({
|
|
|
6524
6592
|
}
|
|
6525
6593
|
/** Get a macro by name (case-insensitive). */
|
|
6526
6594
|
getMacro(name) {
|
|
6527
|
-
return this.
|
|
6595
|
+
return this.resolve(name.toLowerCase());
|
|
6528
6596
|
}
|
|
6529
6597
|
/** Check if a macro is a block (container) macro. */
|
|
6530
6598
|
isBlock(name) {
|
|
6531
|
-
return this.
|
|
6599
|
+
return this.getMacro(name)?.block ?? false;
|
|
6532
6600
|
}
|
|
6533
6601
|
/** Check if a macro is a sub-macro (has parents). */
|
|
6534
6602
|
isSubMacro(name) {
|
|
6535
|
-
const info = this.
|
|
6603
|
+
const info = this.getMacro(name);
|
|
6536
6604
|
return info?.parents != null && info.parents.length > 0;
|
|
6537
6605
|
}
|
|
6538
6606
|
/** Get all registered macros. */
|
|
6539
6607
|
getAllMacros() {
|
|
6540
|
-
|
|
6608
|
+
const keys = /* @__PURE__ */ new Set([...this.macros.keys(), ...this.discovered.keys()]);
|
|
6609
|
+
return Array.from(keys, (key) => this.resolve(key));
|
|
6610
|
+
}
|
|
6611
|
+
/**
|
|
6612
|
+
* Compose the effective entry for a lowercase key: builtins/supplements/
|
|
6613
|
+
* config (stored in `macros`), overlaid by discovered metadata, with user
|
|
6614
|
+
* config re-applied on top so it keeps precedence.
|
|
6615
|
+
*/
|
|
6616
|
+
resolve(key) {
|
|
6617
|
+
const base = this.macros.get(key);
|
|
6618
|
+
const found = this.discovered.get(key);
|
|
6619
|
+
if (!found) return base;
|
|
6620
|
+
const info = {
|
|
6621
|
+
...base,
|
|
6622
|
+
name: base?.name ?? found.name,
|
|
6623
|
+
block: found.block ?? base?.block ?? false,
|
|
6624
|
+
subMacros: found.subMacros?.length ? found.subMacros : base?.subMacros ?? [],
|
|
6625
|
+
source: "user",
|
|
6626
|
+
storeVar: found.storeVar ?? base?.storeVar,
|
|
6627
|
+
interpolate: found.interpolate ?? base?.interpolate,
|
|
6628
|
+
merged: found.merged ?? base?.merged,
|
|
6629
|
+
description: found.description ?? base?.description,
|
|
6630
|
+
// A discovered sub-macro may also belong to other (e.g. built-in) parents
|
|
6631
|
+
parents: found.parents ? [.../* @__PURE__ */ new Set([...base?.parents ?? [], ...found.parents])] : base?.parents
|
|
6632
|
+
};
|
|
6633
|
+
const config2 = this.configEntries.get(key);
|
|
6634
|
+
if (config2) {
|
|
6635
|
+
if (config2.description !== void 0) info.description = config2.description;
|
|
6636
|
+
if (config2.parameters !== void 0) info.parameters = config2.parameters;
|
|
6637
|
+
if (config2.children !== void 0) info.children = config2.children;
|
|
6638
|
+
if (config2.parents !== void 0) info.parents = config2.parents;
|
|
6639
|
+
if (config2.skipArgs !== void 0) info.skipArgs = config2.skipArgs;
|
|
6640
|
+
if (config2.container !== void 0) info.block = config2.container;
|
|
6641
|
+
}
|
|
6642
|
+
return info;
|
|
6541
6643
|
}
|
|
6542
6644
|
/**
|
|
6543
6645
|
* Walk up directory tree from `startDir` to find the
|
|
@@ -6602,7 +6704,63 @@ var init_macro_registry = __esm({
|
|
|
6602
6704
|
});
|
|
6603
6705
|
|
|
6604
6706
|
// src/core/workspace/variable-tracker.ts
|
|
6605
|
-
|
|
6707
|
+
function findLiteralEnd(text, start) {
|
|
6708
|
+
const open = text[start];
|
|
6709
|
+
if (open === '"' || open === "'" || open === "`") {
|
|
6710
|
+
for (let i = start + 1; i < text.length; i++) {
|
|
6711
|
+
const ch = text[i];
|
|
6712
|
+
if (ch === "\\") {
|
|
6713
|
+
i++;
|
|
6714
|
+
continue;
|
|
6715
|
+
}
|
|
6716
|
+
if (open === "`" && ch === "$" && text[i + 1] === "{") return -1;
|
|
6717
|
+
if (ch === open) return i;
|
|
6718
|
+
}
|
|
6719
|
+
return -1;
|
|
6720
|
+
}
|
|
6721
|
+
let depth = 0;
|
|
6722
|
+
for (let i = start; i < text.length; i++) {
|
|
6723
|
+
const ch = text[i];
|
|
6724
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
6725
|
+
const end = findLiteralEnd(text, i);
|
|
6726
|
+
if (end === -1) return -1;
|
|
6727
|
+
i = end;
|
|
6728
|
+
continue;
|
|
6729
|
+
}
|
|
6730
|
+
if (ch === "[" || ch === "{" || ch === "(") depth++;
|
|
6731
|
+
else if (ch === "]" || ch === "}" || ch === ")") {
|
|
6732
|
+
depth--;
|
|
6733
|
+
if (depth === 0) return i;
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
return -1;
|
|
6737
|
+
}
|
|
6738
|
+
function inferLiteralType(expr) {
|
|
6739
|
+
const e = expr.trim();
|
|
6740
|
+
if (e === "") return void 0;
|
|
6741
|
+
if (e === "true" || e === "false") return "boolean";
|
|
6742
|
+
if (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?$/.test(e)) return "number";
|
|
6743
|
+
const first = e[0];
|
|
6744
|
+
if (first !== "[" && first !== "{" && first !== '"' && first !== "'" && first !== "`") {
|
|
6745
|
+
return void 0;
|
|
6746
|
+
}
|
|
6747
|
+
if (findLiteralEnd(e, 0) !== e.length - 1) return void 0;
|
|
6748
|
+
if (first === "[") return "array";
|
|
6749
|
+
if (first === "{") return "object";
|
|
6750
|
+
return "string";
|
|
6751
|
+
}
|
|
6752
|
+
function declarationLocation(line, name, absLine, uri) {
|
|
6753
|
+
const start = line.length - line.trimStart().length;
|
|
6754
|
+
const location = {
|
|
6755
|
+
declarationRange: {
|
|
6756
|
+
start: { line: absLine, character: start },
|
|
6757
|
+
end: { line: absLine, character: start + 1 + name.length }
|
|
6758
|
+
}
|
|
6759
|
+
};
|
|
6760
|
+
if (uri !== void 0) location.declarationUri = uri;
|
|
6761
|
+
return location;
|
|
6762
|
+
}
|
|
6763
|
+
var varRefRegex, transientRefRegex, EXCLUDED_PASSAGES, CLEAN_PATTERNS, ARRAY_MEMBERS, VariableTracker;
|
|
6606
6764
|
var init_variable_tracker = __esm({
|
|
6607
6765
|
"src/core/workspace/variable-tracker.ts"() {
|
|
6608
6766
|
"use strict";
|
|
@@ -6631,6 +6789,20 @@ var init_variable_tracker = __esm({
|
|
|
6631
6789
|
/'(?:\\.|[^'\\])*'/g
|
|
6632
6790
|
// single-quoted strings
|
|
6633
6791
|
];
|
|
6792
|
+
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
6793
|
+
...Object.getOwnPropertyNames(Array.prototype),
|
|
6794
|
+
...Object.getOwnPropertyNames(Object.prototype),
|
|
6795
|
+
"at",
|
|
6796
|
+
"flat",
|
|
6797
|
+
"flatMap",
|
|
6798
|
+
"includes",
|
|
6799
|
+
"findLast",
|
|
6800
|
+
"findLastIndex",
|
|
6801
|
+
"toReversed",
|
|
6802
|
+
"toSorted",
|
|
6803
|
+
"toSpliced",
|
|
6804
|
+
"with"
|
|
6805
|
+
]);
|
|
6634
6806
|
VariableTracker = class {
|
|
6635
6807
|
declared = /* @__PURE__ */ new Map();
|
|
6636
6808
|
_hasStoryVariables = false;
|
|
@@ -6646,7 +6818,7 @@ var init_variable_tracker = __esm({
|
|
|
6646
6818
|
* Parse the StoryVariables passage content for declarations.
|
|
6647
6819
|
* Each line like `$name = value` becomes a declaration.
|
|
6648
6820
|
*/
|
|
6649
|
-
parseStoryVariables(content, contentStartLine = 0) {
|
|
6821
|
+
parseStoryVariables(content, contentStartLine = 0, uri) {
|
|
6650
6822
|
this.declared.clear();
|
|
6651
6823
|
this._hasStoryVariables = true;
|
|
6652
6824
|
this._nullDeclarations = [];
|
|
@@ -6658,9 +6830,10 @@ var init_variable_tracker = __esm({
|
|
|
6658
6830
|
if (!match2) continue;
|
|
6659
6831
|
const name = match2[1];
|
|
6660
6832
|
const expr = match2[2].trim();
|
|
6833
|
+
const absLine = contentStartLine + i;
|
|
6834
|
+
const location = declarationLocation(lines[i], name, absLine, uri);
|
|
6661
6835
|
if (expr === "null") {
|
|
6662
6836
|
const charIdx = lines[i].indexOf("null", lines[i].indexOf("="));
|
|
6663
|
-
const absLine = contentStartLine + i;
|
|
6664
6837
|
this._nullDeclarations.push({
|
|
6665
6838
|
name,
|
|
6666
6839
|
sigil: "$",
|
|
@@ -6669,10 +6842,12 @@ var init_variable_tracker = __esm({
|
|
|
6669
6842
|
end: { line: absLine, character: charIdx + 4 }
|
|
6670
6843
|
}
|
|
6671
6844
|
});
|
|
6672
|
-
this.declared.set(name, { name, sigil: "$" });
|
|
6845
|
+
this.declared.set(name, { name, sigil: "$", ...location });
|
|
6673
6846
|
continue;
|
|
6674
6847
|
}
|
|
6675
|
-
const decl = { name, sigil: "$" };
|
|
6848
|
+
const decl = { name, sigil: "$", ...location };
|
|
6849
|
+
const type = inferLiteralType(expr);
|
|
6850
|
+
if (type) decl.type = type;
|
|
6676
6851
|
if (expr.startsWith("{")) {
|
|
6677
6852
|
const fieldRegex = /(\w+)\s*:/g;
|
|
6678
6853
|
let fieldMatch;
|
|
@@ -6687,11 +6862,23 @@ var init_variable_tracker = __esm({
|
|
|
6687
6862
|
this.declared.set(name, decl);
|
|
6688
6863
|
}
|
|
6689
6864
|
}
|
|
6865
|
+
/** Forget StoryVariables declarations, e.g. after the passage was removed. */
|
|
6866
|
+
clearStoryVariables() {
|
|
6867
|
+
this.declared.clear();
|
|
6868
|
+
this._hasStoryVariables = false;
|
|
6869
|
+
this._nullDeclarations = [];
|
|
6870
|
+
}
|
|
6871
|
+
/** Forget StoryTransients declarations, e.g. after the passage was removed. */
|
|
6872
|
+
clearStoryTransients() {
|
|
6873
|
+
this.declaredTransient.clear();
|
|
6874
|
+
this._hasStoryTransients = false;
|
|
6875
|
+
this._nullTransientDeclarations = [];
|
|
6876
|
+
}
|
|
6690
6877
|
/**
|
|
6691
6878
|
* Parse the StoryTransients passage content for declarations.
|
|
6692
6879
|
* Each line like `%name = value` becomes a declaration.
|
|
6693
6880
|
*/
|
|
6694
|
-
parseStoryTransients(content, contentStartLine = 0) {
|
|
6881
|
+
parseStoryTransients(content, contentStartLine = 0, uri) {
|
|
6695
6882
|
this.declaredTransient.clear();
|
|
6696
6883
|
this._hasStoryTransients = true;
|
|
6697
6884
|
this._nullTransientDeclarations = [];
|
|
@@ -6703,9 +6890,10 @@ var init_variable_tracker = __esm({
|
|
|
6703
6890
|
if (!match2) continue;
|
|
6704
6891
|
const name = match2[1];
|
|
6705
6892
|
const expr = match2[2].trim();
|
|
6893
|
+
const absLine = contentStartLine + i;
|
|
6894
|
+
const location = declarationLocation(lines[i], name, absLine, uri);
|
|
6706
6895
|
if (expr === "null") {
|
|
6707
6896
|
const charIdx = lines[i].indexOf("null", lines[i].indexOf("="));
|
|
6708
|
-
const absLine = contentStartLine + i;
|
|
6709
6897
|
this._nullTransientDeclarations.push({
|
|
6710
6898
|
name,
|
|
6711
6899
|
sigil: "%",
|
|
@@ -6714,10 +6902,12 @@ var init_variable_tracker = __esm({
|
|
|
6714
6902
|
end: { line: absLine, character: charIdx + 4 }
|
|
6715
6903
|
}
|
|
6716
6904
|
});
|
|
6717
|
-
this.declaredTransient.set(name, { name, sigil: "%" });
|
|
6905
|
+
this.declaredTransient.set(name, { name, sigil: "%", ...location });
|
|
6718
6906
|
continue;
|
|
6719
6907
|
}
|
|
6720
|
-
const decl = { name, sigil: "%" };
|
|
6908
|
+
const decl = { name, sigil: "%", ...location };
|
|
6909
|
+
const type = inferLiteralType(expr);
|
|
6910
|
+
if (type) decl.type = type;
|
|
6721
6911
|
if (expr.startsWith("{")) {
|
|
6722
6912
|
const fieldRegex = /(\w+)\s*:/g;
|
|
6723
6913
|
let fieldMatch;
|
|
@@ -6746,19 +6936,20 @@ var init_variable_tracker = __esm({
|
|
|
6746
6936
|
for (let i = 0; i < lines.length; i++) {
|
|
6747
6937
|
const header = parsePassageHeader(lines[i], i);
|
|
6748
6938
|
if (header) {
|
|
6749
|
-
passageBoundaries.push({ name: header.name, startLine: i });
|
|
6939
|
+
passageBoundaries.push({ name: header.name, tags: header.tags, startLine: i });
|
|
6750
6940
|
}
|
|
6751
6941
|
}
|
|
6752
6942
|
for (let pi = 0; pi < passageBoundaries.length; pi++) {
|
|
6753
6943
|
const passage = passageBoundaries[pi];
|
|
6754
6944
|
if (EXCLUDED_PASSAGES.has(passage.name)) continue;
|
|
6945
|
+
if (isScriptOrStylesheetPassage(passage)) continue;
|
|
6755
6946
|
const contentStartLine = passage.startLine + 1;
|
|
6756
6947
|
const contentEndLine = pi + 1 < passageBoundaries.length ? passageBoundaries[pi + 1].startLine : lines.length;
|
|
6757
6948
|
const contentLines = lines.slice(contentStartLine, contentEndLine);
|
|
6758
6949
|
const content = contentLines.join("\n");
|
|
6759
6950
|
let cleaned = content;
|
|
6760
6951
|
for (const pattern of CLEAN_PATTERNS) {
|
|
6761
|
-
cleaned = cleaned.replace(pattern, (m) => " "
|
|
6952
|
+
cleaned = cleaned.replace(pattern, (m) => m.replace(/[^\r\n]/g, " "));
|
|
6762
6953
|
}
|
|
6763
6954
|
const lineOffsets = [0];
|
|
6764
6955
|
for (let i = 0; i < cleaned.length; i++) {
|
|
@@ -6878,6 +7069,29 @@ var init_variable_tracker = __esm({
|
|
|
6878
7069
|
hasStoryTransients() {
|
|
6879
7070
|
return this._hasStoryTransients;
|
|
6880
7071
|
}
|
|
7072
|
+
/**
|
|
7073
|
+
* Get `$var.member` / `%var.member` accesses in a document where `var` is
|
|
7074
|
+
* declared with an array literal default and `member` is not a property
|
|
7075
|
+
* that JavaScript arrays have (so the access always yields undefined).
|
|
7076
|
+
*/
|
|
7077
|
+
getArrayMemberAccesses(uri) {
|
|
7078
|
+
const results = [];
|
|
7079
|
+
const sources = [
|
|
7080
|
+
["$", this.usagesByUri.get(uri), this.declared],
|
|
7081
|
+
["%", this.transientUsagesByUri.get(uri), this.declaredTransient]
|
|
7082
|
+
];
|
|
7083
|
+
for (const [sigil, usages, declared] of sources) {
|
|
7084
|
+
if (!usages) continue;
|
|
7085
|
+
for (const u of usages) {
|
|
7086
|
+
const member = u.fullName.split(".")[1];
|
|
7087
|
+
if (member === void 0) continue;
|
|
7088
|
+
if (declared.get(u.baseName)?.type !== "array") continue;
|
|
7089
|
+
if (ARRAY_MEMBERS.has(member)) continue;
|
|
7090
|
+
results.push({ sigil, name: u.baseName, member, range: u.range });
|
|
7091
|
+
}
|
|
7092
|
+
}
|
|
7093
|
+
return results;
|
|
7094
|
+
}
|
|
6881
7095
|
/** Get variables declared with null values in StoryVariables. */
|
|
6882
7096
|
getNullDeclarations() {
|
|
6883
7097
|
return this._nullDeclarations;
|
|
@@ -6890,80 +7104,6 @@ var init_variable_tracker = __esm({
|
|
|
6890
7104
|
}
|
|
6891
7105
|
});
|
|
6892
7106
|
|
|
6893
|
-
// src/core/workspace/widget-registry.ts
|
|
6894
|
-
var widgetDefRegex, paramRegex, WidgetRegistry;
|
|
6895
|
-
var init_widget_registry = __esm({
|
|
6896
|
-
"src/core/workspace/widget-registry.ts"() {
|
|
6897
|
-
"use strict";
|
|
6898
|
-
widgetDefRegex = /\{widget\s+"([^"]+)"((?:\s+@[A-Za-z_$][\w$]*)*)\s*\}/gi;
|
|
6899
|
-
paramRegex = /@([A-Za-z_$][\w$]*)/g;
|
|
6900
|
-
WidgetRegistry = class {
|
|
6901
|
-
widgets = /* @__PURE__ */ new Map();
|
|
6902
|
-
/**
|
|
6903
|
-
* Scan all passages for widget definitions.
|
|
6904
|
-
* Only passages tagged `widget` are examined.
|
|
6905
|
-
*
|
|
6906
|
-
* @param passages - All known passages.
|
|
6907
|
-
* @param getContent - Function to retrieve document text by URI.
|
|
6908
|
-
*/
|
|
6909
|
-
scan(passages, getContent) {
|
|
6910
|
-
this.widgets.clear();
|
|
6911
|
-
for (const passage of passages) {
|
|
6912
|
-
if (!passage.tags?.includes("widget")) continue;
|
|
6913
|
-
const text = getContent(passage.uri);
|
|
6914
|
-
if (!text) continue;
|
|
6915
|
-
const lines = text.split("\n");
|
|
6916
|
-
const contentStartLine = passage.range.start.line + 1;
|
|
6917
|
-
let contentEndLine = lines.length;
|
|
6918
|
-
for (let i = contentStartLine; i < lines.length; i++) {
|
|
6919
|
-
if (/^::\s+/.test(lines[i])) {
|
|
6920
|
-
contentEndLine = i;
|
|
6921
|
-
break;
|
|
6922
|
-
}
|
|
6923
|
-
}
|
|
6924
|
-
const contentLines = lines.slice(contentStartLine, contentEndLine);
|
|
6925
|
-
for (let i = 0; i < contentLines.length; i++) {
|
|
6926
|
-
const line = contentLines[i];
|
|
6927
|
-
widgetDefRegex.lastIndex = 0;
|
|
6928
|
-
let match2;
|
|
6929
|
-
while ((match2 = widgetDefRegex.exec(line)) !== null) {
|
|
6930
|
-
const widgetName = match2[1];
|
|
6931
|
-
const paramString = match2[2] || "";
|
|
6932
|
-
const params = [];
|
|
6933
|
-
let paramMatch;
|
|
6934
|
-
paramRegex.lastIndex = 0;
|
|
6935
|
-
while ((paramMatch = paramRegex.exec(paramString)) !== null) {
|
|
6936
|
-
params.push(paramMatch[1]);
|
|
6937
|
-
}
|
|
6938
|
-
const lineNum = contentStartLine + i;
|
|
6939
|
-
const charStart = match2.index;
|
|
6940
|
-
const charEnd = charStart + match2[0].length;
|
|
6941
|
-
const range2 = {
|
|
6942
|
-
start: { line: lineNum, character: charStart },
|
|
6943
|
-
end: { line: lineNum, character: charEnd }
|
|
6944
|
-
};
|
|
6945
|
-
this.widgets.set(widgetName, {
|
|
6946
|
-
name: widgetName,
|
|
6947
|
-
params,
|
|
6948
|
-
uri: passage.uri,
|
|
6949
|
-
range: range2
|
|
6950
|
-
});
|
|
6951
|
-
}
|
|
6952
|
-
}
|
|
6953
|
-
}
|
|
6954
|
-
}
|
|
6955
|
-
/** Get a widget definition by name. */
|
|
6956
|
-
getWidget(name) {
|
|
6957
|
-
return this.widgets.get(name);
|
|
6958
|
-
}
|
|
6959
|
-
/** Get all registered widget definitions. */
|
|
6960
|
-
getAllWidgets() {
|
|
6961
|
-
return Array.from(this.widgets.values());
|
|
6962
|
-
}
|
|
6963
|
-
};
|
|
6964
|
-
}
|
|
6965
|
-
});
|
|
6966
|
-
|
|
6967
7107
|
// src/core/parsing/macro-parser.ts
|
|
6968
7108
|
function buildLineStarts(text) {
|
|
6969
7109
|
const starts = [0];
|
|
@@ -7023,20 +7163,30 @@ function parseMacros(text) {
|
|
|
7023
7163
|
}
|
|
7024
7164
|
return macros;
|
|
7025
7165
|
}
|
|
7026
|
-
function pairMacros(macros, isBlock) {
|
|
7027
|
-
const
|
|
7166
|
+
function pairMacros(macros, isBlock, passageStartLines = []) {
|
|
7167
|
+
const boundaries = [...passageStartLines].sort((a, b) => a - b);
|
|
7168
|
+
let nextBoundary = 0;
|
|
7169
|
+
let stack = [];
|
|
7028
7170
|
for (const macro of macros) {
|
|
7029
|
-
|
|
7171
|
+
let crossed = false;
|
|
7172
|
+
while (nextBoundary < boundaries.length && macro.range.start.line >= boundaries[nextBoundary]) {
|
|
7173
|
+
nextBoundary++;
|
|
7174
|
+
crossed = true;
|
|
7175
|
+
}
|
|
7176
|
+
if (crossed) stack = [];
|
|
7030
7177
|
if (!isBlock(macro.name)) continue;
|
|
7031
7178
|
if (macro.open) {
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
}
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7039
|
-
|
|
7179
|
+
stack.push(macro);
|
|
7180
|
+
continue;
|
|
7181
|
+
}
|
|
7182
|
+
const name = macro.name.toLowerCase();
|
|
7183
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
7184
|
+
if (stack[i].name.toLowerCase() === name) {
|
|
7185
|
+
const opener = stack[i];
|
|
7186
|
+
opener.pair = macro.id;
|
|
7187
|
+
macro.pair = opener.id;
|
|
7188
|
+
stack.length = i;
|
|
7189
|
+
break;
|
|
7040
7190
|
}
|
|
7041
7191
|
}
|
|
7042
7192
|
}
|
|
@@ -7050,6 +7200,221 @@ var init_macro_parser = __esm({
|
|
|
7050
7200
|
}
|
|
7051
7201
|
});
|
|
7052
7202
|
|
|
7203
|
+
// src/core/workspace/widget-registry.ts
|
|
7204
|
+
function parseParams(argString) {
|
|
7205
|
+
return argString.trim().split(/\s+/).filter((t) => /^[$_@]/.test(t) && t !== "@children");
|
|
7206
|
+
}
|
|
7207
|
+
var widgetDefRegex, WidgetRegistry;
|
|
7208
|
+
var init_widget_registry = __esm({
|
|
7209
|
+
"src/core/workspace/widget-registry.ts"() {
|
|
7210
|
+
"use strict";
|
|
7211
|
+
init_macro_parser();
|
|
7212
|
+
widgetDefRegex = /(\{widget\s+["']?)([^\s"'}]+)["']?([^}]*)\}/gi;
|
|
7213
|
+
WidgetRegistry = class {
|
|
7214
|
+
/** Widgets keyed by lower-cased name; Spindle resolves widgets case-insensitively. */
|
|
7215
|
+
widgets = /* @__PURE__ */ new Map();
|
|
7216
|
+
/** Per-URI set of lower-cased macro names opened in that document. */
|
|
7217
|
+
invokedByUri = /* @__PURE__ */ new Map();
|
|
7218
|
+
/**
|
|
7219
|
+
* Scan all passages for widget definitions.
|
|
7220
|
+
* Only passages tagged `widget` are examined.
|
|
7221
|
+
*
|
|
7222
|
+
* @param passages - All known passages.
|
|
7223
|
+
* @param getContent - Function to retrieve document text by URI.
|
|
7224
|
+
*/
|
|
7225
|
+
scan(passages, getContent) {
|
|
7226
|
+
this.widgets.clear();
|
|
7227
|
+
for (const passage of passages) {
|
|
7228
|
+
if (!passage.tags?.includes("widget")) continue;
|
|
7229
|
+
const text = getContent(passage.uri);
|
|
7230
|
+
if (!text) continue;
|
|
7231
|
+
const lines = text.split("\n");
|
|
7232
|
+
const contentStartLine = passage.range.start.line + 1;
|
|
7233
|
+
let contentEndLine = lines.length;
|
|
7234
|
+
for (let i = contentStartLine; i < lines.length; i++) {
|
|
7235
|
+
if (/^::\s+/.test(lines[i])) {
|
|
7236
|
+
contentEndLine = i;
|
|
7237
|
+
break;
|
|
7238
|
+
}
|
|
7239
|
+
}
|
|
7240
|
+
const content = lines.slice(contentStartLine, contentEndLine).join("\n");
|
|
7241
|
+
const lineStarts = buildLineStarts(content);
|
|
7242
|
+
const toPosition = (offset) => {
|
|
7243
|
+
const pos = offsetToPosition(offset, lineStarts);
|
|
7244
|
+
return { line: contentStartLine + pos.line, character: pos.character };
|
|
7245
|
+
};
|
|
7246
|
+
widgetDefRegex.lastIndex = 0;
|
|
7247
|
+
let match2;
|
|
7248
|
+
while ((match2 = widgetDefRegex.exec(content)) !== null) {
|
|
7249
|
+
const widgetName = match2[2];
|
|
7250
|
+
const nameStart = match2.index + match2[1].length;
|
|
7251
|
+
const range2 = {
|
|
7252
|
+
start: toPosition(match2.index),
|
|
7253
|
+
end: toPosition(match2.index + match2[0].length)
|
|
7254
|
+
};
|
|
7255
|
+
const nameRange = {
|
|
7256
|
+
start: toPosition(nameStart),
|
|
7257
|
+
end: toPosition(nameStart + widgetName.length)
|
|
7258
|
+
};
|
|
7259
|
+
const bodyStart = match2.index + match2[0].length;
|
|
7260
|
+
const rest = content.slice(bodyStart);
|
|
7261
|
+
const closeIdx = rest.search(/\{\/widget\}/i);
|
|
7262
|
+
const body = closeIdx >= 0 ? rest.slice(0, closeIdx) : rest;
|
|
7263
|
+
this.widgets.set(widgetName.toLowerCase(), {
|
|
7264
|
+
name: widgetName,
|
|
7265
|
+
params: parseParams(match2[3]),
|
|
7266
|
+
uri: passage.uri,
|
|
7267
|
+
range: range2,
|
|
7268
|
+
nameRange,
|
|
7269
|
+
block: /\{@children\}/.test(body)
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
/** Get a widget definition by name (case-insensitive). */
|
|
7275
|
+
getWidget(name) {
|
|
7276
|
+
return this.widgets.get(name.toLowerCase());
|
|
7277
|
+
}
|
|
7278
|
+
/** Get all registered widget definitions. */
|
|
7279
|
+
getAllWidgets() {
|
|
7280
|
+
return Array.from(this.widgets.values());
|
|
7281
|
+
}
|
|
7282
|
+
/**
|
|
7283
|
+
* Record the macro names opened in a document, replacing any previous
|
|
7284
|
+
* record for that URI. Spindle resolves widget names case-insensitively,
|
|
7285
|
+
* so names are stored lower-cased.
|
|
7286
|
+
*/
|
|
7287
|
+
recordInvocations(uri, macros) {
|
|
7288
|
+
const names = /* @__PURE__ */ new Set();
|
|
7289
|
+
for (const macro of macros) {
|
|
7290
|
+
if (macro.open) names.add(macro.name.toLowerCase());
|
|
7291
|
+
}
|
|
7292
|
+
this.invokedByUri.set(uri, names);
|
|
7293
|
+
}
|
|
7294
|
+
/** Forget all recorded invocations. */
|
|
7295
|
+
clearInvocations() {
|
|
7296
|
+
this.invokedByUri.clear();
|
|
7297
|
+
}
|
|
7298
|
+
/** Whether any recorded document invokes the widget `{name}`. */
|
|
7299
|
+
isInvoked(name) {
|
|
7300
|
+
const key = name.toLowerCase();
|
|
7301
|
+
for (const names of this.invokedByUri.values()) {
|
|
7302
|
+
if (names.has(key)) return true;
|
|
7303
|
+
}
|
|
7304
|
+
return false;
|
|
7305
|
+
}
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7308
|
+
});
|
|
7309
|
+
|
|
7310
|
+
// src/core/parsing/macro-discovery.ts
|
|
7311
|
+
function extractBalancedBraces(source, start) {
|
|
7312
|
+
let depth = 0;
|
|
7313
|
+
let inSingleQuote = false;
|
|
7314
|
+
let inDoubleQuote = false;
|
|
7315
|
+
let inBacktick = false;
|
|
7316
|
+
for (let i = start; i < source.length; i++) {
|
|
7317
|
+
const ch = source[i];
|
|
7318
|
+
const prev = i > start ? source[i - 1] : "";
|
|
7319
|
+
if (prev === "\\") continue;
|
|
7320
|
+
if (inSingleQuote) {
|
|
7321
|
+
if (ch === "'") inSingleQuote = false;
|
|
7322
|
+
continue;
|
|
7323
|
+
}
|
|
7324
|
+
if (inDoubleQuote) {
|
|
7325
|
+
if (ch === '"') inDoubleQuote = false;
|
|
7326
|
+
continue;
|
|
7327
|
+
}
|
|
7328
|
+
if (inBacktick) {
|
|
7329
|
+
if (ch === "`") inBacktick = false;
|
|
7330
|
+
continue;
|
|
7331
|
+
}
|
|
7332
|
+
if (ch === "'") {
|
|
7333
|
+
inSingleQuote = true;
|
|
7334
|
+
} else if (ch === '"') {
|
|
7335
|
+
inDoubleQuote = true;
|
|
7336
|
+
} else if (ch === "`") {
|
|
7337
|
+
inBacktick = true;
|
|
7338
|
+
} else if (ch === "{") {
|
|
7339
|
+
depth++;
|
|
7340
|
+
} else if (ch === "}") {
|
|
7341
|
+
depth--;
|
|
7342
|
+
if (depth === 0) {
|
|
7343
|
+
return source.slice(start, i + 1);
|
|
7344
|
+
}
|
|
7345
|
+
}
|
|
7346
|
+
}
|
|
7347
|
+
return null;
|
|
7348
|
+
}
|
|
7349
|
+
function parseConfigFields(config2) {
|
|
7350
|
+
const nameMatch = config2.match(/name:\s*['"]([^'"]+)['"]/);
|
|
7351
|
+
if (!nameMatch) return null;
|
|
7352
|
+
const macro = {
|
|
7353
|
+
name: nameMatch[1]
|
|
7354
|
+
};
|
|
7355
|
+
const boolFields = [
|
|
7356
|
+
"block",
|
|
7357
|
+
"storeVar",
|
|
7358
|
+
"merged",
|
|
7359
|
+
"interpolate"
|
|
7360
|
+
];
|
|
7361
|
+
for (const field of boolFields) {
|
|
7362
|
+
const re = new RegExp(`${field}:\\s*(true|false)`);
|
|
7363
|
+
const m = config2.match(re);
|
|
7364
|
+
if (m) {
|
|
7365
|
+
macro[field] = m[1] === "true";
|
|
7366
|
+
}
|
|
7367
|
+
}
|
|
7368
|
+
const descMatch = config2.match(/description:\s*['"]([^'"]+)['"]/);
|
|
7369
|
+
if (descMatch) {
|
|
7370
|
+
macro.description = descMatch[1];
|
|
7371
|
+
}
|
|
7372
|
+
const subMatch = config2.match(/subMacros:\s*\[([^\]]*)\]/);
|
|
7373
|
+
if (subMatch) {
|
|
7374
|
+
const inner = subMatch[1].trim();
|
|
7375
|
+
if (inner) {
|
|
7376
|
+
macro.subMacros = inner.split(",").map(
|
|
7377
|
+
(s) => s.trim().replace(/^['"]|['"]$/g, "")
|
|
7378
|
+
).filter((s) => s.length > 0);
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
return macro;
|
|
7382
|
+
}
|
|
7383
|
+
function discoverMacrosFromSource(source) {
|
|
7384
|
+
const macros = [];
|
|
7385
|
+
defineMacroRegex.lastIndex = 0;
|
|
7386
|
+
let match2;
|
|
7387
|
+
while ((match2 = defineMacroRegex.exec(source)) !== null) {
|
|
7388
|
+
const braceStart = match2.index + match2[0].length - 1;
|
|
7389
|
+
const config2 = extractBalancedBraces(source, braceStart);
|
|
7390
|
+
if (!config2) continue;
|
|
7391
|
+
const macro = parseConfigFields(config2);
|
|
7392
|
+
if (macro) {
|
|
7393
|
+
macros.push(macro);
|
|
7394
|
+
}
|
|
7395
|
+
}
|
|
7396
|
+
return macros;
|
|
7397
|
+
}
|
|
7398
|
+
function discoverMacrosFromStoryInit(passageContent2) {
|
|
7399
|
+
const macros = [];
|
|
7400
|
+
doBlockRegex.lastIndex = 0;
|
|
7401
|
+
let doMatch;
|
|
7402
|
+
while ((doMatch = doBlockRegex.exec(passageContent2)) !== null) {
|
|
7403
|
+
const jsContent = doMatch[1];
|
|
7404
|
+
const found = discoverMacrosFromSource(jsContent);
|
|
7405
|
+
macros.push(...found);
|
|
7406
|
+
}
|
|
7407
|
+
return macros;
|
|
7408
|
+
}
|
|
7409
|
+
var defineMacroRegex, doBlockRegex;
|
|
7410
|
+
var init_macro_discovery = __esm({
|
|
7411
|
+
"src/core/parsing/macro-discovery.ts"() {
|
|
7412
|
+
"use strict";
|
|
7413
|
+
defineMacroRegex = /Story\.defineMacro\s*\(\s*\{/g;
|
|
7414
|
+
doBlockRegex = /\{do\}([\s\S]*?)\{\/do\}/g;
|
|
7415
|
+
}
|
|
7416
|
+
});
|
|
7417
|
+
|
|
7053
7418
|
// src/macro-supplements.json
|
|
7054
7419
|
var macro_supplements_default;
|
|
7055
7420
|
var init_macro_supplements = __esm({
|
|
@@ -7240,7 +7605,7 @@ var init_macro_supplements = __esm({
|
|
|
7240
7605
|
},
|
|
7241
7606
|
include: {
|
|
7242
7607
|
name: "include",
|
|
7243
|
-
description: 'Outputs the contents of the specified passage.\n\nUsage: `{include "passage"}`',
|
|
7608
|
+
description: 'Outputs the contents of the specified passage. The argument is a passage name or an expression that evaluates to one; add `inline` to render the passage as inline content without markdown processing.\n\nUsage: `{include "passage"}`, `{include $passageName}` or `{include "passage" inline}`',
|
|
7244
7609
|
parameters: ["text"]
|
|
7245
7610
|
},
|
|
7246
7611
|
passage: {
|
|
@@ -7250,28 +7615,28 @@ var init_macro_supplements = __esm({
|
|
|
7250
7615
|
},
|
|
7251
7616
|
textbox: {
|
|
7252
7617
|
name: "textbox",
|
|
7253
|
-
description: 'Creates a text input box.\n\nUsage: `{textbox
|
|
7254
|
-
parameters: ["receiver
|
|
7618
|
+
description: 'Creates a text input box bound to a story variable.\n\nUsage: `{textbox $receiver}` or `{textbox $receiver "placeholder"}`',
|
|
7619
|
+
parameters: ["receiver |+ text"]
|
|
7255
7620
|
},
|
|
7256
7621
|
numberbox: {
|
|
7257
7622
|
name: "numberbox",
|
|
7258
|
-
description: 'Creates a number input box.\n\nUsage: `{numberbox
|
|
7259
|
-
parameters: ["receiver
|
|
7623
|
+
description: 'Creates a number input box bound to a story variable.\n\nUsage: `{numberbox $receiver}` or `{numberbox $receiver "placeholder"}`',
|
|
7624
|
+
parameters: ["receiver |+ text"]
|
|
7260
7625
|
},
|
|
7261
7626
|
textarea: {
|
|
7262
7627
|
name: "textarea",
|
|
7263
|
-
description: 'Creates a multi-line text input area.\n\nUsage: `{textarea
|
|
7264
|
-
parameters: ["receiver
|
|
7628
|
+
description: 'Creates a multi-line text input area bound to a story variable.\n\nUsage: `{textarea $receiver}` or `{textarea $receiver "placeholder"}`',
|
|
7629
|
+
parameters: ["receiver |+ text"]
|
|
7265
7630
|
},
|
|
7266
7631
|
checkbox: {
|
|
7267
7632
|
name: "checkbox",
|
|
7268
|
-
description: 'Creates a checkbox
|
|
7269
|
-
parameters: ["receiver
|
|
7633
|
+
description: 'Creates a checkbox that toggles a boolean story variable.\n\nUsage: `{checkbox $receiver "label"}`',
|
|
7634
|
+
parameters: ["receiver |+ text"]
|
|
7270
7635
|
},
|
|
7271
7636
|
radiobutton: {
|
|
7272
7637
|
name: "radiobutton",
|
|
7273
|
-
description: 'Creates a radio button
|
|
7274
|
-
parameters: ["receiver &+ text
|
|
7638
|
+
description: 'Creates a radio button that sets a story variable to its value. Use the same variable for every option in a group.\n\nUsage: `{radiobutton $receiver "value" "label"}`',
|
|
7639
|
+
parameters: ["receiver &+ text |+ text"]
|
|
7275
7640
|
},
|
|
7276
7641
|
quicksave: {
|
|
7277
7642
|
name: "quicksave",
|
|
@@ -7301,8 +7666,8 @@ var init_macro_supplements = __esm({
|
|
|
7301
7666
|
dialog: {
|
|
7302
7667
|
name: "dialog",
|
|
7303
7668
|
container: true,
|
|
7304
|
-
description: '
|
|
7305
|
-
parameters: ["text"]
|
|
7669
|
+
description: 'Creates a button that opens the passage named in its body in a dialog. Add `noclose` to hide the dialog\'s close button.\n\nUsage:\n\n```\n{dialog "label"}Passage Name{/dialog}\n{dialog "label" noclose}Passage Name{/dialog}\n```',
|
|
7670
|
+
parameters: ["text |+ 'noclose'"]
|
|
7306
7671
|
},
|
|
7307
7672
|
nobr: {
|
|
7308
7673
|
name: "nobr",
|
|
@@ -7358,6 +7723,7 @@ var init_workspace_model = __esm({
|
|
|
7358
7723
|
init_variable_tracker();
|
|
7359
7724
|
init_widget_registry();
|
|
7360
7725
|
init_macro_parser();
|
|
7726
|
+
init_macro_discovery();
|
|
7361
7727
|
init_macro_supplements();
|
|
7362
7728
|
WorkspaceModel = class extends EventEmitter3 {
|
|
7363
7729
|
documents;
|
|
@@ -7405,6 +7771,14 @@ var init_workspace_model = __esm({
|
|
|
7405
7771
|
this.initialized = true;
|
|
7406
7772
|
this.scheduleModelReady();
|
|
7407
7773
|
}
|
|
7774
|
+
/**
|
|
7775
|
+
* Re-run cross-document analysis and schedule 'modelReady', e.g. after
|
|
7776
|
+
* the macro configuration changed without any document changing.
|
|
7777
|
+
*/
|
|
7778
|
+
refresh() {
|
|
7779
|
+
this.cascade();
|
|
7780
|
+
this.scheduleModelReady();
|
|
7781
|
+
}
|
|
7408
7782
|
/** Clean up listeners and timers. */
|
|
7409
7783
|
dispose() {
|
|
7410
7784
|
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
@@ -7422,6 +7796,7 @@ var init_workspace_model = __esm({
|
|
|
7422
7796
|
if (text !== void 0) {
|
|
7423
7797
|
this.passages.rebuild(uri, text);
|
|
7424
7798
|
}
|
|
7799
|
+
this.refreshDiscoveredMacros();
|
|
7425
7800
|
this.cascade();
|
|
7426
7801
|
this.emit("documentChanged", uri);
|
|
7427
7802
|
this.emit("passagesUpdated", uri);
|
|
@@ -7430,6 +7805,7 @@ var init_workspace_model = __esm({
|
|
|
7430
7805
|
/** Handle a document close: remove its passages and cascade. */
|
|
7431
7806
|
handleDocumentClose(uri) {
|
|
7432
7807
|
this.passages.remove(uri);
|
|
7808
|
+
this.refreshDiscoveredMacros();
|
|
7433
7809
|
this.cascade();
|
|
7434
7810
|
this.emit("documentClosed", uri);
|
|
7435
7811
|
this.emit("passagesUpdated", uri);
|
|
@@ -7443,8 +7819,39 @@ var init_workspace_model = __esm({
|
|
|
7443
7819
|
this.passages.rebuild(uri, text);
|
|
7444
7820
|
}
|
|
7445
7821
|
}
|
|
7822
|
+
this.refreshDiscoveredMacros();
|
|
7446
7823
|
this.cascade();
|
|
7447
7824
|
}
|
|
7825
|
+
/**
|
|
7826
|
+
* Re-run static macro discovery over the workspace and replace the
|
|
7827
|
+
* registry's discovered tier (dropping definitions whose source is gone).
|
|
7828
|
+
*
|
|
7829
|
+
* Sources scanned for Story.defineMacro({...}) calls:
|
|
7830
|
+
* - `{do}` blocks in StoryInit passages
|
|
7831
|
+
* - passages tagged `script` (Story JavaScript)
|
|
7832
|
+
* - JavaScript/TypeScript documents in the workspace
|
|
7833
|
+
*/
|
|
7834
|
+
refreshDiscoveredMacros() {
|
|
7835
|
+
const found = [];
|
|
7836
|
+
for (const uri of this.documents.getUris()) {
|
|
7837
|
+
const text = this.documents.getText(uri);
|
|
7838
|
+
if (!text) continue;
|
|
7839
|
+
if (/\.[cm]?[jt]s$/i.test(uri)) {
|
|
7840
|
+
found.push(...discoverMacrosFromSource(text));
|
|
7841
|
+
continue;
|
|
7842
|
+
}
|
|
7843
|
+
let lines;
|
|
7844
|
+
for (const passage of this.passages.getPassagesInDocument(uri)) {
|
|
7845
|
+
const isStoryInit = passage.name === "StoryInit";
|
|
7846
|
+
const isScript = passage.tags?.includes("script") ?? false;
|
|
7847
|
+
if (!isStoryInit && !isScript) continue;
|
|
7848
|
+
lines ??= text.split("\n");
|
|
7849
|
+
const content = lines.slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
|
|
7850
|
+
found.push(...isScript ? discoverMacrosFromSource(content) : discoverMacrosFromStoryInit(content));
|
|
7851
|
+
}
|
|
7852
|
+
}
|
|
7853
|
+
this.macros.setDiscoveredMacros(found);
|
|
7854
|
+
}
|
|
7448
7855
|
/**
|
|
7449
7856
|
* Cascade: rescan variables and widgets based on current passages.
|
|
7450
7857
|
* Called after any passage index update.
|
|
@@ -7464,8 +7871,10 @@ var init_workspace_model = __esm({
|
|
|
7464
7871
|
}
|
|
7465
7872
|
}
|
|
7466
7873
|
const content = lines.slice(contentStart, contentEnd).join("\n");
|
|
7467
|
-
this.variables.parseStoryVariables(content, contentStart);
|
|
7874
|
+
this.variables.parseStoryVariables(content, contentStart, storyVars.uri);
|
|
7468
7875
|
}
|
|
7876
|
+
} else {
|
|
7877
|
+
this.variables.clearStoryVariables();
|
|
7469
7878
|
}
|
|
7470
7879
|
const storyTransients = this.passages.getStoryTransients();
|
|
7471
7880
|
if (storyTransients) {
|
|
@@ -7481,14 +7890,18 @@ var init_workspace_model = __esm({
|
|
|
7481
7890
|
}
|
|
7482
7891
|
}
|
|
7483
7892
|
const content = lines.slice(contentStart, contentEnd).join("\n");
|
|
7484
|
-
this.variables.parseStoryTransients(content, contentStart);
|
|
7893
|
+
this.variables.parseStoryTransients(content, contentStart, storyTransients.uri);
|
|
7485
7894
|
}
|
|
7895
|
+
} else {
|
|
7896
|
+
this.variables.clearStoryTransients();
|
|
7486
7897
|
}
|
|
7898
|
+
this.widgets.clearInvocations();
|
|
7487
7899
|
for (const uri of this.documents.getUris()) {
|
|
7488
7900
|
const text = this.documents.getText(uri);
|
|
7489
7901
|
if (text) {
|
|
7490
7902
|
const macros = parseMacros(text);
|
|
7491
7903
|
this.variables.scanDocument(uri, text, macros);
|
|
7904
|
+
this.widgets.recordInvocations(uri, macros);
|
|
7492
7905
|
}
|
|
7493
7906
|
}
|
|
7494
7907
|
const allPassages = this.passages.getAllPassages();
|
|
@@ -7533,8 +7946,12 @@ var init_diagnostic_codes = __esm({
|
|
|
7533
7946
|
NoStoryVariables: "SP202",
|
|
7534
7947
|
UndeclaredTransient: "SP203",
|
|
7535
7948
|
NullVariableValue: "SP204",
|
|
7949
|
+
TemporaryAssignedInLoop: "SP205",
|
|
7950
|
+
ArrayMemberAccess: "SP206",
|
|
7536
7951
|
BrokenPassageLink: "SP300",
|
|
7537
7952
|
WidgetArgCountMismatch: "SP301",
|
|
7953
|
+
IncludeWidgetPassage: "SP302",
|
|
7954
|
+
UnusedWidget: "SP303",
|
|
7538
7955
|
DeadEndPassage: "SP400",
|
|
7539
7956
|
UnreachablePassage: "SP401",
|
|
7540
7957
|
LineTooLong: "SP500"
|
|
@@ -7556,8 +7973,12 @@ var init_diagnostic_codes = __esm({
|
|
|
7556
7973
|
SP202: "info",
|
|
7557
7974
|
SP203: "warning",
|
|
7558
7975
|
SP204: "error",
|
|
7976
|
+
SP205: "warning",
|
|
7977
|
+
SP206: "warning",
|
|
7559
7978
|
SP300: "warning",
|
|
7560
7979
|
SP301: "warning",
|
|
7980
|
+
SP302: "warning",
|
|
7981
|
+
SP303: "hint",
|
|
7561
7982
|
SP400: "hint",
|
|
7562
7983
|
SP401: "hint",
|
|
7563
7984
|
SP500: "warning"
|
|
@@ -8231,6 +8652,30 @@ var init_parameter_validator = __esm({
|
|
|
8231
8652
|
return arg.type === 4 /* Variable */ ? null : new TypeError2("Argument is not a variable");
|
|
8232
8653
|
}
|
|
8233
8654
|
},
|
|
8655
|
+
{
|
|
8656
|
+
// The story variable a form control binds to: `$name` or `"$name"`.
|
|
8657
|
+
// Spindle strips the quotes and the `$` sigil from the first argument
|
|
8658
|
+
// and reads the rest as a dot path into the story variables.
|
|
8659
|
+
name: ["receiver"],
|
|
8660
|
+
validate(arg) {
|
|
8661
|
+
let name;
|
|
8662
|
+
if (arg.type === 0 /* String */) {
|
|
8663
|
+
name = arg.text.slice(1, -1);
|
|
8664
|
+
} else if (arg.type === 8 /* Bareword */) {
|
|
8665
|
+
name = arg.text;
|
|
8666
|
+
}
|
|
8667
|
+
if (name === void 0) {
|
|
8668
|
+
return new TypeError2("Argument is not a variable receiver");
|
|
8669
|
+
}
|
|
8670
|
+
if (/^[_@%]/.test(name)) {
|
|
8671
|
+
return new TypeError2(`Variable receiver must be a story variable ($name), got '${name}'`);
|
|
8672
|
+
}
|
|
8673
|
+
if (!/^\$?[A-Za-z_][\w$]*(?:\.[\w$]+)*$/.test(name)) {
|
|
8674
|
+
return new TypeError2(`Argument is not a valid variable receiver: '${name}'`);
|
|
8675
|
+
}
|
|
8676
|
+
return null;
|
|
8677
|
+
}
|
|
8678
|
+
},
|
|
8234
8679
|
{
|
|
8235
8680
|
name: ["passage"],
|
|
8236
8681
|
validate(arg, _args, _index, state) {
|
|
@@ -8383,58 +8828,175 @@ var init_parameter_validator = __esm({
|
|
|
8383
8828
|
});
|
|
8384
8829
|
|
|
8385
8830
|
// src/core/parsing/link-parser.ts
|
|
8386
|
-
function
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
|
|
8390
|
-
|
|
8831
|
+
function skipSelectors(text, i) {
|
|
8832
|
+
while (text[i] === "." || text[i] === "#") {
|
|
8833
|
+
i++;
|
|
8834
|
+
for (; ; ) {
|
|
8835
|
+
if (/[a-zA-Z0-9_-]/.test(text[i] ?? "")) {
|
|
8836
|
+
i++;
|
|
8837
|
+
continue;
|
|
8838
|
+
}
|
|
8839
|
+
const interpolation = /^\{[$_@][\w.]*\}/.exec(text.slice(i));
|
|
8840
|
+
if (!interpolation) break;
|
|
8841
|
+
i += interpolation[0].length;
|
|
8391
8842
|
}
|
|
8392
8843
|
}
|
|
8393
|
-
return
|
|
8844
|
+
return i;
|
|
8394
8845
|
}
|
|
8395
|
-
function
|
|
8396
|
-
let
|
|
8397
|
-
let
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8846
|
+
function locateTarget(inner) {
|
|
8847
|
+
let start = 0;
|
|
8848
|
+
let end = inner.length;
|
|
8849
|
+
const pipeIdx = inner.indexOf("|");
|
|
8850
|
+
const arrowIdx = inner.indexOf("->");
|
|
8851
|
+
const revIdx = inner.indexOf("<-");
|
|
8852
|
+
if (pipeIdx !== -1) {
|
|
8853
|
+
start = pipeIdx + 1;
|
|
8854
|
+
} else if (arrowIdx !== -1) {
|
|
8855
|
+
start = arrowIdx + 2;
|
|
8856
|
+
} else if (revIdx !== -1) {
|
|
8857
|
+
end = revIdx;
|
|
8405
8858
|
}
|
|
8406
|
-
|
|
8859
|
+
while (start < end && /\s/.test(inner[start])) start++;
|
|
8860
|
+
while (end > start && /\s/.test(inner[end - 1])) end--;
|
|
8861
|
+
return { start, end };
|
|
8407
8862
|
}
|
|
8408
8863
|
function parseLinks(text, lineOffset = 0) {
|
|
8409
|
-
const lineStarts =
|
|
8864
|
+
const lineStarts = buildLineStarts(text);
|
|
8410
8865
|
const refs = [];
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
if (
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
const
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8866
|
+
let i = text.indexOf("[[");
|
|
8867
|
+
while (i !== -1) {
|
|
8868
|
+
const linkStart = i;
|
|
8869
|
+
i += 2;
|
|
8870
|
+
if (text[i] === "." || text[i] === "#") {
|
|
8871
|
+
i = skipSelectors(text, i);
|
|
8872
|
+
if (text[i] === " ") i++;
|
|
8873
|
+
}
|
|
8874
|
+
const innerStart = i;
|
|
8875
|
+
let depth = 1;
|
|
8876
|
+
while (i < text.length) {
|
|
8877
|
+
if (text.startsWith("[[", i)) {
|
|
8878
|
+
depth++;
|
|
8879
|
+
i += 2;
|
|
8880
|
+
} else if (text.startsWith("]]", i)) {
|
|
8881
|
+
if (--depth === 0) break;
|
|
8882
|
+
i += 2;
|
|
8883
|
+
} else {
|
|
8884
|
+
i++;
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
if (depth !== 0) {
|
|
8888
|
+
i = text.indexOf("[[", linkStart + 2);
|
|
8889
|
+
continue;
|
|
8890
|
+
}
|
|
8891
|
+
const target = locateTarget(text.slice(innerStart, i));
|
|
8892
|
+
if (target.end > target.start) {
|
|
8893
|
+
const nameStart = innerStart + target.start;
|
|
8894
|
+
const nameEnd = innerStart + target.end;
|
|
8895
|
+
const startPos = offsetToPosition(nameStart, lineStarts);
|
|
8896
|
+
const endPos = offsetToPosition(nameEnd, lineStarts);
|
|
8897
|
+
refs.push({
|
|
8898
|
+
name: text.slice(nameStart, nameEnd),
|
|
8899
|
+
range: {
|
|
8900
|
+
start: { line: startPos.line + lineOffset, character: startPos.character },
|
|
8901
|
+
end: { line: endPos.line + lineOffset, character: endPos.character }
|
|
8902
|
+
},
|
|
8903
|
+
source: "link"
|
|
8904
|
+
});
|
|
8905
|
+
}
|
|
8906
|
+
i = text.indexOf("[[", i + 2);
|
|
8907
|
+
}
|
|
8908
|
+
return refs;
|
|
8909
|
+
}
|
|
8910
|
+
function findPassageRefAt(text, position) {
|
|
8911
|
+
const { line, character } = position;
|
|
8912
|
+
return [...parseLinks(text), ...parseMacroPassageRefs(text)].find(({ range: { start, end } }) => (line > start.line || line === start.line && character >= start.character) && (line < end.line || line === end.line && character <= end.character));
|
|
8913
|
+
}
|
|
8914
|
+
function resolveExpressionTarget(args2) {
|
|
8915
|
+
const lead = args2.length - args2.trimStart().length;
|
|
8916
|
+
const expr = args2.trim();
|
|
8917
|
+
if (expr === "") return null;
|
|
8918
|
+
const lexed = lexArguments(expr);
|
|
8919
|
+
if (lexed.length === 1 && lexed[0].start === 0 && lexed[0].end === expr.length && (lexed[0].type === 0 /* String */ || lexed[0].type === 7 /* Expression */)) {
|
|
8920
|
+
const raw = expr.slice(1, -1);
|
|
8921
|
+
if (interpolationRegex.test(raw) || raw.includes("${")) return null;
|
|
8922
|
+
return { name: raw.replace(/\\(.)/g, "$1"), start: lead + 1, end: lead + expr.length - 1 };
|
|
8923
|
+
}
|
|
8924
|
+
if (/[$@%"'`(]/.test(expr) || temporaryRefRegex.test(expr)) return null;
|
|
8925
|
+
if (EXPRESSION_BUILTINS.has(expr)) return null;
|
|
8926
|
+
return { name: expr, start: lead, end: lead + expr.length };
|
|
8927
|
+
}
|
|
8928
|
+
function resolveIncludeTarget(args2) {
|
|
8929
|
+
const inline = /\binline\b/.exec(args2);
|
|
8930
|
+
if (!inline) return resolveExpressionTarget(args2);
|
|
8931
|
+
const cut = inline.index;
|
|
8932
|
+
const width = inline[0].length;
|
|
8933
|
+
const target = resolveExpressionTarget(args2.slice(0, cut) + args2.slice(cut + width));
|
|
8934
|
+
if (!target || target.start < cut && cut < target.end) return null;
|
|
8935
|
+
return {
|
|
8936
|
+
name: target.name,
|
|
8937
|
+
start: target.start < cut ? target.start : target.start + width,
|
|
8938
|
+
end: target.end <= cut ? target.end : target.end + width
|
|
8939
|
+
};
|
|
8940
|
+
}
|
|
8941
|
+
function resolveLinkMacroTarget(args2) {
|
|
8942
|
+
const strings = lexArguments(args2).filter((a) => a.type === 0 /* String */);
|
|
8943
|
+
if (strings.length < 2) return null;
|
|
8944
|
+
const target = strings[1];
|
|
8945
|
+
const name = target.text.slice(1, -1);
|
|
8946
|
+
if (name.includes("\\") || interpolationRegex.test(name)) return null;
|
|
8947
|
+
return { name, start: target.start + 1, end: target.end - 1 };
|
|
8948
|
+
}
|
|
8949
|
+
function parseMacroPassageRefs(text, lineOffset = 0) {
|
|
8950
|
+
const lineStarts = buildLineStarts(text);
|
|
8951
|
+
const refs = [];
|
|
8952
|
+
for (const macro of parseMacros(text)) {
|
|
8953
|
+
if (!macro.open || !macro.rawArgs) continue;
|
|
8954
|
+
const resolve3 = macroTargetResolvers[macro.name.toLowerCase()];
|
|
8955
|
+
if (!resolve3) continue;
|
|
8956
|
+
const argsEnd = lineStarts[macro.range.end.line] + macro.range.end.character - 1;
|
|
8957
|
+
const argsStart = argsEnd - macro.rawArgs.length;
|
|
8958
|
+
const target = resolve3(text.slice(argsStart, argsEnd));
|
|
8959
|
+
if (!target || target.name === "") continue;
|
|
8960
|
+
const startPos = offsetToPosition(argsStart + target.start, lineStarts);
|
|
8961
|
+
const endPos = offsetToPosition(argsStart + target.end, lineStarts);
|
|
8425
8962
|
refs.push({
|
|
8426
|
-
name:
|
|
8427
|
-
range:
|
|
8428
|
-
|
|
8963
|
+
name: target.name,
|
|
8964
|
+
range: {
|
|
8965
|
+
start: { line: startPos.line + lineOffset, character: startPos.character },
|
|
8966
|
+
end: { line: endPos.line + lineOffset, character: endPos.character }
|
|
8967
|
+
},
|
|
8968
|
+
source: "macro"
|
|
8429
8969
|
});
|
|
8430
8970
|
}
|
|
8431
8971
|
return refs;
|
|
8432
8972
|
}
|
|
8433
|
-
var
|
|
8973
|
+
var EXPRESSION_BUILTINS, interpolationRegex, temporaryRefRegex, macroTargetResolvers;
|
|
8434
8974
|
var init_link_parser = __esm({
|
|
8435
8975
|
"src/core/parsing/link-parser.ts"() {
|
|
8436
8976
|
"use strict";
|
|
8437
|
-
|
|
8977
|
+
init_macro_parser();
|
|
8978
|
+
init_argument_lexer();
|
|
8979
|
+
EXPRESSION_BUILTINS = /* @__PURE__ */ new Set([
|
|
8980
|
+
"currentPassage",
|
|
8981
|
+
"previousPassage",
|
|
8982
|
+
"visited",
|
|
8983
|
+
"hasVisited",
|
|
8984
|
+
"hasVisitedAny",
|
|
8985
|
+
"hasVisitedAll",
|
|
8986
|
+
"rendered",
|
|
8987
|
+
"hasRendered",
|
|
8988
|
+
"hasRenderedAny",
|
|
8989
|
+
"hasRenderedAll",
|
|
8990
|
+
"random",
|
|
8991
|
+
"randomInt"
|
|
8992
|
+
]);
|
|
8993
|
+
interpolationRegex = /\{[$_@%][A-Za-z_$]/;
|
|
8994
|
+
temporaryRefRegex = /(?<![.\w$@%])_[A-Za-z_$][\w$]*(?![\w$])/;
|
|
8995
|
+
macroTargetResolvers = {
|
|
8996
|
+
goto: resolveExpressionTarget,
|
|
8997
|
+
include: resolveIncludeTarget,
|
|
8998
|
+
link: resolveLinkMacroTarget
|
|
8999
|
+
};
|
|
8438
9000
|
}
|
|
8439
9001
|
});
|
|
8440
9002
|
|
|
@@ -8447,8 +9009,13 @@ function computeDiagnostics(uri, workspace, options) {
|
|
|
8447
9009
|
const passages = workspace.passages.getPassagesInDocument(uri);
|
|
8448
9010
|
if (passages.length === 0) return [];
|
|
8449
9011
|
const diagnostics = [];
|
|
8450
|
-
const
|
|
8451
|
-
|
|
9012
|
+
const markupText = maskScriptAndStylesheetPassages(text, passages);
|
|
9013
|
+
const macros = parseMacros(markupText);
|
|
9014
|
+
pairMacros(
|
|
9015
|
+
macros,
|
|
9016
|
+
(name) => workspace.macros.isBlock(name),
|
|
9017
|
+
passages.map((p) => p.range.start.line)
|
|
9018
|
+
);
|
|
8452
9019
|
const allPassages = workspace.passages.getAllPassages();
|
|
8453
9020
|
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
8454
9021
|
try {
|
|
@@ -8464,13 +9031,29 @@ function computeDiagnostics(uri, workspace, options) {
|
|
|
8464
9031
|
} catch {
|
|
8465
9032
|
}
|
|
8466
9033
|
try {
|
|
8467
|
-
validateLinks(
|
|
9034
|
+
validateLinks(markupText, passages, passageNames, diagnostics);
|
|
8468
9035
|
} catch {
|
|
8469
9036
|
}
|
|
8470
9037
|
try {
|
|
8471
9038
|
validateWidgetInvocations(macros, workspace, diagnostics);
|
|
8472
9039
|
} catch {
|
|
8473
9040
|
}
|
|
9041
|
+
try {
|
|
9042
|
+
validateArrayMemberAccess(uri, workspace, diagnostics);
|
|
9043
|
+
} catch {
|
|
9044
|
+
}
|
|
9045
|
+
try {
|
|
9046
|
+
validateLoopTemporaries(uri, text, macros, workspace, diagnostics);
|
|
9047
|
+
} catch {
|
|
9048
|
+
}
|
|
9049
|
+
try {
|
|
9050
|
+
validateWidgetIncludes(text, macros, workspace, diagnostics);
|
|
9051
|
+
} catch {
|
|
9052
|
+
}
|
|
9053
|
+
try {
|
|
9054
|
+
validateUnusedWidgets(uri, workspace, diagnostics);
|
|
9055
|
+
} catch {
|
|
9056
|
+
}
|
|
8474
9057
|
if (options?.maxLineLength) {
|
|
8475
9058
|
try {
|
|
8476
9059
|
validateLineLength(text, passages, options.maxLineLength, diagnostics);
|
|
@@ -8482,6 +9065,18 @@ function computeDiagnostics(uri, workspace, options) {
|
|
|
8482
9065
|
return [];
|
|
8483
9066
|
}
|
|
8484
9067
|
}
|
|
9068
|
+
function maskScriptAndStylesheetPassages(text, passages) {
|
|
9069
|
+
const excluded = passages.filter(isScriptOrStylesheetPassage);
|
|
9070
|
+
if (excluded.length === 0) return text;
|
|
9071
|
+
const lines = text.split("\n");
|
|
9072
|
+
for (const passage of excluded) {
|
|
9073
|
+
const last = Math.min(passage.range.end.line, lines.length - 1);
|
|
9074
|
+
for (let i = passage.range.start.line + 1; i <= last; i++) {
|
|
9075
|
+
lines[i] = lines[i].replace(/[^\r]/g, " ");
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
return lines.join("\n");
|
|
9079
|
+
}
|
|
8485
9080
|
function validateMacros(macros, workspace, diagnostics) {
|
|
8486
9081
|
for (let curIndex = 0; curIndex < macros.length; curIndex++) {
|
|
8487
9082
|
const macro = macros[curIndex];
|
|
@@ -8594,8 +9189,13 @@ function validateArguments(macros, workspace, passageNames, diagnostics) {
|
|
|
8594
9189
|
if (info.skipArgs) continue;
|
|
8595
9190
|
if (!info.parameters) continue;
|
|
8596
9191
|
const rawArgs = macro.rawArgs ?? "";
|
|
8597
|
-
const args2 = lexArguments(rawArgs);
|
|
8598
|
-
|
|
9192
|
+
const args2 = macro.name.toLowerCase() === "include" ? includeArguments(rawArgs) : lexArguments(rawArgs);
|
|
9193
|
+
let params;
|
|
9194
|
+
try {
|
|
9195
|
+
params = new Parameters(info.parameters);
|
|
9196
|
+
} catch {
|
|
9197
|
+
continue;
|
|
9198
|
+
}
|
|
8599
9199
|
if (params.isEmpty()) {
|
|
8600
9200
|
if (args2.length > 0) {
|
|
8601
9201
|
diagnostics.push(makeDiag(
|
|
@@ -8635,6 +9235,16 @@ function validateArguments(macros, workspace, passageNames, diagnostics) {
|
|
|
8635
9235
|
}
|
|
8636
9236
|
}
|
|
8637
9237
|
}
|
|
9238
|
+
function includeArguments(rawArgs) {
|
|
9239
|
+
const expr = includeExpression(rawArgs);
|
|
9240
|
+
if (expr === "") return [];
|
|
9241
|
+
const args2 = lexArguments(expr);
|
|
9242
|
+
if (args2.length === 1) return args2;
|
|
9243
|
+
return [{ type: 7 /* Expression */, text: expr, start: 0, end: expr.length }];
|
|
9244
|
+
}
|
|
9245
|
+
function includeExpression(rawArgs) {
|
|
9246
|
+
return rawArgs.replace(/\binline\b/, "").trim();
|
|
9247
|
+
}
|
|
8638
9248
|
function validateVariables(uri, workspace, diagnostics) {
|
|
8639
9249
|
if (!workspace.variables.hasStoryVariables()) {
|
|
8640
9250
|
const undeclared = workspace.variables.getUndeclared(uri);
|
|
@@ -8725,11 +9335,236 @@ function validateWidgetInvocations(macros, workspace, diagnostics) {
|
|
|
8725
9335
|
}
|
|
8726
9336
|
}
|
|
8727
9337
|
}
|
|
9338
|
+
function validateArrayMemberAccess(uri, workspace, diagnostics) {
|
|
9339
|
+
for (const a of workspace.variables.getArrayMemberAccesses(uri)) {
|
|
9340
|
+
const passage = a.sigil === "%" ? "StoryTransients" : "StoryVariables";
|
|
9341
|
+
const ref = `${a.sigil}${a.name}`;
|
|
9342
|
+
diagnostics.push(makeDiag(
|
|
9343
|
+
a.range,
|
|
9344
|
+
DiagnosticCode.ArrayMemberAccess,
|
|
9345
|
+
`'${ref}' is declared as an array in ${passage}, and arrays have no '${a.member}' property: '${ref}.${a.member}' is always undefined. If you meant to test membership, use ${ref}.includes("${a.member}").`
|
|
9346
|
+
));
|
|
9347
|
+
}
|
|
9348
|
+
}
|
|
9349
|
+
function temporaryRefRegex2(name) {
|
|
9350
|
+
const ident = name ? escapeRegex(name) : "[A-Za-z_$][\\w$]*";
|
|
9351
|
+
return new RegExp(`(?<![.\\w$@%])_(${ident})(?![\\w$])`, "g");
|
|
9352
|
+
}
|
|
9353
|
+
function escapeRegex(s) {
|
|
9354
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9355
|
+
}
|
|
9356
|
+
function maskStrings(code) {
|
|
9357
|
+
let out = "";
|
|
9358
|
+
let i = 0;
|
|
9359
|
+
while (i < code.length) {
|
|
9360
|
+
const ch = code[i];
|
|
9361
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
9362
|
+
out += ch;
|
|
9363
|
+
i++;
|
|
9364
|
+
while (i < code.length && code[i] !== ch) {
|
|
9365
|
+
if (code[i] === "\\" && i + 1 < code.length) {
|
|
9366
|
+
out += " ";
|
|
9367
|
+
i += 2;
|
|
9368
|
+
} else if (ch === "`" && code[i] === "$" && code[i + 1] === "{") {
|
|
9369
|
+
const end = interpolationEnd(code, i + 2);
|
|
9370
|
+
out += maskStrings(code.slice(i, end));
|
|
9371
|
+
i = end;
|
|
9372
|
+
} else {
|
|
9373
|
+
out += code[i] === "\n" ? "\n" : " ";
|
|
9374
|
+
i++;
|
|
9375
|
+
}
|
|
9376
|
+
}
|
|
9377
|
+
if (i < code.length) {
|
|
9378
|
+
out += ch;
|
|
9379
|
+
i++;
|
|
9380
|
+
}
|
|
9381
|
+
continue;
|
|
9382
|
+
}
|
|
9383
|
+
out += ch;
|
|
9384
|
+
i++;
|
|
9385
|
+
}
|
|
9386
|
+
return out;
|
|
9387
|
+
}
|
|
9388
|
+
function interpolationEnd(code, from) {
|
|
9389
|
+
let depth = 1;
|
|
9390
|
+
for (let i = from; i < code.length; i++) {
|
|
9391
|
+
if (code[i] === "{") depth++;
|
|
9392
|
+
else if (code[i] === "}" && --depth === 0) return i + 1;
|
|
9393
|
+
}
|
|
9394
|
+
return code.length;
|
|
9395
|
+
}
|
|
9396
|
+
function computedTemporaryTarget(rawArgs) {
|
|
9397
|
+
const lead = rawArgs.length - rawArgs.trimStart().length;
|
|
9398
|
+
const trimmed = rawArgs.trim();
|
|
9399
|
+
let depth = 0;
|
|
9400
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
9401
|
+
const ch = trimmed[i];
|
|
9402
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
9403
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
9404
|
+
else if (ch === "=" && depth === 0) {
|
|
9405
|
+
if (trimmed[i + 1] === "=") {
|
|
9406
|
+
i++;
|
|
9407
|
+
continue;
|
|
9408
|
+
}
|
|
9409
|
+
if (i > 0 && trimmed[i - 1] === "!") continue;
|
|
9410
|
+
const target = trimmed.slice(0, i).trim();
|
|
9411
|
+
const m = /^_(\w+)$/.exec(target);
|
|
9412
|
+
return m ? [{ name: m[1], offset: lead }] : [];
|
|
9413
|
+
}
|
|
9414
|
+
}
|
|
9415
|
+
return [];
|
|
9416
|
+
}
|
|
9417
|
+
function setTemporaryTargets(rawArgs) {
|
|
9418
|
+
const code = maskStrings(rawArgs);
|
|
9419
|
+
const targets = [];
|
|
9420
|
+
const assignRe = /(?<![.\w$@%])_([A-Za-z_$][\w$]*)\s*=(?![=>])/g;
|
|
9421
|
+
let m;
|
|
9422
|
+
while ((m = assignRe.exec(code)) !== null) {
|
|
9423
|
+
const name = m[1];
|
|
9424
|
+
const rhs = code.slice(m.index + m[0].length, statementEnd(code, m.index + m[0].length));
|
|
9425
|
+
if (temporaryRefRegex2(name).test(rhs)) continue;
|
|
9426
|
+
targets.push({ name, offset: m.index });
|
|
9427
|
+
}
|
|
9428
|
+
return targets;
|
|
9429
|
+
}
|
|
9430
|
+
function statementEnd(code, from) {
|
|
9431
|
+
let depth = 0;
|
|
9432
|
+
for (let i = from; i < code.length; i++) {
|
|
9433
|
+
const ch = code[i];
|
|
9434
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
9435
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth--;
|
|
9436
|
+
else if ((ch === ";" || ch === ",") && depth <= 0) return i;
|
|
9437
|
+
}
|
|
9438
|
+
return code.length;
|
|
9439
|
+
}
|
|
9440
|
+
function enclosingLoop(macros, index, uri, workspace) {
|
|
9441
|
+
for (let j = index - 1; j >= 0; j--) {
|
|
9442
|
+
const candidate = macros[j];
|
|
9443
|
+
if (!candidate.open || candidate.pair === -1 || candidate.pair < index) continue;
|
|
9444
|
+
const name = candidate.name.toLowerCase();
|
|
9445
|
+
if (DEFERRED_CONTAINERS.has(name)) return -1;
|
|
9446
|
+
if (name !== "for") continue;
|
|
9447
|
+
const loopPassage = workspace.passages.getPassageAt(uri, candidate.range.start.line);
|
|
9448
|
+
const macroPassage = workspace.passages.getPassageAt(uri, macros[index].range.start.line);
|
|
9449
|
+
return loopPassage === macroPassage ? j : -1;
|
|
9450
|
+
}
|
|
9451
|
+
return -1;
|
|
9452
|
+
}
|
|
9453
|
+
function positionToOffset(pos, lineStarts) {
|
|
9454
|
+
return (lineStarts[pos.line] ?? 0) + pos.character;
|
|
9455
|
+
}
|
|
9456
|
+
function sourceArgs(macro, text, lineStarts) {
|
|
9457
|
+
const end = positionToOffset(macro.range.end, lineStarts) - 1;
|
|
9458
|
+
const start = end - (macro.rawArgs?.length ?? 0);
|
|
9459
|
+
return { args: text.slice(start, end), start };
|
|
9460
|
+
}
|
|
9461
|
+
function validateLoopTemporaries(uri, text, macros, workspace, diagnostics) {
|
|
9462
|
+
const lineStarts = buildLineStarts(text);
|
|
9463
|
+
for (let k = 0; k < macros.length; k++) {
|
|
9464
|
+
const macro = macros[k];
|
|
9465
|
+
if (!macro.open || !macro.rawArgs) continue;
|
|
9466
|
+
const name = macro.name.toLowerCase();
|
|
9467
|
+
if (name !== "computed" && name !== "set") continue;
|
|
9468
|
+
const { args: args2, start: argsStart } = sourceArgs(macro, text, lineStarts);
|
|
9469
|
+
const targets = name === "computed" ? computedTemporaryTarget(args2) : setTemporaryTargets(args2);
|
|
9470
|
+
if (targets.length === 0) continue;
|
|
9471
|
+
const loopIndex = enclosingLoop(macros, k, uri, workspace);
|
|
9472
|
+
if (loopIndex === -1) continue;
|
|
9473
|
+
const loop = macros[loopIndex];
|
|
9474
|
+
const bodyStart = positionToOffset(loop.range.end, lineStarts);
|
|
9475
|
+
const bodyEnd = positionToOffset(macros[loop.pair].range.start, lineStarts);
|
|
9476
|
+
const macroStart = positionToOffset(macro.range.start, lineStarts);
|
|
9477
|
+
const macroEnd = positionToOffset(macro.range.end, lineStarts);
|
|
9478
|
+
const body = text.slice(bodyStart, bodyEnd);
|
|
9479
|
+
for (const target of targets) {
|
|
9480
|
+
if (!isReadInBody(body, bodyStart, target.name, macroStart, macroEnd)) continue;
|
|
9481
|
+
const start = argsStart + target.offset;
|
|
9482
|
+
diagnostics.push(makeDiag(
|
|
9483
|
+
{
|
|
9484
|
+
start: offsetToPosition(start, lineStarts),
|
|
9485
|
+
end: offsetToPosition(start + 1 + target.name.length, lineStarts)
|
|
9486
|
+
},
|
|
9487
|
+
DiagnosticCode.TemporaryAssignedInLoop,
|
|
9488
|
+
`Temporary '_${target.name}' is assigned inside {${loop.name}} and read in the loop body. Temporaries are shared by every iteration, so all iterations end up seeing the value assigned by the last one. Use the iteration-local '@${target.name}' instead, or assign it once before the loop if the value does not depend on the iteration.`
|
|
9489
|
+
));
|
|
9490
|
+
}
|
|
9491
|
+
}
|
|
9492
|
+
}
|
|
9493
|
+
function isReadInBody(body, bodyOffset, name, macroStart, macroEnd) {
|
|
9494
|
+
const re = temporaryRefRegex2(name);
|
|
9495
|
+
let m;
|
|
9496
|
+
while ((m = re.exec(body)) !== null) {
|
|
9497
|
+
const offset = bodyOffset + m.index;
|
|
9498
|
+
if (offset >= macroStart && offset < macroEnd) continue;
|
|
9499
|
+
const after = body.slice(m.index + m[0].length);
|
|
9500
|
+
if (/^\s*=(?![=>])/.test(after)) continue;
|
|
9501
|
+
return true;
|
|
9502
|
+
}
|
|
9503
|
+
return false;
|
|
9504
|
+
}
|
|
9505
|
+
function resolveIncludeTarget2(rawArgs) {
|
|
9506
|
+
const expr = includeExpression(rawArgs);
|
|
9507
|
+
if (expr === "") return null;
|
|
9508
|
+
const literal2 = /^(["'`])((?:\\.|(?!\1)[^\\])*)\1$/s.exec(expr);
|
|
9509
|
+
if (literal2) {
|
|
9510
|
+
if (literal2[1] === "`" && literal2[2].includes("${")) return null;
|
|
9511
|
+
return literal2[2].replace(/\\(.)/g, "$1");
|
|
9512
|
+
}
|
|
9513
|
+
if (/[$@%"'`(]/.test(expr) || temporaryRefRegex2().test(expr)) return null;
|
|
9514
|
+
if (EXPRESSION_BUILTINS2.has(expr)) return null;
|
|
9515
|
+
return expr;
|
|
9516
|
+
}
|
|
9517
|
+
function widgetDefinitionNames(content) {
|
|
9518
|
+
const names = [];
|
|
9519
|
+
const re = /\{widget\s+(["']?)([^\s"'}]+)\1/gi;
|
|
9520
|
+
let m;
|
|
9521
|
+
while ((m = re.exec(content)) !== null) names.push(m[2]);
|
|
9522
|
+
return names;
|
|
9523
|
+
}
|
|
9524
|
+
function passageContent(passage, workspace) {
|
|
9525
|
+
const text = workspace.documents.getText(passage.uri) ?? "";
|
|
9526
|
+
return text.split("\n").slice(passage.range.start.line + 1, passage.range.end.line + 1).join("\n");
|
|
9527
|
+
}
|
|
9528
|
+
function validateWidgetIncludes(text, macros, workspace, diagnostics) {
|
|
9529
|
+
let lineStarts = null;
|
|
9530
|
+
for (const macro of macros) {
|
|
9531
|
+
if (!macro.open || !macro.rawArgs || macro.name.toLowerCase() !== "include") continue;
|
|
9532
|
+
lineStarts ??= buildLineStarts(text);
|
|
9533
|
+
const target = resolveIncludeTarget2(sourceArgs(macro, text, lineStarts).args);
|
|
9534
|
+
if (target === null) continue;
|
|
9535
|
+
const passage = workspace.passages.getPassage(target);
|
|
9536
|
+
if (!passage?.tags?.includes("widget")) continue;
|
|
9537
|
+
const widgets = widgetDefinitionNames(passageContent(passage, workspace));
|
|
9538
|
+
if (widgets.length === 0) continue;
|
|
9539
|
+
const list = widgets.map((w) => `{${w}}`).join(", ");
|
|
9540
|
+
diagnostics.push(makeDiag(
|
|
9541
|
+
macro.range,
|
|
9542
|
+
DiagnosticCode.IncludeWidgetPassage,
|
|
9543
|
+
`{include}: passage "${target}" is tagged [widget] and defines ${list}. Including it does not invoke ${widgets.length === 1 ? "it" : "them"}: {widget} definitions render nothing. Invoke the widget instead, e.g. {${widgets[0]}}.`
|
|
9544
|
+
));
|
|
9545
|
+
}
|
|
9546
|
+
}
|
|
9547
|
+
function validateUnusedWidgets(uri, workspace, diagnostics) {
|
|
9548
|
+
for (const widget of workspace.widgets.getAllWidgets()) {
|
|
9549
|
+
if (widget.uri !== uri) continue;
|
|
9550
|
+
if (workspace.widgets.isInvoked(widget.name)) continue;
|
|
9551
|
+
diagnostics.push(makeDiag(
|
|
9552
|
+
widget.range,
|
|
9553
|
+
DiagnosticCode.UnusedWidget,
|
|
9554
|
+
`Widget "${widget.name}" is defined but never invoked in the workspace`
|
|
9555
|
+
));
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
8728
9558
|
function validateLineLength(text, passages, maxLength, diagnostics) {
|
|
8729
|
-
const
|
|
8730
|
-
const
|
|
9559
|
+
const excludedLines = /* @__PURE__ */ new Set();
|
|
9560
|
+
for (const passage of passages.filter(isScriptOrStylesheetPassage)) {
|
|
9561
|
+
for (let i = passage.range.start.line; i <= passage.range.end.line; i++) {
|
|
9562
|
+
excludedLines.add(i);
|
|
9563
|
+
}
|
|
9564
|
+
}
|
|
8731
9565
|
const lines = text.split("\n");
|
|
8732
9566
|
for (let i = 0; i < lines.length; i++) {
|
|
9567
|
+
if (excludedLines.has(i)) continue;
|
|
8733
9568
|
const line = lines[i];
|
|
8734
9569
|
if (/^::\s+/.test(line)) continue;
|
|
8735
9570
|
if (/^\s*<[a-zA-Z]/.test(line)) continue;
|
|
@@ -8772,7 +9607,7 @@ function toLspDiagnostic(d) {
|
|
|
8772
9607
|
message: d.message
|
|
8773
9608
|
};
|
|
8774
9609
|
}
|
|
8775
|
-
var diagnosticsPlugin;
|
|
9610
|
+
var DEFERRED_CONTAINERS, EXPRESSION_BUILTINS2, diagnosticsPlugin;
|
|
8776
9611
|
var init_diagnostics = __esm({
|
|
8777
9612
|
"src/plugins/diagnostics.ts"() {
|
|
8778
9613
|
"use strict";
|
|
@@ -8781,26 +9616,42 @@ var init_diagnostics = __esm({
|
|
|
8781
9616
|
init_argument_lexer();
|
|
8782
9617
|
init_parameter_validator();
|
|
8783
9618
|
init_link_parser();
|
|
9619
|
+
init_passage_parser();
|
|
9620
|
+
DEFERRED_CONTAINERS = /* @__PURE__ */ new Set(["link", "button"]);
|
|
9621
|
+
EXPRESSION_BUILTINS2 = /* @__PURE__ */ new Set([
|
|
9622
|
+
"currentPassage",
|
|
9623
|
+
"previousPassage",
|
|
9624
|
+
"visited",
|
|
9625
|
+
"hasVisited",
|
|
9626
|
+
"hasVisitedAny",
|
|
9627
|
+
"hasVisitedAll",
|
|
9628
|
+
"rendered",
|
|
9629
|
+
"hasRendered",
|
|
9630
|
+
"hasRenderedAny",
|
|
9631
|
+
"hasRenderedAll",
|
|
9632
|
+
"random",
|
|
9633
|
+
"randomInt"
|
|
9634
|
+
]);
|
|
8784
9635
|
diagnosticsPlugin = {
|
|
8785
9636
|
id: "diagnostics",
|
|
8786
9637
|
capabilities: {},
|
|
8787
9638
|
initialize(ctx) {
|
|
8788
|
-
const
|
|
8789
|
-
|
|
8790
|
-
const diags = computeDiagnostics(uri, ctx.workspace);
|
|
8791
|
-
ctx.connection.sendDiagnostics({
|
|
8792
|
-
uri,
|
|
8793
|
-
diagnostics: diags.map((d) => toLspDiagnostic(d))
|
|
8794
|
-
});
|
|
8795
|
-
}
|
|
8796
|
-
};
|
|
8797
|
-
ctx.workspace.on("modelReady", publishDiagnostics);
|
|
8798
|
-
ctx.workspace.on("documentChanged", (uri) => {
|
|
9639
|
+
const isEnabled = (d) => ctx.config.diagnostics?.[d.code] !== false;
|
|
9640
|
+
const publishFor = (uri) => {
|
|
8799
9641
|
const diags = computeDiagnostics(uri, ctx.workspace);
|
|
8800
9642
|
ctx.connection.sendDiagnostics({
|
|
8801
9643
|
uri,
|
|
8802
|
-
diagnostics: diags.map((d) => toLspDiagnostic(d))
|
|
9644
|
+
diagnostics: diags.filter(isEnabled).map((d) => toLspDiagnostic(d))
|
|
8803
9645
|
});
|
|
9646
|
+
};
|
|
9647
|
+
ctx.workspace.on("modelReady", () => {
|
|
9648
|
+
for (const uri of ctx.workspace.documents.getUris()) {
|
|
9649
|
+
publishFor(uri);
|
|
9650
|
+
}
|
|
9651
|
+
});
|
|
9652
|
+
ctx.workspace.on("documentChanged", publishFor);
|
|
9653
|
+
ctx.workspace.on("documentClosed", (uri) => {
|
|
9654
|
+
ctx.connection.sendDiagnostics({ uri, diagnostics: [] });
|
|
8804
9655
|
});
|
|
8805
9656
|
}
|
|
8806
9657
|
};
|
|
@@ -8820,9 +9671,9 @@ var require_identity = __commonJS({
|
|
|
8820
9671
|
var NODE_TYPE = Symbol.for("yaml.node.type");
|
|
8821
9672
|
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
|
8822
9673
|
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
|
8823
|
-
var
|
|
9674
|
+
var isMap2 = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
|
8824
9675
|
var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR;
|
|
8825
|
-
var
|
|
9676
|
+
var isScalar2 = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR;
|
|
8826
9677
|
var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ;
|
|
8827
9678
|
function isCollection(node) {
|
|
8828
9679
|
if (node && typeof node === "object")
|
|
@@ -8844,7 +9695,7 @@ var require_identity = __commonJS({
|
|
|
8844
9695
|
}
|
|
8845
9696
|
return false;
|
|
8846
9697
|
}
|
|
8847
|
-
var hasAnchor = (node) => (
|
|
9698
|
+
var hasAnchor = (node) => (isScalar2(node) || isCollection(node)) && !!node.anchor;
|
|
8848
9699
|
exports.ALIAS = ALIAS;
|
|
8849
9700
|
exports.DOC = DOC;
|
|
8850
9701
|
exports.MAP = MAP;
|
|
@@ -8856,10 +9707,10 @@ var require_identity = __commonJS({
|
|
|
8856
9707
|
exports.isAlias = isAlias;
|
|
8857
9708
|
exports.isCollection = isCollection;
|
|
8858
9709
|
exports.isDocument = isDocument;
|
|
8859
|
-
exports.isMap =
|
|
9710
|
+
exports.isMap = isMap2;
|
|
8860
9711
|
exports.isNode = isNode;
|
|
8861
9712
|
exports.isPair = isPair;
|
|
8862
|
-
exports.isScalar =
|
|
9713
|
+
exports.isScalar = isScalar2;
|
|
8863
9714
|
exports.isSeq = isSeq;
|
|
8864
9715
|
}
|
|
8865
9716
|
});
|
|
@@ -12961,9 +13812,9 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12961
13812
|
var blockMsg = "Block collections are not allowed within flow collections";
|
|
12962
13813
|
var isBlock = (token) => token && (token.type === "block-map" || token.type === "block-seq");
|
|
12963
13814
|
function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) {
|
|
12964
|
-
const
|
|
12965
|
-
const fcName =
|
|
12966
|
-
const NodeClass = tag?.nodeClass ?? (
|
|
13815
|
+
const isMap2 = fc.start.source === "{";
|
|
13816
|
+
const fcName = isMap2 ? "flow map" : "flow sequence";
|
|
13817
|
+
const NodeClass = tag?.nodeClass ?? (isMap2 ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq);
|
|
12967
13818
|
const coll = new NodeClass(ctx.schema);
|
|
12968
13819
|
coll.flow = true;
|
|
12969
13820
|
const atRoot = ctx.atRoot;
|
|
@@ -12999,7 +13850,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
12999
13850
|
offset = props.end;
|
|
13000
13851
|
continue;
|
|
13001
13852
|
}
|
|
13002
|
-
if (!
|
|
13853
|
+
if (!isMap2 && ctx.options.strict && utilContainsNewline.containsNewline(key))
|
|
13003
13854
|
onError(
|
|
13004
13855
|
key,
|
|
13005
13856
|
// checked by containsNewline()
|
|
@@ -13039,7 +13890,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
13039
13890
|
}
|
|
13040
13891
|
}
|
|
13041
13892
|
}
|
|
13042
|
-
if (!
|
|
13893
|
+
if (!isMap2 && !sep2 && !props.found) {
|
|
13043
13894
|
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
|
|
13044
13895
|
coll.items.push(valueNode);
|
|
13045
13896
|
offset = valueNode.range[2];
|
|
@@ -13062,7 +13913,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
13062
13913
|
startOnNewline: false
|
|
13063
13914
|
});
|
|
13064
13915
|
if (valueProps.found) {
|
|
13065
|
-
if (!
|
|
13916
|
+
if (!isMap2 && !props.found && ctx.options.strict) {
|
|
13066
13917
|
if (sep2)
|
|
13067
13918
|
for (const st of sep2) {
|
|
13068
13919
|
if (st === valueProps.found)
|
|
@@ -13094,7 +13945,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
13094
13945
|
const pair = new Pair.Pair(keyNode, valueNode);
|
|
13095
13946
|
if (ctx.options.keepSourceTokens)
|
|
13096
13947
|
pair.srcToken = collItem;
|
|
13097
|
-
if (
|
|
13948
|
+
if (isMap2) {
|
|
13098
13949
|
const map2 = coll;
|
|
13099
13950
|
if (utilMapIncludes.mapIncludes(ctx, map2.items, keyNode))
|
|
13100
13951
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
@@ -13110,7 +13961,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
13110
13961
|
offset = valueNode ? valueNode.range[2] : valueProps.end;
|
|
13111
13962
|
}
|
|
13112
13963
|
}
|
|
13113
|
-
const expectedEnd =
|
|
13964
|
+
const expectedEnd = isMap2 ? "}" : "]";
|
|
13114
13965
|
const [ce, ...ee] = fc.end;
|
|
13115
13966
|
let cePos = offset;
|
|
13116
13967
|
if (ce?.source === expectedEnd)
|
|
@@ -14393,7 +15244,7 @@ var require_cst = __commonJS({
|
|
|
14393
15244
|
var FLOW_END = "";
|
|
14394
15245
|
var SCALAR = "";
|
|
14395
15246
|
var isCollection = (token) => !!token && "items" in token;
|
|
14396
|
-
var
|
|
15247
|
+
var isScalar2 = (token) => !!token && (token.type === "scalar" || token.type === "single-quoted-scalar" || token.type === "double-quoted-scalar" || token.type === "block-scalar");
|
|
14397
15248
|
function prettyToken(token) {
|
|
14398
15249
|
switch (token) {
|
|
14399
15250
|
case BOM:
|
|
@@ -14477,7 +15328,7 @@ var require_cst = __commonJS({
|
|
|
14477
15328
|
exports.FLOW_END = FLOW_END;
|
|
14478
15329
|
exports.SCALAR = SCALAR;
|
|
14479
15330
|
exports.isCollection = isCollection;
|
|
14480
|
-
exports.isScalar =
|
|
15331
|
+
exports.isScalar = isScalar2;
|
|
14481
15332
|
exports.prettyToken = prettyToken;
|
|
14482
15333
|
exports.tokenType = tokenType;
|
|
14483
15334
|
}
|
|
@@ -15990,7 +16841,7 @@ var require_public_api = __commonJS({
|
|
|
15990
16841
|
return docs;
|
|
15991
16842
|
return Object.assign([], { empty: true }, composer$1.streamInfo());
|
|
15992
16843
|
}
|
|
15993
|
-
function
|
|
16844
|
+
function parseDocument2(source, options = {}) {
|
|
15994
16845
|
const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options);
|
|
15995
16846
|
const parser$1 = new parser.Parser(lineCounter2?.addNewLine);
|
|
15996
16847
|
const composer$1 = new composer.Composer(options);
|
|
@@ -16016,7 +16867,7 @@ var require_public_api = __commonJS({
|
|
|
16016
16867
|
} else if (options === void 0 && reviver && typeof reviver === "object") {
|
|
16017
16868
|
options = reviver;
|
|
16018
16869
|
}
|
|
16019
|
-
const doc =
|
|
16870
|
+
const doc = parseDocument2(src, options);
|
|
16020
16871
|
if (!doc)
|
|
16021
16872
|
return null;
|
|
16022
16873
|
doc.warnings.forEach((warning) => log.warn(doc.options.logLevel, warning));
|
|
@@ -16052,7 +16903,7 @@ var require_public_api = __commonJS({
|
|
|
16052
16903
|
}
|
|
16053
16904
|
exports.parse = parse3;
|
|
16054
16905
|
exports.parseAllDocuments = parseAllDocuments;
|
|
16055
|
-
exports.parseDocument =
|
|
16906
|
+
exports.parseDocument = parseDocument2;
|
|
16056
16907
|
exports.stringify = stringify;
|
|
16057
16908
|
}
|
|
16058
16909
|
});
|
|
@@ -16142,6 +16993,22 @@ function findConfigFile(workspaceRoot) {
|
|
|
16142
16993
|
}
|
|
16143
16994
|
return null;
|
|
16144
16995
|
}
|
|
16996
|
+
function loadConfigFile(configPath) {
|
|
16997
|
+
let content;
|
|
16998
|
+
try {
|
|
16999
|
+
content = readFileSync3(configPath, "utf-8");
|
|
17000
|
+
} catch (err) {
|
|
17001
|
+
const reason = err?.code === "ENOENT" ? "file not found" : err instanceof Error ? err.message : String(err);
|
|
17002
|
+
throw new Error(`Cannot read config file '${configPath}': ${reason}`);
|
|
17003
|
+
}
|
|
17004
|
+
const format = configPath.toLowerCase().endsWith(".json") ? "json" : "yaml";
|
|
17005
|
+
try {
|
|
17006
|
+
return parseConfig(content, format);
|
|
17007
|
+
} catch (err) {
|
|
17008
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
17009
|
+
throw new Error(`Invalid ${format.toUpperCase()} in config file '${configPath}': ${reason}`);
|
|
17010
|
+
}
|
|
17011
|
+
}
|
|
16145
17012
|
function loadConfigFromDisk(workspaceRoot) {
|
|
16146
17013
|
const configPath = findConfigFile(workspaceRoot);
|
|
16147
17014
|
if (!configPath) {
|
|
@@ -16409,6 +17276,15 @@ function filterBySeverity(diagnostics, minSeverity) {
|
|
|
16409
17276
|
async function runCheck(args2) {
|
|
16410
17277
|
const options = parseArgs(args2);
|
|
16411
17278
|
const cwd = process.cwd();
|
|
17279
|
+
let explicitConfig = null;
|
|
17280
|
+
if (options.configPath) {
|
|
17281
|
+
try {
|
|
17282
|
+
explicitConfig = loadConfigFile(resolve(cwd, options.configPath));
|
|
17283
|
+
} catch (err) {
|
|
17284
|
+
console.error(`spindle-lsp check: ${err instanceof Error ? err.message : String(err)}`);
|
|
17285
|
+
return 2;
|
|
17286
|
+
}
|
|
17287
|
+
}
|
|
16412
17288
|
const files = [];
|
|
16413
17289
|
for (const pattern of options.patterns) {
|
|
16414
17290
|
const matches = await glob(pattern, {
|
|
@@ -16429,12 +17305,12 @@ async function runCheck(args2) {
|
|
|
16429
17305
|
}
|
|
16430
17306
|
return 0;
|
|
16431
17307
|
}
|
|
16432
|
-
let
|
|
16433
|
-
if (
|
|
16434
|
-
|
|
17308
|
+
let projectConfig;
|
|
17309
|
+
if (explicitConfig) {
|
|
17310
|
+
projectConfig = explicitConfig;
|
|
16435
17311
|
} else {
|
|
16436
17312
|
const dirs = uniqueFiles.map((f) => resolve(f, ".."));
|
|
16437
|
-
configRoot = dirs.reduce((a, b) => {
|
|
17313
|
+
let configRoot = dirs.reduce((a, b) => {
|
|
16438
17314
|
while (!b.startsWith(a)) a = resolve(a, "..");
|
|
16439
17315
|
return a;
|
|
16440
17316
|
});
|
|
@@ -16448,8 +17324,8 @@ async function runCheck(args2) {
|
|
|
16448
17324
|
if (parent === search) break;
|
|
16449
17325
|
search = parent;
|
|
16450
17326
|
}
|
|
17327
|
+
projectConfig = loadConfigFromDisk(configRoot);
|
|
16451
17328
|
}
|
|
16452
|
-
const projectConfig = loadConfigFromDisk(configRoot);
|
|
16453
17329
|
const workspace = new WorkspaceModel();
|
|
16454
17330
|
const fileContents = /* @__PURE__ */ new Map();
|
|
16455
17331
|
for (const filePath of uniqueFiles) {
|
|
@@ -16462,7 +17338,7 @@ async function runCheck(args2) {
|
|
|
16462
17338
|
}
|
|
16463
17339
|
workspace.initialize(fileContents);
|
|
16464
17340
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
16465
|
-
workspace.macros.
|
|
17341
|
+
workspace.macros.loadConfig(projectConfig.macros);
|
|
16466
17342
|
}
|
|
16467
17343
|
const diagOptions = {};
|
|
16468
17344
|
if (options.maxLineLength !== null) {
|
|
@@ -16549,7 +17425,7 @@ function splitPassages(text) {
|
|
|
16549
17425
|
function classifyPassage(header) {
|
|
16550
17426
|
const tagMatch = header.match(/\[([^\]]*)\]/g);
|
|
16551
17427
|
if (!tagMatch) return "normal";
|
|
16552
|
-
const tags = tagMatch.
|
|
17428
|
+
const tags = tagMatch.flatMap((t) => t.slice(1, -1).toLowerCase().split(/\s+/));
|
|
16553
17429
|
if (tags.includes("script")) return "script";
|
|
16554
17430
|
if (tags.includes("stylesheet")) return "stylesheet";
|
|
16555
17431
|
return "normal";
|
|
@@ -16568,6 +17444,10 @@ function segmentRegions(body) {
|
|
|
16568
17444
|
if (/^<script(\s|>)/i.test(trimmed)) {
|
|
16569
17445
|
const scriptLines = [line];
|
|
16570
17446
|
i++;
|
|
17447
|
+
if (/<\/script\s*>/i.test(line)) {
|
|
17448
|
+
regions.push({ type: "script", lines: scriptLines });
|
|
17449
|
+
continue;
|
|
17450
|
+
}
|
|
16571
17451
|
while (i < lines.length && !/<\/script>/i.test(lines[i])) {
|
|
16572
17452
|
scriptLines.push(lines[i]);
|
|
16573
17453
|
i++;
|
|
@@ -16792,7 +17672,7 @@ function replaceSvgBlocks(html) {
|
|
|
16792
17672
|
function restoreSvgBlocks(text, tokens) {
|
|
16793
17673
|
let result = text;
|
|
16794
17674
|
for (let i = 0; i < tokens.length; i++) {
|
|
16795
|
-
result = result.replace(`<!--SVG:${i}-->`, tokens[i]);
|
|
17675
|
+
result = result.replace(`<!--SVG:${i}-->`, () => tokens[i]);
|
|
16796
17676
|
}
|
|
16797
17677
|
return result;
|
|
16798
17678
|
}
|
|
@@ -16840,8 +17720,8 @@ function replaceSpindleTokens(html) {
|
|
|
16840
17720
|
function restoreSpindleTokens(text, tokens) {
|
|
16841
17721
|
let result = text;
|
|
16842
17722
|
for (let i = 0; i < tokens.length; i++) {
|
|
16843
|
-
result = result.replace(`<!--SP:${i}-->`, tokens[i]);
|
|
16844
|
-
result = result.replace(`__SP${i}__`, tokens[i]);
|
|
17723
|
+
result = result.replace(`<!--SP:${i}-->`, () => tokens[i]);
|
|
17724
|
+
result = result.replace(`__SP${i}__`, () => tokens[i]);
|
|
16845
17725
|
}
|
|
16846
17726
|
return result;
|
|
16847
17727
|
}
|
|
@@ -16884,6 +17764,7 @@ async function formatDocument(text, options) {
|
|
|
16884
17764
|
const isDedenting = options?.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING.has(name.toLowerCase()));
|
|
16885
17765
|
const passages = splitPassages(text);
|
|
16886
17766
|
const resultLines = [];
|
|
17767
|
+
const hardBreakLines = /* @__PURE__ */ new Set();
|
|
16887
17768
|
for (let pi = 0; pi < passages.length; pi++) {
|
|
16888
17769
|
const passage = passages[pi];
|
|
16889
17770
|
if (pi > 0 && passage.header) {
|
|
@@ -16912,6 +17793,10 @@ async function formatDocument(text, options) {
|
|
|
16912
17793
|
const regions = segmentRegions(passage.body);
|
|
16913
17794
|
for (const region of regions) {
|
|
16914
17795
|
if (region.type === "script") {
|
|
17796
|
+
if (region.lines.length === 1) {
|
|
17797
|
+
resultLines.push(region.lines[0]);
|
|
17798
|
+
continue;
|
|
17799
|
+
}
|
|
16915
17800
|
const firstLine = region.lines[0];
|
|
16916
17801
|
const lastLine = region.lines[region.lines.length - 1];
|
|
16917
17802
|
const innerLines = region.lines.slice(1, region.lines.length - 1);
|
|
@@ -16946,49 +17831,92 @@ async function formatDocument(text, options) {
|
|
|
16946
17831
|
if (options?.maxLineLength) {
|
|
16947
17832
|
indented = wrapLines(indented, options.maxLineLength);
|
|
16948
17833
|
}
|
|
16949
|
-
|
|
17834
|
+
for (const line of indented) {
|
|
17835
|
+
if (line.endsWith(" ")) hardBreakLines.add(resultLines.length);
|
|
17836
|
+
resultLines.push(line);
|
|
17837
|
+
}
|
|
16950
17838
|
}
|
|
16951
17839
|
}
|
|
16952
|
-
let output = resultLines.map((l) => l.replace(/\s+$/, "")).join("\n");
|
|
17840
|
+
let output = resultLines.map((l, i) => hardBreakLines.has(i) ? l : l.replace(/\s+$/, "")).join("\n");
|
|
16953
17841
|
output = output.replace(/\n*$/, "\n");
|
|
16954
17842
|
return output;
|
|
16955
17843
|
}
|
|
16956
17844
|
function indentMacros(lines, isBlock, isDedenting) {
|
|
16957
|
-
const
|
|
16958
|
-
|
|
16959
|
-
|
|
16960
|
-
|
|
16961
|
-
|
|
17845
|
+
const bodies = [{ openLine: -1, minCol: Infinity, keepRelative: false }];
|
|
17846
|
+
const stack = [0];
|
|
17847
|
+
const info = [];
|
|
17848
|
+
const openBody = (line) => {
|
|
17849
|
+
bodies.push({ openLine: line, minCol: Infinity, keepRelative: false });
|
|
17850
|
+
stack.push(bodies.length - 1);
|
|
17851
|
+
};
|
|
17852
|
+
const closeBody = () => stack.length > 1 ? stack.pop() : void 0;
|
|
17853
|
+
const addLine = (col, text) => {
|
|
17854
|
+
const body = bodies[stack[stack.length - 1]];
|
|
17855
|
+
body.minCol = Math.min(body.minCol, col);
|
|
17856
|
+
if (LIST_ITEM_REGEX.test(text) || CODE_FENCE_REGEX.test(text)) body.keepRelative = true;
|
|
17857
|
+
info.push({ body: stack[stack.length - 1], col, text });
|
|
17858
|
+
};
|
|
17859
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17860
|
+
const trimmed = lines[i].trim();
|
|
16962
17861
|
if (trimmed === "") {
|
|
16963
|
-
|
|
17862
|
+
info.push(null);
|
|
16964
17863
|
continue;
|
|
16965
17864
|
}
|
|
17865
|
+
const col = indentColumn(lines[i]);
|
|
16966
17866
|
const dedentMatch = trimmed.match(MACRO_OPEN_REGEX);
|
|
16967
17867
|
if (dedentMatch && isDedenting(dedentMatch[1])) {
|
|
16968
|
-
|
|
16969
|
-
|
|
16970
|
-
|
|
17868
|
+
const closed2 = closeBody();
|
|
17869
|
+
if (closed2 === void 0) addLine(col, trimmed);
|
|
17870
|
+
else info.push({ body: stack[stack.length - 1], closes: closed2, col, text: trimmed });
|
|
17871
|
+
openBody(i);
|
|
16971
17872
|
continue;
|
|
16972
17873
|
}
|
|
16973
|
-
const
|
|
16974
|
-
if (
|
|
16975
|
-
|
|
16976
|
-
}
|
|
16977
|
-
result.push(indentLevel > 0 ? " ".repeat(indentLevel) + trimmed : trimmed);
|
|
17874
|
+
const closed = MACRO_CLOSE_REGEX.test(trimmed) ? closeBody() : void 0;
|
|
17875
|
+
if (closed === void 0) addLine(col, trimmed);
|
|
17876
|
+
else info.push({ body: stack[stack.length - 1], closes: closed, col, text: trimmed });
|
|
16978
17877
|
const openMatch = trimmed.match(MACRO_OPEN_REGEX);
|
|
16979
17878
|
if (openMatch && isBlock(openMatch[1])) {
|
|
16980
|
-
|
|
17879
|
+
openBody(i);
|
|
16981
17880
|
}
|
|
16982
17881
|
const closeGlobal = /\{\/[A-Za-z][\w-]*\s*\}/g;
|
|
16983
17882
|
let cm;
|
|
16984
17883
|
while ((cm = closeGlobal.exec(trimmed)) !== null) {
|
|
16985
|
-
if (cm.index > 0)
|
|
16986
|
-
|
|
16987
|
-
|
|
17884
|
+
if (cm.index > 0) closeBody();
|
|
17885
|
+
}
|
|
17886
|
+
}
|
|
17887
|
+
const outCol = [];
|
|
17888
|
+
const result = [];
|
|
17889
|
+
for (let i = 0; i < info.length; i++) {
|
|
17890
|
+
const line = info[i];
|
|
17891
|
+
if (!line) {
|
|
17892
|
+
outCol.push(0);
|
|
17893
|
+
result.push("");
|
|
17894
|
+
continue;
|
|
17895
|
+
}
|
|
17896
|
+
let col;
|
|
17897
|
+
if (line.closes !== void 0) {
|
|
17898
|
+
col = outCol[bodies[line.closes].openLine];
|
|
17899
|
+
} else {
|
|
17900
|
+
const body = bodies[line.body];
|
|
17901
|
+
const isRoot = body.openLine === -1;
|
|
17902
|
+
const base = isRoot ? 0 : outCol[body.openLine] + 2;
|
|
17903
|
+
col = base + (body.keepRelative ? line.col - (isRoot ? 0 : body.minCol) : 0);
|
|
16988
17904
|
}
|
|
17905
|
+
outCol.push(col);
|
|
17906
|
+
const hardBreak = / {2,}$/.test(lines[i]) && info[i + 1] ? " " : "";
|
|
17907
|
+
result.push(" ".repeat(col) + line.text + hardBreak);
|
|
16989
17908
|
}
|
|
16990
17909
|
return result;
|
|
16991
17910
|
}
|
|
17911
|
+
function indentColumn(line) {
|
|
17912
|
+
let col = 0;
|
|
17913
|
+
for (const ch of line) {
|
|
17914
|
+
if (ch === " ") col++;
|
|
17915
|
+
else if (ch === " ") col += 4 - col % 4;
|
|
17916
|
+
else break;
|
|
17917
|
+
}
|
|
17918
|
+
return col;
|
|
17919
|
+
}
|
|
16992
17920
|
async function formatRange(text, _range, options) {
|
|
16993
17921
|
return formatDocument(text, options);
|
|
16994
17922
|
}
|
|
@@ -17010,6 +17938,7 @@ function wrapLines(lines, maxLen) {
|
|
|
17010
17938
|
const indentMatch = line.match(/^(\s*)/);
|
|
17011
17939
|
const indent = indentMatch ? indentMatch[1] : "";
|
|
17012
17940
|
const wrapped = wordWrap(trimmed, maxLen, indent);
|
|
17941
|
+
if (line.endsWith(" ")) wrapped[wrapped.length - 1] += " ";
|
|
17013
17942
|
result.push(...wrapped);
|
|
17014
17943
|
}
|
|
17015
17944
|
return result;
|
|
@@ -17070,7 +17999,7 @@ function normalizePassageHeader(line) {
|
|
|
17070
17999
|
}
|
|
17071
18000
|
return `:: ${name}${suffix}`;
|
|
17072
18001
|
}
|
|
17073
|
-
var PASSAGE_HEADER_REGEX2, MACRO_OPEN_REGEX, MACRO_CLOSE_REGEX, DEFAULT_DEDENTING, formatPlugin;
|
|
18002
|
+
var PASSAGE_HEADER_REGEX2, MACRO_OPEN_REGEX, MACRO_CLOSE_REGEX, DEFAULT_DEDENTING, LIST_ITEM_REGEX, CODE_FENCE_REGEX, formatPlugin;
|
|
17074
18003
|
var init_format = __esm({
|
|
17075
18004
|
"src/plugins/format.ts"() {
|
|
17076
18005
|
"use strict";
|
|
@@ -17082,6 +18011,8 @@ var init_format = __esm({
|
|
|
17082
18011
|
MACRO_OPEN_REGEX = /^\{(?:[#.][a-zA-Z][\w-]*\s*)*([A-Za-z][\w-]*)\b/;
|
|
17083
18012
|
MACRO_CLOSE_REGEX = /^\{\/([A-Za-z][\w-]*)\b/;
|
|
17084
18013
|
DEFAULT_DEDENTING = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
|
|
18014
|
+
LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
18015
|
+
CODE_FENCE_REGEX = /^(?:`{3,}|~{3,})/;
|
|
17085
18016
|
formatPlugin = {
|
|
17086
18017
|
id: "format",
|
|
17087
18018
|
capabilities: {
|
|
@@ -21275,7 +22206,7 @@ __export(util_exports, {
|
|
|
21275
22206
|
createTransparentProxy: () => createTransparentProxy,
|
|
21276
22207
|
defineLazy: () => defineLazy,
|
|
21277
22208
|
esc: () => esc,
|
|
21278
|
-
escapeRegex: () =>
|
|
22209
|
+
escapeRegex: () => escapeRegex2,
|
|
21279
22210
|
extend: () => extend,
|
|
21280
22211
|
finalizeIssue: () => finalizeIssue,
|
|
21281
22212
|
floatSafeRemainder: () => floatSafeRemainder2,
|
|
@@ -21487,7 +22418,7 @@ function numKeys(data) {
|
|
|
21487
22418
|
}
|
|
21488
22419
|
return keyCount;
|
|
21489
22420
|
}
|
|
21490
|
-
function
|
|
22421
|
+
function escapeRegex2(str) {
|
|
21491
22422
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21492
22423
|
}
|
|
21493
22424
|
function clone(inst, def, params) {
|
|
@@ -22293,7 +23224,7 @@ var init_regexes = __esm({
|
|
|
22293
23224
|
ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
22294
23225
|
ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
22295
23226
|
mac = (delimiter) => {
|
|
22296
|
-
const escapedDelim =
|
|
23227
|
+
const escapedDelim = escapeRegex2(delimiter ?? ":");
|
|
22297
23228
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
22298
23229
|
};
|
|
22299
23230
|
cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -22783,7 +23714,7 @@ var init_checks = __esm({
|
|
|
22783
23714
|
});
|
|
22784
23715
|
$ZodCheckIncludes = /* @__PURE__ */ $constructor("$ZodCheckIncludes", (inst, def) => {
|
|
22785
23716
|
$ZodCheck.init(inst, def);
|
|
22786
|
-
const escapedRegex =
|
|
23717
|
+
const escapedRegex = escapeRegex2(def.includes);
|
|
22787
23718
|
const pattern = new RegExp(typeof def.position === "number" ? `^.{${def.position}}${escapedRegex}` : escapedRegex);
|
|
22788
23719
|
def.pattern = pattern;
|
|
22789
23720
|
inst._zod.onattach.push((inst2) => {
|
|
@@ -22807,7 +23738,7 @@ var init_checks = __esm({
|
|
|
22807
23738
|
});
|
|
22808
23739
|
$ZodCheckStartsWith = /* @__PURE__ */ $constructor("$ZodCheckStartsWith", (inst, def) => {
|
|
22809
23740
|
$ZodCheck.init(inst, def);
|
|
22810
|
-
const pattern = new RegExp(`^${
|
|
23741
|
+
const pattern = new RegExp(`^${escapeRegex2(def.prefix)}.*`);
|
|
22811
23742
|
def.pattern ?? (def.pattern = pattern);
|
|
22812
23743
|
inst._zod.onattach.push((inst2) => {
|
|
22813
23744
|
const bag = inst2._zod.bag;
|
|
@@ -22830,7 +23761,7 @@ var init_checks = __esm({
|
|
|
22830
23761
|
});
|
|
22831
23762
|
$ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst, def) => {
|
|
22832
23763
|
$ZodCheck.init(inst, def);
|
|
22833
|
-
const pattern = new RegExp(`.*${
|
|
23764
|
+
const pattern = new RegExp(`.*${escapeRegex2(def.suffix)}$`);
|
|
22834
23765
|
def.pattern ?? (def.pattern = pattern);
|
|
22835
23766
|
inst._zod.onattach.push((inst2) => {
|
|
22836
23767
|
const bag = inst2._zod.bag;
|
|
@@ -24468,7 +25399,7 @@ var init_schemas = __esm({
|
|
|
24468
25399
|
const values = getEnumValues(def.entries);
|
|
24469
25400
|
const valuesSet = new Set(values);
|
|
24470
25401
|
inst._zod.values = valuesSet;
|
|
24471
|
-
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ?
|
|
25402
|
+
inst._zod.pattern = new RegExp(`^(${values.filter((k) => propertyKeyTypes.has(typeof k)).map((o) => typeof o === "string" ? escapeRegex2(o) : o.toString()).join("|")})$`);
|
|
24472
25403
|
inst._zod.parse = (payload, _ctx) => {
|
|
24473
25404
|
const input = payload.value;
|
|
24474
25405
|
if (valuesSet.has(input)) {
|
|
@@ -24490,7 +25421,7 @@ var init_schemas = __esm({
|
|
|
24490
25421
|
}
|
|
24491
25422
|
const values = new Set(def.values);
|
|
24492
25423
|
inst._zod.values = values;
|
|
24493
|
-
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ?
|
|
25424
|
+
inst._zod.pattern = new RegExp(`^(${def.values.map((o) => typeof o === "string" ? escapeRegex2(o) : o ? escapeRegex2(o.toString()) : String(o)).join("|")})$`);
|
|
24494
25425
|
inst._zod.parse = (payload, _ctx) => {
|
|
24495
25426
|
const input = payload.value;
|
|
24496
25427
|
if (values.has(input)) {
|
|
@@ -24785,7 +25716,7 @@ var init_schemas = __esm({
|
|
|
24785
25716
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
24786
25717
|
regexParts.push(source.slice(start, end));
|
|
24787
25718
|
} else if (part === null || primitiveTypes.has(typeof part)) {
|
|
24788
|
-
regexParts.push(
|
|
25719
|
+
regexParts.push(escapeRegex2(`${part}`));
|
|
24789
25720
|
} else {
|
|
24790
25721
|
throw new Error(`Invalid template literal part: ${part}`);
|
|
24791
25722
|
}
|
|
@@ -48422,7 +49353,7 @@ function createWorkspace(files) {
|
|
|
48422
49353
|
}
|
|
48423
49354
|
workspace.initialize(fileContents);
|
|
48424
49355
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
48425
|
-
workspace.macros.
|
|
49356
|
+
workspace.macros.loadConfig(projectConfig.macros);
|
|
48426
49357
|
}
|
|
48427
49358
|
return workspace;
|
|
48428
49359
|
}
|
|
@@ -57688,7 +58619,7 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
57688
58619
|
if (position.line >= lines.length) return [];
|
|
57689
58620
|
const lineText = lines[position.line].substring(0, position.character);
|
|
57690
58621
|
if (/\{\/[A-Za-z\w-]*$/.test(lineText)) {
|
|
57691
|
-
return getClosingMacroCompletions(text, position, workspace);
|
|
58622
|
+
return getClosingMacroCompletions(uri, text, position, workspace);
|
|
57692
58623
|
}
|
|
57693
58624
|
const transientDotPathMatch = /%([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
57694
58625
|
if (transientDotPathMatch) {
|
|
@@ -57718,11 +58649,18 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
57718
58649
|
}
|
|
57719
58650
|
return [];
|
|
57720
58651
|
}
|
|
57721
|
-
function getClosingMacroCompletions(text, position, workspace) {
|
|
58652
|
+
function getClosingMacroCompletions(uri, text, position, workspace) {
|
|
58653
|
+
const passages = workspace.passages.getPassagesInDocument(uri);
|
|
57722
58654
|
const macros = parseMacros(text);
|
|
57723
|
-
pairMacros(
|
|
58655
|
+
pairMacros(
|
|
58656
|
+
macros,
|
|
58657
|
+
(name) => workspace.macros.isBlock(name),
|
|
58658
|
+
passages.map((p) => p.range.start.line)
|
|
58659
|
+
);
|
|
58660
|
+
const passageStart = workspace.passages.getPassageAt(uri, position.line)?.range.start.line ?? 0;
|
|
57724
58661
|
const openStack = [];
|
|
57725
58662
|
for (const macro of macros) {
|
|
58663
|
+
if (macro.range.start.line < passageStart) continue;
|
|
57726
58664
|
if (macro.range.start.line > position.line || macro.range.start.line === position.line && macro.range.start.character >= position.character) {
|
|
57727
58665
|
break;
|
|
57728
58666
|
}
|
|
@@ -57773,7 +58711,7 @@ function getMacroCompletions(workspace) {
|
|
|
57773
58711
|
kind: 3,
|
|
57774
58712
|
// CompletionItemKind.Function
|
|
57775
58713
|
detail: `(widget) ${widget.name}`,
|
|
57776
|
-
documentation: widget.params.length > 0 ? `Parameters: ${widget.params.
|
|
58714
|
+
documentation: widget.params.length > 0 ? `Parameters: ${widget.params.join(", ")}` : void 0
|
|
57777
58715
|
});
|
|
57778
58716
|
}
|
|
57779
58717
|
return items;
|
|
@@ -58048,7 +58986,7 @@ function getWidgetHover(line, position, workspace) {
|
|
|
58048
58986
|
return null;
|
|
58049
58987
|
}
|
|
58050
58988
|
function buildWidgetHover(widget, line, nameStart, nameEnd) {
|
|
58051
|
-
const sig = widget.params.length > 0 ? widget.params.
|
|
58989
|
+
const sig = widget.params.length > 0 ? widget.params.join(", ") : "no parameters";
|
|
58052
58990
|
return {
|
|
58053
58991
|
contents: `**Widget** \`${widget.name}\`
|
|
58054
58992
|
|
|
@@ -58122,7 +59060,7 @@ function getSignatureHelp(uri, position, workspace) {
|
|
|
58122
59060
|
}
|
|
58123
59061
|
const widget = workspace.widgets.getWidget(macroName);
|
|
58124
59062
|
if (widget && widget.params.length > 0) {
|
|
58125
|
-
const paramLabels = widget.params
|
|
59063
|
+
const paramLabels = widget.params;
|
|
58126
59064
|
return {
|
|
58127
59065
|
signatures: [{
|
|
58128
59066
|
label: `{${macroName} ${paramLabels.join(", ")}}`,
|
|
@@ -58178,53 +59116,21 @@ function getDefinition(uri, position, workspace) {
|
|
|
58178
59116
|
const lines = text.split("\n");
|
|
58179
59117
|
if (position.line >= lines.length) return null;
|
|
58180
59118
|
const line = lines[position.line];
|
|
58181
|
-
const
|
|
58182
|
-
if (
|
|
58183
|
-
const macroPassageResult = getMacroPassageDefinition(line, position, workspace);
|
|
58184
|
-
if (macroPassageResult) return macroPassageResult;
|
|
59119
|
+
const passageResult = getPassageRefDefinition(text, position, workspace);
|
|
59120
|
+
if (passageResult) return passageResult;
|
|
58185
59121
|
const widgetResult = getWidgetDefinition(line, position, workspace);
|
|
58186
59122
|
if (widgetResult) return widgetResult;
|
|
58187
59123
|
return null;
|
|
58188
59124
|
}
|
|
58189
|
-
function
|
|
58190
|
-
const
|
|
58191
|
-
|
|
58192
|
-
|
|
58193
|
-
|
|
58194
|
-
|
|
58195
|
-
|
|
58196
|
-
|
|
58197
|
-
|
|
58198
|
-
};
|
|
58199
|
-
}
|
|
58200
|
-
}
|
|
58201
|
-
}
|
|
58202
|
-
return null;
|
|
58203
|
-
}
|
|
58204
|
-
function getMacroPassageDefinition(line, position, workspace) {
|
|
58205
|
-
const patterns = [
|
|
58206
|
-
/\{(?:goto|include)\s+"([^"]+)"\s*\}/gi,
|
|
58207
|
-
/\{(?:link|button)\s+"[^"]*"\s+"([^"]+)"\s*\}/gi
|
|
58208
|
-
];
|
|
58209
|
-
for (const pattern of patterns) {
|
|
58210
|
-
pattern.lastIndex = 0;
|
|
58211
|
-
let match2;
|
|
58212
|
-
while ((match2 = pattern.exec(line)) !== null) {
|
|
58213
|
-
const passageName = match2[1];
|
|
58214
|
-
const nameStart = match2.index + match2[0].indexOf(passageName);
|
|
58215
|
-
const nameEnd = nameStart + passageName.length;
|
|
58216
|
-
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
58217
|
-
const passage = workspace.passages.getPassage(passageName);
|
|
58218
|
-
if (passage) {
|
|
58219
|
-
return {
|
|
58220
|
-
uri: passage.uri,
|
|
58221
|
-
range: passage.headerEnd
|
|
58222
|
-
};
|
|
58223
|
-
}
|
|
58224
|
-
}
|
|
58225
|
-
}
|
|
58226
|
-
}
|
|
58227
|
-
return null;
|
|
59125
|
+
function getPassageRefDefinition(text, position, workspace) {
|
|
59126
|
+
const ref = findPassageRefAt(text, position);
|
|
59127
|
+
if (!ref) return null;
|
|
59128
|
+
const passage = workspace.passages.getPassage(ref.name);
|
|
59129
|
+
if (!passage) return null;
|
|
59130
|
+
return {
|
|
59131
|
+
uri: passage.uri,
|
|
59132
|
+
range: passage.headerEnd
|
|
59133
|
+
};
|
|
58228
59134
|
}
|
|
58229
59135
|
function getWidgetDefinition(line, position, workspace) {
|
|
58230
59136
|
const re = /\{([A-Za-z_$][\w$]*)/g;
|
|
@@ -58324,19 +59230,25 @@ function findReferences(uri, position, workspace, includeDeclaration) {
|
|
|
58324
59230
|
}
|
|
58325
59231
|
}
|
|
58326
59232
|
{
|
|
58327
|
-
const widgetRegex = /\{([A-Za-z_$][\w$]*)/g;
|
|
59233
|
+
const widgetRegex = /\{\/?([A-Za-z_$][\w$]*)/g;
|
|
58328
59234
|
let match2;
|
|
58329
59235
|
while ((match2 = widgetRegex.exec(line)) !== null) {
|
|
58330
59236
|
const name = match2[1];
|
|
58331
|
-
const nameStart = match2.index +
|
|
59237
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
58332
59238
|
const nameEnd = nameStart + name.length;
|
|
58333
59239
|
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
58334
|
-
|
|
59240
|
+
const widget = workspace.widgets.getWidget(name);
|
|
59241
|
+
const isClosing = match2[0][1] === "/";
|
|
59242
|
+
if (!workspace.macros.getMacro(name) && widget && (!isClosing || widget.block)) {
|
|
58335
59243
|
return findWidgetReferences(name, workspace, includeDeclaration);
|
|
58336
59244
|
}
|
|
58337
59245
|
}
|
|
58338
59246
|
}
|
|
58339
59247
|
}
|
|
59248
|
+
const passageRef = findPassageRefAt(text, position);
|
|
59249
|
+
if (passageRef) {
|
|
59250
|
+
return findPassageReferences(passageRef.name, workspace, includeDeclaration);
|
|
59251
|
+
}
|
|
58340
59252
|
{
|
|
58341
59253
|
const allPassages = workspace.passages.getAllPassages();
|
|
58342
59254
|
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
@@ -58362,39 +59274,16 @@ function findPassageReferences(passageName, workspace, includeDeclaration) {
|
|
|
58362
59274
|
if (passage) {
|
|
58363
59275
|
locations.push({
|
|
58364
59276
|
uri: passage.uri,
|
|
58365
|
-
range: passage.
|
|
59277
|
+
range: passage.nameRange
|
|
58366
59278
|
});
|
|
58367
59279
|
}
|
|
58368
59280
|
}
|
|
58369
|
-
const gotoRegex = /\{(?:goto|include)\s+"([^"]+)"\s*\}/gi;
|
|
58370
|
-
const linkMacroRegex = /\{(?:link|button)\s+"[^"]*"\s+"([^"]+)"\s*\}/gi;
|
|
58371
59281
|
for (const docUri of workspace.documents.getUris()) {
|
|
58372
59282
|
const docText = workspace.documents.getText(docUri);
|
|
58373
59283
|
if (!docText) continue;
|
|
58374
|
-
const
|
|
58375
|
-
|
|
58376
|
-
|
|
58377
|
-
locations.push({ uri: docUri, range: link.range });
|
|
58378
|
-
}
|
|
58379
|
-
}
|
|
58380
|
-
const lines = docText.split("\n");
|
|
58381
|
-
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
58382
|
-
const line = lines[lineNum];
|
|
58383
|
-
for (const regex of [gotoRegex, linkMacroRegex]) {
|
|
58384
|
-
regex.lastIndex = 0;
|
|
58385
|
-
let match2;
|
|
58386
|
-
while ((match2 = regex.exec(line)) !== null) {
|
|
58387
|
-
if (match2[1] === passageName) {
|
|
58388
|
-
const nameStart = match2.index + match2[0].indexOf(match2[1]);
|
|
58389
|
-
locations.push({
|
|
58390
|
-
uri: docUri,
|
|
58391
|
-
range: {
|
|
58392
|
-
start: { line: lineNum, character: nameStart },
|
|
58393
|
-
end: { line: lineNum, character: nameStart + match2[1].length }
|
|
58394
|
-
}
|
|
58395
|
-
});
|
|
58396
|
-
}
|
|
58397
|
-
}
|
|
59284
|
+
for (const ref of [...parseLinks(docText), ...parseMacroPassageRefs(docText)]) {
|
|
59285
|
+
if (ref.name === passageName) {
|
|
59286
|
+
locations.push({ uri: docUri, range: ref.range });
|
|
58398
59287
|
}
|
|
58399
59288
|
}
|
|
58400
59289
|
}
|
|
@@ -58433,30 +59322,30 @@ function findTransientReferences(varName, workspace, includeDeclaration) {
|
|
|
58433
59322
|
}
|
|
58434
59323
|
function findWidgetReferences(widgetName, workspace, includeDeclaration) {
|
|
58435
59324
|
const locations = [];
|
|
58436
|
-
|
|
58437
|
-
|
|
58438
|
-
|
|
58439
|
-
locations.push({ uri: widget.uri, range: widget.range });
|
|
58440
|
-
}
|
|
59325
|
+
const widget = workspace.widgets.getWidget(widgetName);
|
|
59326
|
+
if (includeDeclaration && widget) {
|
|
59327
|
+
locations.push({ uri: widget.uri, range: widget.range });
|
|
58441
59328
|
}
|
|
58442
|
-
const widgetInvocationRegex = /\{([A-Za-z_$][\w$]*)\b/g;
|
|
59329
|
+
const widgetInvocationRegex = /\{(\/)?([A-Za-z_$][\w$]*)\b/g;
|
|
59330
|
+
const lowerName = widgetName.toLowerCase();
|
|
59331
|
+
const isBlock = widget?.block ?? false;
|
|
58443
59332
|
for (const docUri of workspace.documents.getUris()) {
|
|
58444
59333
|
const docText = workspace.documents.getText(docUri);
|
|
58445
59334
|
if (!docText) continue;
|
|
58446
59335
|
const lines = docText.split("\n");
|
|
58447
59336
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
58448
59337
|
const line = lines[lineNum];
|
|
58449
|
-
if (/\{widget\s+"/i.test(line)) continue;
|
|
58450
59338
|
widgetInvocationRegex.lastIndex = 0;
|
|
58451
59339
|
let match2;
|
|
58452
59340
|
while ((match2 = widgetInvocationRegex.exec(line)) !== null) {
|
|
58453
|
-
if (match2[1]
|
|
58454
|
-
|
|
59341
|
+
if (match2[1] && !isBlock) continue;
|
|
59342
|
+
if (match2[2].toLowerCase() === lowerName) {
|
|
59343
|
+
const nameStart = match2.index + 1 + (match2[1] ? 1 : 0);
|
|
58455
59344
|
locations.push({
|
|
58456
59345
|
uri: docUri,
|
|
58457
59346
|
range: {
|
|
58458
59347
|
start: { line: lineNum, character: nameStart },
|
|
58459
|
-
end: { line: lineNum, character: nameStart +
|
|
59348
|
+
end: { line: lineNum, character: nameStart + match2[2].length }
|
|
58460
59349
|
}
|
|
58461
59350
|
});
|
|
58462
59351
|
}
|
|
@@ -58524,10 +59413,13 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
58524
59413
|
}
|
|
58525
59414
|
case "variable": {
|
|
58526
59415
|
const bareName = newName.startsWith("$") ? newName.slice(1) : newName.startsWith("%") ? newName.slice(1) : newName;
|
|
58527
|
-
const
|
|
58528
|
-
const refs = isTransient ? findTransientReferences(symbol2.name, workspace, true) : findVariableReferences(symbol2.name, workspace, true);
|
|
59416
|
+
const refs = symbol2.sigil === "%" ? findTransientReferences(symbol2.name, workspace, true) : findVariableReferences(symbol2.name, workspace, true);
|
|
58529
59417
|
for (const ref of refs) {
|
|
58530
|
-
|
|
59418
|
+
const start = ref.range.start.character + 1;
|
|
59419
|
+
addEdit(ref.uri, {
|
|
59420
|
+
start: { line: ref.range.start.line, character: start },
|
|
59421
|
+
end: { line: ref.range.start.line, character: start + symbol2.name.length }
|
|
59422
|
+
}, bareName);
|
|
58531
59423
|
}
|
|
58532
59424
|
break;
|
|
58533
59425
|
}
|
|
@@ -58538,23 +59430,7 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
58538
59430
|
}
|
|
58539
59431
|
const widget = workspace.widgets.getWidget(symbol2.name);
|
|
58540
59432
|
if (widget) {
|
|
58541
|
-
|
|
58542
|
-
if (docText) {
|
|
58543
|
-
const lines = docText.split("\n");
|
|
58544
|
-
const defLine = lines[widget.range.start.line];
|
|
58545
|
-
if (defLine) {
|
|
58546
|
-
const defSlice = defLine.slice(widget.range.start.character);
|
|
58547
|
-
const nameMatch = /\{widget\s+"([^"]+)"/.exec(defSlice);
|
|
58548
|
-
if (nameMatch) {
|
|
58549
|
-
const nameOffsetInSlice = defSlice.indexOf(nameMatch[1]);
|
|
58550
|
-
const nameStart = widget.range.start.character + nameOffsetInSlice;
|
|
58551
|
-
addEdit(widget.uri, {
|
|
58552
|
-
start: { line: widget.range.start.line, character: nameStart },
|
|
58553
|
-
end: { line: widget.range.start.line, character: nameStart + symbol2.name.length }
|
|
58554
|
-
}, newName);
|
|
58555
|
-
}
|
|
58556
|
-
}
|
|
58557
|
-
}
|
|
59433
|
+
addEdit(widget.uri, widget.nameRange, newName);
|
|
58558
59434
|
}
|
|
58559
59435
|
break;
|
|
58560
59436
|
}
|
|
@@ -58596,6 +59472,7 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
58596
59472
|
return {
|
|
58597
59473
|
kind: "variable",
|
|
58598
59474
|
name: match2[1],
|
|
59475
|
+
sigil: "$",
|
|
58599
59476
|
range: {
|
|
58600
59477
|
start: { line: position.line, character: start },
|
|
58601
59478
|
end: { line: position.line, character: end }
|
|
@@ -58614,6 +59491,7 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
58614
59491
|
return {
|
|
58615
59492
|
kind: "variable",
|
|
58616
59493
|
name: match2[1],
|
|
59494
|
+
sigil: "%",
|
|
58617
59495
|
range: {
|
|
58618
59496
|
start: { line: position.line, character: start },
|
|
58619
59497
|
end: { line: position.line, character: end }
|
|
@@ -58622,34 +59500,23 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
58622
59500
|
}
|
|
58623
59501
|
}
|
|
58624
59502
|
}
|
|
58625
|
-
{
|
|
58626
|
-
const
|
|
58627
|
-
|
|
58628
|
-
|
|
58629
|
-
const name = match2[1];
|
|
58630
|
-
const nameStart = match2.index + match2[0].indexOf(name);
|
|
58631
|
-
const nameEnd = nameStart + name.length;
|
|
58632
|
-
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
58633
|
-
return {
|
|
58634
|
-
kind: "widget",
|
|
58635
|
-
name,
|
|
58636
|
-
range: {
|
|
58637
|
-
start: { line: position.line, character: nameStart },
|
|
58638
|
-
end: { line: position.line, character: nameEnd }
|
|
58639
|
-
}
|
|
58640
|
-
};
|
|
58641
|
-
}
|
|
59503
|
+
for (const widget of workspace.widgets.getAllWidgets()) {
|
|
59504
|
+
const { start, end } = widget.nameRange;
|
|
59505
|
+
if (widget.uri === uri && position.line === start.line && position.character >= start.character && position.character <= end.character) {
|
|
59506
|
+
return { kind: "widget", name: widget.name, range: widget.nameRange };
|
|
58642
59507
|
}
|
|
58643
59508
|
}
|
|
58644
59509
|
{
|
|
58645
|
-
const re = /\{([A-Za-z_$][\w$]*)/g;
|
|
59510
|
+
const re = /\{\/?([A-Za-z_$][\w$]*)/g;
|
|
58646
59511
|
let match2;
|
|
58647
59512
|
while ((match2 = re.exec(line)) !== null) {
|
|
58648
59513
|
const name = match2[1];
|
|
58649
|
-
const nameStart = match2.index +
|
|
59514
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
58650
59515
|
const nameEnd = nameStart + name.length;
|
|
58651
59516
|
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
58652
|
-
|
|
59517
|
+
const widget = workspace.widgets.getWidget(name);
|
|
59518
|
+
const isClosing = match2[0][1] === "/";
|
|
59519
|
+
if (!workspace.macros.getMacro(name) && widget && (!isClosing || widget.block)) {
|
|
58653
59520
|
return {
|
|
58654
59521
|
kind: "widget",
|
|
58655
59522
|
name,
|
|
@@ -58743,7 +59610,7 @@ function computeCodeLenses(uri, workspace) {
|
|
|
58743
59610
|
});
|
|
58744
59611
|
continue;
|
|
58745
59612
|
}
|
|
58746
|
-
const widgetMatch = line.match(/\{widget\s+"([
|
|
59613
|
+
const widgetMatch = line.match(/\{widget\s+["']?([^\s"'}]+)/);
|
|
58747
59614
|
if (widgetMatch) {
|
|
58748
59615
|
const widgetName = widgetMatch[1];
|
|
58749
59616
|
const refs = findWidgetReferences(widgetName, workspace, true);
|
|
@@ -58823,34 +59690,24 @@ function computeInlayHints(uri, range2, workspace) {
|
|
|
58823
59690
|
return hints;
|
|
58824
59691
|
}
|
|
58825
59692
|
function addWidgetParamHints(text, range2, workspace, hints) {
|
|
58826
|
-
|
|
58827
|
-
if (allWidgets.length === 0) return;
|
|
58828
|
-
const widgetMap = new Map(allWidgets.map((w) => [w.name, w]));
|
|
59693
|
+
if (workspace.widgets.getAllWidgets().length === 0) return;
|
|
58829
59694
|
const macros = parseMacros(text);
|
|
59695
|
+
const lineStarts = buildLineStarts(text);
|
|
58830
59696
|
for (const macro of macros) {
|
|
58831
59697
|
if (!macro.open) continue;
|
|
58832
59698
|
if (macro.range.start.line < range2.start.line || macro.range.start.line > range2.end.line) continue;
|
|
58833
|
-
const widget =
|
|
59699
|
+
const widget = workspace.widgets.getWidget(macro.name);
|
|
58834
59700
|
if (!widget || widget.params.length === 0) continue;
|
|
58835
59701
|
if (!macro.rawArgs || macro.rawArgs.trim() === "") continue;
|
|
58836
59702
|
const args2 = lexArguments(macro.rawArgs);
|
|
58837
|
-
const
|
|
58838
|
-
const
|
|
58839
|
-
if (!macroLine) continue;
|
|
58840
|
-
const macroText = macroLine.substring(macro.range.start.character, macro.range.end.character);
|
|
58841
|
-
const nameEnd = macroText.indexOf(macro.name) + macro.name.length;
|
|
58842
|
-
const argsText = macroText.substring(nameEnd).replace(/^\s+/, "").replace(/\}$/, "");
|
|
58843
|
-
if (!argsText) continue;
|
|
59703
|
+
const macroEnd = lineStarts[macro.range.end.line] + macro.range.end.character;
|
|
59704
|
+
const argsStart = macroEnd - 1 - macro.rawArgs.length;
|
|
58844
59705
|
for (let i = 0; i < Math.min(args2.length, widget.params.length); i++) {
|
|
58845
|
-
|
|
58846
|
-
|
|
58847
|
-
|
|
58848
|
-
|
|
58849
|
-
|
|
58850
|
-
label: `@${widget.params[i]}:`,
|
|
58851
|
-
kind: "parameter"
|
|
58852
|
-
});
|
|
58853
|
-
}
|
|
59706
|
+
hints.push({
|
|
59707
|
+
position: offsetToPosition(argsStart + args2[i].start, lineStarts),
|
|
59708
|
+
label: `${widget.params[i]}:`,
|
|
59709
|
+
kind: "parameter"
|
|
59710
|
+
});
|
|
58854
59711
|
}
|
|
58855
59712
|
}
|
|
58856
59713
|
}
|
|
@@ -59133,12 +59990,15 @@ var init_semantic_tokens = __esm({
|
|
|
59133
59990
|
});
|
|
59134
59991
|
|
|
59135
59992
|
// src/plugins/code-actions.ts
|
|
59136
|
-
|
|
59993
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
59994
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
59995
|
+
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
59996
|
+
function computeCodeActions(uri, diagnostics, workspace, options = {}) {
|
|
59137
59997
|
const actions = [];
|
|
59138
59998
|
for (const diag of diagnostics) {
|
|
59139
59999
|
switch (diag.code) {
|
|
59140
60000
|
case DiagnosticCode.UndefinedMacro: {
|
|
59141
|
-
const action = fixUndefinedMacro(diag);
|
|
60001
|
+
const action = fixUndefinedMacro(uri, diag, workspace, options);
|
|
59142
60002
|
if (action) actions.push(action);
|
|
59143
60003
|
break;
|
|
59144
60004
|
}
|
|
@@ -59166,27 +60026,141 @@ function computeCodeActions(uri, diagnostics, workspace) {
|
|
|
59166
60026
|
}
|
|
59167
60027
|
return actions;
|
|
59168
60028
|
}
|
|
59169
|
-
function fixUndefinedMacro(diag) {
|
|
60029
|
+
function fixUndefinedMacro(uri, diag, workspace, options) {
|
|
59170
60030
|
const match2 = diag.message.match(/\{(\w[\w-]*)\}/);
|
|
59171
60031
|
if (!match2) return null;
|
|
59172
60032
|
const macroName = match2[1];
|
|
59173
|
-
const
|
|
59174
|
-
|
|
59175
|
-
|
|
59176
|
-
|
|
59177
|
-
|
|
59178
|
-
|
|
59179
|
-
title: `Add '${macroName}' to
|
|
60033
|
+
const root = resolveConfigRoot(uri, options.workspaceRoot);
|
|
60034
|
+
if (!root) return null;
|
|
60035
|
+
const existingPath = findConfigFile(root);
|
|
60036
|
+
const configPath = existingPath ?? join3(root, DEFAULT_CONFIG_FILENAME);
|
|
60037
|
+
const configUri = pathToFileURL4(configPath).toString();
|
|
60038
|
+
const action = {
|
|
60039
|
+
title: `Add '${macroName}' to ${basename(configPath)}`,
|
|
59180
60040
|
kind: "quickfix",
|
|
59181
|
-
diagnosticCodes: [DiagnosticCode.UndefinedMacro]
|
|
59182
|
-
|
|
59183
|
-
|
|
59184
|
-
|
|
59185
|
-
|
|
59186
|
-
|
|
59187
|
-
|
|
59188
|
-
|
|
59189
|
-
|
|
60041
|
+
diagnosticCodes: [DiagnosticCode.UndefinedMacro]
|
|
60042
|
+
};
|
|
60043
|
+
if (!existingPath) {
|
|
60044
|
+
const start = { line: 0, character: 0 };
|
|
60045
|
+
return {
|
|
60046
|
+
...action,
|
|
60047
|
+
createFile: configUri,
|
|
60048
|
+
edits: [{
|
|
60049
|
+
uri: configUri,
|
|
60050
|
+
range: { start, end: start },
|
|
60051
|
+
newText: (0, import_yaml2.stringify)({ macros: { [macroName]: NEW_MACRO_ENTRY } })
|
|
60052
|
+
}]
|
|
60053
|
+
};
|
|
60054
|
+
}
|
|
60055
|
+
let text = workspace.documents.getText(configUri);
|
|
60056
|
+
if (text === void 0) {
|
|
60057
|
+
try {
|
|
60058
|
+
text = readFileSync7(existingPath, "utf-8");
|
|
60059
|
+
} catch {
|
|
60060
|
+
return null;
|
|
60061
|
+
}
|
|
60062
|
+
}
|
|
60063
|
+
const edit = configPath.toLowerCase().endsWith(".json") ? addMacroToJsonConfig(text, macroName) : addMacroToYamlConfig(text, macroName);
|
|
60064
|
+
if (!edit) return null;
|
|
60065
|
+
return { ...action, edits: [{ uri: configUri, ...edit }] };
|
|
60066
|
+
}
|
|
60067
|
+
function resolveConfigRoot(uri, workspaceRoot) {
|
|
60068
|
+
if (workspaceRoot) {
|
|
60069
|
+
return workspaceRoot.startsWith("file:") ? fileURLToPath6(workspaceRoot) : workspaceRoot;
|
|
60070
|
+
}
|
|
60071
|
+
if (!uri.startsWith("file:")) return null;
|
|
60072
|
+
const docDir = dirname2(fileURLToPath6(uri));
|
|
60073
|
+
let search = docDir;
|
|
60074
|
+
for (let i = 0; i < 10; i++) {
|
|
60075
|
+
if (findConfigFile(search)) return search;
|
|
60076
|
+
const parent = dirname2(search);
|
|
60077
|
+
if (parent === search) break;
|
|
60078
|
+
search = parent;
|
|
60079
|
+
}
|
|
60080
|
+
return docDir;
|
|
60081
|
+
}
|
|
60082
|
+
function macrosPath(root) {
|
|
60083
|
+
const legacy = root?.["spindle-0"];
|
|
60084
|
+
return legacy !== null && typeof legacy === "object" ? ["spindle-0", "macros"] : ["macros"];
|
|
60085
|
+
}
|
|
60086
|
+
function definesMacro(macros, macroName) {
|
|
60087
|
+
if (macros === null || typeof macros !== "object") return false;
|
|
60088
|
+
const lower = macroName.toLowerCase();
|
|
60089
|
+
return Object.keys(macros).some((k) => k.toLowerCase() === lower);
|
|
60090
|
+
}
|
|
60091
|
+
function addMacroToYamlConfig(text, macroName) {
|
|
60092
|
+
const doc = (0, import_yaml2.parseDocument)(text);
|
|
60093
|
+
if (doc.errors.length > 0) return null;
|
|
60094
|
+
if (doc.contents !== null && !(0, import_yaml2.isMap)(doc.contents)) return null;
|
|
60095
|
+
const path2 = macrosPath(doc.toJS());
|
|
60096
|
+
const parent = path2.length > 1 ? doc.getIn(path2.slice(0, -1), true) : doc.contents;
|
|
60097
|
+
const macros = doc.getIn(path2, true);
|
|
60098
|
+
if ((0, import_yaml2.isMap)(macros) && definesMacro(macros.toJS(doc), macroName)) return null;
|
|
60099
|
+
if ((0, import_yaml2.isMap)(parent) && (0, import_yaml2.isMap)(macros) && !macros.flow && macros.items.length > 0 && macros.range) {
|
|
60100
|
+
const macrosKey = parent.items.find((p) => (0, import_yaml2.isScalar)(p.key) && p.key.value === "macros")?.key;
|
|
60101
|
+
const firstKey = macros.items[0].key;
|
|
60102
|
+
if ((0, import_yaml2.isScalar)(macrosKey) && macrosKey.range && (0, import_yaml2.isScalar)(firstKey) && firstKey.range) {
|
|
60103
|
+
const indent = columnOf(text, firstKey.range[0]);
|
|
60104
|
+
const step = Math.max(indent - columnOf(text, macrosKey.range[0]), 1);
|
|
60105
|
+
const entry = (0, import_yaml2.stringify)({ [macroName]: NEW_MACRO_ENTRY }, { indent: step }).replace(/^(?=.)/gm, " ".repeat(indent));
|
|
60106
|
+
let offset = macros.range[1];
|
|
60107
|
+
if (offset > 0 && text[offset - 1] !== "\n") {
|
|
60108
|
+
const nl = text.indexOf("\n", offset);
|
|
60109
|
+
offset = nl === -1 ? text.length : nl + 1;
|
|
60110
|
+
}
|
|
60111
|
+
const lead = offset === text.length && text.length > 0 && !text.endsWith("\n") ? "\n" : "";
|
|
60112
|
+
const pos = offsetToPosition2(text, offset);
|
|
60113
|
+
return { range: { start: pos, end: pos }, newText: lead + entry };
|
|
60114
|
+
}
|
|
60115
|
+
}
|
|
60116
|
+
if ((0, import_yaml2.isMap)(macros)) {
|
|
60117
|
+
macros.set(macroName, doc.createNode(NEW_MACRO_ENTRY));
|
|
60118
|
+
} else {
|
|
60119
|
+
doc.setIn(path2, doc.createNode({ [macroName]: NEW_MACRO_ENTRY }));
|
|
60120
|
+
}
|
|
60121
|
+
return minimalEdit(text, doc.toString({ lineWidth: 0 }));
|
|
60122
|
+
}
|
|
60123
|
+
function addMacroToJsonConfig(text, macroName) {
|
|
60124
|
+
let raw;
|
|
60125
|
+
try {
|
|
60126
|
+
raw = text.trim() ? JSON.parse(text) : {};
|
|
60127
|
+
} catch {
|
|
60128
|
+
return null;
|
|
60129
|
+
}
|
|
60130
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
60131
|
+
const path2 = macrosPath(raw);
|
|
60132
|
+
const container = path2.length > 1 ? raw[path2[0]] : raw;
|
|
60133
|
+
const macros = container.macros;
|
|
60134
|
+
if (definesMacro(macros, macroName)) return null;
|
|
60135
|
+
if (macros !== null && typeof macros === "object" && !Array.isArray(macros)) {
|
|
60136
|
+
macros[macroName] = NEW_MACRO_ENTRY;
|
|
60137
|
+
} else {
|
|
60138
|
+
container.macros = { [macroName]: NEW_MACRO_ENTRY };
|
|
60139
|
+
}
|
|
60140
|
+
const indent = text.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
|
|
60141
|
+
const newline = text.endsWith("\n") || !text.trim() ? "\n" : "";
|
|
60142
|
+
return minimalEdit(text, JSON.stringify(raw, null, indent) + newline);
|
|
60143
|
+
}
|
|
60144
|
+
function columnOf(text, offset) {
|
|
60145
|
+
return offset - (text.lastIndexOf("\n", offset - 1) + 1);
|
|
60146
|
+
}
|
|
60147
|
+
function offsetToPosition2(text, offset) {
|
|
60148
|
+
const before = text.slice(0, offset);
|
|
60149
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
60150
|
+
return { line: before.split("\n").length - 1, character: offset - lineStart };
|
|
60151
|
+
}
|
|
60152
|
+
function minimalEdit(oldText, newText) {
|
|
60153
|
+
const max = Math.min(oldText.length, newText.length);
|
|
60154
|
+
let prefix = 0;
|
|
60155
|
+
while (prefix < max && oldText[prefix] === newText[prefix]) prefix++;
|
|
60156
|
+
let suffix = 0;
|
|
60157
|
+
while (suffix < max - prefix && oldText[oldText.length - 1 - suffix] === newText[newText.length - 1 - suffix]) suffix++;
|
|
60158
|
+
return {
|
|
60159
|
+
range: {
|
|
60160
|
+
start: offsetToPosition2(oldText, prefix),
|
|
60161
|
+
end: offsetToPosition2(oldText, oldText.length - suffix)
|
|
60162
|
+
},
|
|
60163
|
+
newText: newText.slice(prefix, newText.length - suffix)
|
|
59190
60164
|
};
|
|
59191
60165
|
}
|
|
59192
60166
|
function fixUndeclaredVariable(diag, workspace) {
|
|
@@ -59296,11 +60270,15 @@ function toLspRange6(r) {
|
|
|
59296
60270
|
end: { line: r.end.line, character: r.end.character }
|
|
59297
60271
|
};
|
|
59298
60272
|
}
|
|
59299
|
-
var codeActionsPlugin;
|
|
60273
|
+
var import_yaml2, DEFAULT_CONFIG_FILENAME, NEW_MACRO_ENTRY, codeActionsPlugin;
|
|
59300
60274
|
var init_code_actions = __esm({
|
|
59301
60275
|
"src/plugins/code-actions.ts"() {
|
|
59302
60276
|
"use strict";
|
|
60277
|
+
import_yaml2 = __toESM(require_dist(), 1);
|
|
59303
60278
|
init_diagnostic_codes();
|
|
60279
|
+
init_config_loader();
|
|
60280
|
+
DEFAULT_CONFIG_FILENAME = "spindle.config.yaml";
|
|
60281
|
+
NEW_MACRO_ENTRY = { description: "" };
|
|
59304
60282
|
codeActionsPlugin = {
|
|
59305
60283
|
id: "code-actions",
|
|
59306
60284
|
capabilities: {
|
|
@@ -59323,7 +60301,8 @@ var init_code_actions = __esm({
|
|
|
59323
60301
|
const actions = computeCodeActions(
|
|
59324
60302
|
params.textDocument.uri,
|
|
59325
60303
|
diagnostics,
|
|
59326
|
-
ctx.workspace
|
|
60304
|
+
ctx.workspace,
|
|
60305
|
+
{ workspaceRoot: ctx.config.workspaceRoot }
|
|
59327
60306
|
);
|
|
59328
60307
|
return actions.map((a) => ({
|
|
59329
60308
|
title: a.title,
|
|
@@ -59331,7 +60310,16 @@ var init_code_actions = __esm({
|
|
|
59331
60310
|
diagnostics: params.context.diagnostics.filter(
|
|
59332
60311
|
(d) => a.diagnosticCodes.includes(String(d.code))
|
|
59333
60312
|
),
|
|
59334
|
-
edit: {
|
|
60313
|
+
edit: a.createFile ? {
|
|
60314
|
+
// Resource operations are only expressible via documentChanges
|
|
60315
|
+
documentChanges: [
|
|
60316
|
+
{ kind: "create", uri: a.createFile, options: { ignoreIfExists: true } },
|
|
60317
|
+
...a.edits.map((e) => ({
|
|
60318
|
+
textDocument: { uri: e.uri, version: null },
|
|
60319
|
+
edits: [{ range: toLspRange6(e.range), newText: e.newText }]
|
|
60320
|
+
}))
|
|
60321
|
+
]
|
|
60322
|
+
} : {
|
|
59335
60323
|
changes: Object.fromEntries(
|
|
59336
60324
|
a.edits.map((e) => [
|
|
59337
60325
|
e.uri,
|
|
@@ -59371,7 +60359,7 @@ function computeDocumentSymbols(uri, workspace) {
|
|
|
59371
60359
|
if (passage.tags?.includes("widget")) {
|
|
59372
60360
|
const contentStart = passage.range.start.line + 1;
|
|
59373
60361
|
const contentEnd = passage.range.end.line + 1;
|
|
59374
|
-
const widgetDefRegex2 = /\{widget\s+"([
|
|
60362
|
+
const widgetDefRegex2 = /\{widget\s+["']?([^\s"'}]+)["']?[^}]*\}/gi;
|
|
59375
60363
|
for (let i = contentStart; i < contentEnd && i < lines.length; i++) {
|
|
59376
60364
|
widgetDefRegex2.lastIndex = 0;
|
|
59377
60365
|
let match2;
|
|
@@ -59476,14 +60464,19 @@ function searchWorkspaceSymbols(query, workspace) {
|
|
|
59476
60464
|
range: widget.range
|
|
59477
60465
|
});
|
|
59478
60466
|
}
|
|
59479
|
-
|
|
59480
|
-
|
|
60467
|
+
const declarations = [
|
|
60468
|
+
...workspace.variables.getDeclared().values(),
|
|
60469
|
+
...workspace.variables.getDeclaredTransient().values()
|
|
60470
|
+
];
|
|
60471
|
+
for (const decl of declarations) {
|
|
60472
|
+
const displayName = decl.sigil + decl.name;
|
|
59481
60473
|
if (lowerQuery && !displayName.toLowerCase().includes(lowerQuery)) continue;
|
|
60474
|
+
if (!decl.declarationUri || !decl.declarationRange) continue;
|
|
59482
60475
|
results.push({
|
|
59483
60476
|
name: displayName,
|
|
59484
60477
|
kind: import_vscode_languageserver3.SymbolKind.Variable,
|
|
59485
|
-
uri: decl.declarationUri
|
|
59486
|
-
range: decl.declarationRange
|
|
60478
|
+
uri: decl.declarationUri,
|
|
60479
|
+
range: decl.declarationRange
|
|
59487
60480
|
});
|
|
59488
60481
|
}
|
|
59489
60482
|
return results;
|
|
@@ -59539,7 +60532,11 @@ function computeFoldingRanges(uri, workspace) {
|
|
|
59539
60532
|
}
|
|
59540
60533
|
}
|
|
59541
60534
|
const macros = parseMacros(text);
|
|
59542
|
-
pairMacros(
|
|
60535
|
+
pairMacros(
|
|
60536
|
+
macros,
|
|
60537
|
+
(name) => workspace.macros.isBlock(name),
|
|
60538
|
+
passages.map((p) => p.range.start.line)
|
|
60539
|
+
);
|
|
59543
60540
|
for (const macro of macros) {
|
|
59544
60541
|
if (macro.open && macro.pair !== -1) {
|
|
59545
60542
|
const closingMacro = macros[macro.pair];
|
|
@@ -59686,17 +60683,22 @@ var server_exports2 = {};
|
|
|
59686
60683
|
__export(server_exports2, {
|
|
59687
60684
|
startServer: () => startServer
|
|
59688
60685
|
});
|
|
59689
|
-
import { fileURLToPath as
|
|
59690
|
-
import { readFileSync as
|
|
60686
|
+
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
60687
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
60688
|
+
import { basename as basename2 } from "node:path";
|
|
59691
60689
|
function uriToFsPath(uri) {
|
|
59692
60690
|
try {
|
|
59693
|
-
return
|
|
60691
|
+
return fileURLToPath7(uri);
|
|
59694
60692
|
} catch {
|
|
59695
60693
|
return uri;
|
|
59696
60694
|
}
|
|
59697
60695
|
}
|
|
59698
60696
|
function fsPathToUri(fsPath) {
|
|
59699
|
-
return
|
|
60697
|
+
return pathToFileURL5(fsPath).toString();
|
|
60698
|
+
}
|
|
60699
|
+
function isConfigFileUri(uri) {
|
|
60700
|
+
const name = basename2(uriToFsPath(uri));
|
|
60701
|
+
return name.startsWith("spindle.config.") || name.includes("twee-config.");
|
|
59700
60702
|
}
|
|
59701
60703
|
function startServer(_args) {
|
|
59702
60704
|
const connection = (0, import_node.createConnection)(import_node.ProposedFeatures.all);
|
|
@@ -59722,7 +60724,7 @@ function startServer(_args) {
|
|
|
59722
60724
|
plugin.initialize({ connection, workspace, config: config2 });
|
|
59723
60725
|
}
|
|
59724
60726
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
59725
|
-
workspace.macros.
|
|
60727
|
+
workspace.macros.loadConfig(projectConfig.macros);
|
|
59726
60728
|
console.error("[spindle-lsp] loaded config macros:", Object.keys(projectConfig.macros).length);
|
|
59727
60729
|
}
|
|
59728
60730
|
return {
|
|
@@ -59746,6 +60748,9 @@ function startServer(_args) {
|
|
|
59746
60748
|
try {
|
|
59747
60749
|
const fileContents = await scanWorkspaceFiles(workspaceRoot);
|
|
59748
60750
|
console.error("[spindle-lsp] scanned", fileContents.size, "files from root:", workspaceRoot ?? "undefined");
|
|
60751
|
+
for (const uri of documents.keys()) {
|
|
60752
|
+
fileContents.delete(uri);
|
|
60753
|
+
}
|
|
59749
60754
|
workspace.initialize(fileContents);
|
|
59750
60755
|
console.error("[spindle-lsp] workspace initialized, macros:", workspace.macros.getAllMacros().length);
|
|
59751
60756
|
} catch (err) {
|
|
@@ -59785,23 +60790,40 @@ function startServer(_args) {
|
|
|
59785
60790
|
if (workspace) {
|
|
59786
60791
|
try {
|
|
59787
60792
|
const fsPath = uriToFsPath(textDocument.uri);
|
|
59788
|
-
const text =
|
|
60793
|
+
const text = readFileSync8(fsPath, "utf-8");
|
|
59789
60794
|
workspace.documents.update(textDocument.uri, text);
|
|
59790
60795
|
} catch {
|
|
59791
60796
|
workspace.documents.close(textDocument.uri);
|
|
59792
60797
|
}
|
|
59793
60798
|
}
|
|
59794
60799
|
});
|
|
60800
|
+
function reloadProjectConfig() {
|
|
60801
|
+
if (!workspaceRoot) return;
|
|
60802
|
+
try {
|
|
60803
|
+
const projectConfig = loadConfigFromDisk(workspaceRoot);
|
|
60804
|
+
workspace.macros.loadConfig(projectConfig.macros);
|
|
60805
|
+
console.error("[spindle-lsp] reloaded config macros:", Object.keys(projectConfig.macros).length);
|
|
60806
|
+
} catch (err) {
|
|
60807
|
+
console.error("[spindle-lsp] config reload failed:", err);
|
|
60808
|
+
return;
|
|
60809
|
+
}
|
|
60810
|
+
workspace.refresh();
|
|
60811
|
+
}
|
|
59795
60812
|
connection.onDidChangeWatchedFiles(({ changes }) => {
|
|
59796
60813
|
if (!workspace) return;
|
|
60814
|
+
let configChanged = false;
|
|
59797
60815
|
for (const change of changes) {
|
|
60816
|
+
if (isConfigFileUri(change.uri)) {
|
|
60817
|
+
configChanged = true;
|
|
60818
|
+
continue;
|
|
60819
|
+
}
|
|
60820
|
+
if (documents.has(change.uri)) continue;
|
|
59798
60821
|
if (change.type === import_node.FileChangeType.Deleted) {
|
|
59799
60822
|
workspace.documents.close(change.uri);
|
|
59800
|
-
documents.delete(change.uri);
|
|
59801
60823
|
} else {
|
|
59802
60824
|
try {
|
|
59803
60825
|
const fsPath = uriToFsPath(change.uri);
|
|
59804
|
-
const text =
|
|
60826
|
+
const text = readFileSync8(fsPath, "utf-8");
|
|
59805
60827
|
if (workspace.documents.has(change.uri)) {
|
|
59806
60828
|
workspace.documents.update(change.uri, text);
|
|
59807
60829
|
} else {
|
|
@@ -59811,6 +60833,9 @@ function startServer(_args) {
|
|
|
59811
60833
|
}
|
|
59812
60834
|
}
|
|
59813
60835
|
}
|
|
60836
|
+
if (configChanged) {
|
|
60837
|
+
reloadProjectConfig();
|
|
60838
|
+
}
|
|
59814
60839
|
});
|
|
59815
60840
|
connection.onShutdown(() => {
|
|
59816
60841
|
for (const plugin of activePlugins) {
|
|
@@ -59831,7 +60856,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
59831
60856
|
});
|
|
59832
60857
|
for (const filePath of files) {
|
|
59833
60858
|
try {
|
|
59834
|
-
const text =
|
|
60859
|
+
const text = readFileSync8(filePath, "utf-8");
|
|
59835
60860
|
const uri = fsPathToUri(filePath);
|
|
59836
60861
|
contents.set(uri, text);
|
|
59837
60862
|
} catch {
|
|
@@ -59858,7 +60883,7 @@ var init_server4 = __esm({
|
|
|
59858
60883
|
|
|
59859
60884
|
// src/bin.ts
|
|
59860
60885
|
var args = process.argv.slice(2);
|
|
59861
|
-
var VERSION = "0.
|
|
60886
|
+
var VERSION = "0.9.1";
|
|
59862
60887
|
var HELP = `spindle-lsp v${VERSION}
|
|
59863
60888
|
Language Server Protocol server for Spindle story format
|
|
59864
60889
|
|