@rohal12/spindle-lsp 0.15.34 → 0.15.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -55,7 +55,7 @@ var VERSION;
55
55
  var init_version = __esm({
56
56
  "src/version.ts"() {
57
57
  "use strict";
58
- VERSION = false ? pkg.version : "0.15.34";
58
+ VERSION = false ? pkg.version : "0.15.36";
59
59
  }
60
60
  });
61
61
 
@@ -14056,6 +14056,9 @@ function peg$parse(input2, options) {
14056
14056
  if (peg$success) return peg$result;
14057
14057
  else peg$throw();
14058
14058
  }
14059
+ function isOptionClause(name) {
14060
+ return name.toLowerCase() === OPTION_CLAUSE;
14061
+ }
14059
14062
  function consumesOptionClauses(macro) {
14060
14063
  const name = macro.name.toLowerCase();
14061
14064
  return macro.source === "builtin" && !macro.widget && (name === "listbox" || name === "cycle");
@@ -21872,6 +21875,71 @@ function blockLookup(macros, includeRuntime = true) {
21872
21875
  for (const macro of effectiveToolingMacros(macros)) definitions.set(macro.name, macro.block);
21873
21876
  return (name) => includeRuntime && isRegisteredBlockMacro(name) || (definitions.get(name) ?? (includeRuntime ? isBlockMacro(name) : isBuiltinBlockMacro(name)));
21874
21877
  }
21878
+ function macroCalls(source, macros = []) {
21879
+ const all = effectiveToolingMacros(macros);
21880
+ const definitions = new NameMap(all.map((macro) => [macro.name, macro]));
21881
+ const parametersOf2 = parameterLookup(all);
21882
+ const options = tokenizationOptions({
21883
+ macros: all,
21884
+ hooks: { isBlock: blockLookup(all, false) }
21885
+ });
21886
+ const hooks = options.hooks;
21887
+ const tokens2 = tokenizeMarkupTolerant$1(source, options).tokens;
21888
+ const calls = [];
21889
+ const classify2 = (tokens3) => {
21890
+ const found = /* @__PURE__ */ new Map();
21891
+ const pending = pairMarkup(tokens3, hooks).nodes.map((node2) => ({ node: node2 }));
21892
+ while (pending.length) {
21893
+ const { node: node2, parent } = pending.pop();
21894
+ const token = node2.token;
21895
+ if (token.type === "macro") {
21896
+ const definition2 = parent && definitions.get(parent.name);
21897
+ const value = definition2 && consumesOptionClauses(definition2) && isOptionClause(token.name);
21898
+ found.set(token, {
21899
+ token,
21900
+ kind: value ? "value" : "invocation",
21901
+ ...value && { owner: parent }
21902
+ });
21903
+ }
21904
+ if (!node2.body) continue;
21905
+ const owner = token.type === "macro" ? token : void 0;
21906
+ for (const child of node2.body.children) pending.push({
21907
+ node: child,
21908
+ parent: owner
21909
+ });
21910
+ for (const branch of node2.body.branches) {
21911
+ found.set(branch.tag, {
21912
+ token: branch.tag,
21913
+ kind: "branch",
21914
+ owner
21915
+ });
21916
+ for (const child of branch.children) pending.push({
21917
+ node: child,
21918
+ parent: owner
21919
+ });
21920
+ }
21921
+ }
21922
+ for (const token of tokens3) {
21923
+ if (token.type !== "macro" || token.isClose) continue;
21924
+ const call = found.get(token) ?? {
21925
+ token,
21926
+ kind: "branch"
21927
+ };
21928
+ calls.push(call);
21929
+ }
21930
+ };
21931
+ classify2(tokens2);
21932
+ for (const piece of passagePiecesOf(source, tokens2, parametersOf2, { isBlock: hooks.isBlock })) {
21933
+ if (piece.kind !== "text" || !piece.tokens.length) continue;
21934
+ classify2(piece.tokens);
21935
+ }
21936
+ let literalEnd = -1;
21937
+ return calls.sort((a, b) => a.token.start - b.token.start).filter((call) => {
21938
+ if (call.token.start < literalEnd) return false;
21939
+ if (call.kind === "value" || call.kind === "branch") literalEnd = call.token.argsEnd;
21940
+ return true;
21941
+ });
21942
+ }
21875
21943
  function passagePieces(source, macros = []) {
21876
21944
  const all = effectiveToolingMacros(macros);
21877
21945
  const parametersOf2 = parameterLookup(all);
@@ -33216,6 +33284,7 @@ var init_passage_markup = __esm({
33216
33284
  cachedTokens;
33217
33285
  cachedPairing;
33218
33286
  cachedPieces;
33287
+ cachedInvocations;
33219
33288
  cachedDeclarations;
33220
33289
  /** The document offset of `contentOffset`. */
33221
33290
  docOffset(contentOffset) {
@@ -33268,6 +33337,16 @@ var init_passage_markup = __esm({
33268
33337
  get pieces() {
33269
33338
  return this.cachedPieces ??= this.isMarkup ? passagePieces(this.content, this.doc.context.macros) : [];
33270
33339
  }
33340
+ /** Contextual calls, excluding parent-owned clauses and widget registration heads. */
33341
+ get invocations() {
33342
+ if (!this.cachedInvocations) {
33343
+ const macros = this.doc.context.macros;
33344
+ const definitions = widgetDefinitions([{ ...this.passage, content: this.content }], macros);
33345
+ const registration = new Set(definitions.map((definition2) => definition2.start));
33346
+ this.cachedInvocations = this.isMarkup ? macroCalls(this.content, macros).filter((call) => call.kind === "invocation" && !registration.has(call.token.start)).map((call) => call.token) : [];
33347
+ }
33348
+ return this.cachedInvocations;
33349
+ }
33271
33350
  /** Whether the cursor is executable, with decoded code mapped back to `content`. */
33272
33351
  completionAt(offset2) {
33273
33352
  return this.isMarkup ? completionContext(this.content, offset2, this.doc.context.macros) : { kind: "literal" };
@@ -33607,24 +33686,6 @@ var init_variable_tracker = __esm({
33607
33686
  }
33608
33687
  });
33609
33688
 
33610
- // src/core/markup/tokens.ts
33611
- function* markupTokens(passage) {
33612
- yield* passage.tokens;
33613
- for (const piece of passage.pieces) {
33614
- if (piece.kind === "text") yield* piece.tokens;
33615
- }
33616
- }
33617
- function* macroTokens(passage) {
33618
- for (const token of markupTokens(passage)) {
33619
- if (token.type === "macro") yield token;
33620
- }
33621
- }
33622
- var init_tokens = __esm({
33623
- "src/core/markup/tokens.ts"() {
33624
- "use strict";
33625
- }
33626
- });
33627
-
33628
33689
  // src/core/markup/widgets.ts
33629
33690
  function widgetSources(passages, macros) {
33630
33691
  const sources = /* @__PURE__ */ new Map();
@@ -33650,7 +33711,6 @@ var init_widget_registry = __esm({
33650
33711
  "src/core/workspace/widget-registry.ts"() {
33651
33712
  "use strict";
33652
33713
  init_tooling();
33653
- init_tokens();
33654
33714
  init_widgets();
33655
33715
  WidgetRegistry = class {
33656
33716
  /** Widgets keyed by lower-cased name; Spindle resolves widgets case-insensitively. */
@@ -33729,8 +33789,8 @@ var init_widget_registry = __esm({
33729
33789
  recordInvocations(uri, passages) {
33730
33790
  const names = /* @__PURE__ */ new Set();
33731
33791
  for (const passage of passages) {
33732
- for (const macro of macroTokens(passage)) {
33733
- if (!macro.isClose) names.add(macro.name.toLowerCase());
33792
+ for (const macro of passage.invocations) {
33793
+ names.add(macro.name.toLowerCase());
33734
33794
  }
33735
33795
  }
33736
33796
  this.invokedByUri.set(uri, names);
@@ -34759,7 +34819,9 @@ var init_diagnostic_codes = __esm({
34759
34819
  UnsupportedSpindleVersion: "SP001",
34760
34820
  /** A fatal Twee source error reported by the public compiler parser. */
34761
34821
  TweeSyntaxError: "SP002",
34762
- /** StoryData explicitly selects an entry passage absent from the story. */
34822
+ /** Compiler-owned errors decoding StoryData metadata. */
34823
+ InvalidStoryData: "SP003",
34824
+ /** The selected entry passage is absent or omitted from compiled story data. */
34763
34825
  MissingEntryPassage: "SP004",
34764
34826
  /** `unknown-macro`: a macro that is none of the built-in, configured or defined ones. */
34765
34827
  UndefinedMacro: "SP100",
@@ -34815,6 +34877,7 @@ var init_diagnostic_codes = __esm({
34815
34877
  severityMap = {
34816
34878
  [DiagnosticCode.UnsupportedSpindleVersion]: "warning",
34817
34879
  [DiagnosticCode.TweeSyntaxError]: "error",
34880
+ [DiagnosticCode.InvalidStoryData]: "error",
34818
34881
  [DiagnosticCode.MissingEntryPassage]: "error",
34819
34882
  [DiagnosticCode.UndefinedMacro]: "warning",
34820
34883
  [DiagnosticCode.MalformedContainer]: "error",
@@ -34853,6 +34916,24 @@ var init_diagnostic_codes = __esm({
34853
34916
  }
34854
34917
  });
34855
34918
 
34919
+ // src/core/markup/tokens.ts
34920
+ function* markupTokens(passage) {
34921
+ yield* passage.tokens;
34922
+ for (const piece of passage.pieces) {
34923
+ if (piece.kind === "text") yield* piece.tokens;
34924
+ }
34925
+ }
34926
+ function* macroTokens(passage) {
34927
+ for (const token of markupTokens(passage)) {
34928
+ if (token.type === "macro") yield token;
34929
+ }
34930
+ }
34931
+ var init_tokens = __esm({
34932
+ "src/core/markup/tokens.ts"() {
34933
+ "use strict";
34934
+ }
34935
+ });
34936
+
34856
34937
  // src/core/markup/source-view.ts
34857
34938
  function viewIndex(view, offset2) {
34858
34939
  if (!view.sourceOffsets) return offset2 - view.offset;
@@ -35117,12 +35198,14 @@ function computeDiagnostics(uri, workspace, options) {
35117
35198
  const story = storyReport(workspace);
35118
35199
  diagnostics.push(...story.metadata.get(doc) ?? []);
35119
35200
  const effective = new Set(workspace.passages.getEffectivePassages());
35201
+ let passageNames;
35202
+ const getPassageNames = () => passageNames ??= [...effective].filter(isRuntimePassage).map((passage) => passage.name);
35120
35203
  for (const passage of doc.passages) {
35121
35204
  if (!effective.has(passage.passage)) continue;
35122
35205
  attempt(() => diagnostics.push(...markupDiagnostics(passage, story, workspace)));
35123
35206
  attempt(() => diagnostics.push(...variableDiagnostics(passage, story)));
35124
35207
  attempt(() => diagnostics.push(...structureDiagnostics(passage, workspace)));
35125
- attempt(() => diagnostics.push(...argumentDiagnostics(passage, workspace)));
35208
+ attempt(() => diagnostics.push(...argumentDiagnostics(passage, workspace, getPassageNames)));
35126
35209
  attempt(() => diagnostics.push(...widgetDiagnostics(passage, story, workspace)));
35127
35210
  }
35128
35211
  attempt(() => diagnostics.push(...versionDiagnostics(uri, doc, workspace)));
@@ -35215,8 +35298,8 @@ function buildReport(documents, workspace) {
35215
35298
  const invoked = /* @__PURE__ */ new Set();
35216
35299
  for (const passage of passages) {
35217
35300
  if (!passage.isMarkup) continue;
35218
- for (const token of macroTokens(passage)) {
35219
- if (!token.isClose) invoked.add(token.name.toLowerCase());
35301
+ for (const token of passage.invocations) {
35302
+ invoked.add(token.name.toLowerCase());
35220
35303
  }
35221
35304
  }
35222
35305
  return {
@@ -35235,17 +35318,33 @@ function buildReport(documents, workspace) {
35235
35318
  }
35236
35319
  function storyDataDiagnostics(documents, workspace) {
35237
35320
  const found = /* @__PURE__ */ new Map();
35238
- const effective = workspace.passages.getEffectivePassage("StoryData");
35239
- for (const doc of documents) {
35321
+ const data2 = workspace.passages.getEffectivePassage("StoryData");
35322
+ const implicit = workspace.passages.getEffectivePassage("Start");
35323
+ if (!data2 && (!implicit || isRuntimePassage(implicit))) return found;
35324
+ const { parseTwee, StoryBuilder, startPassageDiagnostics } = createCompilerRequire3(import.meta.url)("@rohal12/twee-ts");
35325
+ const builder = new StoryBuilder();
35326
+ const sources = new Map(documents.flatMap((doc) => doc.passages.map((passage) => [passage.passage, passage])));
35327
+ const parsed = new Map(documents.map((doc) => [doc, new Map(parseTwee(doc.text).passages.map((passage) => [passage.source?.line, passage]))]));
35328
+ const effective = workspace.passages.getEffectivePassages();
35329
+ for (const source of effective) {
35330
+ const passage = sources.get(source);
35331
+ if (!passage) continue;
35332
+ const input2 = parsed.get(passage.doc)?.get(source.headerEnd.start.line + 1);
35333
+ if (!input2) continue;
35240
35334
  const diagnostics = [];
35241
- for (const passage of doc.passages) {
35242
- if (passage.passage !== effective) continue;
35243
- const data2 = readStoryData(passage.content);
35244
- if (data2.start && !workspace.passages.getEffectivePassage(data2.start)) {
35245
- diagnostics.push(makeDiag(passage.passage.nameRange, DiagnosticCode.MissingEntryPassage, `Starting passage "${data2.start}" not found.`));
35246
- }
35335
+ builder.add(input2, diagnostics);
35336
+ if (source.name === "StoryData") {
35337
+ found.set(passage.doc, diagnostics.filter((d) => d.level === "error").map((d) => makeDiag(passage.range(0, passage.content.trimEnd().length), DiagnosticCode.InvalidStoryData, d.message)));
35247
35338
  }
35248
- found.set(doc, diagnostics);
35339
+ }
35340
+ const story = builder.build();
35341
+ const entry = workspace.passages.getEffectivePassage(story.twine2.start || "Start");
35342
+ const owner = story.twine2.start ? workspace.passages.getEffectivePassage("StoryData") : entry;
35343
+ const markup = owner && sources.get(owner);
35344
+ if (markup) {
35345
+ const diagnostics = found.get(markup.doc) ?? [];
35346
+ diagnostics.push(...startPassageDiagnostics(story).map((d) => makeDiag(markup.passage.nameRange, DiagnosticCode.MissingEntryPassage, d.message)));
35347
+ found.set(markup.doc, diagnostics);
35249
35348
  }
35250
35349
  return found;
35251
35350
  }
@@ -35559,9 +35658,8 @@ function isReadInBody(passage, bodyStart, bodyEnd, name, macroStart, macroEnd) {
35559
35658
  }
35560
35659
  return false;
35561
35660
  }
35562
- function argumentDiagnostics(passage, workspace) {
35661
+ function argumentDiagnostics(passage, workspace, getPassageNames) {
35563
35662
  const diagnostics = [];
35564
- const passageNames = workspace.passages.getEffectivePassages().filter(isRuntimePassage).map((candidate) => candidate.name);
35565
35663
  for (const token of passage.isMarkup ? macroTokens(passage) : []) {
35566
35664
  if (token.type !== "macro" || token.isClose) continue;
35567
35665
  const info = workspace.getEffectiveMacro(token.name);
@@ -35581,7 +35679,7 @@ function argumentDiagnostics(passage, workspace) {
35581
35679
  }
35582
35680
  continue;
35583
35681
  }
35584
- const result2 = params.validate(args2, { passages: passageNames });
35682
+ const result2 = params.validate(args2, { passages: getPassageNames() });
35585
35683
  if (result2.variantIndex === null) continue;
35586
35684
  for (const error62 of result2.errors) {
35587
35685
  const code = error62.message.startsWith("Too many arguments") ? DiagnosticCode.TooManyArguments : DiagnosticCode.ParameterTypeError;
@@ -35597,8 +35695,7 @@ function widgetDiagnostics(passage, story, workspace) {
35597
35695
  if (!passage.isMarkup) return [];
35598
35696
  const diagnostics = [];
35599
35697
  const macros = [...macroTokens(passage)];
35600
- for (const token of macros) {
35601
- if (token.isClose) continue;
35698
+ for (const token of passage.invocations) {
35602
35699
  if (workspace.getEffectiveMacro(token.name)) continue;
35603
35700
  const widget = story.widgets.get(token.name.toLowerCase());
35604
35701
  if (!widget) continue;
@@ -35732,7 +35829,6 @@ var init_diagnostics = __esm({
35732
35829
  init_link_parser();
35733
35830
  init_passage_parser();
35734
35831
  init_story_variables_owner();
35735
- init_story_data();
35736
35832
  init_macro_sources();
35737
35833
  init_spindle_version();
35738
35834
  tweeReports = /* @__PURE__ */ new WeakMap();
@@ -87171,7 +87267,7 @@ var init_markup_cursor = __esm({
87171
87267
  const found = [];
87172
87268
  for (const token of region.tokens) {
87173
87269
  if (token.type !== "macro" || token.isClose || token.nameEnd >= at3 || at3 >= token.end) continue;
87174
- found.push({ start: token.start, name: token.name, argsBefore: at3 >= token.argsStart ? region.view.text.slice(viewIndex(region.view, token.argsStart), viewIndex(region.view, at3)) : "" });
87270
+ found.push({ start: token.start, name: token.name, invocation: this.passage.invocations.some((call) => call.start === token.start), argsBefore: at3 >= token.argsStart ? region.view.text.slice(viewIndex(region.view, token.argsStart), viewIndex(region.view, at3)) : "" });
87175
87271
  }
87176
87272
  for (const error62 of region.errors) {
87177
87273
  const source = region.view.text;
@@ -87181,7 +87277,7 @@ var init_markup_cursor = __esm({
87181
87277
  if (name && name[0] !== "/") found.push({ start: error62.offset, name, argsBefore: source.slice(end, viewIndex(region.view, at3)).trimStart() });
87182
87278
  }
87183
87279
  const innermost = found.filter((macro) => known(macro.name)).sort((a, b) => b.start - a.start)[0];
87184
- if (innermost) return { name: innermost.name, argsBefore: innermost.argsBefore };
87280
+ if (innermost) return innermost;
87185
87281
  }
87186
87282
  return void 0;
87187
87283
  }
@@ -87432,7 +87528,7 @@ function linkable(name) {
87432
87528
  return tokens2.length === 1 && link.type === "link" && link.target === name && !bracketLinkMismatch("label", name);
87433
87529
  }
87434
87530
  function getPassageNameCompletions(workspace) {
87435
- return workspace.passages.getEffectivePassages().filter((p) => isRuntimePassage(p) && linkable(p.name)).map((p) => ({
87531
+ return workspace.passages.getEffectivePassages().filter((p) => isRuntimePassage(p) && !isSpecialPassage(p.name) && linkable(p.name)).map((p) => ({
87436
87532
  label: p.name,
87437
87533
  kind: 18,
87438
87534
  // CompletionItemKind.Reference
@@ -87473,6 +87569,65 @@ var init_completions = __esm({
87473
87569
  }
87474
87570
  });
87475
87571
 
87572
+ // src/core/markup/macro-heads.ts
87573
+ function* pairedHeads(paired, tokens2) {
87574
+ const unclosed = [];
87575
+ for (const node2 of walkNodes(paired.nodes)) {
87576
+ if (node2.token.type === "macro") yield { name: node2.token.name, closing: false, opener: node2.token.start, start: node2.token.nameStart, end: node2.token.nameEnd };
87577
+ for (const branch of node2.body?.branches ?? []) {
87578
+ yield { name: branch.tag.name, closing: false, opener: branch.tag.start, start: branch.tag.nameStart, end: branch.tag.nameEnd };
87579
+ }
87580
+ const close = node2.body?.close;
87581
+ if (node2.token.type !== "macro") continue;
87582
+ if (close?.type === "macro" && close.name.toLowerCase() === node2.token.name.toLowerCase()) {
87583
+ yield { name: close.name, closing: true, opener: node2.token.start, start: close.nameStart, end: close.nameEnd };
87584
+ } else if (node2.body && !close) {
87585
+ unclosed.push({ name: node2.token.name.toLowerCase(), start: node2.token.start });
87586
+ }
87587
+ }
87588
+ const byStart = new Map(tokens2.map((token) => [token.start, token]));
87589
+ for (const error62 of paired.errors) {
87590
+ const token = byStart.get(error62.start);
87591
+ if (error62.code !== "stray-closer" || token?.type !== "macro" || !token.isClose) continue;
87592
+ const name = token.name.toLowerCase();
87593
+ const open2 = unclosed.find((open3) => open3.name === name && open3.start < error62.start);
87594
+ if (open2) {
87595
+ yield { name: token.name, closing: true, opener: open2.start, start: token.nameStart, end: token.nameEnd };
87596
+ }
87597
+ }
87598
+ }
87599
+ function passageMacroHeads(passage) {
87600
+ let heads = kept2.get(passage);
87601
+ if (heads) return heads;
87602
+ const isBlock = (name) => passage.doc.context.isBlock(name);
87603
+ const found = [...pairedHeads(passage.pairing, passage.tokens)];
87604
+ for (const piece of passage.pieces) {
87605
+ if (piece.kind !== "text") continue;
87606
+ found.push(...pairedHeads(pairMarkup(piece.tokens, { isBlock }), piece.tokens));
87607
+ }
87608
+ const invocations = new Set(passage.invocations.map((token) => token.start));
87609
+ heads = found.sort((a, b) => a.start - b.start).map(({ opener, ...head }) => ({ ...head, invocation: invocations.has(opener), passage, range: passage.range(head.start, head.end) }));
87610
+ kept2.set(passage, heads);
87611
+ return heads;
87612
+ }
87613
+ function documentMacroHeads(doc) {
87614
+ return doc.passages.flatMap((passage) => passage.isMarkup ? [...passageMacroHeads(passage)] : []);
87615
+ }
87616
+ function macroHeadAt(doc, position) {
87617
+ const passage = doc.passageAt(position);
87618
+ if (!passage?.isMarkup) return void 0;
87619
+ return passageMacroHeads(passage).find(({ range: { start, end } }) => start.line === position.line && position.character >= start.character && position.character <= end.character);
87620
+ }
87621
+ var kept2;
87622
+ var init_macro_heads = __esm({
87623
+ "src/core/markup/macro-heads.ts"() {
87624
+ "use strict";
87625
+ init_tooling();
87626
+ init_tree();
87627
+ kept2 = /* @__PURE__ */ new WeakMap();
87628
+ }
87629
+ });
87630
+
87476
87631
  // src/plugins/hover.ts
87477
87632
  function getHoverInfo(uri, position, workspace) {
87478
87633
  const doc = workspace.markup.get(uri);
@@ -87501,7 +87656,7 @@ function getMacroHover(passage, at3, workspace) {
87501
87656
  return { contents: parts.join("\n"), range: range2 };
87502
87657
  }
87503
87658
  const widget = workspace.widgets.getWidget(macro.name);
87504
- if (widget) return buildWidgetHover(widget, range2);
87659
+ if (widget && macroHeadAt(passage.doc, passage.position(at3))?.invocation) return buildWidgetHover(widget, range2);
87505
87660
  }
87506
87661
  return null;
87507
87662
  }
@@ -87541,6 +87696,7 @@ var init_hover = __esm({
87541
87696
  "use strict";
87542
87697
  init_text();
87543
87698
  init_tokens();
87699
+ init_macro_heads();
87544
87700
  init_markup_symbols();
87545
87701
  VARIABLE_KINDS = {
87546
87702
  $: "Story variable",
@@ -87678,6 +87834,7 @@ ${p.documentation}`) : []
87678
87834
  activeParameter: positional ? argument : 0
87679
87835
  };
87680
87836
  }
87837
+ if (enclosing.invocation === false) return null;
87681
87838
  const widget = workspace.widgets.getWidget(macroName);
87682
87839
  if (widget && widget.params.length > 0) {
87683
87840
  const paramLabels = widget.params;
@@ -87741,63 +87898,6 @@ var init_signature = __esm({
87741
87898
  }
87742
87899
  });
87743
87900
 
87744
- // src/core/markup/macro-heads.ts
87745
- function* pairedHeads(paired, tokens2) {
87746
- const unclosed = [];
87747
- for (const node2 of walkNodes(paired.nodes)) {
87748
- if (node2.token.type === "macro") yield { name: node2.token.name, closing: false, start: node2.token.nameStart, end: node2.token.nameEnd };
87749
- for (const branch of node2.body?.branches ?? []) {
87750
- yield { name: branch.tag.name, closing: false, start: branch.tag.nameStart, end: branch.tag.nameEnd };
87751
- }
87752
- const close = node2.body?.close;
87753
- if (node2.token.type !== "macro") continue;
87754
- if (close?.type === "macro" && close.name.toLowerCase() === node2.token.name.toLowerCase()) {
87755
- yield { name: close.name, closing: true, start: close.nameStart, end: close.nameEnd };
87756
- } else if (node2.body && !close) {
87757
- unclosed.push({ name: node2.token.name.toLowerCase(), start: node2.token.start });
87758
- }
87759
- }
87760
- const byStart = new Map(tokens2.map((token) => [token.start, token]));
87761
- for (const error62 of paired.errors) {
87762
- const token = byStart.get(error62.start);
87763
- if (error62.code !== "stray-closer" || token?.type !== "macro" || !token.isClose) continue;
87764
- const name = token.name.toLowerCase();
87765
- if (unclosed.some((open2) => open2.name === name && open2.start < error62.start)) {
87766
- yield { name: token.name, closing: true, start: token.nameStart, end: token.nameEnd };
87767
- }
87768
- }
87769
- }
87770
- function passageMacroHeads(passage) {
87771
- let heads = kept2.get(passage);
87772
- if (heads) return heads;
87773
- const isBlock = (name) => passage.doc.context.isBlock(name);
87774
- const found = [...pairedHeads(passage.pairing, passage.tokens)];
87775
- for (const piece of passage.pieces) {
87776
- if (piece.kind !== "text") continue;
87777
- found.push(...pairedHeads(pairMarkup(piece.tokens, { isBlock }), piece.tokens));
87778
- }
87779
- heads = found.sort((a, b) => a.start - b.start).map((head) => ({ ...head, passage, range: passage.range(head.start, head.end) }));
87780
- kept2.set(passage, heads);
87781
- return heads;
87782
- }
87783
- function documentMacroHeads(doc) {
87784
- return doc.passages.flatMap((passage) => passage.isMarkup ? [...passageMacroHeads(passage)] : []);
87785
- }
87786
- function macroHeadAt(doc, position) {
87787
- const passage = doc.passageAt(position);
87788
- if (!passage?.isMarkup) return void 0;
87789
- return passageMacroHeads(passage).find(({ range: { start, end } }) => start.line === position.line && position.character >= start.character && position.character <= end.character);
87790
- }
87791
- var kept2;
87792
- var init_macro_heads = __esm({
87793
- "src/core/markup/macro-heads.ts"() {
87794
- "use strict";
87795
- init_tooling();
87796
- init_tree();
87797
- kept2 = /* @__PURE__ */ new WeakMap();
87798
- }
87799
- });
87800
-
87801
87901
  // src/plugins/definition.ts
87802
87902
  function getDefinition(uri, position, workspace) {
87803
87903
  const text3 = workspace.documents.getText(uri);
@@ -87824,7 +87924,7 @@ function getPassageRefDefinition(uri, position, workspace) {
87824
87924
  function getWidgetDefinition(uri, position, workspace) {
87825
87925
  const doc = workspace.markup.get(uri);
87826
87926
  const head = doc && macroHeadAt(doc, position);
87827
- if (!head) return null;
87927
+ if (!head?.invocation) return null;
87828
87928
  if (workspace.getEffectiveMacro(head.name)) return null;
87829
87929
  const widget = workspace.widgets.getWidget(head.name);
87830
87930
  return widget ? { uri: widget.uri, range: widget.range } : null;
@@ -87887,7 +87987,7 @@ function findReferences(uri, position, workspace, includeDeclaration) {
87887
87987
  if (declaration) return findWidgetReferences(declaration.name, workspace, includeDeclaration);
87888
87988
  const doc = workspace.markup.get(uri);
87889
87989
  const head = doc && macroHeadAt(doc, position);
87890
- if (head) {
87990
+ if (head?.invocation) {
87891
87991
  const widget = workspace.widgets.getWidget(head.name);
87892
87992
  if (!workspace.getEffectiveMacro(head.name) && widget && (!head.closing || workspace.isContainer(head.name))) {
87893
87993
  return findWidgetReferences(head.name, workspace, includeDeclaration);
@@ -87957,17 +88057,11 @@ function findWidgetReferences(widgetName2, workspace, includeDeclaration, includ
87957
88057
  if (workspace.getEffectiveMacro(widgetName2)) return locations;
87958
88058
  const lowerName2 = widgetName2.toLowerCase();
87959
88059
  const isBlock = workspace.isContainer(widgetName2);
87960
- for (const docUri of workspace.documents.getUris()) {
87961
- const doc = workspace.markup.get(docUri);
87962
- if (!doc) continue;
87963
- for (const head of documentMacroHeads(doc)) {
87964
- if (head.closing && (!isBlock || !includeClosingTags)) continue;
87965
- if (head.name.toLowerCase() === lowerName2) {
87966
- locations.push({ uri: docUri, range: head.range });
87967
- }
87968
- }
87969
- }
87970
- return locations;
88060
+ const calls = workspace.documents.getUris().flatMap((uri) => {
88061
+ const doc = workspace.markup.get(uri);
88062
+ return doc ? documentMacroHeads(doc).filter((head) => head.invocation && head.name.toLowerCase() === lowerName2 && (!head.closing || isBlock && includeClosingTags)).map((head) => ({ uri, range: head.range })) : [];
88063
+ });
88064
+ return locations.concat(calls);
87971
88065
  }
87972
88066
  function toLspRange3(r) {
87973
88067
  return {
@@ -88292,7 +88386,7 @@ function resolveSymbolAtCursor(uri, position, workspace) {
88292
88386
  if (declaration) return { kind: "widget", name: declaration.name, range: declaration.nameRange };
88293
88387
  const doc = workspace.markup.get(uri);
88294
88388
  const head = doc && macroHeadAt(doc, position);
88295
- if (head) {
88389
+ if (head?.invocation) {
88296
88390
  const widget = workspace.widgets.getWidget(head.name);
88297
88391
  if (!workspace.getEffectiveMacro(head.name) && widget && (!head.closing || workspace.isContainer(head.name))) {
88298
88392
  return { kind: "widget", name: head.name, range: head.range };
@@ -88479,7 +88573,7 @@ function computeInlayHints(uri, range2, workspace) {
88479
88573
  function addWidgetParamHints(passages, inRange, workspace, hints) {
88480
88574
  if (workspace.widgets.getAllWidgets().length === 0) return;
88481
88575
  for (const passage of passages) {
88482
- for (const token of macroTokens(passage)) {
88576
+ for (const token of passage.invocations) {
88483
88577
  if (token.isClose || token.rawArgs === "") continue;
88484
88578
  if (!inRange(passage.position(token.start).line)) continue;
88485
88579
  if (workspace.getEffectiveMacro(token.name)) continue;
@@ -88518,7 +88612,6 @@ var init_inlay_hints = __esm({
88518
88612
  "use strict";
88519
88613
  init_tooling();
88520
88614
  init_source_view();
88521
- init_tokens();
88522
88615
  inlayHintsPlugin = {
88523
88616
  id: "inlay-hints",
88524
88617
  capabilities: {
@@ -88685,9 +88778,13 @@ var init_semantic_tokens = __esm({
88685
88778
  import { readFileSync as readFileSync7 } from "node:fs";
88686
88779
  import { basename as basename2, dirname as dirname5, join as join4 } from "node:path";
88687
88780
  import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL6 } from "node:url";
88781
+ import { isDeepStrictEqual } from "node:util";
88688
88782
  function computeCodeActions(uri, diagnostics, workspace, options = {}) {
88689
88783
  const actions = [];
88784
+ const current2 = computeDiagnostics(uri, workspace);
88690
88785
  for (const diag of diagnostics) {
88786
+ const fresh = current2.find((candidate) => candidate.code === diag.code && isDeepStrictEqual(candidate.range, diag.range) && isDeepStrictEqual(candidate.data, diag.data));
88787
+ if (!fresh && diag.code !== DiagnosticCode.NoStoryVariables) continue;
88691
88788
  switch (diag.code) {
88692
88789
  case DiagnosticCode.UndefinedMacro:
88693
88790
  actions.push(...fixUndefinedMacro(uri, diag, workspace, options));
@@ -89017,6 +89114,7 @@ var init_code_actions = __esm({
89017
89114
  "use strict";
89018
89115
  import_yaml3 = __toESM(require_dist(), 1);
89019
89116
  init_diagnostic_codes();
89117
+ init_diagnostics();
89020
89118
  init_config_loader();
89021
89119
  init_story_variables_owner();
89022
89120
  init_macro_sources();