@rohal12/spindle-lsp 0.14.2 → 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,32 +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 = line.slice(nameEnd, nameEnd + rawTags.length).trim();
16728
+ let rest = nameEnd;
16698
16729
  let tags = [];
16699
- if (tagsStr) {
16700
- const inner = tagsStr.substring(1, tagsStr.length - 1);
16701
- if (/[\[\]\{\}]/.test(inner.replace(/\\./g, "ec"))) return null;
16702
- const decoded = inner.replace(/\\(.)/g, "$1").trim();
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();
16703
16734
  tags = decoded ? decoded.split(/\s+/) : [];
16735
+ rest = end + 1;
16736
+ while (rest < line.length && /\s/.test(escaped[rest])) rest++;
16704
16737
  }
16705
16738
  let meta3;
16706
- const metaStr = rawMeta.trim();
16707
- if (metaStr) {
16739
+ if (rest < line.length) {
16740
+ const raw = line.slice(rest).trim();
16741
+ if (escaped[rest] !== "{" || !raw.endsWith("}")) return null;
16708
16742
  try {
16709
- meta3 = JSON.parse(line.substring(nameStart + rawName.length + rawTags.length, nameStart + rawName.length + rawTags.length + rawMeta.length).trim());
16743
+ meta3 = JSON.parse(raw);
16710
16744
  } catch {
16711
16745
  return null;
16712
16746
  }
@@ -16739,11 +16773,10 @@ function isScriptOrStylesheetPassage(passage) {
16739
16773
  function isMarkupPassage(passage) {
16740
16774
  return !(passage.name !== void 0 && NON_MARKUP_PASSAGES.has(passage.name)) && !isScriptOrStylesheetPassage(passage);
16741
16775
  }
16742
- 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;
16743
16777
  var init_passage_parser = __esm({
16744
16778
  "src/core/parsing/passage-parser.ts"() {
16745
16779
  "use strict";
16746
- passageHeaderRegex = /^(::\s*)(.*?)(\[.*?\]\s*)?(\{.*?\}\s*)?\r?$/;
16747
16780
  SPECIAL_PASSAGES = /* @__PURE__ */ new Set([
16748
16781
  "StoryVariables",
16749
16782
  "StoryInit",
@@ -24256,8 +24289,8 @@ var require_dist = __commonJS({
24256
24289
  });
24257
24290
 
24258
24291
  // src/core/workspace/config-loader.ts
24259
- import { readFileSync as readFileSync2, existsSync } from "node:fs";
24260
- import { join, resolve } from "node:path";
24292
+ import { readFileSync as readFileSync3, existsSync } from "node:fs";
24293
+ import { join, resolve as resolve2 } from "node:path";
24261
24294
  function parseConfig(content, format) {
24262
24295
  if (!content.trim()) {
24263
24296
  return { macros: {} };
@@ -24317,7 +24350,7 @@ function findConfigRoot(directory) {
24317
24350
  let search = directory;
24318
24351
  for (let i2 = 0; i2 < 10; i2++) {
24319
24352
  if (findConfigFile(search)) return search;
24320
- const parent = resolve(search, "..");
24353
+ const parent = resolve2(search, "..");
24321
24354
  if (parent === search) break;
24322
24355
  search = parent;
24323
24356
  }
@@ -24326,7 +24359,7 @@ function findConfigRoot(directory) {
24326
24359
  function loadConfigFile(configPath) {
24327
24360
  let content;
24328
24361
  try {
24329
- content = readFileSync2(configPath, "utf-8");
24362
+ content = readFileSync3(configPath, "utf-8");
24330
24363
  } catch (err) {
24331
24364
  const reason = err?.code === "ENOENT" ? "file not found" : err instanceof Error ? err.message : String(err);
24332
24365
  throw new Error(`Cannot read config file '${configPath}': ${reason}`);
@@ -24344,7 +24377,7 @@ function loadConfigFromDisk(workspaceRoot) {
24344
24377
  if (!configPath) {
24345
24378
  return { ...EMPTY_CONFIG };
24346
24379
  }
24347
- const content = readFileSync2(configPath, "utf-8");
24380
+ const content = readFileSync3(configPath, "utf-8");
24348
24381
  const format = configPath.endsWith(".json") ? "json" : "yaml";
24349
24382
  return parseConfig(content, format);
24350
24383
  }
@@ -25088,6 +25121,7 @@ var init_widget_registry = __esm({
25088
25121
  const { name, tags } = markup.passage;
25089
25122
  for (const definition of widgetDefinitions([{ name, tags, content: markup.content }], known)) {
25090
25123
  this.widgets.set(definition.name.toLowerCase(), {
25124
+ tooling: definition,
25091
25125
  name: definition.name,
25092
25126
  params: definition.params,
25093
25127
  uri: markup.passage.uri,
@@ -25175,7 +25209,7 @@ var init_markup_index = __esm({
25175
25209
  });
25176
25210
 
25177
25211
  // src/core/workspace/macro-sources.ts
25178
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
25212
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
25179
25213
  import { dirname, isAbsolute, join as join2, relative } from "node:path";
25180
25214
  import { pathToFileURL as pathToFileURL2 } from "node:url";
25181
25215
  function isExcludedSegment(segment) {
@@ -25224,7 +25258,7 @@ async function addProjectMacroSources(contents, dir) {
25224
25258
  const uri = pathToFileURL2(filePath).toString();
25225
25259
  if (contents.has(uri)) continue;
25226
25260
  try {
25227
- contents.set(uri, readFileSync3(filePath, "utf-8"));
25261
+ contents.set(uri, readFileSync4(filePath, "utf-8"));
25228
25262
  } catch {
25229
25263
  }
25230
25264
  }
@@ -25243,8 +25277,8 @@ var init_macro_sources = __esm({
25243
25277
  });
25244
25278
 
25245
25279
  // src/core/workspace/spindle-version.ts
25246
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
25247
- 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";
25248
25282
  function parseSpindleVersion(text2) {
25249
25283
  const m = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?\s*$/.exec(text2);
25250
25284
  return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : void 0;
@@ -25258,12 +25292,12 @@ function atLeast(version2, minimum) {
25258
25292
  }
25259
25293
  function readInstalledSpindleVersion(startDir) {
25260
25294
  const target = join3("node_modules", "@rohal12", "spindle", "package.json");
25261
- let dir = resolve2(startDir);
25295
+ let dir = resolve3(startDir);
25262
25296
  for (; ; ) {
25263
25297
  const candidate = join3(dir, target);
25264
25298
  if (existsSync3(candidate)) {
25265
25299
  try {
25266
- const version2 = JSON.parse(readFileSync4(candidate, "utf-8")).version;
25300
+ const version2 = JSON.parse(readFileSync5(candidate, "utf-8")).version;
25267
25301
  return typeof version2 === "string" && parseSpindleVersion(version2) ? version2 : void 0;
25268
25302
  } catch {
25269
25303
  return void 0;
@@ -25294,7 +25328,7 @@ var init_spindle_version = __esm({
25294
25328
  });
25295
25329
 
25296
25330
  // src/core/workspace/story-format.ts
25297
- import { readFileSync as readFileSync5 } from "node:fs";
25331
+ import { readFileSync as readFileSync6 } from "node:fs";
25298
25332
  function isSpindleFormatName(name) {
25299
25333
  return name.trim().toLowerCase() === "spindle";
25300
25334
  }
@@ -25374,7 +25408,7 @@ async function findStoryFormat(texts, dir) {
25374
25408
  const projectTexts = [];
25375
25409
  for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
25376
25410
  try {
25377
- projectTexts.push(stripBom(readFileSync5(file2, "utf-8")));
25411
+ projectTexts.push(stripBom(readFileSync6(file2, "utf-8")));
25378
25412
  } catch {
25379
25413
  }
25380
25414
  }
@@ -25401,7 +25435,7 @@ async function findSpindleTarget(texts, dir) {
25401
25435
  const projectTexts = [];
25402
25436
  for (const file2 of await findProjectFiles(findProjectRoot(dir), "**/*.{tw,twee}")) {
25403
25437
  try {
25404
- projectTexts.push(readFileSync5(file2, "utf-8"));
25438
+ projectTexts.push(readFileSync6(file2, "utf-8"));
25405
25439
  } catch {
25406
25440
  }
25407
25441
  }
@@ -25877,7 +25911,7 @@ var init_workspace_model = __esm({
25877
25911
  text: (uri) => this.documents.getText(uri),
25878
25912
  passages: (uri) => this.passages.getPassagesInDocument(uri),
25879
25913
  context: () => ({
25880
- macros: this.macros.toolingMacros(),
25914
+ macros: this.toolingMacros(),
25881
25915
  isBlock: (name) => this.isContainer(name)
25882
25916
  })
25883
25917
  });
@@ -26054,60 +26088,38 @@ var init_workspace_model = __esm({
26054
26088
  this.markup.invalidate();
26055
26089
  this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
26056
26090
  this.target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
26057
- const storyVars = this.passages.getStoryVariables();
26058
- if (storyVars) {
26059
- const text2 = this.documents.getText(storyVars.uri);
26060
- if (text2) {
26061
- const lines = text2.split("\n");
26062
- const contentStart = storyVars.headerEnd.end.line + 1;
26063
- let contentEnd = lines.length;
26064
- for (let i2 = contentStart; i2 < lines.length; i2++) {
26065
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
26066
- contentEnd = i2;
26067
- break;
26068
- }
26069
- }
26070
- const content = lines.slice(contentStart, contentEnd).join("\n");
26071
- this.variables.parseStoryVariables(content, contentStart, storyVars.uri);
26072
- }
26073
- } else {
26074
- this.variables.clearStoryVariables();
26075
- }
26076
- const storyTransients = this.passages.getStoryTransients();
26077
- if (storyTransients) {
26078
- const text2 = this.documents.getText(storyTransients.uri);
26079
- if (text2) {
26080
- const lines = text2.split("\n");
26081
- const contentStart = storyTransients.headerEnd.end.line + 1;
26082
- let contentEnd = lines.length;
26083
- for (let i2 = contentStart; i2 < lines.length; i2++) {
26084
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
26085
- contentEnd = i2;
26086
- break;
26087
- }
26088
- }
26089
- const content = lines.slice(contentStart, contentEnd).join("\n");
26090
- this.variables.parseStoryTransients(content, contentStart, storyTransients.uri);
26091
- }
26092
- } else {
26093
- this.variables.clearStoryTransients();
26094
- }
26095
- 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) => {
26096
26102
  const markup = isMacroSource(uri) ? void 0 : this.markup.get(uri);
26097
26103
  return markup ? [markup] : [];
26098
26104
  });
26099
26105
  const markups = /* @__PURE__ */ new Map();
26100
- 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);
26101
26107
  this.widgets.scan(
26102
26108
  this.passages.getAllPassages().flatMap((passage) => markups.get(passage) ?? []),
26103
26109
  this.macros.toolingMacros()
26104
26110
  );
26111
+ this.markup.invalidate();
26105
26112
  this.widgets.clearInvocations();
26106
- for (const doc of documents) {
26113
+ for (const doc of readDocuments()) {
26107
26114
  this.variables.scanDocument(doc.uri, doc.text, doc);
26108
26115
  this.widgets.recordInvocations(doc.uri, doc.passages);
26109
26116
  }
26110
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
+ }
26111
26123
  /** Debounce modelReady emission. */
26112
26124
  scheduleModelReady() {
26113
26125
  if (this.debounceTimer) {
@@ -27881,8 +27893,7 @@ var check_exports = {};
27881
27893
  __export(check_exports, {
27882
27894
  runCheck: () => runCheck
27883
27895
  });
27884
- import { readFileSync as readFileSync6 } from "node:fs";
27885
- import { resolve as resolve3 } from "node:path";
27896
+ import { resolve as resolve4 } from "node:path";
27886
27897
  import { pathToFileURL as pathToFileURL3 } from "node:url";
27887
27898
  function parseArgs(args2) {
27888
27899
  const options = {
@@ -27958,22 +27969,21 @@ async function runCheck(args2) {
27958
27969
  let explicitConfig = null;
27959
27970
  if (options.configPath) {
27960
27971
  try {
27961
- explicitConfig = loadConfigFile(resolve3(cwd, options.configPath));
27972
+ explicitConfig = loadConfigFile(resolve4(cwd, options.configPath));
27962
27973
  } catch (err) {
27963
27974
  console.error(`spindle-lsp check: ${err instanceof Error ? err.message : String(err)}`);
27964
27975
  return 2;
27965
27976
  }
27966
27977
  }
27967
- const files = [];
27968
- for (const pattern of options.patterns) {
27969
- const matches = await Ze(pattern, {
27970
- cwd,
27971
- absolute: true,
27972
- nodir: true
27973
- });
27974
- 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;
27975
27986
  }
27976
- const uniqueFiles = [...new Set(files)];
27977
27987
  if (uniqueFiles.length === 0) {
27978
27988
  if (options.format === "pretty") {
27979
27989
  console.log("No files found");
@@ -27997,15 +28007,7 @@ async function runCheck(args2) {
27997
28007
  return 1;
27998
28008
  }
27999
28009
  }
28000
- const fileContents = /* @__PURE__ */ new Map();
28001
- for (const filePath of uniqueFiles) {
28002
- try {
28003
- const text2 = readFileSync6(filePath, "utf-8");
28004
- const uri = pathToFileURL3(filePath).toString();
28005
- fileContents.set(uri, text2);
28006
- } catch {
28007
- }
28008
- }
28010
+ const fileContents = new Map([...contents].map(([path, text2]) => [pathToFileURL3(path).toString(), text2]));
28009
28011
  const storyFormat = await findStoryFormat(fileContents.values(), commonDir);
28010
28012
  if (!storyFormat.isSpindle) {
28011
28013
  console.error(skippedFormatNote(storyFormat));
@@ -28058,7 +28060,7 @@ var SEVERITY_ORDER;
28058
28060
  var init_check = __esm({
28059
28061
  "src/cli/check.ts"() {
28060
28062
  "use strict";
28061
- init_index_min();
28063
+ init_file_inputs();
28062
28064
  init_workspace_model();
28063
28065
  init_diagnostics();
28064
28066
  init_config_loader();
@@ -28126,7 +28128,7 @@ var init_segment = __esm({
28126
28128
  });
28127
28129
 
28128
28130
  // src/plugins/format/prettier-bridge.ts
28129
- import { resolve as resolve4 } from "node:path";
28131
+ import { resolve as resolve5 } from "node:path";
28130
28132
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28131
28133
  async function loadPrettier(filePath) {
28132
28134
  prettier ??= import("prettier").catch(() => null);
@@ -28134,7 +28136,7 @@ async function loadPrettier(filePath) {
28134
28136
  if (!p) return void 0;
28135
28137
  try {
28136
28138
  const path = filePath?.startsWith("file:") ? fileURLToPath2(filePath) : filePath;
28137
- const resolved = await p.resolveConfig(path ?? resolve4(process.cwd(), "story.tw"));
28139
+ const resolved = await p.resolveConfig(path ?? resolve5(process.cwd(), "story.tw"));
28138
28140
  return { prettier: p, options: { tabWidth: 2, ...resolved ?? {} } };
28139
28141
  } catch {
28140
28142
  return { prettier: p, options: { tabWidth: 2 } };
@@ -28192,8 +28194,10 @@ async function formatLf2(text2, options) {
28192
28194
  resultLines.push(header.replace(/\s+$/, ""));
28193
28195
  }
28194
28196
  if (passage.body === "") continue;
28195
- const body = await formatPassage(passage.body, {
28196
- 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,
28197
28201
  prettier: prettier2?.prettier,
28198
28202
  prettierOptions: prettier2?.options,
28199
28203
  isBlock,
@@ -28232,6 +28236,7 @@ var init_format = __esm({
28232
28236
  "src/plugins/format.ts"() {
28233
28237
  "use strict";
28234
28238
  init_tooling();
28239
+ init_passage_parser();
28235
28240
  init_segment();
28236
28241
  init_prettier_bridge();
28237
28242
  PASSAGE_HEADER_REGEX = /^(::)\s*/;
@@ -28291,7 +28296,7 @@ var format_exports = {};
28291
28296
  __export(format_exports, {
28292
28297
  runFormat: () => runFormat
28293
28298
  });
28294
- import { readFileSync as readFileSync7, writeFileSync } from "node:fs";
28299
+ import { writeFileSync } from "node:fs";
28295
28300
  function parseArgs2(args2) {
28296
28301
  const options = {
28297
28302
  check: false,
@@ -28327,27 +28332,19 @@ function parseArgs2(args2) {
28327
28332
  async function runFormat(args2) {
28328
28333
  const options = parseArgs2(args2);
28329
28334
  const cwd = process.cwd();
28330
- const files = [];
28331
- for (const pattern of options.patterns) {
28332
- const matches = await Ze(pattern, {
28333
- cwd,
28334
- absolute: true,
28335
- nodir: true
28336
- });
28337
- 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;
28338
28343
  }
28339
- const uniqueFiles = [...new Set(files)];
28340
28344
  if (uniqueFiles.length === 0) {
28341
28345
  console.log("No files found");
28342
28346
  return 0;
28343
28347
  }
28344
- const texts = /* @__PURE__ */ new Map();
28345
- for (const filePath of uniqueFiles) {
28346
- try {
28347
- texts.set(filePath, readFileSync7(filePath, "utf-8"));
28348
- } catch {
28349
- }
28350
- }
28351
28348
  const storyFormat = await findStoryFormat(texts.values(), commonDirectory(uniqueFiles));
28352
28349
  if (!storyFormat.isSpindle) {
28353
28350
  console.error(skippedFormatNote(storyFormat));
@@ -28360,6 +28357,7 @@ async function runFormat(args2) {
28360
28357
  formatOpts.maxLineLength = options.maxLineLength;
28361
28358
  }
28362
28359
  const unformatted = [];
28360
+ let failed = false;
28363
28361
  for (const [filePath, text2] of texts) {
28364
28362
  try {
28365
28363
  const formatted = await formatDocument(text2, { ...formatOpts, filePath });
@@ -28371,9 +28369,12 @@ async function runFormat(args2) {
28371
28369
  writeFileSync(filePath, formatted, "utf-8");
28372
28370
  }
28373
28371
  }
28374
- } catch {
28372
+ } catch (error48) {
28373
+ failed = true;
28374
+ console.error(`spindle-lsp format: ${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`);
28375
28375
  }
28376
28376
  }
28377
+ if (failed) return 2;
28377
28378
  if (options.check) {
28378
28379
  if (unformatted.length > 0) {
28379
28380
  for (const file2 of unformatted) {
@@ -28390,7 +28391,7 @@ ${unformatted.length} file${unformatted.length !== 1 ? "s" : ""} would be reform
28390
28391
  var init_format2 = __esm({
28391
28392
  "src/cli/format.ts"() {
28392
28393
  "use strict";
28393
- init_index_min();
28394
+ init_file_inputs();
28394
28395
  init_format();
28395
28396
  init_macro_sources();
28396
28397
  init_story_format();
@@ -59658,8 +59659,7 @@ __export(server_exports, {
59658
59659
  formatFiles: () => formatFiles,
59659
59660
  startMcpServer: () => startMcpServer
59660
59661
  });
59661
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, statSync } from "node:fs";
59662
- import { resolve as resolve5 } from "node:path";
59662
+ import { writeFileSync as writeFileSync2 } from "node:fs";
59663
59663
  import { pathToFileURL as pathToFileURL4 } from "node:url";
59664
59664
  function filterBySeverity2(diagnostics, minSeverity) {
59665
59665
  const minIdx = SEVERITY_ORDER2.indexOf(minSeverity);
@@ -59668,37 +59668,8 @@ function filterBySeverity2(diagnostics, minSeverity) {
59668
59668
  return idx >= minIdx;
59669
59669
  });
59670
59670
  }
59671
- async function resolveFiles(pattern, cwd) {
59672
- let input = pattern;
59673
- let directory = cwd;
59674
- try {
59675
- const literal3 = resolve5(cwd, pattern);
59676
- const stat = statSync(literal3);
59677
- if (stat.isFile()) return [literal3];
59678
- if (stat.isDirectory()) {
59679
- directory = literal3;
59680
- input = "**/*.{tw,twee}";
59681
- }
59682
- } catch {
59683
- }
59684
- const matches = await Ze(input, {
59685
- cwd: directory,
59686
- absolute: true,
59687
- nodir: true
59688
- });
59689
- return [...new Set(matches)];
59690
- }
59691
59671
  function readFiles(files) {
59692
- const fileContents = /* @__PURE__ */ new Map();
59693
- for (const filePath of files) {
59694
- try {
59695
- const text2 = readFileSync8(filePath, "utf-8");
59696
- const uri = pathToFileURL4(filePath).toString();
59697
- fileContents.set(uri, text2);
59698
- } catch {
59699
- }
59700
- }
59701
- return fileContents;
59672
+ return new Map([...readFileContents(files)].map(([path, text2]) => [pathToFileURL4(path).toString(), text2]));
59702
59673
  }
59703
59674
  async function skippedReason(fileContents, files) {
59704
59675
  const format = await findStoryFormat(fileContents.values(), commonDirectory(files));
@@ -59735,7 +59706,6 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
59735
59706
  const results = [];
59736
59707
  for (const filePath of files) {
59737
59708
  const uri = pathToFileURL4(filePath).toString();
59738
- if (!workspace.documents.has(uri)) continue;
59739
59709
  let diags = computeDiagnostics(uri, workspace);
59740
59710
  if (severity) {
59741
59711
  diags = filterBySeverity2(diags, severity);
@@ -59781,7 +59751,8 @@ async function formatFiles(pattern, cwd = process.cwd()) {
59781
59751
  } else {
59782
59752
  unchanged++;
59783
59753
  }
59784
- } catch {
59754
+ } catch (error48) {
59755
+ throw new Error(`${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`, { cause: error48 });
59785
59756
  }
59786
59757
  }
59787
59758
  return { formatted, unchanged, files: changedFiles, ...warning && { warning } };
@@ -59800,7 +59771,8 @@ async function checkFormatting(pattern, cwd = process.cwd()) {
59800
59771
  try {
59801
59772
  const result = await formatDocument(text2, { filePath });
59802
59773
  (result !== text2 ? needsFormatting : alreadyFormatted).push(relativeTo(cwd, filePath));
59803
- } catch {
59774
+ } catch (error48) {
59775
+ throw new Error(`${filePath}: ${error48 instanceof Error ? error48.message : String(error48)}`, { cause: error48 });
59804
59776
  }
59805
59777
  }
59806
59778
  return { needsFormatting, alreadyFormatted, ...warning && { warning } };
@@ -59860,7 +59832,7 @@ var SEVERITY_ORDER2;
59860
59832
  var init_server3 = __esm({
59861
59833
  "src/mcp/server.ts"() {
59862
59834
  "use strict";
59863
- init_index_min();
59835
+ init_file_inputs();
59864
59836
  init_mcp();
59865
59837
  init_stdio2();
59866
59838
  init_zod();
@@ -71635,7 +71607,7 @@ var init_semantic_tokens = __esm({
71635
71607
  });
71636
71608
 
71637
71609
  // src/plugins/code-actions.ts
71638
- import { readFileSync as readFileSync9 } from "node:fs";
71610
+ import { readFileSync as readFileSync7 } from "node:fs";
71639
71611
  import { basename, dirname as dirname3, join as join4 } from "node:path";
71640
71612
  import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL5 } from "node:url";
71641
71613
  function computeCodeActions(uri, diagnostics, workspace, options = {}) {
@@ -71709,7 +71681,7 @@ function addMacroToConfig(uri, macroName, workspace, options) {
71709
71681
  let text2 = workspace.documents.getText(configUri);
71710
71682
  if (text2 === void 0) {
71711
71683
  try {
71712
- text2 = readFileSync9(existingPath, "utf-8");
71684
+ text2 = readFileSync7(existingPath, "utf-8");
71713
71685
  } catch {
71714
71686
  return null;
71715
71687
  }
@@ -71840,18 +71812,6 @@ function lineEndingFor(workspace, uri) {
71840
71812
  }
71841
71813
  return "\n";
71842
71814
  }
71843
- function declarationInsertEdit(text2, headerLine, declaration, eol) {
71844
- const lines = text2.split("\n");
71845
- let contentEnd = lines.length;
71846
- for (let i2 = headerLine + 1; i2 < lines.length; i2++) {
71847
- if (/^\uFEFF?::\s+/.test(lines[i2])) {
71848
- contentEnd = i2;
71849
- break;
71850
- }
71851
- }
71852
- if (contentEnd === lines.length && text2.endsWith("\n")) contentEnd = lines.length;
71853
- return insertLinesAt(text2, contentEnd, declaration, eol);
71854
- }
71855
71815
  function declareVariable(diag, workspace, sigil) {
71856
71816
  if (diag.data?.kind !== "undeclared-variable" || diag.data.sigil !== sigil) return [];
71857
71817
  const { name } = diag.data;
@@ -71859,7 +71819,10 @@ function declareVariable(diag, workspace, sigil) {
71859
71819
  if (!passage) return [];
71860
71820
  const text2 = workspace.documents.getText(passage.uri);
71861
71821
  if (text2 === void 0) return [];
71862
- 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
71863
71826
  `, lineEndingFor(workspace, passage.uri));
71864
71827
  return [{
71865
71828
  title: `Declare '${sigil}${name}' in ${sigil === "$" ? "StoryVariables" : "StoryTransients"}`,
@@ -72354,7 +72317,7 @@ __export(server_exports2, {
72354
72317
  startServer: () => startServer
72355
72318
  });
72356
72319
  import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL6 } from "node:url";
72357
- import { readFileSync as readFileSync10 } from "node:fs";
72320
+ import { readFileSync as readFileSync8 } from "node:fs";
72358
72321
  import { basename as basename2 } from "node:path";
72359
72322
  function uriToFsPath(uri) {
72360
72323
  try {
@@ -72492,7 +72455,7 @@ function startServer(args2) {
72492
72455
  if (workspace) {
72493
72456
  try {
72494
72457
  const fsPath = uriToFsPath(textDocument.uri);
72495
- const text2 = readFileSync10(fsPath, "utf-8");
72458
+ const text2 = readFileSync8(fsPath, "utf-8");
72496
72459
  workspace.documents.update(textDocument.uri, text2);
72497
72460
  } catch {
72498
72461
  workspace.documents.close(textDocument.uri);
@@ -72528,7 +72491,7 @@ function startServer(args2) {
72528
72491
  } else {
72529
72492
  try {
72530
72493
  const fsPath = uriToFsPath(change.uri);
72531
- const text2 = readFileSync10(fsPath, "utf-8");
72494
+ const text2 = readFileSync8(fsPath, "utf-8");
72532
72495
  if (workspace.documents.has(change.uri)) {
72533
72496
  workspace.documents.update(change.uri, text2);
72534
72497
  } else {
@@ -72564,7 +72527,7 @@ async function scanWorkspaceFiles(root) {
72564
72527
  ];
72565
72528
  for (const filePath of files) {
72566
72529
  try {
72567
- const text2 = readFileSync10(filePath, "utf-8");
72530
+ const text2 = readFileSync8(filePath, "utf-8");
72568
72531
  const uri = fsPathToUri(filePath);
72569
72532
  contents.set(uri, text2);
72570
72533
  } catch {
@@ -72594,7 +72557,7 @@ var init_server4 = __esm({
72594
72557
 
72595
72558
  // src/bin.ts
72596
72559
  var args = process.argv.slice(2);
72597
- var VERSION = "0.14.2";
72560
+ var VERSION = "0.14.3";
72598
72561
  var HELP = `spindle-lsp v${VERSION}
72599
72562
  Language Server Protocol server for Spindle story format
72600
72563