@rohal12/spindle-lsp 0.15.1 → 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
@@ -55,7 +55,7 @@ var VERSION;
55
55
  var init_version = __esm({
56
56
  "src/version.ts"() {
57
57
  "use strict";
58
- VERSION = false ? pkg.version : "0.15.1";
58
+ VERSION = false ? pkg.version : "0.15.2";
59
59
  }
60
60
  });
61
61
 
@@ -3050,7 +3050,7 @@ globstar while`, t, d, e, f, m), this.matchOne(t.slice(d), e.slice(f), s)) retur
3050
3050
 
3051
3051
  // src/core/workspace/file-inputs.ts
3052
3052
  import { readFileSync, statSync } from "node:fs";
3053
- import { resolve } from "node:path";
3053
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3054
3054
  async function resolveFiles(pattern, cwd) {
3055
3055
  let input = pattern;
3056
3056
  let directory = cwd;
@@ -3075,6 +3075,10 @@ async function resolveInputs(patterns, cwd) {
3075
3075
  const files = await Promise.all(patterns.map((pattern) => resolveFiles(pattern, cwd)));
3076
3076
  return [...new Set(files.flat())];
3077
3077
  }
3078
+ function relativeTo(cwd, filePath) {
3079
+ const rel = relative(cwd, filePath);
3080
+ return rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel) ? filePath : rel;
3081
+ }
3078
3082
  var init_file_inputs = __esm({
3079
3083
  "src/core/workspace/file-inputs.ts"() {
3080
3084
  "use strict";
@@ -27586,10 +27590,10 @@ var require_resolve_block_map = __commonJS({
27586
27590
  let offset2 = bm.offset;
27587
27591
  let commentEnd = null;
27588
27592
  for (const collItem of bm.items) {
27589
- const { start, key, sep, value } = collItem;
27593
+ const { start, key, sep: sep2, value } = collItem;
27590
27594
  const keyProps = resolveProps.resolveProps(start, {
27591
27595
  indicator: "explicit-key-ind",
27592
- next: key ?? sep?.[0],
27596
+ next: key ?? sep2?.[0],
27593
27597
  offset: offset2,
27594
27598
  onError,
27595
27599
  parentIndent: bm.indent,
@@ -27603,7 +27607,7 @@ var require_resolve_block_map = __commonJS({
27603
27607
  else if ("indent" in key && key.indent !== bm.indent)
27604
27608
  onError(offset2, "BAD_INDENT", startColMsg);
27605
27609
  }
27606
- if (!keyProps.anchor && !keyProps.tag && !sep) {
27610
+ if (!keyProps.anchor && !keyProps.tag && !sep2) {
27607
27611
  commentEnd = keyProps.end;
27608
27612
  if (keyProps.comment) {
27609
27613
  if (map2.comment)
@@ -27627,7 +27631,7 @@ var require_resolve_block_map = __commonJS({
27627
27631
  ctx.atKey = false;
27628
27632
  if (utilMapIncludes.mapIncludes(ctx, map2.items, keyNode))
27629
27633
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
27630
- const valueProps = resolveProps.resolveProps(sep ?? [], {
27634
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
27631
27635
  indicator: "map-value-ind",
27632
27636
  next: value,
27633
27637
  offset: keyNode.range[2],
@@ -27643,7 +27647,7 @@ var require_resolve_block_map = __commonJS({
27643
27647
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
27644
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");
27645
27649
  }
27646
- 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);
27647
27651
  if (ctx.schema.compat)
27648
27652
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
27649
27653
  offset2 = valueNode.range[2];
@@ -27734,7 +27738,7 @@ var require_resolve_end = __commonJS({
27734
27738
  let comment = "";
27735
27739
  if (end) {
27736
27740
  let hasSpace = false;
27737
- let sep = "";
27741
+ let sep2 = "";
27738
27742
  for (const token of end) {
27739
27743
  const { source, type } = token;
27740
27744
  switch (type) {
@@ -27748,13 +27752,13 @@ var require_resolve_end = __commonJS({
27748
27752
  if (!comment)
27749
27753
  comment = cb;
27750
27754
  else
27751
- comment += sep + cb;
27752
- sep = "";
27755
+ comment += sep2 + cb;
27756
+ sep2 = "";
27753
27757
  break;
27754
27758
  }
27755
27759
  case "newline":
27756
27760
  if (comment)
27757
- sep += source;
27761
+ sep2 += source;
27758
27762
  hasSpace = true;
27759
27763
  break;
27760
27764
  default:
@@ -27797,18 +27801,18 @@ var require_resolve_flow_collection = __commonJS({
27797
27801
  let offset2 = fc.offset + fc.start.source.length;
27798
27802
  for (let i2 = 0; i2 < fc.items.length; ++i2) {
27799
27803
  const collItem = fc.items[i2];
27800
- const { start, key, sep, value } = collItem;
27804
+ const { start, key, sep: sep2, value } = collItem;
27801
27805
  const props = resolveProps.resolveProps(start, {
27802
27806
  flow: fcName,
27803
27807
  indicator: "explicit-key-ind",
27804
- next: key ?? sep?.[0],
27808
+ next: key ?? sep2?.[0],
27805
27809
  offset: offset2,
27806
27810
  onError,
27807
27811
  parentIndent: fc.indent,
27808
27812
  startOnNewline: false
27809
27813
  });
27810
27814
  if (!props.found) {
27811
- if (!props.anchor && !props.tag && !sep && !value) {
27815
+ if (!props.anchor && !props.tag && !sep2 && !value) {
27812
27816
  if (i2 === 0 && props.comma)
27813
27817
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
27814
27818
  else if (i2 < fc.items.length - 1)
@@ -27862,8 +27866,8 @@ var require_resolve_flow_collection = __commonJS({
27862
27866
  }
27863
27867
  }
27864
27868
  }
27865
- if (!isMap2 && !sep && !props.found) {
27866
- 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);
27867
27871
  coll.items.push(valueNode);
27868
27872
  offset2 = valueNode.range[2];
27869
27873
  if (isBlock(value))
@@ -27875,7 +27879,7 @@ var require_resolve_flow_collection = __commonJS({
27875
27879
  if (isBlock(key))
27876
27880
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
27877
27881
  ctx.atKey = false;
27878
- const valueProps = resolveProps.resolveProps(sep ?? [], {
27882
+ const valueProps = resolveProps.resolveProps(sep2 ?? [], {
27879
27883
  flow: fcName,
27880
27884
  indicator: "map-value-ind",
27881
27885
  next: value,
@@ -27886,8 +27890,8 @@ var require_resolve_flow_collection = __commonJS({
27886
27890
  });
27887
27891
  if (valueProps.found) {
27888
27892
  if (!isMap2 && !props.found && ctx.options.strict) {
27889
- if (sep)
27890
- for (const st2 of sep) {
27893
+ if (sep2)
27894
+ for (const st2 of sep2) {
27891
27895
  if (st2 === valueProps.found)
27892
27896
  break;
27893
27897
  if (st2.type === "newline") {
@@ -27904,7 +27908,7 @@ var require_resolve_flow_collection = __commonJS({
27904
27908
  else
27905
27909
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
27906
27910
  }
27907
- 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;
27908
27912
  if (valueNode) {
27909
27913
  if (isBlock(value))
27910
27914
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -28084,7 +28088,7 @@ var require_resolve_block_scalar = __commonJS({
28084
28088
  chompStart = i2 + 1;
28085
28089
  }
28086
28090
  let value = "";
28087
- let sep = "";
28091
+ let sep2 = "";
28088
28092
  let prevMoreIndented = false;
28089
28093
  for (let i2 = 0; i2 < contentStart; ++i2)
28090
28094
  value += lines[i2][0].slice(trimIndent) + "\n";
@@ -28101,24 +28105,24 @@ var require_resolve_block_scalar = __commonJS({
28101
28105
  indent = "";
28102
28106
  }
28103
28107
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
28104
- value += sep + indent.slice(trimIndent) + content2;
28105
- sep = "\n";
28108
+ value += sep2 + indent.slice(trimIndent) + content2;
28109
+ sep2 = "\n";
28106
28110
  } else if (indent.length > trimIndent || content2[0] === " ") {
28107
- if (sep === " ")
28108
- sep = "\n";
28109
- else if (!prevMoreIndented && sep === "\n")
28110
- sep = "\n\n";
28111
- value += sep + indent.slice(trimIndent) + content2;
28112
- 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";
28113
28117
  prevMoreIndented = true;
28114
28118
  } else if (content2 === "") {
28115
- if (sep === "\n")
28119
+ if (sep2 === "\n")
28116
28120
  value += "\n";
28117
28121
  else
28118
- sep = "\n";
28122
+ sep2 = "\n";
28119
28123
  } else {
28120
- value += sep + content2;
28121
- sep = " ";
28124
+ value += sep2 + content2;
28125
+ sep2 = " ";
28122
28126
  prevMoreIndented = false;
28123
28127
  }
28124
28128
  }
@@ -28301,25 +28305,25 @@ var require_resolve_flow_scalar = __commonJS({
28301
28305
  trimBoth = /^[ \t]+|[ \t]+$/g;
28302
28306
  }
28303
28307
  let res = match[1].replace(trimEnd, "");
28304
- let sep = " ";
28308
+ let sep2 = " ";
28305
28309
  let pos = line.lastIndex;
28306
28310
  while (match = line.exec(source)) {
28307
28311
  const lm = match[1].replace(trimBoth, "");
28308
28312
  if (lm === "") {
28309
- if (sep === "\n")
28310
- res += sep;
28313
+ if (sep2 === "\n")
28314
+ res += sep2;
28311
28315
  else
28312
- sep = "\n";
28316
+ sep2 = "\n";
28313
28317
  } else {
28314
- res += sep + lm;
28315
- sep = " ";
28318
+ res += sep2 + lm;
28319
+ sep2 = " ";
28316
28320
  }
28317
28321
  pos = line.lastIndex;
28318
28322
  }
28319
28323
  const last2 = /[ \t]*(.*)/sy;
28320
28324
  last2.lastIndex = pos;
28321
28325
  match = last2.exec(source);
28322
- return res + sep + (match?.[1] ?? "");
28326
+ return res + sep2 + (match?.[1] ?? "");
28323
28327
  }
28324
28328
  function doubleQuotedValue(source, onError) {
28325
28329
  let res = "";
@@ -29129,14 +29133,14 @@ var require_cst_stringify = __commonJS({
29129
29133
  }
29130
29134
  }
29131
29135
  }
29132
- function stringifyItem({ start, key, sep, value }) {
29136
+ function stringifyItem({ start, key, sep: sep2, value }) {
29133
29137
  let res = "";
29134
29138
  for (const st2 of start)
29135
29139
  res += st2.source;
29136
29140
  if (key)
29137
29141
  res += stringifyToken(key);
29138
- if (sep)
29139
- for (const st2 of sep)
29142
+ if (sep2)
29143
+ for (const st2 of sep2)
29140
29144
  res += st2.source;
29141
29145
  if (value)
29142
29146
  res += stringifyToken(value);
@@ -30303,18 +30307,18 @@ var require_parser = __commonJS({
30303
30307
  if (this.type === "map-value-ind") {
30304
30308
  const prev = getPrevProps(this.peek(2));
30305
30309
  const start = getFirstKeyStartProps(prev);
30306
- let sep;
30310
+ let sep2;
30307
30311
  if (scalar.end) {
30308
- sep = scalar.end;
30309
- sep.push(this.sourceToken);
30312
+ sep2 = scalar.end;
30313
+ sep2.push(this.sourceToken);
30310
30314
  delete scalar.end;
30311
30315
  } else
30312
- sep = [this.sourceToken];
30316
+ sep2 = [this.sourceToken];
30313
30317
  const map2 = {
30314
30318
  type: "block-map",
30315
30319
  offset: scalar.offset,
30316
30320
  indent: scalar.indent,
30317
- items: [{ start, key: scalar, sep }]
30321
+ items: [{ start, key: scalar, sep: sep2 }]
30318
30322
  };
30319
30323
  this.onKeyLine = true;
30320
30324
  this.stack[this.stack.length - 1] = map2;
@@ -30467,15 +30471,15 @@ var require_parser = __commonJS({
30467
30471
  } else if (isFlowToken(it2.key) && !includesToken(it2.sep, "newline")) {
30468
30472
  const start2 = getFirstKeyStartProps(it2.start);
30469
30473
  const key = it2.key;
30470
- const sep = it2.sep;
30471
- sep.push(this.sourceToken);
30474
+ const sep2 = it2.sep;
30475
+ sep2.push(this.sourceToken);
30472
30476
  delete it2.key;
30473
30477
  delete it2.sep;
30474
30478
  this.stack.push({
30475
30479
  type: "block-map",
30476
30480
  offset: this.offset,
30477
30481
  indent: this.indent,
30478
- items: [{ start: start2, key, sep }]
30482
+ items: [{ start: start2, key, sep: sep2 }]
30479
30483
  });
30480
30484
  } else if (start.length > 0) {
30481
30485
  it2.sep = it2.sep.concat(start, this.sourceToken);
@@ -30669,13 +30673,13 @@ var require_parser = __commonJS({
30669
30673
  const prev = getPrevProps(parent);
30670
30674
  const start = getFirstKeyStartProps(prev);
30671
30675
  fixFlowSeqItems(fc);
30672
- const sep = fc.end.splice(1, fc.end.length);
30673
- sep.push(this.sourceToken);
30676
+ const sep2 = fc.end.splice(1, fc.end.length);
30677
+ sep2.push(this.sourceToken);
30674
30678
  const map2 = {
30675
30679
  type: "block-map",
30676
30680
  offset: fc.offset,
30677
30681
  indent: fc.indent,
30678
- items: [{ start, key: fc, sep }]
30682
+ items: [{ start, key: fc, sep: sep2 }]
30679
30683
  };
30680
30684
  this.onKeyLine = true;
30681
30685
  this.stack[this.stack.length - 1] = map2;
@@ -31524,6 +31528,8 @@ var init_variable_tracker = __esm({
31524
31528
  sigil;
31525
31529
  present = false;
31526
31530
  declared = /* @__PURE__ */ new Map();
31531
+ /** Every declaration, including earlier defaults for the same binding. */
31532
+ occurrences = [];
31527
31533
  /** Lines Spindle rejects. */
31528
31534
  problems = [];
31529
31535
  /** The names declared, with the range of sigil and name of the first. */
@@ -31531,6 +31537,7 @@ var init_variable_tracker = __esm({
31531
31537
  clear() {
31532
31538
  this.present = false;
31533
31539
  this.declared.clear();
31540
+ this.occurrences.length = 0;
31534
31541
  this.problems = [];
31535
31542
  this.names.clear();
31536
31543
  }
@@ -31557,6 +31564,7 @@ var init_variable_tracker = __esm({
31557
31564
  if (schema.fields && schema.fields.size > 0) declared.fields = [...schema.fields.keys()];
31558
31565
  }
31559
31566
  this.declared.set(name, declared);
31567
+ this.occurrences.push(declared);
31560
31568
  if (!this.names.has(name)) this.names.set(name, declarationRange);
31561
31569
  }
31562
31570
  }
@@ -31628,17 +31636,22 @@ var init_variable_tracker = __esm({
31628
31636
  getUsages(name) {
31629
31637
  return usagesOf(this.usagesByUri, name);
31630
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
+ }
31631
31644
  /** The executable usage or declaration containing the cursor, in source coordinates. */
31632
31645
  referenceAt(uri, position) {
31633
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);
31634
31647
  let atEnd = null;
31635
31648
  for (const [sigil, usages, declarations] of [
31636
- ["$", this.usagesByUri.get(uri), this.variables.declared],
31637
- ["%", this.transientUsagesByUri.get(uri), this.transients.declared]
31649
+ ["$", this.usagesByUri.get(uri), this.variables.occurrences],
31650
+ ["%", this.transientUsagesByUri.get(uri), this.transients.occurrences]
31638
31651
  ]) {
31639
31652
  const candidates = (usages ?? []).map((u) => ({ name: u.baseName, range: u.range }));
31640
- for (const [name, declaration] of declarations) {
31641
- 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 });
31642
31655
  }
31643
31656
  for (const candidate of candidates) {
31644
31657
  if (!contains(candidate.range)) continue;
@@ -31851,7 +31864,7 @@ var init_markup_index = __esm({
31851
31864
 
31852
31865
  // src/core/workspace/macro-sources.ts
31853
31866
  import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
31854
- 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";
31855
31868
  import { pathToFileURL as pathToFileURL2 } from "node:url";
31856
31869
  function isExcludedSegment(segment) {
31857
31870
  return EXCLUDED_DIRS.includes(segment) || segment.startsWith(".") && segment !== "." && segment !== "..";
@@ -31859,14 +31872,14 @@ function isExcludedSegment(segment) {
31859
31872
  function isMacroSource(uriOrPath) {
31860
31873
  return MACRO_SOURCE_PATTERN.test(uriOrPath);
31861
31874
  }
31862
- function isExcludedMacroSource(path, root) {
31863
- return (root ? relative(root, path) : path).split(/[\\/]/).some(isExcludedSegment);
31875
+ function isExcludedProjectFile(path, root) {
31876
+ return (root ? relative2(root, path) : path).split(/[\\/]/).some(isExcludedSegment);
31864
31877
  }
31865
31878
  function findMacroSourceFiles(root) {
31866
31879
  return findProjectFiles(root, MACRO_SOURCE_GLOB);
31867
31880
  }
31868
31881
  function findProjectFiles(root, pattern) {
31869
- const excluded = (p) => isExcludedMacroSource(p.fullpath(), root);
31882
+ const excluded = (p) => isExcludedProjectFile(p.fullpath(), root);
31870
31883
  return Ze(pattern, {
31871
31884
  cwd: root,
31872
31885
  absolute: true,
@@ -31877,8 +31890,8 @@ function findProjectFiles(root, pattern) {
31877
31890
  });
31878
31891
  }
31879
31892
  function isWithin(path, dir) {
31880
- const rel = relative(dir, path);
31881
- return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
31893
+ const rel = relative2(dir, path);
31894
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
31882
31895
  }
31883
31896
  function commonDirectory(files) {
31884
31897
  return files.map((f) => dirname(f)).reduce((a, b) => {
@@ -35047,8 +35060,7 @@ async function runFormat(args2) {
35047
35060
  const formatted = await formatDocument(text3, { ...formatOpts, filePath });
35048
35061
  if (formatted !== text3) {
35049
35062
  if (options.check) {
35050
- const relative2 = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
35051
- unformatted.push(relative2);
35063
+ unformatted.push(relativeTo(cwd, filePath));
35052
35064
  } else {
35053
35065
  writeFileSync(filePath, formatted, "utf-8");
35054
35066
  }
@@ -61333,49 +61345,49 @@ var require_fast_uri = __commonJS({
61333
61345
  schemelessOptions.skipEscape = true;
61334
61346
  return serialize(resolved, schemelessOptions);
61335
61347
  }
61336
- function resolveComponent(base, relative2, options, skipNormalization) {
61348
+ function resolveComponent(base, relative3, options, skipNormalization) {
61337
61349
  const target = {};
61338
61350
  if (!skipNormalization) {
61339
61351
  base = parse6(serialize(base, options), options);
61340
- relative2 = parse6(serialize(relative2, options), options);
61352
+ relative3 = parse6(serialize(relative3, options), options);
61341
61353
  }
61342
61354
  options = options || {};
61343
- if (!options.tolerant && relative2.scheme) {
61344
- target.scheme = relative2.scheme;
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;
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;
61350
61362
  } else {
61351
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
61352
- target.userinfo = relative2.userinfo;
61353
- target.host = relative2.host;
61354
- target.port = relative2.port;
61355
- target.path = removeDotSegments(relative2.path || "");
61356
- 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;
61357
61369
  } else {
61358
- if (!relative2.path) {
61370
+ if (!relative3.path) {
61359
61371
  target.path = base.path;
61360
- if (relative2.query !== void 0) {
61361
- target.query = relative2.query;
61372
+ if (relative3.query !== void 0) {
61373
+ target.query = relative3.query;
61362
61374
  } else {
61363
61375
  target.query = base.query;
61364
61376
  }
61365
61377
  } else {
61366
- if (relative2.path[0] === "/") {
61367
- target.path = removeDotSegments(relative2.path);
61378
+ if (relative3.path[0] === "/") {
61379
+ target.path = removeDotSegments(relative3.path);
61368
61380
  } else {
61369
61381
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
61370
- target.path = "/" + relative2.path;
61382
+ target.path = "/" + relative3.path;
61371
61383
  } else if (!base.path) {
61372
- target.path = relative2.path;
61384
+ target.path = relative3.path;
61373
61385
  } else {
61374
- 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;
61375
61387
  }
61376
61388
  target.path = removeDotSegments(target.path);
61377
61389
  }
61378
- target.query = relative2.query;
61390
+ target.query = relative3.query;
61379
61391
  }
61380
61392
  target.userinfo = base.userinfo;
61381
61393
  target.host = base.host;
@@ -61383,7 +61395,7 @@ var require_fast_uri = __commonJS({
61383
61395
  }
61384
61396
  target.scheme = base.scheme;
61385
61397
  }
61386
- target.fragment = relative2.fragment;
61398
+ target.fragment = relative3.fragment;
61387
61399
  return target;
61388
61400
  }
61389
61401
  function equal(uriA, uriB, options) {
@@ -66396,6 +66408,7 @@ var init_sessions = __esm({
66396
66408
  PlaySessions = class {
66397
66409
  entries = /* @__PURE__ */ new Map();
66398
66410
  load;
66411
+ trustedHostCode;
66399
66412
  max;
66400
66413
  idleMs;
66401
66414
  /** Sessions being opened; they count toward the cap before they are registered. */
@@ -66404,6 +66417,7 @@ var init_sessions = __esm({
66404
66417
  closing = false;
66405
66418
  constructor(options = {}) {
66406
66419
  this.load = options.load ?? loadPlay;
66420
+ this.trustedHostCode = options.trustedHostCode === true;
66407
66421
  this.max = options.max ?? 8;
66408
66422
  this.idleMs = options.idleMs ?? 18e5;
66409
66423
  }
@@ -66412,6 +66426,11 @@ var init_sessions = __esm({
66412
66426
  return this.entries.size;
66413
66427
  }
66414
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
+ }
66415
66434
  if (this.closing) throw shuttingDown();
66416
66435
  this.prune();
66417
66436
  if (this.entries.size + this.starting >= this.max) {
@@ -66612,7 +66631,7 @@ async function guarded(handler) {
66612
66631
  function registerPlayTools(server, sessions) {
66613
66632
  server.tool(
66614
66633
  "spindle_play_start",
66615
- "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.",
66616
66635
  {
66617
66636
  story: external_exports3.string().describe("Path to compiled .html, a .twee/.tw file, a glob or a project directory"),
66618
66637
  passage: external_exports3.string().optional().describe("Passage to go to after boot")
@@ -66749,7 +66768,7 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
66749
66768
  if (severity) {
66750
66769
  diags = filterBySeverity2(diags, severity);
66751
66770
  }
66752
- const relativePath = filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
66771
+ const relativePath = relativeTo(cwd, filePath);
66753
66772
  for (const d of diags) {
66754
66773
  results.push({
66755
66774
  file: relativePath,
@@ -66766,9 +66785,6 @@ async function checkProject(pattern, severity, cwd = process.cwd()) {
66766
66785
  workspace.dispose();
66767
66786
  }
66768
66787
  }
66769
- function relativeTo(cwd, filePath) {
66770
- return filePath.startsWith(cwd) ? filePath.slice(cwd.length + 1) : filePath;
66771
- }
66772
66788
  async function formatFiles(pattern, cwd = process.cwd()) {
66773
66789
  const files = await resolveFiles(pattern, cwd);
66774
66790
  const contents = readFiles(files);
@@ -66864,7 +66880,7 @@ async function startMcpServer(version2) {
66864
66880
  };
66865
66881
  }
66866
66882
  );
66867
- const playSessions = new PlaySessions();
66883
+ const playSessions = new PlaySessions({ trustedHostCode: process.env.SPINDLE_LSP_TRUSTED_PLAY === "1" });
66868
66884
  registerPlayTools(server, playSessions);
66869
66885
  const transport = new StdioServerTransport();
66870
66886
  closePlaySessionsOnShutdown(playSessions, transport);
@@ -77962,13 +77978,7 @@ function findPassageRefs(passageName, workspace) {
77962
77978
  function findVariableReferences(varName, workspace, includeDeclaration) {
77963
77979
  const locations = [];
77964
77980
  if (includeDeclaration) {
77965
- const decl = workspace.variables.getDeclared().get(varName);
77966
- if (decl?.declarationRange && decl.declarationUri) {
77967
- locations.push({
77968
- uri: decl.declarationUri,
77969
- range: decl.declarationRange
77970
- });
77971
- }
77981
+ locations.push(...workspace.variables.getDeclarations(varName, "$"));
77972
77982
  }
77973
77983
  const usages = workspace.variables.getUsages(varName);
77974
77984
  for (const u of usages) {
@@ -77979,10 +77989,7 @@ function findVariableReferences(varName, workspace, includeDeclaration) {
77979
77989
  function findTransientReferences(varName, workspace, includeDeclaration) {
77980
77990
  const results = [];
77981
77991
  if (includeDeclaration) {
77982
- const decl = workspace.variables.getDeclaredTransient().get(varName);
77983
- if (decl?.declarationUri && decl.declarationRange) {
77984
- results.push({ uri: decl.declarationUri, range: decl.declarationRange });
77985
- }
77992
+ results.push(...workspace.variables.getDeclarations(varName, "%"));
77986
77993
  }
77987
77994
  const usages = workspace.variables.getTransientUsages(varName);
77988
77995
  for (const u of usages) {
@@ -78487,6 +78494,7 @@ function addWidgetParamHints(passages, inRange, workspace, hints) {
78487
78494
  for (const token of macroTokens(passage)) {
78488
78495
  if (token.isClose || token.rawArgs === "") continue;
78489
78496
  if (!inRange(passage.position(token.start).line)) continue;
78497
+ if (workspace.macros.getMacro(token.name)) continue;
78490
78498
  const widget = workspace.widgets.getWidget(token.name);
78491
78499
  if (!widget || widget.params.length === 0) continue;
78492
78500
  const args2 = splitArgs(token.rawArgs);
@@ -79535,8 +79543,12 @@ function startServer(args2) {
79535
79543
  if (workspace) {
79536
79544
  try {
79537
79545
  const fsPath = uriToFsPath(textDocument.uri);
79538
- const text3 = readFileSync8(fsPath, "utf-8");
79539
- 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
+ }
79540
79552
  } catch {
79541
79553
  workspace.documents.close(textDocument.uri);
79542
79554
  }
@@ -79562,7 +79574,7 @@ function startServer(args2) {
79562
79574
  configChanged = true;
79563
79575
  continue;
79564
79576
  }
79565
- if (isMacroSource(change.uri) && isExcludedMacroSource(uriToFsPath(change.uri), workspaceRoot)) {
79577
+ if (isExcludedProjectFile(uriToFsPath(change.uri), workspaceRoot)) {
79566
79578
  continue;
79567
79579
  }
79568
79580
  if (documents.has(change.uri)) continue;
@@ -79598,11 +79610,7 @@ async function scanWorkspaceFiles(root) {
79598
79610
  if (!root) return contents;
79599
79611
  try {
79600
79612
  const files = [
79601
- ...await Ze("**/*.{tw,twee}", {
79602
- cwd: root,
79603
- absolute: true,
79604
- nodir: true
79605
- }),
79613
+ ...await findProjectFiles(root, "**/*.{tw,twee}"),
79606
79614
  ...await findMacroSourceFiles(root)
79607
79615
  ];
79608
79616
  for (const filePath of files) {
@@ -79623,7 +79631,6 @@ var init_server4 = __esm({
79623
79631
  "use strict";
79624
79632
  import_node = __toESM(require_main3(), 1);
79625
79633
  init_main2();
79626
- init_index_min();
79627
79634
  init_workspace_model();
79628
79635
  init_plugin_loader();
79629
79636
  init_capabilities();
@@ -79651,6 +79658,9 @@ Options:
79651
79658
  --help, -h Show this help message
79652
79659
  --version, -v Show version number
79653
79660
 
79661
+ MCP environment:
79662
+ SPINDLE_LSP_TRUSTED_PLAY=1 Allow trusted stories to access host files, environment and commands
79663
+
79654
79664
  Check options:
79655
79665
  --format <pretty|json|sarif> Output format (default: pretty)
79656
79666
  --severity <level> Minimum severity: error, warning, info, hint