@rohal12/spindle-lsp 0.14.1 → 0.14.3

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
@@ -3039,6 +3039,40 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
3039
3039
  }
3040
3040
  });
3041
3041
 
3042
+ // src/core/workspace/file-inputs.ts
3043
+ import { readFileSync, statSync } from "node:fs";
3044
+ import { resolve } from "node:path";
3045
+ async function resolveFiles(pattern, cwd) {
3046
+ let input = pattern;
3047
+ let directory = cwd;
3048
+ const literal3 = resolve(cwd, pattern);
3049
+ try {
3050
+ const stat = statSync(literal3);
3051
+ if (stat.isFile()) return [literal3];
3052
+ if (stat.isDirectory()) {
3053
+ directory = literal3;
3054
+ input = "**/*.{tw,twee}";
3055
+ }
3056
+ } catch (error48) {
3057
+ const code = error48.code;
3058
+ if (code !== "ENOENT" && code !== "ENOTDIR") throw error48;
3059
+ }
3060
+ return [...new Set(await Ze(input, { cwd: directory, absolute: true, nodir: true }))];
3061
+ }
3062
+ function readFileContents(files) {
3063
+ return new Map(files.map((file2) => [file2, readFileSync(file2, "utf8")]));
3064
+ }
3065
+ async function resolveInputs(patterns, cwd) {
3066
+ const files = await Promise.all(patterns.map((pattern) => resolveFiles(pattern, cwd)));
3067
+ return [...new Set(files.flat())];
3068
+ }
3069
+ var init_file_inputs = __esm({
3070
+ "src/core/workspace/file-inputs.ts"() {
3071
+ "use strict";
3072
+ init_index_min();
3073
+ }
3074
+ });
3075
+
3042
3076
  // node_modules/@rohal12/spindle/dist/pkg/story-variables.js
