@rohal12/spindle-lsp 0.15.11 → 0.15.13

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.11";
58
+ VERSION = false ? pkg.version : "0.15.13";
59
59
  }
60
60
  });
61
61
 
@@ -31356,6 +31356,15 @@ var init_passage_parser = __esm({
31356
31356
 
31357
31357
  // src/core/workspace/passage-index.ts
31358
31358
  import { createRequire as createSettingsRequire } from "node:module";
31359
+ function compareSourceUris(left, right) {
31360
+ const a = left.split("/").map(decodeURIComponent);
31361
+ const b = right.split("/").map(decodeURIComponent);
31362
+ for (let i2 = 0; i2 < Math.min(a.length, b.length); i2++) {
31363
+ const order = Buffer.compare(Buffer.from(a[i2], "utf8"), Buffer.from(b[i2], "utf8"));
31364
+ if (order !== 0) return order;
31365
+ }
31366
+ return a.length - b.length;
31367
+ }
31359
31368
  var PassageIndex;
31360
31369
  var init_passage_index = __esm({
31361
31370
  "src/core/workspace/passage-index.ts"() {
@@ -31368,7 +31377,7 @@ var init_passage_index = __esm({
31368
31377
  /** The documents' URIs in a fixed (sorted) order, so lookups never depend on the order documents were opened. */
31369
31378
  sortedUris = null;
31370
31379
  uris() {
31371
- return this.sortedUris ??= [...this.byUri.keys()].sort();
31380
+ return this.sortedUris ??= [...this.byUri.keys()].sort(compareSourceUris);
31372
31381
  }
31373
31382
  /**
31374
31383
  * Parse all passage headers in the given document text and
@@ -31443,6 +31452,14 @@ var init_passage_index = __esm({
31443
31452
  }
31444
31453
  return found;
31445
31454
  }
31455
+ /** The compiler replaces duplicate passages with the later source declaration. */
31456
+ getEffectivePassage(name) {
31457
+ return this.getPassages(name).at(-1);
31458
+ }
31459
+ /** Later duplicates replace the original slot, before any runtime role exclusions. */
31460
+ getEffectivePassages() {
31461
+ return [...new Map(this.getAllPassages().map((passage) => [passage.name, passage])).values()];
31462
+ }
31446
31463
  /** Get the passage containing a specific line in a document. */
31447
31464
  getPassageAt(uri, line) {
31448
31465
  const passages = this.byUri.get(uri);
@@ -31475,11 +31492,11 @@ var init_passage_index = __esm({
31475
31492
  }
31476
31493
  /** Get the StoryVariables passage, if any. */
31477
31494
  getStoryVariables() {
31478
- return this.getPassage("StoryVariables");
31495
+ return this.getEffectivePassage("StoryVariables");
31479
31496
  }
31480
31497
  /** Get the StoryTransients passage, if any. */
31481
31498
  getStoryTransients() {
31482
- return this.getPassage("StoryTransients");
31499
+ return this.getEffectivePassage("StoryTransients");
31483
31500
  }
31484
31501
  /** Get the StoryInit passage, if any. */
31485
31502
  getStoryInit() {
@@ -32902,28 +32919,44 @@ var init_widget_registry = __esm({
32902
32919
  /** Per-URI set of lower-cased macro names opened in that document. */
32903
32920
  invokedByUri = /* @__PURE__ */ new Map();
32904
32921
  /**
32905
- * Read the widget definitions of `passages` (all of them, in workspace
32906
- * order). Public tooling selects the closed definitions in runtime phase
32907
- * order, preserving their identities so the original source is retained.
32922
+ * Read effective story passages in compiler order. Superseded passages may
32923
+ * supply editor navigation while a replacement is incomplete; they cannot
32924
+ * register runtime widgets. Tooling selects definitions in runtime phase order.
32908
32925
  */
32909
- scan(passages, macros) {
32926
+ scan(passages, macros, superseded = []) {
32910
32927
  this.widgets.clear();
32911
32928
  this.incomplete.clear();
32912
- const sources = widgetSources(passages, [...macros]);
32929
+ const metadata2 = [...macros];
32930
+ const sources = widgetSources(passages, metadata2);
32913
32931
  const effective = new Set(effectiveWidgetDefinitions([...sources.keys()]));
32932
+ const incompletePassages = /* @__PURE__ */ new Map();
32933
+ const located = (definition2, markup) => ({
32934
+ tooling: definition2,
32935
+ name: definition2.name,
32936
+ params: definition2.params,
32937
+ uri: markup.passage.uri,
32938
+ range: markup.range(definition2.start, definition2.end),
32939
+ nameRange: markup.range(definition2.nameStart, definition2.nameEnd),
32940
+ block: definition2.block
32941
+ });
32914
32942
  for (const [definition2, markup] of sources) {
32915
- const widget = {
32916
- tooling: definition2,
32917
- name: definition2.name,
32918
- params: definition2.params,
32919
- uri: markup.passage.uri,
32920
- range: markup.range(definition2.start, definition2.end),
32921
- nameRange: markup.range(definition2.nameStart, definition2.nameEnd),
32922
- block: definition2.block
32923
- };
32943
+ const widget = located(definition2, markup);
32924
32944
  const key = definition2.name.toLowerCase();
32925
32945
  if (effective.has(definition2)) this.widgets.set(key, widget);
32926
- else if (definition2.closeStart === void 0) this.incomplete.set(key, widget);
32946
+ else if (definition2.closeStart === void 0) {
32947
+ this.incomplete.set(key, widget);
32948
+ incompletePassages.set(key, markup.passage.name);
32949
+ }
32950
+ }
32951
+ if (incompletePassages.size > 0) {
32952
+ const previous2 = widgetSources(superseded, metadata2);
32953
+ const candidates = [...previous2.keys()].filter((definition2) => {
32954
+ const name = definition2.name.toLowerCase();
32955
+ return !this.widgets.has(name) && incompletePassages.get(name) === previous2.get(definition2).passage.name;
32956
+ });
32957
+ for (const definition2 of effectiveWidgetDefinitions(candidates)) {
32958
+ this.incomplete.set(definition2.name.toLowerCase(), located(definition2, previous2.get(definition2)));
32959
+ }
32927
32960
  }
32928
32961
  }
32929
32962
  /** The effective definition, or an incomplete declaration while its first definition is being typed. */
@@ -33863,21 +33896,24 @@ var init_workspace_model = __esm({
33863
33896
  refreshDiscoveredMacros() {
33864
33897
  const found = [];
33865
33898
  const init = [];
33866
- for (const uri of this.documents.getUris().sort()) {
33899
+ const seen = /* @__PURE__ */ new Set();
33900
+ const effective = new Map(this.passages.getEffectivePassages().map((passage) => [passage.name, passage]));
33901
+ for (const uri of this.documents.getUris().sort(compareSourceUris)) {
33867
33902
  const text3 = this.documents.getText(uri);
33868
33903
  if (!text3) continue;
33869
33904
  if (isMacroSource(uri)) {
33870
33905
  found.push(...discoverMacros(text3));
33871
33906
  continue;
33872
33907
  }
33873
- let lines;
33874
- for (const passage of this.passages.getPassagesInDocument(uri)) {
33908
+ for (const source of this.passages.getPassagesInDocument(uri)) {
33909
+ if (seen.has(source.name)) continue;
33910
+ seen.add(source.name);
33911
+ const passage = effective.get(source.name);
33875
33912
  if (passage.tags?.includes("Twine.private")) continue;
33876
33913
  const isStoryInit = passage.name === "StoryInit" && isRuntimePassage(passage);
33877
33914
  const isScript = passage.tags?.includes("script") ?? false;
33878
33915
  if (!isStoryInit && !isScript) continue;
33879
- lines ??= text3.split("\n");
33880
- const content2 = lines.slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
33916
+ const content2 = this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage)?.content ?? "";
33881
33917
  if (isScript) found.push(...discoverMacros(content2));
33882
33918
  else init.push(...storyInitMacros(content2));
33883
33919
  }
@@ -33898,7 +33934,7 @@ var init_workspace_model = __esm({
33898
33934
  this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
33899
33935
  this._target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
33900
33936
  const declarations = (name) => {
33901
- const passage = this.passages.getPassage(name);
33937
+ const passage = this.passages.getEffectivePassage(name);
33902
33938
  return passage && isRuntimePassage(passage) ? this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage) : void 0;
33903
33939
  };
33904
33940
  const variables = declarations("StoryVariables");
@@ -33913,15 +33949,18 @@ var init_workspace_model = __esm({
33913
33949
  });
33914
33950
  const markups = /* @__PURE__ */ new Map();
33915
33951
  for (const doc of readDocuments()) for (const passage of doc.passages) markups.set(passage.passage, passage);
33952
+ const effective = new Set(this.passages.getEffectivePassages());
33916
33953
  this.widgets.scan(
33917
- this.passages.getAllPassages().flatMap((passage) => markups.get(passage) ?? []),
33918
- this.macros.toolingMacros()
33954
+ [...effective].flatMap((passage) => markups.get(passage) ?? []),
33955
+ this.macros.toolingMacros(),
33956
+ [...markups].filter(([passage]) => !effective.has(passage)).map(([, markup]) => markup)
33919
33957
  );
33920
33958
  this.markup.invalidate();
33921
33959
  this.widgets.clearInvocations();
33922
33960
  for (const doc of readDocuments()) {
33923
- this.variables.scanDocument(doc.uri, doc.text, doc);
33924
- this.widgets.recordInvocations(doc.uri, doc.passages);
33961
+ const passages = doc.passages.filter((p) => effective.has(p.passage));
33962
+ this.variables.scanDocument(doc.uri, doc.text, { passages });
33963
+ this.widgets.recordInvocations(doc.uri, passages);
33925
33964
  }
33926
33965
  }
33927
33966
  /** All runtime metadata, with real macros taking precedence over widgets. */
@@ -34205,6 +34244,7 @@ var init_passage_refs = __esm({
34205
34244
 
34206
34245
  // src/core/parsing/link-parser.ts
34207
34246
  function markupOfText(text3) {
34247
+ if (typeof text3 !== "string") return text3;
34208
34248
  if (lastMarkup?.text === text3) return lastMarkup;
34209
34249
  const markup = readText2(text3);
34210
34250
  lastMarkup = markup;
@@ -34228,7 +34268,7 @@ function readText2(text3) {
34228
34268
  return new DocumentMarkup(TEXT_URI, text3, passages, TEXT_CONTEXT);
34229
34269
  }
34230
34270
  function findLinkRuntimeMismatches(text3) {
34231
- if (!text3.includes("[[")) return [];
34271
+ if (!(typeof text3 === "string" ? text3 : text3.text).includes("[[")) return [];
34232
34272
  const found = [];
34233
34273
  for (const ref2 of documentPassageRefs(markupOfText(text3))) {
34234
34274
  if (ref2.form !== "bracket") continue;
@@ -34298,7 +34338,9 @@ function computeDiagnostics(uri, workspace, options) {
34298
34338
  }
34299
34339
  };
34300
34340
  const story = storyReport(workspace);
34341
+ const effective = new Set(workspace.passages.getEffectivePassages());
34301
34342
  for (const passage of doc.passages) {
34343
+ if (!effective.has(passage.passage)) continue;
34302
34344
  attempt(() => diagnostics.push(...markupDiagnostics(passage, story, workspace)));
34303
34345
  attempt(() => diagnostics.push(...variableDiagnostics(passage, story)));
34304
34346
  attempt(() => diagnostics.push(...structureDiagnostics(passage, workspace)));
@@ -34308,7 +34350,10 @@ function computeDiagnostics(uri, workspace, options) {
34308
34350
  attempt(() => diagnostics.push(...versionDiagnostics(uri, doc, workspace)));
34309
34351
  attempt(() => diagnostics.push(...storyVariablesDiagnostics(uri, doc, story, workspace)));
34310
34352
  attempt(() => diagnostics.push(...arrayMemberDiagnostics(uri, workspace)));
34311
- attempt(() => diagnostics.push(...linkDiagnostics(doc)));
34353
+ attempt(() => diagnostics.push(...linkDiagnostics(doc).filter((diagnostic) => {
34354
+ const passage = doc.passageAt(diagnostic.range.start);
34355
+ return passage !== void 0 && effective.has(passage.passage);
34356
+ })));
34312
34357
  attempt(() => diagnostics.push(...unusedWidgetDiagnostics(doc, story)));
34313
34358
  if (options?.maxLineLength) {
34314
34359
  const maxLength = options.maxLineLength;
@@ -34335,12 +34380,13 @@ function buildReport(documents, workspace) {
34335
34380
  const storeVar = workspace.macros.getMacro(macro.name)?.storeVar;
34336
34381
  return storeVar ? { ...macro, storeVar } : macro;
34337
34382
  });
34338
- const passages = [];
34383
+ const markups = /* @__PURE__ */ new Map();
34339
34384
  for (const doc of documents) {
34340
34385
  for (const passage of doc.passages) {
34341
- if (isRuntimePassage(passage.passage)) passages.push(passage);
34386
+ markups.set(passage.passage, passage);
34342
34387
  }
34343
34388
  }
34389
+ const passages = workspace.passages.getEffectivePassages().filter(isRuntimePassage).flatMap((passage) => markups.get(passage) ?? []);
34344
34390
  const markup = /* @__PURE__ */ new Map();
34345
34391
  const validationMacros = toolingMacros.map((macro) => workspace.macros.hasConfiguredParameters(macro.name) ? { ...macro, widget: void 0 } : macro);
34346
34392
  const found = validateStoryMarkup(
@@ -34376,6 +34422,7 @@ function buildReport(documents, workspace) {
34376
34422
  }
34377
34423
  return {
34378
34424
  documents,
34425
+ passages,
34379
34426
  macros,
34380
34427
  markup,
34381
34428
  widgets,
@@ -34489,11 +34536,11 @@ function storyVariablesDiagnostics(uri, doc, story, workspace) {
34489
34536
  }
34490
34537
  function storyUsesVariables(story) {
34491
34538
  const excluded = /* @__PURE__ */ new Set(["StoryVariables", "StoryInit", "StoryData", "StoryScript", "StoryInterface"]);
34492
- return story.documents.some((doc) => doc.passages.some((passage) => passage.isMarkup && !excluded.has(passage.passage.name) && validateVariableReferences(
34539
+ return story.passages.some((passage) => passage.isMarkup && !excluded.has(passage.passage.name) && validateVariableReferences(
34493
34540
  [{ name: passage.passage.name, content: passage.content }],
34494
34541
  { variables: /* @__PURE__ */ new Map() },
34495
34542
  story.macros
34496
- ).some((diagnostic) => diagnostic.code === "undeclared-variable")));
34543
+ ).some((diagnostic) => diagnostic.code === "undeclared-variable"));
34497
34544
  }
34498
34545
  function arrayMemberDiagnostics(uri, workspace) {
34499
34546
  return workspace.variables.getArrayMemberAccesses(uri).map((access) => {
@@ -34521,7 +34568,7 @@ function structureDiagnostics(passage, workspace) {
34521
34568
  if (token.type === "macro" && !token.isClose) {
34522
34569
  diagnostics.push(...parentDiagnostics(passage, token, ancestors, workspace));
34523
34570
  diagnostics.push(...childDiagnostics(passage, node, token, workspace));
34524
- diagnostics.push(...loopDiagnostics(passage, token, ancestors));
34571
+ diagnostics.push(...loopDiagnostics(passage, token, ancestors, workspace));
34525
34572
  }
34526
34573
  if (!node.body) continue;
34527
34574
  const inside = [...ancestors, {
@@ -34647,13 +34694,13 @@ function statementEnd(code, from) {
34647
34694
  }
34648
34695
  return code.length;
34649
34696
  }
34650
- function loopDiagnostics(passage, token, ancestors) {
34697
+ function loopDiagnostics(passage, token, ancestors, workspace) {
34651
34698
  const macro = token.name.toLowerCase();
34652
- if (macro !== "computed" && macro !== "set" || !token.rawArgs) return [];
34699
+ if (macro !== "computed" && macro !== "set" || !token.rawArgs || workspace.macros.getMacro(macro)?.source !== "builtin") return [];
34653
34700
  let loop;
34654
34701
  for (let i2 = ancestors.length - 1; i2 >= 0; i2--) {
34655
34702
  const ancestor = ancestors[i2];
34656
- if (ancestor.element) continue;
34703
+ if (ancestor.element || workspace.macros.getMacro(ancestor.name)?.source !== "builtin") continue;
34657
34704
  if (DEFERRED_CONTAINERS.has(ancestor.name)) return [];
34658
34705
  if (ancestor.name === "for") {
34659
34706
  loop = ancestor;
@@ -34694,7 +34741,7 @@ function isReadInBody(passage, bodyStart, bodyEnd, name, macroStart, macroEnd) {
34694
34741
  }
34695
34742
  function argumentDiagnostics(passage, workspace) {
34696
34743
  const diagnostics = [];
34697
- const passageNames = workspace.passages.getAllPassages().filter(isRuntimePassage).map((candidate) => candidate.name);
34744
+ const passageNames = workspace.passages.getEffectivePassages().filter(isRuntimePassage).map((candidate) => candidate.name);
34698
34745
  for (const token of passage.isMarkup ? macroTokens(passage) : []) {
34699
34746
  if (token.type !== "macro" || token.isClose) continue;
34700
34747
  const info = workspace.macros.getMacro(token.name);
@@ -34747,7 +34794,7 @@ function widgetDiagnostics(passage, story, workspace) {
34747
34794
  }
34748
34795
  for (const piece of passage.pieces) {
34749
34796
  if (piece.kind !== "passage" || piece.macro !== "include") continue;
34750
- const target = workspace.passages.getPassage(piece.name);
34797
+ const target = workspace.passages.getEffectivePassage(piece.name);
34751
34798
  if (!target?.tags?.includes("widget")) continue;
34752
34799
  const defined = story.widgetList.filter((widget) => widget.passage.passage === target).map((widget) => widget.definition.name);
34753
34800
  if (defined.length === 0) continue;
@@ -34782,14 +34829,14 @@ function describeNavigation(passage) {
34782
34829
  }
34783
34830
  function linkDiagnostics(doc) {
34784
34831
  const diagnostics = [];
34785
- for (const link of findLinkRuntimeMismatches(doc.text)) {
34832
+ for (const link of findLinkRuntimeMismatches(doc)) {
34786
34833
  diagnostics.push(makeDiag(
34787
34834
  link.range,
34788
34835
  DiagnosticCode.LinkRuntimeMismatch,
34789
34836
  `Spindle reads this link differently from how it is written: the link macro reads the label as ${JSON.stringify(link.runtime.display)} and a click ${describeNavigation(link.runtime.passage)}, not to ${JSON.stringify(link.target)}.`
34790
34837
  ));
34791
34838
  }
34792
- for (const found of findLiteralLinkInterpolations(doc.text)) {
34839
+ for (const found of findLiteralLinkInterpolations(doc)) {
34793
34840
  diagnostics.push(makeDiag(
34794
34841
  found.range,
34795
34842
  DiagnosticCode.LiteralLinkInterpolation,
@@ -79932,7 +79979,7 @@ function linkable(name) {
79932
79979
  return tokens2.length === 1 && link.type === "link" && link.target === name && !bracketLinkMismatch("label", name);
79933
79980
  }
79934
79981
  function getPassageNameCompletions(workspace) {
79935
- return workspace.passages.getAllPassages().filter((p) => linkable(p.name)).map((p) => ({
79982
+ return workspace.passages.getEffectivePassages().filter((p) => isRuntimePassage(p) && linkable(p.name)).map((p) => ({
79936
79983
  label: p.name,
79937
79984
  kind: 18,
79938
79985
  // CompletionItemKind.Reference
@@ -79947,6 +79994,7 @@ var init_completions = __esm({
79947
79994
  init_widgets();
79948
79995
  init_passage_refs();
79949
79996
  init_link_runtime();
79997
+ init_passage_parser();
79950
79998
  init_markup_cursor();
79951
79999
  init_markup_symbols();
79952
80000
  VARIABLE_DETAIL = { $: "story variable", "%": "transient variable", _: "temporary variable", "@": "local/parameter variable" };
@@ -81898,7 +81946,7 @@ __export(server_exports2, {
81898
81946
  });
81899
81947
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL6 } from "node:url";
81900
81948
  import { readFileSync as readFileSync8 } from "node:fs";
81901
- import { basename as basename3 } from "node:path";
81949
+ import { basename as basename3, dirname as dirname6 } from "node:path";
81902
81950
  function uriToFsPath(uri) {
81903
81951
  try {
81904
81952
  return fileURLToPath5(uri);
@@ -82035,7 +82083,7 @@ function startServer(args2) {
82035
82083
  if (workspace) {
82036
82084
  try {
82037
82085
  const fsPath = uriToFsPath(textDocument.uri);
82038
- if (isExcludedProjectFile(fsPath, workspaceRoot)) {
82086
+ if (isExcludedProjectFile(fsPath, workspaceRoot) || !canDiscoverProjectPath(fsPath, workspaceRoot ?? dirname6(fsPath))) {
82039
82087
  workspace.documents.close(textDocument.uri);
82040
82088
  } else {
82041
82089
  const text3 = readFileSync8(fsPath, "utf-8");
@@ -82066,15 +82114,16 @@ function startServer(args2) {
82066
82114
  configChanged = true;
82067
82115
  continue;
82068
82116
  }
82069
- if (isExcludedProjectFile(uriToFsPath(change.uri), workspaceRoot)) {
82117
+ if (documents.has(change.uri)) continue;
82118
+ const fsPath = uriToFsPath(change.uri);
82119
+ if (isExcludedProjectFile(fsPath, workspaceRoot) || !canDiscoverProjectPath(fsPath, workspaceRoot ?? dirname6(fsPath))) {
82120
+ workspace.documents.close(change.uri);
82070
82121
  continue;
82071
82122
  }
82072
- if (documents.has(change.uri)) continue;
82073
82123
  if (change.type === import_node.FileChangeType.Deleted) {
82074
82124
  workspace.documents.close(change.uri);
82075
82125
  } else {
82076
82126
  try {
82077
- const fsPath = uriToFsPath(change.uri);
82078
82127
  const text3 = readFileSync8(fsPath, "utf-8");
82079
82128
  if (workspace.documents.has(change.uri)) {
82080
82129
  workspace.documents.update(change.uri, text3);