@rohal12/spindle-lsp 0.9.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +1829 -406
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6694,7 +6694,7 @@ var init_macro_registry = __esm({
|
|
|
6694
6694
|
}
|
|
6695
6695
|
});
|
|
6696
6696
|
|
|
6697
|
-
// src/core/parsing/
|
|
6697
|
+
// src/core/parsing/code-scanner.ts
|
|
6698
6698
|
function buildCodeScanner(input, from) {
|
|
6699
6699
|
const n = input.length;
|
|
6700
6700
|
const close = new Int32Array(n + 2).fill(-1);
|
|
@@ -6745,6 +6745,293 @@ function buildCodeScanner(input, from) {
|
|
|
6745
6745
|
function createCodeScanner(input) {
|
|
6746
6746
|
return buildCodeScanner(input, 0);
|
|
6747
6747
|
}
|
|
6748
|
+
var NON_STRING_QUOTE_PREFIX;
|
|
6749
|
+
var init_code_scanner = __esm({
|
|
6750
|
+
"src/core/parsing/code-scanner.ts"() {
|
|
6751
|
+
"use strict";
|
|
6752
|
+
NON_STRING_QUOTE_PREFIX = /[\p{L}\p{N}_\\]/u;
|
|
6753
|
+
}
|
|
6754
|
+
});
|
|
6755
|
+
|
|
6756
|
+
// src/core/parsing/html-scanner.ts
|
|
6757
|
+
function plainBraceMatches(input) {
|
|
6758
|
+
const match2 = new Int32Array(input.length).fill(-1);
|
|
6759
|
+
const open = [];
|
|
6760
|
+
for (let i = 0; i < input.length; i++) {
|
|
6761
|
+
if (input[i] === "{") open.push(i);
|
|
6762
|
+
else if (input[i] === "}" && open.length > 0) match2[open.pop()] = i;
|
|
6763
|
+
}
|
|
6764
|
+
return match2;
|
|
6765
|
+
}
|
|
6766
|
+
function scanHtmlTags(text) {
|
|
6767
|
+
const n = text.length;
|
|
6768
|
+
const tags = [];
|
|
6769
|
+
const macros = [];
|
|
6770
|
+
const code = createCodeScanner(text);
|
|
6771
|
+
const plain = plainBraceMatches(text);
|
|
6772
|
+
const rawBodies = [];
|
|
6773
|
+
let doCloses;
|
|
6774
|
+
let nextDoClose = 0;
|
|
6775
|
+
const rawBody = (end) => {
|
|
6776
|
+
doCloses ??= [...text.matchAll(/\{\/do\s*\}/gi)].map((match2) => match2.index);
|
|
6777
|
+
while (nextDoClose < doCloses.length && doCloses[nextDoClose] < end) nextDoClose++;
|
|
6778
|
+
if (nextDoClose < doCloses.length) rawBodies.push([end, doCloses[nextDoClose]]);
|
|
6779
|
+
};
|
|
6780
|
+
const rawBodyLimit = (limit2) => {
|
|
6781
|
+
if (rawBodies.length === 0) return limit2;
|
|
6782
|
+
const macroStarts = new Set(macros);
|
|
6783
|
+
let t = 0;
|
|
6784
|
+
for (const [start, close] of rawBodies) {
|
|
6785
|
+
if (start >= limit2) break;
|
|
6786
|
+
if (close < limit2 && !macroStarts.has(close)) {
|
|
6787
|
+
limit2 = start;
|
|
6788
|
+
break;
|
|
6789
|
+
}
|
|
6790
|
+
while (t < tags.length && tags[t].start < start) t++;
|
|
6791
|
+
if (t < tags.length && tags[t].start < Math.min(close, limit2)) limit2 = tags[t].start;
|
|
6792
|
+
}
|
|
6793
|
+
return limit2;
|
|
6794
|
+
};
|
|
6795
|
+
const braceEnd = (open) => {
|
|
6796
|
+
const close = code.closeBrace(open + 1);
|
|
6797
|
+
if (close !== plain[open]) return GIVE_UP;
|
|
6798
|
+
return close === -1 ? open + 1 : close + 1;
|
|
6799
|
+
};
|
|
6800
|
+
const displayEnd = (open, sigil) => {
|
|
6801
|
+
let j = sigil + 1;
|
|
6802
|
+
while (j < n && DISPLAY_NAME_CHAR.test(text[j])) j++;
|
|
6803
|
+
return text[j] === "}" ? j + 1 : braceEnd(open);
|
|
6804
|
+
};
|
|
6805
|
+
const afterBrace = (i2) => {
|
|
6806
|
+
let at = i2 + 1;
|
|
6807
|
+
const selectors = text[at] === "." || text[at] === "#";
|
|
6808
|
+
if (selectors) {
|
|
6809
|
+
at = selectorsEnd(text, at);
|
|
6810
|
+
if (text[at] === " ") at++;
|
|
6811
|
+
}
|
|
6812
|
+
const c3 = text[at];
|
|
6813
|
+
if (c3 === "$" || c3 === "_" || c3 === "@" || c3 === "%") return displayEnd(i2, at);
|
|
6814
|
+
if (c3 === void 0) return i2 + 1;
|
|
6815
|
+
if (LETTER.test(c3) || c3 === "/" && !selectors) {
|
|
6816
|
+
const end = braceEnd(i2);
|
|
6817
|
+
if (end > i2 + 1) {
|
|
6818
|
+
macros.push(i2);
|
|
6819
|
+
if (/^do$/i.test(text.slice(at, end - 1).trim().split(/\s/)[0])) rawBody(end);
|
|
6820
|
+
}
|
|
6821
|
+
return end;
|
|
6822
|
+
}
|
|
6823
|
+
return i2 + 1;
|
|
6824
|
+
};
|
|
6825
|
+
const readTag = (i2) => {
|
|
6826
|
+
let j = i2 + 1;
|
|
6827
|
+
const isClose = text[j] === "/";
|
|
6828
|
+
if (isClose) j++;
|
|
6829
|
+
const nameStart = j;
|
|
6830
|
+
while (j < n && TAG_NAME_CHAR.test(text[j])) j++;
|
|
6831
|
+
const name = text.slice(nameStart, j);
|
|
6832
|
+
if (!name || !LETTER.test(name[0])) return i2 + 1;
|
|
6833
|
+
const isVoid = VOID_ELEMENTS.has(name.toLowerCase());
|
|
6834
|
+
if (isClose) {
|
|
6835
|
+
while (j < n && WHITESPACE.test(text[j])) j++;
|
|
6836
|
+
if (text[j] !== ">") return i2 + 1;
|
|
6837
|
+
if (!isVoid) tags.push({ name, kind: "close", start: i2, end: j + 1 });
|
|
6838
|
+
return j + 1;
|
|
6839
|
+
}
|
|
6840
|
+
const attrs = readAttributes(j);
|
|
6841
|
+
if (attrs.end === GIVE_UP) return GIVE_UP;
|
|
6842
|
+
j = attrs.end;
|
|
6843
|
+
let selfClosing = isVoid;
|
|
6844
|
+
if (text[j] === "/") {
|
|
6845
|
+
selfClosing = true;
|
|
6846
|
+
j++;
|
|
6847
|
+
}
|
|
6848
|
+
if (text[j] === ">") {
|
|
6849
|
+
tags.push({ name, kind: selfClosing ? "void" : "open", start: i2, end: j + 1, values: attrs.values });
|
|
6850
|
+
return j + 1;
|
|
6851
|
+
}
|
|
6852
|
+
return attrs.count > 0 ? GIVE_UP : i2 + 1;
|
|
6853
|
+
};
|
|
6854
|
+
const readAttributes = (j) => {
|
|
6855
|
+
let count = 0;
|
|
6856
|
+
const values = [];
|
|
6857
|
+
while (j < n) {
|
|
6858
|
+
while (j < n && WHITESPACE.test(text[j])) j++;
|
|
6859
|
+
if (j >= n || text[j] === ">" || text[j] === "/" && text[j + 1] === ">") break;
|
|
6860
|
+
const nameStart = j;
|
|
6861
|
+
while (j < n && ATTR_NAME_CHAR.test(text[j])) j++;
|
|
6862
|
+
if (j === nameStart) break;
|
|
6863
|
+
count++;
|
|
6864
|
+
if (text[j] !== "=") continue;
|
|
6865
|
+
j++;
|
|
6866
|
+
const quote = text[j];
|
|
6867
|
+
if (quote === '"' || quote === "'") {
|
|
6868
|
+
const close = quotedValueEnd(j + 1, quote);
|
|
6869
|
+
if (close === GIVE_UP) return { end: GIVE_UP, count, values };
|
|
6870
|
+
values.push([j + 1, close]);
|
|
6871
|
+
j = close < n ? close + 1 : close;
|
|
6872
|
+
} else {
|
|
6873
|
+
const start = j;
|
|
6874
|
+
while (j < n && !WHITESPACE.test(text[j]) && text[j] !== ">") j++;
|
|
6875
|
+
values.push([start, j]);
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
return { end: j, count, values };
|
|
6879
|
+
};
|
|
6880
|
+
const quotedValueEnd = (from, quote) => {
|
|
6881
|
+
let depth = 0;
|
|
6882
|
+
let sawBrace = false;
|
|
6883
|
+
let k = from;
|
|
6884
|
+
for (; k < n; k++) {
|
|
6885
|
+
const c3 = text[k];
|
|
6886
|
+
if (c3 === "{") {
|
|
6887
|
+
depth++;
|
|
6888
|
+
sawBrace = true;
|
|
6889
|
+
} else if (c3 === "}") {
|
|
6890
|
+
depth--;
|
|
6891
|
+
sawBrace = true;
|
|
6892
|
+
} else if (c3 === quote && depth <= 0) {
|
|
6893
|
+
break;
|
|
6894
|
+
}
|
|
6895
|
+
}
|
|
6896
|
+
if (!sawBrace) return k;
|
|
6897
|
+
let d = from;
|
|
6898
|
+
while (d < n) {
|
|
6899
|
+
if (text[d] === "{") {
|
|
6900
|
+
const close = code.closeBrace(d + 1);
|
|
6901
|
+
if (close !== -1) {
|
|
6902
|
+
d = close + 1;
|
|
6903
|
+
continue;
|
|
6904
|
+
}
|
|
6905
|
+
} else if (text[d] === quote) {
|
|
6906
|
+
break;
|
|
6907
|
+
}
|
|
6908
|
+
d++;
|
|
6909
|
+
}
|
|
6910
|
+
return d === k ? k : GIVE_UP;
|
|
6911
|
+
};
|
|
6912
|
+
let i = 0;
|
|
6913
|
+
let stoppedAt = -1;
|
|
6914
|
+
while (i < n) {
|
|
6915
|
+
const c3 = text[i];
|
|
6916
|
+
let next;
|
|
6917
|
+
if (c3 === "\\") {
|
|
6918
|
+
let k = i + 1;
|
|
6919
|
+
while (text[k] === "\\") k++;
|
|
6920
|
+
if (text[k] === "{" || text[k] === "}") next = (k - i) % 2 === 1 ? k + 1 : GIVE_UP;
|
|
6921
|
+
else next = k;
|
|
6922
|
+
} else if (c3 === "[" && text[i + 1] === "[") {
|
|
6923
|
+
next = linkEnd(text, i);
|
|
6924
|
+
} else if (c3 === "{") {
|
|
6925
|
+
next = afterBrace(i);
|
|
6926
|
+
} else if (c3 === "<") {
|
|
6927
|
+
next = readTag(i);
|
|
6928
|
+
} else {
|
|
6929
|
+
next = i + 1;
|
|
6930
|
+
}
|
|
6931
|
+
if (next === GIVE_UP) {
|
|
6932
|
+
stoppedAt = i;
|
|
6933
|
+
break;
|
|
6934
|
+
}
|
|
6935
|
+
i = next;
|
|
6936
|
+
}
|
|
6937
|
+
const limit = rawBodyLimit(stoppedAt === -1 ? n : stoppedAt);
|
|
6938
|
+
if (limit === stoppedAt || limit === n) return { tags, stoppedAt, macros };
|
|
6939
|
+
return {
|
|
6940
|
+
tags: tags.filter((tag) => tag.start < limit),
|
|
6941
|
+
stoppedAt: limit,
|
|
6942
|
+
macros: macros.filter((at) => at < limit)
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6945
|
+
function selectorsEnd(text, i) {
|
|
6946
|
+
const n = text.length;
|
|
6947
|
+
while (i < n && (text[i] === "." || text[i] === "#")) {
|
|
6948
|
+
i++;
|
|
6949
|
+
while (i < n) {
|
|
6950
|
+
if (SELECTOR_NAME_CHAR.test(text[i])) {
|
|
6951
|
+
i++;
|
|
6952
|
+
} else if (text[i] === "{" && (text[i + 1] === "$" || text[i + 1] === "_" || text[i + 1] === "@")) {
|
|
6953
|
+
let j = i + 2;
|
|
6954
|
+
while (j < n && DISPLAY_NAME_CHAR.test(text[j])) j++;
|
|
6955
|
+
if (text[j] !== "}") break;
|
|
6956
|
+
i = j + 1;
|
|
6957
|
+
} else {
|
|
6958
|
+
break;
|
|
6959
|
+
}
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
return i;
|
|
6963
|
+
}
|
|
6964
|
+
function linkEnd(text, i) {
|
|
6965
|
+
const n = text.length;
|
|
6966
|
+
let depth = 1;
|
|
6967
|
+
let j = i + 2;
|
|
6968
|
+
while (j < n) {
|
|
6969
|
+
if (text[j] === "[" && text[j + 1] === "[") {
|
|
6970
|
+
depth++;
|
|
6971
|
+
j += 2;
|
|
6972
|
+
} else if (text[j] === "]" && text[j + 1] === "]") {
|
|
6973
|
+
if (--depth === 0) return j + 2;
|
|
6974
|
+
j += 2;
|
|
6975
|
+
} else {
|
|
6976
|
+
j++;
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
return GIVE_UP;
|
|
6980
|
+
}
|
|
6981
|
+
function attributeValueSpans(text) {
|
|
6982
|
+
const spans = [];
|
|
6983
|
+
if (!text.includes("<")) return spans;
|
|
6984
|
+
const headers = [...text.matchAll(/^::.*$/gm)];
|
|
6985
|
+
const scan = (from2, to) => {
|
|
6986
|
+
const content = text.slice(from2, to);
|
|
6987
|
+
if (!content.includes("<")) return;
|
|
6988
|
+
for (const tag of scanHtmlTags(content).tags) {
|
|
6989
|
+
for (const [start, end] of tag.values ?? []) spans.push([from2 + start, from2 + end]);
|
|
6990
|
+
}
|
|
6991
|
+
};
|
|
6992
|
+
let from = 0;
|
|
6993
|
+
for (const header of headers) {
|
|
6994
|
+
scan(from, header.index);
|
|
6995
|
+
from = header.index + header[0].length;
|
|
6996
|
+
}
|
|
6997
|
+
scan(from, text.length);
|
|
6998
|
+
return spans;
|
|
6999
|
+
}
|
|
7000
|
+
function inAttributeValue(text, offset) {
|
|
7001
|
+
return attributeValueSpans(text).some(([start, end]) => start <= offset && offset < end);
|
|
7002
|
+
}
|
|
7003
|
+
var VOID_ELEMENTS, TAG_NAME_CHAR, LETTER, ATTR_NAME_CHAR, WHITESPACE, SELECTOR_NAME_CHAR, DISPLAY_NAME_CHAR, GIVE_UP;
|
|
7004
|
+
var init_html_scanner = __esm({
|
|
7005
|
+
"src/core/parsing/html-scanner.ts"() {
|
|
7006
|
+
"use strict";
|
|
7007
|
+
init_code_scanner();
|
|
7008
|
+
VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
7009
|
+
"area",
|
|
7010
|
+
"base",
|
|
7011
|
+
"br",
|
|
7012
|
+
"col",
|
|
7013
|
+
"embed",
|
|
7014
|
+
"hr",
|
|
7015
|
+
"img",
|
|
7016
|
+
"input",
|
|
7017
|
+
"link",
|
|
7018
|
+
"meta",
|
|
7019
|
+
"param",
|
|
7020
|
+
"source",
|
|
7021
|
+
"track",
|
|
7022
|
+
"wbr"
|
|
7023
|
+
]);
|
|
7024
|
+
TAG_NAME_CHAR = /[a-zA-Z0-9-]/;
|
|
7025
|
+
LETTER = /[a-zA-Z]/;
|
|
7026
|
+
ATTR_NAME_CHAR = /[a-zA-Z0-9_\-:@]/;
|
|
7027
|
+
WHITESPACE = /[^\S ]/;
|
|
7028
|
+
SELECTOR_NAME_CHAR = /[a-zA-Z0-9_-]/;
|
|
7029
|
+
DISPLAY_NAME_CHAR = /[\w.]/;
|
|
7030
|
+
GIVE_UP = -2;
|
|
7031
|
+
}
|
|
7032
|
+
});
|
|
7033
|
+
|
|
7034
|
+
// src/core/parsing/macro-parser.ts
|
|
6748
7035
|
function buildLineStarts(text) {
|
|
6749
7036
|
const starts = [0];
|
|
6750
7037
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -6775,10 +7062,20 @@ function parseMacros(text) {
|
|
|
6775
7062
|
const scanner = createCodeScanner(cleaned);
|
|
6776
7063
|
const macros = [];
|
|
6777
7064
|
let id = 0;
|
|
7065
|
+
const attributeValues = attributeValueSpans(text);
|
|
7066
|
+
let value = 0;
|
|
7067
|
+
const inAttributeValue2 = (offset) => {
|
|
7068
|
+
while (value < attributeValues.length && attributeValues[value][1] <= offset) value++;
|
|
7069
|
+
return value < attributeValues.length && attributeValues[value][0] <= offset;
|
|
7070
|
+
};
|
|
6778
7071
|
macroHeadRegex.lastIndex = 0;
|
|
6779
7072
|
let match2;
|
|
6780
|
-
while ((match2 = macroHeadRegex.exec(
|
|
7073
|
+
while ((match2 = macroHeadRegex.exec(text)) !== null) {
|
|
6781
7074
|
const matchStart = match2.index;
|
|
7075
|
+
if (inAttributeValue2(matchStart)) {
|
|
7076
|
+
macroHeadRegex.lastIndex = matchStart + 1;
|
|
7077
|
+
continue;
|
|
7078
|
+
}
|
|
6782
7079
|
const closeIdx = scanner.closeBrace(matchStart + 1);
|
|
6783
7080
|
if (closeIdx === -1) {
|
|
6784
7081
|
macroHeadRegex.lastIndex = matchStart + 1;
|
|
@@ -6787,7 +7084,7 @@ function parseMacros(text) {
|
|
|
6787
7084
|
const matchEnd = closeIdx + 1;
|
|
6788
7085
|
macroHeadRegex.lastIndex = matchEnd;
|
|
6789
7086
|
const closeSlash = match2[1];
|
|
6790
|
-
const cssPrefix =
|
|
7087
|
+
const cssPrefix = match2[2] || "";
|
|
6791
7088
|
const macroName = match2[3];
|
|
6792
7089
|
const rawArgs = cleaned.slice(matchStart + match2[0].length, closeIdx).replace(/^\s+/, "");
|
|
6793
7090
|
const open = closeSlash !== "/";
|
|
@@ -6838,13 +7135,345 @@ function pairMacros(macros, isBlock, passageStartLines = []) {
|
|
|
6838
7135
|
}
|
|
6839
7136
|
}
|
|
6840
7137
|
}
|
|
6841
|
-
var variableInterpolationRegex,
|
|
7138
|
+
var variableInterpolationRegex, SELECTOR_PATTERN, macroHeadRegex;
|
|
6842
7139
|
var init_macro_parser = __esm({
|
|
6843
7140
|
"src/core/parsing/macro-parser.ts"() {
|
|
6844
7141
|
"use strict";
|
|
7142
|
+
init_code_scanner();
|
|
7143
|
+
init_html_scanner();
|
|
7144
|
+
init_code_scanner();
|
|
6845
7145
|
variableInterpolationRegex = /(?<!\\)\{([$_@%][A-Za-z_$][\w$.]*)\}/g;
|
|
6846
|
-
|
|
6847
|
-
|
|
7146
|
+
SELECTOR_PATTERN = String.raw`(?:[.#](?:[\w-]|\{[$_@][\w.]*\})*)+`;
|
|
7147
|
+
macroHeadRegex = new RegExp(
|
|
7148
|
+
String.raw`(?<!\\)\{(?:(\/)|(${SELECTOR_PATTERN}) )?([A-Za-z][\w-]*)(?=[\s}]|\{[$_@%][A-Za-z_$][\w$.]*\})`,
|
|
7149
|
+
"gi"
|
|
7150
|
+
);
|
|
7151
|
+
}
|
|
7152
|
+
});
|
|
7153
|
+
|
|
7154
|
+
// src/core/workspace/variable-schema.ts
|
|
7155
|
+
function inferDefaultSchema(expr) {
|
|
7156
|
+
const parser = new LiteralParser(expr.trim());
|
|
7157
|
+
try {
|
|
7158
|
+
const schema = parser.value();
|
|
7159
|
+
parser.skipSpace();
|
|
7160
|
+
return schema && parser.atEnd() ? schema : void 0;
|
|
7161
|
+
} catch (err) {
|
|
7162
|
+
if (err instanceof Unparsable) return void 0;
|
|
7163
|
+
throw err;
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
function findUnsupportedValue(expr) {
|
|
7167
|
+
const offset = expr.length - expr.trimStart().length;
|
|
7168
|
+
const parser = new LiteralParser(expr.trim());
|
|
7169
|
+
try {
|
|
7170
|
+
const read = parser.read();
|
|
7171
|
+
parser.skipSpace();
|
|
7172
|
+
const value = read.unsupported;
|
|
7173
|
+
if (!parser.atEnd() || !value) return void 0;
|
|
7174
|
+
return { ...value, start: value.start + offset, end: value.end + offset };
|
|
7175
|
+
} catch (err) {
|
|
7176
|
+
if (err instanceof Unparsable) return void 0;
|
|
7177
|
+
throw err;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
function bigintString(literal2) {
|
|
7181
|
+
const digits = literal2.slice(0, -1).replace(/_/g, "");
|
|
7182
|
+
const negative = digits.startsWith("-");
|
|
7183
|
+
try {
|
|
7184
|
+
const value = BigInt(negative ? digits.slice(1) : digits);
|
|
7185
|
+
return String(negative ? -value : value);
|
|
7186
|
+
} catch {
|
|
7187
|
+
return digits;
|
|
7188
|
+
}
|
|
7189
|
+
}
|
|
7190
|
+
function findPrimitiveFieldAccess(schema, fields) {
|
|
7191
|
+
let current = schema;
|
|
7192
|
+
for (let i = 0; i < fields.length; i++) {
|
|
7193
|
+
if (current.type === "array") return void 0;
|
|
7194
|
+
if (current.type !== "object") return { index: i, type: current.type };
|
|
7195
|
+
const next = current.fields?.get(fields[i]);
|
|
7196
|
+
if (!next) return void 0;
|
|
7197
|
+
current = next;
|
|
7198
|
+
}
|
|
7199
|
+
return void 0;
|
|
7200
|
+
}
|
|
7201
|
+
var Unparsable, IDENTIFIER_RE, BIGINT_RE, NUMBER_RE, INDEX_KEY_RE, UNKNOWN2, LiteralParser;
|
|
7202
|
+
var init_variable_schema = __esm({
|
|
7203
|
+
"src/core/workspace/variable-schema.ts"() {
|
|
7204
|
+
"use strict";
|
|
7205
|
+
Unparsable = class extends Error {
|
|
7206
|
+
};
|
|
7207
|
+
IDENTIFIER_RE = /[A-Za-z_$][\w$]*/y;
|
|
7208
|
+
BIGINT_RE = /-?(?:0[xX][\da-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+|0|[1-9][\d_]*)n(?![\w$])/y;
|
|
7209
|
+
NUMBER_RE = /[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?/y;
|
|
7210
|
+
INDEX_KEY_RE = /(?:0|[1-9]\d{0,14})(?![\w$.])/y;
|
|
7211
|
+
UNKNOWN2 = { schema: null };
|
|
7212
|
+
LiteralParser = class {
|
|
7213
|
+
constructor(text) {
|
|
7214
|
+
this.text = text;
|
|
7215
|
+
}
|
|
7216
|
+
pos = 0;
|
|
7217
|
+
atEnd() {
|
|
7218
|
+
return this.pos >= this.text.length;
|
|
7219
|
+
}
|
|
7220
|
+
skipSpace() {
|
|
7221
|
+
while (/\s/.test(this.text[this.pos] ?? "")) this.pos++;
|
|
7222
|
+
}
|
|
7223
|
+
/** A value up to the `,` / `}` / `]` that ends it; null when its type is unknown. */
|
|
7224
|
+
value() {
|
|
7225
|
+
return this.read().schema;
|
|
7226
|
+
}
|
|
7227
|
+
/** A value up to the `,` / `}` / `]` that ends it. */
|
|
7228
|
+
read() {
|
|
7229
|
+
this.skipSpace();
|
|
7230
|
+
const start = this.pos;
|
|
7231
|
+
const read = this.literal();
|
|
7232
|
+
this.skipSpace();
|
|
7233
|
+
const next = this.text[this.pos];
|
|
7234
|
+
if (read && (next === void 0 || next === "," || next === "}" || next === "]")) return read;
|
|
7235
|
+
this.pos = start;
|
|
7236
|
+
this.skipExpression();
|
|
7237
|
+
return UNKNOWN2;
|
|
7238
|
+
}
|
|
7239
|
+
/** A literal, consumed; undefined when the code here is not one. */
|
|
7240
|
+
literal() {
|
|
7241
|
+
const start = this.pos;
|
|
7242
|
+
const ch = this.text[this.pos];
|
|
7243
|
+
if (ch === "{") return this.object();
|
|
7244
|
+
if (ch === "[") {
|
|
7245
|
+
this.pos++;
|
|
7246
|
+
this.skipList("]");
|
|
7247
|
+
return { schema: { type: "array" } };
|
|
7248
|
+
}
|
|
7249
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
7250
|
+
this.stringLiteral();
|
|
7251
|
+
return { schema: { type: "string" } };
|
|
7252
|
+
}
|
|
7253
|
+
if (ch === "(") return this.functionLiteral(start);
|
|
7254
|
+
const word = this.match(IDENTIFIER_RE);
|
|
7255
|
+
if (word === "true" || word === "false") return { schema: { type: "boolean" } };
|
|
7256
|
+
if (word === "null") return this.unsupported("object", start);
|
|
7257
|
+
if (word === "undefined") return this.unsupported("undefined", start);
|
|
7258
|
+
if (word === "void") {
|
|
7259
|
+
this.skipSpace();
|
|
7260
|
+
if (this.match(NUMBER_RE) === void 0) return void 0;
|
|
7261
|
+
return this.unsupported("undefined", start, "undefined");
|
|
7262
|
+
}
|
|
7263
|
+
if (word !== void 0) return this.functionLiteral(start);
|
|
7264
|
+
if (this.match(BIGINT_RE) !== void 0) {
|
|
7265
|
+
return this.unsupported("bigint", start, bigintString(this.text.slice(start, this.pos)));
|
|
7266
|
+
}
|
|
7267
|
+
if (this.match(NUMBER_RE) !== void 0) return { schema: { type: "number" } };
|
|
7268
|
+
return void 0;
|
|
7269
|
+
}
|
|
7270
|
+
/** The value from `start` to here, which Spindle rejects. */
|
|
7271
|
+
unsupported(type, start, text) {
|
|
7272
|
+
const end = this.pos;
|
|
7273
|
+
return { schema: null, unsupported: { type, text: text ?? this.text.slice(start, end), path: [], start, end } };
|
|
7274
|
+
}
|
|
7275
|
+
/**
|
|
7276
|
+
* A function, arrow function or class expression at `start`, consumed;
|
|
7277
|
+
* undefined, with nothing consumed, for any other code.
|
|
7278
|
+
*/
|
|
7279
|
+
functionLiteral(start) {
|
|
7280
|
+
this.pos = start;
|
|
7281
|
+
try {
|
|
7282
|
+
if (this.functionSyntax()) return this.unsupported("function", start);
|
|
7283
|
+
} catch (err) {
|
|
7284
|
+
if (!(err instanceof Unparsable)) throw err;
|
|
7285
|
+
}
|
|
7286
|
+
this.pos = start;
|
|
7287
|
+
return void 0;
|
|
7288
|
+
}
|
|
7289
|
+
functionSyntax() {
|
|
7290
|
+
let word = this.match(IDENTIFIER_RE);
|
|
7291
|
+
if (word === "async") {
|
|
7292
|
+
this.skipSpace();
|
|
7293
|
+
word = this.match(IDENTIFIER_RE);
|
|
7294
|
+
}
|
|
7295
|
+
this.skipSpace();
|
|
7296
|
+
if (word === "function") {
|
|
7297
|
+
if (this.text[this.pos] === "*") this.pos++;
|
|
7298
|
+
this.skipSpace();
|
|
7299
|
+
this.match(IDENTIFIER_RE);
|
|
7300
|
+
this.skipSpace();
|
|
7301
|
+
if (!this.skipBracketed("(", ")")) return false;
|
|
7302
|
+
this.skipSpace();
|
|
7303
|
+
return this.skipBracketed("{", "}");
|
|
7304
|
+
}
|
|
7305
|
+
if (word === "class") {
|
|
7306
|
+
if (this.match(IDENTIFIER_RE) !== void 0) this.skipSpace();
|
|
7307
|
+
return this.skipBracketed("{", "}");
|
|
7308
|
+
}
|
|
7309
|
+
if (word === void 0 && !this.skipBracketed("(", ")")) return false;
|
|
7310
|
+
this.skipSpace();
|
|
7311
|
+
if (!this.text.startsWith("=>", this.pos)) return false;
|
|
7312
|
+
this.pos += 2;
|
|
7313
|
+
this.skipSpace();
|
|
7314
|
+
if (this.text[this.pos] === "{") return this.skipBracketed("{", "}");
|
|
7315
|
+
const bodyStart = this.pos;
|
|
7316
|
+
this.skipExpression();
|
|
7317
|
+
while (this.pos > bodyStart && /\s/.test(this.text[this.pos - 1])) this.pos--;
|
|
7318
|
+
return this.pos > bodyStart;
|
|
7319
|
+
}
|
|
7320
|
+
/** Skip a bracketed list that opens here; false if none does. */
|
|
7321
|
+
skipBracketed(open, close) {
|
|
7322
|
+
if (this.text[this.pos] !== open) return false;
|
|
7323
|
+
this.pos++;
|
|
7324
|
+
this.skipList(close);
|
|
7325
|
+
return true;
|
|
7326
|
+
}
|
|
7327
|
+
object() {
|
|
7328
|
+
this.pos++;
|
|
7329
|
+
let fields = /* @__PURE__ */ new Map();
|
|
7330
|
+
const unsupported = /* @__PURE__ */ new Map();
|
|
7331
|
+
for (; ; ) {
|
|
7332
|
+
this.skipSpace();
|
|
7333
|
+
if (this.text[this.pos] === "}") {
|
|
7334
|
+
this.pos++;
|
|
7335
|
+
break;
|
|
7336
|
+
}
|
|
7337
|
+
const key = this.propertyKey();
|
|
7338
|
+
this.skipSpace();
|
|
7339
|
+
if (key !== void 0 && key !== "__proto__" && this.text[this.pos] === ":") {
|
|
7340
|
+
this.pos++;
|
|
7341
|
+
const read = this.read();
|
|
7342
|
+
fields?.set(key, read.schema);
|
|
7343
|
+
unsupported.delete(key);
|
|
7344
|
+
if (read.unsupported) {
|
|
7345
|
+
unsupported.set(key, { ...read.unsupported, path: [key, ...read.unsupported.path] });
|
|
7346
|
+
}
|
|
7347
|
+
} else {
|
|
7348
|
+
fields = void 0;
|
|
7349
|
+
this.skipExpression();
|
|
7350
|
+
}
|
|
7351
|
+
this.skipSpace();
|
|
7352
|
+
const sep2 = this.text[this.pos++];
|
|
7353
|
+
if (sep2 === "}") break;
|
|
7354
|
+
if (sep2 !== ",") throw new Unparsable();
|
|
7355
|
+
}
|
|
7356
|
+
if (!fields) return { schema: { type: "object" } };
|
|
7357
|
+
const [first] = unsupported.values();
|
|
7358
|
+
return { schema: { type: "object", fields }, unsupported: first };
|
|
7359
|
+
}
|
|
7360
|
+
/** A plain property key, consumed; undefined (possibly consumed) for any other. */
|
|
7361
|
+
propertyKey() {
|
|
7362
|
+
const ch = this.text[this.pos];
|
|
7363
|
+
if (ch === '"' || ch === "'") {
|
|
7364
|
+
const raw = this.stringLiteral();
|
|
7365
|
+
return raw.includes("\\") ? void 0 : raw;
|
|
7366
|
+
}
|
|
7367
|
+
return this.match(IDENTIFIER_RE) ?? this.match(INDEX_KEY_RE);
|
|
7368
|
+
}
|
|
7369
|
+
/** Consume a string or template literal and return its raw text. */
|
|
7370
|
+
stringLiteral() {
|
|
7371
|
+
const quote = this.text[this.pos];
|
|
7372
|
+
const start = ++this.pos;
|
|
7373
|
+
while (this.pos < this.text.length) {
|
|
7374
|
+
const ch = this.text[this.pos];
|
|
7375
|
+
if (ch === "\\") {
|
|
7376
|
+
this.pos += 2;
|
|
7377
|
+
continue;
|
|
7378
|
+
}
|
|
7379
|
+
if (quote === "`" && ch === "$" && this.text[this.pos + 1] === "{") throw new Unparsable();
|
|
7380
|
+
if (ch === quote) return this.text.slice(start, this.pos++);
|
|
7381
|
+
this.pos++;
|
|
7382
|
+
}
|
|
7383
|
+
throw new Unparsable();
|
|
7384
|
+
}
|
|
7385
|
+
/** Skip comma-separated expressions up to and including `close`. */
|
|
7386
|
+
skipList(close) {
|
|
7387
|
+
for (; ; ) {
|
|
7388
|
+
this.skipExpression();
|
|
7389
|
+
const ch = this.text[this.pos++];
|
|
7390
|
+
if (ch === close) return;
|
|
7391
|
+
if (ch !== ",") throw new Unparsable();
|
|
7392
|
+
}
|
|
7393
|
+
}
|
|
7394
|
+
/** Skip code up to a `,`, `)`, `]` or `}` that closes nothing, or the end. */
|
|
7395
|
+
skipExpression() {
|
|
7396
|
+
let depth = 0;
|
|
7397
|
+
while (this.pos < this.text.length) {
|
|
7398
|
+
const ch = this.text[this.pos];
|
|
7399
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
7400
|
+
this.stringLiteral();
|
|
7401
|
+
continue;
|
|
7402
|
+
}
|
|
7403
|
+
if (ch === "/") throw new Unparsable();
|
|
7404
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
7405
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
7406
|
+
if (depth === 0) return;
|
|
7407
|
+
depth--;
|
|
7408
|
+
} else if (ch === "," && depth === 0) return;
|
|
7409
|
+
this.pos++;
|
|
7410
|
+
}
|
|
7411
|
+
if (depth > 0) throw new Unparsable();
|
|
7412
|
+
}
|
|
7413
|
+
match(re) {
|
|
7414
|
+
re.lastIndex = this.pos;
|
|
7415
|
+
const m = re.exec(this.text);
|
|
7416
|
+
if (!m) return void 0;
|
|
7417
|
+
this.pos += m[0].length;
|
|
7418
|
+
return m[0];
|
|
7419
|
+
}
|
|
7420
|
+
};
|
|
7421
|
+
}
|
|
7422
|
+
});
|
|
7423
|
+
|
|
7424
|
+
// src/core/workspace/declaration-check.ts
|
|
7425
|
+
function declaredName(line, sigil) {
|
|
7426
|
+
return DECLARATION_RE[sigil].exec(line.trim())?.[1];
|
|
7427
|
+
}
|
|
7428
|
+
function checkDeclaration(rawLine, sigil) {
|
|
7429
|
+
const line = rawLine.trim();
|
|
7430
|
+
if (!line || line.includes("\r") || line.includes("\xA0")) return void 0;
|
|
7431
|
+
const passage = PASSAGE[sigil];
|
|
7432
|
+
const match2 = DECLARATION_RE[sigil].exec(line);
|
|
7433
|
+
if (!match2) {
|
|
7434
|
+
const comment = /^(?:\/\/|\/\*|<!--|#)/.test(line) ? ` ${passage} has no comment syntax.` : "";
|
|
7435
|
+
return {
|
|
7436
|
+
kind: "invalid",
|
|
7437
|
+
message: `${passage}: Invalid declaration: "${line}". Expected: ${sigil}name = value.${comment}`
|
|
7438
|
+
};
|
|
7439
|
+
}
|
|
7440
|
+
const [, name, expr] = match2;
|
|
7441
|
+
const error48 = compileError(expr);
|
|
7442
|
+
if (error48 !== void 0) {
|
|
7443
|
+
const comment = compileError(expr + "\n") === void 0 ? ` A comment runs to the end of the line and hides the ")" Spindle closes the value with; ${passage} has no comment syntax.` : "";
|
|
7444
|
+
return {
|
|
7445
|
+
kind: "evaluate",
|
|
7446
|
+
message: `${passage}: Failed to evaluate "${sigil}${name} = ${expr}": ${error48}.${comment}`
|
|
7447
|
+
};
|
|
7448
|
+
}
|
|
7449
|
+
const value = findUnsupportedValue(expr);
|
|
7450
|
+
if (!value) return void 0;
|
|
7451
|
+
const exprStart = rawLine.length - rawLine.trimStart().length + line.length - expr.length;
|
|
7452
|
+
if (value.type === "object") {
|
|
7453
|
+
return { kind: "null", field: value.path, start: exprStart + value.start, end: exprStart + value.end };
|
|
7454
|
+
}
|
|
7455
|
+
const where = value.path.length > 0 ? ` (${sigil}${[name, ...value.path].join(".")})` : "";
|
|
7456
|
+
return {
|
|
7457
|
+
kind: "unsupported",
|
|
7458
|
+
message: `${passage}: Unsupported type "${value.type}" for value ${value.text}${where}. Expected number, string, boolean, array, or object.`
|
|
7459
|
+
};
|
|
7460
|
+
}
|
|
7461
|
+
function compileError(expr) {
|
|
7462
|
+
try {
|
|
7463
|
+
new Function("return (" + expr + ")");
|
|
7464
|
+
return void 0;
|
|
7465
|
+
} catch (err) {
|
|
7466
|
+
if (!(err instanceof SyntaxError) || /regular expression/i.test(err.message)) return void 0;
|
|
7467
|
+
return err.message;
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
var DECLARATION_RE, PASSAGE;
|
|
7471
|
+
var init_declaration_check = __esm({
|
|
7472
|
+
"src/core/workspace/declaration-check.ts"() {
|
|
7473
|
+
"use strict";
|
|
7474
|
+
init_variable_schema();
|
|
7475
|
+
DECLARATION_RE = { "$": /^\$(\w+)\s*=\s*(.+)$/, "%": /^%(\w+)\s*=\s*(.+)$/ };
|
|
7476
|
+
PASSAGE = { "$": "StoryVariables", "%": "StoryTransients" };
|
|
6848
7477
|
}
|
|
6849
7478
|
});
|
|
6850
7479
|
|
|
@@ -6853,6 +7482,14 @@ function replaceCodeLiterals(text, replace) {
|
|
|
6853
7482
|
const scanner = createCodeScanner(text);
|
|
6854
7483
|
let result = "";
|
|
6855
7484
|
let copied = 0;
|
|
7485
|
+
for (const [open, close] of codeBlocks(text, scanner)) {
|
|
7486
|
+
result += text.slice(copied, open + 1) + replaceLiterals(text, scanner, open + 1, close, replace);
|
|
7487
|
+
copied = close;
|
|
7488
|
+
}
|
|
7489
|
+
return result + text.slice(copied);
|
|
7490
|
+
}
|
|
7491
|
+
function codeBlocks(text, scanner) {
|
|
7492
|
+
const blocks = [];
|
|
6856
7493
|
for (let i = 0; i < text.length; i++) {
|
|
6857
7494
|
if (text[i] === "\\" && (text[i + 1] === "{" || text[i + 1] === "}")) {
|
|
6858
7495
|
i++;
|
|
@@ -6861,11 +7498,10 @@ function replaceCodeLiterals(text, replace) {
|
|
|
6861
7498
|
if (text[i] !== "{") continue;
|
|
6862
7499
|
const close = scanner.closeBrace(i + 1);
|
|
6863
7500
|
if (close === -1) continue;
|
|
6864
|
-
|
|
6865
|
-
copied = close;
|
|
7501
|
+
blocks.push([i, close]);
|
|
6866
7502
|
i = close;
|
|
6867
7503
|
}
|
|
6868
|
-
return
|
|
7504
|
+
return blocks;
|
|
6869
7505
|
}
|
|
6870
7506
|
function replaceLiterals(text, scanner, from, to, replace) {
|
|
6871
7507
|
let result = "";
|
|
@@ -6891,7 +7527,7 @@ function blankLiteralText(literal2) {
|
|
|
6891
7527
|
for (let i = 1; i < literal2.length - 1; i++) {
|
|
6892
7528
|
if (literal2[i] !== "{") continue;
|
|
6893
7529
|
const templateCode = literal2[0] === "`" && literal2[i - 1] === "$" && literal2[i - 2] !== "\\";
|
|
6894
|
-
if (!templateCode && !/^[$%][
|
|
7530
|
+
if (!templateCode && !/^[$%][\w$]/.test(literal2.slice(i + 1, i + 3))) continue;
|
|
6895
7531
|
let depth = 0;
|
|
6896
7532
|
for (let j = i; j < literal2.length - 1; j++) {
|
|
6897
7533
|
if (literal2[j] === "{") depth++;
|
|
@@ -6904,6 +7540,43 @@ function blankLiteralText(literal2) {
|
|
|
6904
7540
|
}
|
|
6905
7541
|
return literal2.replace(/[^\r\n]/g, (ch, i) => keep[i] ? ch : " ");
|
|
6906
7542
|
}
|
|
7543
|
+
function checkDeclarations(lines, contentStartLine, sigil) {
|
|
7544
|
+
const check2 = { problems: [], nestedNulls: [], names: /* @__PURE__ */ new Map() };
|
|
7545
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7546
|
+
const line = lines[i];
|
|
7547
|
+
if (line.startsWith("::")) break;
|
|
7548
|
+
const absLine = contentStartLine + i;
|
|
7549
|
+
const problem = checkDeclaration(line, sigil);
|
|
7550
|
+
if (!problem) {
|
|
7551
|
+
const name = /[\r\u00a0]/.test(line.trim()) ? void 0 : declaredName(line, sigil);
|
|
7552
|
+
if (name !== void 0 && !check2.names.has(name)) {
|
|
7553
|
+
check2.names.set(name, declarationLocation(line, name, absLine, void 0).declarationRange);
|
|
7554
|
+
}
|
|
7555
|
+
} else if (problem.kind === "null") {
|
|
7556
|
+
if (problem.field.length > 0) {
|
|
7557
|
+
check2.nestedNulls.push({
|
|
7558
|
+
name: declaredName(line, sigil),
|
|
7559
|
+
sigil,
|
|
7560
|
+
field: problem.field,
|
|
7561
|
+
range: {
|
|
7562
|
+
start: { line: absLine, character: problem.start },
|
|
7563
|
+
end: { line: absLine, character: problem.end }
|
|
7564
|
+
}
|
|
7565
|
+
});
|
|
7566
|
+
}
|
|
7567
|
+
} else {
|
|
7568
|
+
const start = line.length - line.trimStart().length;
|
|
7569
|
+
check2.problems.push({
|
|
7570
|
+
message: problem.message,
|
|
7571
|
+
range: {
|
|
7572
|
+
start: { line: absLine, character: start },
|
|
7573
|
+
end: { line: absLine, character: line.trimEnd().length }
|
|
7574
|
+
}
|
|
7575
|
+
});
|
|
7576
|
+
}
|
|
7577
|
+
}
|
|
7578
|
+
return check2;
|
|
7579
|
+
}
|
|
6907
7580
|
function findLiteralEnd(text, start) {
|
|
6908
7581
|
const open = text[start];
|
|
6909
7582
|
if (open === '"' || open === "'" || open === "`") {
|
|
@@ -6960,14 +7633,48 @@ function declarationLocation(line, name, absLine, uri) {
|
|
|
6960
7633
|
if (uri !== void 0) location.declarationUri = uri;
|
|
6961
7634
|
return location;
|
|
6962
7635
|
}
|
|
6963
|
-
|
|
7636
|
+
function validatedReferences(lines, passages) {
|
|
7637
|
+
const refs = [];
|
|
7638
|
+
for (let pi = 0; pi < passages.length; pi++) {
|
|
7639
|
+
const passage = passages[pi];
|
|
7640
|
+
if (UNVALIDATED_PASSAGES.has(passage.name) || isScriptOrStylesheetPassage(passage)) continue;
|
|
7641
|
+
const contentStartLine = passage.startLine + 1;
|
|
7642
|
+
const contentEndLine = pi + 1 < passages.length ? passages[pi + 1].startLine : lines.length;
|
|
7643
|
+
const contentLines = lines.slice(contentStartLine, contentEndLine);
|
|
7644
|
+
const forLocals = /* @__PURE__ */ new Set();
|
|
7645
|
+
for (const m of contentLines.join("\n").matchAll(VALIDATED_FOR_LOCAL_RE)) {
|
|
7646
|
+
forLocals.add(m[1]);
|
|
7647
|
+
if (m[2]) forLocals.add(m[2]);
|
|
7648
|
+
}
|
|
7649
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
7650
|
+
for (const m of contentLines[i].matchAll(VALIDATED_REF_RE)) {
|
|
7651
|
+
const baseName = m[1].split(".")[0];
|
|
7652
|
+
if (forLocals.has(baseName)) continue;
|
|
7653
|
+
const line = contentStartLine + i;
|
|
7654
|
+
refs.push({
|
|
7655
|
+
baseName,
|
|
7656
|
+
path: m[1],
|
|
7657
|
+
range: {
|
|
7658
|
+
start: { line, character: m.index },
|
|
7659
|
+
end: { line, character: m.index + m[0].length }
|
|
7660
|
+
}
|
|
7661
|
+
});
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
return refs;
|
|
7666
|
+
}
|
|
7667
|
+
var varRefRegex, transientRefRegex, DECLARATION_RE2, EXCLUDED_PASSAGES, VALIDATED_REF_RE, VALIDATED_FOR_LOCAL_RE, UNVALIDATED_PASSAGES, COMMENT_PATTERNS, BUILTIN_STORE_VAR_MACROS, QUOTED_RECEIVER_RE, NO_DECLARATIONS, ARRAY_MEMBERS, VariableTracker;
|
|
6964
7668
|
var init_variable_tracker = __esm({
|
|
6965
7669
|
"src/core/workspace/variable-tracker.ts"() {
|
|
6966
7670
|
"use strict";
|
|
6967
7671
|
init_passage_parser();
|
|
6968
7672
|
init_macro_parser();
|
|
6969
|
-
|
|
6970
|
-
|
|
7673
|
+
init_variable_schema();
|
|
7674
|
+
init_declaration_check();
|
|
7675
|
+
varRefRegex = /\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
7676
|
+
transientRefRegex = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
7677
|
+
DECLARATION_RE2 = { "$": /^\$(\w+)\s*=\s*(.*)$/, "%": /^%(\w+)\s*=\s*(.*)$/ };
|
|
6971
7678
|
EXCLUDED_PASSAGES = /* @__PURE__ */ new Set([
|
|
6972
7679
|
"StoryVariables",
|
|
6973
7680
|
"StoryTransients",
|
|
@@ -6975,7 +7682,14 @@ var init_variable_tracker = __esm({
|
|
|
6975
7682
|
"StoryScript",
|
|
6976
7683
|
"StoryInterface"
|
|
6977
7684
|
]);
|
|
6978
|
-
|
|
7685
|
+
VALIDATED_REF_RE = /\$(\w+(?:\.\w+)*)/g;
|
|
7686
|
+
VALIDATED_FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
|
|
7687
|
+
UNVALIDATED_PASSAGES = /* @__PURE__ */ new Set([
|
|
7688
|
+
"StoryVariables",
|
|
7689
|
+
"StoryTransients",
|
|
7690
|
+
"StoryData",
|
|
7691
|
+
"StoryTitle"
|
|
7692
|
+
]);
|
|
6979
7693
|
COMMENT_PATTERNS = [
|
|
6980
7694
|
/<!--[\s\S]*?-->/g,
|
|
6981
7695
|
// HTML comments
|
|
@@ -6993,7 +7707,11 @@ var init_variable_tracker = __esm({
|
|
|
6993
7707
|
"textarea",
|
|
6994
7708
|
"textbox"
|
|
6995
7709
|
]);
|
|
6996
|
-
QUOTED_RECEIVER_RE =
|
|
7710
|
+
QUOTED_RECEIVER_RE = new RegExp(
|
|
7711
|
+
String.raw`(?<!\\)(\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\s+(["']))\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)\3?(?=[\s}])`,
|
|
7712
|
+
"g"
|
|
7713
|
+
);
|
|
7714
|
+
NO_DECLARATIONS = { problems: [], nestedNulls: [], names: /* @__PURE__ */ new Map() };
|
|
6997
7715
|
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
6998
7716
|
...Object.getOwnPropertyNames(Array.prototype),
|
|
6999
7717
|
...Object.getOwnPropertyNames(Object.prototype),
|
|
@@ -7012,13 +7730,17 @@ var init_variable_tracker = __esm({
|
|
|
7012
7730
|
declared = /* @__PURE__ */ new Map();
|
|
7013
7731
|
_hasStoryVariables = false;
|
|
7014
7732
|
_nullDeclarations = [];
|
|
7733
|
+
_variablesCheck = NO_DECLARATIONS;
|
|
7015
7734
|
declaredTransient = /* @__PURE__ */ new Map();
|
|
7016
7735
|
_hasStoryTransients = false;
|
|
7017
7736
|
_nullTransientDeclarations = [];
|
|
7737
|
+
_transientsCheck = NO_DECLARATIONS;
|
|
7018
7738
|
/** Per-URI list of variable usages. */
|
|
7019
7739
|
usagesByUri = /* @__PURE__ */ new Map();
|
|
7020
7740
|
/** Per-URI list of transient variable usages. */
|
|
7021
7741
|
transientUsagesByUri = /* @__PURE__ */ new Map();
|
|
7742
|
+
/** Per-URI list of the `$` references Spindle validates at startup. */
|
|
7743
|
+
validatedRefsByUri = /* @__PURE__ */ new Map();
|
|
7022
7744
|
/**
|
|
7023
7745
|
* Parse the StoryVariables passage content for declarations.
|
|
7024
7746
|
* Each line like `$name = value` becomes a declaration.
|
|
@@ -7028,10 +7750,11 @@ var init_variable_tracker = __esm({
|
|
|
7028
7750
|
this._hasStoryVariables = true;
|
|
7029
7751
|
this._nullDeclarations = [];
|
|
7030
7752
|
const lines = content.split("\n");
|
|
7753
|
+
this._variablesCheck = checkDeclarations(lines, contentStartLine, "$");
|
|
7031
7754
|
for (let i = 0; i < lines.length; i++) {
|
|
7032
7755
|
const trimmed2 = lines[i].trim();
|
|
7033
7756
|
if (!trimmed2 || trimmed2.startsWith("//") || trimmed2.startsWith("<!--")) continue;
|
|
7034
|
-
const match2 =
|
|
7757
|
+
const match2 = DECLARATION_RE2.$.exec(trimmed2);
|
|
7035
7758
|
if (!match2) continue;
|
|
7036
7759
|
const name = match2[1];
|
|
7037
7760
|
const expr = match2[2].trim();
|
|
@@ -7053,6 +7776,8 @@ var init_variable_tracker = __esm({
|
|
|
7053
7776
|
const decl = { name, sigil: "$", ...location };
|
|
7054
7777
|
const type = inferLiteralType(expr);
|
|
7055
7778
|
if (type) decl.type = type;
|
|
7779
|
+
const schema = inferDefaultSchema(expr);
|
|
7780
|
+
if (schema) decl.schema = schema;
|
|
7056
7781
|
if (expr.startsWith("{")) {
|
|
7057
7782
|
const fieldRegex = /(\w+)\s*:/g;
|
|
7058
7783
|
let fieldMatch;
|
|
@@ -7072,12 +7797,14 @@ var init_variable_tracker = __esm({
|
|
|
7072
7797
|
this.declared.clear();
|
|
7073
7798
|
this._hasStoryVariables = false;
|
|
7074
7799
|
this._nullDeclarations = [];
|
|
7800
|
+
this._variablesCheck = NO_DECLARATIONS;
|
|
7075
7801
|
}
|
|
7076
7802
|
/** Forget StoryTransients declarations, e.g. after the passage was removed. */
|
|
7077
7803
|
clearStoryTransients() {
|
|
7078
7804
|
this.declaredTransient.clear();
|
|
7079
7805
|
this._hasStoryTransients = false;
|
|
7080
7806
|
this._nullTransientDeclarations = [];
|
|
7807
|
+
this._transientsCheck = NO_DECLARATIONS;
|
|
7081
7808
|
}
|
|
7082
7809
|
/**
|
|
7083
7810
|
* Parse the StoryTransients passage content for declarations.
|
|
@@ -7088,10 +7815,11 @@ var init_variable_tracker = __esm({
|
|
|
7088
7815
|
this._hasStoryTransients = true;
|
|
7089
7816
|
this._nullTransientDeclarations = [];
|
|
7090
7817
|
const lines = content.split("\n");
|
|
7818
|
+
this._transientsCheck = checkDeclarations(lines, contentStartLine, "%");
|
|
7091
7819
|
for (let i = 0; i < lines.length; i++) {
|
|
7092
7820
|
const trimmed2 = lines[i].trim();
|
|
7093
7821
|
if (!trimmed2 || trimmed2.startsWith("//") || trimmed2.startsWith("<!--")) continue;
|
|
7094
|
-
const match2 =
|
|
7822
|
+
const match2 = DECLARATION_RE2["%"].exec(trimmed2);
|
|
7095
7823
|
if (!match2) continue;
|
|
7096
7824
|
const name = match2[1];
|
|
7097
7825
|
const expr = match2[2].trim();
|
|
@@ -7136,6 +7864,7 @@ var init_variable_tracker = __esm({
|
|
|
7136
7864
|
scanDocument(uri, text, _macros, storeVarMacros = BUILTIN_STORE_VAR_MACROS) {
|
|
7137
7865
|
this.usagesByUri.delete(uri);
|
|
7138
7866
|
this.transientUsagesByUri.delete(uri);
|
|
7867
|
+
this.validatedRefsByUri.delete(uri);
|
|
7139
7868
|
const lines = text.split("\n");
|
|
7140
7869
|
const usages = [];
|
|
7141
7870
|
const transientUsages = [];
|
|
@@ -7170,14 +7899,15 @@ var init_variable_tracker = __esm({
|
|
|
7170
7899
|
for (let i = 0; i < cleaned.length; i++) {
|
|
7171
7900
|
if (cleaned[i] === "\n") lineOffsets.push(i + 1);
|
|
7172
7901
|
}
|
|
7173
|
-
const
|
|
7902
|
+
const blocks = codeBlocks(uncommented, createCodeScanner(uncommented));
|
|
7903
|
+
const inCode = (offset) => blocks.some(([open, close]) => open < offset && offset < close);
|
|
7174
7904
|
const scans = [
|
|
7175
|
-
[varRefRegex, usages],
|
|
7176
|
-
[transientRefRegex, transientUsages]
|
|
7905
|
+
[varRefRegex, usages, false],
|
|
7906
|
+
[transientRefRegex, transientUsages, true]
|
|
7177
7907
|
];
|
|
7178
|
-
for (const [regex, out] of scans) {
|
|
7908
|
+
for (const [regex, out, digitsOnlyInCode] of scans) {
|
|
7179
7909
|
const seen = /* @__PURE__ */ new Set();
|
|
7180
|
-
for (const
|
|
7910
|
+
for (const source of [cleaned, referenced]) {
|
|
7181
7911
|
const re = new RegExp(regex.source, "g");
|
|
7182
7912
|
let match2;
|
|
7183
7913
|
while ((match2 = re.exec(source)) !== null) {
|
|
@@ -7186,6 +7916,7 @@ var init_variable_tracker = __esm({
|
|
|
7186
7916
|
seen.add(charOffset);
|
|
7187
7917
|
const fullName = match2[1];
|
|
7188
7918
|
const baseName = fullName.split(".")[0];
|
|
7919
|
+
if (digitsOnlyInCode && /^\d/.test(baseName) && !inCode(charOffset)) continue;
|
|
7189
7920
|
let localLine = 0;
|
|
7190
7921
|
for (let i = 0; i < lineOffsets.length; i++) {
|
|
7191
7922
|
if (lineOffsets[i] > charOffset) break;
|
|
@@ -7197,7 +7928,7 @@ var init_variable_tracker = __esm({
|
|
|
7197
7928
|
start: { line: absoluteLine, character },
|
|
7198
7929
|
end: { line: absoluteLine, character: character + match2[0].length }
|
|
7199
7930
|
};
|
|
7200
|
-
out.push({ uri, baseName, fullName, range: range2
|
|
7931
|
+
out.push({ uri, baseName, fullName, range: range2 });
|
|
7201
7932
|
}
|
|
7202
7933
|
}
|
|
7203
7934
|
}
|
|
@@ -7208,11 +7939,16 @@ var init_variable_tracker = __esm({
|
|
|
7208
7939
|
if (transientUsages.length > 0) {
|
|
7209
7940
|
this.transientUsagesByUri.set(uri, transientUsages);
|
|
7210
7941
|
}
|
|
7942
|
+
const validated = validatedReferences(lines, passageBoundaries);
|
|
7943
|
+
if (validated.length > 0) {
|
|
7944
|
+
this.validatedRefsByUri.set(uri, validated);
|
|
7945
|
+
}
|
|
7211
7946
|
}
|
|
7212
7947
|
/** Forget the usages recorded for a document, e.g. after it was deleted. */
|
|
7213
7948
|
removeDocument(uri) {
|
|
7214
7949
|
this.usagesByUri.delete(uri);
|
|
7215
7950
|
this.transientUsagesByUri.delete(uri);
|
|
7951
|
+
this.validatedRefsByUri.delete(uri);
|
|
7216
7952
|
}
|
|
7217
7953
|
/** Get all declared variables. */
|
|
7218
7954
|
getDeclared() {
|
|
@@ -7230,20 +7966,52 @@ var init_variable_tracker = __esm({
|
|
|
7230
7966
|
}
|
|
7231
7967
|
return results;
|
|
7232
7968
|
}
|
|
7233
|
-
/**
|
|
7969
|
+
/**
|
|
7970
|
+
* Get undeclared variable references in a specific document: the ones
|
|
7971
|
+
* Spindle rejects when the story starts, wherever they are in the text.
|
|
7972
|
+
*/
|
|
7234
7973
|
getUndeclared(uri) {
|
|
7235
|
-
const
|
|
7236
|
-
if (!
|
|
7974
|
+
const refs = this.validatedRefsByUri.get(uri);
|
|
7975
|
+
if (!refs) return [];
|
|
7237
7976
|
const results = [];
|
|
7238
7977
|
const seen = /* @__PURE__ */ new Set();
|
|
7239
|
-
for (const u of
|
|
7240
|
-
if (
|
|
7978
|
+
for (const u of refs) {
|
|
7979
|
+
if (!this.declared.has(u.baseName) && !seen.has(u.baseName)) {
|
|
7241
7980
|
seen.add(u.baseName);
|
|
7242
7981
|
results.push({ name: u.baseName, range: u.range });
|
|
7243
7982
|
}
|
|
7244
7983
|
}
|
|
7245
7984
|
return results;
|
|
7246
7985
|
}
|
|
7986
|
+
/**
|
|
7987
|
+
* Get the `$var.a.b` paths in a document that Spindle rejects at startup
|
|
7988
|
+
* because they access a field of a number, string or boolean, judged by
|
|
7989
|
+
* the StoryVariables defaults as Spindle's validateRef() does. Every
|
|
7990
|
+
* occurrence is reported; defaults that are not literals are not checked.
|
|
7991
|
+
*/
|
|
7992
|
+
getPrimitiveFieldAccesses(uri) {
|
|
7993
|
+
const results = [];
|
|
7994
|
+
for (const ref of this.validatedRefsByUri.get(uri) ?? []) {
|
|
7995
|
+
const schema = this.declared.get(ref.baseName)?.schema;
|
|
7996
|
+
if (!schema) continue;
|
|
7997
|
+
const parts = ref.path.split(".");
|
|
7998
|
+
const found = findPrimitiveFieldAccess(schema, parts.slice(1));
|
|
7999
|
+
if (!found) continue;
|
|
8000
|
+
const owner = parts.slice(0, found.index + 1).join(".");
|
|
8001
|
+
const field = parts[found.index + 1];
|
|
8002
|
+
const start = ref.range.start.character + 1 + owner.length + 1;
|
|
8003
|
+
results.push({
|
|
8004
|
+
path: `$${owner}`,
|
|
8005
|
+
field,
|
|
8006
|
+
type: found.type,
|
|
8007
|
+
range: {
|
|
8008
|
+
start: { line: ref.range.start.line, character: start },
|
|
8009
|
+
end: { line: ref.range.start.line, character: start + field.length }
|
|
8010
|
+
}
|
|
8011
|
+
});
|
|
8012
|
+
}
|
|
8013
|
+
return results;
|
|
8014
|
+
}
|
|
7247
8015
|
/** Whether a StoryVariables passage has been parsed. */
|
|
7248
8016
|
hasStoryVariables() {
|
|
7249
8017
|
return this._hasStoryVariables;
|
|
@@ -7271,7 +8039,7 @@ var init_variable_tracker = __esm({
|
|
|
7271
8039
|
const results = [];
|
|
7272
8040
|
const seen = /* @__PURE__ */ new Set();
|
|
7273
8041
|
for (const u of usages) {
|
|
7274
|
-
if (
|
|
8042
|
+
if (!this.declaredTransient.has(u.baseName) && !seen.has(u.baseName)) {
|
|
7275
8043
|
seen.add(u.baseName);
|
|
7276
8044
|
results.push({ name: u.baseName, range: u.range });
|
|
7277
8045
|
}
|
|
@@ -7296,7 +8064,6 @@ var init_variable_tracker = __esm({
|
|
|
7296
8064
|
for (const [sigil, usages, declared] of sources) {
|
|
7297
8065
|
if (!usages) continue;
|
|
7298
8066
|
for (const u of usages) {
|
|
7299
|
-
if (!u.checked) continue;
|
|
7300
8067
|
const member = u.fullName.split(".")[1];
|
|
7301
8068
|
if (member === void 0) continue;
|
|
7302
8069
|
if (declared.get(u.baseName)?.type !== "array") continue;
|
|
@@ -7308,11 +8075,32 @@ var init_variable_tracker = __esm({
|
|
|
7308
8075
|
}
|
|
7309
8076
|
/** Get variables declared with null values in StoryVariables. */
|
|
7310
8077
|
getNullDeclarations() {
|
|
7311
|
-
return this._nullDeclarations;
|
|
8078
|
+
return [...this._nullDeclarations, ...this._variablesCheck.nestedNulls];
|
|
8079
|
+
}
|
|
8080
|
+
/** StoryVariables lines that stop Spindle from starting, apart from null values. */
|
|
8081
|
+
getInvalidDeclarations() {
|
|
8082
|
+
return this._variablesCheck.problems;
|
|
8083
|
+
}
|
|
8084
|
+
/**
|
|
8085
|
+
* StoryTransients lines that stop Spindle from starting, apart from null
|
|
8086
|
+
* values, and the names it declares that StoryVariables declares too.
|
|
8087
|
+
*/
|
|
8088
|
+
getInvalidTransientDeclarations() {
|
|
8089
|
+
const collisions = [];
|
|
8090
|
+
if (this._hasStoryVariables) {
|
|
8091
|
+
for (const [name, range2] of this._transientsCheck.names) {
|
|
8092
|
+
if (!this._variablesCheck.names.has(name)) continue;
|
|
8093
|
+
collisions.push({
|
|
8094
|
+
message: `StoryTransients: Variable "${name}" is already declared in StoryVariables. Names must be unique across scopes.`,
|
|
8095
|
+
range: range2
|
|
8096
|
+
});
|
|
8097
|
+
}
|
|
8098
|
+
}
|
|
8099
|
+
return [...this._transientsCheck.problems, ...collisions].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
7312
8100
|
}
|
|
7313
8101
|
/** Get transient variables declared with null values in StoryTransients. */
|
|
7314
8102
|
getNullTransientDeclarations() {
|
|
7315
|
-
return this._nullTransientDeclarations;
|
|
8103
|
+
return [...this._nullTransientDeclarations, ...this._transientsCheck.nestedNulls];
|
|
7316
8104
|
}
|
|
7317
8105
|
};
|
|
7318
8106
|
}
|
|
@@ -7513,11 +8301,11 @@ function discoverMacrosFromSource(source) {
|
|
|
7513
8301
|
}
|
|
7514
8302
|
return macros;
|
|
7515
8303
|
}
|
|
7516
|
-
function discoverMacrosFromStoryInit(
|
|
8304
|
+
function discoverMacrosFromStoryInit(passageContent3) {
|
|
7517
8305
|
const macros = [];
|
|
7518
8306
|
doBlockRegex.lastIndex = 0;
|
|
7519
8307
|
let doMatch;
|
|
7520
|
-
while ((doMatch = doBlockRegex.exec(
|
|
8308
|
+
while ((doMatch = doBlockRegex.exec(passageContent3)) !== null) {
|
|
7521
8309
|
const jsContent = doMatch[1];
|
|
7522
8310
|
const found = discoverMacrosFromSource(jsContent);
|
|
7523
8311
|
macros.push(...found);
|
|
@@ -14924,8 +15712,11 @@ function isExcludedMacroSource(path2, root) {
|
|
|
14924
15712
|
return (root ? relative(root, path2) : path2).split(/[\\/]/).some(isExcludedSegment);
|
|
14925
15713
|
}
|
|
14926
15714
|
function findMacroSourceFiles(root) {
|
|
15715
|
+
return findProjectFiles(root, MACRO_SOURCE_GLOB);
|
|
15716
|
+
}
|
|
15717
|
+
function findProjectFiles(root, pattern) {
|
|
14927
15718
|
const excluded = (p) => isExcludedMacroSource(p.fullpath(), root);
|
|
14928
|
-
return glob(
|
|
15719
|
+
return glob(pattern, {
|
|
14929
15720
|
cwd: root,
|
|
14930
15721
|
absolute: true,
|
|
14931
15722
|
nodir: true,
|
|
@@ -14975,6 +15766,82 @@ var init_macro_sources = __esm({
|
|
|
14975
15766
|
}
|
|
14976
15767
|
});
|
|
14977
15768
|
|
|
15769
|
+
// src/core/workspace/story-format.ts
|
|
15770
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
15771
|
+
function isSpindleFormatName(name) {
|
|
15772
|
+
return name.trim().toLowerCase() === "spindle";
|
|
15773
|
+
}
|
|
15774
|
+
function readStoryDataFormat(content) {
|
|
15775
|
+
let data;
|
|
15776
|
+
try {
|
|
15777
|
+
data = JSON.parse(content);
|
|
15778
|
+
} catch {
|
|
15779
|
+
return void 0;
|
|
15780
|
+
}
|
|
15781
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return void 0;
|
|
15782
|
+
const format = data.format;
|
|
15783
|
+
if (typeof format !== "string") return void 0;
|
|
15784
|
+
return format.trim() || void 0;
|
|
15785
|
+
}
|
|
15786
|
+
function resolveStoryFormat(declared) {
|
|
15787
|
+
let foreign;
|
|
15788
|
+
for (const name of declared) {
|
|
15789
|
+
if (name === void 0) continue;
|
|
15790
|
+
if (isSpindleFormatName(name)) return { name: name.trim(), isSpindle: true };
|
|
15791
|
+
foreign ??= name.trim();
|
|
15792
|
+
}
|
|
15793
|
+
return foreign === void 0 ? UNDECLARED_STORY_FORMAT : { name: foreign, isSpindle: false };
|
|
15794
|
+
}
|
|
15795
|
+
function passageContent(passage, text) {
|
|
15796
|
+
return text.split("\n").slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
|
|
15797
|
+
}
|
|
15798
|
+
function storyDataFormats(passages, getText) {
|
|
15799
|
+
const formats = [];
|
|
15800
|
+
for (const passage of passages) {
|
|
15801
|
+
if (passage.name !== "StoryData") continue;
|
|
15802
|
+
const text = getText(passage.uri);
|
|
15803
|
+
formats.push(text === void 0 ? void 0 : readStoryDataFormat(passageContent(passage, text)));
|
|
15804
|
+
}
|
|
15805
|
+
return formats;
|
|
15806
|
+
}
|
|
15807
|
+
function storyFormatOfTexts(texts) {
|
|
15808
|
+
const index = new PassageIndex();
|
|
15809
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
15810
|
+
for (const text of texts) {
|
|
15811
|
+
if (!text.includes("StoryData")) continue;
|
|
15812
|
+
const uri = `story-format:${byUri.size}`;
|
|
15813
|
+
byUri.set(uri, text);
|
|
15814
|
+
index.rebuild(uri, text);
|
|
15815
|
+
}
|
|
15816
|
+
const formats = storyDataFormats(index.getAllPassages(), (uri) => byUri.get(uri));
|
|
15817
|
+
return { ...resolveStoryFormat(formats), hasStoryData: formats.length > 0 };
|
|
15818
|
+
}
|
|
15819
|
+
async function findStoryFormat(texts, dir) {
|
|
15820
|
+
const own = storyFormatOfTexts(texts);
|
|
15821
|
+
if (own.hasStoryData) return { name: own.name, isSpindle: own.isSpindle };
|
|
15822
|
+
const projectTexts = [];
|
|
15823
|
+
for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
|
|
15824
|
+
try {
|
|
15825
|
+
projectTexts.push(readFileSync5(file2, "utf-8"));
|
|
15826
|
+
} catch {
|
|
15827
|
+
}
|
|
15828
|
+
}
|
|
15829
|
+
const { name, isSpindle } = storyFormatOfTexts(projectTexts);
|
|
15830
|
+
return { name, isSpindle };
|
|
15831
|
+
}
|
|
15832
|
+
function skippedFormatNote(format) {
|
|
15833
|
+
return `Skipped: story format is ${format.name}, not Spindle`;
|
|
15834
|
+
}
|
|
15835
|
+
var UNDECLARED_STORY_FORMAT;
|
|
15836
|
+
var init_story_format = __esm({
|
|
15837
|
+
"src/core/workspace/story-format.ts"() {
|
|
15838
|
+
"use strict";
|
|
15839
|
+
init_passage_index();
|
|
15840
|
+
init_macro_sources();
|
|
15841
|
+
UNDECLARED_STORY_FORMAT = { name: void 0, isSpindle: true };
|
|
15842
|
+
}
|
|
15843
|
+
});
|
|
15844
|
+
|
|
14978
15845
|
// src/macro-supplements.json
|
|
14979
15846
|
var macro_supplements_default;
|
|
14980
15847
|
var init_macro_supplements = __esm({
|
|
@@ -15004,9 +15871,6 @@ var init_macro_supplements = __esm({
|
|
|
15004
15871
|
},
|
|
15005
15872
|
for: {
|
|
15006
15873
|
name: "for",
|
|
15007
|
-
children: [
|
|
15008
|
-
{ name: "stop" }
|
|
15009
|
-
],
|
|
15010
15874
|
container: true,
|
|
15011
15875
|
description: "Loop block. Usage: `{for @item of $list}...{/for}` or `{for @item, @index of $list}...{/for}`",
|
|
15012
15876
|
skipArgs: true
|
|
@@ -15019,8 +15883,8 @@ var init_macro_supplements = __esm({
|
|
|
15019
15883
|
},
|
|
15020
15884
|
stop: {
|
|
15021
15885
|
name: "stop",
|
|
15022
|
-
parents: ["
|
|
15023
|
-
description: "
|
|
15886
|
+
parents: ["repeat"],
|
|
15887
|
+
description: "Stops the enclosing `{repeat}` loop. It has no effect anywhere else, including in a `{for}` loop.\n\nUsage: `{stop}`",
|
|
15024
15888
|
parameters: []
|
|
15025
15889
|
},
|
|
15026
15890
|
switch: {
|
|
@@ -15089,8 +15953,8 @@ var init_macro_supplements = __esm({
|
|
|
15089
15953
|
{ name: "next" }
|
|
15090
15954
|
],
|
|
15091
15955
|
container: true,
|
|
15092
|
-
description: "Displays its contents after a specified delay.\n\nUsage:\n\n```\n{timed delay}\n ...\n {next delay}\n ...\n{/timed}\n```",
|
|
15093
|
-
parameters: ["text"]
|
|
15956
|
+
description: "Displays its contents after a specified delay (immediately when omitted).\n\nUsage:\n\n```\n{timed delay}\n ...\n {next delay}\n ...\n{/timed}\n```",
|
|
15957
|
+
parameters: ["text", ""]
|
|
15094
15958
|
},
|
|
15095
15959
|
repeat: {
|
|
15096
15960
|
name: "repeat",
|
|
@@ -15284,6 +16148,7 @@ var init_workspace_model = __esm({
|
|
|
15284
16148
|
init_macro_parser();
|
|
15285
16149
|
init_macro_discovery();
|
|
15286
16150
|
init_macro_sources();
|
|
16151
|
+
init_story_format();
|
|
15287
16152
|
init_macro_supplements();
|
|
15288
16153
|
WorkspaceModel = class extends EventEmitter3 {
|
|
15289
16154
|
documents;
|
|
@@ -15293,6 +16158,8 @@ var init_workspace_model = __esm({
|
|
|
15293
16158
|
widgets;
|
|
15294
16159
|
/** True after initialize() has completed (full workspace scan done). */
|
|
15295
16160
|
initialized = false;
|
|
16161
|
+
/** The story format declared by StoryData, updated with the passages. */
|
|
16162
|
+
format = UNDECLARED_STORY_FORMAT;
|
|
15296
16163
|
debounceTimer = null;
|
|
15297
16164
|
DEBOUNCE_MS = 200;
|
|
15298
16165
|
/** Bound listeners for cleanup in dispose(). */
|
|
@@ -15347,6 +16214,22 @@ var init_workspace_model = __esm({
|
|
|
15347
16214
|
isContainer(name) {
|
|
15348
16215
|
return this.macros.isBlock(name) || (this.widgets.getWidget(name)?.block ?? false);
|
|
15349
16216
|
}
|
|
16217
|
+
/**
|
|
16218
|
+
* The story format named by the project's StoryData passage (trimmed), or
|
|
16219
|
+
* undefined if no StoryData passage names one.
|
|
16220
|
+
*/
|
|
16221
|
+
get storyFormat() {
|
|
16222
|
+
return this.format.name;
|
|
16223
|
+
}
|
|
16224
|
+
/**
|
|
16225
|
+
* Whether Spindle semantics apply to this project. False only if a
|
|
16226
|
+
* StoryData passage names another story format (SugarCube, Harlowe, ...)
|
|
16227
|
+
* and none names Spindle; a missing or unreadable StoryData counts as
|
|
16228
|
+
* Spindle.
|
|
16229
|
+
*/
|
|
16230
|
+
isSpindleProject() {
|
|
16231
|
+
return this.format.isSpindle;
|
|
16232
|
+
}
|
|
15350
16233
|
/** Clean up listeners and timers. */
|
|
15351
16234
|
dispose() {
|
|
15352
16235
|
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
@@ -15366,6 +16249,7 @@ var init_workspace_model = __esm({
|
|
|
15366
16249
|
}
|
|
15367
16250
|
this.refreshDiscoveredMacros();
|
|
15368
16251
|
this.cascade();
|
|
16252
|
+
this.updateStoryFormat();
|
|
15369
16253
|
this.emit("documentChanged", uri);
|
|
15370
16254
|
this.emit("passagesUpdated", uri);
|
|
15371
16255
|
this.scheduleModelReady();
|
|
@@ -15376,6 +16260,7 @@ var init_workspace_model = __esm({
|
|
|
15376
16260
|
this.variables.removeDocument(uri);
|
|
15377
16261
|
this.refreshDiscoveredMacros();
|
|
15378
16262
|
this.cascade();
|
|
16263
|
+
this.updateStoryFormat();
|
|
15379
16264
|
this.emit("documentClosed", uri);
|
|
15380
16265
|
this.emit("passagesUpdated", uri);
|
|
15381
16266
|
this.scheduleModelReady();
|
|
@@ -15390,6 +16275,21 @@ var init_workspace_model = __esm({
|
|
|
15390
16275
|
}
|
|
15391
16276
|
this.refreshDiscoveredMacros();
|
|
15392
16277
|
this.cascade();
|
|
16278
|
+
this.updateStoryFormat();
|
|
16279
|
+
}
|
|
16280
|
+
/**
|
|
16281
|
+
* Re-read the story format from the StoryData passages after the passage
|
|
16282
|
+
* index changed, emitting 'storyFormatChanged' if it differs. Called after
|
|
16283
|
+
* the cascade, so listeners see up-to-date indices.
|
|
16284
|
+
*/
|
|
16285
|
+
updateStoryFormat() {
|
|
16286
|
+
const format = resolveStoryFormat(storyDataFormats(
|
|
16287
|
+
this.passages.getAllPassages(),
|
|
16288
|
+
(uri) => this.documents.getText(uri)
|
|
16289
|
+
));
|
|
16290
|
+
if (format.name === this.format.name && format.isSpindle === this.format.isSpindle) return;
|
|
16291
|
+
this.format = format;
|
|
16292
|
+
this.emit("storyFormatChanged", format);
|
|
15393
16293
|
}
|
|
15394
16294
|
/**
|
|
15395
16295
|
* Re-run static macro discovery over the workspace and replace the
|
|
@@ -15505,6 +16405,8 @@ var init_diagnostic_codes = __esm({
|
|
|
15505
16405
|
DiagnosticCode = {
|
|
15506
16406
|
UndefinedMacro: "SP100",
|
|
15507
16407
|
MalformedContainer: "SP101",
|
|
16408
|
+
MalformedElement: "SP102",
|
|
16409
|
+
UnevaluatedAttributeBlock: "SP103",
|
|
15508
16410
|
IllegalClosingTag: "SP104",
|
|
15509
16411
|
InvalidChildren: "SP107",
|
|
15510
16412
|
ExpectedNoArguments: "SP108",
|
|
@@ -15515,12 +16417,13 @@ var init_diagnostic_codes = __esm({
|
|
|
15515
16417
|
ChildMaxExceeded: "SP114",
|
|
15516
16418
|
ChildMinNotMet: "SP115",
|
|
15517
16419
|
UndeclaredVariable: "SP200",
|
|
15518
|
-
|
|
16420
|
+
PrimitiveFieldAccess: "SP201",
|
|
15519
16421
|
NoStoryVariables: "SP202",
|
|
15520
16422
|
UndeclaredTransient: "SP203",
|
|
15521
16423
|
NullVariableValue: "SP204",
|
|
15522
16424
|
TemporaryAssignedInLoop: "SP205",
|
|
15523
16425
|
ArrayMemberAccess: "SP206",
|
|
16426
|
+
InvalidDeclaration: "SP207",
|
|
15524
16427
|
BrokenPassageLink: "SP300",
|
|
15525
16428
|
WidgetArgCountMismatch: "SP301",
|
|
15526
16429
|
IncludeWidgetPassage: "SP302",
|
|
@@ -15532,6 +16435,8 @@ var init_diagnostic_codes = __esm({
|
|
|
15532
16435
|
severityMap = {
|
|
15533
16436
|
SP100: "warning",
|
|
15534
16437
|
SP101: "error",
|
|
16438
|
+
SP102: "error",
|
|
16439
|
+
SP103: "warning",
|
|
15535
16440
|
SP104: "error",
|
|
15536
16441
|
SP107: "error",
|
|
15537
16442
|
SP108: "error",
|
|
@@ -15542,12 +16447,13 @@ var init_diagnostic_codes = __esm({
|
|
|
15542
16447
|
SP114: "error",
|
|
15543
16448
|
SP115: "error",
|
|
15544
16449
|
SP200: "error",
|
|
15545
|
-
SP201: "
|
|
16450
|
+
SP201: "error",
|
|
15546
16451
|
SP202: "info",
|
|
15547
16452
|
SP203: "warning",
|
|
15548
16453
|
SP204: "error",
|
|
15549
16454
|
SP205: "warning",
|
|
15550
16455
|
SP206: "warning",
|
|
16456
|
+
SP207: "error",
|
|
15551
16457
|
SP300: "warning",
|
|
15552
16458
|
SP301: "warning",
|
|
15553
16459
|
SP302: "warning",
|
|
@@ -15829,6 +16735,162 @@ var init_argument_lexer = __esm({
|
|
|
15829
16735
|
}
|
|
15830
16736
|
});
|
|
15831
16737
|
|
|
16738
|
+
// src/core/parsing/attribute-blocks.ts
|
|
16739
|
+
function findUnevaluatedBlocks(value, lookup) {
|
|
16740
|
+
const blocks = [];
|
|
16741
|
+
if (!value.includes("{")) return blocks;
|
|
16742
|
+
const code = createCodeScanner(value);
|
|
16743
|
+
let i = 0;
|
|
16744
|
+
while (i < value.length) {
|
|
16745
|
+
i = value.indexOf("{", i);
|
|
16746
|
+
if (i === -1) break;
|
|
16747
|
+
const block = readBlock(value, code, i);
|
|
16748
|
+
if (block.kind === "skip") {
|
|
16749
|
+
i = block.next;
|
|
16750
|
+
continue;
|
|
16751
|
+
}
|
|
16752
|
+
const inner = value.slice(i + 1, block.close);
|
|
16753
|
+
const head = MACRO_HEAD.exec(inner);
|
|
16754
|
+
if (head && lookup.isMacro(head[2])) {
|
|
16755
|
+
const closing = head[1] === "/";
|
|
16756
|
+
let end = block.close + 1;
|
|
16757
|
+
if (!closing && lookup.isContainer(head[2])) {
|
|
16758
|
+
end = containerEnd(value, code, end, head[2]) ?? end;
|
|
16759
|
+
}
|
|
16760
|
+
blocks.push({ start: i, end, kind: "macro", macro: closing ? `/${head[2]}` : head[2] });
|
|
16761
|
+
i = end;
|
|
16762
|
+
} else if (usesVariable(inner)) {
|
|
16763
|
+
blocks.push({ start: i, end: block.close + 1, kind: "expression" });
|
|
16764
|
+
i = block.close + 1;
|
|
16765
|
+
} else {
|
|
16766
|
+
i++;
|
|
16767
|
+
}
|
|
16768
|
+
}
|
|
16769
|
+
return blocks;
|
|
16770
|
+
}
|
|
16771
|
+
function readBlock(value, code, i) {
|
|
16772
|
+
if (i > 0 && value[i - 1] === "\\") return { kind: "skip", next: i + 1 };
|
|
16773
|
+
if (SIGILS.has(value[i + 1])) {
|
|
16774
|
+
const end = Math.max(plainClose(value, i + 1), code.closeBrace(i + 1));
|
|
16775
|
+
return { kind: "skip", next: end === -1 ? i + 1 : end + 1 };
|
|
16776
|
+
}
|
|
16777
|
+
const close = code.closeBrace(i + 1);
|
|
16778
|
+
return close === -1 ? { kind: "skip", next: i + 1 } : { kind: "text", close };
|
|
16779
|
+
}
|
|
16780
|
+
function plainClose(value, i) {
|
|
16781
|
+
let depth = 1;
|
|
16782
|
+
for (; i < value.length; i++) {
|
|
16783
|
+
if (value[i] === "{") depth++;
|
|
16784
|
+
else if (value[i] === "}" && --depth === 0) return i;
|
|
16785
|
+
}
|
|
16786
|
+
return -1;
|
|
16787
|
+
}
|
|
16788
|
+
function containerEnd(value, code, from, name) {
|
|
16789
|
+
const lower = name.toLowerCase();
|
|
16790
|
+
let depth = 1;
|
|
16791
|
+
let i = from;
|
|
16792
|
+
while (i < value.length) {
|
|
16793
|
+
i = value.indexOf("{", i);
|
|
16794
|
+
if (i === -1) break;
|
|
16795
|
+
const block = readBlock(value, code, i);
|
|
16796
|
+
if (block.kind === "skip") {
|
|
16797
|
+
i = block.next;
|
|
16798
|
+
continue;
|
|
16799
|
+
}
|
|
16800
|
+
const head = MACRO_HEAD.exec(value.slice(i + 1, block.close));
|
|
16801
|
+
if (head && head[2].toLowerCase() === lower) {
|
|
16802
|
+
depth += head[1] === "/" ? -1 : 1;
|
|
16803
|
+
if (depth === 0) return block.close + 1;
|
|
16804
|
+
i = block.close + 1;
|
|
16805
|
+
} else {
|
|
16806
|
+
i++;
|
|
16807
|
+
}
|
|
16808
|
+
}
|
|
16809
|
+
return void 0;
|
|
16810
|
+
}
|
|
16811
|
+
function usesVariable(inner) {
|
|
16812
|
+
const code = createCodeScanner(inner);
|
|
16813
|
+
let masked = "";
|
|
16814
|
+
let i = 0;
|
|
16815
|
+
while (i < inner.length) {
|
|
16816
|
+
const literal2 = code.literalEnd(i);
|
|
16817
|
+
if (literal2 !== -1) {
|
|
16818
|
+
masked += " ".repeat(literal2 - i);
|
|
16819
|
+
i = literal2;
|
|
16820
|
+
continue;
|
|
16821
|
+
}
|
|
16822
|
+
if (inner[i] === "{" && SIGILS.has(inner[i + 1])) {
|
|
16823
|
+
const close = code.closeBrace(i + 1);
|
|
16824
|
+
if (close !== -1) {
|
|
16825
|
+
masked += " ".repeat(close + 1 - i);
|
|
16826
|
+
i = close + 1;
|
|
16827
|
+
continue;
|
|
16828
|
+
}
|
|
16829
|
+
}
|
|
16830
|
+
masked += inner[i];
|
|
16831
|
+
i++;
|
|
16832
|
+
}
|
|
16833
|
+
return VARIABLE_REFERENCE.test(masked);
|
|
16834
|
+
}
|
|
16835
|
+
function conditionalExpression(source) {
|
|
16836
|
+
const match2 = IF_ELSE.exec(source);
|
|
16837
|
+
if (!match2) return null;
|
|
16838
|
+
const condition = match2[1].trim();
|
|
16839
|
+
const [whenTrue, whenFalse = ""] = [match2[2], match2[3]];
|
|
16840
|
+
if (!/^[$_@%]\w/.test(condition)) return null;
|
|
16841
|
+
if (/[`\r\n]/.test(condition) || /[\r\n]/.test(whenTrue + whenFalse)) return null;
|
|
16842
|
+
const code = createCodeScanner(condition);
|
|
16843
|
+
let operators = "";
|
|
16844
|
+
for (let i = 0; i < condition.length; ) {
|
|
16845
|
+
const literal2 = code.literalEnd(i);
|
|
16846
|
+
if (literal2 !== -1) {
|
|
16847
|
+
i = literal2;
|
|
16848
|
+
continue;
|
|
16849
|
+
}
|
|
16850
|
+
operators += condition[i];
|
|
16851
|
+
i++;
|
|
16852
|
+
}
|
|
16853
|
+
if (/(?:<<|>>)=/.test(operators)) return null;
|
|
16854
|
+
const rest = operators.replace(/\?\?|\?\./g, "").replace(/[!=]==?|[<>]=/g, "");
|
|
16855
|
+
if (/[?=,;]/.test(rest)) return null;
|
|
16856
|
+
const quote = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
16857
|
+
return `{${condition} ? ${quote(whenTrue)} : ${quote(whenFalse)}}`;
|
|
16858
|
+
}
|
|
16859
|
+
function printExpression(source) {
|
|
16860
|
+
const match2 = /^\{print\s+([^{}\r\n]*)\}$/.exec(source);
|
|
16861
|
+
if (!match2) return null;
|
|
16862
|
+
const expr = match2[1].trim();
|
|
16863
|
+
if (!/^[$_@%]\w/.test(expr)) return null;
|
|
16864
|
+
const code = createCodeScanner(expr);
|
|
16865
|
+
let depth = 0;
|
|
16866
|
+
for (let i = 0; i < expr.length; ) {
|
|
16867
|
+
const literal2 = code.literalEnd(i);
|
|
16868
|
+
if (literal2 !== -1) {
|
|
16869
|
+
i = literal2;
|
|
16870
|
+
continue;
|
|
16871
|
+
}
|
|
16872
|
+
const c3 = expr[i];
|
|
16873
|
+
if (c3 === "(" || c3 === "[") depth++;
|
|
16874
|
+
else if (c3 === ")" || c3 === "]") depth--;
|
|
16875
|
+
else if (c3 === ";" || c3 === "," && depth <= 0) return null;
|
|
16876
|
+
i++;
|
|
16877
|
+
}
|
|
16878
|
+
return SIMPLE_PATH.test(expr) && expr.includes(".") ? `{${expr} ?? ''}` : `{${expr}}`;
|
|
16879
|
+
}
|
|
16880
|
+
var SIGILS, MACRO_HEAD, VARIABLE_REFERENCE, IF_ELSE, SIMPLE_PATH;
|
|
16881
|
+
var init_attribute_blocks = __esm({
|
|
16882
|
+
"src/core/parsing/attribute-blocks.ts"() {
|
|
16883
|
+
"use strict";
|
|
16884
|
+
init_code_scanner();
|
|
16885
|
+
init_macro_parser();
|
|
16886
|
+
SIGILS = /* @__PURE__ */ new Set(["$", "_", "@", "%"]);
|
|
16887
|
+
MACRO_HEAD = new RegExp(String.raw`^(?:(\/)|${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)(?=\s|$|\{[$_@%])`);
|
|
16888
|
+
VARIABLE_REFERENCE = /(?:^|[^\w$.@%])[$_@%][A-Za-z_$]/;
|
|
16889
|
+
IF_ELSE = /^\{if\s+([^{}]*)\}([^{}]*?)(?:\{else\s*\}([^{}]*))?\{\/if\s*\}$/;
|
|
16890
|
+
SIMPLE_PATH = /^[$_@%][\w.]+$/;
|
|
16891
|
+
}
|
|
16892
|
+
});
|
|
16893
|
+
|
|
15832
16894
|
// src/core/parsing/widget-arguments.ts
|
|
15833
16895
|
function splitWidgetArguments(raw) {
|
|
15834
16896
|
const args2 = [];
|
|
@@ -16331,7 +17393,7 @@ var init_parameter_validator = __esm({
|
|
|
16331
17393
|
if (/^[_@%]/.test(name)) {
|
|
16332
17394
|
return new TypeError2(`Variable receiver must be a story variable ($name), got '${name}'`);
|
|
16333
17395
|
}
|
|
16334
|
-
if (
|
|
17396
|
+
if (!/^(?:\$\w|[A-Za-z_])[\w$]*(?:\.[\w$]+)*$/.test(name)) {
|
|
16335
17397
|
return new TypeError2(`Argument is not a valid variable receiver: '${name}'`);
|
|
16336
17398
|
}
|
|
16337
17399
|
return null;
|
|
@@ -16664,6 +17726,7 @@ var init_link_parser = __esm({
|
|
|
16664
17726
|
// src/plugins/diagnostics.ts
|
|
16665
17727
|
function computeDiagnostics(uri, workspace, options) {
|
|
16666
17728
|
if (!workspace.initialized) return [];
|
|
17729
|
+
if (!workspace.isSpindleProject()) return [];
|
|
16667
17730
|
try {
|
|
16668
17731
|
const text = workspace.documents.getText(uri);
|
|
16669
17732
|
if (text === void 0) return [];
|
|
@@ -16679,8 +17742,19 @@ function computeDiagnostics(uri, workspace, options) {
|
|
|
16679
17742
|
);
|
|
16680
17743
|
const allPassages = workspace.passages.getAllPassages();
|
|
16681
17744
|
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
17745
|
+
let elements = /* @__PURE__ */ new Map();
|
|
16682
17746
|
try {
|
|
16683
|
-
|
|
17747
|
+
const replay = replayElements(markupText, macros, passages, workspace);
|
|
17748
|
+
elements = replay.elements;
|
|
17749
|
+
diagnostics.push(...replay.errors);
|
|
17750
|
+
} catch {
|
|
17751
|
+
}
|
|
17752
|
+
try {
|
|
17753
|
+
validateAttributeBlocks(markupText, passages, workspace, diagnostics);
|
|
17754
|
+
} catch {
|
|
17755
|
+
}
|
|
17756
|
+
try {
|
|
17757
|
+
validateMacros(macros, elements, workspace, diagnostics);
|
|
16684
17758
|
} catch {
|
|
16685
17759
|
}
|
|
16686
17760
|
try {
|
|
@@ -16738,7 +17812,7 @@ function maskScriptAndStylesheetPassages(text, passages) {
|
|
|
16738
17812
|
}
|
|
16739
17813
|
return lines.join("\n");
|
|
16740
17814
|
}
|
|
16741
|
-
function validateMacros(macros, workspace, diagnostics) {
|
|
17815
|
+
function validateMacros(macros, elements, workspace, diagnostics) {
|
|
16742
17816
|
for (let curIndex = 0; curIndex < macros.length; curIndex++) {
|
|
16743
17817
|
const macro = macros[curIndex];
|
|
16744
17818
|
const info = workspace.macros.getMacro(macro.name);
|
|
@@ -16767,7 +17841,7 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16767
17841
|
));
|
|
16768
17842
|
}
|
|
16769
17843
|
if (info?.children && info.children.length > 0 && macro.open && macro.pair !== -1) {
|
|
16770
|
-
validateChildren(macros, curIndex, macro, info.children, workspace, diagnostics);
|
|
17844
|
+
validateChildren(macros, curIndex, macro, info.children, elements, workspace, diagnostics);
|
|
16771
17845
|
}
|
|
16772
17846
|
} else if (!macro.open) {
|
|
16773
17847
|
diagnostics.push(makeDiag(
|
|
@@ -16777,8 +17851,20 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16777
17851
|
));
|
|
16778
17852
|
}
|
|
16779
17853
|
if (info?.parents && info.parents.length > 0 && macro.open) {
|
|
16780
|
-
|
|
16781
|
-
|
|
17854
|
+
const parentList = info.parents.join(", ");
|
|
17855
|
+
if (DIRECT_CHILD_MACROS.has(macro.name.toLowerCase())) {
|
|
17856
|
+
const element = elements.get(curIndex);
|
|
17857
|
+
const enclosing = element === void 0 ? innermostContainer(macros, curIndex, workspace) : void 0;
|
|
17858
|
+
const parentSet = new Set(info.parents.map((p) => p.toLowerCase()));
|
|
17859
|
+
if (element !== void 0 || !enclosing || !parentSet.has(enclosing.name.toLowerCase())) {
|
|
17860
|
+
const where = element !== void 0 ? `, not inside <${element}>` : enclosing ? `, not inside {${enclosing.name}}` : "";
|
|
17861
|
+
diagnostics.push(makeDiag(
|
|
17862
|
+
macro.range,
|
|
17863
|
+
DiagnosticCode.InvalidChildren,
|
|
17864
|
+
`Invalid: {${macro.name}} can only be directly inside {${parentList}}${where}`
|
|
17865
|
+
));
|
|
17866
|
+
}
|
|
17867
|
+
} else if (!isInsideParent(macros, curIndex, info.parents, workspace)) {
|
|
16782
17868
|
diagnostics.push(makeDiag(
|
|
16783
17869
|
macro.range,
|
|
16784
17870
|
DiagnosticCode.InvalidChildren,
|
|
@@ -16788,6 +17874,146 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16788
17874
|
}
|
|
16789
17875
|
}
|
|
16790
17876
|
}
|
|
17877
|
+
function innermostContainer(macros, index, workspace) {
|
|
17878
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
17879
|
+
const candidate = macros[i];
|
|
17880
|
+
if (!candidate.open || candidate.pair <= index) continue;
|
|
17881
|
+
if (workspace.isContainer(candidate.name)) return candidate;
|
|
17882
|
+
}
|
|
17883
|
+
return void 0;
|
|
17884
|
+
}
|
|
17885
|
+
function isMarkupPassage(passage) {
|
|
17886
|
+
return !NON_MARKUP_PASSAGES.has(passage.name) && !isScriptOrStylesheetPassage(passage);
|
|
17887
|
+
}
|
|
17888
|
+
function replayElements(text, macros, passages, workspace) {
|
|
17889
|
+
const replay = { elements: /* @__PURE__ */ new Map(), errors: [] };
|
|
17890
|
+
if (!text.includes("<")) return replay;
|
|
17891
|
+
const lineStarts = buildLineStarts(text);
|
|
17892
|
+
const lineOffset = (line) => lineStarts[line] ?? text.length;
|
|
17893
|
+
const ordered = [...passages].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
17894
|
+
let m = 0;
|
|
17895
|
+
for (const passage of ordered) {
|
|
17896
|
+
const contentStart = lineOffset(passage.range.start.line + 1);
|
|
17897
|
+
const contentEnd = lineOffset(passage.range.end.line + 1);
|
|
17898
|
+
while (m < macros.length && positionToOffset(macros[m].range.start, lineStarts) < contentStart) m++;
|
|
17899
|
+
const first = m;
|
|
17900
|
+
while (m < macros.length && positionToOffset(macros[m].range.start, lineStarts) < contentEnd) m++;
|
|
17901
|
+
const content = text.slice(contentStart, contentEnd);
|
|
17902
|
+
if (!content.includes("<") || !isMarkupPassage(passage)) continue;
|
|
17903
|
+
replayPassage(macros, first, m, scanHtmlTags(content), contentStart, lineStarts, workspace, replay);
|
|
17904
|
+
}
|
|
17905
|
+
return replay;
|
|
17906
|
+
}
|
|
17907
|
+
function replayPassage(macros, from, to, scan, base, lineStarts, workspace, replay) {
|
|
17908
|
+
const { tags } = scan;
|
|
17909
|
+
const macroStart = (k) => positionToOffset(macros[k].range.start, lineStarts) - base;
|
|
17910
|
+
let certainUntil = scan.stoppedAt === -1 ? Infinity : scan.stoppedAt;
|
|
17911
|
+
const parsed = /* @__PURE__ */ new Set();
|
|
17912
|
+
for (let k = from; k < to; k++) parsed.add(macroStart(k));
|
|
17913
|
+
const unparsed = scan.macros.find((at) => !parsed.has(at));
|
|
17914
|
+
if (unparsed !== void 0) certainUntil = Math.min(certainUntil, unparsed);
|
|
17915
|
+
const stack = [];
|
|
17916
|
+
let t = 0;
|
|
17917
|
+
let s = 0;
|
|
17918
|
+
let lastMacroEnd = -1;
|
|
17919
|
+
const report2 = (range2, message) => {
|
|
17920
|
+
replay.errors.push(makeDiag(range2, DiagnosticCode.MalformedElement, `Malformed element: ${message}`));
|
|
17921
|
+
};
|
|
17922
|
+
const tagRange = (tag) => ({
|
|
17923
|
+
start: offsetToPosition(base + tag.start, lineStarts),
|
|
17924
|
+
end: offsetToPosition(base + tag.end, lineStarts)
|
|
17925
|
+
});
|
|
17926
|
+
const applyTags = (until) => {
|
|
17927
|
+
for (; t < tags.length; t++) {
|
|
17928
|
+
const tag = tags[t];
|
|
17929
|
+
const at = tag.kind === "close" ? tag.start : tag.end;
|
|
17930
|
+
if (at > until) break;
|
|
17931
|
+
if (tag.start >= certainUntil) return false;
|
|
17932
|
+
if (tag.start < lastMacroEnd) return false;
|
|
17933
|
+
if (tag.kind === "open") {
|
|
17934
|
+
stack.push({ element: tag });
|
|
17935
|
+
} else if (tag.kind === "close") {
|
|
17936
|
+
const top = stack[stack.length - 1];
|
|
17937
|
+
const found = `</${tag.name}>`;
|
|
17938
|
+
if (!top) {
|
|
17939
|
+
report2(tagRange(tag), `unexpected closing ${found}`);
|
|
17940
|
+
return false;
|
|
17941
|
+
}
|
|
17942
|
+
if ("macro" in top) {
|
|
17943
|
+
report2(tagRange(tag), `expected {/${macros[top.macro].name}} but found ${found}`);
|
|
17944
|
+
return false;
|
|
17945
|
+
}
|
|
17946
|
+
if (top.element.name.toLowerCase() !== tag.name.toLowerCase()) {
|
|
17947
|
+
report2(tagRange(tag), `expected </${top.element.name}> but found ${found}`);
|
|
17948
|
+
return false;
|
|
17949
|
+
}
|
|
17950
|
+
stack.pop();
|
|
17951
|
+
}
|
|
17952
|
+
}
|
|
17953
|
+
return true;
|
|
17954
|
+
};
|
|
17955
|
+
for (let k = from; k < to; k++) {
|
|
17956
|
+
const macro = macros[k];
|
|
17957
|
+
const start = macroStart(k);
|
|
17958
|
+
if (!applyTags(start)) return;
|
|
17959
|
+
if (start >= certainUntil) return;
|
|
17960
|
+
while (s < scan.macros.length && scan.macros[s] < start) s++;
|
|
17961
|
+
if (scan.macros[s] !== start) return;
|
|
17962
|
+
lastMacroEnd = positionToOffset(macro.range.end, lineStarts) - base;
|
|
17963
|
+
const top = stack[stack.length - 1];
|
|
17964
|
+
if (top && "element" in top) replay.elements.set(k, top.element.name);
|
|
17965
|
+
if (!workspace.isContainer(macro.name)) continue;
|
|
17966
|
+
if (macro.pair === -1) return;
|
|
17967
|
+
if (macro.open) {
|
|
17968
|
+
stack.push({ macro: k });
|
|
17969
|
+
continue;
|
|
17970
|
+
}
|
|
17971
|
+
const opener = stack.pop();
|
|
17972
|
+
if (opener && "element" in opener) {
|
|
17973
|
+
report2(macro.range, `expected </${opener.element.name}> but found {/${macro.name}}`);
|
|
17974
|
+
return;
|
|
17975
|
+
}
|
|
17976
|
+
if (!opener || opener.macro !== macro.pair) return;
|
|
17977
|
+
}
|
|
17978
|
+
if (!applyTags(Infinity) || certainUntil !== Infinity) return;
|
|
17979
|
+
for (const entry of stack) {
|
|
17980
|
+
if ("element" in entry) report2(tagRange(entry.element), `unclosed <${entry.element.name}>`);
|
|
17981
|
+
}
|
|
17982
|
+
}
|
|
17983
|
+
function validateAttributeBlocks(text, passages, workspace, diagnostics) {
|
|
17984
|
+
if (!text.includes("<")) return;
|
|
17985
|
+
const lineStarts = buildLineStarts(text);
|
|
17986
|
+
const lineOffset = (line) => lineStarts[line] ?? text.length;
|
|
17987
|
+
const lookup = {
|
|
17988
|
+
isMacro: (name) => workspace.macros.getMacro(name) !== void 0 || workspace.widgets.getWidget(name) !== void 0,
|
|
17989
|
+
isContainer: (name) => workspace.isContainer(name)
|
|
17990
|
+
};
|
|
17991
|
+
for (const passage of passages) {
|
|
17992
|
+
if (!isMarkupPassage(passage)) continue;
|
|
17993
|
+
const base = lineOffset(passage.range.start.line + 1);
|
|
17994
|
+
const content = text.slice(base, lineOffset(passage.range.end.line + 1));
|
|
17995
|
+
if (!content.includes("<") || !content.includes("{")) continue;
|
|
17996
|
+
let rawText;
|
|
17997
|
+
for (const tag of scanHtmlTags(content).tags) {
|
|
17998
|
+
const name = tag.name.toLowerCase();
|
|
17999
|
+
if (rawText !== void 0) {
|
|
18000
|
+
if (tag.kind === "close" && name === rawText) rawText = void 0;
|
|
18001
|
+
continue;
|
|
18002
|
+
}
|
|
18003
|
+
for (const [start, end] of tag.values ?? []) {
|
|
18004
|
+
for (const block of findUnevaluatedBlocks(content.slice(start, end), lookup)) {
|
|
18005
|
+
const range2 = {
|
|
18006
|
+
start: offsetToPosition(base + start + block.start, lineStarts),
|
|
18007
|
+
end: offsetToPosition(base + start + block.end, lineStarts)
|
|
18008
|
+
};
|
|
18009
|
+
const message = block.kind === "macro" ? MACRO_IN_ATTRIBUTE(block.macro) : EXPRESSION_IN_ATTRIBUTE;
|
|
18010
|
+
diagnostics.push(makeDiag(range2, DiagnosticCode.UnevaluatedAttributeBlock, message));
|
|
18011
|
+
}
|
|
18012
|
+
}
|
|
18013
|
+
if (tag.kind === "open" && RAW_TEXT_ELEMENTS.has(name)) rawText = name;
|
|
18014
|
+
}
|
|
18015
|
+
}
|
|
18016
|
+
}
|
|
16791
18017
|
function isInsideParent(macros, index, parents, workspace) {
|
|
16792
18018
|
const parentSet = new Set(parents.map((p) => p.toLowerCase()));
|
|
16793
18019
|
for (let i = index - 1; i >= 0; i--) {
|
|
@@ -16803,18 +18029,18 @@ function isInsideParent(macros, index, parents, workspace) {
|
|
|
16803
18029
|
}
|
|
16804
18030
|
return false;
|
|
16805
18031
|
}
|
|
16806
|
-
function validateChildren(macros, curIndex, parentMacro, childConstraints, workspace, diagnostics) {
|
|
18032
|
+
function validateChildren(macros, curIndex, parentMacro, childConstraints, elements, workspace, diagnostics) {
|
|
16807
18033
|
const children = /* @__PURE__ */ Object.create(null);
|
|
16808
18034
|
const startIndex = curIndex + 1;
|
|
16809
18035
|
const endIndex = parentMacro.pair;
|
|
16810
18036
|
for (let i = startIndex; i < endIndex; i++) {
|
|
16811
18037
|
const child = macros[i];
|
|
16812
|
-
|
|
16813
|
-
if (!childInfo) continue;
|
|
16814
|
-
if (childInfo.block && child.open && child.pair !== -1) {
|
|
18038
|
+
if (child.open && child.pair !== -1 && workspace.isContainer(child.name)) {
|
|
16815
18039
|
i = child.pair;
|
|
16816
18040
|
continue;
|
|
16817
18041
|
}
|
|
18042
|
+
if (elements.has(i)) continue;
|
|
18043
|
+
if (!workspace.macros.getMacro(child.name)) continue;
|
|
16818
18044
|
const childKey = child.name.toLowerCase();
|
|
16819
18045
|
if (childConstraints.some((c3) => c3.name.toLowerCase() === childKey)) {
|
|
16820
18046
|
children[childKey] = (children[childKey] ?? 0) + 1;
|
|
@@ -16929,15 +18155,25 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
16929
18155
|
`Variable '$${u.name}' is not declared in StoryVariables`
|
|
16930
18156
|
));
|
|
16931
18157
|
}
|
|
18158
|
+
for (const a of workspace.variables.getPrimitiveFieldAccesses(uri)) {
|
|
18159
|
+
diagnostics.push(makeDiag(
|
|
18160
|
+
a.range,
|
|
18161
|
+
DiagnosticCode.PrimitiveFieldAccess,
|
|
18162
|
+
`Cannot access field "${a.field}" on ${a.path} (type: ${a.type}). Spindle checks field access against the StoryVariables defaults and will not start the story.`
|
|
18163
|
+
));
|
|
18164
|
+
}
|
|
16932
18165
|
const storyVars = workspace.passages.getStoryVariables();
|
|
16933
18166
|
if (storyVars && storyVars.uri === uri) {
|
|
16934
18167
|
for (const nd of workspace.variables.getNullDeclarations()) {
|
|
16935
18168
|
diagnostics.push(makeDiag(
|
|
16936
18169
|
nd.range,
|
|
16937
18170
|
DiagnosticCode.NullVariableValue,
|
|
16938
|
-
|
|
18171
|
+
`${nullSubject(nd)} is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
16939
18172
|
));
|
|
16940
18173
|
}
|
|
18174
|
+
for (const d of workspace.variables.getInvalidDeclarations()) {
|
|
18175
|
+
diagnostics.push(makeDiag(d.range, DiagnosticCode.InvalidDeclaration, `${d.message} ${WILL_NOT_START}`));
|
|
18176
|
+
}
|
|
16941
18177
|
}
|
|
16942
18178
|
}
|
|
16943
18179
|
if (workspace.variables.hasStoryTransients()) {
|
|
@@ -16955,12 +18191,20 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
16955
18191
|
diagnostics.push(makeDiag(
|
|
16956
18192
|
nd.range,
|
|
16957
18193
|
DiagnosticCode.NullVariableValue,
|
|
16958
|
-
|
|
18194
|
+
`${nullSubject(nd)} is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
16959
18195
|
));
|
|
16960
18196
|
}
|
|
18197
|
+
for (const d of workspace.variables.getInvalidTransientDeclarations()) {
|
|
18198
|
+
diagnostics.push(makeDiag(d.range, DiagnosticCode.InvalidDeclaration, `${d.message} ${WILL_NOT_START}`));
|
|
18199
|
+
}
|
|
16961
18200
|
}
|
|
16962
18201
|
}
|
|
16963
18202
|
}
|
|
18203
|
+
function nullSubject(nd) {
|
|
18204
|
+
const variable = nd.sigil === "%" ? "Transient variable" : "Variable";
|
|
18205
|
+
if (nd.field?.length) return `Field '${nd.sigil}${[nd.name, ...nd.field].join(".")}'`;
|
|
18206
|
+
return `${variable} '${nd.sigil}${nd.name}'`;
|
|
18207
|
+
}
|
|
16964
18208
|
function validateLinks(text, passages, passageNames, diagnostics) {
|
|
16965
18209
|
const links = parseLinks(text);
|
|
16966
18210
|
for (const link of links) {
|
|
@@ -17177,7 +18421,7 @@ function widgetDefinitionNames(content) {
|
|
|
17177
18421
|
while ((m = re.exec(content)) !== null) names.push(m[2]);
|
|
17178
18422
|
return names;
|
|
17179
18423
|
}
|
|
17180
|
-
function
|
|
18424
|
+
function passageContent2(passage, workspace) {
|
|
17181
18425
|
const text = workspace.documents.getText(passage.uri) ?? "";
|
|
17182
18426
|
return text.split("\n").slice(passage.range.start.line + 1, passage.range.end.line + 1).join("\n");
|
|
17183
18427
|
}
|
|
@@ -17190,7 +18434,7 @@ function validateWidgetIncludes(text, macros, workspace, diagnostics) {
|
|
|
17190
18434
|
if (target === null) continue;
|
|
17191
18435
|
const passage = workspace.passages.getPassage(target);
|
|
17192
18436
|
if (!passage?.tags?.includes("widget")) continue;
|
|
17193
|
-
const widgets = widgetDefinitionNames(
|
|
18437
|
+
const widgets = widgetDefinitionNames(passageContent2(passage, workspace));
|
|
17194
18438
|
if (widgets.length === 0) continue;
|
|
17195
18439
|
const list = widgets.map((w) => `{${w}}`).join(", ");
|
|
17196
18440
|
diagnostics.push(makeDiag(
|
|
@@ -17263,18 +18507,32 @@ function toLspDiagnostic(d) {
|
|
|
17263
18507
|
message: d.message
|
|
17264
18508
|
};
|
|
17265
18509
|
}
|
|
17266
|
-
var DEFERRED_CONTAINERS, EXPRESSION_BUILTINS2, diagnosticsPlugin;
|
|
18510
|
+
var DIRECT_CHILD_MACROS, NON_MARKUP_PASSAGES, RAW_TEXT_ELEMENTS, MACRO_IN_ATTRIBUTE, EXPRESSION_IN_ATTRIBUTE, WILL_NOT_START, DEFERRED_CONTAINERS, EXPRESSION_BUILTINS2, diagnosticsPlugin;
|
|
17267
18511
|
var init_diagnostics = __esm({
|
|
17268
18512
|
"src/plugins/diagnostics.ts"() {
|
|
17269
18513
|
"use strict";
|
|
17270
18514
|
init_diagnostic_codes();
|
|
17271
18515
|
init_macro_parser();
|
|
17272
18516
|
init_argument_lexer();
|
|
18517
|
+
init_html_scanner();
|
|
18518
|
+
init_attribute_blocks();
|
|
17273
18519
|
init_widget_arguments();
|
|
17274
18520
|
init_parameter_validator();
|
|
17275
18521
|
init_link_parser();
|
|
17276
18522
|
init_passage_parser();
|
|
17277
18523
|
init_macro_sources();
|
|
18524
|
+
DIRECT_CHILD_MACROS = /* @__PURE__ */ new Set(["elseif", "else", "case", "default", "next", "option"]);
|
|
18525
|
+
NON_MARKUP_PASSAGES = /* @__PURE__ */ new Set([
|
|
18526
|
+
"StoryTitle",
|
|
18527
|
+
"StoryData",
|
|
18528
|
+
"StoryVariables",
|
|
18529
|
+
"StoryTransients",
|
|
18530
|
+
"SaveTitle"
|
|
18531
|
+
]);
|
|
18532
|
+
RAW_TEXT_ELEMENTS = /* @__PURE__ */ new Set(["script", "style"]);
|
|
18533
|
+
MACRO_IN_ATTRIBUTE = (macro) => `Macros are not evaluated inside HTML attributes, so Spindle outputs this {${macro}} as text. Use an expression such as {$x ? 'a' : 'b'}`;
|
|
18534
|
+
EXPRESSION_IN_ATTRIBUTE = "Spindle evaluates {\u2026} in an HTML attribute only when $, _, @ or % follows the brace, so it outputs this block as text. Start the expression with a variable, as in {$x ? 'a' : 'b'}";
|
|
18535
|
+
WILL_NOT_START = "Spindle will not start the story.";
|
|
17278
18536
|
DEFERRED_CONTAINERS = /* @__PURE__ */ new Set(["link", "button"]);
|
|
17279
18537
|
EXPRESSION_BUILTINS2 = /* @__PURE__ */ new Set([
|
|
17280
18538
|
"currentPassage",
|
|
@@ -17302,12 +18560,14 @@ var init_diagnostics = __esm({
|
|
|
17302
18560
|
diagnostics: diags.filter(isEnabled).map((d) => toLspDiagnostic(d))
|
|
17303
18561
|
});
|
|
17304
18562
|
};
|
|
17305
|
-
|
|
18563
|
+
const publishAll = () => {
|
|
17306
18564
|
for (const uri of ctx.workspace.documents.getUris()) {
|
|
17307
18565
|
if (isMacroSource(uri)) continue;
|
|
17308
18566
|
publishFor(uri);
|
|
17309
18567
|
}
|
|
17310
|
-
}
|
|
18568
|
+
};
|
|
18569
|
+
ctx.workspace.on("modelReady", publishAll);
|
|
18570
|
+
ctx.workspace.on("storyFormatChanged", publishAll);
|
|
17311
18571
|
ctx.workspace.on("documentChanged", publishFor);
|
|
17312
18572
|
ctx.workspace.on("documentClosed", (uri) => {
|
|
17313
18573
|
ctx.connection.sendDiagnostics({ uri, diagnostics: [] });
|
|
@@ -17484,7 +18744,7 @@ var check_exports = {};
|
|
|
17484
18744
|
__export(check_exports, {
|
|
17485
18745
|
runCheck: () => runCheck
|
|
17486
18746
|
});
|
|
17487
|
-
import { readFileSync as
|
|
18747
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
17488
18748
|
import { resolve as resolve2 } from "node:path";
|
|
17489
18749
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
17490
18750
|
function parseArgs(args2) {
|
|
@@ -17605,16 +18865,22 @@ async function runCheck(args2) {
|
|
|
17605
18865
|
}
|
|
17606
18866
|
projectConfig = loadConfigFromDisk(configRoot);
|
|
17607
18867
|
}
|
|
17608
|
-
const workspace = new WorkspaceModel({ workspaceRoot: commonDir });
|
|
17609
18868
|
const fileContents = /* @__PURE__ */ new Map();
|
|
17610
18869
|
for (const filePath of uniqueFiles) {
|
|
17611
18870
|
try {
|
|
17612
|
-
const text =
|
|
18871
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
17613
18872
|
const uri = pathToFileURL3(filePath).toString();
|
|
17614
18873
|
fileContents.set(uri, text);
|
|
17615
18874
|
} catch {
|
|
17616
18875
|
}
|
|
17617
18876
|
}
|
|
18877
|
+
const storyFormat = await findStoryFormat(fileContents.values(), commonDir);
|
|
18878
|
+
if (!storyFormat.isSpindle) {
|
|
18879
|
+
console.error(skippedFormatNote(storyFormat));
|
|
18880
|
+
report(options.format, []);
|
|
18881
|
+
return 0;
|
|
18882
|
+
}
|
|
18883
|
+
const workspace = new WorkspaceModel({ workspaceRoot: commonDir });
|
|
17618
18884
|
const workspaceContents = new Map(fileContents);
|
|
17619
18885
|
await addProjectMacroSources(workspaceContents, commonDir);
|
|
17620
18886
|
workspace.initialize(workspaceContents);
|
|
@@ -17639,19 +18905,22 @@ async function runCheck(args2) {
|
|
|
17639
18905
|
}
|
|
17640
18906
|
}
|
|
17641
18907
|
}
|
|
17642
|
-
|
|
18908
|
+
report(options.format, allResults);
|
|
18909
|
+
workspace.dispose();
|
|
18910
|
+
return hasErrors ? 1 : 0;
|
|
18911
|
+
}
|
|
18912
|
+
function report(format, results) {
|
|
18913
|
+
switch (format) {
|
|
17643
18914
|
case "pretty":
|
|
17644
|
-
console.log(formatPretty(
|
|
18915
|
+
console.log(formatPretty(results));
|
|
17645
18916
|
break;
|
|
17646
18917
|
case "json":
|
|
17647
|
-
console.log(formatJson(
|
|
18918
|
+
console.log(formatJson(results));
|
|
17648
18919
|
break;
|
|
17649
18920
|
case "sarif":
|
|
17650
|
-
console.log(formatSarif(
|
|
18921
|
+
console.log(formatSarif(results));
|
|
17651
18922
|
break;
|
|
17652
18923
|
}
|
|
17653
|
-
workspace.dispose();
|
|
17654
|
-
return hasErrors ? 1 : 0;
|
|
17655
18924
|
}
|
|
17656
18925
|
var SEVERITY_ORDER;
|
|
17657
18926
|
var init_check = __esm({
|
|
@@ -17662,6 +18931,7 @@ var init_check = __esm({
|
|
|
17662
18931
|
init_diagnostics();
|
|
17663
18932
|
init_config_loader();
|
|
17664
18933
|
init_macro_sources();
|
|
18934
|
+
init_story_format();
|
|
17665
18935
|
init_pretty();
|
|
17666
18936
|
init_json();
|
|
17667
18937
|
init_sarif();
|
|
@@ -17705,13 +18975,17 @@ function splitPassages(text) {
|
|
|
17705
18975
|
return passages;
|
|
17706
18976
|
}
|
|
17707
18977
|
function classifyPassage(header) {
|
|
17708
|
-
const
|
|
17709
|
-
if (!tagMatch) return "normal";
|
|
17710
|
-
const tags = tagMatch.flatMap((t) => t.slice(1, -1).toLowerCase().split(/\s+/));
|
|
18978
|
+
const tags = headerTags(header).map((t) => t.toLowerCase());
|
|
17711
18979
|
if (tags.includes("script")) return "script";
|
|
17712
18980
|
if (tags.includes("stylesheet")) return "stylesheet";
|
|
17713
18981
|
return "normal";
|
|
17714
18982
|
}
|
|
18983
|
+
function headerTags(header) {
|
|
18984
|
+
const parsed = parsePassageHeader(header, 0);
|
|
18985
|
+
if (parsed) return parsed.tags;
|
|
18986
|
+
const unescaped = header.replace(/\\./g, "ec");
|
|
18987
|
+
return (unescaped.match(/\[[^\]]*\]/g) ?? []).flatMap((t) => t.slice(1, -1).split(/\s+/));
|
|
18988
|
+
}
|
|
17715
18989
|
function segmentRegions(body) {
|
|
17716
18990
|
const lines = body.split("\n");
|
|
17717
18991
|
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
@@ -17758,7 +19032,7 @@ function segmentRegions(body) {
|
|
|
17758
19032
|
if (/^<([a-zA-Z][\w-]*)/.test(trimmed2)) {
|
|
17759
19033
|
const tagMatch = trimmed2.match(/^<([a-zA-Z][\w-]*)/);
|
|
17760
19034
|
const tagName = tagMatch[1].toLowerCase();
|
|
17761
|
-
if (
|
|
19035
|
+
if (VOID_ELEMENTS2.has(tagName) && !lines[i + 1]?.trim().startsWith("<")) {
|
|
17762
19036
|
pushSpindleLine(regions, line);
|
|
17763
19037
|
i++;
|
|
17764
19038
|
continue;
|
|
@@ -17795,12 +19069,13 @@ function pushSpindleLine(regions, line) {
|
|
|
17795
19069
|
regions.push({ type: "spindle", lines: [line] });
|
|
17796
19070
|
}
|
|
17797
19071
|
}
|
|
17798
|
-
var PASSAGE_HEADER_REGEX,
|
|
19072
|
+
var PASSAGE_HEADER_REGEX, VOID_ELEMENTS2;
|
|
17799
19073
|
var init_segment = __esm({
|
|
17800
19074
|
"src/plugins/format/segment.ts"() {
|
|
17801
19075
|
"use strict";
|
|
19076
|
+
init_passage_parser();
|
|
17802
19077
|
PASSAGE_HEADER_REGEX = /^::\s+/;
|
|
17803
|
-
|
|
19078
|
+
VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
17804
19079
|
"area",
|
|
17805
19080
|
"base",
|
|
17806
19081
|
"br",
|
|
@@ -18287,11 +19562,12 @@ var init_format = __esm({
|
|
|
18287
19562
|
"src/plugins/format.ts"() {
|
|
18288
19563
|
"use strict";
|
|
18289
19564
|
init_macro_supplements();
|
|
19565
|
+
init_macro_parser();
|
|
18290
19566
|
init_segment();
|
|
18291
19567
|
init_prettier_bridge();
|
|
18292
19568
|
init_placeholders();
|
|
18293
19569
|
PASSAGE_HEADER_REGEX2 = /^(::)\s+/;
|
|
18294
|
-
MACRO_OPEN_REGEX =
|
|
19570
|
+
MACRO_OPEN_REGEX = new RegExp(String.raw`^\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\b`);
|
|
18295
19571
|
MACRO_CLOSE_REGEX = /^\{\/([A-Za-z][\w-]*)\b/;
|
|
18296
19572
|
DEFAULT_DEDENTING = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
|
|
18297
19573
|
LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
@@ -18304,7 +19580,8 @@ var init_format = __esm({
|
|
|
18304
19580
|
},
|
|
18305
19581
|
initialize(ctx) {
|
|
18306
19582
|
const formatOpts = {
|
|
18307
|
-
|
|
19583
|
+
// Block widgets (whose body renders {@children}) are containers too
|
|
19584
|
+
isBlock: (name) => ctx.workspace.isContainer(name),
|
|
18308
19585
|
isDedentingSubMacro: (name) => DEFAULT_DEDENTING.has(name.toLowerCase())
|
|
18309
19586
|
};
|
|
18310
19587
|
ctx.connection.onDocumentFormatting(async (params) => {
|
|
@@ -18349,7 +19626,7 @@ var format_exports = {};
|
|
|
18349
19626
|
__export(format_exports, {
|
|
18350
19627
|
runFormat: () => runFormat
|
|
18351
19628
|
});
|
|
18352
|
-
import { readFileSync as
|
|
19629
|
+
import { readFileSync as readFileSync7, writeFileSync } from "node:fs";
|
|
18353
19630
|
function parseArgs2(args2) {
|
|
18354
19631
|
const options = {
|
|
18355
19632
|
check: false,
|
|
@@ -18399,14 +19676,25 @@ async function runFormat(args2) {
|
|
|
18399
19676
|
console.log("No files found");
|
|
18400
19677
|
return 0;
|
|
18401
19678
|
}
|
|
19679
|
+
const texts = /* @__PURE__ */ new Map();
|
|
19680
|
+
for (const filePath of uniqueFiles) {
|
|
19681
|
+
try {
|
|
19682
|
+
texts.set(filePath, readFileSync7(filePath, "utf-8"));
|
|
19683
|
+
} catch {
|
|
19684
|
+
}
|
|
19685
|
+
}
|
|
19686
|
+
const storyFormat = await findStoryFormat(texts.values(), commonDirectory(uniqueFiles));
|
|
19687
|
+
if (!storyFormat.isSpindle) {
|
|
19688
|
+
console.error(skippedFormatNote(storyFormat));
|
|
19689
|
+
return 0;
|
|
19690
|
+
}
|
|
18402
19691
|
const formatOpts = {};
|
|
18403
19692
|
if (options.maxLineLength !== null) {
|
|
18404
19693
|
formatOpts.maxLineLength = options.maxLineLength;
|
|
18405
19694
|
}
|
|
18406
19695
|
const unformatted = [];
|
|
18407
|
-
for (const filePath of
|
|
19696
|
+
for (const [filePath, text] of texts) {
|
|
18408
19697
|
try {
|
|
18409
|
-
const text = readFileSync6(filePath, "utf-8");
|
|
18410
19698
|
const formatted = await formatDocument(text, formatOpts);
|
|
18411
19699
|
if (formatted !== text) {
|
|
18412
19700
|
if (options.check) {
|
|
@@ -18437,6 +19725,8 @@ var init_format2 = __esm({
|
|
|
18437
19725
|
"use strict";
|
|
18438
19726
|
init_esm6();
|
|
18439
19727
|
init_format();
|
|
19728
|
+
init_macro_sources();
|
|
19729
|
+
init_story_format();
|
|
18440
19730
|
}
|
|
18441
19731
|
});
|
|
18442
19732
|
|
|
@@ -49584,9 +50874,12 @@ var init_stdio2 = __esm({
|
|
|
49584
50874
|
var server_exports = {};
|
|
49585
50875
|
__export(server_exports, {
|
|
49586
50876
|
checkFiles: () => checkFiles,
|
|
50877
|
+
checkFormatting: () => checkFormatting,
|
|
50878
|
+
checkProject: () => checkProject,
|
|
50879
|
+
formatFiles: () => formatFiles,
|
|
49587
50880
|
startMcpServer: () => startMcpServer
|
|
49588
50881
|
});
|
|
49589
|
-
import { readFileSync as
|
|
50882
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
49590
50883
|
import { resolve as resolve3 } from "node:path";
|
|
49591
50884
|
import { pathToFileURL as pathToFileURL4 } from "node:url";
|
|
49592
50885
|
function filterBySeverity2(diagnostics, minSeverity) {
|
|
@@ -49618,20 +50911,28 @@ function findConfigRoot(commonDir) {
|
|
|
49618
50911
|
}
|
|
49619
50912
|
return configRoot;
|
|
49620
50913
|
}
|
|
49621
|
-
|
|
49622
|
-
const commonDir = commonDirectory(files);
|
|
49623
|
-
const configRoot = findConfigRoot(commonDir);
|
|
49624
|
-
const projectConfig = loadConfigFromDisk(configRoot);
|
|
49625
|
-
const workspace = new WorkspaceModel({ workspaceRoot: configRoot });
|
|
50914
|
+
function readFiles(files) {
|
|
49626
50915
|
const fileContents = /* @__PURE__ */ new Map();
|
|
49627
50916
|
for (const filePath of files) {
|
|
49628
50917
|
try {
|
|
49629
|
-
const text =
|
|
50918
|
+
const text = readFileSync8(filePath, "utf-8");
|
|
49630
50919
|
const uri = pathToFileURL4(filePath).toString();
|
|
49631
50920
|
fileContents.set(uri, text);
|
|
49632
50921
|
} catch {
|
|
49633
50922
|
}
|
|
49634
50923
|
}
|
|
50924
|
+
return fileContents;
|
|
50925
|
+
}
|
|
50926
|
+
async function skippedReason(fileContents, files) {
|
|
50927
|
+
const format = await findStoryFormat(fileContents.values(), commonDirectory(files));
|
|
50928
|
+
return format.isSpindle ? void 0 : skippedFormatNote(format);
|
|
50929
|
+
}
|
|
50930
|
+
async function createWorkspace(files, fileContents) {
|
|
50931
|
+
const commonDir = commonDirectory(files);
|
|
50932
|
+
const configRoot = findConfigRoot(commonDir);
|
|
50933
|
+
const projectConfig = loadConfigFromDisk(configRoot);
|
|
50934
|
+
const workspace = new WorkspaceModel({ workspaceRoot: configRoot });
|
|
50935
|
+
fileContents = new Map(fileContents);
|
|
49635
50936
|
await addProjectMacroSources(fileContents, commonDir);
|
|
49636
50937
|
workspace.initialize(fileContents);
|
|
49637
50938
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
@@ -49640,9 +50941,15 @@ async function createWorkspace(files) {
|
|
|
49640
50941
|
return workspace;
|
|
49641
50942
|
}
|
|
49642
50943
|
async function checkFiles(pattern, severity, cwd = process.cwd()) {
|
|
50944
|
+
return (await checkProject(pattern, severity, cwd)).results;
|
|
50945
|
+
}
|
|
50946
|
+
async function checkProject(pattern, severity, cwd = process.cwd()) {
|
|
49643
50947
|
const files = await resolveFiles(pattern, cwd);
|
|
49644
|
-
if (files.length === 0) return [];
|
|
49645
|
-
const
|
|
50948
|
+
if (files.length === 0) return { results: [] };
|
|
50949
|
+
const fileContents = readFiles(files);
|
|
50950
|
+
const skipped = await skippedReason(fileContents, files);
|
|
50951
|
+
if (skipped) return { results: [], skipped };
|
|
50952
|
+
const workspace = await createWorkspace(files, fileContents);
|
|
49646
50953
|
try {
|
|
49647
50954
|
const results = [];
|
|
49648
50955
|
for (const filePath of files) {
|
|
@@ -49664,11 +50971,57 @@ async function checkFiles(pattern, severity, cwd = process.cwd()) {
|
|
|
49664
50971
|
});
|
|
49665
50972
|
}
|
|
49666
50973
|
}
|
|
49667
|
-
return results;
|
|
50974
|
+
return { results };
|
|
49668
50975
|
} finally {
|
|
49669
50976
|
workspace.dispose();
|
|
49670
50977
|
}
|
|
49671
50978
|
}
|
|
50979
|
+
function relativeTo(cwd, filePath) {
|
|
50980
|
+
return filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
50981
|
+
}
|
|
50982
|
+
async function formatFiles(pattern, cwd = process.cwd()) {
|
|
50983
|
+
const files = await resolveFiles(pattern, cwd);
|
|
50984
|
+
const contents = readFiles(files);
|
|
50985
|
+
const skipped = files.length > 0 ? await skippedReason(contents, files) : void 0;
|
|
50986
|
+
if (skipped) return { formatted: 0, unchanged: 0, files: [], skipped };
|
|
50987
|
+
let formatted = 0;
|
|
50988
|
+
let unchanged = 0;
|
|
50989
|
+
const changedFiles = [];
|
|
50990
|
+
for (const filePath of files) {
|
|
50991
|
+
const text = contents.get(pathToFileURL4(filePath).toString());
|
|
50992
|
+
if (text === void 0) continue;
|
|
50993
|
+
try {
|
|
50994
|
+
const result = await formatDocument(text);
|
|
50995
|
+
if (result !== text) {
|
|
50996
|
+
writeFileSync2(filePath, result, "utf-8");
|
|
50997
|
+
formatted++;
|
|
50998
|
+
changedFiles.push(relativeTo(cwd, filePath));
|
|
50999
|
+
} else {
|
|
51000
|
+
unchanged++;
|
|
51001
|
+
}
|
|
51002
|
+
} catch {
|
|
51003
|
+
}
|
|
51004
|
+
}
|
|
51005
|
+
return { formatted, unchanged, files: changedFiles };
|
|
51006
|
+
}
|
|
51007
|
+
async function checkFormatting(pattern, cwd = process.cwd()) {
|
|
51008
|
+
const files = await resolveFiles(pattern, cwd);
|
|
51009
|
+
const contents = readFiles(files);
|
|
51010
|
+
const skipped = files.length > 0 ? await skippedReason(contents, files) : void 0;
|
|
51011
|
+
if (skipped) return { needsFormatting: [], alreadyFormatted: [], skipped };
|
|
51012
|
+
const needsFormatting = [];
|
|
51013
|
+
const alreadyFormatted = [];
|
|
51014
|
+
for (const filePath of files) {
|
|
51015
|
+
const text = contents.get(pathToFileURL4(filePath).toString());
|
|
51016
|
+
if (text === void 0) continue;
|
|
51017
|
+
try {
|
|
51018
|
+
const result = await formatDocument(text);
|
|
51019
|
+
(result !== text ? needsFormatting : alreadyFormatted).push(relativeTo(cwd, filePath));
|
|
51020
|
+
} catch {
|
|
51021
|
+
}
|
|
51022
|
+
}
|
|
51023
|
+
return { needsFormatting, alreadyFormatted };
|
|
51024
|
+
}
|
|
49672
51025
|
async function startMcpServer() {
|
|
49673
51026
|
const server = new McpServer({
|
|
49674
51027
|
name: "spindle-lsp",
|
|
@@ -49682,9 +51035,12 @@ async function startMcpServer() {
|
|
|
49682
51035
|
severity: external_exports3.enum(["error", "warning", "info", "hint"]).optional().describe("Minimum severity to include")
|
|
49683
51036
|
},
|
|
49684
51037
|
async (args2) => {
|
|
49685
|
-
const results = await
|
|
51038
|
+
const { results, skipped } = await checkProject(args2.path, args2.severity);
|
|
49686
51039
|
return {
|
|
49687
|
-
content: [
|
|
51040
|
+
content: [
|
|
51041
|
+
{ type: "text", text: JSON.stringify(results, null, 2) },
|
|
51042
|
+
...skipped ? [{ type: "text", text: skipped }] : []
|
|
51043
|
+
]
|
|
49688
51044
|
};
|
|
49689
51045
|
}
|
|
49690
51046
|
);
|
|
@@ -49695,27 +51051,7 @@ async function startMcpServer() {
|
|
|
49695
51051
|
path: external_exports3.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to format")
|
|
49696
51052
|
},
|
|
49697
51053
|
async (args2) => {
|
|
49698
|
-
const
|
|
49699
|
-
const files = await resolveFiles(args2.path, cwd);
|
|
49700
|
-
let formatted = 0;
|
|
49701
|
-
let unchanged = 0;
|
|
49702
|
-
const changedFiles = [];
|
|
49703
|
-
for (const filePath of files) {
|
|
49704
|
-
try {
|
|
49705
|
-
const text = readFileSync7(filePath, "utf-8");
|
|
49706
|
-
const result = await formatDocument(text);
|
|
49707
|
-
if (result !== text) {
|
|
49708
|
-
writeFileSync2(filePath, result, "utf-8");
|
|
49709
|
-
formatted++;
|
|
49710
|
-
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
49711
|
-
changedFiles.push(relativePath);
|
|
49712
|
-
} else {
|
|
49713
|
-
unchanged++;
|
|
49714
|
-
}
|
|
49715
|
-
} catch {
|
|
49716
|
-
}
|
|
49717
|
-
}
|
|
49718
|
-
const output = { formatted, unchanged, files: changedFiles };
|
|
51054
|
+
const output = await formatFiles(args2.path);
|
|
49719
51055
|
return {
|
|
49720
51056
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
49721
51057
|
};
|
|
@@ -49728,24 +51064,7 @@ async function startMcpServer() {
|
|
|
49728
51064
|
path: external_exports3.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check")
|
|
49729
51065
|
},
|
|
49730
51066
|
async (args2) => {
|
|
49731
|
-
const
|
|
49732
|
-
const files = await resolveFiles(args2.path, cwd);
|
|
49733
|
-
const needsFormatting = [];
|
|
49734
|
-
const alreadyFormatted = [];
|
|
49735
|
-
for (const filePath of files) {
|
|
49736
|
-
try {
|
|
49737
|
-
const text = readFileSync7(filePath, "utf-8");
|
|
49738
|
-
const result = await formatDocument(text);
|
|
49739
|
-
const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
49740
|
-
if (result !== text) {
|
|
49741
|
-
needsFormatting.push(relativePath);
|
|
49742
|
-
} else {
|
|
49743
|
-
alreadyFormatted.push(relativePath);
|
|
49744
|
-
}
|
|
49745
|
-
} catch {
|
|
49746
|
-
}
|
|
49747
|
-
}
|
|
49748
|
-
const output = { needsFormatting, alreadyFormatted };
|
|
51067
|
+
const output = await checkFormatting(args2.path);
|
|
49749
51068
|
return {
|
|
49750
51069
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
49751
51070
|
};
|
|
@@ -49767,6 +51086,7 @@ var init_server3 = __esm({
|
|
|
49767
51086
|
init_macro_sources();
|
|
49768
51087
|
init_diagnostics();
|
|
49769
51088
|
init_format();
|
|
51089
|
+
init_story_format();
|
|
49770
51090
|
SEVERITY_ORDER2 = ["hint", "info", "warning", "error"];
|
|
49771
51091
|
}
|
|
49772
51092
|
});
|
|
@@ -58894,6 +60214,62 @@ var init_capabilities = __esm({
|
|
|
58894
60214
|
}
|
|
58895
60215
|
});
|
|
58896
60216
|
|
|
60217
|
+
// src/server/story-format-gate.ts
|
|
60218
|
+
function gateRegistrations(target, methods, enabled, nested = {}) {
|
|
60219
|
+
return new Proxy(target, {
|
|
60220
|
+
get(obj, prop, receiver) {
|
|
60221
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
60222
|
+
if (typeof prop !== "string") return value;
|
|
60223
|
+
if (Object.hasOwn(nested, prop) && value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
60224
|
+
return nested[prop](value);
|
|
60225
|
+
}
|
|
60226
|
+
if (methods.includes(prop) && typeof value === "function") {
|
|
60227
|
+
return (handler, ...rest) => Reflect.apply(value, obj, [
|
|
60228
|
+
(...args2) => enabled() ? handler(...args2) : null,
|
|
60229
|
+
...rest
|
|
60230
|
+
]);
|
|
60231
|
+
}
|
|
60232
|
+
return value;
|
|
60233
|
+
}
|
|
60234
|
+
});
|
|
60235
|
+
}
|
|
60236
|
+
function gateConnection(connection, enabled) {
|
|
60237
|
+
const languageFeatures = {};
|
|
60238
|
+
for (const [feature, methods] of Object.entries(GATED_LANGUAGE_FEATURES)) {
|
|
60239
|
+
languageFeatures[feature] = (value) => gateRegistrations(value, methods, enabled);
|
|
60240
|
+
}
|
|
60241
|
+
return gateRegistrations(connection, GATED_REQUESTS, enabled, {
|
|
60242
|
+
languages: (value) => gateRegistrations(value, [], enabled, languageFeatures)
|
|
60243
|
+
});
|
|
60244
|
+
}
|
|
60245
|
+
var GATED_REQUESTS, GATED_LANGUAGE_FEATURES;
|
|
60246
|
+
var init_story_format_gate = __esm({
|
|
60247
|
+
"src/server/story-format-gate.ts"() {
|
|
60248
|
+
"use strict";
|
|
60249
|
+
GATED_REQUESTS = [
|
|
60250
|
+
"onCompletion",
|
|
60251
|
+
"onHover",
|
|
60252
|
+
"onSignatureHelp",
|
|
60253
|
+
"onDefinition",
|
|
60254
|
+
"onReferences",
|
|
60255
|
+
"onPrepareRename",
|
|
60256
|
+
"onRenameRequest",
|
|
60257
|
+
"onCodeLens",
|
|
60258
|
+
"onCodeAction",
|
|
60259
|
+
"onDocumentFormatting",
|
|
60260
|
+
"onDocumentRangeFormatting",
|
|
60261
|
+
"onDocumentSymbol",
|
|
60262
|
+
"onWorkspaceSymbol",
|
|
60263
|
+
"onFoldingRanges",
|
|
60264
|
+
"onDocumentLinks"
|
|
60265
|
+
];
|
|
60266
|
+
GATED_LANGUAGE_FEATURES = {
|
|
60267
|
+
semanticTokens: ["on"],
|
|
60268
|
+
inlayHint: ["on"]
|
|
60269
|
+
};
|
|
60270
|
+
}
|
|
60271
|
+
});
|
|
60272
|
+
|
|
58897
60273
|
// src/plugins/completions.ts
|
|
58898
60274
|
function getCompletions(uri, position, triggerChar, workspace) {
|
|
58899
60275
|
const text = workspace.documents.getText(uri);
|
|
@@ -58901,14 +60277,15 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
58901
60277
|
const lines = text.split("\n");
|
|
58902
60278
|
if (position.line >= lines.length) return [];
|
|
58903
60279
|
const lineText = lines[position.line].substring(0, position.character);
|
|
60280
|
+
const inAttribute = () => inAttributeValue(text, (buildLineStarts(text)[position.line] ?? 0) + position.character - 1);
|
|
58904
60281
|
if (/\{\/[A-Za-z\w-]*$/.test(lineText)) {
|
|
58905
|
-
return getClosingMacroCompletions(uri, text, position, workspace);
|
|
60282
|
+
return inAttribute() ? [] : getClosingMacroCompletions(uri, text, position, workspace);
|
|
58906
60283
|
}
|
|
58907
|
-
const transientDotPathMatch = /%([
|
|
60284
|
+
const transientDotPathMatch = /%([\w$]+)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
58908
60285
|
if (transientDotPathMatch) {
|
|
58909
60286
|
return getTransientDotPathCompletions(transientDotPathMatch[1], workspace);
|
|
58910
60287
|
}
|
|
58911
|
-
const dotPathMatch = /\$([
|
|
60288
|
+
const dotPathMatch = /\$([\w$]+)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
58912
60289
|
if (dotPathMatch) {
|
|
58913
60290
|
return getDotPathCompletions(dotPathMatch[1], workspace);
|
|
58914
60291
|
}
|
|
@@ -58928,7 +60305,7 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
58928
60305
|
return getPassageNameCompletions(workspace);
|
|
58929
60306
|
}
|
|
58930
60307
|
if (/(?:^|[^\\])\{[A-Za-z\w-]*$/.test(lineText)) {
|
|
58931
|
-
return getMacroCompletions(workspace);
|
|
60308
|
+
return inAttribute() ? [] : getMacroCompletions(workspace);
|
|
58932
60309
|
}
|
|
58933
60310
|
return [];
|
|
58934
60311
|
}
|
|
@@ -59100,6 +60477,7 @@ var init_completions = __esm({
|
|
|
59100
60477
|
"src/plugins/completions.ts"() {
|
|
59101
60478
|
"use strict";
|
|
59102
60479
|
init_macro_parser();
|
|
60480
|
+
init_html_scanner();
|
|
59103
60481
|
completionsPlugin = {
|
|
59104
60482
|
id: "completions",
|
|
59105
60483
|
capabilities: {
|
|
@@ -59122,6 +60500,215 @@ var init_completions = __esm({
|
|
|
59122
60500
|
}
|
|
59123
60501
|
});
|
|
59124
60502
|
|
|
60503
|
+
// src/plugins/references.ts
|
|
60504
|
+
function findReferences(uri, position, workspace, includeDeclaration) {
|
|
60505
|
+
const text = workspace.documents.getText(uri);
|
|
60506
|
+
if (text === void 0) return [];
|
|
60507
|
+
const lines = text.split("\n");
|
|
60508
|
+
if (position.line >= lines.length) return [];
|
|
60509
|
+
const line = lines[position.line];
|
|
60510
|
+
const header = parsePassageHeader(line, position.line);
|
|
60511
|
+
if (header) {
|
|
60512
|
+
const { start, end } = header.nameRange;
|
|
60513
|
+
if (position.character >= start.character && position.character <= end.character) {
|
|
60514
|
+
return findPassageReferences(header.name, workspace, includeDeclaration);
|
|
60515
|
+
}
|
|
60516
|
+
}
|
|
60517
|
+
{
|
|
60518
|
+
const varRegex = /\$([\w$]+)/g;
|
|
60519
|
+
let match2;
|
|
60520
|
+
while ((match2 = varRegex.exec(line)) !== null) {
|
|
60521
|
+
const start = match2.index;
|
|
60522
|
+
const end = start + match2[0].length;
|
|
60523
|
+
if (position.character >= start && position.character <= end) {
|
|
60524
|
+
const varName = match2[1];
|
|
60525
|
+
return findVariableReferences(varName, workspace, includeDeclaration);
|
|
60526
|
+
}
|
|
60527
|
+
}
|
|
60528
|
+
}
|
|
60529
|
+
{
|
|
60530
|
+
const transRegex = /(?<!\w)%([\w$]+)/g;
|
|
60531
|
+
let match2;
|
|
60532
|
+
while ((match2 = transRegex.exec(line)) !== null) {
|
|
60533
|
+
const start = match2.index;
|
|
60534
|
+
const end = start + match2[0].length;
|
|
60535
|
+
if (position.character >= start && position.character <= end) {
|
|
60536
|
+
const varName = match2[1];
|
|
60537
|
+
if (!isTransientAt(varName, uri, position.line, start, workspace)) break;
|
|
60538
|
+
return findTransientReferences(varName, workspace, includeDeclaration);
|
|
60539
|
+
}
|
|
60540
|
+
}
|
|
60541
|
+
}
|
|
60542
|
+
{
|
|
60543
|
+
const widgetRegex = /\{\/?([A-Za-z_$][\w$]*)/g;
|
|
60544
|
+
let match2;
|
|
60545
|
+
while ((match2 = widgetRegex.exec(line)) !== null) {
|
|
60546
|
+
const name = match2[1];
|
|
60547
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
60548
|
+
const nameEnd = nameStart + name.length;
|
|
60549
|
+
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
60550
|
+
const widget = workspace.widgets.getWidget(name);
|
|
60551
|
+
const isClosing = match2[0][1] === "/";
|
|
60552
|
+
if (!workspace.macros.getMacro(name) && widget && (!isClosing || widget.block)) {
|
|
60553
|
+
return findWidgetReferences(name, workspace, includeDeclaration);
|
|
60554
|
+
}
|
|
60555
|
+
}
|
|
60556
|
+
}
|
|
60557
|
+
}
|
|
60558
|
+
const passageRef = findPassageRefAt(text, position);
|
|
60559
|
+
if (passageRef) {
|
|
60560
|
+
return findPassageReferences(passageRef.name, workspace, includeDeclaration);
|
|
60561
|
+
}
|
|
60562
|
+
{
|
|
60563
|
+
const allPassages = workspace.passages.getAllPassages();
|
|
60564
|
+
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
60565
|
+
const wordRegex = /[A-Za-z_$][\w$\s]*/g;
|
|
60566
|
+
let match2;
|
|
60567
|
+
while ((match2 = wordRegex.exec(line)) !== null) {
|
|
60568
|
+
const start = match2.index;
|
|
60569
|
+
const end = start + match2[0].length;
|
|
60570
|
+
if (position.character >= start && position.character <= end) {
|
|
60571
|
+
const word = match2[0].trim();
|
|
60572
|
+
if (passageNames.has(word)) {
|
|
60573
|
+
return findPassageReferences(word, workspace, includeDeclaration);
|
|
60574
|
+
}
|
|
60575
|
+
}
|
|
60576
|
+
}
|
|
60577
|
+
}
|
|
60578
|
+
return [];
|
|
60579
|
+
}
|
|
60580
|
+
function findPassageReferences(passageName, workspace, includeDeclaration) {
|
|
60581
|
+
const locations = [];
|
|
60582
|
+
if (includeDeclaration) {
|
|
60583
|
+
const passage = workspace.passages.getPassage(passageName);
|
|
60584
|
+
if (passage) {
|
|
60585
|
+
locations.push({
|
|
60586
|
+
uri: passage.uri,
|
|
60587
|
+
range: passage.nameRange
|
|
60588
|
+
});
|
|
60589
|
+
}
|
|
60590
|
+
}
|
|
60591
|
+
for (const docUri of workspace.documents.getUris()) {
|
|
60592
|
+
if (isMacroSource(docUri)) continue;
|
|
60593
|
+
const docText = workspace.documents.getText(docUri);
|
|
60594
|
+
if (!docText) continue;
|
|
60595
|
+
for (const ref of [...parseLinks(docText), ...parseMacroPassageRefs(docText)]) {
|
|
60596
|
+
if (ref.name === passageName) {
|
|
60597
|
+
locations.push({ uri: docUri, range: ref.range });
|
|
60598
|
+
}
|
|
60599
|
+
}
|
|
60600
|
+
}
|
|
60601
|
+
return locations;
|
|
60602
|
+
}
|
|
60603
|
+
function findVariableReferences(varName, workspace, includeDeclaration) {
|
|
60604
|
+
const locations = [];
|
|
60605
|
+
if (includeDeclaration) {
|
|
60606
|
+
const decl = workspace.variables.getDeclared().get(varName);
|
|
60607
|
+
if (decl?.declarationRange && decl.declarationUri) {
|
|
60608
|
+
locations.push({
|
|
60609
|
+
uri: decl.declarationUri,
|
|
60610
|
+
range: decl.declarationRange
|
|
60611
|
+
});
|
|
60612
|
+
}
|
|
60613
|
+
}
|
|
60614
|
+
const usages = workspace.variables.getUsages(varName);
|
|
60615
|
+
for (const u of usages) {
|
|
60616
|
+
locations.push({ uri: u.uri, range: u.range });
|
|
60617
|
+
}
|
|
60618
|
+
return locations;
|
|
60619
|
+
}
|
|
60620
|
+
function isTransientAt(name, uri, line, character, workspace) {
|
|
60621
|
+
if (!/^\d/.test(name)) return true;
|
|
60622
|
+
const at = (u) => u.uri === uri && u.range?.start.line === line && u.range.start.character === character;
|
|
60623
|
+
const decl = workspace.variables.getDeclaredTransient().get(name);
|
|
60624
|
+
if (decl && at({ uri: decl.declarationUri, range: decl.declarationRange })) return true;
|
|
60625
|
+
return workspace.variables.getTransientUsages(name).some(at);
|
|
60626
|
+
}
|
|
60627
|
+
function findTransientReferences(varName, workspace, includeDeclaration) {
|
|
60628
|
+
const results = [];
|
|
60629
|
+
if (includeDeclaration) {
|
|
60630
|
+
const decl = workspace.variables.getDeclaredTransient().get(varName);
|
|
60631
|
+
if (decl?.declarationUri && decl.declarationRange) {
|
|
60632
|
+
results.push({ uri: decl.declarationUri, range: decl.declarationRange });
|
|
60633
|
+
}
|
|
60634
|
+
}
|
|
60635
|
+
const usages = workspace.variables.getTransientUsages(varName);
|
|
60636
|
+
for (const u of usages) {
|
|
60637
|
+
results.push({ uri: u.uri, range: u.range });
|
|
60638
|
+
}
|
|
60639
|
+
return results;
|
|
60640
|
+
}
|
|
60641
|
+
function findWidgetReferences(widgetName, workspace, includeDeclaration) {
|
|
60642
|
+
const locations = [];
|
|
60643
|
+
const widget = workspace.widgets.getWidget(widgetName);
|
|
60644
|
+
if (includeDeclaration && widget) {
|
|
60645
|
+
locations.push({ uri: widget.uri, range: widget.range });
|
|
60646
|
+
}
|
|
60647
|
+
const widgetInvocationRegex = /\{(\/)?([A-Za-z_$][\w$]*)\b/g;
|
|
60648
|
+
const lowerName = widgetName.toLowerCase();
|
|
60649
|
+
const isBlock = widget?.block ?? false;
|
|
60650
|
+
for (const docUri of workspace.documents.getUris()) {
|
|
60651
|
+
if (isMacroSource(docUri)) continue;
|
|
60652
|
+
const docText = workspace.documents.getText(docUri);
|
|
60653
|
+
if (!docText) continue;
|
|
60654
|
+
const lines = docText.split("\n");
|
|
60655
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
60656
|
+
const line = lines[lineNum];
|
|
60657
|
+
widgetInvocationRegex.lastIndex = 0;
|
|
60658
|
+
let match2;
|
|
60659
|
+
while ((match2 = widgetInvocationRegex.exec(line)) !== null) {
|
|
60660
|
+
if (match2[1] && !isBlock) continue;
|
|
60661
|
+
if (match2[2].toLowerCase() === lowerName) {
|
|
60662
|
+
const nameStart = match2.index + 1 + (match2[1] ? 1 : 0);
|
|
60663
|
+
locations.push({
|
|
60664
|
+
uri: docUri,
|
|
60665
|
+
range: {
|
|
60666
|
+
start: { line: lineNum, character: nameStart },
|
|
60667
|
+
end: { line: lineNum, character: nameStart + match2[2].length }
|
|
60668
|
+
}
|
|
60669
|
+
});
|
|
60670
|
+
}
|
|
60671
|
+
}
|
|
60672
|
+
}
|
|
60673
|
+
}
|
|
60674
|
+
return locations;
|
|
60675
|
+
}
|
|
60676
|
+
function toLspRange(r) {
|
|
60677
|
+
return {
|
|
60678
|
+
start: { line: r.start.line, character: r.start.character },
|
|
60679
|
+
end: { line: r.end.line, character: r.end.character }
|
|
60680
|
+
};
|
|
60681
|
+
}
|
|
60682
|
+
var referencesPlugin;
|
|
60683
|
+
var init_references = __esm({
|
|
60684
|
+
"src/plugins/references.ts"() {
|
|
60685
|
+
"use strict";
|
|
60686
|
+
init_link_parser();
|
|
60687
|
+
init_passage_parser();
|
|
60688
|
+
init_macro_sources();
|
|
60689
|
+
referencesPlugin = {
|
|
60690
|
+
id: "references",
|
|
60691
|
+
capabilities: {
|
|
60692
|
+
referencesProvider: true
|
|
60693
|
+
},
|
|
60694
|
+
initialize(ctx) {
|
|
60695
|
+
ctx.connection.onReferences((params) => {
|
|
60696
|
+
const refs = findReferences(
|
|
60697
|
+
params.textDocument.uri,
|
|
60698
|
+
{ line: params.position.line, character: params.position.character },
|
|
60699
|
+
ctx.workspace,
|
|
60700
|
+
params.context.includeDeclaration
|
|
60701
|
+
);
|
|
60702
|
+
return refs.map((r) => ({
|
|
60703
|
+
uri: r.uri,
|
|
60704
|
+
range: toLspRange(r.range)
|
|
60705
|
+
}));
|
|
60706
|
+
});
|
|
60707
|
+
}
|
|
60708
|
+
};
|
|
60709
|
+
}
|
|
60710
|
+
});
|
|
60711
|
+
|
|
59125
60712
|
// src/plugins/hover.ts
|
|
59126
60713
|
function getHoverInfo(uri, position, workspace) {
|
|
59127
60714
|
const text = workspace.documents.getText(uri);
|
|
@@ -59129,20 +60716,22 @@ function getHoverInfo(uri, position, workspace) {
|
|
|
59129
60716
|
const lines = text.split("\n");
|
|
59130
60717
|
if (position.line >= lines.length) return null;
|
|
59131
60718
|
const line = lines[position.line];
|
|
59132
|
-
const
|
|
60719
|
+
const offset = (buildLineStarts(text)[position.line] ?? 0) + position.character;
|
|
60720
|
+
const inAttribute = inAttributeValue(text, offset);
|
|
60721
|
+
const macroResult = inAttribute ? null : getMacroHover(line, position, workspace);
|
|
59133
60722
|
if (macroResult) return macroResult;
|
|
59134
|
-
const varResult = getVariableHover(line, position, workspace);
|
|
60723
|
+
const varResult = getVariableHover(uri, line, position, workspace);
|
|
59135
60724
|
if (varResult) return varResult;
|
|
59136
|
-
const widgetResult = getWidgetHover(line, position, workspace);
|
|
60725
|
+
const widgetResult = inAttribute ? null : getWidgetHover(line, position, workspace);
|
|
59137
60726
|
if (widgetResult) return widgetResult;
|
|
59138
60727
|
return null;
|
|
59139
60728
|
}
|
|
59140
60729
|
function getMacroHover(line, position, workspace) {
|
|
59141
|
-
const macroRegex =
|
|
60730
|
+
const macroRegex = new RegExp(String.raw`\{(?:(\/)|${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)`, "g");
|
|
59142
60731
|
let match2;
|
|
59143
60732
|
while ((match2 = macroRegex.exec(line)) !== null) {
|
|
59144
60733
|
const name = match2[2];
|
|
59145
|
-
const nameStart = match2.index + match2[0].
|
|
60734
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
59146
60735
|
const nameEnd = nameStart + name.length;
|
|
59147
60736
|
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
59148
60737
|
const info = workspace.macros.getMacro(name);
|
|
@@ -59171,9 +60760,9 @@ function getMacroHover(line, position, workspace) {
|
|
|
59171
60760
|
}
|
|
59172
60761
|
return null;
|
|
59173
60762
|
}
|
|
59174
|
-
function getVariableHover(line, position, workspace) {
|
|
60763
|
+
function getVariableHover(uri, line, position, workspace) {
|
|
59175
60764
|
{
|
|
59176
|
-
const re = /\$([
|
|
60765
|
+
const re = /\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
59177
60766
|
let match2;
|
|
59178
60767
|
while ((match2 = re.exec(line)) !== null) {
|
|
59179
60768
|
const start = match2.index;
|
|
@@ -59229,13 +60818,14 @@ Fields: ${decl.fields.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
|
59229
60818
|
}
|
|
59230
60819
|
}
|
|
59231
60820
|
{
|
|
59232
|
-
const re = /(?<!\w)%([
|
|
60821
|
+
const re = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
59233
60822
|
let match2;
|
|
59234
60823
|
while ((match2 = re.exec(line)) !== null) {
|
|
59235
60824
|
const start = match2.index;
|
|
59236
60825
|
const end = start + match2[0].length;
|
|
59237
60826
|
if (position.character >= start && position.character <= end) {
|
|
59238
60827
|
const baseName = match2[1].split(".")[0];
|
|
60828
|
+
if (!isTransientAt(baseName, uri, position.line, start, workspace)) break;
|
|
59239
60829
|
const decl = workspace.variables.getDeclaredTransient().get(baseName);
|
|
59240
60830
|
const typeInfo = decl?.fields && decl.fields.length > 0 ? `
|
|
59241
60831
|
|
|
@@ -59282,7 +60872,7 @@ Defined in: \`${widget.uri}\``,
|
|
|
59282
60872
|
}
|
|
59283
60873
|
};
|
|
59284
60874
|
}
|
|
59285
|
-
function
|
|
60875
|
+
function toLspRange2(r) {
|
|
59286
60876
|
return {
|
|
59287
60877
|
start: { line: r.start.line, character: r.start.character },
|
|
59288
60878
|
end: { line: r.end.line, character: r.end.character }
|
|
@@ -59292,6 +60882,9 @@ var hoverPlugin;
|
|
|
59292
60882
|
var init_hover = __esm({
|
|
59293
60883
|
"src/plugins/hover.ts"() {
|
|
59294
60884
|
"use strict";
|
|
60885
|
+
init_macro_parser();
|
|
60886
|
+
init_references();
|
|
60887
|
+
init_html_scanner();
|
|
59295
60888
|
hoverPlugin = {
|
|
59296
60889
|
id: "hover",
|
|
59297
60890
|
capabilities: {
|
|
@@ -59307,7 +60900,7 @@ var init_hover = __esm({
|
|
|
59307
60900
|
if (!result) return null;
|
|
59308
60901
|
return {
|
|
59309
60902
|
contents: { kind: "markdown", value: result.contents },
|
|
59310
|
-
range:
|
|
60903
|
+
range: toLspRange2(result.range)
|
|
59311
60904
|
};
|
|
59312
60905
|
});
|
|
59313
60906
|
}
|
|
@@ -59374,7 +60967,7 @@ var init_signature = __esm({
|
|
|
59374
60967
|
init_argument_lexer();
|
|
59375
60968
|
init_widget_arguments();
|
|
59376
60969
|
init_macro_parser();
|
|
59377
|
-
macroHeadRegex2 =
|
|
60970
|
+
macroHeadRegex2 = new RegExp(String.raw`(?<!\\)\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\s+`, "g");
|
|
59378
60971
|
signaturePlugin = {
|
|
59379
60972
|
id: "signature",
|
|
59380
60973
|
capabilities: {
|
|
@@ -59449,7 +61042,7 @@ function getWidgetDefinition(line, position, workspace) {
|
|
|
59449
61042
|
}
|
|
59450
61043
|
return null;
|
|
59451
61044
|
}
|
|
59452
|
-
function
|
|
61045
|
+
function toLspRange3(r) {
|
|
59453
61046
|
return {
|
|
59454
61047
|
start: { line: r.start.line, character: r.start.character },
|
|
59455
61048
|
end: { line: r.end.line, character: r.end.character }
|
|
@@ -59475,7 +61068,7 @@ var init_definition = __esm({
|
|
|
59475
61068
|
if (!result) return null;
|
|
59476
61069
|
return {
|
|
59477
61070
|
uri: result.uri,
|
|
59478
|
-
range:
|
|
61071
|
+
range: toLspRange3(result.range)
|
|
59479
61072
|
};
|
|
59480
61073
|
});
|
|
59481
61074
|
}
|
|
@@ -59483,211 +61076,6 @@ var init_definition = __esm({
|
|
|
59483
61076
|
}
|
|
59484
61077
|
});
|
|
59485
61078
|
|
|
59486
|
-
// src/plugins/references.ts
|
|
59487
|
-
function findReferences(uri, position, workspace, includeDeclaration) {
|
|
59488
|
-
const text = workspace.documents.getText(uri);
|
|
59489
|
-
if (text === void 0) return [];
|
|
59490
|
-
const lines = text.split("\n");
|
|
59491
|
-
if (position.line >= lines.length) return [];
|
|
59492
|
-
const line = lines[position.line];
|
|
59493
|
-
const passageHeaderRegex2 = /^::\s*(\S.*?)(?:\s*\[|\s*\{|\s*$)/;
|
|
59494
|
-
if (line.trimStart().startsWith("::")) {
|
|
59495
|
-
const headerMatch = passageHeaderRegex2.exec(line);
|
|
59496
|
-
if (headerMatch) {
|
|
59497
|
-
const passageName = headerMatch[1].trim();
|
|
59498
|
-
const nameStart = line.indexOf(passageName);
|
|
59499
|
-
const nameEnd = nameStart + passageName.length;
|
|
59500
|
-
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
59501
|
-
return findPassageReferences(passageName, workspace, includeDeclaration);
|
|
59502
|
-
}
|
|
59503
|
-
}
|
|
59504
|
-
}
|
|
59505
|
-
{
|
|
59506
|
-
const varRegex = /\$([A-Za-z_$][\w$]*)/g;
|
|
59507
|
-
let match2;
|
|
59508
|
-
while ((match2 = varRegex.exec(line)) !== null) {
|
|
59509
|
-
const start = match2.index;
|
|
59510
|
-
const end = start + match2[0].length;
|
|
59511
|
-
if (position.character >= start && position.character <= end) {
|
|
59512
|
-
const varName = match2[1];
|
|
59513
|
-
return findVariableReferences(varName, workspace, includeDeclaration);
|
|
59514
|
-
}
|
|
59515
|
-
}
|
|
59516
|
-
}
|
|
59517
|
-
{
|
|
59518
|
-
const transRegex = /(?<!\w)%([A-Za-z_$][\w$]*)/g;
|
|
59519
|
-
let match2;
|
|
59520
|
-
while ((match2 = transRegex.exec(line)) !== null) {
|
|
59521
|
-
const start = match2.index;
|
|
59522
|
-
const end = start + match2[0].length;
|
|
59523
|
-
if (position.character >= start && position.character <= end) {
|
|
59524
|
-
const varName = match2[1];
|
|
59525
|
-
return findTransientReferences(varName, workspace, includeDeclaration);
|
|
59526
|
-
}
|
|
59527
|
-
}
|
|
59528
|
-
}
|
|
59529
|
-
{
|
|
59530
|
-
const widgetRegex = /\{\/?([A-Za-z_$][\w$]*)/g;
|
|
59531
|
-
let match2;
|
|
59532
|
-
while ((match2 = widgetRegex.exec(line)) !== null) {
|
|
59533
|
-
const name = match2[1];
|
|
59534
|
-
const nameStart = match2.index + match2[0].length - name.length;
|
|
59535
|
-
const nameEnd = nameStart + name.length;
|
|
59536
|
-
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
59537
|
-
const widget = workspace.widgets.getWidget(name);
|
|
59538
|
-
const isClosing = match2[0][1] === "/";
|
|
59539
|
-
if (!workspace.macros.getMacro(name) && widget && (!isClosing || widget.block)) {
|
|
59540
|
-
return findWidgetReferences(name, workspace, includeDeclaration);
|
|
59541
|
-
}
|
|
59542
|
-
}
|
|
59543
|
-
}
|
|
59544
|
-
}
|
|
59545
|
-
const passageRef = findPassageRefAt(text, position);
|
|
59546
|
-
if (passageRef) {
|
|
59547
|
-
return findPassageReferences(passageRef.name, workspace, includeDeclaration);
|
|
59548
|
-
}
|
|
59549
|
-
{
|
|
59550
|
-
const allPassages = workspace.passages.getAllPassages();
|
|
59551
|
-
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
59552
|
-
const wordRegex = /[A-Za-z_$][\w$\s]*/g;
|
|
59553
|
-
let match2;
|
|
59554
|
-
while ((match2 = wordRegex.exec(line)) !== null) {
|
|
59555
|
-
const start = match2.index;
|
|
59556
|
-
const end = start + match2[0].length;
|
|
59557
|
-
if (position.character >= start && position.character <= end) {
|
|
59558
|
-
const word = match2[0].trim();
|
|
59559
|
-
if (passageNames.has(word)) {
|
|
59560
|
-
return findPassageReferences(word, workspace, includeDeclaration);
|
|
59561
|
-
}
|
|
59562
|
-
}
|
|
59563
|
-
}
|
|
59564
|
-
}
|
|
59565
|
-
return [];
|
|
59566
|
-
}
|
|
59567
|
-
function findPassageReferences(passageName, workspace, includeDeclaration) {
|
|
59568
|
-
const locations = [];
|
|
59569
|
-
if (includeDeclaration) {
|
|
59570
|
-
const passage = workspace.passages.getPassage(passageName);
|
|
59571
|
-
if (passage) {
|
|
59572
|
-
locations.push({
|
|
59573
|
-
uri: passage.uri,
|
|
59574
|
-
range: passage.nameRange
|
|
59575
|
-
});
|
|
59576
|
-
}
|
|
59577
|
-
}
|
|
59578
|
-
for (const docUri of workspace.documents.getUris()) {
|
|
59579
|
-
if (isMacroSource(docUri)) continue;
|
|
59580
|
-
const docText = workspace.documents.getText(docUri);
|
|
59581
|
-
if (!docText) continue;
|
|
59582
|
-
for (const ref of [...parseLinks(docText), ...parseMacroPassageRefs(docText)]) {
|
|
59583
|
-
if (ref.name === passageName) {
|
|
59584
|
-
locations.push({ uri: docUri, range: ref.range });
|
|
59585
|
-
}
|
|
59586
|
-
}
|
|
59587
|
-
}
|
|
59588
|
-
return locations;
|
|
59589
|
-
}
|
|
59590
|
-
function findVariableReferences(varName, workspace, includeDeclaration) {
|
|
59591
|
-
const locations = [];
|
|
59592
|
-
if (includeDeclaration) {
|
|
59593
|
-
const decl = workspace.variables.getDeclared().get(varName);
|
|
59594
|
-
if (decl?.declarationRange && decl.declarationUri) {
|
|
59595
|
-
locations.push({
|
|
59596
|
-
uri: decl.declarationUri,
|
|
59597
|
-
range: decl.declarationRange
|
|
59598
|
-
});
|
|
59599
|
-
}
|
|
59600
|
-
}
|
|
59601
|
-
const usages = workspace.variables.getUsages(varName);
|
|
59602
|
-
for (const u of usages) {
|
|
59603
|
-
locations.push({ uri: u.uri, range: u.range });
|
|
59604
|
-
}
|
|
59605
|
-
return locations;
|
|
59606
|
-
}
|
|
59607
|
-
function findTransientReferences(varName, workspace, includeDeclaration) {
|
|
59608
|
-
const results = [];
|
|
59609
|
-
if (includeDeclaration) {
|
|
59610
|
-
const decl = workspace.variables.getDeclaredTransient().get(varName);
|
|
59611
|
-
if (decl?.declarationUri && decl.declarationRange) {
|
|
59612
|
-
results.push({ uri: decl.declarationUri, range: decl.declarationRange });
|
|
59613
|
-
}
|
|
59614
|
-
}
|
|
59615
|
-
const usages = workspace.variables.getTransientUsages(varName);
|
|
59616
|
-
for (const u of usages) {
|
|
59617
|
-
results.push({ uri: u.uri, range: u.range });
|
|
59618
|
-
}
|
|
59619
|
-
return results;
|
|
59620
|
-
}
|
|
59621
|
-
function findWidgetReferences(widgetName, workspace, includeDeclaration) {
|
|
59622
|
-
const locations = [];
|
|
59623
|
-
const widget = workspace.widgets.getWidget(widgetName);
|
|
59624
|
-
if (includeDeclaration && widget) {
|
|
59625
|
-
locations.push({ uri: widget.uri, range: widget.range });
|
|
59626
|
-
}
|
|
59627
|
-
const widgetInvocationRegex = /\{(\/)?([A-Za-z_$][\w$]*)\b/g;
|
|
59628
|
-
const lowerName = widgetName.toLowerCase();
|
|
59629
|
-
const isBlock = widget?.block ?? false;
|
|
59630
|
-
for (const docUri of workspace.documents.getUris()) {
|
|
59631
|
-
if (isMacroSource(docUri)) continue;
|
|
59632
|
-
const docText = workspace.documents.getText(docUri);
|
|
59633
|
-
if (!docText) continue;
|
|
59634
|
-
const lines = docText.split("\n");
|
|
59635
|
-
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
59636
|
-
const line = lines[lineNum];
|
|
59637
|
-
widgetInvocationRegex.lastIndex = 0;
|
|
59638
|
-
let match2;
|
|
59639
|
-
while ((match2 = widgetInvocationRegex.exec(line)) !== null) {
|
|
59640
|
-
if (match2[1] && !isBlock) continue;
|
|
59641
|
-
if (match2[2].toLowerCase() === lowerName) {
|
|
59642
|
-
const nameStart = match2.index + 1 + (match2[1] ? 1 : 0);
|
|
59643
|
-
locations.push({
|
|
59644
|
-
uri: docUri,
|
|
59645
|
-
range: {
|
|
59646
|
-
start: { line: lineNum, character: nameStart },
|
|
59647
|
-
end: { line: lineNum, character: nameStart + match2[2].length }
|
|
59648
|
-
}
|
|
59649
|
-
});
|
|
59650
|
-
}
|
|
59651
|
-
}
|
|
59652
|
-
}
|
|
59653
|
-
}
|
|
59654
|
-
return locations;
|
|
59655
|
-
}
|
|
59656
|
-
function toLspRange3(r) {
|
|
59657
|
-
return {
|
|
59658
|
-
start: { line: r.start.line, character: r.start.character },
|
|
59659
|
-
end: { line: r.end.line, character: r.end.character }
|
|
59660
|
-
};
|
|
59661
|
-
}
|
|
59662
|
-
var referencesPlugin;
|
|
59663
|
-
var init_references = __esm({
|
|
59664
|
-
"src/plugins/references.ts"() {
|
|
59665
|
-
"use strict";
|
|
59666
|
-
init_link_parser();
|
|
59667
|
-
init_macro_sources();
|
|
59668
|
-
referencesPlugin = {
|
|
59669
|
-
id: "references",
|
|
59670
|
-
capabilities: {
|
|
59671
|
-
referencesProvider: true
|
|
59672
|
-
},
|
|
59673
|
-
initialize(ctx) {
|
|
59674
|
-
ctx.connection.onReferences((params) => {
|
|
59675
|
-
const refs = findReferences(
|
|
59676
|
-
params.textDocument.uri,
|
|
59677
|
-
{ line: params.position.line, character: params.position.character },
|
|
59678
|
-
ctx.workspace,
|
|
59679
|
-
params.context.includeDeclaration
|
|
59680
|
-
);
|
|
59681
|
-
return refs.map((r) => ({
|
|
59682
|
-
uri: r.uri,
|
|
59683
|
-
range: toLspRange3(r.range)
|
|
59684
|
-
}));
|
|
59685
|
-
});
|
|
59686
|
-
}
|
|
59687
|
-
};
|
|
59688
|
-
}
|
|
59689
|
-
});
|
|
59690
|
-
|
|
59691
61079
|
// src/plugins/rename.ts
|
|
59692
61080
|
function prepareRename(uri, position, workspace) {
|
|
59693
61081
|
const symbol2 = resolveSymbolAtCursor(uri, position, workspace);
|
|
@@ -59705,8 +61093,11 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
59705
61093
|
}
|
|
59706
61094
|
switch (symbol2.kind) {
|
|
59707
61095
|
case "passage": {
|
|
59708
|
-
const
|
|
59709
|
-
|
|
61096
|
+
const declaration = workspace.passages.getPassage(symbol2.name);
|
|
61097
|
+
if (declaration) {
|
|
61098
|
+
addEdit(declaration.uri, declaration.nameRange, escapePassageName(newName));
|
|
61099
|
+
}
|
|
61100
|
+
for (const ref of findPassageReferences(symbol2.name, workspace, false)) {
|
|
59710
61101
|
addEdit(ref.uri, ref.range, newName);
|
|
59711
61102
|
}
|
|
59712
61103
|
break;
|
|
@@ -59737,33 +61128,24 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
59737
61128
|
}
|
|
59738
61129
|
return edits;
|
|
59739
61130
|
}
|
|
61131
|
+
function escapePassageName(name) {
|
|
61132
|
+
return name.replace(/[[\]{}\\]/g, "\\$&");
|
|
61133
|
+
}
|
|
59740
61134
|
function resolveSymbolAtCursor(uri, position, workspace) {
|
|
59741
61135
|
const text = workspace.documents.getText(uri);
|
|
59742
61136
|
if (text === void 0) return null;
|
|
59743
61137
|
const lines = text.split("\n");
|
|
59744
61138
|
if (position.line >= lines.length) return null;
|
|
59745
61139
|
const line = lines[position.line];
|
|
59746
|
-
const
|
|
59747
|
-
if (
|
|
59748
|
-
const
|
|
59749
|
-
if (
|
|
59750
|
-
|
|
59751
|
-
const nameStart = line.indexOf(passageName);
|
|
59752
|
-
const nameEnd = nameStart + passageName.length;
|
|
59753
|
-
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
59754
|
-
return {
|
|
59755
|
-
kind: "passage",
|
|
59756
|
-
name: passageName,
|
|
59757
|
-
range: {
|
|
59758
|
-
start: { line: position.line, character: nameStart },
|
|
59759
|
-
end: { line: position.line, character: nameEnd }
|
|
59760
|
-
}
|
|
59761
|
-
};
|
|
59762
|
-
}
|
|
61140
|
+
const header = parsePassageHeader(line, position.line);
|
|
61141
|
+
if (header) {
|
|
61142
|
+
const { start, end } = header.nameRange;
|
|
61143
|
+
if (position.character >= start.character && position.character <= end.character) {
|
|
61144
|
+
return { kind: "passage", name: header.name, range: header.nameRange };
|
|
59763
61145
|
}
|
|
59764
61146
|
}
|
|
59765
61147
|
{
|
|
59766
|
-
const varRegex = /\$([
|
|
61148
|
+
const varRegex = /\$([\w$]+)/g;
|
|
59767
61149
|
let match2;
|
|
59768
61150
|
while ((match2 = varRegex.exec(line)) !== null) {
|
|
59769
61151
|
const start = match2.index;
|
|
@@ -59782,12 +61164,13 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
59782
61164
|
}
|
|
59783
61165
|
}
|
|
59784
61166
|
{
|
|
59785
|
-
const transRegex = /(?<!\w)%([
|
|
61167
|
+
const transRegex = /(?<!\w)%([\w$]+)/g;
|
|
59786
61168
|
let match2;
|
|
59787
61169
|
while ((match2 = transRegex.exec(line)) !== null) {
|
|
59788
61170
|
const start = match2.index;
|
|
59789
61171
|
const end = start + match2[0].length;
|
|
59790
61172
|
if (position.character >= start && position.character <= end) {
|
|
61173
|
+
if (!isTransientAt(match2[1], uri, position.line, start, workspace)) break;
|
|
59791
61174
|
return {
|
|
59792
61175
|
kind: "variable",
|
|
59793
61176
|
name: match2[1],
|
|
@@ -59846,6 +61229,7 @@ var init_rename = __esm({
|
|
|
59846
61229
|
"src/plugins/rename.ts"() {
|
|
59847
61230
|
"use strict";
|
|
59848
61231
|
init_link_parser();
|
|
61232
|
+
init_passage_parser();
|
|
59849
61233
|
init_references();
|
|
59850
61234
|
renamePlugin = {
|
|
59851
61235
|
id: "rename",
|
|
@@ -59898,9 +61282,9 @@ function computeCodeLenses(uri, workspace) {
|
|
|
59898
61282
|
const isStoryVarsFile = storyVarsPassage && storyVarsPassage.uri === uri;
|
|
59899
61283
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
59900
61284
|
const line = lines[lineNum];
|
|
59901
|
-
const
|
|
59902
|
-
if (
|
|
59903
|
-
const passageName =
|
|
61285
|
+
const header = parsePassageHeader(line, lineNum);
|
|
61286
|
+
if (header) {
|
|
61287
|
+
const passageName = header.name;
|
|
59904
61288
|
if (passageName === "StoryData") continue;
|
|
59905
61289
|
const refs = findPassageReferences(passageName, workspace, true);
|
|
59906
61290
|
const refCount = Math.max(0, refs.length - 1);
|
|
@@ -59931,7 +61315,7 @@ function computeCodeLenses(uri, workspace) {
|
|
|
59931
61315
|
});
|
|
59932
61316
|
}
|
|
59933
61317
|
if (isStoryVarsFile) {
|
|
59934
|
-
const varDeclMatch = line.match(/^\$(
|
|
61318
|
+
const varDeclMatch = line.match(/^\$(\w+)\s*=/);
|
|
59935
61319
|
if (varDeclMatch) {
|
|
59936
61320
|
const varName = varDeclMatch[1];
|
|
59937
61321
|
const refs = findVariableReferences(varName, workspace, true);
|
|
@@ -59960,6 +61344,7 @@ var codeLensPlugin;
|
|
|
59960
61344
|
var init_code_lens = __esm({
|
|
59961
61345
|
"src/plugins/code-lens.ts"() {
|
|
59962
61346
|
"use strict";
|
|
61347
|
+
init_passage_parser();
|
|
59963
61348
|
init_references();
|
|
59964
61349
|
codeLensPlugin = {
|
|
59965
61350
|
id: "code-lens",
|
|
@@ -60025,7 +61410,7 @@ function addVariableTypeHints(uri, text, range2, workspace, hints) {
|
|
|
60025
61410
|
const endLine = Math.min(range2.end.line, lines.length - 1);
|
|
60026
61411
|
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
|
|
60027
61412
|
const line = lines[lineNum];
|
|
60028
|
-
const declMatch = line.match(/^\$(
|
|
61413
|
+
const declMatch = line.match(/^\$(\w+)\s*=\s*(.+)$/);
|
|
60029
61414
|
if (!declMatch) continue;
|
|
60030
61415
|
const valueStr = declMatch[2].trim();
|
|
60031
61416
|
const inferredType = inferType(valueStr);
|
|
@@ -60047,7 +61432,7 @@ function addTransientTypeHints(uri, text, range2, workspace, hints) {
|
|
|
60047
61432
|
const endLine = Math.min(range2.end.line, lines.length - 1);
|
|
60048
61433
|
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
|
|
60049
61434
|
const line = lines[lineNum];
|
|
60050
|
-
const declMatch = line.match(/^%(
|
|
61435
|
+
const declMatch = line.match(/^%(\w+)\s*=\s*(.+)$/);
|
|
60051
61436
|
if (!declMatch) continue;
|
|
60052
61437
|
const valueStr = declMatch[2].trim();
|
|
60053
61438
|
const inferredType = inferType(valueStr);
|
|
@@ -60123,7 +61508,6 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60123
61508
|
for (const passage of passages) {
|
|
60124
61509
|
const headerLine = passage.headerEnd.start.line;
|
|
60125
61510
|
headerLines.add(headerLine);
|
|
60126
|
-
const rawLine = lines[headerLine] ?? "";
|
|
60127
61511
|
tokens.push({
|
|
60128
61512
|
line: headerLine,
|
|
60129
61513
|
startChar: 0,
|
|
@@ -60131,18 +61515,14 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60131
61515
|
tokenType: encodeType("namespace"),
|
|
60132
61516
|
tokenModifiers: 0
|
|
60133
61517
|
});
|
|
60134
|
-
const
|
|
60135
|
-
|
|
60136
|
-
|
|
60137
|
-
|
|
60138
|
-
|
|
60139
|
-
|
|
60140
|
-
|
|
60141
|
-
|
|
60142
|
-
tokenType: encodeType("namespace"),
|
|
60143
|
-
tokenModifiers: encodeModifiers(["declaration"])
|
|
60144
|
-
});
|
|
60145
|
-
}
|
|
61518
|
+
const { start, end } = passage.nameRange;
|
|
61519
|
+
tokens.push({
|
|
61520
|
+
line: headerLine,
|
|
61521
|
+
startChar: start.character,
|
|
61522
|
+
length: end.character - start.character,
|
|
61523
|
+
tokenType: encodeType("namespace"),
|
|
61524
|
+
tokenModifiers: encodeModifiers(["declaration"])
|
|
61525
|
+
});
|
|
60146
61526
|
}
|
|
60147
61527
|
const macros = parseMacros(text);
|
|
60148
61528
|
for (const macro of macros) {
|
|
@@ -60161,10 +61541,10 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60161
61541
|
tokenModifiers: encodeModifiers(isDefined ? ["defaultLibrary"] : [])
|
|
60162
61542
|
});
|
|
60163
61543
|
}
|
|
60164
|
-
const storyVarRegex = /(?<!\w)\$([
|
|
61544
|
+
const storyVarRegex = /(?<!\w)\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
60165
61545
|
const tempVarRegex = /(?<!\w)_([A-Za-z_$][\w$]*)/g;
|
|
60166
61546
|
const localVarRegex = /(?<!\w)@([A-Za-z_$][\w$]*)/g;
|
|
60167
|
-
const transientVarRegex = /(?<!\w)%([
|
|
61547
|
+
const transientVarRegex = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
60168
61548
|
const sugarKeywordRegex = /\b(to|is|isnot|eq|neq|gt|gte|lt|lte|and|or|not|def|ndef)\b/g;
|
|
60169
61549
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
60170
61550
|
if (headerLines.has(lineIndex)) continue;
|
|
@@ -60202,6 +61582,7 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60202
61582
|
}
|
|
60203
61583
|
transientVarRegex.lastIndex = 0;
|
|
60204
61584
|
while ((m = transientVarRegex.exec(line)) !== null) {
|
|
61585
|
+
if (!isTransientAt(m[1].split(".")[0], uri, lineIndex, m.index, workspace)) continue;
|
|
60205
61586
|
tokens.push({
|
|
60206
61587
|
line: lineIndex,
|
|
60207
61588
|
startChar: m.index,
|
|
@@ -60246,6 +61627,7 @@ var init_semantic_tokens = __esm({
|
|
|
60246
61627
|
"src/plugins/semantic-tokens.ts"() {
|
|
60247
61628
|
"use strict";
|
|
60248
61629
|
init_macro_parser();
|
|
61630
|
+
init_references();
|
|
60249
61631
|
tokenTypesLegend = [
|
|
60250
61632
|
"function",
|
|
60251
61633
|
"variable",
|
|
@@ -60295,7 +61677,7 @@ var init_semantic_tokens = __esm({
|
|
|
60295
61677
|
});
|
|
60296
61678
|
|
|
60297
61679
|
// src/plugins/code-actions.ts
|
|
60298
|
-
import { readFileSync as
|
|
61680
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
60299
61681
|
import { basename, dirname as dirname3, join as join4 } from "node:path";
|
|
60300
61682
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
60301
61683
|
function computeCodeActions(uri, diagnostics, workspace, options = {}) {
|
|
@@ -60327,6 +61709,11 @@ function computeCodeActions(uri, diagnostics, workspace, options = {}) {
|
|
|
60327
61709
|
if (action) actions.push(action);
|
|
60328
61710
|
break;
|
|
60329
61711
|
}
|
|
61712
|
+
case DiagnosticCode.UnevaluatedAttributeBlock: {
|
|
61713
|
+
const action = fixAttributeMacro(uri, diag, workspace);
|
|
61714
|
+
if (action) actions.push(action);
|
|
61715
|
+
break;
|
|
61716
|
+
}
|
|
60330
61717
|
}
|
|
60331
61718
|
}
|
|
60332
61719
|
return actions;
|
|
@@ -60360,7 +61747,7 @@ function fixUndefinedMacro(uri, diag, workspace, options) {
|
|
|
60360
61747
|
let text = workspace.documents.getText(configUri);
|
|
60361
61748
|
if (text === void 0) {
|
|
60362
61749
|
try {
|
|
60363
|
-
text =
|
|
61750
|
+
text = readFileSync9(existingPath, "utf-8");
|
|
60364
61751
|
} catch {
|
|
60365
61752
|
return null;
|
|
60366
61753
|
}
|
|
@@ -60569,6 +61956,21 @@ function fixNullVariableValue(uri, diag) {
|
|
|
60569
61956
|
}]
|
|
60570
61957
|
};
|
|
60571
61958
|
}
|
|
61959
|
+
function fixAttributeMacro(uri, diag, workspace) {
|
|
61960
|
+
const text = workspace.documents.getText(uri);
|
|
61961
|
+
if (text === void 0) return null;
|
|
61962
|
+
const lineStarts = buildLineStarts(text);
|
|
61963
|
+
const offset = (p) => (lineStarts[p.line] ?? text.length) + p.character;
|
|
61964
|
+
const source = text.slice(offset(diag.range.start), offset(diag.range.end));
|
|
61965
|
+
const expression = conditionalExpression(source) ?? printExpression(source);
|
|
61966
|
+
if (!expression) return null;
|
|
61967
|
+
return {
|
|
61968
|
+
title: `Rewrite as ${expression}`,
|
|
61969
|
+
kind: "quickfix",
|
|
61970
|
+
diagnosticCodes: [DiagnosticCode.UnevaluatedAttributeBlock],
|
|
61971
|
+
edits: [{ uri, range: diag.range, newText: expression }]
|
|
61972
|
+
};
|
|
61973
|
+
}
|
|
60572
61974
|
function toLspRange6(r) {
|
|
60573
61975
|
return {
|
|
60574
61976
|
start: { line: r.start.line, character: r.start.character },
|
|
@@ -60583,6 +61985,8 @@ var init_code_actions = __esm({
|
|
|
60583
61985
|
init_diagnostic_codes();
|
|
60584
61986
|
init_config_loader();
|
|
60585
61987
|
init_macro_sources();
|
|
61988
|
+
init_attribute_blocks();
|
|
61989
|
+
init_macro_parser();
|
|
60586
61990
|
DEFAULT_CONFIG_FILENAME = "spindle.config.yaml";
|
|
60587
61991
|
NEW_MACRO_ENTRY = { description: "" };
|
|
60588
61992
|
codeActionsPlugin = {
|
|
@@ -60691,7 +62095,7 @@ function computeDocumentSymbols(uri, workspace) {
|
|
|
60691
62095
|
const contentStart = passage.range.start.line + 1;
|
|
60692
62096
|
const contentEnd = passage.range.end.line + 1;
|
|
60693
62097
|
for (let i = contentStart; i < contentEnd && i < lines.length; i++) {
|
|
60694
|
-
const varMatch = lines[i].match(/^\$(
|
|
62098
|
+
const varMatch = lines[i].match(/^\$(\w+)\s*=/);
|
|
60695
62099
|
if (varMatch) {
|
|
60696
62100
|
const charEnd = varMatch[0].length;
|
|
60697
62101
|
passageSymbol.children.push({
|
|
@@ -60990,7 +62394,7 @@ __export(server_exports2, {
|
|
|
60990
62394
|
startServer: () => startServer
|
|
60991
62395
|
});
|
|
60992
62396
|
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
60993
|
-
import { readFileSync as
|
|
62397
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
60994
62398
|
import { basename as basename2 } from "node:path";
|
|
60995
62399
|
function uriToFsPath(uri) {
|
|
60996
62400
|
try {
|
|
@@ -61028,9 +62432,27 @@ function startServer(_args) {
|
|
|
61028
62432
|
for (const warning of workspace.macros.warnings) console.error("[spindle-lsp]", warning);
|
|
61029
62433
|
activePlugins = loadPlugins(allPlugins, config2);
|
|
61030
62434
|
console.error("[spindle-lsp] plugins loaded:", activePlugins.map((p) => p.id).join(", "));
|
|
62435
|
+
const model = workspace;
|
|
62436
|
+
const pluginConnection = gateConnection(connection, () => model.isSpindleProject());
|
|
61031
62437
|
for (const plugin of activePlugins) {
|
|
61032
|
-
plugin.initialize({ connection, workspace, config: config2 });
|
|
61033
|
-
}
|
|
62438
|
+
plugin.initialize({ connection: pluginConnection, workspace, config: config2 });
|
|
62439
|
+
}
|
|
62440
|
+
const refresh = params.capabilities.workspace;
|
|
62441
|
+
let spindle = workspace.isSpindleProject();
|
|
62442
|
+
workspace.on("storyFormatChanged", (format) => {
|
|
62443
|
+
if (format.isSpindle === spindle) return;
|
|
62444
|
+
spindle = format.isSpindle;
|
|
62445
|
+
console.error(format.isSpindle ? "[spindle-lsp] story format is Spindle; language features enabled" : `[spindle-lsp] story format "${format.name}" is not Spindle; language features disabled`);
|
|
62446
|
+
const requests = [
|
|
62447
|
+
refresh?.semanticTokens?.refreshSupport && "workspace/semanticTokens/refresh",
|
|
62448
|
+
refresh?.inlayHint?.refreshSupport && "workspace/inlayHint/refresh",
|
|
62449
|
+
refresh?.codeLens?.refreshSupport && "workspace/codeLens/refresh"
|
|
62450
|
+
];
|
|
62451
|
+
for (const method of requests) {
|
|
62452
|
+
if (method) connection.sendRequest(method).catch(() => {
|
|
62453
|
+
});
|
|
62454
|
+
}
|
|
62455
|
+
});
|
|
61034
62456
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
61035
62457
|
workspace.macros.loadConfig(projectConfig.macros);
|
|
61036
62458
|
console.error("[spindle-lsp] loaded config macros:", Object.keys(projectConfig.macros).length);
|
|
@@ -61097,7 +62519,7 @@ function startServer(_args) {
|
|
|
61097
62519
|
if (workspace) {
|
|
61098
62520
|
try {
|
|
61099
62521
|
const fsPath = uriToFsPath(textDocument.uri);
|
|
61100
|
-
const text =
|
|
62522
|
+
const text = readFileSync10(fsPath, "utf-8");
|
|
61101
62523
|
workspace.documents.update(textDocument.uri, text);
|
|
61102
62524
|
} catch {
|
|
61103
62525
|
workspace.documents.close(textDocument.uri);
|
|
@@ -61133,7 +62555,7 @@ function startServer(_args) {
|
|
|
61133
62555
|
} else {
|
|
61134
62556
|
try {
|
|
61135
62557
|
const fsPath = uriToFsPath(change.uri);
|
|
61136
|
-
const text =
|
|
62558
|
+
const text = readFileSync10(fsPath, "utf-8");
|
|
61137
62559
|
if (workspace.documents.has(change.uri)) {
|
|
61138
62560
|
workspace.documents.update(change.uri, text);
|
|
61139
62561
|
} else {
|
|
@@ -61169,7 +62591,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
61169
62591
|
];
|
|
61170
62592
|
for (const filePath of files) {
|
|
61171
62593
|
try {
|
|
61172
|
-
const text =
|
|
62594
|
+
const text = readFileSync10(filePath, "utf-8");
|
|
61173
62595
|
const uri = fsPathToUri(filePath);
|
|
61174
62596
|
contents.set(uri, text);
|
|
61175
62597
|
} catch {
|
|
@@ -61189,6 +62611,7 @@ var init_server4 = __esm({
|
|
|
61189
62611
|
init_workspace_model();
|
|
61190
62612
|
init_plugin_loader();
|
|
61191
62613
|
init_capabilities();
|
|
62614
|
+
init_story_format_gate();
|
|
61192
62615
|
init_plugins();
|
|
61193
62616
|
init_config_loader();
|
|
61194
62617
|
init_macro_sources();
|
|
@@ -61197,7 +62620,7 @@ var init_server4 = __esm({
|
|
|
61197
62620
|
|
|
61198
62621
|
// src/bin.ts
|
|
61199
62622
|
var args = process.argv.slice(2);
|
|
61200
|
-
var VERSION = "0.
|
|
62623
|
+
var VERSION = "0.11.0";
|
|
61201
62624
|
var HELP = `spindle-lsp v${VERSION}
|
|
61202
62625
|
Language Server Protocol server for Spindle story format
|
|
61203
62626
|
|