@rohal12/spindle-lsp 0.13.2 → 0.14.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.
Files changed (3) hide show
  1. package/dist/bin.js +1551 -767
  2. package/dist/bin.js.map +4 -4
  3. package/package.json +3 -3
package/dist/bin.js CHANGED
@@ -6174,7 +6174,7 @@ function isRawMacro(name) {
6174
6174
  return RAW_BODY_MACROS.has(name);
6175
6175
  }
6176
6176
  function isBlockMacro(name) {
6177
- return BLOCK_MACROS.has(name);
6177
+ return BLOCK_MACROS.has(name) || BLOCK_WIDGETS.has(name);
6178
6178
  }
6179
6179
  function buildAst(nodes) {
6180
6180
  return nodes.map(astNode);
@@ -6445,7 +6445,7 @@ function getLineInfo(input, offset2) {
6445
6445
  }
6446
6446
  function getOptions(opts) {
6447
6447
  var options = {};
6448
- for (var opt in defaultOptions) options[opt] = opts && hasOwn(opts, opt) ? opts[opt] : defaultOptions[opt];
6448
+ for (var opt in defaultOptions) options[opt] = opts && hasOwn$1(opts, opt) ? opts[opt] : defaultOptions[opt];
6449
6449
  if (options.ecmaVersion === "latest") options.ecmaVersion = 1e8;
6450
6450
  else if (options.ecmaVersion == null) {
6451
6451
  if (!warnedAboutEcmaVersion && typeof console === "object" && console.warn) {
@@ -6617,7 +6617,7 @@ function transientAssignment(p, end) {
6617
6617
  if (t === types$1.eof) return false;
6618
6618
  if (depth > 0) {
6619
6619
  if (OPENERS.has(t)) depth++;
6620
- else if (CLOSERS.has(t)) depth--;
6620
+ else if (CLOSERS$1.has(t)) depth--;
6621
6621
  } else if (t === types$1.bracketL) depth = 1;
6622
6622
  else if (t === types$1.dot) {
6623
6623
  q2.nextToken();
@@ -6628,6 +6628,9 @@ function transientAssignment(p, end) {
6628
6628
  return false;
6629
6629
  }
6630
6630
  }
6631
+ function isMemberDot(type) {
6632
+ return type === types$1.dot || type === types$1.questionDot;
6633
+ }
6631
6634
  function tokenizerAt(src, start, goal, { onComment, afterOperand = false } = {}) {
6632
6635
  const p = new SigilParser(onComment ? {
6633
6636
  ...OPTIONS,
@@ -6699,6 +6702,9 @@ function bracketsBefore(src, pos) {
6699
6702
  };
6700
6703
  }
6701
6704
  function parseCode(src, goal = "expression") {
6705
+ return parseWithAst(src, goal).out;
6706
+ }
6707
+ function parseWithAst(src, goal) {
6702
6708
  const percentAt = /* @__PURE__ */ new Map();
6703
6709
  for (; ; ) {
6704
6710
  const transientsRead = /* @__PURE__ */ new Set();
@@ -6710,7 +6716,10 @@ function parseCode(src, goal = "expression") {
6710
6716
  };
6711
6717
  walk(ast, src, out, false, false);
6712
6718
  out.refs.sort((a, b) => a.start - b.start);
6713
- return out;
6719
+ return {
6720
+ out,
6721
+ ast
6722
+ };
6714
6723
  } catch (error48) {
6715
6724
  const pos = error48.pos ?? -1;
6716
6725
  if (!(error48 instanceof SyntaxError) || percentAt.has(pos)) throw error48;
@@ -6846,8 +6855,8 @@ function walk(node, src, out, binding, shorthand) {
6846
6855
  }
6847
6856
  }
6848
6857
  }
6849
- function findCodeEnd(src, start, { goal = "expression", stop } = {}) {
6850
- const end = scanCodeEnd(src, start, goal, stop);
6858
+ function findCodeEnd(src, start, { goal = "expression", stop, onRunOut } = {}) {
6859
+ const end = scanCodeEnd(src, start, goal, stop, onRunOut);
6851
6860
  return end < 0 && !stop ? parsedCodeEnd(src, start, goal) : end;
6852
6861
  }
6853
6862
  function parsedCodeEnd(src, start, goal) {
@@ -6865,7 +6874,11 @@ function parsedCodeEnd(src, start, goal) {
6865
6874
  }
6866
6875
  return -1;
6867
6876
  }
6868
- function scanCodeEnd(src, start, goal, stop) {
6877
+ function scanCodeEnd(src, start, goal, stop, onRunOut) {
6878
+ const ranOut = () => {
6879
+ onRunOut?.();
6880
+ return -1;
6881
+ };
6869
6882
  const stack = [];
6870
6883
  let prevEnd = -1;
6871
6884
  let prevWord = false;
@@ -6881,11 +6894,12 @@ function scanCodeEnd(src, start, goal, stop) {
6881
6894
  if (!/^Unterminated/.test(message) && !inTemplate) {
6882
6895
  resumeAt = Math.max(tok.pos, from) + 1;
6883
6896
  afterOperand = false;
6884
- if (resumeAt >= src.length) return -1;
6897
+ if (resumeAt >= src.length) return ranOut();
6885
6898
  break;
6886
6899
  }
6887
6900
  const k2 = stack.lastIndexOf("${");
6888
- if (!/^Unterminated template/.test(message) || k2 < 0 || templateSlip || stop) return -1;
6901
+ if (!/^Unterminated template/.test(message) || k2 < 0 || templateSlip || stop) return /^Unterminated/.test(message) && !/^Unterminated regular/.test(message) && !(/^Unterminated string/.test(message) && prevWord && tok.pos === prevEnd) ? ranOut() : -1;
6902
+ onRunOut?.();
6889
6903
  templateSlip = true;
6890
6904
  stack.length = k2;
6891
6905
  resumeAt = tok.pos;
@@ -6894,7 +6908,7 @@ function scanCodeEnd(src, start, goal, stop) {
6894
6908
  }
6895
6909
  if (tok.restarted && stack.includes("${")) return -1;
6896
6910
  const t = tok.type;
6897
- if (t === types$1.eof) return -1;
6911
+ if (t === types$1.eof) return ranOut();
6898
6912
  if (t === types$1.string && prevWord && tok.start === prevEnd) return -1;
6899
6913
  prevWord = t === types$1.name && !WORDS_BEFORE_STRING.has(tok.value) || t === types$1.num;
6900
6914
  prevEnd = tok.end;
@@ -7035,7 +7049,7 @@ function walkTokens(src, from, handlers, nesting, goal, oneTemplate) {
7035
7049
  if (t === types$1.name) {
7036
7050
  const sigil = sigilAt(src, tok.start, tok.value);
7037
7051
  const property = prev === types$1.dot || prev === types$1.questionDot || (sigil === "$" || sigil === "_") && keyPosition(src, tok.end, prev);
7038
- if (sigil && !property) handlers.variable?.(sigil, src.slice(tok.start + 1, tok.end), tok.start, nesting);
7052
+ if (sigil && !property) handlers.variable?.(sigil, tok.value.slice(1), tok.start, nesting, tok.end);
7039
7053
  else code(tok.start, tok.end);
7040
7054
  } else if (t === types$1.string || t === types$1.regexp || t === types$1.template || t === types$1.invalidTemplate) literal3(tok.start, tok.end);
7041
7055
  else if (t === types$1.backQuote) {
@@ -7122,48 +7136,107 @@ function scanStringLiteral(src, start) {
7122
7136
  function skipLiteral(input, i2) {
7123
7137
  const quote = input[i2];
7124
7138
  const template = quote === "`";
7139
+ let ranOut = false;
7125
7140
  for (let j2 = i2 + 1; j2 < input.length; ) {
7126
7141
  const c = input[j2];
7127
7142
  if (c === "\\") j2 += 2;
7128
- else if (c === quote) return j2 + 1;
7129
- else if (!template && c === "\n") return -1;
7143
+ else if (c === quote) return {
7144
+ end: j2 + 1,
7145
+ ranOut
7146
+ };
7147
+ else if (!template && c === "\n") return {
7148
+ end: -1,
7149
+ ranOut
7150
+ };
7130
7151
  else if (template && c === "$" && input[j2 + 1] === "{") {
7131
- const close = lenientClose(input, j2 + 2);
7132
- if (close === -1) return -1;
7133
- j2 = close + 1;
7152
+ const close = lenientRead(input, j2 + 2);
7153
+ ranOut || (ranOut = close.ranOut);
7154
+ if (close.end === -1) return {
7155
+ end: -1,
7156
+ ranOut
7157
+ };
7158
+ j2 = close.end + 1;
7134
7159
  } else j2++;
7135
7160
  }
7136
- return -1;
7161
+ return {
7162
+ end: -1,
7163
+ ranOut: ranOut || template
7164
+ };
7165
+ }
7166
+ function lenientClose(input, i2, onRunOut) {
7167
+ const { end, ranOut } = lenientRead(input, i2);
7168
+ if (ranOut) onRunOut?.();
7169
+ return end;
7137
7170
  }
7138
- function lenientClose(input, i2) {
7171
+ function lenientRead(input, i2) {
7139
7172
  if (memoInput !== input) {
7140
7173
  memoInput = input;
7141
7174
  memo.clear();
7142
7175
  }
7143
- let end = memo.get(i2);
7144
- if (end === void 0) {
7145
- end = scanLenient(input, i2);
7146
- memo.set(i2, end);
7176
+ let read = memo.get(i2);
7177
+ if (read === void 0) {
7178
+ read = scanLenient(input, i2);
7179
+ memo.set(i2, read);
7147
7180
  }
7148
- return end;
7181
+ return read;
7149
7182
  }
7150
7183
  function scanLenient(input, i2) {
7151
7184
  let depth = 0;
7185
+ let ranOut = false;
7152
7186
  while (i2 < input.length) {
7153
7187
  const c = input[i2];
7154
7188
  if (c === "{") {
7155
7189
  depth++;
7156
7190
  i2++;
7157
7191
  } else if (c === "}") {
7158
- if (depth === 0) return i2;
7192
+ if (depth === 0) return {
7193
+ end: i2,
7194
+ ranOut
7195
+ };
7159
7196
  depth--;
7160
7197
  i2++;
7161
7198
  } else if (c === "`" || (c === '"' || c === "'") && !(i2 > 0 && NON_STRING_QUOTE_PREFIX.test(input[i2 - 1]))) {
7162
7199
  const close = skipLiteral(input, i2);
7163
- i2 = close === -1 ? i2 + 1 : close;
7200
+ ranOut || (ranOut = close.ranOut);
7201
+ i2 = close.end === -1 ? i2 + 1 : close.end;
7164
7202
  } else i2++;
7165
7203
  }
7166
- return -1;
7204
+ return {
7205
+ end: -1,
7206
+ ranOut: true
7207
+ };
7208
+ }
7209
+ function jsCodeEnd(onOpenEnd) {
7210
+ return {
7211
+ closeBrace(input, codeStart, lenientStart) {
7212
+ let ranOut = false;
7213
+ const onRunOut = () => ranOut = true;
7214
+ const strict = findCodeEnd(input, codeStart, { onRunOut });
7215
+ const end = strict !== -1 ? strict : lenientClose(input, lenientStart, onRunOut);
7216
+ if (ranOut) onOpenEnd?.(input, codeStart);
7217
+ return end;
7218
+ },
7219
+ rawBodyEnd(input, bodyStart, name) {
7220
+ const closer = `\\{/${name}\\s*\\}`;
7221
+ const first = new RegExp(closer, "gi");
7222
+ first.lastIndex = bodyStart;
7223
+ const firstAt = first.exec(input)?.index ?? -1;
7224
+ if (firstAt === -1) return -1;
7225
+ const at3 = new RegExp(closer, "iy");
7226
+ let ranOut = false;
7227
+ const end = findCodeEnd(input, bodyStart, {
7228
+ goal: "statements",
7229
+ stop: (k2) => {
7230
+ at3.lastIndex = k2;
7231
+ return at3.test(input);
7232
+ },
7233
+ onRunOut: () => ranOut = true
7234
+ });
7235
+ if (end !== -1) return end;
7236
+ if (onOpenEnd && (ranOut || new RegExp(`\\{/${name}\\s*$`, "i").test(input))) onOpenEnd(input, bodyStart);
7237
+ return firstAt;
7238
+ }
7239
+ };
7167
7240
  }
7168
7241
  function isCodeAttribute(name) {
7169
7242
  const lower = name.toLowerCase();
@@ -7195,7 +7268,7 @@ function splitSigilTemplate(template) {
7195
7268
  if (text2) parts.push({ text: text2 });
7196
7269
  return parts;
7197
7270
  }
7198
- function run(source, startRule, options) {
7271
+ function run$1(source, startRule, options) {
7199
7272
  const hooks = options.hooks ? {
7200
7273
  ...defaultHooks,
7201
7274
  ...options.hooks
@@ -7217,7 +7290,7 @@ function run(source, startRule, options) {
7217
7290
  }
7218
7291
  }
7219
7292
  function analyzeMarkup(source, options = {}) {
7220
- const { tokens: tokens2, errors, damage } = scan(source, options);
7293
+ const { tokens: tokens2, errors, damage } = scanMarkupTolerant(source, options);
7221
7294
  const hooks = {
7222
7295
  ...defaultHooks,
7223
7296
  ...options.hooks
@@ -7252,23 +7325,23 @@ function pairingError(source, error48) {
7252
7325
  return new MarkupError(error48.message, error48.start, line, column, error48.code, error48.end, error48.data);
7253
7326
  }
7254
7327
  function tokenizeMarkup(source, options = {}) {
7255
- return run(source, "Tokens", options);
7328
+ return run$1(source, "Tokens", options);
7256
7329
  }
7257
7330
  function parseSelectors(source, at3 = 0) {
7258
- const found = run(source.slice(at3), "SelectorsPrefix", {});
7331
+ const found = run$1(source.slice(at3), "SelectorsPrefix", {});
7259
7332
  return {
7260
7333
  ...found,
7261
7334
  end: at3 + found.end
7262
7335
  };
7263
7336
  }
7264
7337
  function tokenizeMarkupTolerant(source, options = {}) {
7265
- const { tokens: tokens2, errors } = scan(source, options);
7338
+ const { tokens: tokens2, errors } = scanMarkupTolerant(source, options);
7266
7339
  return {
7267
7340
  tokens: tokens2,
7268
7341
  errors
7269
7342
  };
7270
7343
  }
7271
- function scan(source, options) {
7344
+ function scanMarkupTolerant(source, options = {}) {
7272
7345
  const tokens2 = [];
7273
7346
  const errors = [];
7274
7347
  const damage = [];
@@ -7416,15 +7489,15 @@ function topLevelIndices(src, isSeparator) {
7416
7489
  } });
7417
7490
  return indices;
7418
7491
  }
7419
- function splitTopLevel(src, isSeparator) {
7420
- const segments = [];
7492
+ function topLevelSpans(src, isSeparator) {
7493
+ const spans = [];
7421
7494
  let from = 0;
7422
7495
  for (const i2 of topLevelIndices(src, isSeparator)) {
7423
- segments.push(src.slice(from, i2));
7496
+ spans.push([from, i2]);
7424
7497
  from = i2 + 1;
7425
7498
  }
7426
- segments.push(src.slice(from));
7427
- return segments;
7499
+ spans.push([from, src.length]);
7500
+ return spans;
7428
7501
  }
7429
7502
  function isStandaloneValue(token) {
7430
7503
  return startsLikeValue(token) && !endsWithOperator(token);
@@ -7441,21 +7514,31 @@ function startsLikeValue(token) {
7441
7514
  return false;
7442
7515
  }
7443
7516
  function trySplitOnWhitespace(raw) {
7444
- const args2 = splitTopLevel(raw, isWhitespace).filter(Boolean);
7517
+ const args2 = topLevelSpans(raw, isWhitespace).filter(([a, b]) => b > a);
7445
7518
  if (args2.length < 2) return null;
7446
- for (const arg of args2) if (!isStandaloneValue(arg)) return null;
7519
+ for (const [a, b] of args2) if (!isStandaloneValue(raw.slice(a, b))) return null;
7447
7520
  return args2;
7448
7521
  }
7449
- function splitArgs(raw) {
7450
- const args2 = splitTopLevel(raw, (ch) => ch === ",").map((a) => a.trim());
7522
+ function trimmed$1(src, [start, end]) {
7523
+ const text2 = src.slice(start, end);
7524
+ const lead = text2.length - text2.trimStart().length;
7525
+ return [start + lead, Math.max(start + lead, end - (text2.length - text2.trimEnd().length))];
7526
+ }
7527
+ function splitArgSpans(raw) {
7528
+ const args2 = topLevelSpans(raw, (ch) => ch === ",").map((span) => trimmed$1(raw, span));
7451
7529
  const hasComma = args2.length > 1;
7452
- if (args2[args2.length - 1] === "") args2.pop();
7530
+ const last2 = args2[args2.length - 1];
7531
+ if (last2[0] === last2[1]) args2.pop();
7453
7532
  if (!hasComma && args2.length === 1) {
7454
- const split = trySplitOnWhitespace(args2[0]);
7455
- if (split) return split;
7533
+ const [start, end] = args2[0];
7534
+ const split = trySplitOnWhitespace(raw.slice(start, end));
7535
+ if (split) return split.map(([a, b]) => [start + a, start + b]);
7456
7536
  }
7457
7537
  return args2;
7458
7538
  }
7539
+ function splitArgs(raw) {
7540
+ return splitArgSpans(raw).map(([a, b]) => raw.slice(a, b));
7541
+ }
7459
7542
  function terms(src) {
7460
7543
  const spans = [];
7461
7544
  let start = 0;
@@ -7529,8 +7612,9 @@ function readOptions(params, src, { at: at3, spans }) {
7529
7612
  }
7530
7613
  return options;
7531
7614
  }
7532
- function takeFlag(src, word) {
7615
+ function takeFlag(src, word, alone) {
7533
7616
  const spans = terms(src);
7617
+ if (spans.length === 1) return alone && slice(src, spans[0]) === word ? [src.length, src.length] : null;
7534
7618
  if (spans.length < 2) return null;
7535
7619
  if (slice(src, spans[spans.length - 1]) === word) {
7536
7620
  const rest = [0, spans[spans.length - 2][1]];
@@ -7565,8 +7649,9 @@ function trimmed(src, at3) {
7565
7649
  }
7566
7650
  function readGroup(params, src, args2, { at: srcAt, spans }) {
7567
7651
  let [text2, at3] = trimmed(src ?? "", srcAt);
7652
+ const alone = !params.some((p) => p.type !== "flag" && p.required);
7568
7653
  for (const flag of params.filter((p) => p.type === "flag")) {
7569
- const rest2 = src === null ? null : takeFlag(text2, flag.name);
7654
+ const rest2 = src === null ? null : takeFlag(text2, flag.name, alone);
7570
7655
  args2[flag.name] = rest2 !== null;
7571
7656
  if (rest2) [text2, at3] = trimmed(slice(text2, rest2), at3 + rest2[0]);
7572
7657
  }
@@ -7637,9 +7722,40 @@ function passageTarget(arg) {
7637
7722
  name
7638
7723
  };
7639
7724
  }
7725
+ function checkParameterTypes(macro, parameters) {
7726
+ const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
7727
+ for (const param of parameters) {
7728
+ const type = param.type;
7729
+ if (!PARAMETER_TYPES.includes(type)) throw parameterError(macro, param, type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`, `Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
7730
+ const holds = param.holds;
7731
+ if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
7732
+ if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
7733
+ if (param.parameters) checkParameterTypes(macro, param.parameters);
7734
+ }
7735
+ }
7736
+ function parameterError(macro, param, problem, fix) {
7737
+ return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
7738
+ }
7739
+ function getMacroRegistry() {
7740
+ return Array.from(metadataRegistry.values());
7741
+ }
7640
7742
  function subMacroParameters(name) {
7641
7743
  return SUB_MACRO_PARAMETERS.get(name.toLowerCase());
7642
7744
  }
7745
+ function variableNameError(name, label2 = name) {
7746
+ return name === "__proto__" ? `"${label2}" cannot be used as a variable name (${RESERVED_NAME} is reserved)` : void 0;
7747
+ }
7748
+ function ownEntries(sources, keep = () => true) {
7749
+ const record2 = /* @__PURE__ */ Object.create(null);
7750
+ for (const source of sources) {
7751
+ if (!source) continue;
7752
+ for (const key of Object.keys(source)) {
7753
+ const value = source[key];
7754
+ if (keep(value)) record2[key] = value;
7755
+ }
7756
+ }
7757
+ return record2;
7758
+ }
7643
7759
  function withParseCache(fn) {
7644
7760
  const outer = parses;
7645
7761
  parses = /* @__PURE__ */ new Map();
@@ -7668,6 +7784,13 @@ function pieceOffset(piece, index) {
7668
7784
  function extentOf(piece) {
7669
7785
  return piece.kind === "code" ? piece.code.length : piece.kind === "text" ? piece.text.length : piece.length;
7670
7786
  }
7787
+ function widgetParameters(params, holds = {}) {
7788
+ if (!params.some((param) => hasOwn(holds, param))) return void 0;
7789
+ return params.map((name) => ({
7790
+ name,
7791
+ ...hasOwn(holds, name) ? { holds: holds[name] } : {}
7792
+ }));
7793
+ }
7671
7794
  function withHolds(params, holds) {
7672
7795
  return params.map((param) => {
7673
7796
  if (param.parameters) return {
@@ -7682,8 +7805,13 @@ function withHolds(params, holds) {
7682
7805
  }
7683
7806
  function parameterLookup(macros) {
7684
7807
  const parameters = new NameMap();
7685
- for (const { name, parameters: params, interpolate } of macros) if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
7686
- return (name) => {
7808
+ const widgets = new NameMap();
7809
+ for (const { name, parameters: params, interpolate, widget } of macros) {
7810
+ if (params) parameters.set(name, withHolds(params, holdsByDefault(interpolate)));
7811
+ const declared = widget && widgetParameters(widget.params, widget.holds);
7812
+ if (declared) widgets.set(name, declared);
7813
+ }
7814
+ const lookup = (name) => {
7687
7815
  let params = parameters.get(name);
7688
7816
  if (!params) {
7689
7817
  const sub = subMacroParameters(name);
@@ -7691,6 +7819,8 @@ function parameterLookup(macros) {
7691
7819
  }
7692
7820
  return params;
7693
7821
  };
7822
+ if (widgets.size > 0) lookup.widget = (name) => widgets.get(name);
7823
+ return lookup;
7694
7824
  }
7695
7825
  function codeGoal(param) {
7696
7826
  if (param.type === "expression" || param.type === "passage") return "expression";
@@ -7781,6 +7911,8 @@ function* codeAndText(src, tokens2, parametersOf2) {
7781
7911
  }
7782
7912
  const params = parametersOf2(name);
7783
7913
  if (params) yield* argPieces(args2, argsAt, params, token.name, label2);
7914
+ const widget = parametersOf2.widget?.(name);
7915
+ if (widget) yield* widgetPieces(args2, argsAt, widget, token.name, label2);
7784
7916
  }
7785
7917
  }
7786
7918
  }
@@ -7836,23 +7968,42 @@ function nestedPiece(piece, toRoot, where2) {
7836
7968
  ...nested
7837
7969
  };
7838
7970
  }
7839
- function escapeOffsets(raw, at3, value) {
7971
+ function escapeAt(raw, i2, escapes) {
7972
+ if (escapes === "quotes") {
7973
+ const next = raw.charAt(i2 + 1);
7974
+ return /["'\\]/.test(next) ? {
7975
+ length: 2,
7976
+ text: next
7977
+ } : void 0;
7978
+ }
7979
+ JS_ESCAPE_RE.lastIndex = i2;
7980
+ const written2 = JS_ESCAPE_RE.exec(raw)?.[0];
7981
+ const text2 = written2 && stringLiteralValue(`"${written2}"`);
7982
+ return typeof text2 === "string" ? {
7983
+ length: written2.length,
7984
+ text: text2
7985
+ } : void 0;
7986
+ }
7987
+ function escapeOffsets(raw, at3, value, escapes) {
7840
7988
  const offsets = [];
7841
7989
  let escaped = false;
7842
7990
  let i2 = 0;
7843
7991
  while (offsets.length < value.length) {
7844
7992
  if (i2 >= raw.length) return void 0;
7845
- offsets.push(at3 + i2);
7846
- if (raw[i2] === "\\" && /["'\\]/.test(raw.charAt(i2 + 1))) {
7847
- escaped = true;
7848
- i2 += 2;
7849
- } else i2++;
7993
+ const escape2 = raw[i2] === "\\" ? escapeAt(raw, i2, escapes) : void 0;
7994
+ if (!escape2) {
7995
+ offsets.push(at3 + i2++);
7996
+ continue;
7997
+ }
7998
+ if (!value.startsWith(escape2.text, offsets.length)) return void 0;
7999
+ for (let k2 = 0; k2 < escape2.text.length; k2++) offsets.push(at3 + i2);
8000
+ escaped = true;
8001
+ i2 += escape2.length;
7850
8002
  }
7851
8003
  offsets.push(at3 + i2);
7852
8004
  return escaped ? offsets : void 0;
7853
8005
  }
7854
8006
  function* argPieces(args2, offset2, params, macro, label2) {
7855
- const name = macro.toLowerCase();
7856
8007
  let values;
7857
8008
  const spans = /* @__PURE__ */ new Map();
7858
8009
  try {
@@ -7865,10 +8016,34 @@ function* argPieces(args2, offset2, params, macro, label2) {
7865
8016
  offset: offset2,
7866
8017
  length: args2.length,
7867
8018
  label: label2,
7868
- macro: name
8019
+ macro: macro.toLowerCase()
7869
8020
  };
7870
8021
  return;
7871
8022
  }
8023
+ yield* valuePieces(args2, offset2, params, values, spans, macro, label2, "quotes");
8024
+ }
8025
+ function* widgetPieces(args2, offset2, params, macro, label2) {
8026
+ const written2 = splitArgSpans(args2);
8027
+ const declared = [];
8028
+ const values = {};
8029
+ const spans = /* @__PURE__ */ new Map();
8030
+ params.forEach(({ name, holds }, i2) => {
8031
+ const span = written2[i2];
8032
+ const value = span && stringLiteralValue(args2.slice(...span));
8033
+ if (!holds || value == null) return;
8034
+ const param = {
8035
+ name,
8036
+ type: "string",
8037
+ holds
8038
+ };
8039
+ declared.push(param);
8040
+ values[name] = value;
8041
+ spans.set(param, span);
8042
+ });
8043
+ yield* valuePieces(args2, offset2, declared, values, spans, macro, label2, "javascript");
8044
+ }
8045
+ function* valuePieces(args2, offset2, params, values, spans, macro, label2, escapes) {
8046
+ const name = macro.toLowerCase();
7872
8047
  function* visit(list3, from) {
7873
8048
  for (const param of list3) {
7874
8049
  const value = from[param.name];
@@ -7913,7 +8088,7 @@ function* argPieces(args2, offset2, params, macro, label2) {
7913
8088
  }
7914
8089
  const at3 = offset2 + start + (value !== written2 && /^["']/.test(written2) ? 1 : 0);
7915
8090
  const holds = stringHolds(param);
7916
- const sourceOffsets = escapeOffsets(args2.slice(at3 - offset2), at3, value);
8091
+ const sourceOffsets = escapeOffsets(args2.slice(at3 - offset2), at3, value, escapes);
7917
8092
  if (holds === "expression" || holds === "statements") {
7918
8093
  const piece = {
7919
8094
  kind: "code",
@@ -7966,15 +8141,13 @@ function attributeValues(src, token) {
7966
8141
  function sigilExpressions(value) {
7967
8142
  const found = [];
7968
8143
  let from = 0;
7969
- for (const part of splitSigilTemplate(value)) {
7970
- if (!("expr" in part)) continue;
7971
- const at3 = locate(value, part.expr, from);
7972
- from = at3 + part.expr.length;
8144
+ for (const part of splitSigilTemplate(value)) if ("expr" in part) {
7973
8145
  found.push({
7974
8146
  expr: part.expr,
7975
- at: at3
8147
+ at: from + 1
7976
8148
  });
7977
- }
8149
+ from += part.expr.length + 2;
8150
+ } else from += ("text" in part ? part.text : part.verbatim).length;
7978
8151
  return found;
7979
8152
  }
7980
8153
  function locate(text2, part, from) {
@@ -7983,23 +8156,6 @@ function locate(text2, part, from) {
7983
8156
  const anywhere = text2.indexOf(part);
7984
8157
  return anywhere >= 0 ? anywhere : from;
7985
8158
  }
7986
- function checkParameterTypes(macro, parameters) {
7987
- const holdsFix = `Give a \`string\` or \`text\` parameter one of ${STRING_HOLDS.join(", ")} to hold (see docs/custom-macros.md#what-a-string-holds).`;
7988
- for (const param of parameters) {
7989
- const type = param.type;
7990
- if (!PARAMETER_TYPES.includes(type)) throw parameterError(macro, param, type === void 0 ? "has no type" : `has the unknown type ${JSON.stringify(type)}`, `Give it one of the types ${PARAMETER_TYPES.join(", ")} (see docs/custom-macros.md#parameter-types), or declare no parameters and read props.rawArgs.`);
7991
- const holds = param.holds;
7992
- if (holds !== void 0 && type !== "string" && type !== "text") throw parameterError(macro, param, `is of the type ${type}, which holds what it is`, holdsFix);
7993
- if (holds !== void 0 && !STRING_HOLDS.includes(holds)) throw parameterError(macro, param, `holds the unknown ${JSON.stringify(holds)}`, holdsFix);
7994
- if (param.parameters) checkParameterTypes(macro, param.parameters);
7995
- }
7996
- }
7997
- function parameterError(macro, param, problem, fix) {
7998
- return /* @__PURE__ */ new Error(`spindle: The parameter "${param.name}" of the macro {${macro}} ${problem}. ${fix}`);
7999
- }
8000
- function getMacroRegistry() {
8001
- return Array.from(metadataRegistry.values());
8002
- }
8003
8159
  function textContainsChildren(text2, parametersOf2) {
8004
8160
  if (!text2.includes("{")) return false;
8005
8161
  let nodes;
@@ -8030,6 +8186,7 @@ function astContainsChildren(nodes, parametersOf2 = registeredParameters) {
8030
8186
  }, parametersOf2) || astContainsChildren(node.children, parametersOf2)) return true;
8031
8187
  }
8032
8188
  if (node.type === "macro") {
8189
+ if (node.name.toLowerCase() === "widget") continue;
8033
8190
  if (tokenTextContainsChildren({
8034
8191
  isClose: false,
8035
8192
  nameStart: 0,
@@ -8048,9 +8205,25 @@ function astContainsChildren(nodes, parametersOf2 = registeredParameters) {
8048
8205
  return false;
8049
8206
  }
8050
8207
  function widgetDef({ name = "", parameters = "" }) {
8208
+ const params = [];
8209
+ const holds = {};
8210
+ const badHolds = [];
8211
+ for (const word of parameters.split(/[\s,]+/)) {
8212
+ if (!word.startsWith("@")) continue;
8213
+ const [param = word, written2] = word.split(/:(.*)/s);
8214
+ params.push(param);
8215
+ if (written2 === void 0) continue;
8216
+ if (STRING_HOLDS.includes(written2)) holds[param] = written2;
8217
+ else badHolds.push({
8218
+ param,
8219
+ written: written2
8220
+ });
8221
+ }
8051
8222
  return {
8052
8223
  name,
8053
- params: parameters.split(/[\s,]+/).filter((word) => word.startsWith("@"))
8224
+ params,
8225
+ ...Object.keys(holds).length > 0 ? { holds } : {},
8226
+ ...badHolds.length > 0 ? { badHolds } : {}
8054
8227
  };
8055
8228
  }
8056
8229
  function parseWidgetDef(rawArgs) {
@@ -8102,8 +8275,11 @@ function widgetDefinitions$1(passages, parametersOf2 = registeredParameters) {
8102
8275
  }
8103
8276
  return found;
8104
8277
  }
8105
- function blockWidgetNames(passages, parametersOf2 = registeredParameters) {
8106
- return widgetDefinitions$1(passages, parametersOf2).filter((def) => def.block && def.closeStart !== void 0).map((def) => def.name);
8278
+ function effectiveWidgetDefinitions(definitions) {
8279
+ const inInit = (def) => def.passage === "StoryInit";
8280
+ const effective = new NameMap();
8281
+ for (const def of [...definitions.filter((def2) => !inInit(def2)), ...definitions.filter(inInit)]) if (def.closeStart !== void 0) effective.set(def.name, def);
8282
+ return [...effective.values()];
8107
8283
  }
8108
8284
  function distance(a, b) {
8109
8285
  let prev = Array.from({ length: b.length + 1 }, (_2, j2) => j2);
@@ -8257,20 +8433,6 @@ function validateMarkup(passages, options) {
8257
8433
  for (const [passage, tokens2] of parsed) withParseCache(() => checkPassage(passage, tokens2));
8258
8434
  return diagnostics;
8259
8435
  }
8260
- function variableNameError(name, label2 = name) {
8261
- return name === "__proto__" ? `"${label2}" cannot be used as a variable name (${RESERVED_NAME} is reserved)` : void 0;
8262
- }
8263
- function ownEntries(sources, keep = () => true) {
8264
- const record2 = /* @__PURE__ */ Object.create(null);
8265
- for (const source of sources) {
8266
- if (!source) continue;
8267
- for (const key of Object.keys(source)) {
8268
- const value = source[key];
8269
- if (keep(value)) record2[key] = value;
8270
- }
8271
- }
8272
- return record2;
8273
- }
8274
8436
  function staticLiteral(src) {
8275
8437
  const reader = new Reader(src);
8276
8438
  const value = reader.value();
@@ -8281,6 +8443,17 @@ function entryOrder(keys) {
8281
8443
  const all = [...keys];
8282
8444
  return [...all.filter((key) => ARRAY_INDEX_RE.test(key)).sort((a, b) => Number(a) - Number(b)), ...all.filter((key) => !ARRAY_INDEX_RE.test(key))];
8283
8445
  }
8446
+ function regexLiterals(src, goal = "expression", handlers = {}) {
8447
+ const ends = /* @__PURE__ */ new Map();
8448
+ lexJs(src, {
8449
+ ...handlers,
8450
+ literal(text2, index, nesting) {
8451
+ if (text2.charAt(0) === "/" && !/^\/[/*]/.test(text2)) ends.set(index, index + text2.length);
8452
+ handlers.literal?.(text2, index, nesting);
8453
+ }
8454
+ }, goal);
8455
+ return ends;
8456
+ }
8284
8457
  function bigintValue(digits) {
8285
8458
  try {
8286
8459
  return BigInt(digits.replace(/_/g, "")).toString();
@@ -8406,6 +8579,7 @@ function checkReference(ref2, declared) {
8406
8579
  const part = ref2.path[i2];
8407
8580
  if (current2.type === "array") return null;
8408
8581
  if (current2.type === "null") return null;
8582
+ if (current2.type === "string" && isArrayIndex(part)) continue;
8409
8583
  const sample = PRIMITIVE_SAMPLES[current2.type];
8410
8584
  if (sample && part in sample) {
8411
8585
  const memberType = typeof sample[part];
@@ -8421,9 +8595,20 @@ function checkReference(ref2, declared) {
8421
8595
  }
8422
8596
  return null;
8423
8597
  }
8424
- function referenceAt(code, index, at3) {
8598
+ function referenceAt(code, sigil, name, start, end, at3) {
8599
+ FIELD_PATH_RE.lastIndex = end;
8600
+ const fields = FIELD_PATH_RE.exec(code)[0];
8601
+ return {
8602
+ sigil,
8603
+ name,
8604
+ path: fields ? fields.slice(1).split(".") : [],
8605
+ start: at3(start),
8606
+ end: at3(end + fields.length)
8607
+ };
8608
+ }
8609
+ function termReferenceAt(term, index, at3) {
8425
8610
  VAR_PATH_RE.lastIndex = index;
8426
- const match = VAR_PATH_RE.exec(code);
8611
+ const match = VAR_PATH_RE.exec(term);
8427
8612
  if (!match) return void 0;
8428
8613
  const [name, ...path] = match[2].split(".");
8429
8614
  return {
@@ -8449,39 +8634,42 @@ function scanCode(code, at3, emit, goal = "expression") {
8449
8634
  }
8450
8635
  for (const ref2 of parsed.refs) {
8451
8636
  if (ref2.sigil !== "$" && ref2.sigil !== "%") continue;
8452
- const found = referenceAt(code, ref2.start, at3);
8453
- if (found) emit(found);
8637
+ emit(referenceAt(code, ref2.sigil, ref2.name, ref2.start, ref2.end, at3));
8454
8638
  }
8455
8639
  }
8456
8640
  function scanCodeLeniently(code, at3, emit, goal) {
8457
8641
  lexJs(code, {
8458
- variable(sigil, _name, index) {
8642
+ variable(sigil, name, index, _nesting, end) {
8459
8643
  if (sigil !== "$" && sigil !== "%") return;
8460
- const found = referenceAt(code, index, at3);
8461
- if (found) emit(found);
8644
+ emit(referenceAt(code, sigil, name, index, end, at3));
8462
8645
  },
8463
8646
  literal() {
8464
8647
  }
8465
8648
  }, goal);
8466
8649
  }
8467
- function scanArgs(token, params, at3, emit, options) {
8650
+ function scanPieces(pieces, at3, emit, options) {
8651
+ for (const piece of pieces) {
8652
+ const pieceAt = (i2) => at3(pieceOffset(piece, i2));
8653
+ if (piece.kind === "code") scanCode(piece.code, pieceAt, emit, piece.goal);
8654
+ else if (piece.kind === "text") scanInterpolations(piece.text, pieceAt, emit, {
8655
+ ...options,
8656
+ storeVarMacros: NO_STORE_VAR_MACROS
8657
+ });
8658
+ }
8659
+ }
8660
+ function scanArgs(token, parametersOf2, at3, emit, options) {
8468
8661
  if (!token.rawArgs) return;
8662
+ const params = parametersOf2(token.name);
8663
+ let byParameters = false;
8469
8664
  if (params) {
8470
8665
  if (options.all && !options.storeVarMacros.has(token.name.toLowerCase())) scanVariableParameters(token, params, at3, emit);
8471
8666
  const pieces = [...argPieces(token.rawArgs, token.argsStart, params, token.name, "")];
8472
- if (!pieces.some((piece) => piece.kind === "argument-error")) {
8473
- for (const piece of pieces) {
8474
- const pieceAt = (i2) => at3(pieceOffset(piece, i2));
8475
- if (piece.kind === "code") scanCode(piece.code, pieceAt, emit, piece.goal);
8476
- else if (piece.kind === "text") scanInterpolations(piece.text, pieceAt, emit, {
8477
- ...options,
8478
- storeVarMacros: NO_STORE_VAR_MACROS
8479
- });
8480
- }
8481
- return;
8482
- }
8667
+ byParameters = !pieces.some((piece) => piece.kind === "argument-error");
8668
+ if (byParameters) scanPieces(pieces, at3, emit, options);
8483
8669
  }
8484
- scanCode(token.rawArgs, (i2) => at3(token.argsStart + i2), emit);
8670
+ if (!byParameters) scanCode(token.rawArgs, (i2) => at3(token.argsStart + i2), emit);
8671
+ const widget = parametersOf2.widget?.(token.name);
8672
+ if (widget) scanPieces(widgetPieces(token.rawArgs, token.argsStart, widget, token.name, ""), at3, emit, options);
8485
8673
  }
8486
8674
  function scanVariableParameters(token, params, at3, emit) {
8487
8675
  const spans = /* @__PURE__ */ new Map();
@@ -8494,7 +8682,7 @@ function scanVariableParameters(token, params, at3, emit) {
8494
8682
  for (const param of params) {
8495
8683
  const span = spans.get(param);
8496
8684
  if (param.type !== "variable" || !span) continue;
8497
- const ref2 = referenceAt(token.rawArgs.slice(span[0], span[1]), 0, (i2) => at3(token.argsStart + span[0] + i2));
8685
+ const ref2 = termReferenceAt(token.rawArgs.slice(span[0], span[1]), 0, (i2) => at3(token.argsStart + span[0] + i2));
8498
8686
  if (ref2) emit(ref2);
8499
8687
  }
8500
8688
  }
@@ -8533,7 +8721,7 @@ function collectTokenRefs(content, tokens2, at3, emit, options) {
8533
8721
  else scanInterpolations(value, valueIn, emit, options);
8534
8722
  }
8535
8723
  else if (token.type === "macro" && !token.isClose) {
8536
- scanArgs(token, parametersOf2(token.name), at3, emit, options);
8724
+ scanArgs(token, parametersOf2, at3, emit, options);
8537
8725
  scanSelectors(content, token, at3, emit, options);
8538
8726
  if (storeVarMacros.has(token.name.toLowerCase())) {
8539
8727
  const lead = token.rawArgs.length - token.rawArgs.trimStart().length;
@@ -8569,7 +8757,7 @@ function collectTokenRefs(content, tokens2, at3, emit, options) {
8569
8757
  function collectVariableReferences(content, options) {
8570
8758
  const found = [];
8571
8759
  withParseCache(() => collectTokenRefs(content, tokensOf(content, false, options), (i2) => i2, (ref2) => found.push(ref2), options));
8572
- return found;
8760
+ return found.sort((a, b) => a.start - b.start);
8573
8761
  }
8574
8762
  function checkVariableReferences(passages, declared, options) {
8575
8763
  const diagnostics = [];
@@ -8593,10 +8781,13 @@ function checkVariableReferences(passages, declared, options) {
8593
8781
  }
8594
8782
  function codeOnly(source) {
8595
8783
  const chars = Array.from({ length: source.length }, () => " ");
8596
- lexJs(source, { code(ch, index) {
8784
+ const regexes = regexLiterals(source, "statements", { code(ch, index) {
8597
8785
  for (let k2 = 0; k2 < ch.length; k2++) chars[index + k2] = ch[k2];
8598
- } }, "statements");
8599
- return chars.join("");
8786
+ } });
8787
+ return {
8788
+ code: chars.join(""),
8789
+ regexes
8790
+ };
8600
8791
  }
8601
8792
  function written(props, key, read) {
8602
8793
  const value = read(props.get(key));
@@ -8659,18 +8850,23 @@ function macroOf(config2) {
8659
8850
  return macro;
8660
8851
  }
8661
8852
  function discoverMacros(source) {
8662
- const code = codeOnly(source);
8853
+ const { code, regexes } = codeOnly(source);
8663
8854
  const found = [];
8855
+ let bindings;
8664
8856
  for (const call of code.matchAll(CALL_RE)) {
8665
8857
  let at3 = call.index + call[0].length;
8666
8858
  if (code.charAt(at3) !== "{") {
8667
8859
  const variable = IDENTIFIER_ARGUMENT_RE.exec(code.slice(at3))?.[1];
8668
8860
  if (!variable) continue;
8669
- const declaration = new RegExp(`(?<![\\w$.])(?:const|let|var)\\s+${variable.replace(/\$/g, "\\$")}\\s*=\\s*(?=\\{)`).exec(code);
8670
- if (!declaration) continue;
8671
- at3 = declaration.index + declaration[0].length;
8672
- }
8673
- const reader = new ConfigReader(source);
8861
+ bindings ?? (bindings = new Bindings(code));
8862
+ const object3 = bindings.objectOf(variable, call.index, CONFIG_KEYS);
8863
+ if (object3 === void 0) continue;
8864
+ at3 = object3;
8865
+ }
8866
+ const reader = new ConfigReader(source, {
8867
+ goal: "statements",
8868
+ regexes
8869
+ });
8674
8870
  reader.i = at3;
8675
8871
  const config2 = reader.datum();
8676
8872
  const macro = config2 && macroOf(config2);
@@ -8678,12 +8874,561 @@ function discoverMacros(source) {
8678
8874
  }
8679
8875
  return found;
8680
8876
  }
8877
+ function segmentRegions(body) {
8878
+ const lines = body.split("\n");
8879
+ if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
8880
+ if (lines.length === 0) return [];
8881
+ const regions = [];
8882
+ let i2 = 0;
8883
+ while (i2 < lines.length) {
8884
+ const line = lines[i2];
8885
+ const trimmed2 = line.trim();
8886
+ if (/^<script(\s|>)/i.test(trimmed2)) {
8887
+ const block = blockLines(lines, i2, /<\/script\s*>/i);
8888
+ regions.push({
8889
+ type: "script",
8890
+ lines: block
8891
+ });
8892
+ i2 += block.length;
8893
+ continue;
8894
+ }
8895
+ if (/^<svg(\s|>)/i.test(trimmed2)) {
8896
+ const block = blockLines(lines, i2, /<\/svg\s*>/i);
8897
+ regions.push({
8898
+ type: "svg",
8899
+ lines: block
8900
+ });
8901
+ i2 += block.length;
8902
+ continue;
8903
+ }
8904
+ if (/^<([a-zA-Z][\w-]*)/.test(trimmed2)) {
8905
+ const tagName = trimmed2.match(/^<([a-zA-Z][\w-]*)/)[1].toLowerCase();
8906
+ if (VOID_ELEMENTS.has(tagName) && !lines[i2 + 1]?.trim().startsWith("<")) {
8907
+ pushSpindleLine(regions, line);
8908
+ i2++;
8909
+ continue;
8910
+ }
8911
+ if (new RegExp(`</${tagName}\\s*>`, "i").test(trimmed2)) {
8912
+ pushSpindleLine(regions, line);
8913
+ i2++;
8914
+ continue;
8915
+ }
8916
+ const htmlLines = [line];
8917
+ let depth = 1;
8918
+ i2++;
8919
+ while (i2 < lines.length && depth > 0) {
8920
+ htmlLines.push(lines[i2]);
8921
+ const opens = (lines[i2].match(new RegExp(`<${tagName}(?:[\\s>]|$)`, "gi")) ?? []).length;
8922
+ const closes2 = (lines[i2].match(new RegExp(`</${tagName}\\s*>`, "gi")) ?? []).length;
8923
+ depth += opens - closes2;
8924
+ i2++;
8925
+ }
8926
+ regions.push({
8927
+ type: "html",
8928
+ lines: htmlLines
8929
+ });
8930
+ continue;
8931
+ }
8932
+ pushSpindleLine(regions, line);
8933
+ i2++;
8934
+ }
8935
+ return regions;
8936
+ }
8937
+ function blockLines(lines, start, close) {
8938
+ let end = start;
8939
+ while (end < lines.length - 1 && !close.test(lines[end])) end++;
8940
+ return lines.slice(start, end + 1);
8941
+ }
8942
+ function pushSpindleLine(regions, line) {
8943
+ const last2 = regions[regions.length - 1];
8944
+ if (last2?.type === "spindle") last2.lines.push(line);
8945
+ else regions.push({
8946
+ type: "spindle",
8947
+ lines: [line]
8948
+ });
8949
+ }
8950
+ async function run(code, prettier2, options, overrides) {
8951
+ if (!prettier2) return void 0;
8952
+ try {
8953
+ return await prettier2.format(code, {
8954
+ ...options ?? DEFAULT_PRETTIER_OPTIONS,
8955
+ ...overrides
8956
+ });
8957
+ } catch {
8958
+ return;
8959
+ }
8960
+ }
8961
+ function formatJS(code, prettier2, options) {
8962
+ return run(code, prettier2, options, { parser: "babel" });
8963
+ }
8964
+ function formatCSS(code, prettier2, options) {
8965
+ return run(code, prettier2, options, { parser: "css" });
8966
+ }
8967
+ function formatHTML(code, prettier2, options) {
8968
+ return run(code, prettier2, options, {
8969
+ parser: "html",
8970
+ htmlWhitespaceSensitivity: "css"
8971
+ });
8972
+ }
8973
+ function scanSpindleMarkup(text2) {
8974
+ const tokens2 = [];
8975
+ const tags = [];
8976
+ const doBlocks = [];
8977
+ let open;
8978
+ const push = (t, inAttribute) => {
8979
+ const match = {
8980
+ start: t.start,
8981
+ end: t.end,
8982
+ token: text2.slice(t.start, t.end)
8983
+ };
8984
+ if (inAttribute) match.inAttribute = true;
8985
+ tokens2.push(match);
8986
+ };
8987
+ for (const t of tokenizeMarkupTolerant(text2).tokens) if (t.type === "html") {
8988
+ tags.push({
8989
+ start: t.start,
8990
+ end: t.end
8991
+ });
8992
+ for (const attribute of t.attributeSpans) {
8993
+ if (attribute.valueStart === void 0 || attribute.valueEnd === void 0) continue;
8994
+ const value = text2.slice(attribute.valueStart, attribute.valueEnd);
8995
+ if (!value.includes("{")) continue;
8996
+ for (const inner of tokenizeMarkupTolerant(value, { text: true }).tokens) if (inner.type === "macro" || inner.type === "variable" || inner.type === "expression") push({
8997
+ start: attribute.valueStart + inner.start,
8998
+ end: attribute.valueStart + inner.end
8999
+ }, true);
9000
+ }
9001
+ } else if (t.type !== "text") {
9002
+ if (t.type === "macro" && t.name === "do") {
9003
+ if (!t.isClose) open = t;
9004
+ else if (open) {
9005
+ doBlocks.push({
9006
+ start: open.start,
9007
+ end: t.end,
9008
+ bodyStart: open.end,
9009
+ bodyEnd: t.start
9010
+ });
9011
+ open = void 0;
9012
+ }
9013
+ }
9014
+ push(t);
9015
+ }
9016
+ return {
9017
+ tokens: tokens2,
9018
+ tags,
9019
+ doBlocks
9020
+ };
9021
+ }
9022
+ function multilineJsLiterals(code) {
9023
+ const spans = [];
9024
+ let skipUntil = 0;
9025
+ lexJs(code, { literal(text2, index, nesting) {
9026
+ if (nesting > 0 || index < skipUntil) return;
9027
+ const quote = text2[0];
9028
+ if (quote === "`") {
9029
+ skipUntil = lexTemplate(code, index);
9030
+ if (code.slice(index, skipUntil).includes("\n")) spans.push([index, skipUntil]);
9031
+ } else if ((quote === '"' || quote === "'") && text2.includes("\n")) spans.push([index, index + text2.length]);
9032
+ } }, "statements");
9033
+ return spans;
9034
+ }
9035
+ function lineHasTag(tags, start, end) {
9036
+ return tags.some((tag) => tag.start < end && tag.end > start);
9037
+ }
9038
+ function replaceSvgBlocks(html) {
9039
+ const tokens2 = [];
9040
+ return {
9041
+ text: html.replace(/<svg[\s>][\s\S]*?<\/svg>/gi, (match) => {
9042
+ const idx = tokens2.length;
9043
+ tokens2.push(match);
9044
+ return `<!--SVG:${idx}-->`;
9045
+ }),
9046
+ tokens: tokens2
9047
+ };
9048
+ }
9049
+ function placeholdersIntact(text2, tokens2, placeholdersOf) {
9050
+ return tokens2.every((_2, index) => placeholdersOf(index).reduce((count, placeholder) => count + text2.split(placeholder).length - 1, 0) === 1);
9051
+ }
9052
+ function sameSpindleTokens(before, after) {
9053
+ const tokensOf2 = (text2) => scanSpindleMarkup(text2).tokens.map((m) => m.token);
9054
+ const expected = tokensOf2(before);
9055
+ const found = tokensOf2(after);
9056
+ return expected.length === found.length && expected.every((t, i2) => t === found[i2]);
9057
+ }
9058
+ function restorePlaceholders(text2, tokens2, placeholdersOf) {
9059
+ let result = text2;
9060
+ tokens2.forEach((token, index) => {
9061
+ for (const placeholder of placeholdersOf(index)) result = result.replace(placeholder, () => token);
9062
+ });
9063
+ return result;
9064
+ }
9065
+ function restoreSvgBlocks(text2, tokens2) {
9066
+ return restorePlaceholders(text2, tokens2, SVG_PLACEHOLDERS);
9067
+ }
9068
+ function replaceSpindleTokens(html) {
9069
+ const { tokens: found, tags, doBlocks } = scanSpindleMarkup(html);
9070
+ const units = [];
9071
+ const pushDoBlock = (block) => units.push({
9072
+ start: block.start,
9073
+ end: block.end,
9074
+ token: html.slice(block.start, block.end)
9075
+ });
9076
+ let doIndex = 0;
9077
+ for (const m of found) {
9078
+ for (let block = doBlocks[doIndex]; block && block.end <= m.start; ) {
9079
+ pushDoBlock(block);
9080
+ block = doBlocks[++doIndex];
9081
+ }
9082
+ const open = doBlocks[doIndex];
9083
+ if (open && m.start >= open.start) continue;
9084
+ units.push(m);
9085
+ }
9086
+ for (; doIndex < doBlocks.length; doIndex++) pushDoBlock(doBlocks[doIndex]);
9087
+ units.sort((a, b) => a.start - b.start);
9088
+ const tokens2 = [];
9089
+ const resultLines = [];
9090
+ let unit = 0;
9091
+ let lineStart = 0;
9092
+ while (lineStart <= html.length) {
9093
+ let lineEnd = lineStart;
9094
+ const lineUnits = [];
9095
+ for (; ; ) {
9096
+ while (unit < units.length && units[unit].end <= lineEnd) unit++;
9097
+ const next = units[unit];
9098
+ const inside = next && next.start <= lineEnd ? next : void 0;
9099
+ if (inside) {
9100
+ lineUnits.push(inside);
9101
+ lineEnd = inside.end;
9102
+ unit++;
9103
+ continue;
9104
+ }
9105
+ if (lineEnd >= html.length || html[lineEnd] === "\n") break;
9106
+ lineEnd++;
9107
+ }
9108
+ const line = html.slice(lineStart, lineEnd);
9109
+ const indent = line.match(/^(\s*)/)?.[1] ?? "";
9110
+ if (lineUnits.length === 0) resultLines.push(line);
9111
+ else if (!lineHasTag(tags, lineStart, lineEnd)) {
9112
+ const idx = tokens2.length;
9113
+ tokens2.push(line.trim());
9114
+ resultLines.push(`${indent}<!--SP:${idx}-->`);
9115
+ } else {
9116
+ let replaced = "";
9117
+ let last2 = lineStart;
9118
+ for (const u of lineUnits) {
9119
+ const idx = tokens2.length;
9120
+ tokens2.push(u.token);
9121
+ replaced += html.slice(last2, u.start) + (u.inAttribute ? `__SP${idx}__` : `<!--SP:${idx}-->`);
9122
+ last2 = u.end;
9123
+ }
9124
+ resultLines.push(replaced + html.slice(last2, lineEnd));
9125
+ }
9126
+ lineStart = lineEnd + 1;
9127
+ }
9128
+ return {
9129
+ text: resultLines.join("\n"),
9130
+ tokens: tokens2
9131
+ };
9132
+ }
9133
+ function restoreSpindleTokens(text2, tokens2) {
9134
+ return restorePlaceholders(text2, tokens2, SPINDLE_PLACEHOLDERS);
9135
+ }
9136
+ function closedMacroNames(text2) {
9137
+ const found = /* @__PURE__ */ new Set();
9138
+ for (const t of tokenizeMarkupTolerant(text2).tokens) if (t.type === "macro" && t.isClose) found.add(t.name.toLowerCase());
9139
+ return found;
9140
+ }
9141
+ function defaultIsBlock(text2, extra = []) {
9142
+ const closed = closedMacroNames(text2);
9143
+ for (const name of extra) closed.add(name.toLowerCase());
9144
+ return (name) => isBlockMacro(name) || closed.has(name.toLowerCase());
9145
+ }
9146
+ async function formatPassage(body, options = {}) {
9147
+ if (body.charCodeAt(0) === 65279) return "\uFEFF" + await formatPassage(body.slice(1), options);
9148
+ const eol = dominantEol(body);
9149
+ const output = await formatLf(body.replace(/\r\n/g, "\n"), options);
9150
+ return eol === "\r\n" ? output.replace(/\n/g, "\r\n") : output;
9151
+ }
9152
+ function dominantEol(text2) {
9153
+ const crlf = (text2.match(/\r\n/g) ?? []).length;
9154
+ const lf = (text2.match(/\n/g) ?? []).length - crlf;
9155
+ if (crlf !== lf) return crlf > lf ? "\r\n" : "\n";
9156
+ return text2.indexOf("\r\n") !== -1 && text2.indexOf("\r\n") < text2.indexOf("\n") ? "\r\n" : "\n";
9157
+ }
9158
+ function replaceAll(text2, from, to) {
9159
+ return text2.split(from).join(to);
9160
+ }
9161
+ function newlineSentinel(text2) {
9162
+ for (let code = 57344; code <= 63743; code++) {
9163
+ const ch = String.fromCharCode(code);
9164
+ if (!text2.includes(ch)) return ch;
9165
+ }
9166
+ throw new Error("no free private-use character for the newline stand-in");
9167
+ }
9168
+ function protectMultilineTokens(body, sentinel) {
9169
+ const { tokens: tokens2, doBlocks } = scanSpindleMarkup(body);
9170
+ const spans = [];
9171
+ for (const m of tokens2) if (m.token.includes("\n")) spans.push([m.start, m.end]);
9172
+ for (const block of doBlocks) for (const [a, b] of multilineJsLiterals(body.slice(block.bodyStart, block.bodyEnd))) spans.push([block.bodyStart + a, block.bodyStart + b]);
9173
+ return joinSpans(body, spans, sentinel);
9174
+ }
9175
+ function joinJsLiterals(code, sentinel) {
9176
+ return joinSpans(code, multilineJsLiterals(code), sentinel);
9177
+ }
9178
+ function joinSpans(text2, spans, sentinel) {
9179
+ let out = "";
9180
+ let last2 = 0;
9181
+ for (const [a, b] of [...spans].sort((x2, y) => x2[0] - y[0])) {
9182
+ out += text2.slice(last2, a) + replaceAll(text2.slice(a, b), "\n", sentinel);
9183
+ last2 = b;
9184
+ }
9185
+ return out + text2.slice(last2);
9186
+ }
9187
+ async function formatLf(text2, options) {
9188
+ const { prettier: prettier2, prettierOptions } = options;
9189
+ const body = text2.replace(/(?:[ \t]*\n)+[ \t]*$/, "\n");
9190
+ if (options.kind === "script" || options.kind === "stylesheet") {
9191
+ const format = options.kind === "script" ? formatJS : formatCSS;
9192
+ const code = body.trim();
9193
+ return (await format(code, prettier2, prettierOptions) ?? code).trim().replace(/\n*$/, "\n");
9194
+ }
9195
+ const isBlock = options.isBlock ?? defaultIsBlock(body, options.blockMacros);
9196
+ const isDedenting = options.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING.has(name.toLowerCase()));
9197
+ const sentinel = newlineSentinel(body);
9198
+ const lines = [];
9199
+ const hardBreakLines = /* @__PURE__ */ new Set();
9200
+ const regions = segmentRegions(protectMultilineTokens(body, sentinel));
9201
+ for (const region of regions) {
9202
+ if (region.type !== "spindle") region.lines = region.lines.map((l) => replaceAll(l, sentinel, "\n"));
9203
+ if (region.type === "script") {
9204
+ if (region.lines.length === 1) {
9205
+ lines.push(...region.lines);
9206
+ continue;
9207
+ }
9208
+ const innerCode = region.lines.slice(1, -1).join("\n");
9209
+ const formatted = await formatJS(innerCode.trim(), prettier2, prettierOptions);
9210
+ if (formatted === void 0 || !sameSpindleTokens(innerCode, formatted)) {
9211
+ lines.push(...region.lines);
9212
+ continue;
9213
+ }
9214
+ lines.push(region.lines[0]);
9215
+ if (formatted.trim()) for (const fLine of joinJsLiterals(formatted.trim(), sentinel).split("\n")) lines.push(" " + fLine);
9216
+ lines.push(region.lines[region.lines.length - 1]);
9217
+ continue;
9218
+ }
9219
+ if (region.type === "svg") {
9220
+ lines.push(...region.lines);
9221
+ continue;
9222
+ }
9223
+ if (region.type === "html") {
9224
+ const htmlText = region.lines.join("\n");
9225
+ if (WHITESPACE_AS_WRITTEN.test(htmlText)) {
9226
+ lines.push(...region.lines);
9227
+ continue;
9228
+ }
9229
+ const { text: svgPlaceholdered, tokens: svgTokens } = replaceSvgBlocks(htmlText);
9230
+ const { text: placeholdered, tokens: tokens2 } = replaceSpindleTokens(svgPlaceholdered);
9231
+ const formatted = await formatHTML(placeholdered, prettier2, prettierOptions);
9232
+ if (formatted === void 0 || !placeholdersIntact(formatted, tokens2, SPINDLE_PLACEHOLDERS) || !placeholdersIntact(formatted, svgTokens, SVG_PLACEHOLDERS)) {
9233
+ lines.push(...region.lines);
9234
+ continue;
9235
+ }
9236
+ const restored = restoreSvgBlocks(restoreSpindleTokens(formatted.trim(), tokens2.map((t) => replaceAll(t, "\n", sentinel))), svgTokens);
9237
+ for (const fLine of restored.split("\n")) lines.push(fLine);
9238
+ continue;
9239
+ }
9240
+ let indented = indentMacros(region.lines, isBlock, isDedenting);
9241
+ if (options.maxLineLength) indented = wrapLines(indented, options.maxLineLength);
9242
+ for (const line of indented) {
9243
+ if (line.endsWith(" ")) hardBreakLines.add(lines.length);
9244
+ lines.push(line);
9245
+ }
9246
+ }
9247
+ let output = lines.map((l, i2) => hardBreakLines.has(i2) ? l : l.replace(/\s+$/, "")).join("\n");
9248
+ output = output.replace(/\n*$/, "\n");
9249
+ return replaceAll(output, sentinel, "\n");
9250
+ }
9251
+ function indentMacros(lines, isBlock, isDedenting) {
9252
+ const macrosOnLine = macroTokensByLine(lines);
9253
+ const bodies = [{
9254
+ openLine: -1,
9255
+ minCol: Infinity,
9256
+ keepRelative: false
9257
+ }];
9258
+ const stack = [0];
9259
+ const info = [];
9260
+ const openBody = (line) => {
9261
+ bodies.push({
9262
+ openLine: line,
9263
+ minCol: Infinity,
9264
+ keepRelative: false
9265
+ });
9266
+ stack.push(bodies.length - 1);
9267
+ };
9268
+ const currentBody = () => stack[stack.length - 1];
9269
+ const closeBody = () => stack.length > 1 ? stack.pop() : void 0;
9270
+ const addLine = (col, text2) => {
9271
+ const body = bodies[currentBody()];
9272
+ body.minCol = Math.min(body.minCol, col);
9273
+ if (LIST_ITEM_REGEX.test(text2) || CODE_FENCE_REGEX.test(text2)) body.keepRelative = true;
9274
+ info.push({
9275
+ body: currentBody(),
9276
+ col,
9277
+ text: text2
9278
+ });
9279
+ };
9280
+ for (let i2 = 0; i2 < lines.length; i2++) {
9281
+ const lineText = lines[i2];
9282
+ const trimmed2 = lineText.trim();
9283
+ if (trimmed2 === "") {
9284
+ info.push(null);
9285
+ continue;
9286
+ }
9287
+ const col = indentColumn(lineText);
9288
+ const lead = lineText.length - lineText.trimStart().length;
9289
+ const macros = macrosOnLine[i2];
9290
+ const [first, ...rest] = macros;
9291
+ const startsWithMacro = first !== void 0 && first.start === lead;
9292
+ if (startsWithMacro && !first.isClose && isDedenting(first.name)) {
9293
+ const closed2 = closeBody();
9294
+ if (closed2 === void 0) addLine(col, trimmed2);
9295
+ else info.push({
9296
+ body: currentBody(),
9297
+ closes: closed2,
9298
+ col,
9299
+ text: trimmed2
9300
+ });
9301
+ openBody(i2);
9302
+ continue;
9303
+ }
9304
+ const closed = startsWithMacro && first.isClose ? closeBody() : void 0;
9305
+ if (closed === void 0) addLine(col, trimmed2);
9306
+ else info.push({
9307
+ body: currentBody(),
9308
+ closes: closed,
9309
+ col,
9310
+ text: trimmed2
9311
+ });
9312
+ if (startsWithMacro && !first.isClose && isBlock(first.name)) openBody(i2);
9313
+ for (const macro of startsWithMacro ? rest : macros) if (macro.isClose && macro.start > lead) closeBody();
9314
+ }
9315
+ const outCol = [];
9316
+ const result = [];
9317
+ for (let i2 = 0; i2 < info.length; i2++) {
9318
+ const line = info[i2];
9319
+ if (!line) {
9320
+ outCol.push(0);
9321
+ result.push("");
9322
+ continue;
9323
+ }
9324
+ let col;
9325
+ if (line.closes !== void 0) col = outCol[bodies[line.closes].openLine];
9326
+ else {
9327
+ const body = bodies[line.body];
9328
+ const isRoot = body.openLine === -1;
9329
+ col = (isRoot ? 0 : outCol[body.openLine] + 2) + (body.keepRelative ? line.col - (isRoot ? 0 : body.minCol) : 0);
9330
+ }
9331
+ outCol.push(col);
9332
+ const hardBreak = / {2,}$/.test(lines[i2]) && info[i2 + 1] ? " " : "";
9333
+ result.push(" ".repeat(col) + line.text + hardBreak);
9334
+ }
9335
+ return result;
9336
+ }
9337
+ function macroTokensByLine(lines) {
9338
+ const byLine = lines.map(() => []);
9339
+ const starts = [];
9340
+ let offset2 = 0;
9341
+ for (const line2 of lines) {
9342
+ starts.push(offset2);
9343
+ offset2 += line2.length + 1;
9344
+ }
9345
+ let line = 0;
9346
+ for (const token of tokenizeMarkupTolerant(lines.join("\n")).tokens) {
9347
+ if (token.type !== "macro") continue;
9348
+ while (line + 1 < lines.length && starts[line + 1] <= token.start) line++;
9349
+ const lineStart = starts[line];
9350
+ byLine[line].push({
9351
+ ...token,
9352
+ start: token.start - lineStart,
9353
+ end: token.end - lineStart
9354
+ });
9355
+ }
9356
+ return byLine;
9357
+ }
9358
+ function indentColumn(line) {
9359
+ let col = 0;
9360
+ for (const ch of line) if (ch === " ") col++;
9361
+ else if (ch === " ") col += 4 - col % 4;
9362
+ else break;
9363
+ return col;
9364
+ }
9365
+ function wrapLines(lines, maxLen) {
9366
+ const result = [];
9367
+ const javaScript = doBodyLines(lines);
9368
+ for (const [index, line] of lines.entries()) {
9369
+ if (line.length <= maxLen || javaScript.has(index)) {
9370
+ result.push(line);
9371
+ continue;
9372
+ }
9373
+ const trimmed2 = line.trim();
9374
+ if (trimmed2 === "" || /^\{[\/#.]/.test(trimmed2) || /^\[\[/.test(trimmed2) || /^\{choice\b/.test(trimmed2) || /^\{choices\b/.test(trimmed2)) {
9375
+ result.push(line);
9376
+ continue;
9377
+ }
9378
+ const wrapped = wordWrap(trimmed2, maxLen, line.match(/^(\s*)/)?.[1] ?? "");
9379
+ if (line.endsWith(" ")) wrapped[wrapped.length - 1] += " ";
9380
+ result.push(...wrapped);
9381
+ }
9382
+ return result;
9383
+ }
9384
+ function doBodyLines(lines) {
9385
+ const found = /* @__PURE__ */ new Set();
9386
+ const text2 = lines.join("\n");
9387
+ for (const block of scanSpindleMarkup(text2).doBlocks) {
9388
+ const first = text2.slice(0, block.bodyStart).split("\n").length - 1;
9389
+ const last2 = text2.slice(0, block.bodyEnd).split("\n").length - 1;
9390
+ for (let i2 = first; i2 <= last2; i2++) found.add(i2);
9391
+ }
9392
+ return found;
9393
+ }
9394
+ function wordWrap(text2, maxLen, indent) {
9395
+ const lines = [];
9396
+ const inToken = new Array(text2.length).fill(false);
9397
+ for (const token of tokenizeMarkupTolerant(text2).tokens) {
9398
+ if (token.type === "text" || token.type === "html") continue;
9399
+ inToken.fill(true, token.start, token.end);
9400
+ }
9401
+ const room = maxLen - indent.length;
9402
+ let pos = 0;
9403
+ while (pos < text2.length) {
9404
+ if (text2.length - pos <= room) {
9405
+ lines.push(indent + text2.slice(pos));
9406
+ break;
9407
+ }
9408
+ let breakIdx = -1;
9409
+ for (let i2 = pos + 1; i2 < text2.length && i2 - pos <= room; i2++) if (text2[i2] === " " && !inToken[i2]) breakIdx = i2;
9410
+ if (breakIdx === -1) {
9411
+ for (let i2 = pos + room + 1; i2 < text2.length; i2++) if (text2[i2] === " " && !inToken[i2]) {
9412
+ breakIdx = i2;
9413
+ break;
9414
+ }
9415
+ }
9416
+ if (breakIdx === -1) {
9417
+ lines.push(indent + text2.slice(pos));
9418
+ break;
9419
+ }
9420
+ lines.push(indent + text2.slice(pos, breakIdx));
9421
+ pos = breakIdx + 1;
9422
+ }
9423
+ return lines;
9424
+ }
8681
9425
  function validateStoryMarkup(passages, macros, options = {}) {
8682
9426
  const list3 = [...passages];
8683
9427
  const all = [...macros];
8684
9428
  const known = /* @__PURE__ */ new Set();
8685
- const parametersOf2 = parameterLookup(all);
8686
- const blocks = new Set(blockWidgetNames(list3, parametersOf2).map((n7) => n7.toLowerCase()));
9429
+ const widgets = widgetDefinitions$1(list3, parameterLookup(all));
9430
+ const blocks = new Set(effectiveWidgetDefinitions(widgets).filter((def) => def.block).map((def) => def.name.toLowerCase()));
9431
+ const parametersOf2 = parameterLookup([...all, ...definedWidgetMacros(widgets)]);
8687
9432
  for (const macro of all) {
8688
9433
  const name = macro.name.toLowerCase();
8689
9434
  known.add(name);
@@ -8699,24 +9444,33 @@ function validateStoryMarkup(passages, macros, options = {}) {
8699
9444
  tolerant: options.tolerant
8700
9445
  });
8701
9446
  }
9447
+ function definedWidgetMacros(widgets) {
9448
+ return widgets.map((widget) => ({
9449
+ name: widget.name,
9450
+ widget
9451
+ }));
9452
+ }
8702
9453
  function blockLookup(macros) {
8703
9454
  const blocks = new Set(macros.filter((m) => m.block).map((m) => m.name.toLowerCase()));
8704
9455
  return (name) => blocks.has(name.toLowerCase()) || isBlockMacro(name);
8705
9456
  }
8706
9457
  function passagePieces(source, macros = []) {
9458
+ return passagePiecesOfTokens(source, macros, tokenizeMarkupTolerant(source).tokens);
9459
+ }
9460
+ function passagePiecesOfTokens(source, macros, tokens2) {
8707
9461
  const all = [...macros];
8708
- const { tokens: tokens2 } = tokenizeMarkupTolerant(source);
8709
9462
  return [...passagePiecesOf(source, tokens2, parameterLookup(all), { isBlock: blockLookup(all) })];
8710
9463
  }
8711
9464
  function widgetDefinitions(passages, macros = []) {
8712
9465
  return widgetDefinitions$1(passages, parameterLookup([...macros]));
8713
9466
  }
8714
- function referenceOptions(macros, all = false) {
9467
+ function referenceOptions(macros, all = false, passages = []) {
8715
9468
  const list3 = [...macros];
9469
+ const widgets = widgetDefinitions$1(passages, parameterLookup(list3));
8716
9470
  return {
8717
9471
  all,
8718
9472
  storeVarMacros: new Set(list3.filter((m) => m.storeVar).map((m) => m.name.toLowerCase())),
8719
- parametersOf: parameterLookup(list3),
9473
+ parametersOf: parameterLookup([...list3, ...definedWidgetMacros(widgets)]),
8720
9474
  tolerant: true
8721
9475
  };
8722
9476
  }
@@ -8724,9 +9478,10 @@ function variableReferences(source, macros = [], options = {}) {
8724
9478
  return collectVariableReferences(source, referenceOptions(macros, options.all));
8725
9479
  }
8726
9480
  function validateVariableReferences(passages, declarations, macros = []) {
8727
- return checkVariableReferences([...passages].filter((p) => !p.tags?.some((t) => NOT_MARKUP_TAGS.includes(t))), declarations, referenceOptions(macros));
9481
+ const list3 = [...passages];
9482
+ return checkVariableReferences(list3.filter((p) => !p.tags?.some((t) => NOT_MARKUP_TAGS.includes(t))), declarations, referenceOptions(macros, false, list3));
8728
9483
  }
8729
- var SIGIL_SCOPES, SIGIL_CHARS, grammar_shared_default, LB, RB, SIGIL_SCOPES$1, withSelectors, VOID_TAGS, BRANCH_PARENT$1, LINK_SEPARATORS, NO_SELECTORS, EMAIL_AUTOLINK, peg$SyntaxError, NameMap, NameSet, BLOCK_MACROS, RAW_BODY_MACROS, BRANCHING, quoteArg, last$1, BRANCH_PARENT, isBranchTag, lowerName, nameOf, closerOf, closes, astralIdentifierCodes, astralIdentifierStartCodes, nonASCIIidentifierChars, nonASCIIidentifierStartChars, reservedWords, ecma5AndLessKeywords, keywords$1, keywordRelationalOperator, nonASCIIidentifierStart, nonASCIIidentifier, TokenType, beforeExpr, startsExpr, keywords, types$1, lineBreak, lineBreakG, nonASCIIwhitespace, skipWhiteSpace, ref, hasOwnProperty, toString, hasOwn, isArray, regexpCache, loneSurrogate, Position, SourceLocation, defaultOptions, warnedAboutEcmaVersion, SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, SCOPE_DIRECT_SUPER, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH, SCOPE_VAR, BIND_NONE, BIND_VAR, BIND_LEXICAL, BIND_FUNCTION, BIND_SIMPLE_CATCH, BIND_OUTSIDE, Parser, prototypeAccessors, pp$9, literal, DestructuringErrors, pp$8, loopLabel, switchLabel, empty$1, FUNC_STATEMENT, FUNC_HANGING_STATEMENT, FUNC_NULLABLE_ID, pp$7, TokContext, types, pp$6, pp$5, empty, pp$4, pp$3, Scope, Node, pp$2, scriptValuesAddedInUnicode, ecma9BinaryProperties, ecma10BinaryProperties, ecma11BinaryProperties, ecma12BinaryProperties, ecma13BinaryProperties, unicodeBinaryProperties, unicodeBinaryPropertiesOfStrings, unicodeGeneralCategoryValues, ecma9ScriptValues, ecma10ScriptValues, ecma11ScriptValues, ecma12ScriptValues, ecma13ScriptValues, unicodeScriptValues, data, ecmaVersion, i, list, pp$1, BranchID, RegExpValidationState, CharSetNone, CharSetOk, CharSetString, Token, pp, INVALID_TEMPLATE_ESCAPE_ERROR, OPTIONS, AT_NAME_RE, TRANS_NAME_RE, SIGIL_IDENT_RE, LINE_BREAK_RE, NOT_A_BODY_RE, OPERAND_ENDS, SigilParser, OPENERS, CLOSERS, CodeSyntaxError, last, CLOSER, isNode, NAMESPACE_NAME, WORD_RE$2, FLAGS_RE, WORDS_BEFORE_STRING, NON_STRING_QUOTE_PREFIX, memoInput, memo, defaultCodeEnd, defaultHooks, MarkupError, OFFSET_FIELD, listeners, TRAILING_OPERATOR_RE, slice, MacroArgumentError, WORD_RE$1, NUMBER_RE$1, WORD_OR_PUNCT_RE, LEADING_OPERATOR_RE, takesRest, SUB_MACRO_PARAMETERS, parses, CONDITION_MACROS, PASSAGE_BODIES, holdsByDefault, textPiece, PARAMETER_TYPES, STRING_HOLDS, metadataRegistry, registeredParameters, WIDGET_PARAMETERS, NOT_MARKUP, NOT_MARKUP_TAGS$1, BRANCH_MACROS, didYouMean, PASSAGE_ARGUMENT_HINT, BARE_WORD_RE, RESERVED_NAME, createNamespace, NUMBER_RE, IDENT_RE, KEY_RE, ARROW_RE, ARRAY_INDEX_RE, UNSUPPORTED_UNDEFINED, Reader, VAR_PATH_RE, QUOTED_VAR_ARG_RE, VALID_VAR_TYPES, PRIMITIVE_SAMPLES, UNSUPPORTED_TYPE, NO_STORE_VAR_MACROS, CALL_RE, IDENTIFIER_ARGUMENT_RE, WORD_RE, ConfigReader, stringOf, booleanOf, textOf, stringsOf, NOT_MARKUP_TAGS;
9484
+ var SIGIL_SCOPES, SIGIL_CHARS, grammar_shared_default, LB, RB, SIGIL_SCOPES$1, withSelectors, VOID_TAGS, BRANCH_PARENT$1, LINK_SEPARATORS, NO_SELECTORS, EMAIL_AUTOLINK, peg$SyntaxError, NameMap, NameSet, BLOCK_MACROS, BLOCK_WIDGETS, RAW_BODY_MACROS, BRANCHING, quoteArg, last$1, BRANCH_PARENT, isBranchTag, lowerName, nameOf, closerOf, closes, astralIdentifierCodes, astralIdentifierStartCodes, nonASCIIidentifierChars, nonASCIIidentifierStartChars, reservedWords, ecma5AndLessKeywords, keywords$1, keywordRelationalOperator, nonASCIIidentifierStart, nonASCIIidentifier, TokenType, beforeExpr, startsExpr, keywords, types$1, lineBreak, lineBreakG, nonASCIIwhitespace, skipWhiteSpace, ref, hasOwnProperty, toString, hasOwn$1, isArray, regexpCache, loneSurrogate, Position, SourceLocation, defaultOptions, warnedAboutEcmaVersion, SCOPE_TOP, SCOPE_FUNCTION, SCOPE_ASYNC, SCOPE_GENERATOR, SCOPE_ARROW, SCOPE_SIMPLE_CATCH, SCOPE_SUPER, SCOPE_DIRECT_SUPER, SCOPE_CLASS_STATIC_BLOCK, SCOPE_CLASS_FIELD_INIT, SCOPE_SWITCH, SCOPE_VAR, BIND_NONE, BIND_VAR, BIND_LEXICAL, BIND_FUNCTION, BIND_SIMPLE_CATCH, BIND_OUTSIDE, Parser, prototypeAccessors, pp$9, literal, DestructuringErrors, pp$8, loopLabel, switchLabel, empty$1, FUNC_STATEMENT, FUNC_HANGING_STATEMENT, FUNC_NULLABLE_ID, pp$7, TokContext, types, pp$6, pp$5, empty, pp$4, pp$3, Scope, Node, pp$2, scriptValuesAddedInUnicode, ecma9BinaryProperties, ecma10BinaryProperties, ecma11BinaryProperties, ecma12BinaryProperties, ecma13BinaryProperties, unicodeBinaryProperties, unicodeBinaryPropertiesOfStrings, unicodeGeneralCategoryValues, ecma9ScriptValues, ecma10ScriptValues, ecma11ScriptValues, ecma12ScriptValues, ecma13ScriptValues, unicodeScriptValues, data, ecmaVersion, i, list, pp$1, BranchID, RegExpValidationState, CharSetNone, CharSetOk, CharSetString, Token, pp, INVALID_TEMPLATE_ESCAPE_ERROR, OPTIONS, AT_NAME_RE, TRANS_NAME_RE, SIGIL_IDENT_RE, LINE_BREAK_RE, NOT_A_BODY_RE, OPERAND_ENDS, SigilParser, OPENERS, CLOSERS$1, CodeSyntaxError, last, CLOSER, isNode, NAMESPACE_NAME, WORD_RE$2, FLAGS_RE, WORDS_BEFORE_STRING, NON_STRING_QUOTE_PREFIX, memoInput, memo, defaultCodeEnd, defaultHooks, MarkupError, OFFSET_FIELD, listeners, TRAILING_OPERATOR_RE, slice, MacroArgumentError, WORD_RE$1, NUMBER_RE$1, WORD_OR_PUNCT_RE, LEADING_OPERATOR_RE, takesRest, PARAMETER_TYPES, STRING_HOLDS, metadataRegistry, SUB_MACRO_PARAMETERS, RESERVED_NAME, isArrayIndex, hasOwn, createNamespace, parses, CONDITION_MACROS, PASSAGE_BODIES, holdsByDefault, JS_ESCAPE_RE, textPiece, registeredParameters, WIDGET_PARAMETERS, NOT_MARKUP, NOT_MARKUP_TAGS$1, BRANCH_MACROS, didYouMean, PASSAGE_ARGUMENT_HINT, BARE_WORD_RE, NUMBER_RE, IDENT_RE, KEY_RE, ARROW_RE, LINE_END_RE, ARRAY_INDEX_RE, UNSUPPORTED_UNDEFINED, Reader, VAR_PATH_RE, QUOTED_VAR_ARG_RE, VALID_VAR_TYPES, PRIMITIVE_SAMPLES, UNSUPPORTED_TYPE, NO_STORE_VAR_MACROS, FIELD_PATH_RE, CLOSERS, BLOCK_WORDS, NOT_PARAMETERS, DECLARATIONS, WRITE_RE, MUTATORS, MUTATING_CALL_RE, BEFORE_BINDING, isWordChar, Bindings, CALL_RE, IDENTIFIER_ARGUMENT_RE, WORD_RE, MODIFIER_RE, KEY_START_RE, CONFIG_KEYS, ConfigReader, stringOf, booleanOf, textOf, stringsOf, VOID_ELEMENTS, DEFAULT_PRETTIER_OPTIONS, SVG_PLACEHOLDERS, SPINDLE_PLACEHOLDERS, DEFAULT_DEDENTING, WHITESPACE_AS_WRITTEN, LIST_ITEM_REGEX, CODE_FENCE_REGEX, NOT_MARKUP_TAGS;
8730
9485
  var init_story_variables = __esm({
8731
9486
  "node_modules/@rohal12/spindle/dist/pkg/story-variables.js"() {
8732
9487
  SIGIL_SCOPES = {
@@ -8891,6 +9646,9 @@ var init_story_variables = __esm({
8891
9646
  delete(name) {
8892
9647
  this.names.delete(name);
8893
9648
  }
9649
+ clear() {
9650
+ this.names.clear();
9651
+ }
8894
9652
  };
8895
9653
  BLOCK_MACROS = new NameSet([
8896
9654
  "if",
@@ -8908,6 +9666,7 @@ var init_story_variables = __esm({
8908
9666
  "span",
8909
9667
  "nobr"
8910
9668
  ]);
9669
+ BLOCK_WIDGETS = new NameSet();
8911
9670
  RAW_BODY_MACROS = /* @__PURE__ */ new Set(["do"]);
8912
9671
  BRANCHING = /* @__PURE__ */ new Set([
8913
9672
  "if",
@@ -10085,7 +10844,7 @@ var init_story_variables = __esm({
10085
10844
  ref = Object.prototype;
10086
10845
  hasOwnProperty = ref.hasOwnProperty;
10087
10846
  toString = ref.toString;
10088
- hasOwn = Object.hasOwn || (function(obj, propName) {
10847
+ hasOwn$1 = Object.hasOwn || (function(obj, propName) {
10089
10848
  return hasOwnProperty.call(obj, propName);
10090
10849
  });
10091
10850
  isArray = Array.isArray || (function(obj) {
@@ -11015,7 +11774,7 @@ var init_story_variables = __esm({
11015
11774
  var parent = len === 0 ? null : this.privateNameStack[len - 1];
11016
11775
  for (var i2 = 0; i2 < used.length; ++i2) {
11017
11776
  var id = used[i2];
11018
- if (!hasOwn(declared, id.name)) {
11777
+ if (!hasOwn$1(declared, id.name)) {
11019
11778
  if (parent) parent.used.push(id);
11020
11779
  else this.raiseRecoverable(id.start, "Private field '#" + id.name + "' must be declared in an enclosing class");
11021
11780
  }
@@ -11093,7 +11852,7 @@ var init_story_variables = __esm({
11093
11852
  pp$8.checkExport = function(exports$1, name, pos) {
11094
11853
  if (!exports$1) return;
11095
11854
  if (typeof name !== "string") name = name.type === "Identifier" ? name.name : name.value;
11096
- if (hasOwn(exports$1, name)) this.raiseRecoverable(pos, "Duplicate export '" + name + "'");
11855
+ if (hasOwn$1(exports$1, name)) this.raiseRecoverable(pos, "Duplicate export '" + name + "'");
11097
11856
  exports$1[name] = true;
11098
11857
  };
11099
11858
  pp$8.checkPatternExport = function(exports$1, pat) {
@@ -11212,7 +11971,7 @@ var init_story_variables = __esm({
11212
11971
  } else first = false;
11213
11972
  var attr = this.parseImportAttribute();
11214
11973
  var keyName = attr.key.type === "Identifier" ? attr.key.name : attr.key.value;
11215
- if (hasOwn(attributeKeys, keyName)) this.raiseRecoverable(attr.key.start, "Duplicate attribute key '" + keyName + "'");
11974
+ if (hasOwn$1(attributeKeys, keyName)) this.raiseRecoverable(attr.key.start, "Duplicate attribute key '" + keyName + "'");
11216
11975
  attributeKeys[keyName] = true;
11217
11976
  nodes.push(attr);
11218
11977
  }
@@ -11374,7 +12133,7 @@ var init_story_variables = __esm({
11374
12133
  if (isBind) {
11375
12134
  if (bindingType === BIND_LEXICAL && expr.name === "let") this.raiseRecoverable(expr.start, "let is disallowed as a lexically bound name");
11376
12135
  if (checkClashes) {
11377
- if (hasOwn(checkClashes, expr.name)) this.raiseRecoverable(expr.start, "Argument name clash");
12136
+ if (hasOwn$1(checkClashes, expr.name)) this.raiseRecoverable(expr.start, "Argument name clash");
11378
12137
  checkClashes[expr.name] = true;
11379
12138
  }
11380
12139
  if (bindingType !== BIND_OUTSIDE) this.declareName(expr.name, bindingType, expr.start);
@@ -13034,7 +13793,7 @@ var init_story_variables = __esm({
13034
13793
  return CharSetNone;
13035
13794
  };
13036
13795
  pp$1.regexp_validateUnicodePropertyNameAndValue = function(state, name, value) {
13037
- if (!hasOwn(state.unicodeProperties.nonBinary, name)) state.raise("Invalid property name");
13796
+ if (!hasOwn$1(state.unicodeProperties.nonBinary, name)) state.raise("Invalid property name");
13038
13797
  if (!state.unicodeProperties.nonBinary[name].test(value)) state.raise("Invalid property value");
13039
13798
  };
13040
13799
  pp$1.regexp_validateUnicodePropertyNameOrValue = function(state, nameOrValue) {
@@ -14026,15 +14785,15 @@ var init_story_variables = __esm({
14026
14785
  }
14027
14786
  const self = this;
14028
14787
  const prevType = self.type;
14029
- this.prevValue = self.value;
14030
- const property = type.keyword !== void 0 && (prevType === types$1.dot || prevType === types$1.questionDot) || (type === types$1._function || type === types$1._class) && notABody(self.input, self.pos);
14788
+ this.prevValue = isMemberDot(this.typeBeforeLast) ? void 0 : self.value;
14789
+ const property = type.keyword !== void 0 && isMemberDot(prevType) || (type === types$1._function || type === types$1._class) && notABody(self.input, self.pos);
14031
14790
  if (!property && (type === types$1._function || type === types$1._class)) {
14032
14791
  const before = type === types$1._function && prevType === types$1.name && this.prevValue === "async" && !this.breakHere ? this.typeBeforeLast : prevType;
14033
14792
  self.type = before === types$1.colon && this.colonEndsTernary ? types$1.parenL : before;
14034
14793
  }
14035
14794
  this.typeBeforeLast = prevType;
14036
14795
  super.finishToken(property ? types$1.name : type, value);
14037
- if (type === types$1.name && value === "of" && (prevType === types$1._const || prevType === types$1._var || this.prevValue === "let")) self.exprAllowed = false;
14796
+ if (type === types$1.name && (isMemberDot(prevType) || value === "of" && (prevType === types$1._const || prevType === types$1._var || this.prevValue === "let"))) self.exprAllowed = false;
14038
14797
  }
14039
14798
  braceIsBlock(prevType) {
14040
14799
  if (prevType === types$1.colon && this.colonEndsTernary) return false;
@@ -14054,7 +14813,7 @@ var init_story_variables = __esm({
14054
14813
  types$1.braceL,
14055
14814
  types$1.dollarBraceL
14056
14815
  ]);
14057
- CLOSERS = /* @__PURE__ */ new Set([
14816
+ CLOSERS$1 = /* @__PURE__ */ new Set([
14058
14817
  types$1.parenR,
14059
14818
  types$1.bracketR,
14060
14819
  types$1.braceR
@@ -14106,28 +14865,7 @@ var init_story_variables = __esm({
14106
14865
  NON_STRING_QUOTE_PREFIX = /[\p{L}\p{N}_\\]/u;
14107
14866
  memoInput = "";
14108
14867
  memo = /* @__PURE__ */ new Map();
14109
- defaultCodeEnd = {
14110
- closeBrace(input, codeStart, lenientStart) {
14111
- const end = findCodeEnd(input, codeStart);
14112
- return end !== -1 ? end : lenientClose(input, lenientStart);
14113
- },
14114
- rawBodyEnd(input, bodyStart, name) {
14115
- const closer = `\\{/${name}\\s*\\}`;
14116
- const first = new RegExp(closer, "gi");
14117
- first.lastIndex = bodyStart;
14118
- const firstAt = first.exec(input)?.index ?? -1;
14119
- if (firstAt === -1) return -1;
14120
- const at3 = new RegExp(closer, "iy");
14121
- const end = findCodeEnd(input, bodyStart, {
14122
- goal: "statements",
14123
- stop: (k2) => {
14124
- at3.lastIndex = k2;
14125
- return at3.test(input);
14126
- }
14127
- });
14128
- return end === -1 ? firstAt : end;
14129
- }
14130
- };
14868
+ defaultCodeEnd = jsCodeEnd();
14131
14869
  defaultHooks = {
14132
14870
  ...defaultCodeEnd,
14133
14871
  isBlock: isBlockMacro,
@@ -14163,28 +14901,6 @@ var init_story_variables = __esm({
14163
14901
  WORD_OR_PUNCT_RE = /\S+/y;
14164
14902
  LEADING_OPERATOR_RE = /^(?:[-+*/&|^=<>?:,.]|%(?![A-Za-z_]))/;
14165
14903
  takesRest = (param) => param.type === "expression" || param.type === "statements" || param.type === "passage" || param.type === "text";
14166
- SUB_MACRO_PARAMETERS = /* @__PURE__ */ new Map([["option", [{
14167
- name: "value",
14168
- type: "string",
14169
- holds: "text",
14170
- required: true
14171
- }]]]);
14172
- parses = null;
14173
- CONDITION_MACROS = /* @__PURE__ */ new Set([
14174
- "if",
14175
- "elseif",
14176
- "case"
14177
- ]);
14178
- PASSAGE_BODIES = /* @__PURE__ */ new Set(["dialog"]);
14179
- holdsByDefault = (interpolate) => interpolate ? "markup" : "text";
14180
- textPiece = (text2, offset2, where2) => ({
14181
- kind: "text",
14182
- text: text2,
14183
- offset: offset2,
14184
- where: where2,
14185
- tokens: [],
14186
- errors: []
14187
- });
14188
14904
  new NameMap();
14189
14905
  new NameMap();
14190
14906
  new NameSet();
@@ -14210,6 +14926,34 @@ var init_story_variables = __esm({
14210
14926
  "statements"
14211
14927
  ];
14212
14928
  metadataRegistry = new NameMap();
14929
+ SUB_MACRO_PARAMETERS = /* @__PURE__ */ new Map([["option", [{
14930
+ name: "value",
14931
+ type: "string",
14932
+ holds: "text",
14933
+ required: true
14934
+ }]]]);
14935
+ RESERVED_NAME = "__proto__";
14936
+ isArrayIndex = (key) => /^(?:0|[1-9]\d*)$/.test(key);
14937
+ hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
14938
+ createNamespace = (...sources) => ownEntries(sources);
14939
+ Object.freeze(createNamespace());
14940
+ parses = null;
14941
+ CONDITION_MACROS = /* @__PURE__ */ new Set([
14942
+ "if",
14943
+ "elseif",
14944
+ "case"
14945
+ ]);
14946
+ PASSAGE_BODIES = /* @__PURE__ */ new Set(["dialog"]);
14947
+ holdsByDefault = (interpolate) => interpolate ? "markup" : "text";
14948
+ JS_ESCAPE_RE = /\\(?:u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|[0-3][0-7]{0,2}|[4-7][0-7]?|\r\n|[^])/y;
14949
+ textPiece = (text2, offset2, where2) => ({
14950
+ kind: "text",
14951
+ text: text2,
14952
+ offset: offset2,
14953
+ where: where2,
14954
+ tokens: [],
14955
+ errors: []
14956
+ });
14213
14957
  registeredParameters = (name) => parameterLookup(getMacroRegistry())(name);
14214
14958
  WIDGET_PARAMETERS = [{
14215
14959
  name: "name",
@@ -14237,22 +14981,26 @@ var init_story_variables = __esm({
14237
14981
  didYouMean = (best, quote) => best ? ` Did you mean ${quote(best)}?` : "";
14238
14982
  PASSAGE_ARGUMENT_HINT = " (a passage name is a quoted string or an expression)";
14239
14983
  BARE_WORD_RE = /^[A-Za-z][\w$]*$/;
14240
- RESERVED_NAME = "__proto__";
14241
- createNamespace = (...sources) => ownEntries(sources);
14242
- Object.freeze(createNamespace());
14243
- NUMBER_RE = /(?:0[xX][\da-fA-F_]+|0[bB][01_]+|0[oO][0-7_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d+)?)(n?)/y;
14984
+ NUMBER_RE = /(?:0[xX][\da-fA-F_]+|0[bB][01_]+|0[oO][0-7_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d[\d_]*)?)(n?)/y;
14244
14985
  IDENT_RE = /[A-Za-z_$][\w$]*/y;
14245
14986
  KEY_RE = /[\w$]+/y;
14246
14987
  ARROW_RE = /(?:async\s+)?(?:\((?:[^()]|\([^()]*\))*\)|[A-Za-z_$][\w$]*)\s*=>/y;
14988
+ LINE_END_RE = /[\n\r\u2028\u2029]/g;
14247
14989
  ARRAY_INDEX_RE = /^(?:0|[1-9]\d{0,8})$/;
14248
14990
  UNSUPPORTED_UNDEFINED = {
14249
14991
  unsupported: "undefined",
14250
14992
  value: "undefined"
14251
14993
  };
14252
14994
  Reader = class {
14253
- constructor(src) {
14995
+ /**
14996
+ * `regexes` (see regexLiterals) are read from `src` when they are first
14997
+ * needed, unless the caller has them already.
14998
+ */
14999
+ constructor(src, options = {}) {
14254
15000
  this.src = src;
14255
15001
  this.i = 0;
15002
+ this.goal = options.goal ?? "expression";
15003
+ this.regexes = options.regexes;
14256
15004
  }
14257
15005
  /** Skip whitespace and comments. */
14258
15006
  space() {
@@ -14260,8 +15008,8 @@ var init_story_variables = __esm({
14260
15008
  for (; ; ) {
14261
15009
  while (/\s/.test(src.charAt(this.i))) this.i++;
14262
15010
  if (src.startsWith("//", this.i)) {
14263
- const end = src.indexOf("\n", this.i);
14264
- this.i = end < 0 ? src.length : end;
15011
+ LINE_END_RE.lastIndex = this.i;
15012
+ this.i = LINE_END_RE.test(src) ? LINE_END_RE.lastIndex - 1 : src.length;
14265
15013
  } else if (src.startsWith("/*", this.i)) {
14266
15014
  const end = src.indexOf("*/", this.i + 2);
14267
15015
  this.i = end < 0 ? src.length : end + 2;
@@ -14320,10 +15068,9 @@ var init_story_variables = __esm({
14320
15068
  i2++;
14321
15069
  while (/\s/.test(src.charAt(i2))) i2++;
14322
15070
  }
14323
- NUMBER_RE.lastIndex = i2;
14324
- const match = NUMBER_RE.exec(src);
15071
+ const match = this.numberAt(i2);
14325
15072
  if (match) {
14326
- this.i = NUMBER_RE.lastIndex;
15073
+ this.i = i2 + match[0].length;
14327
15074
  if (!match[1]) return { shape: { type: "number" } };
14328
15075
  if (sign === "+") return { throws: "Cannot convert a BigInt value to a number" };
14329
15076
  const digits = bigintValue(match[0].slice(0, -1));
@@ -14333,11 +15080,17 @@ var init_story_variables = __esm({
14333
15080
  };
14334
15081
  }
14335
15082
  IDENT_RE.lastIndex = i2;
14336
- if (IDENT_RE.exec(src)?.[0] === "Infinity") {
15083
+ const word = IDENT_RE.exec(src)?.[0];
15084
+ if (word === "Infinity" || word === "NaN") {
14337
15085
  this.i = IDENT_RE.lastIndex;
14338
15086
  return { shape: { type: "number" } };
14339
15087
  }
14340
15088
  }
15089
+ /** The numeric literal (a BigInt's `n` is `match[1]`) at `at`, if there is one. */
15090
+ numberAt(at3) {
15091
+ NUMBER_RE.lastIndex = at3;
15092
+ return NUMBER_RE.exec(this.src);
15093
+ }
14341
15094
  /** A keyword, a function, or a name (which is not static). */
14342
15095
  word() {
14343
15096
  const { src } = this;
@@ -14357,6 +15110,7 @@ var init_story_variables = __esm({
14357
15110
  this.i = end;
14358
15111
  return { shape: { type: "null" } };
14359
15112
  case "NaN":
15113
+ case "Infinity":
14360
15114
  this.i = end;
14361
15115
  return { shape: { type: "number" } };
14362
15116
  case "undefined":
@@ -14498,14 +15252,22 @@ var init_story_variables = __esm({
14498
15252
  const { src } = this;
14499
15253
  const c = src.charAt(this.i);
14500
15254
  if (c === '"' || c === "'") return this.stringValue();
15255
+ if (/[\d.]/.test(c)) return this.numberKey();
14501
15256
  KEY_RE.lastIndex = this.i;
14502
15257
  const found = KEY_RE.exec(src)?.[0];
14503
15258
  if (found === void 0) return void 0;
14504
15259
  this.i = KEY_RE.lastIndex;
14505
- if (!/^\d/.test(found)) return found;
14506
- const digits = found.replace(/_/g, "");
15260
+ return found;
15261
+ }
15262
+ /** A number as a key: the whole literal, named as the runtime names it. */
15263
+ numberKey() {
15264
+ const match = this.numberAt(this.i);
15265
+ if (!match) return void 0;
15266
+ this.i += match[0].length;
15267
+ if (/[\w$]/.test(this.src.charAt(this.i))) return void 0;
15268
+ const digits = match[0].replace(/_/g, "");
14507
15269
  if (/^0\d/.test(digits)) return void 0;
14508
- if (digits.endsWith("n") && !/^0[xX]/.test(digits)) return bigintValue(digits.slice(0, -1));
15270
+ if (match[1]) return bigintValue(digits.slice(0, -1));
14509
15271
  const value = Number(digits);
14510
15272
  return Number.isNaN(value) ? void 0 : String(value);
14511
15273
  }
@@ -14548,6 +15310,14 @@ var init_story_variables = __esm({
14548
15310
  this.space();
14549
15311
  return true;
14550
15312
  }
15313
+ if (c === "/") {
15314
+ this.regexes ?? (this.regexes = regexLiterals(src, this.goal));
15315
+ const end = this.regexes.get(this.i);
15316
+ if (end !== void 0) {
15317
+ this.i = end;
15318
+ return true;
15319
+ }
15320
+ }
14551
15321
  if (c === "[" || c === "{" || c === "(") return this.skipBalanced();
14552
15322
  if (c === "]" || c === "}" || c === ")") return false;
14553
15323
  this.i++;
@@ -14585,9 +15355,470 @@ var init_story_variables = __esm({
14585
15355
  };
14586
15356
  UNSUPPORTED_TYPE = "Expected number, string, boolean, array, or object.";
14587
15357
  NO_STORE_VAR_MACROS = /* @__PURE__ */ new Set();
15358
+ FIELD_PATH_RE = /(?:\.\w+)*/y;
15359
+ CLOSERS = {
15360
+ "}": "{",
15361
+ ")": "(",
15362
+ "]": "["
15363
+ };
15364
+ BLOCK_WORDS = /* @__PURE__ */ new Set([
15365
+ "if",
15366
+ "for",
15367
+ "while",
15368
+ "switch",
15369
+ "with",
15370
+ "catch"
15371
+ ]);
15372
+ NOT_PARAMETERS = /* @__PURE__ */ new Set([
15373
+ "if",
15374
+ "for",
15375
+ "while",
15376
+ "switch",
15377
+ "with"
15378
+ ]);
15379
+ DECLARATIONS = /* @__PURE__ */ new Set([
15380
+ "const",
15381
+ "let",
15382
+ "var"
15383
+ ]);
15384
+ WRITE_RE = /(?:(?:\*\*|<<|>>>?|&&|\|\||\?\?|[-+*/%&|^])=(?!=)|=(?![=>])|\+\+|--)/y;
15385
+ MUTATORS = /* @__PURE__ */ new Set([
15386
+ "push",
15387
+ "pop",
15388
+ "shift",
15389
+ "unshift",
15390
+ "splice",
15391
+ "sort",
15392
+ "reverse",
15393
+ "fill",
15394
+ "copyWithin"
15395
+ ]);
15396
+ MUTATING_CALL_RE = /(?:Object|Reflect)\s*\.\s*(?:assign|defineProperty|defineProperties|setPrototypeOf|set|deleteProperty)\s*$/;
15397
+ BEFORE_BINDING = {
15398
+ "{": "{,:",
15399
+ "[": "[,",
15400
+ "(": "(,"
15401
+ };
15402
+ isWordChar = (c) => !!c && /[\w$]/.test(c);
15403
+ Bindings = class {
15404
+ constructor(code) {
15405
+ this.code = code;
15406
+ this.groups = [];
15407
+ this.byOpen = /* @__PURE__ */ new Map();
15408
+ this.byClose = /* @__PURE__ */ new Map();
15409
+ this.functions = /* @__PURE__ */ new Map();
15410
+ this.top = {
15411
+ start: -1,
15412
+ end: Infinity
15413
+ };
15414
+ this.readGroups();
15415
+ }
15416
+ /**
15417
+ * The `{` of the object literal that `name`, used at `at`, is declared as
15418
+ * (`const`, `let` or `var`), when that is the declaration it names: the
15419
+ * innermost one in the scope of the use, that comes before it unless the
15420
+ * use is in a function of its own. `undefined` if it names another value,
15421
+ * more than one declaration is the innermost, or the value is written to
15422
+ * (given another, or one of its `properties` set) where the use can follow.
15423
+ */
15424
+ objectOf(name, at3, properties) {
15425
+ const { code } = this;
15426
+ const escaped = name.replace(/\$/g, "\\$");
15427
+ const bindings = [];
15428
+ for (const m of code.matchAll(new RegExp(`(?<![\\w$])${escaped}(?![\\w$])`, "g"))) {
15429
+ const binding = this.bindingAt(m.index, name.length, properties);
15430
+ if (binding) bindings.push(binding);
15431
+ }
15432
+ const declarations = bindings.filter((b) => !b.write);
15433
+ for (const b of bindings) if (b.write) b.scope = this.innermostVisible(declarations, b.at)[0]?.scope ?? this.top;
15434
+ const found = this.innermostVisible(bindings, at3);
15435
+ return found.length === 1 ? found[0].object : void 0;
15436
+ }
15437
+ /**
15438
+ * Of the `bindings` the use at `at` can see, those in the innermost scope:
15439
+ * the scope is the use's, and a binding comes before it, unless the use is
15440
+ * in a function of its own.
15441
+ */
15442
+ innermostVisible(bindings, at3) {
15443
+ const useFunction = this.functionStart(at3);
15444
+ const visible = bindings.filter((b) => b.scope.start <= at3 && at3 < b.scope.end && (!b.ordered || b.at < at3 || this.functionStart(b.at) !== useFunction));
15445
+ const innermost = Math.max(...visible.map((b) => b.scope.start));
15446
+ return visible.filter((b) => b.scope.start === innermost);
15447
+ }
15448
+ readGroups() {
15449
+ const { code, groups } = this;
15450
+ const stack = [];
15451
+ for (let i2 = 0; i2 < code.length; i2++) {
15452
+ const c = code.charAt(i2);
15453
+ if (c === "{" || c === "(" || c === "[") {
15454
+ const group = {
15455
+ kind: c,
15456
+ open: i2,
15457
+ close: code.length,
15458
+ parent: stack[stack.length - 1]
15459
+ };
15460
+ groups.push(group);
15461
+ stack.push(group);
15462
+ this.byOpen.set(i2, group);
15463
+ } else if (CLOSERS[c]) {
15464
+ let k2 = stack.length - 1;
15465
+ while (k2 >= 0 && stack[k2].kind !== CLOSERS[c]) k2--;
15466
+ if (k2 >= 0) {
15467
+ stack[k2].close = i2;
15468
+ this.byClose.set(i2, stack[k2]);
15469
+ stack.length = k2;
15470
+ }
15471
+ }
15472
+ }
15473
+ }
15474
+ /** The innermost group that has `index` inside it, of this kind if given. */
15475
+ innermost(index, kind) {
15476
+ const { groups } = this;
15477
+ for (let k2 = groups.length - 1; k2 >= 0; k2--) {
15478
+ const g = groups[k2];
15479
+ if (g.open < index && index < g.close && (!kind || g.kind === kind)) return g;
15480
+ }
15481
+ }
15482
+ /**
15483
+ * The word before a `(` group (`if`, `for`, `catch`, a function's name…);
15484
+ * `for await` is `for`.
15485
+ */
15486
+ ownerWord(paren) {
15487
+ const word = this.wordBefore(this.backFrom(paren.open) + 1);
15488
+ if (word.text !== "await") return word.text;
15489
+ const before = this.wordBefore(this.backFrom(word.start) + 1).text;
15490
+ return before === "for" ? before : word.text;
15491
+ }
15492
+ /**
15493
+ * Whether the `{` group is the body of a function (or method), or of a
15494
+ * class static block, which has the scope of a `var` of its own.
15495
+ */
15496
+ isFunctionBody(group) {
15497
+ if (group.kind !== "{") return false;
15498
+ let known = this.functions.get(group);
15499
+ if (known === void 0) {
15500
+ const before = this.backFrom(group.open);
15501
+ const { code } = this;
15502
+ if (code.charAt(before) === ")") {
15503
+ const params = this.byClose.get(before);
15504
+ known = !!params && !BLOCK_WORDS.has(this.ownerWord(params));
15505
+ } else known = code.slice(before - 1, before + 1) === "=>" || this.wordBefore(before + 1).text === "static";
15506
+ this.functions.set(group, known);
15507
+ }
15508
+ return known;
15509
+ }
15510
+ /** The start of the function that has `index` in its body, or -1. */
15511
+ functionStart(index) {
15512
+ for (let g = this.innermost(index, "{"); g; g = g.parent) if (g.kind === "{" && this.isFunctionBody(g)) return g.open;
15513
+ return -1;
15514
+ }
15515
+ scopeOf(group) {
15516
+ return group ? {
15517
+ start: group.open,
15518
+ end: group.close
15519
+ } : this.top;
15520
+ }
15521
+ /** The block `index` is in: what `let` and `const` declare for. */
15522
+ blockAt(index) {
15523
+ return this.scopeOf(this.innermost(index, "{"));
15524
+ }
15525
+ /** The function (or the whole text) `index` is in: what `var` declares for. */
15526
+ functionAt(index) {
15527
+ const start = this.functionStart(index);
15528
+ return start < 0 ? this.top : this.scopeOf(this.byOpen.get(start));
15529
+ }
15530
+ /**
15531
+ * What the declaration keyword `word` (`var`, `let`, `const`) declares for:
15532
+ * the function for `var`, the block for the others, or the `for` statement,
15533
+ * header and body, when the declaration is in its header.
15534
+ */
15535
+ declaredIn(word) {
15536
+ if (word.text === "var") return this.functionAt(word.start);
15537
+ const group = this.innermost(word.start);
15538
+ if (group?.kind === "(" && this.ownerWord(group) === "for") return {
15539
+ start: group.open,
15540
+ end: this.statementEnd(group.close + 1)
15541
+ };
15542
+ return this.blockAt(word.start);
15543
+ }
15544
+ /**
15545
+ * Where the statement that starts at `from` ends: a block, the body of an
15546
+ * `if`, `for`, `while` or `with` (and the `else` of an `if`), or else up to
15547
+ * a `;`, the closer of the group it is in, or a line break that is followed
15548
+ * by a statement of its own.
15549
+ */
15550
+ statementEnd(from) {
15551
+ const { code } = this;
15552
+ const start = this.forward(from);
15553
+ const block = this.byOpen.get(start);
15554
+ if (block?.kind === "{") return Math.min(block.close + 1, code.length);
15555
+ const word = this.wordAt(start);
15556
+ if (/^(?:if|for|while|with)$/.test(word)) {
15557
+ let paren = this.forward(start + word.length);
15558
+ if (word === "for" && this.wordAt(paren) === "await") paren = this.forward(paren + 5);
15559
+ const header = this.byOpen.get(paren);
15560
+ if (header) {
15561
+ const end = this.statementEnd(header.close + 1);
15562
+ const next = this.forward(end);
15563
+ return word === "if" && this.wordAt(next) === "else" ? this.statementEnd(next + 4) : end;
15564
+ }
15565
+ }
15566
+ for (let end = start; end < code.length; end++) {
15567
+ const c = code.charAt(end);
15568
+ if (c === ";") return end + 1;
15569
+ if (CLOSERS[c]) return end;
15570
+ const group = this.byOpen.get(end);
15571
+ if (group) end = group.close;
15572
+ else if (c === "\n" && this.startsStatement(end)) return end;
15573
+ }
15574
+ return code.length;
15575
+ }
15576
+ /** Whether the line break at `index` ends a statement, and one follows. */
15577
+ startsStatement(index) {
15578
+ const next = this.forward(index);
15579
+ return /[\w$)\]}]/.test(this.code.charAt(this.backFrom(index))) && isWordChar(this.code.charAt(next)) && !/^(?:in|of|instanceof|else)$/.test(this.wordAt(next));
15580
+ }
15581
+ /** What a parameter list, whose `)` is `close`, is the scope of: the body. */
15582
+ bodyAfter(close) {
15583
+ const next = this.forward(close + 1);
15584
+ if (this.code.charAt(next) === "{") return this.scopeOf(this.byOpen.get(next));
15585
+ return this.code.startsWith("=>", next) ? this.arrowBody(next + 2) : void 0;
15586
+ }
15587
+ /** The body of an arrow function, whose `=>` ends before `from`. */
15588
+ arrowBody(from) {
15589
+ const { code } = this;
15590
+ const start = this.forward(from);
15591
+ if (code.charAt(start) === "{") return this.scopeOf(this.byOpen.get(start));
15592
+ let end = start;
15593
+ while (end < code.length && !",;)]}".includes(code.charAt(end))) {
15594
+ end = this.byOpen.get(end)?.close ?? end;
15595
+ end++;
15596
+ }
15597
+ return {
15598
+ start,
15599
+ end
15600
+ };
15601
+ }
15602
+ /** The index of the last character that is no space before `index`, or -1. */
15603
+ backFrom(index) {
15604
+ let i2 = index - 1;
15605
+ while (i2 >= 0 && /\s/.test(this.code.charAt(i2))) i2--;
15606
+ return i2;
15607
+ }
15608
+ /** The index of the first character that is no space from `index` on. */
15609
+ forward(index) {
15610
+ let i2 = index;
15611
+ while (i2 < this.code.length && /\s/.test(this.code.charAt(i2))) i2++;
15612
+ return i2;
15613
+ }
15614
+ /** The word that starts at `index` (empty if there is none). */
15615
+ wordAt(index) {
15616
+ return this.code.slice(index, this.nameEnd(index));
15617
+ }
15618
+ /** The word that ends before `end`, and where it starts. */
15619
+ wordBefore(end) {
15620
+ let start = end;
15621
+ while (start > 0 && isWordChar(this.code.charAt(start - 1))) start--;
15622
+ return {
15623
+ text: this.code.slice(start, end),
15624
+ start
15625
+ };
15626
+ }
15627
+ /**
15628
+ * What the name of `length` characters at `at` declares, or writes to, if it
15629
+ * does. A write to a `properties` of it counts, one to another does not.
15630
+ */
15631
+ bindingAt(at3, length, properties) {
15632
+ const { code } = this;
15633
+ let before = this.backFrom(at3);
15634
+ const spread = code.slice(before - 2, before + 1) === "...";
15635
+ if (code.charAt(before) === "." && !spread) return void 0;
15636
+ if (spread) before = this.backFrom(before - 2);
15637
+ const after = this.forward(at3 + length);
15638
+ return this.declarationAt(at3, before, spread) ?? this.patternAt(at3, before, after) ?? this.arrowParameterAt(at3, after) ?? (spread ? void 0 : this.writeAt(at3, before, after, properties));
15639
+ }
15640
+ /** `const x`, `let x`, `var x`, a later `y` of `let x, y`, `function x`, `class x`. */
15641
+ declarationAt(at3, before, spread) {
15642
+ if (spread) return void 0;
15643
+ const word = this.code.charAt(before) === "," ? this.listKeyword(before) : this.wordBefore(before + 1);
15644
+ if (!word || this.code.charAt(word.start - 1) === ".") return void 0;
15645
+ if (DECLARATIONS.has(word.text)) {
15646
+ const scope = this.declaredIn(word);
15647
+ const init = /\s*=\s*(?=\{)/y;
15648
+ init.lastIndex = this.nameEnd(at3);
15649
+ return {
15650
+ scope,
15651
+ at: at3,
15652
+ ordered: true,
15653
+ object: init.test(this.code) ? init.lastIndex : void 0
15654
+ };
15655
+ }
15656
+ if (word.text === "function" || word.text === "class") return {
15657
+ scope: this.blockAt(word.start),
15658
+ at: at3,
15659
+ ordered: word.text === "class"
15660
+ };
15661
+ }
15662
+ /**
15663
+ * The `const`, `let` or `var` of the declaration list that the `,` at
15664
+ * `comma` is in (`let a = 1, b`, also in a `for` header): the nearest one
15665
+ * before it in the same brackets, that no `;` is between.
15666
+ */
15667
+ listKeyword(comma) {
15668
+ const { code } = this;
15669
+ const floor = this.innermost(comma)?.open ?? -1;
15670
+ for (let i2 = comma - 1; i2 > floor; i2--) {
15671
+ const c = code.charAt(i2);
15672
+ if (c === ";") return void 0;
15673
+ const group = CLOSERS[c] ? this.byClose.get(i2) : void 0;
15674
+ if (group) i2 = group.open;
15675
+ else if (isWordChar(c)) {
15676
+ const word = this.wordBefore(i2 + 1);
15677
+ if (DECLARATIONS.has(word.text) && code.charAt(word.start - 1) !== ".") return word;
15678
+ i2 = word.start;
15679
+ }
15680
+ }
15681
+ }
15682
+ nameEnd(at3) {
15683
+ let end = at3;
15684
+ while (isWordChar(this.code.charAt(end))) end++;
15685
+ return end;
15686
+ }
15687
+ /**
15688
+ * A name in a pattern (`const { x } = …`) or a parameter list; or one a
15689
+ * pattern assigns to (`({ x } = …)`).
15690
+ */
15691
+ patternAt(at3, before, after) {
15692
+ const { code } = this;
15693
+ const inner = this.innermost(at3);
15694
+ if (!inner || !BEFORE_BINDING[inner.kind].includes(code.charAt(before))) return;
15695
+ if (inner.kind === "{" && code.charAt(after) === ":") return void 0;
15696
+ let pattern;
15697
+ for (let group = inner; group; group = group.parent) {
15698
+ if (group.kind === "(") {
15699
+ const scope = NOT_PARAMETERS.has(this.ownerWord(group)) ? void 0 : this.bodyAfter(group.close);
15700
+ return scope ? {
15701
+ scope,
15702
+ at: at3,
15703
+ ordered: false
15704
+ } : this.patternWriteAt(at3, pattern);
15705
+ }
15706
+ pattern = group;
15707
+ const open = this.backFrom(group.open);
15708
+ const word = code.charAt(open) === "," ? this.listKeyword(open) : this.wordBefore(open + 1);
15709
+ if (word && DECLARATIONS.has(word.text)) return {
15710
+ scope: this.declaredIn(word),
15711
+ at: at3,
15712
+ ordered: true
15713
+ };
15714
+ if (!"({[,:".includes(code.charAt(open))) break;
15715
+ }
15716
+ return this.patternWriteAt(at3, pattern);
15717
+ }
15718
+ /** `[x] = …`, `({ x } = …)`, `for ({ x } of …)`: a name that is assigned to. */
15719
+ patternWriteAt(at3, pattern) {
15720
+ if (!pattern) return void 0;
15721
+ const { code } = this;
15722
+ const open = this.backFrom(pattern.open);
15723
+ if (/[\w$)\]]/.test(code.charAt(open))) return void 0;
15724
+ const next = this.forward(pattern.close + 1);
15725
+ return pattern.parent?.kind === "(" && this.ownerWord(pattern.parent) === "for" && /^(?:of|in)(?![\w$])/.test(code.slice(next)) || this.isWriteOperator(next) ? {
15726
+ scope: this.top,
15727
+ at: at3,
15728
+ ordered: true,
15729
+ write: true
15730
+ } : void 0;
15731
+ }
15732
+ /** `x => …`. */
15733
+ arrowParameterAt(at3, after) {
15734
+ return this.code.startsWith("=>", after) ? {
15735
+ scope: this.arrowBody(after + 2),
15736
+ at: at3,
15737
+ ordered: false
15738
+ } : void 0;
15739
+ }
15740
+ /** Whether an assignment, `++` or `--` operator is at `index`. */
15741
+ isWriteOperator(index) {
15742
+ WRITE_RE.lastIndex = index;
15743
+ return WRITE_RE.test(this.code);
15744
+ }
15745
+ /**
15746
+ * `x = …`, `x += …`, `x++`, `for (x of …)`, and the writes to what it
15747
+ * holds that change the config it is (`x.name = …`, `delete x.name`,
15748
+ * `x.parameters.push(…)`, `Object.assign(x, …)`): a value not known.
15749
+ */
15750
+ writeAt(at3, before, after, properties) {
15751
+ const { code } = this;
15752
+ if (this.isClassBody(this.innermost(at3))) return void 0;
15753
+ const { keys, end } = this.memberChain(after);
15754
+ const written2 = ["++", "--"].includes(code.slice(before - 1, before + 1)) || this.isWriteOperator(end) || keys.length > 0 && (this.wordBefore(before + 1).text === "delete" || keys.length > 1 && code.charAt(end) === "(" && MUTATORS.has(keys[keys.length - 1])) || keys.length === 0 && this.isForTarget(before, end) || keys.length === 0 && this.isMutatedArgument(before, end);
15755
+ const first = keys[0];
15756
+ if (!written2 || first && !properties.has(first)) return void 0;
15757
+ return {
15758
+ scope: this.top,
15759
+ at: at3,
15760
+ ordered: true,
15761
+ write: true
15762
+ };
15763
+ }
15764
+ /** Whether the group is the body of a class: its members are its own. */
15765
+ isClassBody(group) {
15766
+ if (group?.kind !== "{") return false;
15767
+ let start = group.open;
15768
+ while (start > 0 && !"{};".includes(this.code.charAt(start - 1))) start--;
15769
+ return /(?<![\w$.])class(?![\w$])/.test(this.code.slice(start, group.open));
15770
+ }
15771
+ /**
15772
+ * The properties read from a name at `from` (`.a.b[c]`, a key is null if
15773
+ * computed), and where the chain ends.
15774
+ */
15775
+ memberChain(from) {
15776
+ const { code } = this;
15777
+ const keys = [];
15778
+ let end = from;
15779
+ for (; ; ) {
15780
+ let i2 = end;
15781
+ if (code.startsWith("?.", i2)) i2 += 2;
15782
+ else if (code.charAt(i2) === "." && code.charAt(i2 + 1) !== ".") i2++;
15783
+ else if (code.charAt(i2) !== "[") break;
15784
+ i2 = this.forward(i2);
15785
+ const bracket = code.charAt(i2) === "[" ? this.byOpen.get(i2) : void 0;
15786
+ const key = bracket ? "" : this.wordAt(i2);
15787
+ if (!bracket && !key) break;
15788
+ keys.push(bracket ? null : key);
15789
+ end = this.forward(bracket ? bracket.close + 1 : i2 + key.length);
15790
+ }
15791
+ return {
15792
+ keys,
15793
+ end
15794
+ };
15795
+ }
15796
+ /** `for (x of …)`: the name is assigned each item. */
15797
+ isForTarget(before, end) {
15798
+ const group = this.byOpen.get(before);
15799
+ return group?.kind === "(" && this.ownerWord(group) === "for" && /^(?:of|in)(?![\w$])/.test(this.code.slice(end));
15800
+ }
15801
+ /** `Object.assign(x, …)` and the like: the name is the first argument. */
15802
+ isMutatedArgument(before, end) {
15803
+ const group = this.byOpen.get(before);
15804
+ return group?.kind === "(" && ",)".includes(this.code.charAt(end)) && MUTATING_CALL_RE.test(this.code.slice(0, group.open));
15805
+ }
15806
+ };
14588
15807
  CALL_RE = /(?<![\w$])defineMacro\s*\(\s*/g;
14589
15808
  IDENTIFIER_ARGUMENT_RE = /^([A-Za-z_$][\w$]*)\s*[,)]/;
14590
15809
  WORD_RE = /[A-Za-z_]+/y;
15810
+ MODIFIER_RE = /^(?:get|set|async)?$/;
15811
+ KEY_START_RE = /[\w$'"[.*]/;
15812
+ CONFIG_KEYS = /* @__PURE__ */ new Set([
15813
+ "name",
15814
+ "block",
15815
+ "subMacros",
15816
+ "parameters",
15817
+ "interpolate",
15818
+ "storeVar",
15819
+ "merged",
15820
+ "description"
15821
+ ]);
14591
15822
  ConfigReader = class extends Reader {
14592
15823
  constructor(..._args) {
14593
15824
  super(..._args);
@@ -14686,18 +15917,47 @@ var init_story_variables = __esm({
14686
15917
  }
14687
15918
  record() {
14688
15919
  const props = /* @__PURE__ */ new Map();
14689
- return this.elements("}", () => {
14690
- const key = this.key();
14691
- this.space();
14692
- if (key !== void 0 && this.src.charAt(this.i) === ":") {
14693
- this.i++;
14694
- props.set(key, this.member());
14695
- } else if (!this.skipMember()) this.fail();
14696
- }) ? {
15920
+ return this.elements("}", () => this.property(props)) ? {
14697
15921
  type: "object",
14698
15922
  props
14699
15923
  } : void 0;
14700
15924
  }
15925
+ /**
15926
+ * A member: what it sets replaces an earlier member of the key. One that is
15927
+ * not a literal (a method, an accessor, a shorthand) sets its key to a
15928
+ * value not known; a spread of an object literal sets the members of it, a
15929
+ * spread of anything else or a computed key can set any key.
15930
+ */
15931
+ property(props) {
15932
+ const { src } = this;
15933
+ if (src.startsWith("...", this.i)) {
15934
+ this.i += 3;
15935
+ this.space();
15936
+ const spread = src.charAt(this.i) === "{" ? this.record() : void 0;
15937
+ this.space();
15938
+ if (spread?.type === "object" && this.atElementEnd()) {
15939
+ for (const [key, value] of spread.props) props.set(key, value);
15940
+ return;
15941
+ }
15942
+ props.clear();
15943
+ } else {
15944
+ let key = this.key();
15945
+ this.space();
15946
+ if (key !== void 0 && src.charAt(this.i) === ":") {
15947
+ this.i++;
15948
+ props.set(key, this.member());
15949
+ return;
15950
+ }
15951
+ if (MODIFIER_RE.test(key ?? "") && KEY_START_RE.test(src.charAt(this.i))) {
15952
+ if (src.charAt(this.i) === "*") this.i++;
15953
+ this.space();
15954
+ key = this.key();
15955
+ }
15956
+ if (key === void 0) props.clear();
15957
+ else props.set(key, void 0);
15958
+ }
15959
+ if (!this.skipMember()) this.fail();
15960
+ }
14701
15961
  /** Stop reading: the text cannot be skipped through. */
14702
15962
  fail() {
14703
15963
  this.failed = true;
@@ -14707,6 +15967,35 @@ var init_story_variables = __esm({
14707
15967
  booleanOf = (d) => d?.type === "boolean" ? d.value : void 0;
14708
15968
  textOf = (d) => stringOf(d)?.value;
14709
15969
  stringsOf = (d) => d?.type === "array" ? d.items.flatMap((item) => stringOf(item)?.value ?? []) : [];
15970
+ VOID_ELEMENTS = /* @__PURE__ */ new Set([
15971
+ "area",
15972
+ "base",
15973
+ "br",
15974
+ "col",
15975
+ "embed",
15976
+ "hr",
15977
+ "img",
15978
+ "input",
15979
+ "link",
15980
+ "meta",
15981
+ "param",
15982
+ "source",
15983
+ "track",
15984
+ "wbr"
15985
+ ]);
15986
+ DEFAULT_PRETTIER_OPTIONS = { tabWidth: 2 };
15987
+ SVG_PLACEHOLDERS = (i2) => [`<!--SVG:${i2}-->`];
15988
+ SPINDLE_PLACEHOLDERS = (i2) => [`<!--SP:${i2}-->`, `__SP${i2}__`];
15989
+ DEFAULT_DEDENTING = /* @__PURE__ */ new Set([
15990
+ "else",
15991
+ "elseif",
15992
+ "next",
15993
+ "case",
15994
+ "default"
15995
+ ]);
15996
+ WHITESPACE_AS_WRITTEN = /<(?:pre|textarea)[\s>]|white-space\s*:/i;
15997
+ LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
15998
+ CODE_FENCE_REGEX = /^(?:`{3,}|~{3,})/;
14710
15999
  NOT_MARKUP_TAGS = ["script", "stylesheet"];
14711
16000
  }
14712
16001
  });
@@ -14956,48 +16245,56 @@ var init_macro_registry = __esm({
14956
16245
  "name": "story-title",
14957
16246
  "block": false,
14958
16247
  "subMacros": [],
16248
+ "interpolate": true,
14959
16249
  "source": "builtin"
14960
16250
  },
14961
16251
  {
14962
16252
  "name": "back",
14963
16253
  "block": false,
14964
16254
  "subMacros": [],
16255
+ "interpolate": true,
14965
16256
  "source": "builtin"
14966
16257
  },
14967
16258
  {
14968
16259
  "name": "forward",
14969
16260
  "block": false,
14970
16261
  "subMacros": [],
16262
+ "interpolate": true,
14971
16263
  "source": "builtin"
14972
16264
  },
14973
16265
  {
14974
16266
  "name": "quicksave",
14975
16267
  "block": false,
14976
16268
  "subMacros": [],
16269
+ "interpolate": true,
14977
16270
  "source": "builtin"
14978
16271
  },
14979
16272
  {
14980
16273
  "name": "quickload",
14981
16274
  "block": false,
14982
16275
  "subMacros": [],
16276
+ "interpolate": true,
14983
16277
  "source": "builtin"
14984
16278
  },
14985
16279
  {
14986
16280
  "name": "restart",
14987
16281
  "block": false,
14988
16282
  "subMacros": [],
16283
+ "interpolate": true,
14989
16284
  "source": "builtin"
14990
16285
  },
14991
16286
  {
14992
16287
  "name": "saves",
14993
16288
  "block": false,
14994
16289
  "subMacros": [],
16290
+ "interpolate": true,
14995
16291
  "source": "builtin"
14996
16292
  },
14997
16293
  {
14998
16294
  "name": "settings",
14999
16295
  "block": false,
15000
16296
  "subMacros": [],
16297
+ "interpolate": true,
15001
16298
  "source": "builtin"
15002
16299
  },
15003
16300
  {
@@ -15023,12 +16320,14 @@ var init_macro_registry = __esm({
15023
16320
  "name": "save-manager",
15024
16321
  "block": false,
15025
16322
  "subMacros": [],
16323
+ "interpolate": true,
15026
16324
  "source": "builtin"
15027
16325
  },
15028
16326
  {
15029
16327
  "name": "settings-controls",
15030
16328
  "block": false,
15031
16329
  "subMacros": [],
16330
+ "interpolate": true,
15032
16331
  "source": "builtin"
15033
16332
  },
15034
16333
  {
@@ -15054,6 +16353,7 @@ var init_macro_registry = __esm({
15054
16353
  "name": "nobr",
15055
16354
  "block": true,
15056
16355
  "subMacros": [],
16356
+ "interpolate": true,
15057
16357
  "source": "builtin"
15058
16358
  },
15059
16359
  {
@@ -15088,6 +16388,7 @@ var init_macro_registry = __esm({
15088
16388
  "block": false,
15089
16389
  "subMacros": [],
15090
16390
  "storeVar": true,
16391
+ "interpolate": true,
15091
16392
  "parameters": [
15092
16393
  {
15093
16394
  "name": "variable",
@@ -15107,6 +16408,7 @@ var init_macro_registry = __esm({
15107
16408
  "block": false,
15108
16409
  "subMacros": [],
15109
16410
  "storeVar": true,
16411
+ "interpolate": true,
15110
16412
  "parameters": [
15111
16413
  {
15112
16414
  "name": "variable",
@@ -15126,6 +16428,7 @@ var init_macro_registry = __esm({
15126
16428
  "block": false,
15127
16429
  "subMacros": [],
15128
16430
  "storeVar": true,
16431
+ "interpolate": true,
15129
16432
  "parameters": [
15130
16433
  {
15131
16434
  "name": "variable",
@@ -15145,6 +16448,7 @@ var init_macro_registry = __esm({
15145
16448
  "block": false,
15146
16449
  "subMacros": [],
15147
16450
  "storeVar": true,
16451
+ "interpolate": true,
15148
16452
  "parameters": [
15149
16453
  {
15150
16454
  "name": "variable",
@@ -15164,6 +16468,7 @@ var init_macro_registry = __esm({
15164
16468
  "block": false,
15165
16469
  "subMacros": [],
15166
16470
  "storeVar": true,
16471
+ "interpolate": true,
15167
16472
  "parameters": [
15168
16473
  {
15169
16474
  "name": "variable",
@@ -15191,6 +16496,7 @@ var init_macro_registry = __esm({
15191
16496
  "option"
15192
16497
  ],
15193
16498
  "storeVar": true,
16499
+ "interpolate": true,
15194
16500
  "parameters": [
15195
16501
  {
15196
16502
  "name": "variable",
@@ -15205,6 +16511,7 @@ var init_macro_registry = __esm({
15205
16511
  "block": true,
15206
16512
  "subMacros": [],
15207
16513
  "storeVar": true,
16514
+ "interpolate": true,
15208
16515
  "parameters": [
15209
16516
  {
15210
16517
  "name": "variable",
@@ -15218,6 +16525,7 @@ var init_macro_registry = __esm({
15218
16525
  "name": "span",
15219
16526
  "block": true,
15220
16527
  "subMacros": [],
16528
+ "interpolate": true,
15221
16529
  "source": "builtin"
15222
16530
  },
15223
16531
  {
@@ -23929,7 +25237,7 @@ var MINIMUM_SPINDLE_VERSION, DEFAULT_TARGET;
23929
25237
  var init_spindle_version = __esm({
23930
25238
  "src/core/workspace/spindle-version.ts"() {
23931
25239
  "use strict";
23932
- MINIMUM_SPINDLE_VERSION = "0.59.20";
25240
+ MINIMUM_SPINDLE_VERSION = "0.60.0";
23933
25241
  DEFAULT_TARGET = { version: void 0, source: "default", supported: true };
23934
25242
  }
23935
25243
  });
@@ -26955,322 +28263,31 @@ function headerTags(header) {
26955
28263
  const unescaped = header.replace(/\\./g, "ec");
26956
28264
  return (unescaped.match(/\[[^\]]*\]/g) ?? []).flatMap((t) => t.slice(1, -1).split(/\s+/));
26957
28265
  }
26958
- function segmentRegions(body) {
26959
- const lines = body.split("\n");
26960
- if (lines.length > 0 && lines[lines.length - 1] === "") {
26961
- lines.pop();
26962
- }
26963
- if (lines.length === 0) return [];
26964
- const regions = [];
26965
- let i2 = 0;
26966
- while (i2 < lines.length) {
26967
- const line = lines[i2];
26968
- const trimmed2 = line.trim();
26969
- if (/^<script(\s|>)/i.test(trimmed2)) {
26970
- const scriptLines = [line];
26971
- i2++;
26972
- if (/<\/script\s*>/i.test(line)) {
26973
- regions.push({ type: "script", lines: scriptLines });
26974
- continue;
26975
- }
26976
- while (i2 < lines.length && !/<\/script>/i.test(lines[i2])) {
26977
- scriptLines.push(lines[i2]);
26978
- i2++;
26979
- }
26980
- if (i2 < lines.length) {
26981
- scriptLines.push(lines[i2]);
26982
- i2++;
26983
- }
26984
- regions.push({ type: "script", lines: scriptLines });
26985
- continue;
26986
- }
26987
- if (/^<svg(\s|>)/i.test(trimmed2)) {
26988
- const svgLines = [line];
26989
- i2++;
26990
- while (i2 < lines.length && !/<\/svg>/i.test(lines[i2])) {
26991
- svgLines.push(lines[i2]);
26992
- i2++;
26993
- }
26994
- if (i2 < lines.length) {
26995
- svgLines.push(lines[i2]);
26996
- i2++;
26997
- }
26998
- regions.push({ type: "svg", lines: svgLines });
26999
- continue;
27000
- }
27001
- if (/^<([a-zA-Z][\w-]*)/.test(trimmed2)) {
27002
- const tagMatch = trimmed2.match(/^<([a-zA-Z][\w-]*)/);
27003
- const tagName = tagMatch[1].toLowerCase();
27004
- if (VOID_ELEMENTS.has(tagName) && !lines[i2 + 1]?.trim().startsWith("<")) {
27005
- pushSpindleLine(regions, line);
27006
- i2++;
27007
- continue;
27008
- }
27009
- const selfClosing = new RegExp(`</${tagName}\\s*>`, "i");
27010
- if (selfClosing.test(trimmed2)) {
27011
- pushSpindleLine(regions, line);
27012
- i2++;
27013
- continue;
27014
- }
27015
- const htmlLines = [line];
27016
- let depth = 1;
27017
- i2++;
27018
- while (i2 < lines.length && depth > 0) {
27019
- htmlLines.push(lines[i2]);
27020
- const opens = (lines[i2].match(new RegExp(`<${tagName}(?:[\\s>]|$)`, "gi")) ?? []).length;
27021
- const closes2 = (lines[i2].match(new RegExp(`</${tagName}\\s*>`, "gi")) ?? []).length;
27022
- depth += opens - closes2;
27023
- i2++;
27024
- }
27025
- regions.push({ type: "html", lines: htmlLines });
27026
- continue;
27027
- }
27028
- pushSpindleLine(regions, line);
27029
- i2++;
27030
- }
27031
- return regions;
27032
- }
27033
- function pushSpindleLine(regions, line) {
27034
- const last2 = regions[regions.length - 1];
27035
- if (last2?.type === "spindle") {
27036
- last2.lines.push(line);
27037
- } else {
27038
- regions.push({ type: "spindle", lines: [line] });
27039
- }
27040
- }
27041
- var PASSAGE_HEADER_REGEX, VOID_ELEMENTS;
28266
+ var PASSAGE_HEADER_REGEX;
27042
28267
  var init_segment = __esm({
27043
28268
  "src/plugins/format/segment.ts"() {
27044
28269
  "use strict";
27045
28270
  init_passage_parser();
27046
28271
  PASSAGE_HEADER_REGEX = /^::\s+/;
27047
- VOID_ELEMENTS = /* @__PURE__ */ new Set([
27048
- "area",
27049
- "base",
27050
- "br",
27051
- "col",
27052
- "embed",
27053
- "hr",
27054
- "img",
27055
- "input",
27056
- "link",
27057
- "meta",
27058
- "param",
27059
- "source",
27060
- "track",
27061
- "wbr"
27062
- ]);
27063
28272
  }
27064
28273
  });
27065
28274
 
27066
28275
  // src/plugins/format/prettier-bridge.ts
27067
28276
  async function loadPrettier() {
27068
- if (prettierLoaded) return prettier;
27069
- prettierLoaded = true;
27070
- try {
27071
- prettier = await import("prettier");
27072
- } catch {
27073
- prettier = null;
27074
- }
27075
- return prettier;
27076
- }
27077
- async function getBaseConfig() {
27078
- const p = await loadPrettier();
27079
- if (!p) return {};
28277
+ prettier ??= import("prettier").catch(() => null);
28278
+ const p = await prettier;
28279
+ if (!p) return void 0;
27080
28280
  try {
27081
28281
  const resolved = await p.resolveConfig(process.cwd());
27082
- return { tabWidth: 2, ...resolved ?? {} };
27083
- } catch {
27084
- return { tabWidth: 2 };
27085
- }
27086
- }
27087
- async function formatJS(code) {
27088
- const p = await loadPrettier();
27089
- if (!p) return code;
27090
- try {
27091
- const config2 = await getBaseConfig();
27092
- return await p.format(code, { ...config2, parser: "babel" });
27093
- } catch {
27094
- return code;
27095
- }
27096
- }
27097
- async function formatCSS(code) {
27098
- const p = await loadPrettier();
27099
- if (!p) return code;
27100
- try {
27101
- const config2 = await getBaseConfig();
27102
- return await p.format(code, { ...config2, parser: "css" });
27103
- } catch {
27104
- return code;
27105
- }
27106
- }
27107
- async function formatHTML(code) {
27108
- const p = await loadPrettier();
27109
- if (!p) return code;
27110
- try {
27111
- const config2 = await getBaseConfig();
27112
- return await p.format(code, {
27113
- ...config2,
27114
- parser: "html",
27115
- htmlWhitespaceSensitivity: "ignore"
27116
- });
28282
+ return { prettier: p, options: { tabWidth: 2, ...resolved ?? {} } };
27117
28283
  } catch {
27118
- return code;
28284
+ return { prettier: p, options: { tabWidth: 2 } };
27119
28285
  }
27120
28286
  }
27121
- var prettier, prettierLoaded;
28287
+ var prettier;
27122
28288
  var init_prettier_bridge = __esm({
27123
28289
  "src/plugins/format/prettier-bridge.ts"() {
27124
28290
  "use strict";
27125
- prettier = null;
27126
- prettierLoaded = false;
27127
- }
27128
- });
27129
-
27130
- // src/plugins/format/placeholders.ts
27131
- function scanSpindleMarkup(text2) {
27132
- const tokens2 = [];
27133
- const tags = [];
27134
- const doBlocks = [];
27135
- let open;
27136
- const push = (t, inAttribute) => {
27137
- const match = { start: t.start, end: t.end, token: text2.slice(t.start, t.end) };
27138
- if (inAttribute) match.inAttribute = true;
27139
- tokens2.push(match);
27140
- };
27141
- for (const t of tokenizeMarkupTolerant(text2).tokens) {
27142
- if (t.type === "html") {
27143
- tags.push({ start: t.start, end: t.end });
27144
- for (const attribute of t.attributeSpans) {
27145
- if (attribute.valueStart === void 0 || attribute.valueEnd === void 0) continue;
27146
- const value = text2.slice(attribute.valueStart, attribute.valueEnd);
27147
- if (!value.includes("{")) continue;
27148
- for (const inner of tokenizeMarkupTolerant(value, { text: true }).tokens) {
27149
- if (inner.type === "macro" || inner.type === "variable" || inner.type === "expression") {
27150
- push({ start: attribute.valueStart + inner.start, end: attribute.valueStart + inner.end }, true);
27151
- }
27152
- }
27153
- }
27154
- } else if (t.type !== "text") {
27155
- if (t.type === "macro" && t.name === "do") {
27156
- if (!t.isClose) open = t;
27157
- else if (open) {
27158
- doBlocks.push({ start: open.start, end: t.end, bodyStart: open.end, bodyEnd: t.start });
27159
- open = void 0;
27160
- }
27161
- }
27162
- push(t);
27163
- }
27164
- }
27165
- return { tokens: tokens2, tags, doBlocks };
27166
- }
27167
- function multilineJsLiterals(code) {
27168
- const spans = [];
27169
- let skipUntil = 0;
27170
- lexJs(code, {
27171
- literal(text2, index, nesting) {
27172
- if (nesting > 0 || index < skipUntil) return;
27173
- const quote = text2[0];
27174
- if (quote === "`") {
27175
- skipUntil = lexTemplate(code, index);
27176
- if (code.slice(index, skipUntil).includes("\n")) spans.push([index, skipUntil]);
27177
- } else if ((quote === '"' || quote === "'") && text2.includes("\n")) {
27178
- spans.push([index, index + text2.length]);
27179
- }
27180
- }
27181
- }, "statements");
27182
- return spans;
27183
- }
27184
- function lineHasTag(tags, start, end) {
27185
- return tags.some((tag) => tag.start < end && tag.end > start);
27186
- }
27187
- function replaceSvgBlocks(html) {
27188
- const tokens2 = [];
27189
- const result = html.replace(/<svg[\s>][\s\S]*?<\/svg>/gi, (match) => {
27190
- const idx = tokens2.length;
27191
- tokens2.push(match);
27192
- return `<!--SVG:${idx}-->`;
27193
- });
27194
- return { text: result, tokens: tokens2 };
27195
- }
27196
- function restoreSvgBlocks(text2, tokens2) {
27197
- let result = text2;
27198
- for (let i2 = 0; i2 < tokens2.length; i2++) {
27199
- result = result.replace(`<!--SVG:${i2}-->`, () => tokens2[i2]);
27200
- }
27201
- return result;
27202
- }
27203
- function replaceSpindleTokens(html) {
27204
- const { tokens: found, tags, doBlocks } = scanSpindleMarkup(html);
27205
- const units = [];
27206
- let doIndex = 0;
27207
- for (const m of found) {
27208
- while (doIndex < doBlocks.length && doBlocks[doIndex].end <= m.start) {
27209
- const block = doBlocks[doIndex++];
27210
- units.push({ start: block.start, end: block.end, token: html.slice(block.start, block.end) });
27211
- }
27212
- if (doBlocks[doIndex] && m.start >= doBlocks[doIndex].start) continue;
27213
- units.push(m);
27214
- }
27215
- for (; doIndex < doBlocks.length; doIndex++) {
27216
- const block = doBlocks[doIndex];
27217
- units.push({ start: block.start, end: block.end, token: html.slice(block.start, block.end) });
27218
- }
27219
- units.sort((a, b) => a.start - b.start);
27220
- const tokens2 = [];
27221
- const resultLines = [];
27222
- let unit = 0;
27223
- let lineStart = 0;
27224
- while (lineStart <= html.length) {
27225
- let lineEnd = lineStart;
27226
- const lineUnits = [];
27227
- for (; ; ) {
27228
- while (unit < units.length && units[unit].end <= lineEnd) unit++;
27229
- const inside = units[unit] && units[unit].start <= lineEnd ? units[unit] : void 0;
27230
- if (inside) {
27231
- lineUnits.push(inside);
27232
- lineEnd = inside.end;
27233
- unit++;
27234
- continue;
27235
- }
27236
- if (lineEnd >= html.length || html[lineEnd] === "\n") break;
27237
- lineEnd++;
27238
- }
27239
- const line = html.slice(lineStart, lineEnd);
27240
- const indent = line.match(/^(\s*)/)?.[1] ?? "";
27241
- if (lineUnits.length === 0) {
27242
- resultLines.push(line);
27243
- } else if (!lineHasTag(tags, lineStart, lineEnd)) {
27244
- const idx = tokens2.length;
27245
- tokens2.push(line.trim());
27246
- resultLines.push(`${indent}<!--SP:${idx}-->`);
27247
- } else {
27248
- let replaced = "";
27249
- let last2 = lineStart;
27250
- for (const u of lineUnits) {
27251
- const idx = tokens2.length;
27252
- tokens2.push(u.token);
27253
- replaced += html.slice(last2, u.start) + (u.inAttribute ? `__SP${idx}__` : `<!--SP:${idx}-->`);
27254
- last2 = u.end;
27255
- }
27256
- resultLines.push(replaced + html.slice(last2, lineEnd));
27257
- }
27258
- lineStart = lineEnd + 1;
27259
- }
27260
- return { text: resultLines.join("\n"), tokens: tokens2 };
27261
- }
27262
- function restoreSpindleTokens(text2, tokens2) {
27263
- let result = text2;
27264
- for (let i2 = 0; i2 < tokens2.length; i2++) {
27265
- result = result.replace(`<!--SP:${i2}-->`, () => tokens2[i2]);
27266
- result = result.replace(`__SP${i2}__`, () => tokens2[i2]);
27267
- }
27268
- return result;
27269
- }
27270
- var init_placeholders = __esm({
27271
- "src/plugins/format/placeholders.ts"() {
27272
- "use strict";
27273
- init_tooling();
27274
28291
  }
27275
28292
  });
27276
28293
 
@@ -27288,50 +28305,22 @@ function buildDefaultIsBlock(text2) {
27288
28305
  }
27289
28306
  async function formatDocument(text2, options) {
27290
28307
  if (text2.charCodeAt(0) === 65279) return "\uFEFF" + await formatDocument(text2.slice(1), options);
27291
- const eol = dominantEol(text2);
27292
- const output = await formatLf(text2.replace(/\r\n/g, "\n"), options);
28308
+ const eol = dominantEol2(text2);
28309
+ const output = await formatLf2(text2.replace(/\r\n/g, "\n"), options);
27293
28310
  return eol === "\r\n" ? output.replace(/\n/g, "\r\n") : output;
27294
28311
  }
27295
- function dominantEol(text2) {
28312
+ function dominantEol2(text2) {
27296
28313
  const crlf = (text2.match(/\r\n/g) ?? []).length;
27297
28314
  const lf = (text2.match(/\n/g) ?? []).length - crlf;
27298
28315
  if (crlf !== lf) return crlf > lf ? "\r\n" : "\n";
27299
28316
  return text2.indexOf("\r\n") !== -1 && text2.indexOf("\r\n") < text2.indexOf("\n") ? "\r\n" : "\n";
27300
28317
  }
27301
- function newlineSentinel(text2) {
27302
- for (let code = 57344; code <= 63743; code++) {
27303
- const ch = String.fromCharCode(code);
27304
- if (!text2.includes(ch)) return ch;
27305
- }
27306
- throw new Error("no free private-use character for the newline stand-in");
27307
- }
27308
- function protectMultilineTokens(body, sentinel) {
27309
- const { tokens: tokens2, doBlocks } = scanSpindleMarkup(body);
27310
- const spans = [];
27311
- for (const m of tokens2) {
27312
- if (m.token.includes("\n")) spans.push([m.start, m.end]);
27313
- }
27314
- for (const block of doBlocks) {
27315
- for (const [a, b] of multilineJsLiterals(body.slice(block.bodyStart, block.bodyEnd))) {
27316
- spans.push([block.bodyStart + a, block.bodyStart + b]);
27317
- }
27318
- }
27319
- spans.sort((x2, y) => x2[0] - y[0]);
27320
- let out = "";
27321
- let last2 = 0;
27322
- for (const [a, b] of spans) {
27323
- out += body.slice(last2, a) + body.slice(a, b).replaceAll("\n", sentinel);
27324
- last2 = b;
27325
- }
27326
- return out + body.slice(last2);
27327
- }
27328
- async function formatLf(text2, options) {
28318
+ async function formatLf2(text2, options) {
27329
28319
  const isBlock = options?.isBlock ?? buildDefaultIsBlock(text2);
27330
- const isDedenting = options?.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING.has(name.toLowerCase()));
28320
+ const isDedentingSubMacro = options?.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING2.has(name.toLowerCase()));
28321
+ const prettier2 = await loadPrettier();
27331
28322
  const passages = splitPassages(text2);
27332
- const sentinel = newlineSentinel(text2);
27333
28323
  const resultLines = [];
27334
- const hardBreakLines = /* @__PURE__ */ new Set();
27335
28324
  for (let pi2 = 0; pi2 < passages.length; pi2++) {
27336
28325
  const passage = passages[pi2];
27337
28326
  if (pi2 > 0 && passage.header) {
@@ -27344,226 +28333,24 @@ async function formatLf(text2, options) {
27344
28333
  }
27345
28334
  if (passage.header) {
27346
28335
  const header = PASSAGE_HEADER_REGEX2.test(passage.header) ? normalizePassageHeader(passage.header) : passage.header;
27347
- resultLines.push(header);
27348
- }
27349
- const kind = classifyPassage(passage.header);
27350
- if (kind === "script") {
27351
- const formatted = await formatJS(passage.body.trim());
27352
- resultLines.push(formatted.trim());
27353
- continue;
27354
- }
27355
- if (kind === "stylesheet") {
27356
- const formatted = await formatCSS(passage.body.trim());
27357
- resultLines.push(formatted.trim());
27358
- continue;
27359
- }
27360
- const regions = segmentRegions(protectMultilineTokens(passage.body, sentinel));
27361
- for (const region of regions) {
27362
- if (region.type !== "spindle") {
27363
- region.lines = region.lines.map((l) => l.replaceAll(sentinel, "\n"));
27364
- }
27365
- if (region.type === "script") {
27366
- if (region.lines.length === 1) {
27367
- resultLines.push(region.lines[0]);
27368
- continue;
27369
- }
27370
- const firstLine = region.lines[0];
27371
- const lastLine = region.lines[region.lines.length - 1];
27372
- const innerLines = region.lines.slice(1, region.lines.length - 1);
27373
- const innerCode = innerLines.join("\n");
27374
- const formatted = await formatJS(innerCode.trim());
27375
- resultLines.push(firstLine);
27376
- if (formatted.trim()) {
27377
- for (const fLine of formatted.trim().split("\n")) {
27378
- resultLines.push(" " + fLine);
27379
- }
27380
- }
27381
- resultLines.push(lastLine);
27382
- continue;
27383
- }
27384
- if (region.type === "svg") {
27385
- resultLines.push(...region.lines);
27386
- continue;
27387
- }
27388
- if (region.type === "html") {
27389
- const htmlText = region.lines.join("\n");
27390
- const { text: svgPlaceholdered, tokens: svgTokens } = replaceSvgBlocks(htmlText);
27391
- const { text: placeholdered, tokens: tokens2 } = replaceSpindleTokens(svgPlaceholdered);
27392
- const formatted = await formatHTML(placeholdered);
27393
- const restoredSpindle = restoreSpindleTokens(formatted.trim(), tokens2.map((t) => t.replaceAll("\n", sentinel)));
27394
- const restored = restoreSvgBlocks(restoredSpindle, svgTokens);
27395
- for (const fLine of restored.split("\n")) {
27396
- resultLines.push(fLine);
27397
- }
27398
- continue;
27399
- }
27400
- let indented = indentMacros(region.lines, isBlock, isDedenting);
27401
- if (options?.maxLineLength) {
27402
- indented = wrapLines(indented, options.maxLineLength);
27403
- }
27404
- for (const line of indented) {
27405
- if (line.endsWith(" ")) hardBreakLines.add(resultLines.length);
27406
- resultLines.push(line);
27407
- }
27408
- }
27409
- }
27410
- let output = resultLines.map((l, i2) => hardBreakLines.has(i2) ? l : l.replace(/\s+$/, "")).join("\n");
27411
- output = output.replace(/\n*$/, "\n");
27412
- return output.replaceAll(sentinel, "\n");
27413
- }
27414
- function indentMacros(lines, isBlock, isDedenting) {
27415
- const macrosOnLine = macroTokensByLine(lines);
27416
- const bodies = [{ openLine: -1, minCol: Infinity, keepRelative: false }];
27417
- const stack = [0];
27418
- const info = [];
27419
- const openBody = (line) => {
27420
- bodies.push({ openLine: line, minCol: Infinity, keepRelative: false });
27421
- stack.push(bodies.length - 1);
27422
- };
27423
- const closeBody = () => stack.length > 1 ? stack.pop() : void 0;
27424
- const addLine = (col, text2) => {
27425
- const body = bodies[stack[stack.length - 1]];
27426
- body.minCol = Math.min(body.minCol, col);
27427
- if (LIST_ITEM_REGEX.test(text2) || CODE_FENCE_REGEX.test(text2)) body.keepRelative = true;
27428
- info.push({ body: stack[stack.length - 1], col, text: text2 });
27429
- };
27430
- for (let i2 = 0; i2 < lines.length; i2++) {
27431
- const trimmed2 = lines[i2].trim();
27432
- if (trimmed2 === "") {
27433
- info.push(null);
27434
- continue;
27435
- }
27436
- const col = indentColumn(lines[i2]);
27437
- const lead = lines[i2].length - lines[i2].trimStart().length;
27438
- const [first, ...rest] = macrosOnLine[i2];
27439
- const startsWithMacro = first !== void 0 && first.start === lead;
27440
- if (startsWithMacro && !first.isClose && isDedenting(first.name)) {
27441
- const closed2 = closeBody();
27442
- if (closed2 === void 0) addLine(col, trimmed2);
27443
- else info.push({ body: stack[stack.length - 1], closes: closed2, col, text: trimmed2 });
27444
- openBody(i2);
27445
- continue;
27446
- }
27447
- const closed = startsWithMacro && first.isClose ? closeBody() : void 0;
27448
- if (closed === void 0) addLine(col, trimmed2);
27449
- else info.push({ body: stack[stack.length - 1], closes: closed, col, text: trimmed2 });
27450
- if (startsWithMacro && !first.isClose && isBlock(first.name)) {
27451
- openBody(i2);
27452
- }
27453
- for (const macro of startsWithMacro ? rest : macrosOnLine[i2]) {
27454
- if (macro.isClose && macro.start > lead) closeBody();
27455
- }
27456
- }
27457
- const outCol = [];
27458
- const result = [];
27459
- for (let i2 = 0; i2 < info.length; i2++) {
27460
- const line = info[i2];
27461
- if (!line) {
27462
- outCol.push(0);
27463
- result.push("");
27464
- continue;
27465
- }
27466
- let col;
27467
- if (line.closes !== void 0) {
27468
- col = outCol[bodies[line.closes].openLine];
27469
- } else {
27470
- const body = bodies[line.body];
27471
- const isRoot = body.openLine === -1;
27472
- const base = isRoot ? 0 : outCol[body.openLine] + 2;
27473
- col = base + (body.keepRelative ? line.col - (isRoot ? 0 : body.minCol) : 0);
27474
- }
27475
- outCol.push(col);
27476
- const hardBreak = / {2,}$/.test(lines[i2]) && info[i2 + 1] ? " " : "";
27477
- result.push(" ".repeat(col) + line.text + hardBreak);
27478
- }
27479
- return result;
27480
- }
27481
- function macroTokensByLine(lines) {
27482
- const byLine = lines.map(() => []);
27483
- const starts = [];
27484
- let offset2 = 0;
27485
- for (const line2 of lines) {
27486
- starts.push(offset2);
27487
- offset2 += line2.length + 1;
27488
- }
27489
- let line = 0;
27490
- for (const token of tokenizeMarkupTolerant(lines.join("\n")).tokens) {
27491
- if (token.type !== "macro") continue;
27492
- while (line + 1 < lines.length && starts[line + 1] <= token.start) line++;
27493
- byLine[line].push({ ...token, start: token.start - starts[line], end: token.end - starts[line] });
27494
- }
27495
- return byLine;
27496
- }
27497
- function indentColumn(line) {
27498
- let col = 0;
27499
- for (const ch of line) {
27500
- if (ch === " ") col++;
27501
- else if (ch === " ") col += 4 - col % 4;
27502
- else break;
28336
+ resultLines.push(header.replace(/\s+$/, ""));
28337
+ }
28338
+ if (passage.body === "") continue;
28339
+ const body = await formatPassage(passage.body, {
28340
+ kind: classifyPassage(passage.header),
28341
+ prettier: prettier2?.prettier,
28342
+ prettierOptions: prettier2?.options,
28343
+ isBlock,
28344
+ isDedentingSubMacro,
28345
+ maxLineLength: options?.maxLineLength
28346
+ });
28347
+ resultLines.push(body.replace(/\n$/, ""));
27503
28348
  }
27504
- return col;
28349
+ return resultLines.join("\n").replace(/\n*$/, "\n");
27505
28350
  }
27506
28351
  async function formatRange(text2, _range, options) {
27507
28352
  return formatDocument(text2, options);
27508
28353
  }
27509
- function wrapLines(lines, maxLen) {
27510
- const result = [];
27511
- for (const line of lines) {
27512
- if (line.length <= maxLen) {
27513
- result.push(line);
27514
- continue;
27515
- }
27516
- const trimmed2 = line.trim();
27517
- if (trimmed2 === "" || /^::/.test(trimmed2) || /^\{[\/#.]/.test(trimmed2) || // closing/opening-only macro lines
27518
- /^\[\[/.test(trimmed2) || // links
27519
- /^\{choice\b/.test(trimmed2) || // choice macros
27520
- /^\{choices\b/.test(trimmed2)) {
27521
- result.push(line);
27522
- continue;
27523
- }
27524
- const indentMatch = line.match(/^(\s*)/);
27525
- const indent = indentMatch ? indentMatch[1] : "";
27526
- const wrapped = wordWrap(trimmed2, maxLen, indent);
27527
- if (line.endsWith(" ")) wrapped[wrapped.length - 1] += " ";
27528
- result.push(...wrapped);
27529
- }
27530
- return result;
27531
- }
27532
- function wordWrap(text2, maxLen, indent) {
27533
- const lines = [];
27534
- const inToken = new Array(text2.length).fill(false);
27535
- for (const token of tokenizeMarkupTolerant(text2).tokens) {
27536
- if (token.type === "text" || token.type === "html") continue;
27537
- inToken.fill(true, token.start, token.end);
27538
- }
27539
- const room = maxLen - indent.length;
27540
- let pos = 0;
27541
- while (pos < text2.length) {
27542
- if (text2.length - pos <= room) {
27543
- lines.push(indent + text2.slice(pos));
27544
- break;
27545
- }
27546
- let breakIdx = -1;
27547
- for (let i2 = pos + 1; i2 < text2.length && i2 - pos <= room; i2++) {
27548
- if (text2[i2] === " " && !inToken[i2]) breakIdx = i2;
27549
- }
27550
- if (breakIdx === -1) {
27551
- for (let i2 = pos + room + 1; i2 < text2.length; i2++) {
27552
- if (text2[i2] === " " && !inToken[i2]) {
27553
- breakIdx = i2;
27554
- break;
27555
- }
27556
- }
27557
- }
27558
- if (breakIdx === -1) {
27559
- lines.push(indent + text2.slice(pos));
27560
- break;
27561
- }
27562
- lines.push(indent + text2.slice(pos, breakIdx));
27563
- pos = breakIdx + 1;
27564
- }
27565
- return lines;
27566
- }
27567
28354
  function normalizePassageHeader(line) {
27568
28355
  const headerMatch = line.match(/^::\s+(.*)/);
27569
28356
  if (!headerMatch) return line;
@@ -27584,19 +28371,16 @@ function normalizePassageHeader(line) {
27584
28371
  }
27585
28372
  return `:: ${name}${suffix}`;
27586
28373
  }
27587
- var PASSAGE_HEADER_REGEX2, DEFAULT_DEDENTING, BUILTIN_CONTAINERS, LIST_ITEM_REGEX, CODE_FENCE_REGEX, formatPlugin;
28374
+ var PASSAGE_HEADER_REGEX2, DEFAULT_DEDENTING2, BUILTIN_CONTAINERS, formatPlugin;
27588
28375
  var init_format = __esm({
27589
28376
  "src/plugins/format.ts"() {
27590
28377
  "use strict";
27591
28378
  init_tooling();
27592
28379
  init_segment();
27593
28380
  init_prettier_bridge();
27594
- init_placeholders();
27595
28381
  PASSAGE_HEADER_REGEX2 = /^(::)\s+/;
27596
- DEFAULT_DEDENTING = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
28382
+ DEFAULT_DEDENTING2 = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
27597
28383
  BUILTIN_CONTAINERS = new Set(builtinMacros.filter((m) => m.block).map((m) => m.name.toLowerCase()));
27598
- LIST_ITEM_REGEX = /^(?:[-+*]|\d{1,9}[.)])(?:[ \t]|$)/;
27599
- CODE_FENCE_REGEX = /^(?:`{3,}|~{3,})/;
27600
28384
  formatPlugin = {
27601
28385
  id: "format",
27602
28386
  capabilities: {
@@ -27607,7 +28391,7 @@ var init_format = __esm({
27607
28391
  const formatOpts = {
27608
28392
  // Block widgets (whose body renders {@children}) are containers too
27609
28393
  isBlock: (name) => ctx.workspace.isContainer(name),
27610
- isDedentingSubMacro: (name) => DEFAULT_DEDENTING.has(name.toLowerCase())
28394
+ isDedentingSubMacro: (name) => DEFAULT_DEDENTING2.has(name.toLowerCase())
27611
28395
  };
27612
28396
  ctx.connection.onDocumentFormatting(async (params) => {
27613
28397
  const text2 = ctx.workspace.documents.getText(params.textDocument.uri);
@@ -71918,7 +72702,7 @@ var init_server4 = __esm({
71918
72702
 
71919
72703
  // src/bin.ts
71920
72704
  var args = process.argv.slice(2);
71921
- var VERSION = "0.13.2";
72705
+ var VERSION = "0.14.0";
71922
72706
  var HELP = `spindle-lsp v${VERSION}
71923
72707
  Language Server Protocol server for Spindle story format
71924
72708