3043
3077
  function isSigil(c) {
3044
3078
  return c !== void 0 && SIGIL_CHARS.has(c);
@@ -16619,7 +16653,7 @@ var init_tooling = __esm({
16619
16653
 
16620
16654
  // src/core/workspace/document-store.ts
16621
16655
  import { EventEmitter } from "node:events";
16622
- import { readFileSync } from "node:fs";
16656
+ import { readFileSync as readFileSync2 } from "node:fs";
16623
16657
  import { pathToFileURL } from "node:url";
16624
16658
  function stripBom(text2) {
16625
16659
  return text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
@@ -16667,7 +16701,7 @@ var init_document_store = __esm({
16667
16701
  loadFromDisk(paths) {
16668
16702
  for (const filePath of paths) {
16669
16703
  try {
16670
- const text2 = readFileSync(filePath, "utf-8");
16704
+ const text2 = readFileSync2(filePath, "utf-8");
16671
16705
  const uri = pathToFileURL(filePath).href;
16672
16706
  this.open(uri, text2);
16673
16707
  } catch {
@@ -16681,31 +16715,32 @@ var init_document_store = __esm({
16681
16715
  // src/core/parsing/passage-parser.ts
16682
16716
  function parsePassageHeader(line, lineNumber) {
16683
16717
  const bomLength = lineNumber === 0 && line.charCodeAt(0) === 65279 ? 1 : 0;
16684
- const escaped = line.slice(bomLength).replace(/\\./g, "ec");
16685
- const match = passageHeaderRegex.exec(escaped);
16686
- if (!match) return null;
16687
- const prefix = line.slice(0, bomLength) + (match[1] ?? "");
16688
- const rawName = match[2] ?? "";
16689
- const rawTags = match[3] ?? "";
16690
- const rawMeta = match[4] ?? "";
16691
- const nameStart = prefix.length;
16692
- const nameEnd = nameStart + rawName.length;
16693
- const name = line.substring(nameStart, nameEnd).trim();
16718
+ const escaped = line.replace(/\\./g, "ec");
16719
+ const prefix = /^::\s*/.exec(escaped.slice(bomLength));
16720
+ if (!prefix) return null;
16721
+ const nameStart = bomLength + prefix[0].length;
16722
+ const delimiter = /[\[\]{}]/g;
16723
+ delimiter.lastIndex = nameStart;
16724
+ const first = delimiter.exec(escaped);
16725
+ const nameEnd = first?.index ?? line.length;
16726
+ const name = line.slice(nameStart, nameEnd).trim();
16694
16727
  if (!name) return null;
16695
- const escapedName = name.replace(/\\./g, "ec");
16696
- if (/[\[\]\{\}]/.test(escapedName)) return null;
16697
- const tagsStr = rawTags.trim();
16728
+ let rest = nameEnd;
16698
16729
  let tags = [];
16699
- if (tagsStr) {
16700
- const inner = tagsStr.substring(1, tagsStr.length - 1).trim();
16701
- if (/[\[\]\{\}]/.test(inner.replace(/\\./g, "ec"))) return null;
16702
- tags = inner ? inner.split(/\s+/) : [];
16730
+ if (escaped[rest] === "[") {
16731
+ const end = escaped.indexOf("]", rest + 1);
16732
+ if (end === -1 || /[\[{}]/.test(escaped.slice(rest + 1, end))) return null;
16733
+ const decoded = line.slice(rest + 1, end).replace(/\\(.)/g, "$1").trim();
16734
+ tags = decoded ? decoded.split(/\s+/) : [];
16735
+ rest = end + 1;
16736
+ while (rest < line.length && /\s/.test(escaped[rest])) rest++;
16703
16737
  }
16704
16738
  let meta3;
16705
- const metaStr = rawMeta.trim();
16706
- if (metaStr) {
16739
+ if (rest < line.length) {
16740
+ const raw = line.slice(rest).trim();
16741
+ if (escaped[rest] !== "{" || !raw.endsWith("}")) return null;
16707
16742
  try {
16708
- meta3 = JSON.parse(line.substring(nameStart + rawName.length + rawTags.length, nameStart + rawName.length + rawTags.length + rawMeta.length).trim());
16743
+ meta3 = JSON.parse(raw);
16709
16744
  } catch {
16710
16745
  return null;
16711
16746
  }
@@ -16738,11 +16773,10 @@ function isScriptOrStylesheetPassage(passage) {
16738
16773
  function isMarkupPassage(passage) {
16739
16774
  return !(passage.name !== void 0 && NON_MARKUP_PASSAGES.has(passage.name)) && !isScriptOrStylesheetPassage(passage);
16740
16775
  }
16741
- var passageHeaderRegex, SPECIAL_PASSAGES, CODE_PASSAGE_TAGS, RESERVED_PASSAGE_NAMES, NON_MARKUP_PASSAGES;
16776
+ var SPECIAL_PASSAGES, CODE_PASSAGE_TAGS, RESERVED_PASSAGE_NAMES, NON_MARKUP_PASSAGES;
16742
16777
  var init_passage_parser = __esm({
16743
16778
  "src/core/parsing/passage-parser.ts"() {
16744
16779
  "use strict";
16745
- passageHeaderRegex = /^(::\s+)(.*?)(\[.*?\]\s*)?(\{.*?\}\s*)?\r?$/;
16746
16780
  SPECIAL_PASSAGES = /* @__PURE__ */ new Set([
16747
16781
  "StoryVariables",
16748
16782
  "StoryInit",
@@ -24255,8 +24289,8 @@ var require_dist = __commonJS({
24255
24289
  });
24256
24290
 
24257
24291
  // src/core/workspace/config-loader.ts
24258
- import { readFileSync as readFileSync2, existsSync } from "node:fs";
24259
- import { join, resolve } from "node:path";
24292
+ import { readFileSync as readFileSync3, existsSync } from "node:fs";
24293
+ import { join, resolve as resolve2 } from "node:path";
24260
24294
  function parseConfig(content, format) {
24261
24295
  if (!content.trim()) {
24262
24296
  return { macros: {} };
@@ -24316,7 +24350,7 @@ function findConfigRoot(directory) {
24316
24350
  let search = directory;
24317
24351
  for (let i2 = 0; i2 < 10; i2++) {
24318
24352
  if (findConfigFile(search)) return search;
24319
- const parent = resolve(search, "..");
24353
+ const parent = resolve2(search, "..");
24320
24354
  if (parent === search) break;
24321
24355
  search = parent;
24322
24356
  }
@@ -24325,7 +24359,7 @@ function findConfigRoot(directory) {
24325
24359
  function loadConfigFile(configPath) {
24326
24360
  let content;
24327
24361
  try {
24328
- content = readFileSync2(configPath, "utf-8");
24362
+ content = readFileSync3(configPath, "utf-8");
24329
24363
  } catch (err) {
24330
24364
  const reason = err?.code === "ENOENT" ? "file not found" : err instanceof Error ? err.message : String(err);
24331
24365
  throw new Error(`Cannot read config file '${configPath}': ${reason}`);
@@ -24343,7 +24377,7 @@ function loadConfigFromDisk(workspaceRoot) {
24343
24377
  if (!configPath) {
24344
24378
  return { ...EMPTY_CONFIG };
24345
24379
  }
24346
- const content = readFileSync2(configPath, "utf-8");
24380
+ const content = readFileSync3(configPath, "utf-8");
24347
24381
  const format = configPath.endsWith(".json") ? "json" : "yaml";
24348
24382
  return parseConfig(content, format);
24349
24383
  }
@@ -24365,6 +24399,12 @@ var init_config_loader = __esm({
24365
24399
  });
24366
24400
 
24367
24401
  // src/core/workspace/macro-registry.ts
24402
+ function applySupplement(info, entry) {
24403
+ for (const field of ["description", "parameters", "parameterDocs", "children", "parents", "skipArgs"]) {
24404
+ if (entry[field] !== void 0) Object.assign(info, { [field]: entry[field] });
24405
+ }
24406
+ if (entry.container !== void 0) info.block = entry.container;
24407
+ }
24368
24408
  var MacroRegistry;
24369
24409
  var init_macro_registry2 = __esm({
24370
24410
  "src/core/workspace/macro-registry.ts"() {
@@ -24537,30 +24577,20 @@ var init_macro_registry2 = __esm({
24537
24577
  const found = this.discovered.get(key);
24538
24578
  if (!found) return base;
24539
24579
  const info = {
24540
- ...base,
24541
- name: base?.name ?? found.name,
24542
- block: found.block ?? base?.block ?? false,
24543
- subMacros: found.subMacros?.length ? found.subMacros : base?.subMacros ?? [],
24580
+ name: found.name,
24581
+ block: found.block ?? false,
24582
+ subMacros: found.subMacros ?? [],
24544
24583
  source: "user",
24545
- storeVar: found.storeVar ?? base?.storeVar,
24546
- interpolate: found.interpolate ?? base?.interpolate,
24547
- merged: found.merged ?? base?.merged,
24548
- description: found.description ?? base?.description,
24584
+ storeVar: found.storeVar,
24585
+ interpolate: found.interpolate,
24586
+ merged: found.merged,
24587
+ description: found.description,
24549
24588
  // A definition replaces the macro it names, parameters included
24550
24589
  parameterDefs: found.parameterDefs,
24551
- // A discovered sub-macro may also belong to other (e.g. built-in) parents
24552
- parents: found.parents ? [.../* @__PURE__ */ new Set([...base?.parents ?? [], ...found.parents])] : base?.parents
24590
+ parents: found.parents
24553
24591
  };
24554
24592
  const config2 = this.configEntries.get(key);
24555
- if (config2) {
24556
- if (config2.description !== void 0) info.description = config2.description;
24557
- if (config2.parameters !== void 0) info.parameters = config2.parameters;
24558
- if (config2.parameterDocs !== void 0) info.parameterDocs = config2.parameterDocs;
24559
- if (config2.children !== void 0) info.children = config2.children;
24560
- if (config2.parents !== void 0) info.parents = config2.parents;
24561
- if (config2.skipArgs !== void 0) info.skipArgs = config2.skipArgs;
24562
- if (config2.container !== void 0) info.block = config2.container;
24563
- }
24593
+ if (config2) applySupplement(info, config2);
24564
24594
  return info;
24565
24595
  }
24566
24596
  /** Merge a set of supplement/config entries into the registry. */
@@ -24569,13 +24599,7 @@ var init_macro_registry2 = __esm({
24569
24599
  const key = rawKey.toLowerCase();
24570
24600
  const existing = this.macros.get(key);
24571
24601
  if (existing) {
24572
- if (entry.description !== void 0) existing.description = entry.description;
24573
- if (entry.parameters !== void 0) existing.parameters = entry.parameters;
24574
- if (entry.parameterDocs !== void 0) existing.parameterDocs = entry.parameterDocs;
24575
- if (entry.children !== void 0) existing.children = entry.children;
24576
- if (entry.parents !== void 0) existing.parents = entry.parents;
24577
- if (entry.skipArgs !== void 0) existing.skipArgs = entry.skipArgs;
24578
- if (entry.container !== void 0) existing.block = entry.container;
24602
+ applySupplement(existing, entry);
24579
24603
  } else {
24580
24604
  this.macros.set(key, {
24581
24605
  name: entry.name ?? rawKey,
@@ -24970,6 +24994,27 @@ var init_variable_tracker = __esm({
24970
24994
  getUsages(name) {
24971
24995
  return usagesOf(this.usagesByUri, name);
24972
24996
  }
24997
+ /** The executable usage or declaration containing the cursor, in source coordinates. */
24998
+ referenceAt(uri, position) {
24999
+ const contains = (range) => (position.line > range.start.line || position.line === range.start.line && position.character >= range.start.character) && (position.line < range.end.line || position.line === range.end.line && position.character <= range.end.character);
25000
+ let atEnd = null;
25001
+ for (const [sigil, usages, declarations] of [
25002
+ ["$", this.usagesByUri.get(uri), this.variables.declared],
25003
+ ["%", this.transientUsagesByUri.get(uri), this.transients.declared]
25004
+ ]) {
25005
+ const candidates = (usages ?? []).map((u) => ({ name: u.baseName, range: u.range }));
25006
+ for (const [name, declaration] of declarations) {
25007
+ if (declaration.declarationUri === uri && declaration.declarationRange) candidates.push({ name, range: declaration.declarationRange });
25008
+ }
25009
+ for (const candidate of candidates) {
25010
+ if (!contains(candidate.range)) continue;
25011
+ const symbol2 = { sigil, ...candidate };
25012
+ if (position.line !== candidate.range.end.line || position.character < candidate.range.end.character) return symbol2;
25013
+ atEnd ??= symbol2;
25014
+ }
25015
+ }
25016
+ return atEnd;
25017
+ }
24973
25018
  /** Whether a StoryVariables passage has been parsed. */
24974
25019
  hasStoryVariables() {
24975
25020
  return this.variables.present;
@@ -25076,6 +25121,7 @@ var init_widget_registry = __esm({
25076
25121
  const { name, tags } = markup.passage;
25077
25122
  for (const definition of widgetDefinitions([{ name, tags, content: markup.content }], known)) {
25078
25123
  this.widgets.set(definition.name.toLowerCase(), {
25124
+ tooling: definition,
25079
25125
  name: definition.name,
25080
25126
  params: definition.params,
25081
25127
  uri: markup.passage.uri,
@@ -25163,7 +25209,7 @@ var init_markup_index = __esm({
25163
25209
  });
25164
25210
 
25165
25211
  // src/core/workspace/macro-sources.ts
25166
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
25212
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
25167
25213
  import { dirname, isAbsolute, join as join2, relative } from "node:path";
25168
25214
  import { pathToFileURL as pathToFileURL2 } from "node:url";
25169
25215
  function isExcludedSegment(segment) {
@@ -25212,7 +25258,7 @@ async function addProjectMacroSources(contents, dir) {
25212
25258
  const uri = pathToFileURL2(filePath).toString();
25213
25259
  if (contents.has(uri)) continue;
25214
25260
  try {
25215
- contents.set(uri, readFileSync3(filePath, "utf-8"));
25261
+ contents.set(uri, readFileSync4(filePath, "utf-8"));
25216
25262
  } catch {
25217
25263
  }
25218
25264
  }
@@ -25231,8 +25277,8 @@ var init_macro_sources = __esm({
25231
25277
  });
25232
25278
 
25233
25279
  // src/core/workspace/spindle-version.ts
25234
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
25235
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
25280
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "node:fs";
25281
+ import { dirname as dirname2, join as join3, resolve as resolve3 } from "node:path";
25236
25282
  function parseSpindleVersion(text2) {
25237
25283
  const m = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?\s*$/.exec(text2);
25238
25284
  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : void 0;
@@ -25246,12 +25292,12 @@ function atLeast(version2, minimum) {
25246
25292
  }
25247
25293
  function readInstalledSpindleVersion(startDir) {
25248
25294
  const target = join3("node_modules", "@rohal12", "spindle", "package.json");
25249
- let dir = resolve2(startDir);
25295
+ let dir = resolve3(startDir);
25250
25296
  for (; ; ) {
25251
25297
  const candidate = join3(dir, target);
25252
25298
  if (existsSync3(candidate)) {
25253
25299
  try {
25254
- const version2 = JSON.parse(readFileSync4(candidate, "utf-8")).version;
25300
+ const version2 = JSON.parse(readFileSync5(candidate, "utf-8")).version;
25255
25301
  return typeof version2 === "string" && parseSpindleVersion(version2) ? version2 : void 0;
25256
25302
  } catch {
25257
25303
  return void 0;
@@ -25282,7 +25328,7 @@ var init_spindle_version = __esm({
25282
25328
  });
25283
25329
 
25284
25330
  // src/core/workspace/story-format.ts
25285
- import { readFileSync as readFileSync5 } from "node:fs";
25331
+ import { readFileSync as readFileSync6 } from "node:fs";
25286
25332
  function isSpindleFormatName(name) {
25287
25333
  return name.trim().toLowerCase() === "spindle";
25288
25334
  }
@@ -25362,7 +25408,7 @@ async function findStoryFormat(texts, dir) {
25362
25408
  const projectTexts = [];
25363
25409
  for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
25364
25410
  try {
25365
- projectTexts.push(stripBom(readFileSync5(file2, "utf-8")));
25411
+ projectTexts.push(stripBom(readFileSync6(file2, "utf-8")));
25366
25412
  } catch {
25367
25413
  }
25368
25414
  }
@@ -25389,7 +25435,7 @@ async function findSpindleTarget(texts, dir) {
25389
25435
  const projectTexts = [];
25390
25436
  for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
25391
25437
  try {
25392
- projectTexts.push(readFileSync5(file2, "utf-8"));
25438
+ projectTexts.push(readFileSync6(file2, "utf-8"));
25393
25439
  } catch {
25394
25440
  }
25395
25441
  }
@@ -25865,7 +25911,7 @@ var init_workspace_model = __esm({
25865
25911
  text: (uri) => this.documents.getText(uri),
25866
25912
  passages: (uri) => this.passages.getPassagesInDocument(uri),
25867
25913
  context: () => ({
25868
- macros: this.macros.toolingMacros(),
25914
+ macros: this.toolingMacros(),
25869
25915
  isBlock: (name) => this.isContainer(name)
25870
25916
  })
25871
25917
  });
@@ -26042,60 +26088,38 @@ var init_workspace_model = __esm({
26042
26088
  this.markup.invalidate();
26043
26089
  this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
26044
26090
  this.target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
26045
- const storyVars = this.passages.getStoryVariables();
26046
- if (storyVars) {
26047
- const text2 = this.documents.getText(storyVars.uri);
26048
- if (text2) {
26049
- const lines = text2.split("\n");
26050
- const contentStart = storyVars.headerEnd.end.line + 1;
26051
- let contentEnd = lines.length;
26052
- for (let i2 = contentStart; i2 < lines.length; i2++) {
26053
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
26054
- contentEnd = i2;
26055
- break;
26056
- }
26057
- }
26058
- const content = lines.slice(contentStart, contentEnd).join("\n");
26059
- this.variables.parseStoryVariables(content, contentStart, storyVars.uri);
26060
- }
26061
- } else {
26062
- this.variables.clearStoryVariables();
26063
- }
26064
- const storyTransients = this.passages.getStoryTransients();
26065
- if (storyTransients) {
26066
- const text2 = this.documents.getText(storyTransients.uri);
26067
- if (text2) {
26068
- const lines = text2.split("\n");
26069
- const contentStart = storyTransients.headerEnd.end.line + 1;
26070
- let contentEnd = lines.length;
26071
- for (let i2 = contentStart; i2 < lines.length; i2++) {
26072
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
26073
- contentEnd = i2;
26074
- break;
26075
- }
26076
- }
26077
- const content = lines.slice(contentStart, contentEnd).join("\n");
26078
- this.variables.parseStoryTransients(content, contentStart, storyTransients.uri);
26079
- }
26080
- } else {
26081
- this.variables.clearStoryTransients();
26082
- }
26083
- const documents = this.documents.getUris().flatMap((uri) => {
26091
+ const declarations = (name) => {
26092
+ const passage = this.passages.getPassage(name);
26093
+ return passage ? this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage) : void 0;
26094
+ };
26095
+ const variables = declarations("StoryVariables");
26096
+ if (variables) this.variables.parseStoryVariables(variables.content, variables.passage.headerEnd.end.line + 1, variables.doc.uri);
26097
+ else this.variables.clearStoryVariables();
26098
+ const transients = declarations("StoryTransients");
26099
+ if (transients) this.variables.parseStoryTransients(transients.content, transients.passage.headerEnd.end.line + 1, transients.doc.uri);
26100
+ else this.variables.clearStoryTransients();
26101
+ const readDocuments = () => this.documents.getUris().flatMap((uri) => {
26084
26102
  const markup = isMacroSource(uri) ? void 0 : this.markup.get(uri);
26085
26103
  return markup ? [markup] : [];
26086
26104
  });
26087
26105
  const markups = /* @__PURE__ */ new Map();
26088
- for (const doc of documents) for (const passage of doc.passages) markups.set(passage.passage, passage);
26106
+ for (const doc of readDocuments()) for (const passage of doc.passages) markups.set(passage.passage, passage);
26089
26107
  this.widgets.scan(
26090
26108
  this.passages.getAllPassages().flatMap((passage) => markups.get(passage) ?? []),
26091
26109
  this.macros.toolingMacros()
26092
26110
  );
26111
+ this.markup.invalidate();
26093
26112
  this.widgets.clearInvocations();
26094
- for (const doc of documents) {
26113
+ for (const doc of readDocuments()) {
26095
26114
  this.variables.scanDocument(doc.uri, doc.text, doc);
26096
26115
  this.widgets.recordInvocations(doc.uri, doc.passages);
26097
26116
  }
26098
26117
  }
26118
+ /** All runtime metadata, with real macros taking precedence over widgets. */
26119
+ toolingMacros() {
26120
+ const macros = this.macros.toolingMacros();
26121
+ return [...macros, ...this.widgets.getAllWidgets().filter((widget) => !this.macros.getMacro(widget.name)).map((widget) => ({ name: widget.name, block: widget.block, subMacros: [], widget: widget.tooling }))];
26122
+ }
26099
26123
  /** Debounce modelReady emission. */
26100
26124
  scheduleModelReady() {
26101
26125
  if (this.debounceTimer) {
@@ -26205,6 +26229,36 @@ var init_diagnostic_codes = __esm({
26205
26229
  }
26206
26230
  });
26207
26231
 
26232
+ // src/core/markup/source-view.ts
26233
+ function viewIndex(view, offset2) {
26234
+ if (!view.sourceOffsets) return offset2 - view.offset;
26235
+ let low = 0;
26236
+ let high = view.sourceOffsets.length - 1;
26237
+ while (low < high) {
26238
+ const mid = Math.ceil((low + high) / 2);
26239
+ if (view.sourceOffsets[mid] <= offset2) low = mid;
26240
+ else high = mid - 1;
26241
+ }
26242
+ return low;
26243
+ }
26244
+ function textAt(passage, start, end) {
26245
+ return passage.pieces.filter((piece) => piece.kind === "text" && piece.offset <= start && pieceOffset(piece, piece.text.length) >= end).at(-1);
26246
+ }
26247
+ function sourceView(passage, start, end) {
26248
+ const owner = passage.pieces.filter((piece) => {
26249
+ const value = piece.kind === "text" ? piece.text : piece.kind === "code" ? piece.code : void 0;
26250
+ return value !== void 0 && piece.offset <= start && pieceOffset(piece, value.length) >= end;
26251
+ }).at(-1);
26252
+ if (owner?.kind === "code") return { ...owner, text: owner.code };
26253
+ return owner?.kind === "text" ? owner : { offset: 0, text: passage.content };
26254
+ }
26255
+ var init_source_view = __esm({
26256
+ "src/core/markup/source-view.ts"() {
26257
+ "use strict";
26258
+ init_tooling();
26259
+ }
26260
+ });
26261
+
26208
26262
  // src/core/parsing/argument-lexer.ts
26209
26263
  function lexArguments(source) {
26210
26264
  let from = 0;
@@ -26553,6 +26607,7 @@ var TypeError2, TypeWarning, parameterTypes, LEX_LIMIT, wsRe, identStartRe, iden
26553
26607
  var init_parameter_validator = __esm({
26554
26608
  "src/core/parsing/parameter-validator.ts"() {
26555
26609
  "use strict";
26610
+ init_tooling();
26556
26611
  init_argument_lexer();
26557
26612
  TypeError2 = class {
26558
26613
  constructor(message) {
@@ -26666,7 +26721,8 @@ var init_parameter_validator = __esm({
26666
26721
  validate(arg, _args, _index, state) {
26667
26722
  let passageName;
26668
26723
  if (arg.type === 0 /* String */) {
26669
- passageName = arg.text.slice(1, -1);
26724
+ const target = passageTarget(arg.text);
26725
+ if (target.kind === "name") passageName = target.name;
26670
26726
  } else if (arg.type === 8 /* Bareword */) {
26671
26727
  passageName = arg.text;
26672
26728
  } else if (arg.type === 1 /* Number */) {
@@ -26675,7 +26731,6 @@ var init_parameter_validator = __esm({
26675
26731
  passageName = "NaN";
26676
26732
  }
26677
26733
  if (passageName !== void 0) {
26678
- passageName = passageName.replace(/\\/g, "");
26679
26734
  if (state && state.passages.length > 0) {
26680
26735
  if (!state.passages.includes(passageName)) {
26681
26736
  return new TypeWarning(`Nonexistent passage: "${passageName}"`);
@@ -27167,7 +27222,9 @@ function fromMarkup(passage, found, workspace) {
27167
27222
  if (element) code = DiagnosticCode.MalformedElement;
27168
27223
  else if (found.code === "stray-closer" && !workspace.isContainer(name)) code = DiagnosticCode.IllegalClosingTag;
27169
27224
  if (found.code === "unclosed-block") {
27170
- data2 = { kind: "unclosed-block", closer: element ? `</${name}>` : `{/${name}}`, at: passage.position(passage.content.trimEnd().length) };
27225
+ const owner = textAt(passage, found.start, found.end);
27226
+ const end = owner ? pieceOffset(owner, owner.text.trimEnd().length) : passage.content.trimEnd().length;
27227
+ data2 = { kind: "unclosed-block", closer: element ? `</${name}>` : `{/${name}}`, at: passage.position(end) };
27171
27228
  }
27172
27229
  break;
27173
27230
  }
@@ -27399,10 +27456,9 @@ function loopDiagnostics(passage, token, ancestors) {
27399
27456
  if (!loop || !close) return [];
27400
27457
  const targets = macro === "computed" ? computedTemporaryTarget(token.rawArgs) : setTemporaryTargets(token.rawArgs);
27401
27458
  const bodyStart = loop.node.token.end;
27402
- const body = passage.content.slice(bodyStart, close.start);
27403
27459
  const diagnostics = [];
27404
27460
  for (const target of targets) {
27405
- if (!isReadInBody(body, bodyStart, target.name, token.start, token.end)) continue;
27461
+ if (!isReadInBody(passage, bodyStart, close.start, target.name, token.start, token.end)) continue;
27406
27462
  const start = token.argsStart + target.offset;
27407
27463
  diagnostics.push(makeDiag(
27408
27464
  passage.range(start, start + 1 + target.name.length),
@@ -27412,22 +27468,28 @@ function loopDiagnostics(passage, token, ancestors) {
27412
27468
  }
27413
27469
  return diagnostics;
27414
27470
  }
27415
- function isReadInBody(body, bodyOffset, name, macroStart, macroEnd) {
27416
- const re2 = temporaryRefRegex(name);
27417
- let m;
27418
- while ((m = re2.exec(body)) !== null) {
27419
- const offset2 = bodyOffset + m.index;
27420
- if (offset2 >= macroStart && offset2 < macroEnd) continue;
27421
- const after = body.slice(m.index + m[0].length);
27422
- if (/^\s*=(?![=>])/.test(after)) continue;
27423
- return true;
27471
+ function isReadInBody(passage, bodyStart, bodyEnd, name, macroStart, macroEnd) {
27472
+ const eligible = (offset2) => offset2 >= bodyStart && offset2 < bodyEnd && !(offset2 >= macroStart && offset2 < macroEnd);
27473
+ for (const token of markupTokens(passage)) {
27474
+ if (token.type === "variable" && token.scope === "temporary" && token.name.split(".")[0] === name && eligible(token.start)) return true;
27475
+ }
27476
+ for (const piece of passage.pieces) {
27477
+ if (piece.kind !== "code") continue;
27478
+ let read = false;
27479
+ lexJs(piece.code, {
27480
+ variable(sigil, found, index, _nesting, end) {
27481
+ if (sigil !== "_" || found !== name || !eligible(pieceOffset(piece, index))) return;
27482
+ if (!/^\s*=(?![=>])/.test(maskLiterals(piece.code.slice(end)))) read = true;
27483
+ }
27484
+ }, piece.goal);
27485
+ if (read) return true;
27424
27486
  }
27425
27487
  return false;
27426
27488
  }
27427
27489
  function argumentDiagnostics(passage, workspace) {
27428
27490
  const diagnostics = [];
27429
27491
  const passageNames = Array.from(workspace.passages.getAllPassages(), (candidate) => candidate.name);
27430
- for (const token of passage.isMarkup ? passage.tokens : []) {
27492
+ for (const token of passage.isMarkup ? macroTokens(passage) : []) {
27431
27493
  if (token.type !== "macro" || token.isClose) continue;
27432
27494
  const info = workspace.macros.getMacro(token.name);
27433
27495
  if (!info?.parameters || info.skipArgs || SPINDLE_MACROS.has(token.name.toLowerCase())) continue;
@@ -27588,6 +27650,7 @@ var init_diagnostics = __esm({
27588
27650
  init_tooling();
27589
27651
  init_diagnostic_codes();
27590
27652
  init_tokens();
27653
+ init_source_view();
27591
27654
  init_argument_lexer();
27592
27655
  init_parameter_validator();
27593
27656
  init_link_parser();
@@ -27830,8 +27893,7 @@ var check_exports = {};
27830
27893
  __export(check_exports, {
27831
27894
  runCheck: () => runCheck
27832
27895
  });
27833
- import { readFileSync as readFileSync6 } from "node:fs";
27834
- import { resolve as resolve3 } from "node:path";
27896
+ import { resolve as resolve4 } from "node:path";
27835
27897
  import { pathToFileURL as pathToFileURL3 } from "node:url";
27836
27898
  function parseArgs(args2) {
27837
27899
  const options = {
@@ -27907,22 +27969,21 @@ async function runCheck(args2) {
27907
27969
  let explicitConfig = null;
27908
27970
  if (options.configPath) {
27909
27971
  try {
27910
- explicitConfig = loadConfigFile(resolve3(cwd, options.configPath));
27972
+ explicitConfig = loadConfigFile(resolve4(cwd, options.configPath));
27911
27973
  } catch (err) {
27912
27974
  console.error(`spindle-lsp check: ${err instanceof Error ? err.message : String(err)}`);
27913
27975
  return 2;
27914
27976
  }
27915
27977
  }
27916
- const files = [];
27917
- for (const pattern of options.patterns) {
27918
- const matches = await Ze(pattern, {
27919
- cwd,
27920
- absolute: true,
27921
- nodir: true
27922
- });
27923
- files.push(...matches);
27978
+ let uniqueFiles;
27979
+ let contents;
27980
+ try {
27981
+ uniqueFiles = await resolveInputs(options.patterns, cwd);
27982
+ contents = readFileContents(uniqueFiles);
27983
+ } catch (error48) {
27984
+ console.error(`spindle-lsp check: ${error48 instanceof Error ? error48.message : String(error48)}`);
27985
+ return 2;
27924
27986
  }
27925
- const uniqueFiles = [...new Set(files)];
27926
27987
  if (uniqueFiles.length === 0) {
27927
27988
  if (options.format === "pretty") {
27928
27989
  console.log("No files found");
@@ -27946,15 +28007,7 @@ async function runCheck(args2) {
27946
28007
  return 1;
27947
28008
  }
27948
28009
  }
27949
- const fileContents = /* @__PURE__ */ new Map();
27950
- for (const filePath of uniqueFiles) {
27951
- try {
27952
- const text2 = readFileSync6(filePath, "utf-8");
27953
- const uri = pathToFileURL3(filePath).toString();
27954
- fileContents.set(uri, text2);
27955
- } catch {
27956
- }
27957
- }
28010
+ const fileContents = new Map([...contents].map(([path, text2]) => [pathToFileURL3(path).toString(), text2]));
27958
28011
  const storyFormat = await findStoryFormat(fileContents.values(), commonDir);
27959
28012
  if (!storyFormat.isSpindle) {
27960
28013
  console.error(skippedFormatNote(storyFormat));
@@ -28007,7 +28060,7 @@ var SEVERITY_ORDER;
28007
28060
  var init_check = __esm({
28008
28061
  "src/cli/check.ts"() {
28009
28062
  "use strict";
28010
- init_index_min();
28063
+ init_file_inputs();
28011
28064
  init_workspace_model();
28012
28065
  init_diagnostics();
28013
28066
  init_config_loader();
@@ -28043,7 +28096,7 @@ function splitPassages(text2) {
28043
28096
  }
28044
28097
  }
28045
28098
  for (let i2 = 0; i2 < lines.length; i2++) {
28046
- if (PASSAGE_HEADER_REGEX.test(lines[i2])) {
28099
+ if (parsePassageHeader(lines[i2], i2)) {
28047
28100
  pushPassage();
28048
28101
  currentHeader = lines[i2];
28049
28102
  bodyLines = [];
@@ -28056,7 +28109,7 @@ function splitPassages(text2) {
28056
28109
  return passages;
28057
28110
  }
28058
28111
  function classifyPassage(header) {
28059
- const tags = headerTags(header).map((t) => t.toLowerCase());
28112
+ const tags = headerTags(header);
28060
28113
  if (tags.includes("script")) return "script";
28061
28114
  if (tags.includes("stylesheet")) return "stylesheet";
28062
28115
  return "normal";
@@ -28067,22 +28120,23 @@ function headerTags(header) {
28067
28120
  const unescaped = header.replace(/\\./g, "ec");
28068
28121
  return (unescaped.match(/\[[^\]]*\]/g) ?? []).flatMap((t) => t.slice(1, -1).split(/\s+/));
28069
28122
  }
28070
- var PASSAGE_HEADER_REGEX;
28071
28123
  var init_segment = __esm({
28072
28124
  "src/plugins/format/segment.ts"() {
28073
28125
  "use strict";
28074
28126
  init_passage_parser();
28075
- PASSAGE_HEADER_REGEX = /^::\s+/;
28076
28127
  }
28077
28128
  });
28078
28129
 
28079
28130
  // src/plugins/format/prettier-bridge.ts
28080
- async function loadPrettier() {
28131
+ import { resolve as resolve5 } from "node:path";
28132
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
28133
+ async function loadPrettier(filePath) {
28081
28134
  prettier ??= import("prettier").catch(() => null);
28082
28135
  const p = await prettier;
28083
28136
  if (!p) return void 0;
28084
28137
  try {
28085
- const resolved = await p.resolveConfig(process.cwd());
28138
+ const path = filePath?.startsWith("file:") ? fileURLToPath2(filePath) : filePath;
28139
+ const resolved = await p.resolveConfig(path ?? resolve5(process.cwd(), "story.tw"));
28086
28140
  return { prettier: p, options: { tabWidth: 2, ...resolved ?? {} } };
28087
28141
  } catch {
28088
28142
  return { prettier: p, options: { tabWidth: 2 } };
@@ -28122,7 +28176,7 @@ function dominantEol2(text2) {
28122
28176
  async function formatLf2(text2, options) {
28123
28177
  const isBlock = options?.isBlock ?? buildDefaultIsBlock(text2);
28124
28178
  const isDedentingSubMacro = options?.isDedentingSubMacro ?? ((name) => DEFAULT_DEDENTING2.has(name.toLowerCase()));
28125
- const prettier2 = await loadPrettier();
28179
+ const prettier2 = await loadPrettier(options?.filePath);
28126
28180
  const passages = splitPassages(text2);
28127
28181
  const resultLines = [];
28128
28182
  for (let pi2 = 0; pi2 < passages.length; pi2++) {
@@ -28136,12 +28190,14 @@ async function formatLf2(text2, options) {
28136
28190
  }
28137
28191
  }
28138
28192
  if (passage.header) {
28139
- const header = PASSAGE_HEADER_REGEX2.test(passage.header) ? normalizePassageHeader(passage.header) : passage.header;
28193
+ const header = PASSAGE_HEADER_REGEX.test(passage.header) ? normalizePassageHeader(passage.header) : passage.header;
28140
28194
  resultLines.push(header.replace(/\s+$/, ""));
28141
28195
  }
28142
28196
  if (passage.body === "") continue;
28143
- const body = await formatPassage(passage.body, {
28144
- kind: classifyPassage(passage.header),
28197
+ const role = parsePassageHeader(passage.header, passage.startLine);
28198
+ const kind = classifyPassage(passage.header);
28199
+ const body = kind === "normal" && role && !isMarkupPassage(role) ? passage.body : await formatPassage(passage.body, {
28200
+ kind,
28145
28201
  prettier: prettier2?.prettier,
28146
28202
  prettierOptions: prettier2?.options,
28147
28203
  isBlock,
@@ -28156,7 +28212,7 @@ async function formatRange(text2, _range, options) {
28156
28212
  return formatDocument(text2, options);
28157
28213
  }
28158
28214
  function normalizePassageHeader(line) {
28159
- const headerMatch = line.match(/^::\s+(.*)/);
28215
+ const headerMatch = line.match(/^::\s*(.*)/);
28160
28216
  if (!headerMatch) return line;
28161
28217
  const rest = headerMatch[1];
28162
28218
  const unescaped = rest.replace(/\\./g, "ec");
@@ -28175,14 +28231,15 @@ function normalizePassageHeader(line) {
28175
28231
  }
28176
28232
  return `:: ${name}${suffix}`;
28177
28233
  }
28178
- var PASSAGE_HEADER_REGEX2, DEFAULT_DEDENTING2, BUILTIN_CONTAINERS, formatPlugin;
28234
+ var PASSAGE_HEADER_REGEX, DEFAULT_DEDENTING2, BUILTIN_CONTAINERS, formatPlugin;
28179
28235
  var init_format = __esm({
28180
28236
  "src/plugins/format.ts"() {
28181
28237
  "use strict";
28182
28238
  init_tooling();
28239
+ init_passage_parser();
28183
28240
  init_segment();
28184
28241
  init_prettier_bridge();
28185
- PASSAGE_HEADER_REGEX2 = /^(::)\s+/;
28242
+ PASSAGE_HEADER_REGEX = /^(::)\s*/;
28186
28243
  DEFAULT_DEDENTING2 = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
28187
28244
  BUILTIN_CONTAINERS = new Set(builtinMacros.filter((m) => m.block).map((m) => m.name.toLowerCase()));
28188
28245
  formatPlugin = {
@@ -28200,7 +28257,7 @@ var init_format = __esm({
28200
28257
  ctx.connection.onDocumentFormatting(async (params) => {
28201
28258
  const text2 = ctx.workspace.documents.getText(params.textDocument.uri);
28202
28259
  if (text2 === void 0) return [];
28203
- const formatted = await formatDocument(text2, formatOpts);
28260
+ const formatted = await formatDocument(text2, { ...formatOpts, filePath: params.textDocument.uri });
28204
28261
  if (formatted === text2) return [];
28205
28262
  const lines = text2.split("\n");
28206
28263
  return [{
@@ -28218,7 +28275,7 @@ var init_format = __esm({
28218
28275
  start: { line: params.range.start.line, character: params.range.start.character },
28219
28276
  end: { line: params.range.end.line, character: params.range.end.character }
28220
28277
  };
28221
- const formatted = await formatRange(text2, range, formatOpts);
28278
+ const formatted = await formatRange(text2, range, { ...formatOpts, filePath: params.textDocument.uri });
28222
28279
  if (formatted === text2) return [];
28223
28280
  const lines = text2.split("\n");
28224
28281
  return [{
@@ -28239,7 +28296,7 @@ var format_exports = {};
28239
28296
  __export(format_exports, {
28240
28297
  runFormat: () => runFormat
28241
28298
  });
28242
- import { readFileSync as readFileSync7, writeFileSync } from "node:fs";
28299
+ import { writeFileSync } from "node:fs";
28243
28300
  function parseArgs2(args2) {
28244
28301
  const options = {
28245
28302
  check: false,
@@ -28275,27 +28332,19 @@ function parseArgs2(args2) {
28275
28332
  async function runFormat(args2) {
28276
28333
  const options = parseArgs2(args2);
28277
28334
  const cwd = process.cwd();
28278
- const files = [];
28279
- for (const pattern of options.patterns) {
28280
- const matches = await Ze(pattern, {
28281
- cwd,
28282
- absolute: true,
28283
- nodir: true
28284
- });
28285
- files.push(...matches);
28335
+ let uniqueFiles;
28336
+ let texts;
28337
+ try {
28338
+ uniqueFiles = await resolveInputs(options.patterns, cwd);
28339
+ texts = readFileContents(uniqueFiles);
28340
+ } catch (error48) {
28341
+ console.error(`spindle-lsp format: ${error48 instanceof Error ? error48.message : String(error48)}`);
28342
+ return 2;
28286
28343
  }
28287
- const uniqueFiles = [...new Set(files)];
28288
28344
  if (uniqueFiles.length === 0) {
28289
28345
  console.log("No files found");
28290
28346
  return 0;
28291
28347
  }
28292
- const texts = /* @__PURE__ */ new Map();
28293
- for (const filePath of uniqueFiles) {
28294
- try {
28295
- texts.set(filePath, readFileSync7(filePath, "utf-8"));
28296
- } catch {
28297
- }
28298
- }
28299
28348
  const storyFormat = await findStoryFormat(texts.values(), commonDirectory(uniqueFiles));
28300
28349
  if (!storyFormat.isSpindle) {
28301
28350
  console.error(skippedFormatNote(storyFormat));
@@ -28308,9 +28357,10 @@ async function runFormat(args2) {
28308
28357
  formatOpts.maxLineLength = options.maxLineLength;
28309
28358
  }
28310
28359
  const unformatted = [];
28360
+ let failed = false;
28311
28361
  for (const [filePath, text2] of texts) {
28312
28362
  try {
28313
- const formatted = await formatDocument(text2, formatOpts);
28363
+ const formatted = await formatDocument(text2, { ...formatOpts, filePath });
28314
28364
  if (formatted !== text2) {
28315
28365
  if (options.check) {
28316
28366
  const relative2 = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
@@ -28319,9 +28369,12 @@ async function runFormat(args2) {
28319
28369
  writeFileSync(filePath, formatted, "utf-8");
28320
28370
  }
28321
28371
  }
28322
- } catch {
28372
+ } catch (error48) {
28373
+ failed = true;
28374
+ console.error(`spindle-lsp format: ${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`);
28323
28375
  }
28324
28376
  }
28377
+ if (failed) return 2;
28325
28378
  if (options.check) {
28326
28379
  if (unformatted.length > 0) {
28327
28380
  for (const file2 of unformatted) {
@@ -28338,7 +28391,7 @@ ${unformatted.length} file${unformatted.length !== 1 ? "s" : ""} would be reform
28338
28391
  var init_format2 = __esm({
28339
28392
  "src/cli/format.ts"() {
28340
28393
  "use strict";
28341
- init_index_min();
28394
+ init_file_inputs();
28342
28395
  init_format();
28343
28396
  init_macro_sources();
28344
28397
  init_story_format();
@@ -50599,7 +50652,7 @@ var init_protocol = __esm({
50599
50652
  return;
50600
50653
  }
50601
50654
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
50602
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
50655
+ await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
50603
50656
  options?.signal?.throwIfAborted();
50604
50657
  }
50605
50658
  } catch (error48) {
@@ -50616,7 +50669,7 @@ var init_protocol = __esm({
50616
50669
  */
50617
50670
  request(request, resultSchema, options) {
50618
50671
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
50619
- return new Promise((resolve5, reject) => {
50672
+ return new Promise((resolve6, reject) => {
50620
50673
  const earlyReject = (error48) => {
50621
50674
  reject(error48);
50622
50675
  };
@@ -50694,7 +50747,7 @@ var init_protocol = __esm({
50694
50747
  if (!parseResult.success) {
50695
50748
  reject(parseResult.error);
50696
50749
  } else {
50697
- resolve5(parseResult.data);
50750
+ resolve6(parseResult.data);
50698
50751
  }
50699
50752
  } catch (error48) {
50700
50753
  reject(error48);
@@ -50956,12 +51009,12 @@ var init_protocol = __esm({
50956
51009
  }
50957
51010
  } catch {
50958
51011
  }
50959
- return new Promise((resolve5, reject) => {
51012
+ return new Promise((resolve6, reject) => {
50960
51013
  if (signal.aborted) {
50961
51014
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
50962
51015
  return;
50963
51016
  }
50964
- const timeoutId = setTimeout(resolve5, interval);
51017
+ const timeoutId = setTimeout(resolve6, interval);
50965
51018
  signal.addEventListener("abort", () => {
50966
51019
  clearTimeout(timeoutId);
50967
51020
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -53988,7 +54041,7 @@ var require_compile = __commonJS({
53988
54041
  const schOrFunc = root.refs[ref2];
53989
54042
  if (schOrFunc)
53990
54043
  return schOrFunc;
53991
- let _sch = resolve5.call(this, root, ref2);
54044
+ let _sch = resolve6.call(this, root, ref2);
53992
54045
  if (_sch === void 0) {
53993
54046
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref2];
53994
54047
  const { schemaId } = this.opts;
@@ -54015,7 +54068,7 @@ var require_compile = __commonJS({
54015
54068
  function sameSchemaEnv(s1, s2) {
54016
54069
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
54017
54070
  }
54018
- function resolve5(root, ref2) {
54071
+ function resolve6(root, ref2) {
54019
54072
  let sch;
54020
54073
  while (typeof (sch = this.refs[ref2]) == "string")
54021
54074
  ref2 = sch;
@@ -54590,7 +54643,7 @@ var require_fast_uri = __commonJS({
54590
54643
  }
54591
54644
  return uri;
54592
54645
  }
54593
- function resolve5(baseURI, relativeURI, options) {
54646
+ function resolve6(baseURI, relativeURI, options) {
54594
54647
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
54595
54648
  const resolved = resolveComponent(parse6(baseURI, schemelessOptions), parse6(relativeURI, schemelessOptions), schemelessOptions, true);
54596
54649
  schemelessOptions.skipEscape = true;
@@ -54817,7 +54870,7 @@ var require_fast_uri = __commonJS({
54817
54870
  var fastUri = {
54818
54871
  SCHEMES,
54819
54872
  normalize,
54820
- resolve: resolve5,
54873
+ resolve: resolve6,
54821
54874
  resolveComponent,
54822
54875
  equal,
54823
54876
  serialize,
@@ -58975,7 +59028,7 @@ var init_mcp = __esm({
58975
59028
  let task = createTaskResult.task;
58976
59029
  const pollInterval = task.pollInterval ?? 5e3;
58977
59030
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
58978
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
59031
+ await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
58979
59032
  const updatedTask = await extra.taskStore.getTask(taskId);
58980
59033
  if (!updatedTask) {
58981
59034
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -59584,12 +59637,12 @@ var init_stdio2 = __esm({
59584
59637
  this.onclose?.();
59585
59638
  }
59586
59639
  send(message) {
59587
- return new Promise((resolve5) => {
59640
+ return new Promise((resolve6) => {
59588
59641
  const json2 = serializeMessage(message);
59589
59642
  if (this._stdout.write(json2)) {
59590
- resolve5();
59643
+ resolve6();
59591
59644
  } else {
59592
- this._stdout.once("drain", resolve5);
59645
+ this._stdout.once("drain", resolve6);
59593
59646
  }
59594
59647
  });
59595
59648
  }
@@ -59606,8 +59659,7 @@ __export(server_exports, {
59606
59659
  formatFiles: () => formatFiles,
59607
59660
  startMcpServer: () => startMcpServer
59608
59661
  });
59609
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, statSync } from "node:fs";
59610
- import { resolve as resolve4 } from "node:path";
59662
+ import { writeFileSync as writeFileSync2 } from "node:fs";
59611
59663
  import { pathToFileURL as pathToFileURL4 } from "node:url";
59612
59664
  function filterBySeverity2(diagnostics, minSeverity) {
59613
59665
  const minIdx = SEVERITY_ORDER2.indexOf(minSeverity);
@@ -59616,37 +59668,8 @@ function filterBySeverity2(diagnostics, minSeverity) {
59616
59668
  return idx >= minIdx;
59617
59669
  });
59618
59670
  }
59619
- async function resolveFiles(pattern, cwd) {
59620
- let input = pattern;
59621
- let directory = cwd;
59622
- try {
59623
- const literal3 = resolve4(cwd, pattern);
59624
- const stat = statSync(literal3);
59625
- if (stat.isFile()) return [literal3];
59626
- if (stat.isDirectory()) {
59627
- directory = literal3;
59628
- input = "**/*.{tw,twee}";
59629
- }
59630
- } catch {
59631
- }
59632
- const matches = await Ze(input, {
59633
- cwd: directory,
59634
- absolute: true,
59635
- nodir: true
59636
- });
59637
- return [...new Set(matches)];
59638
- }
59639
59671
  function readFiles(files) {
59640
- const fileContents = /* @__PURE__ */ new Map();
59641
- for (const filePath of files) {
59642
- try {
59643
- const text2 = readFileSync8(filePath, "utf-8");
59644
- const uri = pathToFileURL4(filePath).toString();
59645
- fileContents.set(uri, text2);
59646
- } catch {
59647
- }
59648
- }
59649
- return fileContents;
59672
+ return new Map([...readFileContents(files)].map(([path, text2]) => [pathToFileURL4(path).toString(), text2]));
59650
59673
  }
59651
59674
  async function skippedReason(fileContents, files) {
59652
59675
  const format = await findStoryFormat(fileContents.values(), commonDirectory(files));
@@ -59683,7 +59706,6 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
59683
59706
  const results = [];
59684
59707
  for (const filePath of files) {
59685
59708
  const uri = pathToFileURL4(filePath).toString();
59686
- if (!workspace.documents.has(uri)) continue;
59687
59709
  let diags = computeDiagnostics(uri, workspace);
59688
59710
  if (severity) {
59689
59711
  diags = filterBySeverity2(diags, severity);
@@ -59721,7 +59743,7 @@ async function formatFiles(pattern, cwd = process.cwd()) {
59721
59743
  const text2 = contents.get(pathToFileURL4(filePath).toString());
59722
59744
  if (text2 === void 0) continue;
59723
59745
  try {
59724
- const result = await formatDocument(text2);
59746
+ const result = await formatDocument(text2, { filePath });
59725
59747
  if (result !== text2) {
59726
59748
  writeFileSync2(filePath, result, "utf-8");
59727
59749
  formatted++;
@@ -59729,7 +59751,8 @@ async function formatFiles(pattern, cwd = process.cwd()) {
59729
59751
  } else {
59730
59752
  unchanged++;
59731
59753
  }
59732
- } catch {
59754
+ } catch (error48) {
59755
+ throw new Error(`${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`, { cause: error48 });
59733
59756
  }
59734
59757
  }
59735
59758
  return { formatted, unchanged, files: changedFiles, ...warning && { warning } };
@@ -59746,9 +59769,10 @@ async function checkFormatting(pattern, cwd = process.cwd()) {
59746
59769
  const text2 = contents.get(pathToFileURL4(filePath).toString());
59747
59770
  if (text2 === void 0) continue;
59748
59771
  try {
59749
- const result = await formatDocument(text2);
59772
+ const result = await formatDocument(text2, { filePath });
59750
59773
  (result !== text2 ? needsFormatting : alreadyFormatted).push(relativeTo(cwd, filePath));
59751
- } catch {
59774
+ } catch (error48) {
59775
+ throw new Error(`${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`, { cause: error48 });
59752
59776
  }
59753
59777
  }
59754
59778
  return { needsFormatting, alreadyFormatted, ...warning && { warning } };
@@ -59808,7 +59832,7 @@ var SEVERITY_ORDER2;
59808
59832
  var init_server3 = __esm({
59809
59833
  "src/mcp/server.ts"() {
59810
59834
  "use strict";
59811
- init_index_min();
59835
+ init_file_inputs();
59812
59836
  init_mcp();
59813
59837
  init_stdio2();
59814
59838
  init_zod();
@@ -61083,8 +61107,8 @@ var require_semaphore = __commonJS({
61083
61107
  this._waiting = [];
61084
61108
  }
61085
61109
  lock(thunk) {
61086
- return new Promise((resolve5, reject) => {
61087
- this._waiting.push({ thunk, resolve: resolve5, reject });
61110
+ return new Promise((resolve6, reject) => {
61111
+ this._waiting.push({ thunk, resolve: resolve6, reject });
61088
61112
  this.runNext();
61089
61113
  });
61090
61114
  }
@@ -62710,9 +62734,9 @@ ${JSON.stringify(message, null, 4)}`);
62710
62734
  if (typeof cancellationStrategy.sender.enableCancellation === "function") {
62711
62735
  cancellationStrategy.sender.enableCancellation(requestMessage);
62712
62736
  }
62713
- return new Promise(async (resolve5, reject) => {
62737
+ return new Promise(async (resolve6, reject) => {
62714
62738
  const resolveWithCleanup = (r) => {
62715
- resolve5(r);
62739
+ resolve6(r);
62716
62740
  cancellationStrategy.sender.cleanup(id);
62717
62741
  disposable?.dispose();
62718
62742
  };
@@ -68354,8 +68378,8 @@ var require_server = __commonJS({
68354
68378
  if (value instanceof Promise) {
68355
68379
  return value;
68356
68380
  } else if (Is3.thenable(value)) {
68357
- return new Promise((resolve5, reject) => {
68358
- value.then((resolved) => resolve5(resolved), (error48) => reject(error48));
68381
+ return new Promise((resolve6, reject) => {
68382
+ value.then((resolved) => resolve6(resolved), (error48) => reject(error48));
68359
68383
  });
68360
68384
  } else {
68361
68385
  return Promise.resolve(value);
@@ -68639,7 +68663,7 @@ var require_files = __commonJS({
68639
68663
  Object.defineProperty(exports, "__esModule", { value: true });
68640
68664
  exports.FileSystem = void 0;
68641
68665
  exports.uriToFilePath = uriToFilePath;
68642
- exports.resolve = resolve5;
68666
+ exports.resolve = resolve6;
68643
68667
  exports.resolveGlobalNodePath = resolveGlobalNodePath;
68644
68668
  exports.resolveGlobalYarnPath = resolveGlobalYarnPath;
68645
68669
  exports.resolveModulePath = resolveModulePath;
@@ -68668,7 +68692,7 @@ var require_files = __commonJS({
68668
68692
  function isWindows() {
68669
68693
  return process.platform === "win32";
68670
68694
  }
68671
- function resolve5(moduleName, nodePath, cwd, tracer) {
68695
+ function resolve6(moduleName, nodePath, cwd, tracer) {
68672
68696
  const nodePathKey = "NODE_PATH";
68673
68697
  const app = [
68674
68698
  "var p = process;",
@@ -68687,7 +68711,7 @@ var require_files = __commonJS({
68687
68711
  "}",
68688
68712
  "});"
68689
68713
  ].join("");
68690
- return new Promise((resolve6, reject) => {
68714
+ return new Promise((resolve7, reject) => {
68691
68715
  const env = process.env;
68692
68716
  const newEnv = /* @__PURE__ */ Object.create(null);
68693
68717
  Object.keys(env).forEach((key) => newEnv[key] = env[key]);
@@ -68719,7 +68743,7 @@ var require_files = __commonJS({
68719
68743
  if (message2.c === "r") {
68720
68744
  cp.send({ c: "e" });
68721
68745
  if (message2.s) {
68722
- resolve6(message2.r);
68746
+ resolve7(message2.r);
68723
68747
  } else {
68724
68748
  reject(new Error(`Failed to resolve module: ${moduleName}`));
68725
68749
  }
@@ -68847,17 +68871,17 @@ var require_files = __commonJS({
68847
68871
  if (!path.isAbsolute(nodePath)) {
68848
68872
  nodePath = path.join(workspaceRoot, nodePath);
68849
68873
  }
68850
- return resolve5(moduleName, nodePath, nodePath, tracer).then((value) => {
68874
+ return resolve6(moduleName, nodePath, nodePath, tracer).then((value) => {
68851
68875
  if (FileSystem.isParent(nodePath, value)) {
68852
68876
  return value;
68853
68877
  } else {
68854
68878
  return Promise.reject(new Error(`Failed to load ${moduleName} from node path location.`));
68855
68879
  }
68856
68880
  }).then(void 0, (_error) => {
68857
- return resolve5(moduleName, resolveGlobalNodePath(tracer), workspaceRoot, tracer);
68881
+ return resolve6(moduleName, resolveGlobalNodePath(tracer), workspaceRoot, tracer);
68858
68882
  });
68859
68883
  } else {
68860
- return resolve5(moduleName, resolveGlobalNodePath(tracer), workspaceRoot, tracer);
68884
+ return resolve6(moduleName, resolveGlobalNodePath(tracer), workspaceRoot, tracer);
68861
68885
  }
68862
68886
  }
68863
68887
  }
@@ -68939,10 +68963,10 @@ var require_ril = __commonJS({
68939
68963
  return api_1.Disposable.create(() => this.stream.off("end", listener));
68940
68964
  }
68941
68965
  write(data2, encoding) {
68942
- return new Promise((resolve5, reject) => {
68966
+ return new Promise((resolve6, reject) => {
68943
68967
  const callback = (error48) => {
68944
68968
  if (error48 === void 0 || error48 === null) {
68945
- resolve5();
68969
+ resolve6();
68946
68970
  } else {
68947
68971
  reject(error48);
68948
68972
  }
@@ -69234,10 +69258,10 @@ var require_main = __commonJS({
69234
69258
  }
69235
69259
  function createClientPipeTransport(pipeName, encoding = "utf-8") {
69236
69260
  let connectResolve;
69237
- const connected = new Promise((resolve5, _reject) => {
69238
- connectResolve = resolve5;
69261
+ const connected = new Promise((resolve6, _reject) => {
69262
+ connectResolve = resolve6;
69239
69263
  });
69240
- return new Promise((resolve5, reject) => {
69264
+ return new Promise((resolve6, reject) => {
69241
69265
  const server = (0, net_1.createServer)((socket) => {
69242
69266
  server.close();
69243
69267
  connectResolve([
@@ -69248,7 +69272,7 @@ var require_main = __commonJS({
69248
69272
  server.on("error", reject);
69249
69273
  server.listen(pipeName, () => {
69250
69274
  server.removeListener("error", reject);
69251
- resolve5({
69275
+ resolve6({
69252
69276
  onConnected: () => {
69253
69277
  return connected;
69254
69278
  }
@@ -69265,10 +69289,10 @@ var require_main = __commonJS({
69265
69289
  }
69266
69290
  function createClientSocketTransport(port, encoding = "utf-8") {
69267
69291
  let connectResolve;
69268
- const connected = new Promise((resolve5, _reject) => {
69269
- connectResolve = resolve5;
69292
+ const connected = new Promise((resolve6, _reject) => {
69293
+ connectResolve = resolve6;
69270
69294
  });
69271
- return new Promise((resolve5, reject) => {
69295
+ return new Promise((resolve6, reject) => {
69272
69296
  const server = (0, net_1.createServer)((socket) => {
69273
69297
  server.close();
69274
69298
  connectResolve([
@@ -69285,7 +69309,7 @@ var require_main = __commonJS({
69285
69309
  return;
69286
69310
  }
69287
69311
  const boundPort = address.port;
69288
- resolve5({
69312
+ resolve6({
69289
69313
  port: () => boundPort,
69290
69314
  onConnected: () => {
69291
69315
  return connected;
@@ -70079,6 +70103,7 @@ var init_markup_cursor = __esm({
70079
70103
  init_tooling();
70080
70104
  init_passage_markup();
70081
70105
  init_tree();
70106
+ init_source_view();
70082
70107
  init_text();
70083
70108
  MarkupCursor = class {
70084
70109
  constructor(passage, at3, workspace) {
@@ -70104,6 +70129,7 @@ var init_markup_cursor = __esm({
70104
70129
  start: 0,
70105
70130
  end: content.length,
70106
70131
  text: false,
70132
+ view: { offset: 0, text: content },
70107
70133
  tokens: passage.tokens,
70108
70134
  errors: passage.tokenization.errors,
70109
70135
  nodes: () => passage.pairing.nodes
@@ -70116,6 +70142,7 @@ var init_markup_cursor = __esm({
70116
70142
  start: piece.offset,
70117
70143
  end,
70118
70144
  text: true,
70145
+ view: piece,
70119
70146
  tokens: piece.tokens,
70120
70147
  errors: piece.errors,
70121
70148
  nodes: () => pairMarkup(piece.tokens, { isBlock: (name) => this.workspace.isContainer(name), source: content }).nodes
@@ -70130,17 +70157,19 @@ var init_markup_cursor = __esm({
70130
70157
  * of its name.
70131
70158
  */
70132
70159
  enclosingMacro(known) {
70133
- const { content, at: at3 } = this;
70160
+ const { at: at3 } = this;
70134
70161
  for (const region of this.regions) {
70135
70162
  const found = [];
70136
70163
  for (const token of region.tokens) {
70137
70164
  if (token.type !== "macro" || token.isClose || token.nameEnd >= at3 || at3 >= token.end) continue;
70138
- found.push({ start: token.start, name: token.name, argsBefore: at3 >= token.argsStart ? content.slice(token.argsStart, at3) : "" });
70165
+ 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)) : "" });
70139
70166
  }
70140
70167
  for (const error48 of region.errors) {
70141
- if (error48.code !== "unclosed-macro" || error48.offset >= at3 || error48.end >= at3 || !/\s/.test(content[error48.end] ?? "")) continue;
70142
- const name = content.slice(parseSelectors(content, error48.offset + 1).end, error48.end);
70143
- if (name && name[0] !== "/") found.push({ start: error48.offset, name, argsBefore: content.slice(error48.end, at3).trimStart() });
70168
+ const source = region.view.text;
70169
+ const end = viewIndex(region.view, error48.end);
70170
+ if (error48.code !== "unclosed-macro" || error48.offset >= at3 || error48.end >= at3 || !/\s/.test(source[end] ?? "")) continue;
70171
+ const name = source.slice(parseSelectors(source, viewIndex(region.view, error48.offset) + 1).end, end);
70172
+ if (name && name[0] !== "/") found.push({ start: error48.offset, name, argsBefore: source.slice(end, viewIndex(region.view, at3)).trimStart() });
70144
70173
  }
70145
70174
  const innermost = found.filter((macro) => known(macro.name)).sort((a, b) => b.start - a.start)[0];
70146
70175
  if (innermost) return { name: innermost.name, argsBefore: innermost.argsBefore };
@@ -70229,10 +70258,9 @@ function variableUses(passage) {
70229
70258
  for (const piece of passage.pieces) {
70230
70259
  if (piece.kind !== "code") continue;
70231
70260
  lexJs(piece.code, {
70232
- variable(sigil, name, index) {
70233
- const nameEnd = index + 1 + name.length;
70261
+ variable(sigil, name, index, _nesting, nameEnd) {
70234
70262
  const pathEnd = nameEnd + pathLength(piece.code, nameEnd);
70235
- uses.push({ sigil, name, start: pieceOffset(piece, index), end: pieceOffset(piece, pathEnd - 1) + 1 });
70263
+ uses.push({ sigil, name, start: pieceOffset(piece, index), end: pieceOffset(piece, pathEnd) });
70236
70264
  }
70237
70265
  }, piece.goal);
70238
70266
  }
@@ -70824,27 +70852,7 @@ function findReferences(uri, position, workspace, includeDeclaration) {
70824
70852
  return [];
70825
70853
  }
70826
70854
  function variableAt(uri, position, workspace) {
70827
- const line = workspace.documents.getText(uri)?.split("\n")[position.line];
70828
- if (line === void 0) return null;
70829
- let atEnd = null;
70830
- VARIABLE_CANDIDATE.lastIndex = 0;
70831
- let found;
70832
- while ((found = VARIABLE_CANDIDATE.exec(line)) !== null) {
70833
- const match = VARIABLE_PATH.exec(line.slice(found.index));
70834
- const start = found.index;
70835
- const end = start + match[0].length;
70836
- VARIABLE_CANDIDATE.lastIndex = start + 1;
70837
- if (position.character < start || position.character > end) continue;
70838
- const sigil = match[1];
70839
- const name = match[2].split(".")[0];
70840
- const refs = sigil === "%" ? findTransientReferences(name, workspace, true) : findVariableReferences(name, workspace, true);
70841
- const hit = refs.find((r) => r.uri === uri && r.range.start.line === position.line && r.range.start.character === start);
70842
- if (!hit) continue;
70843
- const symbol2 = { sigil, name, range: hit.range };
70844
- if (position.character < end) return symbol2;
70845
- atEnd ??= symbol2;
70846
- }
70847
- return atEnd;
70855
+ return workspace.variables.referenceAt(uri, position);
70848
70856
  }
70849
70857
  function findPassageReferences(passageName, workspace, includeDeclaration) {
70850
70858
  const locations = [];
@@ -70927,15 +70935,13 @@ function toLspRange3(r) {
70927
70935
  end: { line: r.end.line, character: r.end.character }
70928
70936
  };
70929
70937
  }
70930
- var VARIABLE_CANDIDATE, VARIABLE_PATH, referencesPlugin;
70938
+ var referencesPlugin;
70931
70939
  var init_references = __esm({
70932
70940
  "src/plugins/references.ts"() {
70933
70941
  "use strict";
70934
70942
  init_macro_heads();
70935
70943
  init_passage_refs();
70936
70944
  init_passage_parser();
70937
- VARIABLE_CANDIDATE = /(?:\$|(?<!\w)%)(?=\w)/g;
70938
- VARIABLE_PATH = /^([$%])(\w+(?:\.[A-Za-z_$][\w$]*)*)/;
70939
70945
  referencesPlugin = {
70940
70946
  id: "references",
70941
70947
  capabilities: {
@@ -71039,17 +71045,32 @@ function computeRename(uri, position, newName, workspace) {
71039
71045
  `Cannot rename to ${JSON.stringify(newName)}: a transient name cannot start with a digit, \`%5\` is the modulo operator in code.`
71040
71046
  ).at(uri, symbol2.range);
71041
71047
  }
71048
+ const declaration = parseDeclarations(`${symbol2.sigil}${bareName} = 0`, symbol2.sigil);
71049
+ if (declaration.errors.length || declaration.declarations[0]?.name !== bareName) {
71050
+ throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: ${declaration.errors[0]?.message ?? "invalid variable name"}.`).at(uri, symbol2.range);
71051
+ }
71042
71052
  if (bareName === symbol2.name) return /* @__PURE__ */ new Map();
71043
71053
  if (workspace.variables.getDeclared().has(bareName) || workspace.variables.getDeclaredTransient().has(bareName)) {
71044
71054
  throw new RenameError(`Cannot rename to ${JSON.stringify(newName)}: a variable with that name already exists.`).at(uri, symbol2.range);
71045
71055
  }
71046
71056
  const refs = symbol2.sigil === "%" ? findTransientReferences(symbol2.name, workspace, true) : findVariableReferences(symbol2.name, workspace, true);
71047
71057
  for (const ref2 of refs) {
71048
- const start = ref2.range.start.character + 1;
71049
- addEdit(ref2.uri, {
71050
- start: { line: ref2.range.start.line, character: start },
71051
- end: { line: ref2.range.start.line, character: start + symbol2.name.length }
71052
- }, bareName);
71058
+ const doc = workspace.markup.get(ref2.uri);
71059
+ const passage = doc.passageAt(ref2.range.start);
71060
+ const start = passage.contentOffset(positionToOffset(ref2.range.start, doc.lineStarts));
71061
+ const end = passage.contentOffset(positionToOffset(ref2.range.end, doc.lineStarts));
71062
+ const view = sourceView(passage, start, end);
71063
+ const index = viewIndex(view, start);
71064
+ let base;
71065
+ lexJs(view.text.slice(index, viewIndex(view, end)), {
71066
+ variable(sigil, name, at3, _nesting, nameEnd) {
71067
+ if (at3 === 0 && sigil === symbol2.sigil && name === symbol2.name) {
71068
+ base = [pieceOffset(view, index + 1), pieceOffset(view, index + nameEnd)];
71069
+ }
71070
+ }
71071
+ });
71072
+ if (!base) throw new RenameError("Cannot locate the variable name safely.").at(ref2.uri, ref2.range);
71073
+ addEdit(ref2.uri, passage.range(...base), bareName);
71053
71074
  }
71054
71075
  break;
71055
71076
  }
@@ -71204,6 +71225,8 @@ var init_rename = __esm({
71204
71225
  "use strict";
71205
71226
  import_vscode_languageserver2 = __toESM(require_api3(), 1);
71206
71227
  init_tooling();
71228
+ init_source_view();
71229
+ init_text();
71207
71230
  init_link_runtime();
71208
71231
  init_passage_refs();
71209
71232
  init_js_string_literal();
@@ -71358,7 +71381,8 @@ var init_code_lens = __esm({
71358
71381
  range: toLspRange5(l.range),
71359
71382
  command: {
71360
71383
  title: l.command.title,
71361
- command: "spindle.findReferences"
71384
+ // Informational counts: no editor-specific navigation command is registered.
71385
+ command: ""
71362
71386
  }
71363
71387
  }));
71364
71388
  });
@@ -71387,13 +71411,15 @@ function addWidgetParamHints(passages, inRange, workspace, hints) {
71387
71411
  const widget = workspace.widgets.getWidget(token.name);
71388
71412
  if (!widget || widget.params.length === 0) continue;
71389
71413
  const args2 = splitArgs(token.rawArgs);
71414
+ const view = textAt(passage, token.start, token.end) ?? { offset: 0 };
71415
+ const argsStart = viewIndex(view, token.argsStart);
71390
71416
  let from = 0;
71391
71417
  for (let i2 = 0; i2 < Math.min(args2.length, widget.params.length); i2++) {
71392
71418
  const at3 = token.rawArgs.indexOf(args2[i2], from);
71393
71419
  if (at3 === -1) break;
71394
71420
  from = at3 + args2[i2].length;
71395
71421
  hints.push({
71396
- position: passage.position(token.argsStart + at3),
71422
+ position: passage.position(pieceOffset(view, argsStart + at3)),
71397
71423
  label: `${widget.params[i2]}:`,
71398
71424
  kind: "parameter"
71399
71425
  });
@@ -71416,6 +71442,7 @@ var init_inlay_hints = __esm({
71416
71442
  "src/plugins/inlay-hints.ts"() {
71417
71443
  "use strict";
71418
71444
  init_tooling();
71445
+ init_source_view();
71419
71446
  init_tokens();
71420
71447
  inlayHintsPlugin = {
71421
71448
  id: "inlay-hints",
@@ -71580,9 +71607,9 @@ var init_semantic_tokens = __esm({
71580
71607
  });
71581
71608
 
71582
71609
  // src/plugins/code-actions.ts
71583
- import { readFileSync as readFileSync9 } from "node:fs";
71610
+ import { readFileSync as readFileSync7 } from "node:fs";
71584
71611
  import { basename, dirname as dirname3, join as join4 } from "node:path";
71585
- import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL5 } from "node:url";
71612
+ import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL5 } from "node:url";
71586
71613
  function computeCodeActions(uri, diagnostics, workspace, options = {}) {
71587
71614
  const actions = [];
71588
71615
  for (const diag of diagnostics) {
@@ -71654,7 +71681,7 @@ function addMacroToConfig(uri, macroName, workspace, options) {
71654
71681
  let text2 = workspace.documents.getText(configUri);
71655
71682
  if (text2 === void 0) {
71656
71683
  try {
71657
- text2 = readFileSync9(existingPath, "utf-8");
71684
+ text2 = readFileSync7(existingPath, "utf-8");
71658
71685
  } catch {
71659
71686
  return null;
71660
71687
  }
@@ -71665,10 +71692,10 @@ function addMacroToConfig(uri, macroName, workspace, options) {
71665
71692
  }
71666
71693
  function resolveConfigRoot(uri, workspaceRoot) {
71667
71694
  if (workspaceRoot) {
71668
- return workspaceRoot.startsWith("file:") ? fileURLToPath2(workspaceRoot) : workspaceRoot;
71695
+ return workspaceRoot.startsWith("file:") ? fileURLToPath3(workspaceRoot) : workspaceRoot;
71669
71696
  }
71670
71697
  if (!uri.startsWith("file:")) return null;
71671
- const docDir = dirname3(fileURLToPath2(uri));
71698
+ const docDir = dirname3(fileURLToPath3(uri));
71672
71699
  let search = docDir;
71673
71700
  for (let i2 = 0; i2 < 10; i2++) {
71674
71701
  if (findConfigFile(search)) return search;
@@ -71785,18 +71812,6 @@ function lineEndingFor(workspace, uri) {
71785
71812
  }
71786
71813
  return "\n";
71787
71814
  }
71788
- function declarationInsertEdit(text2, headerLine, declaration, eol) {
71789
- const lines = text2.split("\n");
71790
- let contentEnd = lines.length;
71791
- for (let i2 = headerLine + 1; i2 < lines.length; i2++) {
71792
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
71793
- contentEnd = i2;
71794
- break;
71795
- }
71796
- }
71797
- if (contentEnd === lines.length && text2.endsWith("\n")) contentEnd = lines.length;
71798
- return insertLinesAt(text2, contentEnd, declaration, eol);
71799
- }
71800
71815
  function declareVariable(diag, workspace, sigil) {
71801
71816
  if (diag.data?.kind !== "undeclared-variable" || diag.data.sigil !== sigil) return [];
71802
71817
  const { name } = diag.data;
@@ -71804,7 +71819,10 @@ function declareVariable(diag, workspace, sigil) {
71804
71819
  if (!passage) return [];
71805
71820
  const text2 = workspace.documents.getText(passage.uri);
71806
71821
  if (text2 === void 0) return [];
71807
- const edit = declarationInsertEdit(text2, passage.headerEnd.end.line, `${sigil}${name} = 0
71822
+ const markup = workspace.markup.get(passage.uri)?.passages.find((p) => p.passage === passage);
71823
+ if (!markup) return [];
71824
+ const boundary = markup.bodyEnd === text2.length ? text2.split("\n").length : markup.position(markup.content.length).line;
71825
+ const edit = insertLinesAt(text2, boundary, `${sigil}${name} = 0
71808
71826
  `, lineEndingFor(workspace, passage.uri));
71809
71827
  return [{
71810
71828
  title: `Declare '${sigil}${name}' in ${sigil === "$" ? "StoryVariables" : "StoryTransients"}`,
@@ -72298,12 +72316,12 @@ var server_exports2 = {};
72298
72316
  __export(server_exports2, {
72299
72317
  startServer: () => startServer
72300
72318
  });
72301
- import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL6 } from "node:url";
72302
- import { readFileSync as readFileSync10 } from "node:fs";
72319
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL6 } from "node:url";
72320
+ import { readFileSync as readFileSync8 } from "node:fs";
72303
72321
  import { basename as basename2 } from "node:path";
72304
72322
  function uriToFsPath(uri) {
72305
72323
  try {
72306
- return fileURLToPath3(uri);
72324
+ return fileURLToPath4(uri);
72307
72325
  } catch {
72308
72326
  return uri;
72309
72327
  }
@@ -72437,7 +72455,7 @@ function startServer(args2) {
72437
72455
  if (workspace) {
72438
72456
  try {
72439
72457
  const fsPath = uriToFsPath(textDocument.uri);
72440
- const text2 = readFileSync10(fsPath, "utf-8");
72458
+ const text2 = readFileSync8(fsPath, "utf-8");
72441
72459
  workspace.documents.update(textDocument.uri, text2);
72442
72460
  } catch {
72443
72461
  workspace.documents.close(textDocument.uri);
@@ -72473,7 +72491,7 @@ function startServer(args2) {
72473
72491
  } else {
72474
72492
  try {
72475
72493
  const fsPath = uriToFsPath(change.uri);
72476
- const text2 = readFileSync10(fsPath, "utf-8");
72494
+ const text2 = readFileSync8(fsPath, "utf-8");
72477
72495
  if (workspace.documents.has(change.uri)) {
72478
72496
  workspace.documents.update(change.uri, text2);
72479
72497
  } else {
@@ -72509,7 +72527,7 @@ async function scanWorkspaceFiles(root) {
72509
72527
  ];
72510
72528
  for (const filePath of files) {
72511
72529
  try {
72512
- const text2 = readFileSync10(filePath, "utf-8");
72530
+ const text2 = readFileSync8(filePath, "utf-8");
72513
72531
  const uri = fsPathToUri(filePath);
72514
72532
  contents.set(uri, text2);
72515
72533
  } catch {
@@ -72539,7 +72557,7 @@ var init_server4 = __esm({
72539
72557
 
72540
72558
  // src/bin.ts
72541
72559
  var args = process.argv.slice(2);
72542
- var VERSION = "0.14.1";
72560
+ var VERSION = "0.14.3";
72543
72561
  var HELP = `spindle-lsp v${VERSION}
72544
72562
  Language Server Protocol server for Spindle story format
72545
72563