@rohal12/spindle-lsp 0.15.0 → 0.15.2

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
@@ -50,6 +50,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
50
50
  ));
51
51
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
52
52
 
53
+ // src/version.ts
54
+ var VERSION;
55
+ var init_version = __esm({
56
+ "src/version.ts"() {
57
+ "use strict";
58
+ VERSION = false ? pkg.version : "0.15.2";
59
+ }
60
+ });
61
+
53
62
  // node_modules/glob/dist/esm/index.min.js
54
63
  import { fileURLToPath as Wi } from "node:url";
55
64
  import { posix as mi, win32 as re } from "node:path";
@@ -3041,7 +3050,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
3041
3050
 
3042
3051
  // src/core/workspace/file-inputs.ts
3043
3052
  import { readFileSync, statSync } from "node:fs";
3044
- import { resolve } from "node:path";
3053
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3045
3054
  async function resolveFiles(pattern, cwd) {
3046
3055
  let input = pattern;
3047
3056
  let directory = cwd;
@@ -3066,6 +3075,10 @@ async function resolveInputs(patterns, cwd) {
3066
3075
  const files = await Promise.all(patterns.map((pattern) => resolveFiles(pattern, cwd)));
3067
3076
  return [...new Set(files.flat())];
3068
3077
  }
3078
+ function relativeTo(cwd, filePath) {
3079
+ const rel = relative(cwd, filePath);
3080
+ return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? filePath : rel;
3081
+ }
3069
3082
  var init_file_inputs = __esm({
3070
3083
  "src/core/workspace/file-inputs.ts"() {
3071
3084
  "use strict";
@@ -23372,10 +23385,11 @@ function parsePassageHeader(rawLine, lineNumber) {
23372
23385
  const delimiter = /[\[\]{}]/g;
23373
23386
  delimiter.lastIndex = nameStart;
23374
23387
  const first = delimiter.exec(escaped);
23375
- const nameEnd = first?.index ?? line.length;
23376
- const name = line.slice(nameStart, nameEnd).trim();
23388
+ const boundary = first?.index ?? line.length;
23389
+ const nameEnd = escaped.slice(0, boundary).trimEnd().length;
23390
+ const name = line.slice(nameStart, nameEnd).replace(/\\(.)/g, "$1").trim();
23377
23391
  if (!name) return null;
23378
- let rest = nameEnd;
23392
+ let rest = boundary;
23379
23393
  let tags = [];
23380
23394
  if (escaped[rest] === "[") {
23381
23395
  const end = escaped.indexOf("]", rest + 1);
@@ -23400,14 +23414,13 @@ function parsePassageHeader(rawLine, lineNumber) {
23400
23414
  end: { line: lineNumber, character: line.length }
23401
23415
  };
23402
23416
  const nameCharStart = nameStart;
23403
- const nameCharEnd = nameCharStart + name.length;
23417
+ const nameCharEnd = nameEnd;
23404
23418
  const nameRange = {
23405
23419
  start: { line: lineNumber, character: nameCharStart },
23406
23420
  end: { line: lineNumber, character: nameCharEnd }
23407
23421
  };
23408
23422
  return {
23409
- name: name.replace(/\\(.)/g, "$1"),
23410
- // unescape
23423
+ name,
23411
23424
  tags,
23412
23425
  meta: meta3,
23413
23426
  headerRange,
@@ -27577,10 +27590,10 @@ var require_resolve_block_map = __commonJS({
27577
27590
  let offset2 = bm.offset;
27578
27591
  let commentEnd = null;
27579
27592
  for (const collItem of bm.items) {
27580
- const { start, key, sep, value } = collItem;
27593
+ const { start, key, sep: sep2, value } = collItem;
27581
27594
  const keyProps = resolveProps.resolveProps(start, {
27582
27595
  indicator: "explicit-key-ind",
27583
- next: key ?? sep?.[0],
27596
+ next: key ?? sep2?.[0],
27584
27597
  offset: offset2,
27585
27598
  onError,
27586
27599
  parentIndent: bm.indent,
@@ -27594,7 +27607,7 @@ var require_resolve_block_map = __commonJS({
27594
27607
  else if ("indent" in key && key.indent !== bm.indent)
27595
27608
  onError(offset2, "BAD_INDENT", startColMsg);
27596
27609
  }
27597
- if (!keyProps.anchor && !keyProps.tag && !sep) {
27610
+ if (!keyProps.anchor && !keyProps.tag && !sep2) {
27598
27611
  commentEnd = keyProps.end;
27599
27612
  if (keyProps.comment) {
27600
27613
  if (map2.comment)
@@ -27618,7 +27631,7 @@ var require_resolve_block_map = __commonJS({
27618
27631
  ctx.atKey = false;
27619
27632
  if (utilMapIncludes.mapIncludes(ctx, map2.items, keyNode))
27620
27633
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
27621
- const valueProps = resolveProps.resolveProps(sep ?? [], {
27634
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
27622
27635
  indicator: "map-value-ind",
27623
27636
  next: value,
27624
27637
  offset: keyNode.range[2],
@@ -27634,7 +27647,7 @@ var require_resolve_block_map = __commonJS({
27634
27647
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
27635
27648
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
27636
27649
  }
27637
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset2, sep, null, valueProps, onError);
27650
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset2, sep2, null, valueProps, onError);
27638
27651
  if (ctx.schema.compat)
27639
27652
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
27640
27653
  offset2 = valueNode.range[2];
@@ -27725,7 +27738,7 @@ var require_resolve_end = __commonJS({
27725
27738
  let comment = "";
27726
27739
  if (end) {
27727
27740
  let hasSpace = false;
27728
- let sep = "";
27741
+ let sep2 = "";
27729
27742
  for (const token of end) {
27730
27743
  const { source, type } = token;
27731
27744
  switch (type) {
@@ -27739,13 +27752,13 @@ var require_resolve_end = __commonJS({
27739
27752
  if (!comment)
27740
27753
  comment = cb;
27741
27754
  else
27742
- comment += sep + cb;
27743
- sep = "";
27755
+ comment += sep2 + cb;
27756
+ sep2 = "";
27744
27757
  break;
27745
27758
  }
27746
27759
  case "newline":
27747
27760
  if (comment)
27748
- sep += source;
27761
+ sep2 += source;
27749
27762
  hasSpace = true;
27750
27763
  break;
27751
27764
  default:
@@ -27788,18 +27801,18 @@ var require_resolve_flow_collection = __commonJS({
27788
27801
  let offset2 = fc.offset + fc.start.source.length;
27789
27802
  for (let i2 = 0; i2 < fc.items.length; ++i2) {
27790
27803
  const collItem = fc.items[i2];
27791
- const { start, key, sep, value } = collItem;
27804
+ const { start, key, sep: sep2, value } = collItem;
27792
27805
  const props = resolveProps.resolveProps(start, {
27793
27806
  flow: fcName,
27794
27807
  indicator: "explicit-key-ind",
27795
- next: key ?? sep?.[0],
27808
+ next: key ?? sep2?.[0],
27796
27809
  offset: offset2,
27797
27810
  onError,
27798
27811
  parentIndent: fc.indent,
27799
27812
  startOnNewline: false
27800
27813
  });
27801
27814
  if (!props.found) {
27802
- if (!props.anchor && !props.tag && !sep && !value) {
27815
+ if (!props.anchor && !props.tag && !sep2 && !value) {
27803
27816
  if (i2 === 0 && props.comma)
27804
27817
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
27805
27818
  else if (i2 < fc.items.length - 1)
@@ -27853,8 +27866,8 @@ var require_resolve_flow_collection = __commonJS({
27853
27866
  }
27854
27867
  }
27855
27868
  }
27856
- if (!isMap2 && !sep && !props.found) {
27857
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError);
27869
+ if (!isMap2 && !sep2 && !props.found) {
27870
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
27858
27871
  coll.items.push(valueNode);
27859
27872
  offset2 = valueNode.range[2];
27860
27873
  if (isBlock(value))
@@ -27866,7 +27879,7 @@ var require_resolve_flow_collection = __commonJS({
27866
27879
  if (isBlock(key))
27867
27880
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
27868
27881
  ctx.atKey = false;
27869
- const valueProps = resolveProps.resolveProps(sep ?? [], {
27882
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
27870
27883
  flow: fcName,
27871
27884
  indicator: "map-value-ind",
27872
27885
  next: value,
@@ -27877,8 +27890,8 @@ var require_resolve_flow_collection = __commonJS({
27877
27890
  });
27878
27891
  if (valueProps.found) {
27879
27892
  if (!isMap2 && !props.found && ctx.options.strict) {
27880
- if (sep)
27881
- for (const st2 of sep) {
27893
+ if (sep2)
27894
+ for (const st2 of sep2) {
27882
27895
  if (st2 === valueProps.found)
27883
27896
  break;
27884
27897
  if (st2.type === "newline") {
@@ -27895,7 +27908,7 @@ var require_resolve_flow_collection = __commonJS({
27895
27908
  else
27896
27909
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
27897
27910
  }
27898
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null;
27911
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
27899
27912
  if (valueNode) {
27900
27913
  if (isBlock(value))
27901
27914
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -28075,7 +28088,7 @@ var require_resolve_block_scalar = __commonJS({
28075
28088
  chompStart = i2 + 1;
28076
28089
  }
28077
28090
  let value = "";
28078
- let sep = "";
28091
+ let sep2 = "";
28079
28092
  let prevMoreIndented = false;
28080
28093
  for (let i2 = 0; i2 < contentStart; ++i2)
28081
28094
  value += lines[i2][0].slice(trimIndent) + "\n";
@@ -28092,24 +28105,24 @@ var require_resolve_block_scalar = __commonJS({
28092
28105
  indent = "";
28093
28106
  }
28094
28107
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
28095
- value += sep + indent.slice(trimIndent) + content2;
28096
- sep = "\n";
28108
+ value += sep2 + indent.slice(trimIndent) + content2;
28109
+ sep2 = "\n";
28097
28110
  } else if (indent.length > trimIndent || content2[0] === " ") {
28098
- if (sep === " ")
28099
- sep = "\n";
28100
- else if (!prevMoreIndented && sep === "\n")
28101
- sep = "\n\n";
28102
- value += sep + indent.slice(trimIndent) + content2;
28103
- sep = "\n";
28111
+ if (sep2 === " ")
28112
+ sep2 = "\n";
28113
+ else if (!prevMoreIndented && sep2 === "\n")
28114
+ sep2 = "\n\n";
28115
+ value += sep2 + indent.slice(trimIndent) + content2;
28116
+ sep2 = "\n";
28104
28117
  prevMoreIndented = true;
28105
28118
  } else if (content2 === "") {
28106
- if (sep === "\n")
28119
+ if (sep2 === "\n")
28107
28120
  value += "\n";
28108
28121
  else
28109
- sep = "\n";
28122
+ sep2 = "\n";
28110
28123
  } else {
28111
- value += sep + content2;
28112
- sep = " ";
28124
+ value += sep2 + content2;
28125
+ sep2 = " ";
28113
28126
  prevMoreIndented = false;
28114
28127
  }
28115
28128
  }
@@ -28292,25 +28305,25 @@ var require_resolve_flow_scalar = __commonJS({
28292
28305
  trimBoth = /^[ \t]+|[ \t]+$/g;
28293
28306
  }
28294
28307
  let res = match[1].replace(trimEnd, "");
28295
- let sep = " ";
28308
+ let sep2 = " ";
28296
28309
  let pos = line.lastIndex;
28297
28310
  while (match = line.exec(source)) {
28298
28311
  const lm = match[1].replace(trimBoth, "");
28299
28312
  if (lm === "") {
28300
- if (sep === "\n")
28301
- res += sep;
28313
+ if (sep2 === "\n")
28314
+ res += sep2;
28302
28315
  else
28303
- sep = "\n";
28316
+ sep2 = "\n";
28304
28317
  } else {
28305
- res += sep + lm;
28306
- sep = " ";
28318
+ res += sep2 + lm;
28319
+ sep2 = " ";
28307
28320
  }
28308
28321
  pos = line.lastIndex;
28309
28322
  }
28310
28323
  const last2 = /[ \t]*(.*)/sy;
28311
28324
  last2.lastIndex = pos;
28312
28325
  match = last2.exec(source);
28313
- return res + sep + (match?.[1] ?? "");
28326
+ return res + sep2 + (match?.[1] ?? "");
28314
28327
  }
28315
28328
  function doubleQuotedValue(source, onError) {
28316
28329
  let res = "";
@@ -29120,14 +29133,14 @@ var require_cst_stringify = __commonJS({
29120
29133
  }
29121
29134
  }
29122
29135
  }
29123
- function stringifyItem({ start, key, sep, value }) {
29136
+ function stringifyItem({ start, key, sep: sep2, value }) {
29124
29137
  let res = "";
29125
29138
  for (const st2 of start)
29126
29139
  res += st2.source;
29127
29140
  if (key)
29128
29141
  res += stringifyToken(key);
29129
- if (sep)
29130
- for (const st2 of sep)
29142
+ if (sep2)
29143
+ for (const st2 of sep2)
29131
29144
  res += st2.source;
29132
29145
  if (value)
29133
29146
  res += stringifyToken(value);
@@ -30294,18 +30307,18 @@ var require_parser = __commonJS({
30294
30307
  if (this.type === "map-value-ind") {
30295
30308
  const prev = getPrevProps(this.peek(2));
30296
30309
  const start = getFirstKeyStartProps(prev);
30297
- let sep;
30310
+ let sep2;
30298
30311
  if (scalar.end) {
30299
- sep = scalar.end;
30300
- sep.push(this.sourceToken);
30312
+ sep2 = scalar.end;
30313
+ sep2.push(this.sourceToken);
30301
30314
  delete scalar.end;
30302
30315
  } else
30303
- sep = [this.sourceToken];
30316
+ sep2 = [this.sourceToken];
30304
30317
  const map2 = {
30305
30318
  type: "block-map",
30306
30319
  offset: scalar.offset,
30307
30320
  indent: scalar.indent,
30308
- items: [{ start, key: scalar, sep }]
30321
+ items: [{ start, key: scalar, sep: sep2 }]
30309
30322
  };
30310
30323
  this.onKeyLine = true;
30311
30324
  this.stack[this.stack.length - 1] = map2;
@@ -30458,15 +30471,15 @@ var require_parser = __commonJS({
30458
30471
  } else if (isFlowToken(it2.key) && !includesToken(it2.sep, "newline")) {
30459
30472
  const start2 = getFirstKeyStartProps(it2.start);
30460
30473
  const key = it2.key;
30461
- const sep = it2.sep;
30462
- sep.push(this.sourceToken);
30474
+ const sep2 = it2.sep;
30475
+ sep2.push(this.sourceToken);
30463
30476
  delete it2.key;
30464
30477
  delete it2.sep;
30465
30478
  this.stack.push({
30466
30479
  type: "block-map",
30467
30480
  offset: this.offset,
30468
30481
  indent: this.indent,
30469
- items: [{ start: start2, key, sep }]
30482
+ items: [{ start: start2, key, sep: sep2 }]
30470
30483
  });
30471
30484
  } else if (start.length > 0) {
30472
30485
  it2.sep = it2.sep.concat(start, this.sourceToken);
@@ -30660,13 +30673,13 @@ var require_parser = __commonJS({
30660
30673
  const prev = getPrevProps(parent);
30661
30674
  const start = getFirstKeyStartProps(prev);
30662
30675
  fixFlowSeqItems(fc);
30663
- const sep = fc.end.splice(1, fc.end.length);
30664
- sep.push(this.sourceToken);
30676
+ const sep2 = fc.end.splice(1, fc.end.length);
30677
+ sep2.push(this.sourceToken);
30665
30678
  const map2 = {
30666
30679
  type: "block-map",
30667
30680
  offset: fc.offset,
30668
30681
  indent: fc.indent,
30669
- items: [{ start, key: fc, sep }]
30682
+ items: [{ start, key: fc, sep: sep2 }]
30670
30683
  };
30671
30684
  this.onKeyLine = true;
30672
30685
  this.stack[this.stack.length - 1] = map2;
@@ -31515,6 +31528,8 @@ var init_variable_tracker = __esm({
31515
31528
  sigil;
31516
31529
  present = false;
31517
31530
  declared = /* @__PURE__ */ new Map();
31531
+ /** Every declaration, including earlier defaults for the same binding. */
31532
+ occurrences = [];
31518
31533
  /** Lines Spindle rejects. */
31519
31534
  problems = [];
31520
31535
  /** The names declared, with the range of sigil and name of the first. */
@@ -31522,6 +31537,7 @@ var init_variable_tracker = __esm({
31522
31537
  clear() {
31523
31538
  this.present = false;
31524
31539
  this.declared.clear();
31540
+ this.occurrences.length = 0;
31525
31541
  this.problems = [];
31526
31542
  this.names.clear();
31527
31543
  }
@@ -31548,6 +31564,7 @@ var init_variable_tracker = __esm({
31548
31564
  if (schema.fields && schema.fields.size > 0) declared.fields = [...schema.fields.keys()];
31549
31565
  }
31550
31566
  this.declared.set(name, declared);
31567
+ this.occurrences.push(declared);
31551
31568
  if (!this.names.has(name)) this.names.set(name, declarationRange);
31552
31569
  }
31553
31570
  }
@@ -31619,17 +31636,22 @@ var init_variable_tracker = __esm({
31619
31636
  getUsages(name) {
31620
31637
  return usagesOf(this.usagesByUri, name);
31621
31638
  }
31639
+ /** Every declaration location of a story or transient binding, in source order. */
31640
+ getDeclarations(name, sigil) {
31641
+ const declarations = sigil === "$" ? this.variables : this.transients;
31642
+ return declarations.occurrences.flatMap((d) => d.name === name && d.declarationUri !== void 0 && d.declarationRange ? [{ uri: d.declarationUri, range: d.declarationRange }] : []);
31643
+ }
31622
31644
  /** The executable usage or declaration containing the cursor, in source coordinates. */
31623
31645
  referenceAt(uri, position) {
31624
31646
  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);
31625
31647
  let atEnd = null;
31626
31648
  for (const [sigil, usages, declarations] of [
31627
- ["$", this.usagesByUri.get(uri), this.variables.declared],
31628
- ["%", this.transientUsagesByUri.get(uri), this.transients.declared]
31649
+ ["$", this.usagesByUri.get(uri), this.variables.occurrences],
31650
+ ["%", this.transientUsagesByUri.get(uri), this.transients.occurrences]
31629
31651
  ]) {
31630
31652
  const candidates = (usages ?? []).map((u) => ({ name: u.baseName, range: u.range }));
31631
- for (const [name, declaration] of declarations) {
31632
- if (declaration.declarationUri === uri && declaration.declarationRange) candidates.push({ name, range: declaration.declarationRange });
31653
+ for (const declaration of declarations) {
31654
+ if (declaration.declarationUri === uri && declaration.declarationRange) candidates.push({ name: declaration.name, range: declaration.declarationRange });
31633
31655
  }
31634
31656
  for (const candidate of candidates) {
31635
31657
  if (!contains(candidate.range)) continue;
@@ -31765,6 +31787,13 @@ var init_widget_registry = __esm({
31765
31787
  getAllWidgets() {
31766
31788
  return Array.from(this.widgets.values());
31767
31789
  }
31790
+ /** The effective widget whose declared name contains the cursor. */
31791
+ definitionAt(uri, position) {
31792
+ return this.getAllWidgets().find((widget) => {
31793
+ const { start, end } = widget.nameRange;
31794
+ return widget.uri === uri && position.line === start.line && position.character >= start.character && position.character <= end.character;
31795
+ });
31796
+ }
31768
31797
  /**
31769
31798
  * Record the macros opened in a document (its tags and those in the labels
31770
31799
  * and attribute values that hold markup), replacing any previous record for
@@ -31835,7 +31864,7 @@ var init_markup_index = __esm({
31835
31864
 
31836
31865
  // src/core/workspace/macro-sources.ts
31837
31866
  import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
31838
- import { dirname, isAbsolute, join as join2, relative } from "node:path";
31867
+ import { dirname, isAbsolute as isAbsolute2, join as join2, relative as relative2 } from "node:path";
31839
31868
  import { pathToFileURL as pathToFileURL2 } from "node:url";
31840
31869
  function isExcludedSegment(segment) {
31841
31870
  return EXCLUDED_DIRS.includes(segment) || segment.startsWith(".") && segment !== "." && segment !== "..";
@@ -31843,14 +31872,14 @@ function isExcludedSegment(segment) {
31843
31872
  function isMacroSource(uriOrPath) {
31844
31873
  return MACRO_SOURCE_PATTERN.test(uriOrPath);
31845
31874
  }
31846
- function isExcludedMacroSource(path, root) {
31847
- return (root ? relative(root, path) : path).split(/[\\/]/).some(isExcludedSegment);
31875
+ function isExcludedProjectFile(path, root) {
31876
+ return (root ? relative2(root, path) : path).split(/[\\/]/).some(isExcludedSegment);
31848
31877
  }
31849
31878
  function findMacroSourceFiles(root) {
31850
31879
  return findProjectFiles(root, MACRO_SOURCE_GLOB);
31851
31880
  }
31852
31881
  function findProjectFiles(root, pattern) {
31853
- const excluded = (p) => isExcludedMacroSource(p.fullpath(), root);
31882
+ const excluded = (p) => isExcludedProjectFile(p.fullpath(), root);
31854
31883
  return Ze(pattern, {
31855
31884
  cwd: root,
31856
31885
  absolute: true,
@@ -31861,8 +31890,8 @@ function findProjectFiles(root, pattern) {
31861
31890
  });
31862
31891
  }
31863
31892
  function isWithin(path, dir) {
31864
- const rel = relative(dir, path);
31865
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
31893
+ const rel = relative2(dir, path);
31894
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
31866
31895
  }
31867
31896
  function commonDirectory(files) {
31868
31897
  return files.map((f) => dirname(f)).reduce((a, b) => {
@@ -34007,6 +34036,10 @@ function structureDiagnostics(passage, workspace) {
34007
34036
  }
34008
34037
  };
34009
34038
  visit(passage.pairing.nodes, []);
34039
+ for (const piece of passage.pieces) {
34040
+ if (piece.kind !== "text") continue;
34041
+ visit(pairMarkup(piece.tokens, { isBlock: (name) => workspace.isContainer(name) }).nodes, []);
34042
+ }
34010
34043
  return diagnostics;
34011
34044
  }
34012
34045
  function parentDiagnostics(passage, token, ancestors, workspace) {
@@ -34539,7 +34572,7 @@ function formatSarif(results) {
34539
34572
  tool: {
34540
34573
  driver: {
34541
34574
  name: "spindle-lsp",
34542
- version: "0.1.0",
34575
+ version: VERSION,
34543
34576
  rules: Array.from(ruleIds).map((id) => ({
34544
34577
  id,
34545
34578
  shortDescription: { text: `Spindle diagnostic ${id}` }
@@ -34556,6 +34589,7 @@ var sarifLevelMap;
34556
34589
  var init_sarif = __esm({
34557
34590
  "src/cli/reporters/sarif.ts"() {
34558
34591
  "use strict";
34592
+ init_version();
34559
34593
  sarifLevelMap = {
34560
34594
  error: "error",
34561
34595
  warning: "warning",
@@ -34813,7 +34847,7 @@ async function loadPrettier(filePath) {
34813
34847
  if (!p) return void 0;
34814
34848
  try {
34815
34849
  const path = filePath?.startsWith("file:") ? fileURLToPath3(filePath) : filePath;
34816
- const resolved = await p.resolveConfig(path ?? resolve5(process.cwd(), "story.tw"));
34850
+ const resolved = await p.resolveConfig(path ?? resolve5(process.cwd(), "story.tw"), { useCache: false });
34817
34851
  return { prettier: p, options: { tabWidth: 2, ...resolved ?? {} } };
34818
34852
  } catch {
34819
34853
  return { prettier: p, options: { tabWidth: 2 } };
@@ -34867,8 +34901,7 @@ async function formatLf2(text3, options) {
34867
34901
  }
34868
34902
  }
34869
34903
  if (passage.header) {
34870
- const header = PASSAGE_HEADER_REGEX.test(passage.header) ? normalizePassageHeader(passage.header) : passage.header;
34871
- resultLines.push(header.replace(/\s+$/, ""));
34904
+ resultLines.push(normalizePassageHeader(passage.header));
34872
34905
  }
34873
34906
  if (passage.body === "") continue;
34874
34907
  const role = parsePassageHeader(passage.header, passage.startLine);
@@ -34889,26 +34922,14 @@ async function formatRange(text3, _range, options) {
34889
34922
  return formatDocument(text3, options);
34890
34923
  }
34891
34924
  function normalizePassageHeader(line) {
34892
- const headerMatch = line.match(/^::\s*(.*)/);
34893
- if (!headerMatch) return line;
34894
- const rest = headerMatch[1];
34895
- const unescaped = rest.replace(/\\./g, "ec");
34896
- const bracketIdx = unescaped.indexOf("[");
34897
- const braceIdx = unescaped.indexOf("{");
34898
- let name;
34899
- let suffix = "";
34900
- if (bracketIdx !== -1 && (braceIdx === -1 || bracketIdx < braceIdx)) {
34901
- name = rest.substring(0, bracketIdx).trim();
34902
- suffix = " " + rest.substring(bracketIdx).trim();
34903
- } else if (braceIdx !== -1) {
34904
- name = rest.substring(0, braceIdx).trim();
34905
- suffix = " " + rest.substring(braceIdx).trim();
34906
- } else {
34907
- name = rest.trim();
34908
- }
34909
- return `:: ${name}${suffix}`;
34910
- }
34911
- var PASSAGE_HEADER_REGEX, DEFAULT_DEDENTING2, BUILTIN_CONTAINERS, formatPlugin;
34925
+ const header = parsePassageHeader(line, 0);
34926
+ if (!header) return line;
34927
+ const { start, end } = header.nameRange;
34928
+ const name = line.slice(start.character, end.character);
34929
+ const suffix = line.slice(end.character).trim();
34930
+ return `:: ${name}${suffix ? ` ${suffix}` : ""}`;
34931
+ }
34932
+ var DEFAULT_DEDENTING2, BUILTIN_CONTAINERS, formatPlugin;
34912
34933
  var init_format = __esm({
34913
34934
  "src/plugins/format.ts"() {
34914
34935
  "use strict";
@@ -34916,7 +34937,6 @@ var init_format = __esm({
34916
34937
  init_passage_parser();
34917
34938
  init_segment();
34918
34939
  init_prettier_bridge();
34919
- PASSAGE_HEADER_REGEX = /^(::)\s*/;
34920
34940
  DEFAULT_DEDENTING2 = /* @__PURE__ */ new Set(["else", "elseif", "next", "case", "default"]);
34921
34941
  BUILTIN_CONTAINERS = new Set(builtinMacros.filter((m) => m.block).map((m) => m.name.toLowerCase()));
34922
34942
  formatPlugin = {
@@ -35040,8 +35060,7 @@ async function runFormat(args2) {
35040
35060
  const formatted = await formatDocument(text3, { ...formatOpts, filePath });
35041
35061
  if (formatted !== text3) {
35042
35062
  if (options.check) {
35043
- const relative2 = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
35044
- unformatted.push(relative2);
35063
+ unformatted.push(relativeTo(cwd, filePath));
35045
35064
  } else {
35046
35065
  writeFileSync(filePath, formatted, "utf-8");
35047
35066
  }
@@ -61326,49 +61345,49 @@ var require_fast_uri = __commonJS({
61326
61345
  schemelessOptions.skipEscape = true;
61327
61346
  return serialize(resolved, schemelessOptions);
61328
61347
  }
61329
- function resolveComponent(base, relative2, options, skipNormalization) {
61348
+ function resolveComponent(base, relative3, options, skipNormalization) {
61330
61349
  const target = {};
61331
61350
  if (!skipNormalization) {
61332
61351
  base = parse6(serialize(base, options), options);
61333
- relative2 = parse6(serialize(relative2, options), options);
61352
+ relative3 = parse6(serialize(relative3, options), options);
61334
61353
  }
61335
61354
  options = options || {};
61336
- if (!options.tolerant && relative2.scheme) {
61337
- target.scheme = relative2.scheme;
61338
- target.userinfo = relative2.userinfo;
61339
- target.host = relative2.host;
61340
- target.port = relative2.port;
61341
- target.path = removeDotSegments(relative2.path || "");
61342
- target.query = relative2.query;
61355
+ if (!options.tolerant && relative3.scheme) {
61356
+ target.scheme = relative3.scheme;
61357
+ target.userinfo = relative3.userinfo;
61358
+ target.host = relative3.host;
61359
+ target.port = relative3.port;
61360
+ target.path = removeDotSegments(relative3.path || "");
61361
+ target.query = relative3.query;
61343
61362
  } else {
61344
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
61345
- target.userinfo = relative2.userinfo;
61346
- target.host = relative2.host;
61347
- target.port = relative2.port;
61348
- target.path = removeDotSegments(relative2.path || "");
61349
- target.query = relative2.query;
61363
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
61364
+ target.userinfo = relative3.userinfo;
61365
+ target.host = relative3.host;
61366
+ target.port = relative3.port;
61367
+ target.path = removeDotSegments(relative3.path || "");
61368
+ target.query = relative3.query;
61350
61369
  } else {
61351
- if (!relative2.path) {
61370
+ if (!relative3.path) {
61352
61371
  target.path = base.path;
61353
- if (relative2.query !== void 0) {
61354
- target.query = relative2.query;
61372
+ if (relative3.query !== void 0) {
61373
+ target.query = relative3.query;
61355
61374
  } else {
61356
61375
  target.query = base.query;
61357
61376
  }
61358
61377
  } else {
61359
- if (relative2.path[0] === "/") {
61360
- target.path = removeDotSegments(relative2.path);
61378
+ if (relative3.path[0] === "/") {
61379
+ target.path = removeDotSegments(relative3.path);
61361
61380
  } else {
61362
61381
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
61363
- target.path = "/" + relative2.path;
61382
+ target.path = "/" + relative3.path;
61364
61383
  } else if (!base.path) {
61365
- target.path = relative2.path;
61384
+ target.path = relative3.path;
61366
61385
  } else {
61367
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
61386
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
61368
61387
  }
61369
61388
  target.path = removeDotSegments(target.path);
61370
61389
  }
61371
- target.query = relative2.query;
61390
+ target.query = relative3.query;
61372
61391
  }
61373
61392
  target.userinfo = base.userinfo;
61374
61393
  target.host = base.host;
@@ -61376,7 +61395,7 @@ var require_fast_uri = __commonJS({
61376
61395
  }
61377
61396
  target.scheme = base.scheme;
61378
61397
  }
61379
- target.fragment = relative2.fragment;
61398
+ target.fragment = relative3.fragment;
61380
61399
  return target;
61381
61400
  }
61382
61401
  function equal(uriA, uriB, options) {
@@ -66389,6 +66408,7 @@ var init_sessions = __esm({
66389
66408
  PlaySessions = class {
66390
66409
  entries = /* @__PURE__ */ new Map();
66391
66410
  load;
66411
+ trustedHostCode;
66392
66412
  max;
66393
66413
  idleMs;
66394
66414
  /** Sessions being opened; they count toward the cap before they are registered. */
@@ -66397,6 +66417,7 @@ var init_sessions = __esm({
66397
66417
  closing = false;
66398
66418
  constructor(options = {}) {
66399
66419
  this.load = options.load ?? loadPlay;
66420
+ this.trustedHostCode = options.trustedHostCode === true;
66400
66421
  this.max = options.max ?? 8;
66401
66422
  this.idleMs = options.idleMs ?? 18e5;
66402
66423
  }
@@ -66405,6 +66426,11 @@ var init_sessions = __esm({
66405
66426
  return this.entries.size;
66406
66427
  }
66407
66428
  async start(storyPath, passage) {
66429
+ if (!this.trustedHostCode) {
66430
+ throw new Error(
66431
+ "spindle_play requires host-code trust: stories can read/write host files, access environment variables and run commands with your account privileges. Only for trusted stories, set SPINDLE_LSP_TRUSTED_PLAY=1 in the MCP server environment and restart it."
66432
+ );
66433
+ }
66408
66434
  if (this.closing) throw shuttingDown();
66409
66435
  this.prune();
66410
66436
  if (this.entries.size + this.starting >= this.max) {
@@ -66605,7 +66631,7 @@ async function guarded(handler) {
66605
66631
  function registerPlayTools(server, sessions) {
66606
66632
  server.tool(
66607
66633
  "spindle_play_start",
66608
- "Start playing a Spindle story headlessly. Returns a session id and what the reader sees.",
66634
+ "Execute a trusted Spindle story headlessly with host file, environment and command access. Requires SPINDLE_LSP_TRUSTED_PLAY=1 in the server environment. Returns a session id and what the reader sees.",
66609
66635
  {
66610
66636
  story: external_exports3.string().describe("Path to compiled .html, a .twee/.tw file, a glob or a project directory"),
66611
66637
  passage: external_exports3.string().optional().describe("Passage to go to after boot")
@@ -66742,7 +66768,7 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
66742
66768
  if (severity) {
66743
66769
  diags = filterBySeverity2(diags, severity);
66744
66770
  }
66745
- const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
66771
+ const relativePath = relativeTo(cwd, filePath);
66746
66772
  for (const d of diags) {
66747
66773
  results.push({
66748
66774
  file: relativePath,
@@ -66759,9 +66785,6 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
66759
66785
  workspace.dispose();
66760
66786
  }
66761
66787
  }
66762
- function relativeTo(cwd, filePath) {
66763
- return filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
66764
- }
66765
66788
  async function formatFiles(pattern, cwd = process.cwd()) {
66766
66789
  const files = await resolveFiles(pattern, cwd);
66767
66790
  const contents = readFiles(files);
@@ -66857,7 +66880,7 @@ async function startMcpServer(version2) {
66857
66880
  };
66858
66881
  }
66859
66882
  );
66860
- const playSessions = new PlaySessions();
66883
+ const playSessions = new PlaySessions({ trustedHostCode: process.env.SPINDLE_LSP_TRUSTED_PLAY === "1" });
66861
66884
  registerPlayTools(server, playSessions);
66862
66885
  const transport = new StdioServerTransport();
66863
66886
  closePlaySessionsOnShutdown(playSessions, transport);
@@ -77681,6 +77704,30 @@ function getSignatureHelp(uri, position, workspace) {
77681
77704
  return { signatures, activeSignature, activeParameter };
77682
77705
  }
77683
77706
  }
77707
+ if (macroInfo) {
77708
+ const definitions = macroInfo.parameters === void 0 ? macroInfo.parameterDefs : void 0;
77709
+ if (!definitions?.length) return null;
77710
+ const parameters = definitions.map((parameter) => ({
77711
+ label: parameter.required ? parameter.name : `[${parameter.name}]`,
77712
+ documentation: [parameter.description, `Type: ${parameter.type}`].filter(Boolean).join("\n\n")
77713
+ }));
77714
+ const positional = definitions.every((p) => p.type === "string" || p.type === "number") && argument < parameters.length;
77715
+ const documentation = [
77716
+ macroInfo.description,
77717
+ ...!positional ? parameters.map((p) => `${p.label}
77718
+
77719
+ ${p.documentation}`) : []
77720
+ ].filter(Boolean).join("\n\n") || void 0;
77721
+ return {
77722
+ signatures: [{
77723
+ label: `{${[macroName, ...parameters.map((p) => p.label)].join(" ")}}`,
77724
+ documentation,
77725
+ parameters: positional ? parameters : []
77726
+ }],
77727
+ activeSignature: 0,
77728
+ activeParameter: positional ? argument : 0
77729
+ };
77730
+ }
77684
77731
  const widget = workspace.widgets.getWidget(macroName);
77685
77732
  if (widget && widget.params.length > 0) {
77686
77733
  const paramLabels = widget.params;
@@ -77885,6 +77932,8 @@ function findReferences(uri, position, workspace, includeDeclaration) {
77885
77932
  if (variable) {
77886
77933
  return variable.sigil === "%" ? findTransientReferences(variable.name, workspace, includeDeclaration) : findVariableReferences(variable.name, workspace, includeDeclaration);
77887
77934
  }
77935
+ const declaration = workspace.widgets.definitionAt(uri, position);
77936
+ if (declaration) return findWidgetReferences(declaration.name, workspace, includeDeclaration);
77888
77937
  const doc = workspace.markup.get(uri);
77889
77938
  const head = doc && macroHeadAt(doc, position);
77890
77939
  if (head) {
@@ -77929,13 +77978,7 @@ function findPassageRefs(passageName, workspace) {
77929
77978
  function findVariableReferences(varName, workspace, includeDeclaration) {
77930
77979
  const locations = [];
77931
77980
  if (includeDeclaration) {
77932
- const decl = workspace.variables.getDeclared().get(varName);
77933
- if (decl?.declarationRange && decl.declarationUri) {
77934
- locations.push({
77935
- uri: decl.declarationUri,
77936
- range: decl.declarationRange
77937
- });
77938
- }
77981
+ locations.push(...workspace.variables.getDeclarations(varName, "$"));
77939
77982
  }
77940
77983
  const usages = workspace.variables.getUsages(varName);
77941
77984
  for (const u of usages) {
@@ -77946,10 +77989,7 @@ function findVariableReferences(varName, workspace, includeDeclaration) {
77946
77989
  function findTransientReferences(varName, workspace, includeDeclaration) {
77947
77990
  const results = [];
77948
77991
  if (includeDeclaration) {
77949
- const decl = workspace.variables.getDeclaredTransient().get(varName);
77950
- if (decl?.declarationUri && decl.declarationRange) {
77951
- results.push({ uri: decl.declarationUri, range: decl.declarationRange });
77952
- }
77992
+ results.push(...workspace.variables.getDeclarations(varName, "%"));
77953
77993
  }
77954
77994
  const usages = workspace.variables.getTransientUsages(varName);
77955
77995
  for (const u of usages) {
@@ -77963,6 +78003,7 @@ function findWidgetReferences(widgetName2, workspace, includeDeclaration) {
77963
78003
  if (includeDeclaration && widget) {
77964
78004
  locations.push({ uri: widget.uri, range: widget.range });
77965
78005
  }
78006
+ if (workspace.macros.getMacro(widgetName2)) return locations;
77966
78007
  const lowerName2 = widgetName2.toLowerCase();
77967
78008
  const isBlock = widget?.block ?? false;
77968
78009
  for (const docUri of workspace.documents.getUris()) {
@@ -78241,12 +78282,8 @@ function resolveSymbolAtCursor(uri, position, workspace) {
78241
78282
  if (variable) {
78242
78283
  return { kind: "variable", name: variable.name, sigil: variable.sigil, range: variable.range };
78243
78284
  }
78244
- for (const widget of workspace.widgets.getAllWidgets()) {
78245
- const { start, end } = widget.nameRange;
78246
- if (widget.uri === uri && position.line === start.line && position.character >= start.character && position.character <= end.character) {
78247
- return { kind: "widget", name: widget.name, range: widget.nameRange };
78248
- }
78249
- }
78285
+ const declaration = workspace.widgets.definitionAt(uri, position);
78286
+ if (declaration) return { kind: "widget", name: declaration.name, range: declaration.nameRange };
78250
78287
  const doc = workspace.markup.get(uri);
78251
78288
  const head = doc && macroHeadAt(doc, position);
78252
78289
  if (head) {
@@ -78457,6 +78494,7 @@ function addWidgetParamHints(passages, inRange, workspace, hints) {
78457
78494
  for (const token of macroTokens(passage)) {
78458
78495
  if (token.isClose || token.rawArgs === "") continue;
78459
78496
  if (!inRange(passage.position(token.start).line)) continue;
78497
+ if (workspace.macros.getMacro(token.name)) continue;
78460
78498
  const widget = workspace.widgets.getWidget(token.name);
78461
78499
  if (!widget || widget.params.length === 0) continue;
78462
78500
  const args2 = splitArgs(token.rawArgs);
@@ -79505,8 +79543,12 @@ function startServer(args2) {
79505
79543
  if (workspace) {
79506
79544
  try {
79507
79545
  const fsPath = uriToFsPath(textDocument.uri);
79508
- const text3 = readFileSync8(fsPath, "utf-8");
79509
- workspace.documents.update(textDocument.uri, text3);
79546
+ if (isExcludedProjectFile(fsPath, workspaceRoot)) {
79547
+ workspace.documents.close(textDocument.uri);
79548
+ } else {
79549
+ const text3 = readFileSync8(fsPath, "utf-8");
79550
+ workspace.documents.update(textDocument.uri, text3);
79551
+ }
79510
79552
  } catch {
79511
79553
  workspace.documents.close(textDocument.uri);
79512
79554
  }
@@ -79532,7 +79574,7 @@ function startServer(args2) {
79532
79574
  configChanged = true;
79533
79575
  continue;
79534
79576
  }
79535
- if (isMacroSource(change.uri) && isExcludedMacroSource(uriToFsPath(change.uri), workspaceRoot)) {
79577
+ if (isExcludedProjectFile(uriToFsPath(change.uri), workspaceRoot)) {
79536
79578
  continue;
79537
79579
  }
79538
79580
  if (documents.has(change.uri)) continue;
@@ -79568,11 +79610,7 @@ async function scanWorkspaceFiles(root) {
79568
79610
  if (!root) return contents;
79569
79611
  try {
79570
79612
  const files = [
79571
- ...await Ze("**/*.{tw,twee}", {
79572
- cwd: root,
79573
- absolute: true,
79574
- nodir: true
79575
- }),
79613
+ ...await findProjectFiles(root, "**/*.{tw,twee}"),
79576
79614
  ...await findMacroSourceFiles(root)
79577
79615
  ];
79578
79616
  for (const filePath of files) {
@@ -79593,7 +79631,6 @@ var init_server4 = __esm({
79593
79631
  "use strict";
79594
79632
  import_node = __toESM(require_main3(), 1);
79595
79633
  init_main2();
79596
- init_index_min();
79597
79634
  init_workspace_model();
79598
79635
  init_plugin_loader();
79599
79636
  init_capabilities();
@@ -79606,8 +79643,8 @@ var init_server4 = __esm({
79606
79643
  });
79607
79644
 
79608
79645
  // src/bin.ts
79646
+ init_version();
79609
79647
  var args = process.argv.slice(2);
79610
- var VERSION = "0.15.0";
79611
79648
  var HELP = `spindle-lsp v${VERSION}
79612
79649
  Language Server Protocol server for Spindle story format
79613
79650
 
@@ -79621,6 +79658,9 @@ Options:
79621
79658
  --help, -h Show this help message
79622
79659
  --version, -v Show version number
79623
79660
 
79661
+ MCP environment:
79662
+ SPINDLE_LSP_TRUSTED_PLAY=1 Allow trusted stories to access host files, environment and commands
79663
+
79624
79664
  Check options:
79625
79665
  --format <pretty|json|sarif> Output format (default: pretty)
79626
79666
  --severity <level> Minimum severity: error, warning, info, hint