@rohal12/spindle-lsp 0.9.3 → 0.10.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 +1549 -400
- package/dist/bin.js.map +4 -4
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6777,7 +6777,7 @@ function parseMacros(text) {
|
|
|
6777
6777
|
let id = 0;
|
|
6778
6778
|
macroHeadRegex.lastIndex = 0;
|
|
6779
6779
|
let match2;
|
|
6780
|
-
while ((match2 = macroHeadRegex.exec(
|
|
6780
|
+
while ((match2 = macroHeadRegex.exec(text)) !== null) {
|
|
6781
6781
|
const matchStart = match2.index;
|
|
6782
6782
|
const closeIdx = scanner.closeBrace(matchStart + 1);
|
|
6783
6783
|
if (closeIdx === -1) {
|
|
@@ -6787,7 +6787,7 @@ function parseMacros(text) {
|
|
|
6787
6787
|
const matchEnd = closeIdx + 1;
|
|
6788
6788
|
macroHeadRegex.lastIndex = matchEnd;
|
|
6789
6789
|
const closeSlash = match2[1];
|
|
6790
|
-
const cssPrefix =
|
|
6790
|
+
const cssPrefix = match2[2] || "";
|
|
6791
6791
|
const macroName = match2[3];
|
|
6792
6792
|
const rawArgs = cleaned.slice(matchStart + match2[0].length, closeIdx).replace(/^\s+/, "");
|
|
6793
6793
|
const open = closeSlash !== "/";
|
|
@@ -6838,21 +6838,359 @@ function pairMacros(macros, isBlock, passageStartLines = []) {
|
|
|
6838
6838
|
}
|
|
6839
6839
|
}
|
|
6840
6840
|
}
|
|
6841
|
-
var variableInterpolationRegex, macroHeadRegex, NON_STRING_QUOTE_PREFIX;
|
|
6841
|
+
var variableInterpolationRegex, SELECTOR_PATTERN, macroHeadRegex, NON_STRING_QUOTE_PREFIX;
|
|
6842
6842
|
var init_macro_parser = __esm({
|
|
6843
6843
|
"src/core/parsing/macro-parser.ts"() {
|
|
6844
6844
|
"use strict";
|
|
6845
6845
|
variableInterpolationRegex = /(?<!\\)\{([$_@%][A-Za-z_$][\w$.]*)\}/g;
|
|
6846
|
-
|
|
6846
|
+
SELECTOR_PATTERN = String.raw`(?:[.#](?:[\w-]|\{[$_@][\w.]*\})*)+`;
|
|
6847
|
+
macroHeadRegex = new RegExp(
|
|
6848
|
+
String.raw`(?<!\\)\{(?:(\/)|(${SELECTOR_PATTERN}) )?([A-Za-z][\w-]*)(?=[\s}]|\{[$_@%][A-Za-z_$][\w$.]*\})`,
|
|
6849
|
+
"gi"
|
|
6850
|
+
);
|
|
6847
6851
|
NON_STRING_QUOTE_PREFIX = /[\p{L}\p{N}_\\]/u;
|
|
6848
6852
|
}
|
|
6849
6853
|
});
|
|
6850
6854
|
|
|
6855
|
+
// src/core/workspace/variable-schema.ts
|
|
6856
|
+
function inferDefaultSchema(expr) {
|
|
6857
|
+
const parser = new LiteralParser(expr.trim());
|
|
6858
|
+
try {
|
|
6859
|
+
const schema = parser.value();
|
|
6860
|
+
parser.skipSpace();
|
|
6861
|
+
return schema && parser.atEnd() ? schema : void 0;
|
|
6862
|
+
} catch (err) {
|
|
6863
|
+
if (err instanceof Unparsable) return void 0;
|
|
6864
|
+
throw err;
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
function findUnsupportedValue(expr) {
|
|
6868
|
+
const offset = expr.length - expr.trimStart().length;
|
|
6869
|
+
const parser = new LiteralParser(expr.trim());
|
|
6870
|
+
try {
|
|
6871
|
+
const read = parser.read();
|
|
6872
|
+
parser.skipSpace();
|
|
6873
|
+
const value = read.unsupported;
|
|
6874
|
+
if (!parser.atEnd() || !value) return void 0;
|
|
6875
|
+
return { ...value, start: value.start + offset, end: value.end + offset };
|
|
6876
|
+
} catch (err) {
|
|
6877
|
+
if (err instanceof Unparsable) return void 0;
|
|
6878
|
+
throw err;
|
|
6879
|
+
}
|
|
6880
|
+
}
|
|
6881
|
+
function bigintString(literal2) {
|
|
6882
|
+
const digits = literal2.slice(0, -1).replace(/_/g, "");
|
|
6883
|
+
const negative = digits.startsWith("-");
|
|
6884
|
+
try {
|
|
6885
|
+
const value = BigInt(negative ? digits.slice(1) : digits);
|
|
6886
|
+
return String(negative ? -value : value);
|
|
6887
|
+
} catch {
|
|
6888
|
+
return digits;
|
|
6889
|
+
}
|
|
6890
|
+
}
|
|
6891
|
+
function findPrimitiveFieldAccess(schema, fields) {
|
|
6892
|
+
let current = schema;
|
|
6893
|
+
for (let i = 0; i < fields.length; i++) {
|
|
6894
|
+
if (current.type === "array") return void 0;
|
|
6895
|
+
if (current.type !== "object") return { index: i, type: current.type };
|
|
6896
|
+
const next = current.fields?.get(fields[i]);
|
|
6897
|
+
if (!next) return void 0;
|
|
6898
|
+
current = next;
|
|
6899
|
+
}
|
|
6900
|
+
return void 0;
|
|
6901
|
+
}
|
|
6902
|
+
var Unparsable, IDENTIFIER_RE, BIGINT_RE, NUMBER_RE, INDEX_KEY_RE, UNKNOWN2, LiteralParser;
|
|
6903
|
+
var init_variable_schema = __esm({
|
|
6904
|
+
"src/core/workspace/variable-schema.ts"() {
|
|
6905
|
+
"use strict";
|
|
6906
|
+
Unparsable = class extends Error {
|
|
6907
|
+
};
|
|
6908
|
+
IDENTIFIER_RE = /[A-Za-z_$][\w$]*/y;
|
|
6909
|
+
BIGINT_RE = /-?(?:0[xX][\da-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+|0|[1-9][\d_]*)n(?![\w$])/y;
|
|
6910
|
+
NUMBER_RE = /[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?/y;
|
|
6911
|
+
INDEX_KEY_RE = /(?:0|[1-9]\d{0,14})(?![\w$.])/y;
|
|
6912
|
+
UNKNOWN2 = { schema: null };
|
|
6913
|
+
LiteralParser = class {
|
|
6914
|
+
constructor(text) {
|
|
6915
|
+
this.text = text;
|
|
6916
|
+
}
|
|
6917
|
+
pos = 0;
|
|
6918
|
+
atEnd() {
|
|
6919
|
+
return this.pos >= this.text.length;
|
|
6920
|
+
}
|
|
6921
|
+
skipSpace() {
|
|
6922
|
+
while (/\s/.test(this.text[this.pos] ?? "")) this.pos++;
|
|
6923
|
+
}
|
|
6924
|
+
/** A value up to the `,` / `}` / `]` that ends it; null when its type is unknown. */
|
|
6925
|
+
value() {
|
|
6926
|
+
return this.read().schema;
|
|
6927
|
+
}
|
|
6928
|
+
/** A value up to the `,` / `}` / `]` that ends it. */
|
|
6929
|
+
read() {
|
|
6930
|
+
this.skipSpace();
|
|
6931
|
+
const start = this.pos;
|
|
6932
|
+
const read = this.literal();
|
|
6933
|
+
this.skipSpace();
|
|
6934
|
+
const next = this.text[this.pos];
|
|
6935
|
+
if (read && (next === void 0 || next === "," || next === "}" || next === "]")) return read;
|
|
6936
|
+
this.pos = start;
|
|
6937
|
+
this.skipExpression();
|
|
6938
|
+
return UNKNOWN2;
|
|
6939
|
+
}
|
|
6940
|
+
/** A literal, consumed; undefined when the code here is not one. */
|
|
6941
|
+
literal() {
|
|
6942
|
+
const start = this.pos;
|
|
6943
|
+
const ch = this.text[this.pos];
|
|
6944
|
+
if (ch === "{") return this.object();
|
|
6945
|
+
if (ch === "[") {
|
|
6946
|
+
this.pos++;
|
|
6947
|
+
this.skipList("]");
|
|
6948
|
+
return { schema: { type: "array" } };
|
|
6949
|
+
}
|
|
6950
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
6951
|
+
this.stringLiteral();
|
|
6952
|
+
return { schema: { type: "string" } };
|
|
6953
|
+
}
|
|
6954
|
+
if (ch === "(") return this.functionLiteral(start);
|
|
6955
|
+
const word = this.match(IDENTIFIER_RE);
|
|
6956
|
+
if (word === "true" || word === "false") return { schema: { type: "boolean" } };
|
|
6957
|
+
if (word === "null") return this.unsupported("object", start);
|
|
6958
|
+
if (word === "undefined") return this.unsupported("undefined", start);
|
|
6959
|
+
if (word === "void") {
|
|
6960
|
+
this.skipSpace();
|
|
6961
|
+
if (this.match(NUMBER_RE) === void 0) return void 0;
|
|
6962
|
+
return this.unsupported("undefined", start, "undefined");
|
|
6963
|
+
}
|
|
6964
|
+
if (word !== void 0) return this.functionLiteral(start);
|
|
6965
|
+
if (this.match(BIGINT_RE) !== void 0) {
|
|
6966
|
+
return this.unsupported("bigint", start, bigintString(this.text.slice(start, this.pos)));
|
|
6967
|
+
}
|
|
6968
|
+
if (this.match(NUMBER_RE) !== void 0) return { schema: { type: "number" } };
|
|
6969
|
+
return void 0;
|
|
6970
|
+
}
|
|
6971
|
+
/** The value from `start` to here, which Spindle rejects. */
|
|
6972
|
+
unsupported(type, start, text) {
|
|
6973
|
+
const end = this.pos;
|
|
6974
|
+
return { schema: null, unsupported: { type, text: text ?? this.text.slice(start, end), path: [], start, end } };
|
|
6975
|
+
}
|
|
6976
|
+
/**
|
|
6977
|
+
* A function, arrow function or class expression at `start`, consumed;
|
|
6978
|
+
* undefined, with nothing consumed, for any other code.
|
|
6979
|
+
*/
|
|
6980
|
+
functionLiteral(start) {
|
|
6981
|
+
this.pos = start;
|
|
6982
|
+
try {
|
|
6983
|
+
if (this.functionSyntax()) return this.unsupported("function", start);
|
|
6984
|
+
} catch (err) {
|
|
6985
|
+
if (!(err instanceof Unparsable)) throw err;
|
|
6986
|
+
}
|
|
6987
|
+
this.pos = start;
|
|
6988
|
+
return void 0;
|
|
6989
|
+
}
|
|
6990
|
+
functionSyntax() {
|
|
6991
|
+
let word = this.match(IDENTIFIER_RE);
|
|
6992
|
+
if (word === "async") {
|
|
6993
|
+
this.skipSpace();
|
|
6994
|
+
word = this.match(IDENTIFIER_RE);
|
|
6995
|
+
}
|
|
6996
|
+
this.skipSpace();
|
|
6997
|
+
if (word === "function") {
|
|
6998
|
+
if (this.text[this.pos] === "*") this.pos++;
|
|
6999
|
+
this.skipSpace();
|
|
7000
|
+
this.match(IDENTIFIER_RE);
|
|
7001
|
+
this.skipSpace();
|
|
7002
|
+
if (!this.skipBracketed("(", ")")) return false;
|
|
7003
|
+
this.skipSpace();
|
|
7004
|
+
return this.skipBracketed("{", "}");
|
|
7005
|
+
}
|
|
7006
|
+
if (word === "class") {
|
|
7007
|
+
if (this.match(IDENTIFIER_RE) !== void 0) this.skipSpace();
|
|
7008
|
+
return this.skipBracketed("{", "}");
|
|
7009
|
+
}
|
|
7010
|
+
if (word === void 0 && !this.skipBracketed("(", ")")) return false;
|
|
7011
|
+
this.skipSpace();
|
|
7012
|
+
if (!this.text.startsWith("=>", this.pos)) return false;
|
|
7013
|
+
this.pos += 2;
|
|
7014
|
+
this.skipSpace();
|
|
7015
|
+
if (this.text[this.pos] === "{") return this.skipBracketed("{", "}");
|
|
7016
|
+
const bodyStart = this.pos;
|
|
7017
|
+
this.skipExpression();
|
|
7018
|
+
while (this.pos > bodyStart && /\s/.test(this.text[this.pos - 1])) this.pos--;
|
|
7019
|
+
return this.pos > bodyStart;
|
|
7020
|
+
}
|
|
7021
|
+
/** Skip a bracketed list that opens here; false if none does. */
|
|
7022
|
+
skipBracketed(open, close) {
|
|
7023
|
+
if (this.text[this.pos] !== open) return false;
|
|
7024
|
+
this.pos++;
|
|
7025
|
+
this.skipList(close);
|
|
7026
|
+
return true;
|
|
7027
|
+
}
|
|
7028
|
+
object() {
|
|
7029
|
+
this.pos++;
|
|
7030
|
+
let fields = /* @__PURE__ */ new Map();
|
|
7031
|
+
const unsupported = /* @__PURE__ */ new Map();
|
|
7032
|
+
for (; ; ) {
|
|
7033
|
+
this.skipSpace();
|
|
7034
|
+
if (this.text[this.pos] === "}") {
|
|
7035
|
+
this.pos++;
|
|
7036
|
+
break;
|
|
7037
|
+
}
|
|
7038
|
+
const key = this.propertyKey();
|
|
7039
|
+
this.skipSpace();
|
|
7040
|
+
if (key !== void 0 && key !== "__proto__" && this.text[this.pos] === ":") {
|
|
7041
|
+
this.pos++;
|
|
7042
|
+
const read = this.read();
|
|
7043
|
+
fields?.set(key, read.schema);
|
|
7044
|
+
unsupported.delete(key);
|
|
7045
|
+
if (read.unsupported) {
|
|
7046
|
+
unsupported.set(key, { ...read.unsupported, path: [key, ...read.unsupported.path] });
|
|
7047
|
+
}
|
|
7048
|
+
} else {
|
|
7049
|
+
fields = void 0;
|
|
7050
|
+
this.skipExpression();
|
|
7051
|
+
}
|
|
7052
|
+
this.skipSpace();
|
|
7053
|
+
const sep2 = this.text[this.pos++];
|
|
7054
|
+
if (sep2 === "}") break;
|
|
7055
|
+
if (sep2 !== ",") throw new Unparsable();
|
|
7056
|
+
}
|
|
7057
|
+
if (!fields) return { schema: { type: "object" } };
|
|
7058
|
+
const [first] = unsupported.values();
|
|
7059
|
+
return { schema: { type: "object", fields }, unsupported: first };
|
|
7060
|
+
}
|
|
7061
|
+
/** A plain property key, consumed; undefined (possibly consumed) for any other. */
|
|
7062
|
+
propertyKey() {
|
|
7063
|
+
const ch = this.text[this.pos];
|
|
7064
|
+
if (ch === '"' || ch === "'") {
|
|
7065
|
+
const raw = this.stringLiteral();
|
|
7066
|
+
return raw.includes("\\") ? void 0 : raw;
|
|
7067
|
+
}
|
|
7068
|
+
return this.match(IDENTIFIER_RE) ?? this.match(INDEX_KEY_RE);
|
|
7069
|
+
}
|
|
7070
|
+
/** Consume a string or template literal and return its raw text. */
|
|
7071
|
+
stringLiteral() {
|
|
7072
|
+
const quote = this.text[this.pos];
|
|
7073
|
+
const start = ++this.pos;
|
|
7074
|
+
while (this.pos < this.text.length) {
|
|
7075
|
+
const ch = this.text[this.pos];
|
|
7076
|
+
if (ch === "\\") {
|
|
7077
|
+
this.pos += 2;
|
|
7078
|
+
continue;
|
|
7079
|
+
}
|
|
7080
|
+
if (quote === "`" && ch === "$" && this.text[this.pos + 1] === "{") throw new Unparsable();
|
|
7081
|
+
if (ch === quote) return this.text.slice(start, this.pos++);
|
|
7082
|
+
this.pos++;
|
|
7083
|
+
}
|
|
7084
|
+
throw new Unparsable();
|
|
7085
|
+
}
|
|
7086
|
+
/** Skip comma-separated expressions up to and including `close`. */
|
|
7087
|
+
skipList(close) {
|
|
7088
|
+
for (; ; ) {
|
|
7089
|
+
this.skipExpression();
|
|
7090
|
+
const ch = this.text[this.pos++];
|
|
7091
|
+
if (ch === close) return;
|
|
7092
|
+
if (ch !== ",") throw new Unparsable();
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
/** Skip code up to a `,`, `)`, `]` or `}` that closes nothing, or the end. */
|
|
7096
|
+
skipExpression() {
|
|
7097
|
+
let depth = 0;
|
|
7098
|
+
while (this.pos < this.text.length) {
|
|
7099
|
+
const ch = this.text[this.pos];
|
|
7100
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
7101
|
+
this.stringLiteral();
|
|
7102
|
+
continue;
|
|
7103
|
+
}
|
|
7104
|
+
if (ch === "/") throw new Unparsable();
|
|
7105
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
7106
|
+
else if (ch === ")" || ch === "]" || ch === "}") {
|
|
7107
|
+
if (depth === 0) return;
|
|
7108
|
+
depth--;
|
|
7109
|
+
} else if (ch === "," && depth === 0) return;
|
|
7110
|
+
this.pos++;
|
|
7111
|
+
}
|
|
7112
|
+
if (depth > 0) throw new Unparsable();
|
|
7113
|
+
}
|
|
7114
|
+
match(re) {
|
|
7115
|
+
re.lastIndex = this.pos;
|
|
7116
|
+
const m = re.exec(this.text);
|
|
7117
|
+
if (!m) return void 0;
|
|
7118
|
+
this.pos += m[0].length;
|
|
7119
|
+
return m[0];
|
|
7120
|
+
}
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
});
|
|
7124
|
+
|
|
7125
|
+
// src/core/workspace/declaration-check.ts
|
|
7126
|
+
function declaredName(line, sigil) {
|
|
7127
|
+
return DECLARATION_RE[sigil].exec(line.trim())?.[1];
|
|
7128
|
+
}
|
|
7129
|
+
function checkDeclaration(rawLine, sigil) {
|
|
7130
|
+
const line = rawLine.trim();
|
|
7131
|
+
if (!line || line.includes("\r") || line.includes("\xA0")) return void 0;
|
|
7132
|
+
const passage = PASSAGE[sigil];
|
|
7133
|
+
const match2 = DECLARATION_RE[sigil].exec(line);
|
|
7134
|
+
if (!match2) {
|
|
7135
|
+
const comment = /^(?:\/\/|\/\*|<!--|#)/.test(line) ? ` ${passage} has no comment syntax.` : "";
|
|
7136
|
+
return {
|
|
7137
|
+
kind: "invalid",
|
|
7138
|
+
message: `${passage}: Invalid declaration: "${line}". Expected: ${sigil}name = value.${comment}`
|
|
7139
|
+
};
|
|
7140
|
+
}
|
|
7141
|
+
const [, name, expr] = match2;
|
|
7142
|
+
const error48 = compileError(expr);
|
|
7143
|
+
if (error48 !== void 0) {
|
|
7144
|
+
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.` : "";
|
|
7145
|
+
return {
|
|
7146
|
+
kind: "evaluate",
|
|
7147
|
+
message: `${passage}: Failed to evaluate "${sigil}${name} = ${expr}": ${error48}.${comment}`
|
|
7148
|
+
};
|
|
7149
|
+
}
|
|
7150
|
+
const value = findUnsupportedValue(expr);
|
|
7151
|
+
if (!value) return void 0;
|
|
7152
|
+
const exprStart = rawLine.length - rawLine.trimStart().length + line.length - expr.length;
|
|
7153
|
+
if (value.type === "object") {
|
|
7154
|
+
return { kind: "null", field: value.path, start: exprStart + value.start, end: exprStart + value.end };
|
|
7155
|
+
}
|
|
7156
|
+
const where = value.path.length > 0 ? ` (${sigil}${[name, ...value.path].join(".")})` : "";
|
|
7157
|
+
return {
|
|
7158
|
+
kind: "unsupported",
|
|
7159
|
+
message: `${passage}: Unsupported type "${value.type}" for value ${value.text}${where}. Expected number, string, boolean, array, or object.`
|
|
7160
|
+
};
|
|
7161
|
+
}
|
|
7162
|
+
function compileError(expr) {
|
|
7163
|
+
try {
|
|
7164
|
+
new Function("return (" + expr + ")");
|
|
7165
|
+
return void 0;
|
|
7166
|
+
} catch (err) {
|
|
7167
|
+
if (!(err instanceof SyntaxError) || /regular expression/i.test(err.message)) return void 0;
|
|
7168
|
+
return err.message;
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
var DECLARATION_RE, PASSAGE;
|
|
7172
|
+
var init_declaration_check = __esm({
|
|
7173
|
+
"src/core/workspace/declaration-check.ts"() {
|
|
7174
|
+
"use strict";
|
|
7175
|
+
init_variable_schema();
|
|
7176
|
+
DECLARATION_RE = { "$": /^\$(\w+)\s*=\s*(.+)$/, "%": /^%(\w+)\s*=\s*(.+)$/ };
|
|
7177
|
+
PASSAGE = { "$": "StoryVariables", "%": "StoryTransients" };
|
|
7178
|
+
}
|
|
7179
|
+
});
|
|
7180
|
+
|
|
6851
7181
|
// src/core/workspace/variable-tracker.ts
|
|
6852
7182
|
function replaceCodeLiterals(text, replace) {
|
|
6853
7183
|
const scanner = createCodeScanner(text);
|
|
6854
7184
|
let result = "";
|
|
6855
7185
|
let copied = 0;
|
|
7186
|
+
for (const [open, close] of codeBlocks(text, scanner)) {
|
|
7187
|
+
result += text.slice(copied, open + 1) + replaceLiterals(text, scanner, open + 1, close, replace);
|
|
7188
|
+
copied = close;
|
|
7189
|
+
}
|
|
7190
|
+
return result + text.slice(copied);
|
|
7191
|
+
}
|
|
7192
|
+
function codeBlocks(text, scanner) {
|
|
7193
|
+
const blocks = [];
|
|
6856
7194
|
for (let i = 0; i < text.length; i++) {
|
|
6857
7195
|
if (text[i] === "\\" && (text[i + 1] === "{" || text[i + 1] === "}")) {
|
|
6858
7196
|
i++;
|
|
@@ -6861,11 +7199,10 @@ function replaceCodeLiterals(text, replace) {
|
|
|
6861
7199
|
if (text[i] !== "{") continue;
|
|
6862
7200
|
const close = scanner.closeBrace(i + 1);
|
|
6863
7201
|
if (close === -1) continue;
|
|
6864
|
-
|
|
6865
|
-
copied = close;
|
|
7202
|
+
blocks.push([i, close]);
|
|
6866
7203
|
i = close;
|
|
6867
7204
|
}
|
|
6868
|
-
return
|
|
7205
|
+
return blocks;
|
|
6869
7206
|
}
|
|
6870
7207
|
function replaceLiterals(text, scanner, from, to, replace) {
|
|
6871
7208
|
let result = "";
|
|
@@ -6891,7 +7228,7 @@ function blankLiteralText(literal2) {
|
|
|
6891
7228
|
for (let i = 1; i < literal2.length - 1; i++) {
|
|
6892
7229
|
if (literal2[i] !== "{") continue;
|
|
6893
7230
|
const templateCode = literal2[0] === "`" && literal2[i - 1] === "$" && literal2[i - 2] !== "\\";
|
|
6894
|
-
if (!templateCode && !/^[$%][
|
|
7231
|
+
if (!templateCode && !/^[$%][\w$]/.test(literal2.slice(i + 1, i + 3))) continue;
|
|
6895
7232
|
let depth = 0;
|
|
6896
7233
|
for (let j = i; j < literal2.length - 1; j++) {
|
|
6897
7234
|
if (literal2[j] === "{") depth++;
|
|
@@ -6904,6 +7241,43 @@ function blankLiteralText(literal2) {
|
|
|
6904
7241
|
}
|
|
6905
7242
|
return literal2.replace(/[^\r\n]/g, (ch, i) => keep[i] ? ch : " ");
|
|
6906
7243
|
}
|
|
7244
|
+
function checkDeclarations(lines, contentStartLine, sigil) {
|
|
7245
|
+
const check2 = { problems: [], nestedNulls: [], names: /* @__PURE__ */ new Map() };
|
|
7246
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7247
|
+
const line = lines[i];
|
|
7248
|
+
if (line.startsWith("::")) break;
|
|
7249
|
+
const absLine = contentStartLine + i;
|
|
7250
|
+
const problem = checkDeclaration(line, sigil);
|
|
7251
|
+
if (!problem) {
|
|
7252
|
+
const name = /[\r\u00a0]/.test(line.trim()) ? void 0 : declaredName(line, sigil);
|
|
7253
|
+
if (name !== void 0 && !check2.names.has(name)) {
|
|
7254
|
+
check2.names.set(name, declarationLocation(line, name, absLine, void 0).declarationRange);
|
|
7255
|
+
}
|
|
7256
|
+
} else if (problem.kind === "null") {
|
|
7257
|
+
if (problem.field.length > 0) {
|
|
7258
|
+
check2.nestedNulls.push({
|
|
7259
|
+
name: declaredName(line, sigil),
|
|
7260
|
+
sigil,
|
|
7261
|
+
field: problem.field,
|
|
7262
|
+
range: {
|
|
7263
|
+
start: { line: absLine, character: problem.start },
|
|
7264
|
+
end: { line: absLine, character: problem.end }
|
|
7265
|
+
}
|
|
7266
|
+
});
|
|
7267
|
+
}
|
|
7268
|
+
} else {
|
|
7269
|
+
const start = line.length - line.trimStart().length;
|
|
7270
|
+
check2.problems.push({
|
|
7271
|
+
message: problem.message,
|
|
7272
|
+
range: {
|
|
7273
|
+
start: { line: absLine, character: start },
|
|
7274
|
+
end: { line: absLine, character: line.trimEnd().length }
|
|
7275
|
+
}
|
|
7276
|
+
});
|
|
7277
|
+
}
|
|
7278
|
+
}
|
|
7279
|
+
return check2;
|
|
7280
|
+
}
|
|
6907
7281
|
function findLiteralEnd(text, start) {
|
|
6908
7282
|
const open = text[start];
|
|
6909
7283
|
if (open === '"' || open === "'" || open === "`") {
|
|
@@ -6960,14 +7334,48 @@ function declarationLocation(line, name, absLine, uri) {
|
|
|
6960
7334
|
if (uri !== void 0) location.declarationUri = uri;
|
|
6961
7335
|
return location;
|
|
6962
7336
|
}
|
|
6963
|
-
|
|
7337
|
+
function validatedReferences(lines, passages) {
|
|
7338
|
+
const refs = [];
|
|
7339
|
+
for (let pi = 0; pi < passages.length; pi++) {
|
|
7340
|
+
const passage = passages[pi];
|
|
7341
|
+
if (UNVALIDATED_PASSAGES.has(passage.name) || isScriptOrStylesheetPassage(passage)) continue;
|
|
7342
|
+
const contentStartLine = passage.startLine + 1;
|
|
7343
|
+
const contentEndLine = pi + 1 < passages.length ? passages[pi + 1].startLine : lines.length;
|
|
7344
|
+
const contentLines = lines.slice(contentStartLine, contentEndLine);
|
|
7345
|
+
const forLocals = /* @__PURE__ */ new Set();
|
|
7346
|
+
for (const m of contentLines.join("\n").matchAll(VALIDATED_FOR_LOCAL_RE)) {
|
|
7347
|
+
forLocals.add(m[1]);
|
|
7348
|
+
if (m[2]) forLocals.add(m[2]);
|
|
7349
|
+
}
|
|
7350
|
+
for (let i = 0; i < contentLines.length; i++) {
|
|
7351
|
+
for (const m of contentLines[i].matchAll(VALIDATED_REF_RE)) {
|
|
7352
|
+
const baseName = m[1].split(".")[0];
|
|
7353
|
+
if (forLocals.has(baseName)) continue;
|
|
7354
|
+
const line = contentStartLine + i;
|
|
7355
|
+
refs.push({
|
|
7356
|
+
baseName,
|
|
7357
|
+
path: m[1],
|
|
7358
|
+
range: {
|
|
7359
|
+
start: { line, character: m.index },
|
|
7360
|
+
end: { line, character: m.index + m[0].length }
|
|
7361
|
+
}
|
|
7362
|
+
});
|
|
7363
|
+
}
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
return refs;
|
|
7367
|
+
}
|
|
7368
|
+
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
7369
|
var init_variable_tracker = __esm({
|
|
6965
7370
|
"src/core/workspace/variable-tracker.ts"() {
|
|
6966
7371
|
"use strict";
|
|
6967
7372
|
init_passage_parser();
|
|
6968
7373
|
init_macro_parser();
|
|
6969
|
-
|
|
6970
|
-
|
|
7374
|
+
init_variable_schema();
|
|
7375
|
+
init_declaration_check();
|
|
7376
|
+
varRefRegex = /\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
7377
|
+
transientRefRegex = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
7378
|
+
DECLARATION_RE2 = { "$": /^\$(\w+)\s*=\s*(.*)$/, "%": /^%(\w+)\s*=\s*(.*)$/ };
|
|
6971
7379
|
EXCLUDED_PASSAGES = /* @__PURE__ */ new Set([
|
|
6972
7380
|
"StoryVariables",
|
|
6973
7381
|
"StoryTransients",
|
|
@@ -6975,7 +7383,14 @@ var init_variable_tracker = __esm({
|
|
|
6975
7383
|
"StoryScript",
|
|
6976
7384
|
"StoryInterface"
|
|
6977
7385
|
]);
|
|
6978
|
-
|
|
7386
|
+
VALIDATED_REF_RE = /\$(\w+(?:\.\w+)*)/g;
|
|
7387
|
+
VALIDATED_FOR_LOCAL_RE = /\{for\s+@(\w+)(?:\s*,\s*@(\w+))?\s+of\b/g;
|
|
7388
|
+
UNVALIDATED_PASSAGES = /* @__PURE__ */ new Set([
|
|
7389
|
+
"StoryVariables",
|
|
7390
|
+
"StoryTransients",
|
|
7391
|
+
"StoryData",
|
|
7392
|
+
"StoryTitle"
|
|
7393
|
+
]);
|
|
6979
7394
|
COMMENT_PATTERNS = [
|
|
6980
7395
|
/<!--[\s\S]*?-->/g,
|
|
6981
7396
|
// HTML comments
|
|
@@ -6993,7 +7408,11 @@ var init_variable_tracker = __esm({
|
|
|
6993
7408
|
"textarea",
|
|
6994
7409
|
"textbox"
|
|
6995
7410
|
]);
|
|
6996
|
-
QUOTED_RECEIVER_RE =
|
|
7411
|
+
QUOTED_RECEIVER_RE = new RegExp(
|
|
7412
|
+
String.raw`(?<!\\)(\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\s+(["']))\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)\3?(?=[\s}])`,
|
|
7413
|
+
"g"
|
|
7414
|
+
);
|
|
7415
|
+
NO_DECLARATIONS = { problems: [], nestedNulls: [], names: /* @__PURE__ */ new Map() };
|
|
6997
7416
|
ARRAY_MEMBERS = /* @__PURE__ */ new Set([
|
|
6998
7417
|
...Object.getOwnPropertyNames(Array.prototype),
|
|
6999
7418
|
...Object.getOwnPropertyNames(Object.prototype),
|
|
@@ -7012,13 +7431,17 @@ var init_variable_tracker = __esm({
|
|
|
7012
7431
|
declared = /* @__PURE__ */ new Map();
|
|
7013
7432
|
_hasStoryVariables = false;
|
|
7014
7433
|
_nullDeclarations = [];
|
|
7434
|
+
_variablesCheck = NO_DECLARATIONS;
|
|
7015
7435
|
declaredTransient = /* @__PURE__ */ new Map();
|
|
7016
7436
|
_hasStoryTransients = false;
|
|
7017
7437
|
_nullTransientDeclarations = [];
|
|
7438
|
+
_transientsCheck = NO_DECLARATIONS;
|
|
7018
7439
|
/** Per-URI list of variable usages. */
|
|
7019
7440
|
usagesByUri = /* @__PURE__ */ new Map();
|
|
7020
7441
|
/** Per-URI list of transient variable usages. */
|
|
7021
7442
|
transientUsagesByUri = /* @__PURE__ */ new Map();
|
|
7443
|
+
/** Per-URI list of the `$` references Spindle validates at startup. */
|
|
7444
|
+
validatedRefsByUri = /* @__PURE__ */ new Map();
|
|
7022
7445
|
/**
|
|
7023
7446
|
* Parse the StoryVariables passage content for declarations.
|
|
7024
7447
|
* Each line like `$name = value` becomes a declaration.
|
|
@@ -7028,10 +7451,11 @@ var init_variable_tracker = __esm({
|
|
|
7028
7451
|
this._hasStoryVariables = true;
|
|
7029
7452
|
this._nullDeclarations = [];
|
|
7030
7453
|
const lines = content.split("\n");
|
|
7454
|
+
this._variablesCheck = checkDeclarations(lines, contentStartLine, "$");
|
|
7031
7455
|
for (let i = 0; i < lines.length; i++) {
|
|
7032
7456
|
const trimmed2 = lines[i].trim();
|
|
7033
7457
|
if (!trimmed2 || trimmed2.startsWith("//") || trimmed2.startsWith("<!--")) continue;
|
|
7034
|
-
const match2 =
|
|
7458
|
+
const match2 = DECLARATION_RE2.$.exec(trimmed2);
|
|
7035
7459
|
if (!match2) continue;
|
|
7036
7460
|
const name = match2[1];
|
|
7037
7461
|
const expr = match2[2].trim();
|
|
@@ -7053,6 +7477,8 @@ var init_variable_tracker = __esm({
|
|
|
7053
7477
|
const decl = { name, sigil: "$", ...location };
|
|
7054
7478
|
const type = inferLiteralType(expr);
|
|
7055
7479
|
if (type) decl.type = type;
|
|
7480
|
+
const schema = inferDefaultSchema(expr);
|
|
7481
|
+
if (schema) decl.schema = schema;
|
|
7056
7482
|
if (expr.startsWith("{")) {
|
|
7057
7483
|
const fieldRegex = /(\w+)\s*:/g;
|
|
7058
7484
|
let fieldMatch;
|
|
@@ -7072,12 +7498,14 @@ var init_variable_tracker = __esm({
|
|
|
7072
7498
|
this.declared.clear();
|
|
7073
7499
|
this._hasStoryVariables = false;
|
|
7074
7500
|
this._nullDeclarations = [];
|
|
7501
|
+
this._variablesCheck = NO_DECLARATIONS;
|
|
7075
7502
|
}
|
|
7076
7503
|
/** Forget StoryTransients declarations, e.g. after the passage was removed. */
|
|
7077
7504
|
clearStoryTransients() {
|
|
7078
7505
|
this.declaredTransient.clear();
|
|
7079
7506
|
this._hasStoryTransients = false;
|
|
7080
7507
|
this._nullTransientDeclarations = [];
|
|
7508
|
+
this._transientsCheck = NO_DECLARATIONS;
|
|
7081
7509
|
}
|
|
7082
7510
|
/**
|
|
7083
7511
|
* Parse the StoryTransients passage content for declarations.
|
|
@@ -7088,10 +7516,11 @@ var init_variable_tracker = __esm({
|
|
|
7088
7516
|
this._hasStoryTransients = true;
|
|
7089
7517
|
this._nullTransientDeclarations = [];
|
|
7090
7518
|
const lines = content.split("\n");
|
|
7519
|
+
this._transientsCheck = checkDeclarations(lines, contentStartLine, "%");
|
|
7091
7520
|
for (let i = 0; i < lines.length; i++) {
|
|
7092
7521
|
const trimmed2 = lines[i].trim();
|
|
7093
7522
|
if (!trimmed2 || trimmed2.startsWith("//") || trimmed2.startsWith("<!--")) continue;
|
|
7094
|
-
const match2 =
|
|
7523
|
+
const match2 = DECLARATION_RE2["%"].exec(trimmed2);
|
|
7095
7524
|
if (!match2) continue;
|
|
7096
7525
|
const name = match2[1];
|
|
7097
7526
|
const expr = match2[2].trim();
|
|
@@ -7136,6 +7565,7 @@ var init_variable_tracker = __esm({
|
|
|
7136
7565
|
scanDocument(uri, text, _macros, storeVarMacros = BUILTIN_STORE_VAR_MACROS) {
|
|
7137
7566
|
this.usagesByUri.delete(uri);
|
|
7138
7567
|
this.transientUsagesByUri.delete(uri);
|
|
7568
|
+
this.validatedRefsByUri.delete(uri);
|
|
7139
7569
|
const lines = text.split("\n");
|
|
7140
7570
|
const usages = [];
|
|
7141
7571
|
const transientUsages = [];
|
|
@@ -7170,14 +7600,15 @@ var init_variable_tracker = __esm({
|
|
|
7170
7600
|
for (let i = 0; i < cleaned.length; i++) {
|
|
7171
7601
|
if (cleaned[i] === "\n") lineOffsets.push(i + 1);
|
|
7172
7602
|
}
|
|
7173
|
-
const
|
|
7603
|
+
const blocks = codeBlocks(uncommented, createCodeScanner(uncommented));
|
|
7604
|
+
const inCode = (offset) => blocks.some(([open, close]) => open < offset && offset < close);
|
|
7174
7605
|
const scans = [
|
|
7175
|
-
[varRefRegex, usages],
|
|
7176
|
-
[transientRefRegex, transientUsages]
|
|
7606
|
+
[varRefRegex, usages, false],
|
|
7607
|
+
[transientRefRegex, transientUsages, true]
|
|
7177
7608
|
];
|
|
7178
|
-
for (const [regex, out] of scans) {
|
|
7609
|
+
for (const [regex, out, digitsOnlyInCode] of scans) {
|
|
7179
7610
|
const seen = /* @__PURE__ */ new Set();
|
|
7180
|
-
for (const
|
|
7611
|
+
for (const source of [cleaned, referenced]) {
|
|
7181
7612
|
const re = new RegExp(regex.source, "g");
|
|
7182
7613
|
let match2;
|
|
7183
7614
|
while ((match2 = re.exec(source)) !== null) {
|
|
@@ -7186,6 +7617,7 @@ var init_variable_tracker = __esm({
|
|
|
7186
7617
|
seen.add(charOffset);
|
|
7187
7618
|
const fullName = match2[1];
|
|
7188
7619
|
const baseName = fullName.split(".")[0];
|
|
7620
|
+
if (digitsOnlyInCode && /^\d/.test(baseName) && !inCode(charOffset)) continue;
|
|
7189
7621
|
let localLine = 0;
|
|
7190
7622
|
for (let i = 0; i < lineOffsets.length; i++) {
|
|
7191
7623
|
if (lineOffsets[i] > charOffset) break;
|
|
@@ -7197,7 +7629,7 @@ var init_variable_tracker = __esm({
|
|
|
7197
7629
|
start: { line: absoluteLine, character },
|
|
7198
7630
|
end: { line: absoluteLine, character: character + match2[0].length }
|
|
7199
7631
|
};
|
|
7200
|
-
out.push({ uri, baseName, fullName, range: range2
|
|
7632
|
+
out.push({ uri, baseName, fullName, range: range2 });
|
|
7201
7633
|
}
|
|
7202
7634
|
}
|
|
7203
7635
|
}
|
|
@@ -7208,11 +7640,16 @@ var init_variable_tracker = __esm({
|
|
|
7208
7640
|
if (transientUsages.length > 0) {
|
|
7209
7641
|
this.transientUsagesByUri.set(uri, transientUsages);
|
|
7210
7642
|
}
|
|
7643
|
+
const validated = validatedReferences(lines, passageBoundaries);
|
|
7644
|
+
if (validated.length > 0) {
|
|
7645
|
+
this.validatedRefsByUri.set(uri, validated);
|
|
7646
|
+
}
|
|
7211
7647
|
}
|
|
7212
7648
|
/** Forget the usages recorded for a document, e.g. after it was deleted. */
|
|
7213
7649
|
removeDocument(uri) {
|
|
7214
7650
|
this.usagesByUri.delete(uri);
|
|
7215
7651
|
this.transientUsagesByUri.delete(uri);
|
|
7652
|
+
this.validatedRefsByUri.delete(uri);
|
|
7216
7653
|
}
|
|
7217
7654
|
/** Get all declared variables. */
|
|
7218
7655
|
getDeclared() {
|
|
@@ -7230,20 +7667,52 @@ var init_variable_tracker = __esm({
|
|
|
7230
7667
|
}
|
|
7231
7668
|
return results;
|
|
7232
7669
|
}
|
|
7233
|
-
/**
|
|
7670
|
+
/**
|
|
7671
|
+
* Get undeclared variable references in a specific document: the ones
|
|
7672
|
+
* Spindle rejects when the story starts, wherever they are in the text.
|
|
7673
|
+
*/
|
|
7234
7674
|
getUndeclared(uri) {
|
|
7235
|
-
const
|
|
7236
|
-
if (!
|
|
7675
|
+
const refs = this.validatedRefsByUri.get(uri);
|
|
7676
|
+
if (!refs) return [];
|
|
7237
7677
|
const results = [];
|
|
7238
7678
|
const seen = /* @__PURE__ */ new Set();
|
|
7239
|
-
for (const u of
|
|
7240
|
-
if (
|
|
7679
|
+
for (const u of refs) {
|
|
7680
|
+
if (!this.declared.has(u.baseName) && !seen.has(u.baseName)) {
|
|
7241
7681
|
seen.add(u.baseName);
|
|
7242
7682
|
results.push({ name: u.baseName, range: u.range });
|
|
7243
7683
|
}
|
|
7244
7684
|
}
|
|
7245
7685
|
return results;
|
|
7246
7686
|
}
|
|
7687
|
+
/**
|
|
7688
|
+
* Get the `$var.a.b` paths in a document that Spindle rejects at startup
|
|
7689
|
+
* because they access a field of a number, string or boolean, judged by
|
|
7690
|
+
* the StoryVariables defaults as Spindle's validateRef() does. Every
|
|
7691
|
+
* occurrence is reported; defaults that are not literals are not checked.
|
|
7692
|
+
*/
|
|
7693
|
+
getPrimitiveFieldAccesses(uri) {
|
|
7694
|
+
const results = [];
|
|
7695
|
+
for (const ref of this.validatedRefsByUri.get(uri) ?? []) {
|
|
7696
|
+
const schema = this.declared.get(ref.baseName)?.schema;
|
|
7697
|
+
if (!schema) continue;
|
|
7698
|
+
const parts = ref.path.split(".");
|
|
7699
|
+
const found = findPrimitiveFieldAccess(schema, parts.slice(1));
|
|
7700
|
+
if (!found) continue;
|
|
7701
|
+
const owner = parts.slice(0, found.index + 1).join(".");
|
|
7702
|
+
const field = parts[found.index + 1];
|
|
7703
|
+
const start = ref.range.start.character + 1 + owner.length + 1;
|
|
7704
|
+
results.push({
|
|
7705
|
+
path: `$${owner}`,
|
|
7706
|
+
field,
|
|
7707
|
+
type: found.type,
|
|
7708
|
+
range: {
|
|
7709
|
+
start: { line: ref.range.start.line, character: start },
|
|
7710
|
+
end: { line: ref.range.start.line, character: start + field.length }
|
|
7711
|
+
}
|
|
7712
|
+
});
|
|
7713
|
+
}
|
|
7714
|
+
return results;
|
|
7715
|
+
}
|
|
7247
7716
|
/** Whether a StoryVariables passage has been parsed. */
|
|
7248
7717
|
hasStoryVariables() {
|
|
7249
7718
|
return this._hasStoryVariables;
|
|
@@ -7271,7 +7740,7 @@ var init_variable_tracker = __esm({
|
|
|
7271
7740
|
const results = [];
|
|
7272
7741
|
const seen = /* @__PURE__ */ new Set();
|
|
7273
7742
|
for (const u of usages) {
|
|
7274
|
-
if (
|
|
7743
|
+
if (!this.declaredTransient.has(u.baseName) && !seen.has(u.baseName)) {
|
|
7275
7744
|
seen.add(u.baseName);
|
|
7276
7745
|
results.push({ name: u.baseName, range: u.range });
|
|
7277
7746
|
}
|
|
@@ -7296,7 +7765,6 @@ var init_variable_tracker = __esm({
|
|
|
7296
7765
|
for (const [sigil, usages, declared] of sources) {
|
|
7297
7766
|
if (!usages) continue;
|
|
7298
7767
|
for (const u of usages) {
|
|
7299
|
-
if (!u.checked) continue;
|
|
7300
7768
|
const member = u.fullName.split(".")[1];
|
|
7301
7769
|
if (member === void 0) continue;
|
|
7302
7770
|
if (declared.get(u.baseName)?.type !== "array") continue;
|
|
@@ -7308,11 +7776,32 @@ var init_variable_tracker = __esm({
|
|
|
7308
7776
|
}
|
|
7309
7777
|
/** Get variables declared with null values in StoryVariables. */
|
|
7310
7778
|
getNullDeclarations() {
|
|
7311
|
-
return this._nullDeclarations;
|
|
7779
|
+
return [...this._nullDeclarations, ...this._variablesCheck.nestedNulls];
|
|
7780
|
+
}
|
|
7781
|
+
/** StoryVariables lines that stop Spindle from starting, apart from null values. */
|
|
7782
|
+
getInvalidDeclarations() {
|
|
7783
|
+
return this._variablesCheck.problems;
|
|
7784
|
+
}
|
|
7785
|
+
/**
|
|
7786
|
+
* StoryTransients lines that stop Spindle from starting, apart from null
|
|
7787
|
+
* values, and the names it declares that StoryVariables declares too.
|
|
7788
|
+
*/
|
|
7789
|
+
getInvalidTransientDeclarations() {
|
|
7790
|
+
const collisions = [];
|
|
7791
|
+
if (this._hasStoryVariables) {
|
|
7792
|
+
for (const [name, range2] of this._transientsCheck.names) {
|
|
7793
|
+
if (!this._variablesCheck.names.has(name)) continue;
|
|
7794
|
+
collisions.push({
|
|
7795
|
+
message: `StoryTransients: Variable "${name}" is already declared in StoryVariables. Names must be unique across scopes.`,
|
|
7796
|
+
range: range2
|
|
7797
|
+
});
|
|
7798
|
+
}
|
|
7799
|
+
}
|
|
7800
|
+
return [...this._transientsCheck.problems, ...collisions].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
7312
7801
|
}
|
|
7313
7802
|
/** Get transient variables declared with null values in StoryTransients. */
|
|
7314
7803
|
getNullTransientDeclarations() {
|
|
7315
|
-
return this._nullTransientDeclarations;
|
|
7804
|
+
return [...this._nullTransientDeclarations, ...this._transientsCheck.nestedNulls];
|
|
7316
7805
|
}
|
|
7317
7806
|
};
|
|
7318
7807
|
}
|
|
@@ -7513,11 +8002,11 @@ function discoverMacrosFromSource(source) {
|
|
|
7513
8002
|
}
|
|
7514
8003
|
return macros;
|
|
7515
8004
|
}
|
|
7516
|
-
function discoverMacrosFromStoryInit(
|
|
8005
|
+
function discoverMacrosFromStoryInit(passageContent3) {
|
|
7517
8006
|
const macros = [];
|
|
7518
8007
|
doBlockRegex.lastIndex = 0;
|
|
7519
8008
|
let doMatch;
|
|
7520
|
-
while ((doMatch = doBlockRegex.exec(
|
|
8009
|
+
while ((doMatch = doBlockRegex.exec(passageContent3)) !== null) {
|
|
7521
8010
|
const jsContent = doMatch[1];
|
|
7522
8011
|
const found = discoverMacrosFromSource(jsContent);
|
|
7523
8012
|
macros.push(...found);
|
|
@@ -14924,8 +15413,11 @@ function isExcludedMacroSource(path2, root) {
|
|
|
14924
15413
|
return (root ? relative(root, path2) : path2).split(/[\\/]/).some(isExcludedSegment);
|
|
14925
15414
|
}
|
|
14926
15415
|
function findMacroSourceFiles(root) {
|
|
15416
|
+
return findProjectFiles(root, MACRO_SOURCE_GLOB);
|
|
15417
|
+
}
|
|
15418
|
+
function findProjectFiles(root, pattern) {
|
|
14927
15419
|
const excluded = (p) => isExcludedMacroSource(p.fullpath(), root);
|
|
14928
|
-
return glob(
|
|
15420
|
+
return glob(pattern, {
|
|
14929
15421
|
cwd: root,
|
|
14930
15422
|
absolute: true,
|
|
14931
15423
|
nodir: true,
|
|
@@ -14975,6 +15467,82 @@ var init_macro_sources = __esm({
|
|
|
14975
15467
|
}
|
|
14976
15468
|
});
|
|
14977
15469
|
|
|
15470
|
+
// src/core/workspace/story-format.ts
|
|
15471
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
15472
|
+
function isSpindleFormatName(name) {
|
|
15473
|
+
return name.trim().toLowerCase() === "spindle";
|
|
15474
|
+
}
|
|
15475
|
+
function readStoryDataFormat(content) {
|
|
15476
|
+
let data;
|
|
15477
|
+
try {
|
|
15478
|
+
data = JSON.parse(content);
|
|
15479
|
+
} catch {
|
|
15480
|
+
return void 0;
|
|
15481
|
+
}
|
|
15482
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) return void 0;
|
|
15483
|
+
const format = data.format;
|
|
15484
|
+
if (typeof format !== "string") return void 0;
|
|
15485
|
+
return format.trim() || void 0;
|
|
15486
|
+
}
|
|
15487
|
+
function resolveStoryFormat(declared) {
|
|
15488
|
+
let foreign;
|
|
15489
|
+
for (const name of declared) {
|
|
15490
|
+
if (name === void 0) continue;
|
|
15491
|
+
if (isSpindleFormatName(name)) return { name: name.trim(), isSpindle: true };
|
|
15492
|
+
foreign ??= name.trim();
|
|
15493
|
+
}
|
|
15494
|
+
return foreign === void 0 ? UNDECLARED_STORY_FORMAT : { name: foreign, isSpindle: false };
|
|
15495
|
+
}
|
|
15496
|
+
function passageContent(passage, text) {
|
|
15497
|
+
return text.split("\n").slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
|
|
15498
|
+
}
|
|
15499
|
+
function storyDataFormats(passages, getText) {
|
|
15500
|
+
const formats = [];
|
|
15501
|
+
for (const passage of passages) {
|
|
15502
|
+
if (passage.name !== "StoryData") continue;
|
|
15503
|
+
const text = getText(passage.uri);
|
|
15504
|
+
formats.push(text === void 0 ? void 0 : readStoryDataFormat(passageContent(passage, text)));
|
|
15505
|
+
}
|
|
15506
|
+
return formats;
|
|
15507
|
+
}
|
|
15508
|
+
function storyFormatOfTexts(texts) {
|
|
15509
|
+
const index = new PassageIndex();
|
|
15510
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
15511
|
+
for (const text of texts) {
|
|
15512
|
+
if (!text.includes("StoryData")) continue;
|
|
15513
|
+
const uri = `story-format:${byUri.size}`;
|
|
15514
|
+
byUri.set(uri, text);
|
|
15515
|
+
index.rebuild(uri, text);
|
|
15516
|
+
}
|
|
15517
|
+
const formats = storyDataFormats(index.getAllPassages(), (uri) => byUri.get(uri));
|
|
15518
|
+
return { ...resolveStoryFormat(formats), hasStoryData: formats.length > 0 };
|
|
15519
|
+
}
|
|
15520
|
+
async function findStoryFormat(texts, dir) {
|
|
15521
|
+
const own = storyFormatOfTexts(texts);
|
|
15522
|
+
if (own.hasStoryData) return { name: own.name, isSpindle: own.isSpindle };
|
|
15523
|
+
const projectTexts = [];
|
|
15524
|
+
for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
|
|
15525
|
+
try {
|
|
15526
|
+
projectTexts.push(readFileSync5(file2, "utf-8"));
|
|
15527
|
+
} catch {
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
const { name, isSpindle } = storyFormatOfTexts(projectTexts);
|
|
15531
|
+
return { name, isSpindle };
|
|
15532
|
+
}
|
|
15533
|
+
function skippedFormatNote(format) {
|
|
15534
|
+
return `Skipped: story format is ${format.name}, not Spindle`;
|
|
15535
|
+
}
|
|
15536
|
+
var UNDECLARED_STORY_FORMAT;
|
|
15537
|
+
var init_story_format = __esm({
|
|
15538
|
+
"src/core/workspace/story-format.ts"() {
|
|
15539
|
+
"use strict";
|
|
15540
|
+
init_passage_index();
|
|
15541
|
+
init_macro_sources();
|
|
15542
|
+
UNDECLARED_STORY_FORMAT = { name: void 0, isSpindle: true };
|
|
15543
|
+
}
|
|
15544
|
+
});
|
|
15545
|
+
|
|
14978
15546
|
// src/macro-supplements.json
|
|
14979
15547
|
var macro_supplements_default;
|
|
14980
15548
|
var init_macro_supplements = __esm({
|
|
@@ -15004,9 +15572,6 @@ var init_macro_supplements = __esm({
|
|
|
15004
15572
|
},
|
|
15005
15573
|
for: {
|
|
15006
15574
|
name: "for",
|
|
15007
|
-
children: [
|
|
15008
|
-
{ name: "stop" }
|
|
15009
|
-
],
|
|
15010
15575
|
container: true,
|
|
15011
15576
|
description: "Loop block. Usage: `{for @item of $list}...{/for}` or `{for @item, @index of $list}...{/for}`",
|
|
15012
15577
|
skipArgs: true
|
|
@@ -15019,8 +15584,8 @@ var init_macro_supplements = __esm({
|
|
|
15019
15584
|
},
|
|
15020
15585
|
stop: {
|
|
15021
15586
|
name: "stop",
|
|
15022
|
-
parents: ["
|
|
15023
|
-
description: "
|
|
15587
|
+
parents: ["repeat"],
|
|
15588
|
+
description: "Stops the enclosing `{repeat}` loop. It has no effect anywhere else, including in a `{for}` loop.\n\nUsage: `{stop}`",
|
|
15024
15589
|
parameters: []
|
|
15025
15590
|
},
|
|
15026
15591
|
switch: {
|
|
@@ -15089,8 +15654,8 @@ var init_macro_supplements = __esm({
|
|
|
15089
15654
|
{ name: "next" }
|
|
15090
15655
|
],
|
|
15091
15656
|
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"]
|
|
15657
|
+
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```",
|
|
15658
|
+
parameters: ["text", ""]
|
|
15094
15659
|
},
|
|
15095
15660
|
repeat: {
|
|
15096
15661
|
name: "repeat",
|
|
@@ -15284,6 +15849,7 @@ var init_workspace_model = __esm({
|
|
|
15284
15849
|
init_macro_parser();
|
|
15285
15850
|
init_macro_discovery();
|
|
15286
15851
|
init_macro_sources();
|
|
15852
|
+
init_story_format();
|
|
15287
15853
|
init_macro_supplements();
|
|
15288
15854
|
WorkspaceModel = class extends EventEmitter3 {
|
|
15289
15855
|
documents;
|
|
@@ -15293,6 +15859,8 @@ var init_workspace_model = __esm({
|
|
|
15293
15859
|
widgets;
|
|
15294
15860
|
/** True after initialize() has completed (full workspace scan done). */
|
|
15295
15861
|
initialized = false;
|
|
15862
|
+
/** The story format declared by StoryData, updated with the passages. */
|
|
15863
|
+
format = UNDECLARED_STORY_FORMAT;
|
|
15296
15864
|
debounceTimer = null;
|
|
15297
15865
|
DEBOUNCE_MS = 200;
|
|
15298
15866
|
/** Bound listeners for cleanup in dispose(). */
|
|
@@ -15347,6 +15915,22 @@ var init_workspace_model = __esm({
|
|
|
15347
15915
|
isContainer(name) {
|
|
15348
15916
|
return this.macros.isBlock(name) || (this.widgets.getWidget(name)?.block ?? false);
|
|
15349
15917
|
}
|
|
15918
|
+
/**
|
|
15919
|
+
* The story format named by the project's StoryData passage (trimmed), or
|
|
15920
|
+
* undefined if no StoryData passage names one.
|
|
15921
|
+
*/
|
|
15922
|
+
get storyFormat() {
|
|
15923
|
+
return this.format.name;
|
|
15924
|
+
}
|
|
15925
|
+
/**
|
|
15926
|
+
* Whether Spindle semantics apply to this project. False only if a
|
|
15927
|
+
* StoryData passage names another story format (SugarCube, Harlowe, ...)
|
|
15928
|
+
* and none names Spindle; a missing or unreadable StoryData counts as
|
|
15929
|
+
* Spindle.
|
|
15930
|
+
*/
|
|
15931
|
+
isSpindleProject() {
|
|
15932
|
+
return this.format.isSpindle;
|
|
15933
|
+
}
|
|
15350
15934
|
/** Clean up listeners and timers. */
|
|
15351
15935
|
dispose() {
|
|
15352
15936
|
this.documents.removeListener("documentChanged", this.onDocumentChanged);
|
|
@@ -15366,6 +15950,7 @@ var init_workspace_model = __esm({
|
|
|
15366
15950
|
}
|
|
15367
15951
|
this.refreshDiscoveredMacros();
|
|
15368
15952
|
this.cascade();
|
|
15953
|
+
this.updateStoryFormat();
|
|
15369
15954
|
this.emit("documentChanged", uri);
|
|
15370
15955
|
this.emit("passagesUpdated", uri);
|
|
15371
15956
|
this.scheduleModelReady();
|
|
@@ -15376,6 +15961,7 @@ var init_workspace_model = __esm({
|
|
|
15376
15961
|
this.variables.removeDocument(uri);
|
|
15377
15962
|
this.refreshDiscoveredMacros();
|
|
15378
15963
|
this.cascade();
|
|
15964
|
+
this.updateStoryFormat();
|
|
15379
15965
|
this.emit("documentClosed", uri);
|
|
15380
15966
|
this.emit("passagesUpdated", uri);
|
|
15381
15967
|
this.scheduleModelReady();
|
|
@@ -15390,6 +15976,21 @@ var init_workspace_model = __esm({
|
|
|
15390
15976
|
}
|
|
15391
15977
|
this.refreshDiscoveredMacros();
|
|
15392
15978
|
this.cascade();
|
|
15979
|
+
this.updateStoryFormat();
|
|
15980
|
+
}
|
|
15981
|
+
/**
|
|
15982
|
+
* Re-read the story format from the StoryData passages after the passage
|
|
15983
|
+
* index changed, emitting 'storyFormatChanged' if it differs. Called after
|
|
15984
|
+
* the cascade, so listeners see up-to-date indices.
|
|
15985
|
+
*/
|
|
15986
|
+
updateStoryFormat() {
|
|
15987
|
+
const format = resolveStoryFormat(storyDataFormats(
|
|
15988
|
+
this.passages.getAllPassages(),
|
|
15989
|
+
(uri) => this.documents.getText(uri)
|
|
15990
|
+
));
|
|
15991
|
+
if (format.name === this.format.name && format.isSpindle === this.format.isSpindle) return;
|
|
15992
|
+
this.format = format;
|
|
15993
|
+
this.emit("storyFormatChanged", format);
|
|
15393
15994
|
}
|
|
15394
15995
|
/**
|
|
15395
15996
|
* Re-run static macro discovery over the workspace and replace the
|
|
@@ -15505,6 +16106,7 @@ var init_diagnostic_codes = __esm({
|
|
|
15505
16106
|
DiagnosticCode = {
|
|
15506
16107
|
UndefinedMacro: "SP100",
|
|
15507
16108
|
MalformedContainer: "SP101",
|
|
16109
|
+
MalformedElement: "SP102",
|
|
15508
16110
|
IllegalClosingTag: "SP104",
|
|
15509
16111
|
InvalidChildren: "SP107",
|
|
15510
16112
|
ExpectedNoArguments: "SP108",
|
|
@@ -15515,12 +16117,13 @@ var init_diagnostic_codes = __esm({
|
|
|
15515
16117
|
ChildMaxExceeded: "SP114",
|
|
15516
16118
|
ChildMinNotMet: "SP115",
|
|
15517
16119
|
UndeclaredVariable: "SP200",
|
|
15518
|
-
|
|
16120
|
+
PrimitiveFieldAccess: "SP201",
|
|
15519
16121
|
NoStoryVariables: "SP202",
|
|
15520
16122
|
UndeclaredTransient: "SP203",
|
|
15521
16123
|
NullVariableValue: "SP204",
|
|
15522
16124
|
TemporaryAssignedInLoop: "SP205",
|
|
15523
16125
|
ArrayMemberAccess: "SP206",
|
|
16126
|
+
InvalidDeclaration: "SP207",
|
|
15524
16127
|
BrokenPassageLink: "SP300",
|
|
15525
16128
|
WidgetArgCountMismatch: "SP301",
|
|
15526
16129
|
IncludeWidgetPassage: "SP302",
|
|
@@ -15532,6 +16135,7 @@ var init_diagnostic_codes = __esm({
|
|
|
15532
16135
|
severityMap = {
|
|
15533
16136
|
SP100: "warning",
|
|
15534
16137
|
SP101: "error",
|
|
16138
|
+
SP102: "error",
|
|
15535
16139
|
SP104: "error",
|
|
15536
16140
|
SP107: "error",
|
|
15537
16141
|
SP108: "error",
|
|
@@ -15542,12 +16146,13 @@ var init_diagnostic_codes = __esm({
|
|
|
15542
16146
|
SP114: "error",
|
|
15543
16147
|
SP115: "error",
|
|
15544
16148
|
SP200: "error",
|
|
15545
|
-
SP201: "
|
|
16149
|
+
SP201: "error",
|
|
15546
16150
|
SP202: "info",
|
|
15547
16151
|
SP203: "warning",
|
|
15548
16152
|
SP204: "error",
|
|
15549
16153
|
SP205: "warning",
|
|
15550
16154
|
SP206: "warning",
|
|
16155
|
+
SP207: "error",
|
|
15551
16156
|
SP300: "warning",
|
|
15552
16157
|
SP301: "warning",
|
|
15553
16158
|
SP302: "warning",
|
|
@@ -15829,6 +16434,258 @@ var init_argument_lexer = __esm({
|
|
|
15829
16434
|
}
|
|
15830
16435
|
});
|
|
15831
16436
|
|
|
16437
|
+
// src/core/parsing/html-scanner.ts
|
|
16438
|
+
function plainBraceMatches(input) {
|
|
16439
|
+
const match2 = new Int32Array(input.length).fill(-1);
|
|
16440
|
+
const open = [];
|
|
16441
|
+
for (let i = 0; i < input.length; i++) {
|
|
16442
|
+
if (input[i] === "{") open.push(i);
|
|
16443
|
+
else if (input[i] === "}" && open.length > 0) match2[open.pop()] = i;
|
|
16444
|
+
}
|
|
16445
|
+
return match2;
|
|
16446
|
+
}
|
|
16447
|
+
function scanHtmlTags(text) {
|
|
16448
|
+
const n = text.length;
|
|
16449
|
+
const tags = [];
|
|
16450
|
+
const macros = [];
|
|
16451
|
+
const code = createCodeScanner(text);
|
|
16452
|
+
const plain = plainBraceMatches(text);
|
|
16453
|
+
const rawBodies = [];
|
|
16454
|
+
let doCloses;
|
|
16455
|
+
let nextDoClose = 0;
|
|
16456
|
+
const rawBody = (end) => {
|
|
16457
|
+
doCloses ??= [...text.matchAll(/\{\/do\s*\}/gi)].map((match2) => match2.index);
|
|
16458
|
+
while (nextDoClose < doCloses.length && doCloses[nextDoClose] < end) nextDoClose++;
|
|
16459
|
+
if (nextDoClose < doCloses.length) rawBodies.push([end, doCloses[nextDoClose]]);
|
|
16460
|
+
};
|
|
16461
|
+
const rawBodyLimit = (limit2) => {
|
|
16462
|
+
if (rawBodies.length === 0) return limit2;
|
|
16463
|
+
const macroStarts = new Set(macros);
|
|
16464
|
+
let t = 0;
|
|
16465
|
+
for (const [start, close] of rawBodies) {
|
|
16466
|
+
if (start >= limit2) break;
|
|
16467
|
+
if (close < limit2 && !macroStarts.has(close)) {
|
|
16468
|
+
limit2 = start;
|
|
16469
|
+
break;
|
|
16470
|
+
}
|
|
16471
|
+
while (t < tags.length && tags[t].start < start) t++;
|
|
16472
|
+
if (t < tags.length && tags[t].start < Math.min(close, limit2)) limit2 = tags[t].start;
|
|
16473
|
+
}
|
|
16474
|
+
return limit2;
|
|
16475
|
+
};
|
|
16476
|
+
const braceEnd = (open) => {
|
|
16477
|
+
const close = code.closeBrace(open + 1);
|
|
16478
|
+
if (close !== plain[open]) return GIVE_UP;
|
|
16479
|
+
return close === -1 ? open + 1 : close + 1;
|
|
16480
|
+
};
|
|
16481
|
+
const displayEnd = (open, sigil) => {
|
|
16482
|
+
let j = sigil + 1;
|
|
16483
|
+
while (j < n && DISPLAY_NAME_CHAR.test(text[j])) j++;
|
|
16484
|
+
return text[j] === "}" ? j + 1 : braceEnd(open);
|
|
16485
|
+
};
|
|
16486
|
+
const afterBrace = (i2) => {
|
|
16487
|
+
let at = i2 + 1;
|
|
16488
|
+
const selectors = text[at] === "." || text[at] === "#";
|
|
16489
|
+
if (selectors) {
|
|
16490
|
+
at = selectorsEnd(text, at);
|
|
16491
|
+
if (text[at] === " ") at++;
|
|
16492
|
+
}
|
|
16493
|
+
const c3 = text[at];
|
|
16494
|
+
if (c3 === "$" || c3 === "_" || c3 === "@" || c3 === "%") return displayEnd(i2, at);
|
|
16495
|
+
if (c3 === void 0) return i2 + 1;
|
|
16496
|
+
if (LETTER.test(c3) || c3 === "/" && !selectors) {
|
|
16497
|
+
const end = braceEnd(i2);
|
|
16498
|
+
if (end > i2 + 1) {
|
|
16499
|
+
macros.push(i2);
|
|
16500
|
+
if (/^do$/i.test(text.slice(at, end - 1).trim().split(/\s/)[0])) rawBody(end);
|
|
16501
|
+
}
|
|
16502
|
+
return end;
|
|
16503
|
+
}
|
|
16504
|
+
return i2 + 1;
|
|
16505
|
+
};
|
|
16506
|
+
const readTag = (i2) => {
|
|
16507
|
+
let j = i2 + 1;
|
|
16508
|
+
const isClose = text[j] === "/";
|
|
16509
|
+
if (isClose) j++;
|
|
16510
|
+
const nameStart = j;
|
|
16511
|
+
while (j < n && TAG_NAME_CHAR.test(text[j])) j++;
|
|
16512
|
+
const name = text.slice(nameStart, j);
|
|
16513
|
+
if (!name || !LETTER.test(name[0])) return i2 + 1;
|
|
16514
|
+
const isVoid = VOID_ELEMENTS.has(name.toLowerCase());
|
|
16515
|
+
if (isClose) {
|
|
16516
|
+
while (j < n && WHITESPACE.test(text[j])) j++;
|
|
16517
|
+
if (text[j] !== ">") return i2 + 1;
|
|
16518
|
+
if (!isVoid) tags.push({ name, kind: "close", start: i2, end: j + 1 });
|
|
16519
|
+
return j + 1;
|
|
16520
|
+
}
|
|
16521
|
+
const attrs = readAttributes(j);
|
|
16522
|
+
if (attrs.end === GIVE_UP) return GIVE_UP;
|
|
16523
|
+
j = attrs.end;
|
|
16524
|
+
let selfClosing = isVoid;
|
|
16525
|
+
if (text[j] === "/") {
|
|
16526
|
+
selfClosing = true;
|
|
16527
|
+
j++;
|
|
16528
|
+
}
|
|
16529
|
+
if (text[j] === ">") {
|
|
16530
|
+
tags.push({ name, kind: selfClosing ? "void" : "open", start: i2, end: j + 1 });
|
|
16531
|
+
return j + 1;
|
|
16532
|
+
}
|
|
16533
|
+
return attrs.count > 0 ? GIVE_UP : i2 + 1;
|
|
16534
|
+
};
|
|
16535
|
+
const readAttributes = (j) => {
|
|
16536
|
+
let count = 0;
|
|
16537
|
+
while (j < n) {
|
|
16538
|
+
while (j < n && WHITESPACE.test(text[j])) j++;
|
|
16539
|
+
if (j >= n || text[j] === ">" || text[j] === "/" && text[j + 1] === ">") break;
|
|
16540
|
+
const nameStart = j;
|
|
16541
|
+
while (j < n && ATTR_NAME_CHAR.test(text[j])) j++;
|
|
16542
|
+
if (j === nameStart) break;
|
|
16543
|
+
count++;
|
|
16544
|
+
if (text[j] !== "=") continue;
|
|
16545
|
+
j++;
|
|
16546
|
+
const quote = text[j];
|
|
16547
|
+
if (quote === '"' || quote === "'") {
|
|
16548
|
+
const close = quotedValueEnd(j + 1, quote);
|
|
16549
|
+
if (close === GIVE_UP) return { end: GIVE_UP, count };
|
|
16550
|
+
j = close < n ? close + 1 : close;
|
|
16551
|
+
} else {
|
|
16552
|
+
while (j < n && !WHITESPACE.test(text[j]) && text[j] !== ">") j++;
|
|
16553
|
+
}
|
|
16554
|
+
}
|
|
16555
|
+
return { end: j, count };
|
|
16556
|
+
};
|
|
16557
|
+
const quotedValueEnd = (from, quote) => {
|
|
16558
|
+
let depth = 0;
|
|
16559
|
+
let sawBrace = false;
|
|
16560
|
+
let k = from;
|
|
16561
|
+
for (; k < n; k++) {
|
|
16562
|
+
const c3 = text[k];
|
|
16563
|
+
if (c3 === "{") {
|
|
16564
|
+
depth++;
|
|
16565
|
+
sawBrace = true;
|
|
16566
|
+
} else if (c3 === "}") {
|
|
16567
|
+
depth--;
|
|
16568
|
+
sawBrace = true;
|
|
16569
|
+
} else if (c3 === quote && depth <= 0) {
|
|
16570
|
+
break;
|
|
16571
|
+
}
|
|
16572
|
+
}
|
|
16573
|
+
if (!sawBrace) return k;
|
|
16574
|
+
let d = from;
|
|
16575
|
+
while (d < n) {
|
|
16576
|
+
if (text[d] === "{") {
|
|
16577
|
+
const close = code.closeBrace(d + 1);
|
|
16578
|
+
if (close !== -1) {
|
|
16579
|
+
d = close + 1;
|
|
16580
|
+
continue;
|
|
16581
|
+
}
|
|
16582
|
+
} else if (text[d] === quote) {
|
|
16583
|
+
break;
|
|
16584
|
+
}
|
|
16585
|
+
d++;
|
|
16586
|
+
}
|
|
16587
|
+
return d === k ? k : GIVE_UP;
|
|
16588
|
+
};
|
|
16589
|
+
let i = 0;
|
|
16590
|
+
let stoppedAt = -1;
|
|
16591
|
+
while (i < n) {
|
|
16592
|
+
const c3 = text[i];
|
|
16593
|
+
let next;
|
|
16594
|
+
if (c3 === "\\") {
|
|
16595
|
+
let k = i + 1;
|
|
16596
|
+
while (text[k] === "\\") k++;
|
|
16597
|
+
if (text[k] === "{" || text[k] === "}") next = (k - i) % 2 === 1 ? k + 1 : GIVE_UP;
|
|
16598
|
+
else next = k;
|
|
16599
|
+
} else if (c3 === "[" && text[i + 1] === "[") {
|
|
16600
|
+
next = linkEnd(text, i);
|
|
16601
|
+
} else if (c3 === "{") {
|
|
16602
|
+
next = afterBrace(i);
|
|
16603
|
+
} else if (c3 === "<") {
|
|
16604
|
+
next = readTag(i);
|
|
16605
|
+
} else {
|
|
16606
|
+
next = i + 1;
|
|
16607
|
+
}
|
|
16608
|
+
if (next === GIVE_UP) {
|
|
16609
|
+
stoppedAt = i;
|
|
16610
|
+
break;
|
|
16611
|
+
}
|
|
16612
|
+
i = next;
|
|
16613
|
+
}
|
|
16614
|
+
const limit = rawBodyLimit(stoppedAt === -1 ? n : stoppedAt);
|
|
16615
|
+
if (limit === stoppedAt || limit === n) return { tags, stoppedAt, macros };
|
|
16616
|
+
return {
|
|
16617
|
+
tags: tags.filter((tag) => tag.start < limit),
|
|
16618
|
+
stoppedAt: limit,
|
|
16619
|
+
macros: macros.filter((at) => at < limit)
|
|
16620
|
+
};
|
|
16621
|
+
}
|
|
16622
|
+
function selectorsEnd(text, i) {
|
|
16623
|
+
const n = text.length;
|
|
16624
|
+
while (i < n && (text[i] === "." || text[i] === "#")) {
|
|
16625
|
+
i++;
|
|
16626
|
+
while (i < n) {
|
|
16627
|
+
if (SELECTOR_NAME_CHAR.test(text[i])) {
|
|
16628
|
+
i++;
|
|
16629
|
+
} else if (text[i] === "{" && (text[i + 1] === "$" || text[i + 1] === "_" || text[i + 1] === "@")) {
|
|
16630
|
+
let j = i + 2;
|
|
16631
|
+
while (j < n && DISPLAY_NAME_CHAR.test(text[j])) j++;
|
|
16632
|
+
if (text[j] !== "}") break;
|
|
16633
|
+
i = j + 1;
|
|
16634
|
+
} else {
|
|
16635
|
+
break;
|
|
16636
|
+
}
|
|
16637
|
+
}
|
|
16638
|
+
}
|
|
16639
|
+
return i;
|
|
16640
|
+
}
|
|
16641
|
+
function linkEnd(text, i) {
|
|
16642
|
+
const n = text.length;
|
|
16643
|
+
let depth = 1;
|
|
16644
|
+
let j = i + 2;
|
|
16645
|
+
while (j < n) {
|
|
16646
|
+
if (text[j] === "[" && text[j + 1] === "[") {
|
|
16647
|
+
depth++;
|
|
16648
|
+
j += 2;
|
|
16649
|
+
} else if (text[j] === "]" && text[j + 1] === "]") {
|
|
16650
|
+
if (--depth === 0) return j + 2;
|
|
16651
|
+
j += 2;
|
|
16652
|
+
} else {
|
|
16653
|
+
j++;
|
|
16654
|
+
}
|
|
16655
|
+
}
|
|
16656
|
+
return GIVE_UP;
|
|
16657
|
+
}
|
|
16658
|
+
var VOID_ELEMENTS, TAG_NAME_CHAR, LETTER, ATTR_NAME_CHAR, WHITESPACE, SELECTOR_NAME_CHAR, DISPLAY_NAME_CHAR, GIVE_UP;
|
|
16659
|
+
var init_html_scanner = __esm({
|
|
16660
|
+
"src/core/parsing/html-scanner.ts"() {
|
|
16661
|
+
"use strict";
|
|
16662
|
+
init_macro_parser();
|
|
16663
|
+
VOID_ELEMENTS = /* @__PURE__ */ new Set([
|
|
16664
|
+
"area",
|
|
16665
|
+
"base",
|
|
16666
|
+
"br",
|
|
16667
|
+
"col",
|
|
16668
|
+
"embed",
|
|
16669
|
+
"hr",
|
|
16670
|
+
"img",
|
|
16671
|
+
"input",
|
|
16672
|
+
"link",
|
|
16673
|
+
"meta",
|
|
16674
|
+
"param",
|
|
16675
|
+
"source",
|
|
16676
|
+
"track",
|
|
16677
|
+
"wbr"
|
|
16678
|
+
]);
|
|
16679
|
+
TAG_NAME_CHAR = /[a-zA-Z0-9-]/;
|
|
16680
|
+
LETTER = /[a-zA-Z]/;
|
|
16681
|
+
ATTR_NAME_CHAR = /[a-zA-Z0-9_\-:@]/;
|
|
16682
|
+
WHITESPACE = /[^\S ]/;
|
|
16683
|
+
SELECTOR_NAME_CHAR = /[a-zA-Z0-9_-]/;
|
|
16684
|
+
DISPLAY_NAME_CHAR = /[\w.]/;
|
|
16685
|
+
GIVE_UP = -2;
|
|
16686
|
+
}
|
|
16687
|
+
});
|
|
16688
|
+
|
|
15832
16689
|
// src/core/parsing/widget-arguments.ts
|
|
15833
16690
|
function splitWidgetArguments(raw) {
|
|
15834
16691
|
const args2 = [];
|
|
@@ -16331,7 +17188,7 @@ var init_parameter_validator = __esm({
|
|
|
16331
17188
|
if (/^[_@%]/.test(name)) {
|
|
16332
17189
|
return new TypeError2(`Variable receiver must be a story variable ($name), got '${name}'`);
|
|
16333
17190
|
}
|
|
16334
|
-
if (
|
|
17191
|
+
if (!/^(?:\$\w|[A-Za-z_])[\w$]*(?:\.[\w$]+)*$/.test(name)) {
|
|
16335
17192
|
return new TypeError2(`Argument is not a valid variable receiver: '${name}'`);
|
|
16336
17193
|
}
|
|
16337
17194
|
return null;
|
|
@@ -16664,6 +17521,7 @@ var init_link_parser = __esm({
|
|
|
16664
17521
|
// src/plugins/diagnostics.ts
|
|
16665
17522
|
function computeDiagnostics(uri, workspace, options) {
|
|
16666
17523
|
if (!workspace.initialized) return [];
|
|
17524
|
+
if (!workspace.isSpindleProject()) return [];
|
|
16667
17525
|
try {
|
|
16668
17526
|
const text = workspace.documents.getText(uri);
|
|
16669
17527
|
if (text === void 0) return [];
|
|
@@ -16679,8 +17537,15 @@ function computeDiagnostics(uri, workspace, options) {
|
|
|
16679
17537
|
);
|
|
16680
17538
|
const allPassages = workspace.passages.getAllPassages();
|
|
16681
17539
|
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
17540
|
+
let elements = /* @__PURE__ */ new Map();
|
|
17541
|
+
try {
|
|
17542
|
+
const replay = replayElements(markupText, macros, passages, workspace);
|
|
17543
|
+
elements = replay.elements;
|
|
17544
|
+
diagnostics.push(...replay.errors);
|
|
17545
|
+
} catch {
|
|
17546
|
+
}
|
|
16682
17547
|
try {
|
|
16683
|
-
validateMacros(macros, workspace, diagnostics);
|
|
17548
|
+
validateMacros(macros, elements, workspace, diagnostics);
|
|
16684
17549
|
} catch {
|
|
16685
17550
|
}
|
|
16686
17551
|
try {
|
|
@@ -16738,7 +17603,7 @@ function maskScriptAndStylesheetPassages(text, passages) {
|
|
|
16738
17603
|
}
|
|
16739
17604
|
return lines.join("\n");
|
|
16740
17605
|
}
|
|
16741
|
-
function validateMacros(macros, workspace, diagnostics) {
|
|
17606
|
+
function validateMacros(macros, elements, workspace, diagnostics) {
|
|
16742
17607
|
for (let curIndex = 0; curIndex < macros.length; curIndex++) {
|
|
16743
17608
|
const macro = macros[curIndex];
|
|
16744
17609
|
const info = workspace.macros.getMacro(macro.name);
|
|
@@ -16767,7 +17632,7 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16767
17632
|
));
|
|
16768
17633
|
}
|
|
16769
17634
|
if (info?.children && info.children.length > 0 && macro.open && macro.pair !== -1) {
|
|
16770
|
-
validateChildren(macros, curIndex, macro, info.children, workspace, diagnostics);
|
|
17635
|
+
validateChildren(macros, curIndex, macro, info.children, elements, workspace, diagnostics);
|
|
16771
17636
|
}
|
|
16772
17637
|
} else if (!macro.open) {
|
|
16773
17638
|
diagnostics.push(makeDiag(
|
|
@@ -16777,8 +17642,20 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16777
17642
|
));
|
|
16778
17643
|
}
|
|
16779
17644
|
if (info?.parents && info.parents.length > 0 && macro.open) {
|
|
16780
|
-
|
|
16781
|
-
|
|
17645
|
+
const parentList = info.parents.join(", ");
|
|
17646
|
+
if (DIRECT_CHILD_MACROS.has(macro.name.toLowerCase())) {
|
|
17647
|
+
const element = elements.get(curIndex);
|
|
17648
|
+
const enclosing = element === void 0 ? innermostContainer(macros, curIndex, workspace) : void 0;
|
|
17649
|
+
const parentSet = new Set(info.parents.map((p) => p.toLowerCase()));
|
|
17650
|
+
if (element !== void 0 || !enclosing || !parentSet.has(enclosing.name.toLowerCase())) {
|
|
17651
|
+
const where = element !== void 0 ? `, not inside <${element}>` : enclosing ? `, not inside {${enclosing.name}}` : "";
|
|
17652
|
+
diagnostics.push(makeDiag(
|
|
17653
|
+
macro.range,
|
|
17654
|
+
DiagnosticCode.InvalidChildren,
|
|
17655
|
+
`Invalid: {${macro.name}} can only be directly inside {${parentList}}${where}`
|
|
17656
|
+
));
|
|
17657
|
+
}
|
|
17658
|
+
} else if (!isInsideParent(macros, curIndex, info.parents, workspace)) {
|
|
16782
17659
|
diagnostics.push(makeDiag(
|
|
16783
17660
|
macro.range,
|
|
16784
17661
|
DiagnosticCode.InvalidChildren,
|
|
@@ -16788,6 +17665,112 @@ function validateMacros(macros, workspace, diagnostics) {
|
|
|
16788
17665
|
}
|
|
16789
17666
|
}
|
|
16790
17667
|
}
|
|
17668
|
+
function innermostContainer(macros, index, workspace) {
|
|
17669
|
+
for (let i = index - 1; i >= 0; i--) {
|
|
17670
|
+
const candidate = macros[i];
|
|
17671
|
+
if (!candidate.open || candidate.pair <= index) continue;
|
|
17672
|
+
if (workspace.isContainer(candidate.name)) return candidate;
|
|
17673
|
+
}
|
|
17674
|
+
return void 0;
|
|
17675
|
+
}
|
|
17676
|
+
function isMarkupPassage(passage) {
|
|
17677
|
+
return !NON_MARKUP_PASSAGES.has(passage.name) && !isScriptOrStylesheetPassage(passage);
|
|
17678
|
+
}
|
|
17679
|
+
function replayElements(text, macros, passages, workspace) {
|
|
17680
|
+
const replay = { elements: /* @__PURE__ */ new Map(), errors: [] };
|
|
17681
|
+
if (!text.includes("<")) return replay;
|
|
17682
|
+
const lineStarts = buildLineStarts(text);
|
|
17683
|
+
const lineOffset = (line) => lineStarts[line] ?? text.length;
|
|
17684
|
+
const ordered = [...passages].sort((a, b) => a.range.start.line - b.range.start.line);
|
|
17685
|
+
let m = 0;
|
|
17686
|
+
for (const passage of ordered) {
|
|
17687
|
+
const contentStart = lineOffset(passage.range.start.line + 1);
|
|
17688
|
+
const contentEnd = lineOffset(passage.range.end.line + 1);
|
|
17689
|
+
while (m < macros.length && positionToOffset(macros[m].range.start, lineStarts) < contentStart) m++;
|
|
17690
|
+
const first = m;
|
|
17691
|
+
while (m < macros.length && positionToOffset(macros[m].range.start, lineStarts) < contentEnd) m++;
|
|
17692
|
+
const content = text.slice(contentStart, contentEnd);
|
|
17693
|
+
if (!content.includes("<") || !isMarkupPassage(passage)) continue;
|
|
17694
|
+
replayPassage(macros, first, m, scanHtmlTags(content), contentStart, lineStarts, workspace, replay);
|
|
17695
|
+
}
|
|
17696
|
+
return replay;
|
|
17697
|
+
}
|
|
17698
|
+
function replayPassage(macros, from, to, scan, base, lineStarts, workspace, replay) {
|
|
17699
|
+
const { tags } = scan;
|
|
17700
|
+
const macroStart = (k) => positionToOffset(macros[k].range.start, lineStarts) - base;
|
|
17701
|
+
let certainUntil = scan.stoppedAt === -1 ? Infinity : scan.stoppedAt;
|
|
17702
|
+
const parsed = /* @__PURE__ */ new Set();
|
|
17703
|
+
for (let k = from; k < to; k++) parsed.add(macroStart(k));
|
|
17704
|
+
const unparsed = scan.macros.find((at) => !parsed.has(at));
|
|
17705
|
+
if (unparsed !== void 0) certainUntil = Math.min(certainUntil, unparsed);
|
|
17706
|
+
const stack = [];
|
|
17707
|
+
let t = 0;
|
|
17708
|
+
let s = 0;
|
|
17709
|
+
let lastMacroEnd = -1;
|
|
17710
|
+
const report2 = (range2, message) => {
|
|
17711
|
+
replay.errors.push(makeDiag(range2, DiagnosticCode.MalformedElement, `Malformed element: ${message}`));
|
|
17712
|
+
};
|
|
17713
|
+
const tagRange = (tag) => ({
|
|
17714
|
+
start: offsetToPosition(base + tag.start, lineStarts),
|
|
17715
|
+
end: offsetToPosition(base + tag.end, lineStarts)
|
|
17716
|
+
});
|
|
17717
|
+
const applyTags = (until) => {
|
|
17718
|
+
for (; t < tags.length; t++) {
|
|
17719
|
+
const tag = tags[t];
|
|
17720
|
+
const at = tag.kind === "close" ? tag.start : tag.end;
|
|
17721
|
+
if (at > until) break;
|
|
17722
|
+
if (tag.start >= certainUntil) return false;
|
|
17723
|
+
if (tag.start < lastMacroEnd) return false;
|
|
17724
|
+
if (tag.kind === "open") {
|
|
17725
|
+
stack.push({ element: tag });
|
|
17726
|
+
} else if (tag.kind === "close") {
|
|
17727
|
+
const top = stack[stack.length - 1];
|
|
17728
|
+
const found = `</${tag.name}>`;
|
|
17729
|
+
if (!top) {
|
|
17730
|
+
report2(tagRange(tag), `unexpected closing ${found}`);
|
|
17731
|
+
return false;
|
|
17732
|
+
}
|
|
17733
|
+
if ("macro" in top) {
|
|
17734
|
+
report2(tagRange(tag), `expected {/${macros[top.macro].name}} but found ${found}`);
|
|
17735
|
+
return false;
|
|
17736
|
+
}
|
|
17737
|
+
if (top.element.name.toLowerCase() !== tag.name.toLowerCase()) {
|
|
17738
|
+
report2(tagRange(tag), `expected </${top.element.name}> but found ${found}`);
|
|
17739
|
+
return false;
|
|
17740
|
+
}
|
|
17741
|
+
stack.pop();
|
|
17742
|
+
}
|
|
17743
|
+
}
|
|
17744
|
+
return true;
|
|
17745
|
+
};
|
|
17746
|
+
for (let k = from; k < to; k++) {
|
|
17747
|
+
const macro = macros[k];
|
|
17748
|
+
const start = macroStart(k);
|
|
17749
|
+
if (!applyTags(start)) return;
|
|
17750
|
+
if (start >= certainUntil) return;
|
|
17751
|
+
while (s < scan.macros.length && scan.macros[s] < start) s++;
|
|
17752
|
+
if (scan.macros[s] !== start) return;
|
|
17753
|
+
lastMacroEnd = positionToOffset(macro.range.end, lineStarts) - base;
|
|
17754
|
+
const top = stack[stack.length - 1];
|
|
17755
|
+
if (top && "element" in top) replay.elements.set(k, top.element.name);
|
|
17756
|
+
if (!workspace.isContainer(macro.name)) continue;
|
|
17757
|
+
if (macro.pair === -1) return;
|
|
17758
|
+
if (macro.open) {
|
|
17759
|
+
stack.push({ macro: k });
|
|
17760
|
+
continue;
|
|
17761
|
+
}
|
|
17762
|
+
const opener = stack.pop();
|
|
17763
|
+
if (opener && "element" in opener) {
|
|
17764
|
+
report2(macro.range, `expected </${opener.element.name}> but found {/${macro.name}}`);
|
|
17765
|
+
return;
|
|
17766
|
+
}
|
|
17767
|
+
if (!opener || opener.macro !== macro.pair) return;
|
|
17768
|
+
}
|
|
17769
|
+
if (!applyTags(Infinity) || certainUntil !== Infinity) return;
|
|
17770
|
+
for (const entry of stack) {
|
|
17771
|
+
if ("element" in entry) report2(tagRange(entry.element), `unclosed <${entry.element.name}>`);
|
|
17772
|
+
}
|
|
17773
|
+
}
|
|
16791
17774
|
function isInsideParent(macros, index, parents, workspace) {
|
|
16792
17775
|
const parentSet = new Set(parents.map((p) => p.toLowerCase()));
|
|
16793
17776
|
for (let i = index - 1; i >= 0; i--) {
|
|
@@ -16803,18 +17786,18 @@ function isInsideParent(macros, index, parents, workspace) {
|
|
|
16803
17786
|
}
|
|
16804
17787
|
return false;
|
|
16805
17788
|
}
|
|
16806
|
-
function validateChildren(macros, curIndex, parentMacro, childConstraints, workspace, diagnostics) {
|
|
17789
|
+
function validateChildren(macros, curIndex, parentMacro, childConstraints, elements, workspace, diagnostics) {
|
|
16807
17790
|
const children = /* @__PURE__ */ Object.create(null);
|
|
16808
17791
|
const startIndex = curIndex + 1;
|
|
16809
17792
|
const endIndex = parentMacro.pair;
|
|
16810
17793
|
for (let i = startIndex; i < endIndex; i++) {
|
|
16811
17794
|
const child = macros[i];
|
|
16812
|
-
|
|
16813
|
-
if (!childInfo) continue;
|
|
16814
|
-
if (childInfo.block && child.open && child.pair !== -1) {
|
|
17795
|
+
if (child.open && child.pair !== -1 && workspace.isContainer(child.name)) {
|
|
16815
17796
|
i = child.pair;
|
|
16816
17797
|
continue;
|
|
16817
17798
|
}
|
|
17799
|
+
if (elements.has(i)) continue;
|
|
17800
|
+
if (!workspace.macros.getMacro(child.name)) continue;
|
|
16818
17801
|
const childKey = child.name.toLowerCase();
|
|
16819
17802
|
if (childConstraints.some((c3) => c3.name.toLowerCase() === childKey)) {
|
|
16820
17803
|
children[childKey] = (children[childKey] ?? 0) + 1;
|
|
@@ -16929,15 +17912,25 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
16929
17912
|
`Variable '$${u.name}' is not declared in StoryVariables`
|
|
16930
17913
|
));
|
|
16931
17914
|
}
|
|
17915
|
+
for (const a of workspace.variables.getPrimitiveFieldAccesses(uri)) {
|
|
17916
|
+
diagnostics.push(makeDiag(
|
|
17917
|
+
a.range,
|
|
17918
|
+
DiagnosticCode.PrimitiveFieldAccess,
|
|
17919
|
+
`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.`
|
|
17920
|
+
));
|
|
17921
|
+
}
|
|
16932
17922
|
const storyVars = workspace.passages.getStoryVariables();
|
|
16933
17923
|
if (storyVars && storyVars.uri === uri) {
|
|
16934
17924
|
for (const nd of workspace.variables.getNullDeclarations()) {
|
|
16935
17925
|
diagnostics.push(makeDiag(
|
|
16936
17926
|
nd.range,
|
|
16937
17927
|
DiagnosticCode.NullVariableValue,
|
|
16938
|
-
|
|
17928
|
+
`${nullSubject(nd)} is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
16939
17929
|
));
|
|
16940
17930
|
}
|
|
17931
|
+
for (const d of workspace.variables.getInvalidDeclarations()) {
|
|
17932
|
+
diagnostics.push(makeDiag(d.range, DiagnosticCode.InvalidDeclaration, `${d.message} ${WILL_NOT_START}`));
|
|
17933
|
+
}
|
|
16941
17934
|
}
|
|
16942
17935
|
}
|
|
16943
17936
|
if (workspace.variables.hasStoryTransients()) {
|
|
@@ -16955,12 +17948,20 @@ function validateVariables(uri, workspace, diagnostics) {
|
|
|
16955
17948
|
diagnostics.push(makeDiag(
|
|
16956
17949
|
nd.range,
|
|
16957
17950
|
DiagnosticCode.NullVariableValue,
|
|
16958
|
-
|
|
17951
|
+
`${nullSubject(nd)} is set to null. Spindle does not support null \u2014 use a valid default (number, string, boolean, array, or object).`
|
|
16959
17952
|
));
|
|
16960
17953
|
}
|
|
17954
|
+
for (const d of workspace.variables.getInvalidTransientDeclarations()) {
|
|
17955
|
+
diagnostics.push(makeDiag(d.range, DiagnosticCode.InvalidDeclaration, `${d.message} ${WILL_NOT_START}`));
|
|
17956
|
+
}
|
|
16961
17957
|
}
|
|
16962
17958
|
}
|
|
16963
17959
|
}
|
|
17960
|
+
function nullSubject(nd) {
|
|
17961
|
+
const variable = nd.sigil === "%" ? "Transient variable" : "Variable";
|
|
17962
|
+
if (nd.field?.length) return `Field '${nd.sigil}${[nd.name, ...nd.field].join(".")}'`;
|
|
17963
|
+
return `${variable} '${nd.sigil}${nd.name}'`;
|
|
17964
|
+
}
|
|
16964
17965
|
function validateLinks(text, passages, passageNames, diagnostics) {
|
|
16965
17966
|
const links = parseLinks(text);
|
|
16966
17967
|
for (const link of links) {
|
|
@@ -17177,7 +18178,7 @@ function widgetDefinitionNames(content) {
|
|
|
17177
18178
|
while ((m = re.exec(content)) !== null) names.push(m[2]);
|
|
17178
18179
|
return names;
|
|
17179
18180
|
}
|
|
17180
|
-
function
|
|
18181
|
+
function passageContent2(passage, workspace) {
|
|
17181
18182
|
const text = workspace.documents.getText(passage.uri) ?? "";
|
|
17182
18183
|
return text.split("\n").slice(passage.range.start.line + 1, passage.range.end.line + 1).join("\n");
|
|
17183
18184
|
}
|
|
@@ -17190,7 +18191,7 @@ function validateWidgetIncludes(text, macros, workspace, diagnostics) {
|
|
|
17190
18191
|
if (target === null) continue;
|
|
17191
18192
|
const passage = workspace.passages.getPassage(target);
|
|
17192
18193
|
if (!passage?.tags?.includes("widget")) continue;
|
|
17193
|
-
const widgets = widgetDefinitionNames(
|
|
18194
|
+
const widgets = widgetDefinitionNames(passageContent2(passage, workspace));
|
|
17194
18195
|
if (widgets.length === 0) continue;
|
|
17195
18196
|
const list = widgets.map((w) => `{${w}}`).join(", ");
|
|
17196
18197
|
diagnostics.push(makeDiag(
|
|
@@ -17263,18 +18264,28 @@ function toLspDiagnostic(d) {
|
|
|
17263
18264
|
message: d.message
|
|
17264
18265
|
};
|
|
17265
18266
|
}
|
|
17266
|
-
var DEFERRED_CONTAINERS, EXPRESSION_BUILTINS2, diagnosticsPlugin;
|
|
18267
|
+
var DIRECT_CHILD_MACROS, NON_MARKUP_PASSAGES, WILL_NOT_START, DEFERRED_CONTAINERS, EXPRESSION_BUILTINS2, diagnosticsPlugin;
|
|
17267
18268
|
var init_diagnostics = __esm({
|
|
17268
18269
|
"src/plugins/diagnostics.ts"() {
|
|
17269
18270
|
"use strict";
|
|
17270
18271
|
init_diagnostic_codes();
|
|
17271
18272
|
init_macro_parser();
|
|
17272
18273
|
init_argument_lexer();
|
|
18274
|
+
init_html_scanner();
|
|
17273
18275
|
init_widget_arguments();
|
|
17274
18276
|
init_parameter_validator();
|
|
17275
18277
|
init_link_parser();
|
|
17276
18278
|
init_passage_parser();
|
|
17277
18279
|
init_macro_sources();
|
|
18280
|
+
DIRECT_CHILD_MACROS = /* @__PURE__ */ new Set(["elseif", "else", "case", "default", "next", "option"]);
|
|
18281
|
+
NON_MARKUP_PASSAGES = /* @__PURE__ */ new Set([
|
|
18282
|
+
"StoryTitle",
|
|
18283
|
+
"StoryData",
|
|
18284
|
+
"StoryVariables",
|
|
18285
|
+
"StoryTransients",
|
|
18286
|
+
"SaveTitle"
|
|
18287
|
+
]);
|
|
18288
|
+
WILL_NOT_START = "Spindle will not start the story.";
|
|
17278
18289
|
DEFERRED_CONTAINERS = /* @__PURE__ */ new Set(["link", "button"]);
|
|
17279
18290
|
EXPRESSION_BUILTINS2 = /* @__PURE__ */ new Set([
|
|
17280
18291
|
"currentPassage",
|
|
@@ -17302,12 +18313,14 @@ var init_diagnostics = __esm({
|
|
|
17302
18313
|
diagnostics: diags.filter(isEnabled).map((d) => toLspDiagnostic(d))
|
|
17303
18314
|
});
|
|
17304
18315
|
};
|
|
17305
|
-
|
|
18316
|
+
const publishAll = () => {
|
|
17306
18317
|
for (const uri of ctx.workspace.documents.getUris()) {
|
|
17307
18318
|
if (isMacroSource(uri)) continue;
|
|
17308
18319
|
publishFor(uri);
|
|
17309
18320
|
}
|
|
17310
|
-
}
|
|
18321
|
+
};
|
|
18322
|
+
ctx.workspace.on("modelReady", publishAll);
|
|
18323
|
+
ctx.workspace.on("storyFormatChanged", publishAll);
|
|
17311
18324
|
ctx.workspace.on("documentChanged", publishFor);
|
|
17312
18325
|
ctx.workspace.on("documentClosed", (uri) => {
|
|
17313
18326
|
ctx.connection.sendDiagnostics({ uri, diagnostics: [] });
|
|
@@ -17484,7 +18497,7 @@ var check_exports = {};
|
|
|
17484
18497
|
__export(check_exports, {
|
|
17485
18498
|
runCheck: () => runCheck
|
|
17486
18499
|
});
|
|
17487
|
-
import { readFileSync as
|
|
18500
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
17488
18501
|
import { resolve as resolve2 } from "node:path";
|
|
17489
18502
|
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
17490
18503
|
function parseArgs(args2) {
|
|
@@ -17605,16 +18618,22 @@ async function runCheck(args2) {
|
|
|
17605
18618
|
}
|
|
17606
18619
|
projectConfig = loadConfigFromDisk(configRoot);
|
|
17607
18620
|
}
|
|
17608
|
-
const workspace = new WorkspaceModel({ workspaceRoot: commonDir });
|
|
17609
18621
|
const fileContents = /* @__PURE__ */ new Map();
|
|
17610
18622
|
for (const filePath of uniqueFiles) {
|
|
17611
18623
|
try {
|
|
17612
|
-
const text =
|
|
18624
|
+
const text = readFileSync6(filePath, "utf-8");
|
|
17613
18625
|
const uri = pathToFileURL3(filePath).toString();
|
|
17614
18626
|
fileContents.set(uri, text);
|
|
17615
18627
|
} catch {
|
|
17616
18628
|
}
|
|
17617
18629
|
}
|
|
18630
|
+
const storyFormat = await findStoryFormat(fileContents.values(), commonDir);
|
|
18631
|
+
if (!storyFormat.isSpindle) {
|
|
18632
|
+
console.error(skippedFormatNote(storyFormat));
|
|
18633
|
+
report(options.format, []);
|
|
18634
|
+
return 0;
|
|
18635
|
+
}
|
|
18636
|
+
const workspace = new WorkspaceModel({ workspaceRoot: commonDir });
|
|
17618
18637
|
const workspaceContents = new Map(fileContents);
|
|
17619
18638
|
await addProjectMacroSources(workspaceContents, commonDir);
|
|
17620
18639
|
workspace.initialize(workspaceContents);
|
|
@@ -17639,19 +18658,22 @@ async function runCheck(args2) {
|
|
|
17639
18658
|
}
|
|
17640
18659
|
}
|
|
17641
18660
|
}
|
|
17642
|
-
|
|
18661
|
+
report(options.format, allResults);
|
|
18662
|
+
workspace.dispose();
|
|
18663
|
+
return hasErrors ? 1 : 0;
|
|
18664
|
+
}
|
|
18665
|
+
function report(format, results) {
|
|
18666
|
+
switch (format) {
|
|
17643
18667
|
case "pretty":
|
|
17644
|
-
console.log(formatPretty(
|
|
18668
|
+
console.log(formatPretty(results));
|
|
17645
18669
|
break;
|
|
17646
18670
|
case "json":
|
|
17647
|
-
console.log(formatJson(
|
|
18671
|
+
console.log(formatJson(results));
|
|
17648
18672
|
break;
|
|
17649
18673
|
case "sarif":
|
|
17650
|
-
console.log(formatSarif(
|
|
18674
|
+
console.log(formatSarif(results));
|
|
17651
18675
|
break;
|
|
17652
18676
|
}
|
|
17653
|
-
workspace.dispose();
|
|
17654
|
-
return hasErrors ? 1 : 0;
|
|
17655
18677
|
}
|
|
17656
18678
|
var SEVERITY_ORDER;
|
|
17657
18679
|
var init_check = __esm({
|
|
@@ -17662,6 +18684,7 @@ var init_check = __esm({
|
|
|
17662
18684
|
init_diagnostics();
|
|
17663
18685
|
init_config_loader();
|
|
17664
18686
|
init_macro_sources();
|
|
18687
|
+
init_story_format();
|
|
17665
18688
|
init_pretty();
|
|
17666
18689
|
init_json();
|
|
17667
18690
|
init_sarif();
|
|
@@ -17705,13 +18728,17 @@ function splitPassages(text) {
|
|
|
17705
18728
|
return passages;
|
|
17706
18729
|
}
|
|
17707
18730
|
function classifyPassage(header) {
|
|
17708
|
-
const
|
|
17709
|
-
if (!tagMatch) return "normal";
|
|
17710
|
-
const tags = tagMatch.flatMap((t) => t.slice(1, -1).toLowerCase().split(/\s+/));
|
|
18731
|
+
const tags = headerTags(header).map((t) => t.toLowerCase());
|
|
17711
18732
|
if (tags.includes("script")) return "script";
|
|
17712
18733
|
if (tags.includes("stylesheet")) return "stylesheet";
|
|
17713
18734
|
return "normal";
|
|
17714
18735
|
}
|
|
18736
|
+
function headerTags(header) {
|
|
18737
|
+
const parsed = parsePassageHeader(header, 0);
|
|
18738
|
+
if (parsed) return parsed.tags;
|
|
18739
|
+
const unescaped = header.replace(/\\./g, "ec");
|
|
18740
|
+
return (unescaped.match(/\[[^\]]*\]/g) ?? []).flatMap((t) => t.slice(1, -1).split(/\s+/));
|
|
18741
|
+
}
|
|
17715
18742
|
function segmentRegions(body) {
|
|
17716
18743
|
const lines = body.split("\n");
|
|
17717
18744
|
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
@@ -17758,7 +18785,7 @@ function segmentRegions(body) {
|
|
|
17758
18785
|
if (/^<([a-zA-Z][\w-]*)/.test(trimmed2)) {
|
|
17759
18786
|
const tagMatch = trimmed2.match(/^<([a-zA-Z][\w-]*)/);
|
|
17760
18787
|
const tagName = tagMatch[1].toLowerCase();
|
|
17761
|
-
if (
|
|
18788
|
+
if (VOID_ELEMENTS2.has(tagName) && !lines[i + 1]?.trim().startsWith("<")) {
|
|
17762
18789
|
pushSpindleLine(regions, line);
|
|
17763
18790
|
i++;
|
|
17764
18791
|
continue;
|
|
@@ -17795,12 +18822,13 @@ function pushSpindleLine(regions, line) {
|
|
|
17795
18822
|
regions.push({ type: "spindle", lines: [line] });
|
|
17796
18823
|
}
|
|
17797
18824
|
}
|
|
17798
|
-
var PASSAGE_HEADER_REGEX,
|
|
18825
|
+
var PASSAGE_HEADER_REGEX, VOID_ELEMENTS2;
|
|
17799
18826
|
var init_segment = __esm({
|
|
17800
18827
|
"src/plugins/format/segment.ts"() {
|
|
17801
18828
|
"use strict";
|
|
18829
|
+
init_passage_parser();
|
|
17802
18830
|
PASSAGE_HEADER_REGEX = /^::\s+/;
|
|
17803
|
-
|
|
18831
|
+
VOID_ELEMENTS2 = /* @__PURE__ */ new Set([
|
|
17804
18832
|
"area",
|
|
17805
18833
|
"base",
|
|
17806
18834
|
"br",
|
|
@@ -18287,11 +19315,12 @@ var init_format = __esm({
|
|
|
18287
19315
|
"src/plugins/format.ts"() {
|
|
18288
19316
|
"use strict";
|
|
18289
19317
|
init_macro_supplements();
|
|
19318
|
+
init_macro_parser();
|
|
18290
19319
|
init_segment();
|
|
18291
19320
|
init_prettier_bridge();
|
|
18292
19321
|
init_placeholders();
|
|
18293
19322
|
PASSAGE_HEADER_REGEX2 = /^(::)\s+/;
|
|
18294
|
-
MACRO_OPEN_REGEX =
|
|
19323
|
+
MACRO_OPEN_REGEX = new RegExp(String.raw`^\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\b`);
|
|
18295
19324
|
MACRO_CLOSE_REGEX = /^\{\/([A-Za-z][\w-]*)\b/;
|
|
18296
19325
|
DEFAULT_DEDENTING = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
|
|
18297
19326
|
LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
@@ -18304,7 +19333,8 @@ var init_format = __esm({
|
|
|
18304
19333
|
},
|
|
18305
19334
|
initialize(ctx) {
|
|
18306
19335
|
const formatOpts = {
|
|
18307
|
-
|
|
19336
|
+
// Block widgets (whose body renders {@children}) are containers too
|
|
19337
|
+
isBlock: (name) => ctx.workspace.isContainer(name),
|
|
18308
19338
|
isDedentingSubMacro: (name) => DEFAULT_DEDENTING.has(name.toLowerCase())
|
|
18309
19339
|
};
|
|
18310
19340
|
ctx.connection.onDocumentFormatting(async (params) => {
|
|
@@ -18349,7 +19379,7 @@ var format_exports = {};
|
|
|
18349
19379
|
__export(format_exports, {
|
|
18350
19380
|
runFormat: () => runFormat
|
|
18351
19381
|
});
|
|
18352
|
-
import { readFileSync as
|
|
19382
|
+
import { readFileSync as readFileSync7, writeFileSync } from "node:fs";
|
|
18353
19383
|
function parseArgs2(args2) {
|
|
18354
19384
|
const options = {
|
|
18355
19385
|
check: false,
|
|
@@ -18399,14 +19429,25 @@ async function runFormat(args2) {
|
|
|
18399
19429
|
console.log("No files found");
|
|
18400
19430
|
return 0;
|
|
18401
19431
|
}
|
|
19432
|
+
const texts = /* @__PURE__ */ new Map();
|
|
19433
|
+
for (const filePath of uniqueFiles) {
|
|
19434
|
+
try {
|
|
19435
|
+
texts.set(filePath, readFileSync7(filePath, "utf-8"));
|
|
19436
|
+
} catch {
|
|
19437
|
+
}
|
|
19438
|
+
}
|
|
19439
|
+
const storyFormat = await findStoryFormat(texts.values(), commonDirectory(uniqueFiles));
|
|
19440
|
+
if (!storyFormat.isSpindle) {
|
|
19441
|
+
console.error(skippedFormatNote(storyFormat));
|
|
19442
|
+
return 0;
|
|
19443
|
+
}
|
|
18402
19444
|
const formatOpts = {};
|
|
18403
19445
|
if (options.maxLineLength !== null) {
|
|
18404
19446
|
formatOpts.maxLineLength = options.maxLineLength;
|
|
18405
19447
|
}
|
|
18406
19448
|
const unformatted = [];
|
|
18407
|
-
for (const filePath of
|
|
19449
|
+
for (const [filePath, text] of texts) {
|
|
18408
19450
|
try {
|
|
18409
|
-
const text = readFileSync6(filePath, "utf-8");
|
|
18410
19451
|
const formatted = await formatDocument(text, formatOpts);
|
|
18411
19452
|
if (formatted !== text) {
|
|
18412
19453
|
if (options.check) {
|
|
@@ -18437,6 +19478,8 @@ var init_format2 = __esm({
|
|
|
18437
19478
|
"use strict";
|
|
18438
19479
|
init_esm6();
|
|
18439
19480
|
init_format();
|
|
19481
|
+
init_macro_sources();
|
|
19482
|
+
init_story_format();
|
|
18440
19483
|
}
|
|
18441
19484
|
});
|
|
18442
19485
|
|
|
@@ -49584,9 +50627,12 @@ var init_stdio2 = __esm({
|
|
|
49584
50627
|
var server_exports = {};
|
|
49585
50628
|
__export(server_exports, {
|
|
49586
50629
|
checkFiles: () => checkFiles,
|
|
50630
|
+
checkFormatting: () => checkFormatting,
|
|
50631
|
+
checkProject: () => checkProject,
|
|
50632
|
+
formatFiles: () => formatFiles,
|
|
49587
50633
|
startMcpServer: () => startMcpServer
|
|
49588
50634
|
});
|
|
49589
|
-
import { readFileSync as
|
|
50635
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
49590
50636
|
import { resolve as resolve3 } from "node:path";
|
|
49591
50637
|
import { pathToFileURL as pathToFileURL4 } from "node:url";
|
|
49592
50638
|
function filterBySeverity2(diagnostics, minSeverity) {
|
|
@@ -49618,20 +50664,28 @@ function findConfigRoot(commonDir) {
|
|
|
49618
50664
|
}
|
|
49619
50665
|
return configRoot;
|
|
49620
50666
|
}
|
|
49621
|
-
|
|
49622
|
-
const commonDir = commonDirectory(files);
|
|
49623
|
-
const configRoot = findConfigRoot(commonDir);
|
|
49624
|
-
const projectConfig = loadConfigFromDisk(configRoot);
|
|
49625
|
-
const workspace = new WorkspaceModel({ workspaceRoot: configRoot });
|
|
50667
|
+
function readFiles(files) {
|
|
49626
50668
|
const fileContents = /* @__PURE__ */ new Map();
|
|
49627
50669
|
for (const filePath of files) {
|
|
49628
50670
|
try {
|
|
49629
|
-
const text =
|
|
50671
|
+
const text = readFileSync8(filePath, "utf-8");
|
|
49630
50672
|
const uri = pathToFileURL4(filePath).toString();
|
|
49631
50673
|
fileContents.set(uri, text);
|
|
49632
50674
|
} catch {
|
|
49633
50675
|
}
|
|
49634
50676
|
}
|
|
50677
|
+
return fileContents;
|
|
50678
|
+
}
|
|
50679
|
+
async function skippedReason(fileContents, files) {
|
|
50680
|
+
const format = await findStoryFormat(fileContents.values(), commonDirectory(files));
|
|
50681
|
+
return format.isSpindle ? void 0 : skippedFormatNote(format);
|
|
50682
|
+
}
|
|
50683
|
+
async function createWorkspace(files, fileContents) {
|
|
50684
|
+
const commonDir = commonDirectory(files);
|
|
50685
|
+
const configRoot = findConfigRoot(commonDir);
|
|
50686
|
+
const projectConfig = loadConfigFromDisk(configRoot);
|
|
50687
|
+
const workspace = new WorkspaceModel({ workspaceRoot: configRoot });
|
|
50688
|
+
fileContents = new Map(fileContents);
|
|
49635
50689
|
await addProjectMacroSources(fileContents, commonDir);
|
|
49636
50690
|
workspace.initialize(fileContents);
|
|
49637
50691
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
@@ -49640,9 +50694,15 @@ async function createWorkspace(files) {
|
|
|
49640
50694
|
return workspace;
|
|
49641
50695
|
}
|
|
49642
50696
|
async function checkFiles(pattern, severity, cwd = process.cwd()) {
|
|
50697
|
+
return (await checkProject(pattern, severity, cwd)).results;
|
|
50698
|
+
}
|
|
50699
|
+
async function checkProject(pattern, severity, cwd = process.cwd()) {
|
|
49643
50700
|
const files = await resolveFiles(pattern, cwd);
|
|
49644
|
-
if (files.length === 0) return [];
|
|
49645
|
-
const
|
|
50701
|
+
if (files.length === 0) return { results: [] };
|
|
50702
|
+
const fileContents = readFiles(files);
|
|
50703
|
+
const skipped = await skippedReason(fileContents, files);
|
|
50704
|
+
if (skipped) return { results: [], skipped };
|
|
50705
|
+
const workspace = await createWorkspace(files, fileContents);
|
|
49646
50706
|
try {
|
|
49647
50707
|
const results = [];
|
|
49648
50708
|
for (const filePath of files) {
|
|
@@ -49664,11 +50724,57 @@ async function checkFiles(pattern, severity, cwd = process.cwd()) {
|
|
|
49664
50724
|
});
|
|
49665
50725
|
}
|
|
49666
50726
|
}
|
|
49667
|
-
return results;
|
|
50727
|
+
return { results };
|
|
49668
50728
|
} finally {
|
|
49669
50729
|
workspace.dispose();
|
|
49670
50730
|
}
|
|
49671
50731
|
}
|
|
50732
|
+
function relativeTo(cwd, filePath) {
|
|
50733
|
+
return filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
|
|
50734
|
+
}
|
|
50735
|
+
async function formatFiles(pattern, cwd = process.cwd()) {
|
|
50736
|
+
const files = await resolveFiles(pattern, cwd);
|
|
50737
|
+
const contents = readFiles(files);
|
|
50738
|
+
const skipped = files.length > 0 ? await skippedReason(contents, files) : void 0;
|
|
50739
|
+
if (skipped) return { formatted: 0, unchanged: 0, files: [], skipped };
|
|
50740
|
+
let formatted = 0;
|
|
50741
|
+
let unchanged = 0;
|
|
50742
|
+
const changedFiles = [];
|
|
50743
|
+
for (const filePath of files) {
|
|
50744
|
+
const text = contents.get(pathToFileURL4(filePath).toString());
|
|
50745
|
+
if (text === void 0) continue;
|
|
50746
|
+
try {
|
|
50747
|
+
const result = await formatDocument(text);
|
|
50748
|
+
if (result !== text) {
|
|
50749
|
+
writeFileSync2(filePath, result, "utf-8");
|
|
50750
|
+
formatted++;
|
|
50751
|
+
changedFiles.push(relativeTo(cwd, filePath));
|
|
50752
|
+
} else {
|
|
50753
|
+
unchanged++;
|
|
50754
|
+
}
|
|
50755
|
+
} catch {
|
|
50756
|
+
}
|
|
50757
|
+
}
|
|
50758
|
+
return { formatted, unchanged, files: changedFiles };
|
|
50759
|
+
}
|
|
50760
|
+
async function checkFormatting(pattern, cwd = process.cwd()) {
|
|
50761
|
+
const files = await resolveFiles(pattern, cwd);
|
|
50762
|
+
const contents = readFiles(files);
|
|
50763
|
+
const skipped = files.length > 0 ? await skippedReason(contents, files) : void 0;
|
|
50764
|
+
if (skipped) return { needsFormatting: [], alreadyFormatted: [], skipped };
|
|
50765
|
+
const needsFormatting = [];
|
|
50766
|
+
const alreadyFormatted = [];
|
|
50767
|
+
for (const filePath of files) {
|
|
50768
|
+
const text = contents.get(pathToFileURL4(filePath).toString());
|
|
50769
|
+
if (text === void 0) continue;
|
|
50770
|
+
try {
|
|
50771
|
+
const result = await formatDocument(text);
|
|
50772
|
+
(result !== text ? needsFormatting : alreadyFormatted).push(relativeTo(cwd, filePath));
|
|
50773
|
+
} catch {
|
|
50774
|
+
}
|
|
50775
|
+
}
|
|
50776
|
+
return { needsFormatting, alreadyFormatted };
|
|
50777
|
+
}
|
|
49672
50778
|
async function startMcpServer() {
|
|
49673
50779
|
const server = new McpServer({
|
|
49674
50780
|
name: "spindle-lsp",
|
|
@@ -49682,9 +50788,12 @@ async function startMcpServer() {
|
|
|
49682
50788
|
severity: external_exports3.enum(["error", "warning", "info", "hint"]).optional().describe("Minimum severity to include")
|
|
49683
50789
|
},
|
|
49684
50790
|
async (args2) => {
|
|
49685
|
-
const results = await
|
|
50791
|
+
const { results, skipped } = await checkProject(args2.path, args2.severity);
|
|
49686
50792
|
return {
|
|
49687
|
-
content: [
|
|
50793
|
+
content: [
|
|
50794
|
+
{ type: "text", text: JSON.stringify(results, null, 2) },
|
|
50795
|
+
...skipped ? [{ type: "text", text: skipped }] : []
|
|
50796
|
+
]
|
|
49688
50797
|
};
|
|
49689
50798
|
}
|
|
49690
50799
|
);
|
|
@@ -49695,27 +50804,7 @@ async function startMcpServer() {
|
|
|
49695
50804
|
path: external_exports3.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to format")
|
|
49696
50805
|
},
|
|
49697
50806
|
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 };
|
|
50807
|
+
const output = await formatFiles(args2.path);
|
|
49719
50808
|
return {
|
|
49720
50809
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
49721
50810
|
};
|
|
@@ -49728,24 +50817,7 @@ async function startMcpServer() {
|
|
|
49728
50817
|
path: external_exports3.string().default("**/*.{tw,twee}").describe("Glob pattern or directory to check")
|
|
49729
50818
|
},
|
|
49730
50819
|
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 };
|
|
50820
|
+
const output = await checkFormatting(args2.path);
|
|
49749
50821
|
return {
|
|
49750
50822
|
content: [{ type: "text", text: JSON.stringify(output, null, 2) }]
|
|
49751
50823
|
};
|
|
@@ -49767,6 +50839,7 @@ var init_server3 = __esm({
|
|
|
49767
50839
|
init_macro_sources();
|
|
49768
50840
|
init_diagnostics();
|
|
49769
50841
|
init_format();
|
|
50842
|
+
init_story_format();
|
|
49770
50843
|
SEVERITY_ORDER2 = ["hint", "info", "warning", "error"];
|
|
49771
50844
|
}
|
|
49772
50845
|
});
|
|
@@ -58894,6 +59967,62 @@ var init_capabilities = __esm({
|
|
|
58894
59967
|
}
|
|
58895
59968
|
});
|
|
58896
59969
|
|
|
59970
|
+
// src/server/story-format-gate.ts
|
|
59971
|
+
function gateRegistrations(target, methods, enabled, nested = {}) {
|
|
59972
|
+
return new Proxy(target, {
|
|
59973
|
+
get(obj, prop, receiver) {
|
|
59974
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
59975
|
+
if (typeof prop !== "string") return value;
|
|
59976
|
+
if (Object.hasOwn(nested, prop) && value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
59977
|
+
return nested[prop](value);
|
|
59978
|
+
}
|
|
59979
|
+
if (methods.includes(prop) && typeof value === "function") {
|
|
59980
|
+
return (handler, ...rest) => Reflect.apply(value, obj, [
|
|
59981
|
+
(...args2) => enabled() ? handler(...args2) : null,
|
|
59982
|
+
...rest
|
|
59983
|
+
]);
|
|
59984
|
+
}
|
|
59985
|
+
return value;
|
|
59986
|
+
}
|
|
59987
|
+
});
|
|
59988
|
+
}
|
|
59989
|
+
function gateConnection(connection, enabled) {
|
|
59990
|
+
const languageFeatures = {};
|
|
59991
|
+
for (const [feature, methods] of Object.entries(GATED_LANGUAGE_FEATURES)) {
|
|
59992
|
+
languageFeatures[feature] = (value) => gateRegistrations(value, methods, enabled);
|
|
59993
|
+
}
|
|
59994
|
+
return gateRegistrations(connection, GATED_REQUESTS, enabled, {
|
|
59995
|
+
languages: (value) => gateRegistrations(value, [], enabled, languageFeatures)
|
|
59996
|
+
});
|
|
59997
|
+
}
|
|
59998
|
+
var GATED_REQUESTS, GATED_LANGUAGE_FEATURES;
|
|
59999
|
+
var init_story_format_gate = __esm({
|
|
60000
|
+
"src/server/story-format-gate.ts"() {
|
|
60001
|
+
"use strict";
|
|
60002
|
+
GATED_REQUESTS = [
|
|
60003
|
+
"onCompletion",
|
|
60004
|
+
"onHover",
|
|
60005
|
+
"onSignatureHelp",
|
|
60006
|
+
"onDefinition",
|
|
60007
|
+
"onReferences",
|
|
60008
|
+
"onPrepareRename",
|
|
60009
|
+
"onRenameRequest",
|
|
60010
|
+
"onCodeLens",
|
|
60011
|
+
"onCodeAction",
|
|
60012
|
+
"onDocumentFormatting",
|
|
60013
|
+
"onDocumentRangeFormatting",
|
|
60014
|
+
"onDocumentSymbol",
|
|
60015
|
+
"onWorkspaceSymbol",
|
|
60016
|
+
"onFoldingRanges",
|
|
60017
|
+
"onDocumentLinks"
|
|
60018
|
+
];
|
|
60019
|
+
GATED_LANGUAGE_FEATURES = {
|
|
60020
|
+
semanticTokens: ["on"],
|
|
60021
|
+
inlayHint: ["on"]
|
|
60022
|
+
};
|
|
60023
|
+
}
|
|
60024
|
+
});
|
|
60025
|
+
|
|
58897
60026
|
// src/plugins/completions.ts
|
|
58898
60027
|
function getCompletions(uri, position, triggerChar, workspace) {
|
|
58899
60028
|
const text = workspace.documents.getText(uri);
|
|
@@ -58904,11 +60033,11 @@ function getCompletions(uri, position, triggerChar, workspace) {
|
|
|
58904
60033
|
if (/\{\/[A-Za-z\w-]*$/.test(lineText)) {
|
|
58905
60034
|
return getClosingMacroCompletions(uri, text, position, workspace);
|
|
58906
60035
|
}
|
|
58907
|
-
const transientDotPathMatch = /%([
|
|
60036
|
+
const transientDotPathMatch = /%([\w$]+)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
58908
60037
|
if (transientDotPathMatch) {
|
|
58909
60038
|
return getTransientDotPathCompletions(transientDotPathMatch[1], workspace);
|
|
58910
60039
|
}
|
|
58911
|
-
const dotPathMatch = /\$([
|
|
60040
|
+
const dotPathMatch = /\$([\w$]+)\.([A-Za-z_$][\w$]*)?$/.exec(lineText);
|
|
58912
60041
|
if (dotPathMatch) {
|
|
58913
60042
|
return getDotPathCompletions(dotPathMatch[1], workspace);
|
|
58914
60043
|
}
|
|
@@ -59122,6 +60251,215 @@ var init_completions = __esm({
|
|
|
59122
60251
|
}
|
|
59123
60252
|
});
|
|
59124
60253
|
|
|
60254
|
+
// src/plugins/references.ts
|
|
60255
|
+
function findReferences(uri, position, workspace, includeDeclaration) {
|
|
60256
|
+
const text = workspace.documents.getText(uri);
|
|
60257
|
+
if (text === void 0) return [];
|
|
60258
|
+
const lines = text.split("\n");
|
|
60259
|
+
if (position.line >= lines.length) return [];
|
|
60260
|
+
const line = lines[position.line];
|
|
60261
|
+
const header = parsePassageHeader(line, position.line);
|
|
60262
|
+
if (header) {
|
|
60263
|
+
const { start, end } = header.nameRange;
|
|
60264
|
+
if (position.character >= start.character && position.character <= end.character) {
|
|
60265
|
+
return findPassageReferences(header.name, workspace, includeDeclaration);
|
|
60266
|
+
}
|
|
60267
|
+
}
|
|
60268
|
+
{
|
|
60269
|
+
const varRegex = /\$([\w$]+)/g;
|
|
60270
|
+
let match2;
|
|
60271
|
+
while ((match2 = varRegex.exec(line)) !== null) {
|
|
60272
|
+
const start = match2.index;
|
|
60273
|
+
const end = start + match2[0].length;
|
|
60274
|
+
if (position.character >= start && position.character <= end) {
|
|
60275
|
+
const varName = match2[1];
|
|
60276
|
+
return findVariableReferences(varName, workspace, includeDeclaration);
|
|
60277
|
+
}
|
|
60278
|
+
}
|
|
60279
|
+
}
|
|
60280
|
+
{
|
|
60281
|
+
const transRegex = /(?<!\w)%([\w$]+)/g;
|
|
60282
|
+
let match2;
|
|
60283
|
+
while ((match2 = transRegex.exec(line)) !== null) {
|
|
60284
|
+
const start = match2.index;
|
|
60285
|
+
const end = start + match2[0].length;
|
|
60286
|
+
if (position.character >= start && position.character <= end) {
|
|
60287
|
+
const varName = match2[1];
|
|
60288
|
+
if (!isTransientAt(varName, uri, position.line, start, workspace)) break;
|
|
60289
|
+
return findTransientReferences(varName, workspace, includeDeclaration);
|
|
60290
|
+
}
|
|
60291
|
+
}
|
|
60292
|
+
}
|
|
60293
|
+
{
|
|
60294
|
+
const widgetRegex = /\{\/?([A-Za-z_$][\w$]*)/g;
|
|
60295
|
+
let match2;
|
|
60296
|
+
while ((match2 = widgetRegex.exec(line)) !== null) {
|
|
60297
|
+
const name = match2[1];
|
|
60298
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
60299
|
+
const nameEnd = nameStart + name.length;
|
|
60300
|
+
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
60301
|
+
const widget = workspace.widgets.getWidget(name);
|
|
60302
|
+
const isClosing = match2[0][1] === "/";
|
|
60303
|
+
if (!workspace.macros.getMacro(name) && widget && (!isClosing || widget.block)) {
|
|
60304
|
+
return findWidgetReferences(name, workspace, includeDeclaration);
|
|
60305
|
+
}
|
|
60306
|
+
}
|
|
60307
|
+
}
|
|
60308
|
+
}
|
|
60309
|
+
const passageRef = findPassageRefAt(text, position);
|
|
60310
|
+
if (passageRef) {
|
|
60311
|
+
return findPassageReferences(passageRef.name, workspace, includeDeclaration);
|
|
60312
|
+
}
|
|
60313
|
+
{
|
|
60314
|
+
const allPassages = workspace.passages.getAllPassages();
|
|
60315
|
+
const passageNames = new Set(allPassages.map((p) => p.name));
|
|
60316
|
+
const wordRegex = /[A-Za-z_$][\w$\s]*/g;
|
|
60317
|
+
let match2;
|
|
60318
|
+
while ((match2 = wordRegex.exec(line)) !== null) {
|
|
60319
|
+
const start = match2.index;
|
|
60320
|
+
const end = start + match2[0].length;
|
|
60321
|
+
if (position.character >= start && position.character <= end) {
|
|
60322
|
+
const word = match2[0].trim();
|
|
60323
|
+
if (passageNames.has(word)) {
|
|
60324
|
+
return findPassageReferences(word, workspace, includeDeclaration);
|
|
60325
|
+
}
|
|
60326
|
+
}
|
|
60327
|
+
}
|
|
60328
|
+
}
|
|
60329
|
+
return [];
|
|
60330
|
+
}
|
|
60331
|
+
function findPassageReferences(passageName, workspace, includeDeclaration) {
|
|
60332
|
+
const locations = [];
|
|
60333
|
+
if (includeDeclaration) {
|
|
60334
|
+
const passage = workspace.passages.getPassage(passageName);
|
|
60335
|
+
if (passage) {
|
|
60336
|
+
locations.push({
|
|
60337
|
+
uri: passage.uri,
|
|
60338
|
+
range: passage.nameRange
|
|
60339
|
+
});
|
|
60340
|
+
}
|
|
60341
|
+
}
|
|
60342
|
+
for (const docUri of workspace.documents.getUris()) {
|
|
60343
|
+
if (isMacroSource(docUri)) continue;
|
|
60344
|
+
const docText = workspace.documents.getText(docUri);
|
|
60345
|
+
if (!docText) continue;
|
|
60346
|
+
for (const ref of [...parseLinks(docText), ...parseMacroPassageRefs(docText)]) {
|
|
60347
|
+
if (ref.name === passageName) {
|
|
60348
|
+
locations.push({ uri: docUri, range: ref.range });
|
|
60349
|
+
}
|
|
60350
|
+
}
|
|
60351
|
+
}
|
|
60352
|
+
return locations;
|
|
60353
|
+
}
|
|
60354
|
+
function findVariableReferences(varName, workspace, includeDeclaration) {
|
|
60355
|
+
const locations = [];
|
|
60356
|
+
if (includeDeclaration) {
|
|
60357
|
+
const decl = workspace.variables.getDeclared().get(varName);
|
|
60358
|
+
if (decl?.declarationRange && decl.declarationUri) {
|
|
60359
|
+
locations.push({
|
|
60360
|
+
uri: decl.declarationUri,
|
|
60361
|
+
range: decl.declarationRange
|
|
60362
|
+
});
|
|
60363
|
+
}
|
|
60364
|
+
}
|
|
60365
|
+
const usages = workspace.variables.getUsages(varName);
|
|
60366
|
+
for (const u of usages) {
|
|
60367
|
+
locations.push({ uri: u.uri, range: u.range });
|
|
60368
|
+
}
|
|
60369
|
+
return locations;
|
|
60370
|
+
}
|
|
60371
|
+
function isTransientAt(name, uri, line, character, workspace) {
|
|
60372
|
+
if (!/^\d/.test(name)) return true;
|
|
60373
|
+
const at = (u) => u.uri === uri && u.range?.start.line === line && u.range.start.character === character;
|
|
60374
|
+
const decl = workspace.variables.getDeclaredTransient().get(name);
|
|
60375
|
+
if (decl && at({ uri: decl.declarationUri, range: decl.declarationRange })) return true;
|
|
60376
|
+
return workspace.variables.getTransientUsages(name).some(at);
|
|
60377
|
+
}
|
|
60378
|
+
function findTransientReferences(varName, workspace, includeDeclaration) {
|
|
60379
|
+
const results = [];
|
|
60380
|
+
if (includeDeclaration) {
|
|
60381
|
+
const decl = workspace.variables.getDeclaredTransient().get(varName);
|
|
60382
|
+
if (decl?.declarationUri && decl.declarationRange) {
|
|
60383
|
+
results.push({ uri: decl.declarationUri, range: decl.declarationRange });
|
|
60384
|
+
}
|
|
60385
|
+
}
|
|
60386
|
+
const usages = workspace.variables.getTransientUsages(varName);
|
|
60387
|
+
for (const u of usages) {
|
|
60388
|
+
results.push({ uri: u.uri, range: u.range });
|
|
60389
|
+
}
|
|
60390
|
+
return results;
|
|
60391
|
+
}
|
|
60392
|
+
function findWidgetReferences(widgetName, workspace, includeDeclaration) {
|
|
60393
|
+
const locations = [];
|
|
60394
|
+
const widget = workspace.widgets.getWidget(widgetName);
|
|
60395
|
+
if (includeDeclaration && widget) {
|
|
60396
|
+
locations.push({ uri: widget.uri, range: widget.range });
|
|
60397
|
+
}
|
|
60398
|
+
const widgetInvocationRegex = /\{(\/)?([A-Za-z_$][\w$]*)\b/g;
|
|
60399
|
+
const lowerName = widgetName.toLowerCase();
|
|
60400
|
+
const isBlock = widget?.block ?? false;
|
|
60401
|
+
for (const docUri of workspace.documents.getUris()) {
|
|
60402
|
+
if (isMacroSource(docUri)) continue;
|
|
60403
|
+
const docText = workspace.documents.getText(docUri);
|
|
60404
|
+
if (!docText) continue;
|
|
60405
|
+
const lines = docText.split("\n");
|
|
60406
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
60407
|
+
const line = lines[lineNum];
|
|
60408
|
+
widgetInvocationRegex.lastIndex = 0;
|
|
60409
|
+
let match2;
|
|
60410
|
+
while ((match2 = widgetInvocationRegex.exec(line)) !== null) {
|
|
60411
|
+
if (match2[1] && !isBlock) continue;
|
|
60412
|
+
if (match2[2].toLowerCase() === lowerName) {
|
|
60413
|
+
const nameStart = match2.index + 1 + (match2[1] ? 1 : 0);
|
|
60414
|
+
locations.push({
|
|
60415
|
+
uri: docUri,
|
|
60416
|
+
range: {
|
|
60417
|
+
start: { line: lineNum, character: nameStart },
|
|
60418
|
+
end: { line: lineNum, character: nameStart + match2[2].length }
|
|
60419
|
+
}
|
|
60420
|
+
});
|
|
60421
|
+
}
|
|
60422
|
+
}
|
|
60423
|
+
}
|
|
60424
|
+
}
|
|
60425
|
+
return locations;
|
|
60426
|
+
}
|
|
60427
|
+
function toLspRange(r) {
|
|
60428
|
+
return {
|
|
60429
|
+
start: { line: r.start.line, character: r.start.character },
|
|
60430
|
+
end: { line: r.end.line, character: r.end.character }
|
|
60431
|
+
};
|
|
60432
|
+
}
|
|
60433
|
+
var referencesPlugin;
|
|
60434
|
+
var init_references = __esm({
|
|
60435
|
+
"src/plugins/references.ts"() {
|
|
60436
|
+
"use strict";
|
|
60437
|
+
init_link_parser();
|
|
60438
|
+
init_passage_parser();
|
|
60439
|
+
init_macro_sources();
|
|
60440
|
+
referencesPlugin = {
|
|
60441
|
+
id: "references",
|
|
60442
|
+
capabilities: {
|
|
60443
|
+
referencesProvider: true
|
|
60444
|
+
},
|
|
60445
|
+
initialize(ctx) {
|
|
60446
|
+
ctx.connection.onReferences((params) => {
|
|
60447
|
+
const refs = findReferences(
|
|
60448
|
+
params.textDocument.uri,
|
|
60449
|
+
{ line: params.position.line, character: params.position.character },
|
|
60450
|
+
ctx.workspace,
|
|
60451
|
+
params.context.includeDeclaration
|
|
60452
|
+
);
|
|
60453
|
+
return refs.map((r) => ({
|
|
60454
|
+
uri: r.uri,
|
|
60455
|
+
range: toLspRange(r.range)
|
|
60456
|
+
}));
|
|
60457
|
+
});
|
|
60458
|
+
}
|
|
60459
|
+
};
|
|
60460
|
+
}
|
|
60461
|
+
});
|
|
60462
|
+
|
|
59125
60463
|
// src/plugins/hover.ts
|
|
59126
60464
|
function getHoverInfo(uri, position, workspace) {
|
|
59127
60465
|
const text = workspace.documents.getText(uri);
|
|
@@ -59131,18 +60469,18 @@ function getHoverInfo(uri, position, workspace) {
|
|
|
59131
60469
|
const line = lines[position.line];
|
|
59132
60470
|
const macroResult = getMacroHover(line, position, workspace);
|
|
59133
60471
|
if (macroResult) return macroResult;
|
|
59134
|
-
const varResult = getVariableHover(line, position, workspace);
|
|
60472
|
+
const varResult = getVariableHover(uri, line, position, workspace);
|
|
59135
60473
|
if (varResult) return varResult;
|
|
59136
60474
|
const widgetResult = getWidgetHover(line, position, workspace);
|
|
59137
60475
|
if (widgetResult) return widgetResult;
|
|
59138
60476
|
return null;
|
|
59139
60477
|
}
|
|
59140
60478
|
function getMacroHover(line, position, workspace) {
|
|
59141
|
-
const macroRegex =
|
|
60479
|
+
const macroRegex = new RegExp(String.raw`\{(?:(\/)|${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)`, "g");
|
|
59142
60480
|
let match2;
|
|
59143
60481
|
while ((match2 = macroRegex.exec(line)) !== null) {
|
|
59144
60482
|
const name = match2[2];
|
|
59145
|
-
const nameStart = match2.index + match2[0].
|
|
60483
|
+
const nameStart = match2.index + match2[0].length - name.length;
|
|
59146
60484
|
const nameEnd = nameStart + name.length;
|
|
59147
60485
|
if (position.character >= nameStart && position.character <= nameEnd) {
|
|
59148
60486
|
const info = workspace.macros.getMacro(name);
|
|
@@ -59171,9 +60509,9 @@ function getMacroHover(line, position, workspace) {
|
|
|
59171
60509
|
}
|
|
59172
60510
|
return null;
|
|
59173
60511
|
}
|
|
59174
|
-
function getVariableHover(line, position, workspace) {
|
|
60512
|
+
function getVariableHover(uri, line, position, workspace) {
|
|
59175
60513
|
{
|
|
59176
|
-
const re = /\$([
|
|
60514
|
+
const re = /\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
59177
60515
|
let match2;
|
|
59178
60516
|
while ((match2 = re.exec(line)) !== null) {
|
|
59179
60517
|
const start = match2.index;
|
|
@@ -59229,13 +60567,14 @@ Fields: ${decl.fields.map((f) => `\`${f}\``).join(", ")}` : "";
|
|
|
59229
60567
|
}
|
|
59230
60568
|
}
|
|
59231
60569
|
{
|
|
59232
|
-
const re = /(?<!\w)%([
|
|
60570
|
+
const re = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
59233
60571
|
let match2;
|
|
59234
60572
|
while ((match2 = re.exec(line)) !== null) {
|
|
59235
60573
|
const start = match2.index;
|
|
59236
60574
|
const end = start + match2[0].length;
|
|
59237
60575
|
if (position.character >= start && position.character <= end) {
|
|
59238
60576
|
const baseName = match2[1].split(".")[0];
|
|
60577
|
+
if (!isTransientAt(baseName, uri, position.line, start, workspace)) break;
|
|
59239
60578
|
const decl = workspace.variables.getDeclaredTransient().get(baseName);
|
|
59240
60579
|
const typeInfo = decl?.fields && decl.fields.length > 0 ? `
|
|
59241
60580
|
|
|
@@ -59282,7 +60621,7 @@ Defined in: \`${widget.uri}\``,
|
|
|
59282
60621
|
}
|
|
59283
60622
|
};
|
|
59284
60623
|
}
|
|
59285
|
-
function
|
|
60624
|
+
function toLspRange2(r) {
|
|
59286
60625
|
return {
|
|
59287
60626
|
start: { line: r.start.line, character: r.start.character },
|
|
59288
60627
|
end: { line: r.end.line, character: r.end.character }
|
|
@@ -59292,6 +60631,8 @@ var hoverPlugin;
|
|
|
59292
60631
|
var init_hover = __esm({
|
|
59293
60632
|
"src/plugins/hover.ts"() {
|
|
59294
60633
|
"use strict";
|
|
60634
|
+
init_macro_parser();
|
|
60635
|
+
init_references();
|
|
59295
60636
|
hoverPlugin = {
|
|
59296
60637
|
id: "hover",
|
|
59297
60638
|
capabilities: {
|
|
@@ -59307,7 +60648,7 @@ var init_hover = __esm({
|
|
|
59307
60648
|
if (!result) return null;
|
|
59308
60649
|
return {
|
|
59309
60650
|
contents: { kind: "markdown", value: result.contents },
|
|
59310
|
-
range:
|
|
60651
|
+
range: toLspRange2(result.range)
|
|
59311
60652
|
};
|
|
59312
60653
|
});
|
|
59313
60654
|
}
|
|
@@ -59374,7 +60715,7 @@ var init_signature = __esm({
|
|
|
59374
60715
|
init_argument_lexer();
|
|
59375
60716
|
init_widget_arguments();
|
|
59376
60717
|
init_macro_parser();
|
|
59377
|
-
macroHeadRegex2 =
|
|
60718
|
+
macroHeadRegex2 = new RegExp(String.raw`(?<!\\)\{(?:${SELECTOR_PATTERN} )?([A-Za-z][\w-]*)\s+`, "g");
|
|
59378
60719
|
signaturePlugin = {
|
|
59379
60720
|
id: "signature",
|
|
59380
60721
|
capabilities: {
|
|
@@ -59449,7 +60790,7 @@ function getWidgetDefinition(line, position, workspace) {
|
|
|
59449
60790
|
}
|
|
59450
60791
|
return null;
|
|
59451
60792
|
}
|
|
59452
|
-
function
|
|
60793
|
+
function toLspRange3(r) {
|
|
59453
60794
|
return {
|
|
59454
60795
|
start: { line: r.start.line, character: r.start.character },
|
|
59455
60796
|
end: { line: r.end.line, character: r.end.character }
|
|
@@ -59475,7 +60816,7 @@ var init_definition = __esm({
|
|
|
59475
60816
|
if (!result) return null;
|
|
59476
60817
|
return {
|
|
59477
60818
|
uri: result.uri,
|
|
59478
|
-
range:
|
|
60819
|
+
range: toLspRange3(result.range)
|
|
59479
60820
|
};
|
|
59480
60821
|
});
|
|
59481
60822
|
}
|
|
@@ -59483,211 +60824,6 @@ var init_definition = __esm({
|
|
|
59483
60824
|
}
|
|
59484
60825
|
});
|
|
59485
60826
|
|
|
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
60827
|
// src/plugins/rename.ts
|
|
59692
60828
|
function prepareRename(uri, position, workspace) {
|
|
59693
60829
|
const symbol2 = resolveSymbolAtCursor(uri, position, workspace);
|
|
@@ -59705,8 +60841,11 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
59705
60841
|
}
|
|
59706
60842
|
switch (symbol2.kind) {
|
|
59707
60843
|
case "passage": {
|
|
59708
|
-
const
|
|
59709
|
-
|
|
60844
|
+
const declaration = workspace.passages.getPassage(symbol2.name);
|
|
60845
|
+
if (declaration) {
|
|
60846
|
+
addEdit(declaration.uri, declaration.nameRange, escapePassageName(newName));
|
|
60847
|
+
}
|
|
60848
|
+
for (const ref of findPassageReferences(symbol2.name, workspace, false)) {
|
|
59710
60849
|
addEdit(ref.uri, ref.range, newName);
|
|
59711
60850
|
}
|
|
59712
60851
|
break;
|
|
@@ -59737,33 +60876,24 @@ function computeRename(uri, position, newName, workspace) {
|
|
|
59737
60876
|
}
|
|
59738
60877
|
return edits;
|
|
59739
60878
|
}
|
|
60879
|
+
function escapePassageName(name) {
|
|
60880
|
+
return name.replace(/[[\]{}\\]/g, "\\$&");
|
|
60881
|
+
}
|
|
59740
60882
|
function resolveSymbolAtCursor(uri, position, workspace) {
|
|
59741
60883
|
const text = workspace.documents.getText(uri);
|
|
59742
60884
|
if (text === void 0) return null;
|
|
59743
60885
|
const lines = text.split("\n");
|
|
59744
60886
|
if (position.line >= lines.length) return null;
|
|
59745
60887
|
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
|
-
}
|
|
60888
|
+
const header = parsePassageHeader(line, position.line);
|
|
60889
|
+
if (header) {
|
|
60890
|
+
const { start, end } = header.nameRange;
|
|
60891
|
+
if (position.character >= start.character && position.character <= end.character) {
|
|
60892
|
+
return { kind: "passage", name: header.name, range: header.nameRange };
|
|
59763
60893
|
}
|
|
59764
60894
|
}
|
|
59765
60895
|
{
|
|
59766
|
-
const varRegex = /\$([
|
|
60896
|
+
const varRegex = /\$([\w$]+)/g;
|
|
59767
60897
|
let match2;
|
|
59768
60898
|
while ((match2 = varRegex.exec(line)) !== null) {
|
|
59769
60899
|
const start = match2.index;
|
|
@@ -59782,12 +60912,13 @@ function resolveSymbolAtCursor(uri, position, workspace) {
|
|
|
59782
60912
|
}
|
|
59783
60913
|
}
|
|
59784
60914
|
{
|
|
59785
|
-
const transRegex = /(?<!\w)%([
|
|
60915
|
+
const transRegex = /(?<!\w)%([\w$]+)/g;
|
|
59786
60916
|
let match2;
|
|
59787
60917
|
while ((match2 = transRegex.exec(line)) !== null) {
|
|
59788
60918
|
const start = match2.index;
|
|
59789
60919
|
const end = start + match2[0].length;
|
|
59790
60920
|
if (position.character >= start && position.character <= end) {
|
|
60921
|
+
if (!isTransientAt(match2[1], uri, position.line, start, workspace)) break;
|
|
59791
60922
|
return {
|
|
59792
60923
|
kind: "variable",
|
|
59793
60924
|
name: match2[1],
|
|
@@ -59846,6 +60977,7 @@ var init_rename = __esm({
|
|
|
59846
60977
|
"src/plugins/rename.ts"() {
|
|
59847
60978
|
"use strict";
|
|
59848
60979
|
init_link_parser();
|
|
60980
|
+
init_passage_parser();
|
|
59849
60981
|
init_references();
|
|
59850
60982
|
renamePlugin = {
|
|
59851
60983
|
id: "rename",
|
|
@@ -59898,9 +61030,9 @@ function computeCodeLenses(uri, workspace) {
|
|
|
59898
61030
|
const isStoryVarsFile = storyVarsPassage && storyVarsPassage.uri === uri;
|
|
59899
61031
|
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
59900
61032
|
const line = lines[lineNum];
|
|
59901
|
-
const
|
|
59902
|
-
if (
|
|
59903
|
-
const passageName =
|
|
61033
|
+
const header = parsePassageHeader(line, lineNum);
|
|
61034
|
+
if (header) {
|
|
61035
|
+
const passageName = header.name;
|
|
59904
61036
|
if (passageName === "StoryData") continue;
|
|
59905
61037
|
const refs = findPassageReferences(passageName, workspace, true);
|
|
59906
61038
|
const refCount = Math.max(0, refs.length - 1);
|
|
@@ -59931,7 +61063,7 @@ function computeCodeLenses(uri, workspace) {
|
|
|
59931
61063
|
});
|
|
59932
61064
|
}
|
|
59933
61065
|
if (isStoryVarsFile) {
|
|
59934
|
-
const varDeclMatch = line.match(/^\$(
|
|
61066
|
+
const varDeclMatch = line.match(/^\$(\w+)\s*=/);
|
|
59935
61067
|
if (varDeclMatch) {
|
|
59936
61068
|
const varName = varDeclMatch[1];
|
|
59937
61069
|
const refs = findVariableReferences(varName, workspace, true);
|
|
@@ -59960,6 +61092,7 @@ var codeLensPlugin;
|
|
|
59960
61092
|
var init_code_lens = __esm({
|
|
59961
61093
|
"src/plugins/code-lens.ts"() {
|
|
59962
61094
|
"use strict";
|
|
61095
|
+
init_passage_parser();
|
|
59963
61096
|
init_references();
|
|
59964
61097
|
codeLensPlugin = {
|
|
59965
61098
|
id: "code-lens",
|
|
@@ -60025,7 +61158,7 @@ function addVariableTypeHints(uri, text, range2, workspace, hints) {
|
|
|
60025
61158
|
const endLine = Math.min(range2.end.line, lines.length - 1);
|
|
60026
61159
|
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
|
|
60027
61160
|
const line = lines[lineNum];
|
|
60028
|
-
const declMatch = line.match(/^\$(
|
|
61161
|
+
const declMatch = line.match(/^\$(\w+)\s*=\s*(.+)$/);
|
|
60029
61162
|
if (!declMatch) continue;
|
|
60030
61163
|
const valueStr = declMatch[2].trim();
|
|
60031
61164
|
const inferredType = inferType(valueStr);
|
|
@@ -60047,7 +61180,7 @@ function addTransientTypeHints(uri, text, range2, workspace, hints) {
|
|
|
60047
61180
|
const endLine = Math.min(range2.end.line, lines.length - 1);
|
|
60048
61181
|
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
|
|
60049
61182
|
const line = lines[lineNum];
|
|
60050
|
-
const declMatch = line.match(/^%(
|
|
61183
|
+
const declMatch = line.match(/^%(\w+)\s*=\s*(.+)$/);
|
|
60051
61184
|
if (!declMatch) continue;
|
|
60052
61185
|
const valueStr = declMatch[2].trim();
|
|
60053
61186
|
const inferredType = inferType(valueStr);
|
|
@@ -60123,7 +61256,6 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60123
61256
|
for (const passage of passages) {
|
|
60124
61257
|
const headerLine = passage.headerEnd.start.line;
|
|
60125
61258
|
headerLines.add(headerLine);
|
|
60126
|
-
const rawLine = lines[headerLine] ?? "";
|
|
60127
61259
|
tokens.push({
|
|
60128
61260
|
line: headerLine,
|
|
60129
61261
|
startChar: 0,
|
|
@@ -60131,18 +61263,14 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60131
61263
|
tokenType: encodeType("namespace"),
|
|
60132
61264
|
tokenModifiers: 0
|
|
60133
61265
|
});
|
|
60134
|
-
const
|
|
60135
|
-
|
|
60136
|
-
|
|
60137
|
-
|
|
60138
|
-
|
|
60139
|
-
|
|
60140
|
-
|
|
60141
|
-
|
|
60142
|
-
tokenType: encodeType("namespace"),
|
|
60143
|
-
tokenModifiers: encodeModifiers(["declaration"])
|
|
60144
|
-
});
|
|
60145
|
-
}
|
|
61266
|
+
const { start, end } = passage.nameRange;
|
|
61267
|
+
tokens.push({
|
|
61268
|
+
line: headerLine,
|
|
61269
|
+
startChar: start.character,
|
|
61270
|
+
length: end.character - start.character,
|
|
61271
|
+
tokenType: encodeType("namespace"),
|
|
61272
|
+
tokenModifiers: encodeModifiers(["declaration"])
|
|
61273
|
+
});
|
|
60146
61274
|
}
|
|
60147
61275
|
const macros = parseMacros(text);
|
|
60148
61276
|
for (const macro of macros) {
|
|
@@ -60161,10 +61289,10 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60161
61289
|
tokenModifiers: encodeModifiers(isDefined ? ["defaultLibrary"] : [])
|
|
60162
61290
|
});
|
|
60163
61291
|
}
|
|
60164
|
-
const storyVarRegex = /(?<!\w)\$([
|
|
61292
|
+
const storyVarRegex = /(?<!\w)\$([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
60165
61293
|
const tempVarRegex = /(?<!\w)_([A-Za-z_$][\w$]*)/g;
|
|
60166
61294
|
const localVarRegex = /(?<!\w)@([A-Za-z_$][\w$]*)/g;
|
|
60167
|
-
const transientVarRegex = /(?<!\w)%([
|
|
61295
|
+
const transientVarRegex = /(?<!\w)%([\w$]+(?:\.[A-Za-z_$][\w$]*)*)/g;
|
|
60168
61296
|
const sugarKeywordRegex = /\b(to|is|isnot|eq|neq|gt|gte|lt|lte|and|or|not|def|ndef)\b/g;
|
|
60169
61297
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
60170
61298
|
if (headerLines.has(lineIndex)) continue;
|
|
@@ -60202,6 +61330,7 @@ function computeSemanticTokensAbsolute(uri, workspace) {
|
|
|
60202
61330
|
}
|
|
60203
61331
|
transientVarRegex.lastIndex = 0;
|
|
60204
61332
|
while ((m = transientVarRegex.exec(line)) !== null) {
|
|
61333
|
+
if (!isTransientAt(m[1].split(".")[0], uri, lineIndex, m.index, workspace)) continue;
|
|
60205
61334
|
tokens.push({
|
|
60206
61335
|
line: lineIndex,
|
|
60207
61336
|
startChar: m.index,
|
|
@@ -60246,6 +61375,7 @@ var init_semantic_tokens = __esm({
|
|
|
60246
61375
|
"src/plugins/semantic-tokens.ts"() {
|
|
60247
61376
|
"use strict";
|
|
60248
61377
|
init_macro_parser();
|
|
61378
|
+
init_references();
|
|
60249
61379
|
tokenTypesLegend = [
|
|
60250
61380
|
"function",
|
|
60251
61381
|
"variable",
|
|
@@ -60295,7 +61425,7 @@ var init_semantic_tokens = __esm({
|
|
|
60295
61425
|
});
|
|
60296
61426
|
|
|
60297
61427
|
// src/plugins/code-actions.ts
|
|
60298
|
-
import { readFileSync as
|
|
61428
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
60299
61429
|
import { basename, dirname as dirname3, join as join4 } from "node:path";
|
|
60300
61430
|
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
60301
61431
|
function computeCodeActions(uri, diagnostics, workspace, options = {}) {
|
|
@@ -60360,7 +61490,7 @@ function fixUndefinedMacro(uri, diag, workspace, options) {
|
|
|
60360
61490
|
let text = workspace.documents.getText(configUri);
|
|
60361
61491
|
if (text === void 0) {
|
|
60362
61492
|
try {
|
|
60363
|
-
text =
|
|
61493
|
+
text = readFileSync9(existingPath, "utf-8");
|
|
60364
61494
|
} catch {
|
|
60365
61495
|
return null;
|
|
60366
61496
|
}
|
|
@@ -60691,7 +61821,7 @@ function computeDocumentSymbols(uri, workspace) {
|
|
|
60691
61821
|
const contentStart = passage.range.start.line + 1;
|
|
60692
61822
|
const contentEnd = passage.range.end.line + 1;
|
|
60693
61823
|
for (let i = contentStart; i < contentEnd && i < lines.length; i++) {
|
|
60694
|
-
const varMatch = lines[i].match(/^\$(
|
|
61824
|
+
const varMatch = lines[i].match(/^\$(\w+)\s*=/);
|
|
60695
61825
|
if (varMatch) {
|
|
60696
61826
|
const charEnd = varMatch[0].length;
|
|
60697
61827
|
passageSymbol.children.push({
|
|
@@ -60990,7 +62120,7 @@ __export(server_exports2, {
|
|
|
60990
62120
|
startServer: () => startServer
|
|
60991
62121
|
});
|
|
60992
62122
|
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
60993
|
-
import { readFileSync as
|
|
62123
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
60994
62124
|
import { basename as basename2 } from "node:path";
|
|
60995
62125
|
function uriToFsPath(uri) {
|
|
60996
62126
|
try {
|
|
@@ -61028,9 +62158,27 @@ function startServer(_args) {
|
|
|
61028
62158
|
for (const warning of workspace.macros.warnings) console.error("[spindle-lsp]", warning);
|
|
61029
62159
|
activePlugins = loadPlugins(allPlugins, config2);
|
|
61030
62160
|
console.error("[spindle-lsp] plugins loaded:", activePlugins.map((p) => p.id).join(", "));
|
|
62161
|
+
const model = workspace;
|
|
62162
|
+
const pluginConnection = gateConnection(connection, () => model.isSpindleProject());
|
|
61031
62163
|
for (const plugin of activePlugins) {
|
|
61032
|
-
plugin.initialize({ connection, workspace, config: config2 });
|
|
61033
|
-
}
|
|
62164
|
+
plugin.initialize({ connection: pluginConnection, workspace, config: config2 });
|
|
62165
|
+
}
|
|
62166
|
+
const refresh = params.capabilities.workspace;
|
|
62167
|
+
let spindle = workspace.isSpindleProject();
|
|
62168
|
+
workspace.on("storyFormatChanged", (format) => {
|
|
62169
|
+
if (format.isSpindle === spindle) return;
|
|
62170
|
+
spindle = format.isSpindle;
|
|
62171
|
+
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`);
|
|
62172
|
+
const requests = [
|
|
62173
|
+
refresh?.semanticTokens?.refreshSupport && "workspace/semanticTokens/refresh",
|
|
62174
|
+
refresh?.inlayHint?.refreshSupport && "workspace/inlayHint/refresh",
|
|
62175
|
+
refresh?.codeLens?.refreshSupport && "workspace/codeLens/refresh"
|
|
62176
|
+
];
|
|
62177
|
+
for (const method of requests) {
|
|
62178
|
+
if (method) connection.sendRequest(method).catch(() => {
|
|
62179
|
+
});
|
|
62180
|
+
}
|
|
62181
|
+
});
|
|
61034
62182
|
if (Object.keys(projectConfig.macros).length > 0) {
|
|
61035
62183
|
workspace.macros.loadConfig(projectConfig.macros);
|
|
61036
62184
|
console.error("[spindle-lsp] loaded config macros:", Object.keys(projectConfig.macros).length);
|
|
@@ -61097,7 +62245,7 @@ function startServer(_args) {
|
|
|
61097
62245
|
if (workspace) {
|
|
61098
62246
|
try {
|
|
61099
62247
|
const fsPath = uriToFsPath(textDocument.uri);
|
|
61100
|
-
const text =
|
|
62248
|
+
const text = readFileSync10(fsPath, "utf-8");
|
|
61101
62249
|
workspace.documents.update(textDocument.uri, text);
|
|
61102
62250
|
} catch {
|
|
61103
62251
|
workspace.documents.close(textDocument.uri);
|
|
@@ -61133,7 +62281,7 @@ function startServer(_args) {
|
|
|
61133
62281
|
} else {
|
|
61134
62282
|
try {
|
|
61135
62283
|
const fsPath = uriToFsPath(change.uri);
|
|
61136
|
-
const text =
|
|
62284
|
+
const text = readFileSync10(fsPath, "utf-8");
|
|
61137
62285
|
if (workspace.documents.has(change.uri)) {
|
|
61138
62286
|
workspace.documents.update(change.uri, text);
|
|
61139
62287
|
} else {
|
|
@@ -61169,7 +62317,7 @@ async function scanWorkspaceFiles(root) {
|
|
|
61169
62317
|
];
|
|
61170
62318
|
for (const filePath of files) {
|
|
61171
62319
|
try {
|
|
61172
|
-
const text =
|
|
62320
|
+
const text = readFileSync10(filePath, "utf-8");
|
|
61173
62321
|
const uri = fsPathToUri(filePath);
|
|
61174
62322
|
contents.set(uri, text);
|
|
61175
62323
|
} catch {
|
|
@@ -61189,6 +62337,7 @@ var init_server4 = __esm({
|
|
|
61189
62337
|
init_workspace_model();
|
|
61190
62338
|
init_plugin_loader();
|
|
61191
62339
|
init_capabilities();
|
|
62340
|
+
init_story_format_gate();
|
|
61192
62341
|
init_plugins();
|
|
61193
62342
|
init_config_loader();
|
|
61194
62343
|
init_macro_sources();
|
|
@@ -61197,7 +62346,7 @@ var init_server4 = __esm({
|
|
|
61197
62346
|
|
|
61198
62347
|
// src/bin.ts
|
|
61199
62348
|
var args = process.argv.slice(2);
|
|
61200
|
-
var VERSION = "0.
|
|
62349
|
+
var VERSION = "0.10.0";
|
|
61201
62350
|
var HELP = `spindle-lsp v${VERSION}
|
|
61202
62351
|
Language Server Protocol server for Spindle story format
|
|
61203
62352
|
|