@rohal12/spindle-lsp 0.15.3 → 0.15.5

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.3";
58
+ VERSION = false ? pkg.version : "0.15.5";
59
59
  }
60
60
  });
61
61
 
@@ -23448,6 +23448,7 @@ var init_passage_parser = __esm({
23448
23448
  "StoryTitle",
23449
23449
  "StoryBanner",
23450
23450
  "StoryCaption",
23451
+ "StoryContext",
23451
23452
  "StoryMenu",
23452
23453
  "StoryInterface",
23453
23454
  "StoryAuthor"
@@ -31694,7 +31695,7 @@ var init_variable_tracker = __esm({
31694
31695
  const member = u.fullName.split(".")[1];
31695
31696
  if (member === void 0) continue;
31696
31697
  if (declared.get(u.baseName)?.type !== "array") continue;
31697
- if (ARRAY_MEMBERS.has(member)) continue;
31698
+ if (ARRAY_MEMBERS.has(member) || /^(0|[1-9]\d*)$/.test(member) && Number(member) < 4294967295) continue;
31698
31699
  results.push({ sigil, name: u.baseName, member, range: u.range });
31699
31700
  }
31700
31701
  }
@@ -32580,11 +32581,27 @@ var init_workspace_model = __esm({
32580
32581
  WorkspaceModel = class extends EventEmitter2 {
32581
32582
  documents;
32582
32583
  passages;
32583
- macros;
32584
- variables;
32585
- widgets;
32584
+ _macros;
32585
+ get macros() {
32586
+ this.ensureCurrent();
32587
+ return this._macros;
32588
+ }
32589
+ _variables;
32590
+ get variables() {
32591
+ this.ensureCurrent();
32592
+ return this._variables;
32593
+ }
32594
+ _widgets;
32595
+ get widgets() {
32596
+ this.ensureCurrent();
32597
+ return this._widgets;
32598
+ }
32586
32599
  /** Every document's markup, read through Spindle's tooling API. */
32587
- markup;
32600
+ _markup;
32601
+ get markup() {
32602
+ this.ensureCurrent();
32603
+ return this._markup;
32604
+ }
32588
32605
  /** True after initialize() has completed (full workspace scan done). */
32589
32606
  initialized = false;
32590
32607
  /** The story format declared by StoryData, updated with the passages. */
@@ -32594,12 +32611,17 @@ var init_workspace_model = __esm({
32594
32611
  * workspace root, else StoryData's `format-version`. Only a version below
32595
32612
  * {@link MINIMUM_SPINDLE_VERSION} matters (it is reported, not emulated).
32596
32613
  */
32597
- target;
32614
+ _target;
32615
+ get target() {
32616
+ this.ensureCurrent();
32617
+ return this._target;
32618
+ }
32598
32619
  workspaceRoot;
32599
32620
  /** The version installed under the workspace root, read at startup and on refresh(). */
32600
32621
  installedVersion;
32601
32622
  /** The `format-version` StoryData declares. */
32602
32623
  declaredVersion;
32624
+ analysisPending = false;
32603
32625
  debounceTimer = null;
32604
32626
  DEBOUNCE_MS = 200;
32605
32627
  /** Bound listeners for cleanup in dispose(). */
@@ -32610,11 +32632,11 @@ var init_workspace_model = __esm({
32610
32632
  super();
32611
32633
  this.documents = new DocumentStore();
32612
32634
  this.passages = new PassageIndex();
32613
- this.macros = new MacroRegistry();
32635
+ this._macros = new MacroRegistry();
32614
32636
  this.macros.onChange = () => this.markup?.invalidate();
32615
- this.variables = new VariableTracker();
32616
- this.widgets = new WidgetRegistry();
32617
- this.markup = new MarkupIndex({
32637
+ this._variables = new VariableTracker();
32638
+ this._widgets = new WidgetRegistry();
32639
+ this._markup = new MarkupIndex({
32618
32640
  text: (uri) => this.documents.getText(uri),
32619
32641
  passages: (uri) => this.passages.getPassagesInDocument(uri),
32620
32642
  context: () => ({
@@ -32624,7 +32646,7 @@ var init_workspace_model = __esm({
32624
32646
  });
32625
32647
  this.workspaceRoot = config2?.workspaceRoot;
32626
32648
  this.installedVersion = this.workspaceRoot ? readInstalledSpindleVersion(this.workspaceRoot) : void 0;
32627
- this.target = resolveSpindleTarget(this.installedVersion);
32649
+ this._target = resolveSpindleTarget(this.installedVersion);
32628
32650
  this.macros.loadBuiltins();
32629
32651
  this.macros.loadSupplements(macro_supplements_default);
32630
32652
  this.onDocumentChanged = (uri) => this.handleDocumentChange(uri);
@@ -32656,7 +32678,8 @@ var init_workspace_model = __esm({
32656
32678
  */
32657
32679
  refresh() {
32658
32680
  if (this.workspaceRoot) this.installedVersion = readInstalledSpindleVersion(this.workspaceRoot);
32659
- this.cascade();
32681
+ this.analysisPending = true;
32682
+ this.ensureCurrent();
32660
32683
  this.scheduleModelReady();
32661
32684
  }
32662
32685
  /**
@@ -32680,6 +32703,7 @@ var init_workspace_model = __esm({
32680
32703
  * undefined if no StoryData passage names one.
32681
32704
  */
32682
32705
  get storyFormat() {
32706
+ this.ensureCurrent();
32683
32707
  return this.format.name;
32684
32708
  }
32685
32709
  /**
@@ -32689,6 +32713,7 @@ var init_workspace_model = __esm({
32689
32713
  * Spindle.
32690
32714
  */
32691
32715
  isSpindleProject() {
32716
+ this.ensureCurrent();
32692
32717
  return this.format.isSpindle;
32693
32718
  }
32694
32719
  /** Clean up listeners and timers. */
@@ -32702,25 +32727,23 @@ var init_workspace_model = __esm({
32702
32727
  }
32703
32728
  this.removeAllListeners();
32704
32729
  }
32705
- /** Handle a document change: rebuild passages for that document, then cascade. */
32730
+ /** Index local headers immediately; defer expensive cross-document analysis. */
32706
32731
  handleDocumentChange(uri) {
32707
32732
  const text3 = this.documents.getText(uri);
32708
32733
  if (text3 !== void 0 && !isMacroSource(uri)) {
32709
32734
  this.passages.rebuild(uri, text3);
32710
32735
  }
32711
- this.refreshDiscoveredMacros();
32712
- this.cascade();
32736
+ this.analysisPending = true;
32713
32737
  this.updateStoryFormat();
32714
32738
  this.emit("documentChanged", uri);
32715
32739
  this.emit("passagesUpdated", uri);
32716
32740
  this.scheduleModelReady();
32717
32741
  }
32718
- /** Handle a document close: remove its passages and variable usages, then cascade. */
32742
+ /** Remove local indices immediately and schedule cross-document analysis. */
32719
32743
  handleDocumentClose(uri) {
32720
32744
  this.passages.remove(uri);
32721
- this.variables.removeDocument(uri);
32722
- this.refreshDiscoveredMacros();
32723
- this.cascade();
32745
+ this._variables.removeDocument(uri);
32746
+ this.analysisPending = true;
32724
32747
  this.updateStoryFormat();
32725
32748
  this.emit("documentClosed", uri);
32726
32749
  this.emit("passagesUpdated", uri);
@@ -32734,14 +32757,21 @@ var init_workspace_model = __esm({
32734
32757
  this.passages.rebuild(uri, text3);
32735
32758
  }
32736
32759
  }
32760
+ this.analysisPending = true;
32761
+ this.ensureCurrent();
32762
+ }
32763
+ /** Finish pending cross-document work once, before a feature reads the indices. */
32764
+ ensureCurrent() {
32765
+ if (!this.analysisPending) return;
32766
+ this.analysisPending = false;
32737
32767
  this.refreshDiscoveredMacros();
32738
32768
  this.cascade();
32739
32769
  this.updateStoryFormat();
32740
32770
  }
32741
32771
  /**
32742
32772
  * Re-read the story format from the StoryData passages after the passage
32743
- * index changed, emitting 'storyFormatChanged' if it differs. Called after
32744
- * the cascade, so listeners see up-to-date indices.
32773
+ * index changed, emitting 'storyFormatChanged' if it differs. Listeners
32774
+ * that read the analysis indices finish pending analysis through getters.
32745
32775
  */
32746
32776
  updateStoryFormat() {
32747
32777
  const format = resolveStoryFormat(storyDataFormats(
@@ -32763,7 +32793,8 @@ var init_workspace_model = __esm({
32763
32793
  */
32764
32794
  refreshDiscoveredMacros() {
32765
32795
  const found = [];
32766
- for (const uri of this.documents.getUris()) {
32796
+ const init = [];
32797
+ for (const uri of this.documents.getUris().sort()) {
32767
32798
  const text3 = this.documents.getText(uri);
32768
32799
  if (!text3) continue;
32769
32800
  if (isMacroSource(uri)) {
@@ -32777,10 +32808,11 @@ var init_workspace_model = __esm({
32777
32808
  if (!isStoryInit && !isScript) continue;
32778
32809
  lines ??= text3.split("\n");
32779
32810
  const content2 = lines.slice(passage.headerEnd.end.line + 1, passage.range.end.line + 1).join("\n");
32780
- found.push(...isScript ? discoverMacros(content2) : storyInitMacros(content2));
32811
+ if (isScript) found.push(...discoverMacros(content2));
32812
+ else init.push(...storyInitMacros(content2));
32781
32813
  }
32782
32814
  }
32783
- this.macros.setDiscoveredMacros(found);
32815
+ this.macros.setDiscoveredMacros([...found, ...init]);
32784
32816
  }
32785
32817
  /**
32786
32818
  * Cascade: rescan variables and widgets based on current passages.
@@ -32794,7 +32826,7 @@ var init_workspace_model = __esm({
32794
32826
  cascade() {
32795
32827
  this.markup.invalidate();
32796
32828
  this.declaredVersion = storyDataFormatVersion(this.passages.getAllPassages(), (uri) => this.documents.getText(uri));
32797
- this.target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
32829
+ this._target = resolveSpindleTarget(this.installedVersion, this.declaredVersion);
32798
32830
  const declarations = (name) => {
32799
32831
  const passage = this.passages.getPassage(name);
32800
32832
  return passage ? this.markup.get(passage.uri)?.passages.find((p) => p.passage === passage) : void 0;
@@ -32827,13 +32859,14 @@ var init_workspace_model = __esm({
32827
32859
  const macros = this.macros.toolingMacros();
32828
32860
  return [...macros, ...this.widgets.getAllWidgets().filter((widget) => !this.macros.getMacro(widget.name)).map((widget) => ({ name: widget.name, block: widget.block, subMacros: [], widget: widget.tooling }))];
32829
32861
  }
32830
- /** Debounce modelReady emission. */
32862
+ /** Coalesce analysis and diagnostics across a burst of document changes. */
32831
32863
  scheduleModelReady() {
32832
32864
  if (this.debounceTimer) {
32833
32865
  clearTimeout(this.debounceTimer);
32834
32866
  }
32835
32867
  this.debounceTimer = setTimeout(() => {
32836
32868
  this.debounceTimer = null;
32869
+ this.ensureCurrent();
32837
32870
  this.emit("modelReady");
32838
32871
  }, this.DEBOUNCE_MS);
32839
32872
  }
@@ -33043,17 +33076,10 @@ function lexFormat(formatString) {
33043
33076
  throw new Error(`Lex: Invalid character after '|': '${next}'`);
33044
33077
  }
33045
33078
  } else if (quoteRe.test(ch)) {
33046
- const openQuote = ch;
33047
- const start = pos;
33048
- pos++;
33049
- while (pos < formatString.length && formatString[pos] !== openQuote) {
33050
- if (++iter > LEX_LIMIT) throw new Error("Format lex string iteration limit exceeded");
33051
- pos++;
33052
- }
33053
- if (pos >= formatString.length) throw new Error("Lex: Unterminated string literal");
33054
- const value = formatString.slice(start + 1, pos);
33055
- pos++;
33056
- lexed.push({ kind: 1 /* Literal */, value });
33079
+ const quoted = readQuoted(formatString, pos);
33080
+ if (!quoted) throw new Error("Lex: Unterminated string literal");
33081
+ pos = quoted.end;
33082
+ lexed.push({ kind: 1 /* Literal */, value: quoted.value });
33057
33083
  } else if (ch === "(" || ch === ")") {
33058
33084
  pos++;
33059
33085
  lexed.push({ kind: 2 /* Group */, open: ch === "(" });
@@ -33201,114 +33227,111 @@ function isFail(s) {
33201
33227
  return s === 1 /* Failure */ || s === 0 /* NotFound */;
33202
33228
  }
33203
33229
  function crawlValidate(format, args2, startIndex, state) {
33204
- let iter = 0;
33230
+ const memo2 = /* @__PURE__ */ new Map();
33231
+ function append(left, right) {
33232
+ return { ...right, rank: left.rank + right.rank, warnings: left.warnings.concat(right.warnings) };
33233
+ }
33234
+ function follow(left, right) {
33235
+ return left.flatMap((matched) => isFail(matched.status) ? [matched] : crawl(right, matched.argIndex).map((result2) => append(matched, result2)));
33236
+ }
33205
33237
  function crawl(fmt, argIdx) {
33206
- if (++iter > VALIDATE_LIMIT) {
33238
+ let positions = memo2.get(fmt);
33239
+ if (!positions) memo2.set(fmt, positions = /* @__PURE__ */ new Map());
33240
+ const cached2 = positions.get(argIdx);
33241
+ if (cached2) return cached2;
33242
+ if (positions.size >= VALIDATE_LIMIT) {
33207
33243
  throw new Error(`Validation iteration limit exceeded at argument ${argIdx}`);
33208
33244
  }
33245
+ const unique = /* @__PURE__ */ new Map();
33246
+ for (const result2 of match2(fmt, argIdx)) {
33247
+ const key = isFail(result2.status) ? "failure" : result2.argIndex;
33248
+ const previous2 = unique.get(key);
33249
+ if (!previous2 || key === "failure" && result2.rank > previous2.rank) unique.set(key, result2);
33250
+ }
33251
+ const results2 = [...unique.values()];
33252
+ positions.set(argIdx, results2);
33253
+ return results2;
33254
+ }
33255
+ function match2(fmt, argIdx) {
33209
33256
  switch (fmt.kind) {
33210
33257
  case 4 /* MaybeNext */: {
33211
- let rank = 0;
33212
- let warnings = [];
33213
- let idx = argIdx;
33214
- if (fmt.left) {
33215
- const left = crawl(fmt.left, idx);
33216
- if (isFail(left.status)) return left;
33217
- rank += left.rank;
33218
- idx = left.argIndex;
33219
- warnings = left.warnings;
33220
- }
33221
- const right = crawl(fmt.right, idx);
33222
- if (right.status === 0 /* NotFound */) {
33223
- return makeSuccess(idx, rank, warnings);
33224
- }
33225
- if (isFail(right.status)) {
33226
- right.status = 1 /* Failure */;
33227
- return right;
33228
- }
33229
- return makeSuccess(right.argIndex, rank + right.rank, warnings.concat(right.warnings));
33258
+ const left = fmt.left ? crawl(fmt.left, argIdx) : [makeSuccess(argIdx, 0)];
33259
+ return [...follow(left, fmt.right), ...left];
33230
33260
  }
33231
33261
  case 3 /* AndNext */: {
33232
- const left = crawl(fmt.left, argIdx);
33233
- if (isFail(left.status)) return left;
33234
- const right = crawl(fmt.right, left.argIndex);
33235
- if (isFail(right.status)) return right;
33236
- return makeSuccess(
33237
- right.argIndex,
33238
- left.rank + right.rank,
33239
- left.warnings.concat(right.warnings)
33240
- );
33262
+ return follow(crawl(fmt.left, argIdx), fmt.right);
33241
33263
  }
33242
33264
  case 5 /* Or */: {
33243
- const left = crawl(fmt.left, argIdx);
33244
- if (!isFail(left.status)) return left;
33245
- const right = crawl(fmt.right, argIdx);
33246
- if (!isFail(right.status)) return right;
33247
- return left;
33265
+ return [...crawl(fmt.left, argIdx), ...crawl(fmt.right, argIdx)];
33248
33266
  }
33249
33267
  case 6 /* Repeat */: {
33250
- let rank = 0;
33251
- let warnings = [];
33252
- let idx = argIdx;
33253
- for (; ; ) {
33254
- const r = crawl(fmt.right, idx);
33255
- if (isFail(r.status)) break;
33256
- idx = r.argIndex;
33257
- rank += r.rank;
33258
- warnings = warnings.concat(r.warnings);
33259
- }
33260
- return makeSuccess(idx, rank, warnings);
33268
+ const matches = [];
33269
+ const pending = [makeSuccess(argIdx, 0)];
33270
+ const reached = /* @__PURE__ */ new Set();
33271
+ while (pending.length > 0) {
33272
+ const matched = pending.pop();
33273
+ if (reached.has(matched.argIndex)) continue;
33274
+ reached.add(matched.argIndex);
33275
+ matches.push(matched);
33276
+ const items = crawl(fmt.right, matched.argIndex);
33277
+ for (let i2 = items.length - 1; i2 >= 0; i2--) {
33278
+ const item = items[i2];
33279
+ if (!isFail(item.status) && !reached.has(item.argIndex)) pending.push(append(matched, item));
33280
+ }
33281
+ }
33282
+ return matches.sort((left, right) => right.argIndex - left.argIndex);
33261
33283
  }
33262
33284
  case 1 /* Literal */: {
33263
33285
  const arg = args2[argIdx];
33264
33286
  if (arg === void 0) {
33265
- return makeError(0 /* NotFound */, `Expected literal '${fmt.value}' but no argument found`, argIdx);
33287
+ return [makeError(0 /* NotFound */, `Expected literal '${fmt.value}' but no argument found`, argIdx)];
33266
33288
  }
33267
33289
  if (isAlwaysArg(arg)) {
33268
- return makeSuccess(argIdx + 1, CORRECT_RANK);
33290
+ return [makeSuccess(argIdx + 1, CORRECT_RANK)];
33269
33291
  }
33270
- const argText = arg.type === 0 /* String */ ? arg.text.slice(1, -1) : arg.text;
33292
+ const argText = arg.type === 0 /* String */ ? readQuoted(arg.text, 0)?.value : arg.text;
33271
33293
  if (argText === fmt.value) {
33272
- return makeSuccess(argIdx + 1, CORRECT_RANK);
33294
+ return [makeSuccess(argIdx + 1, CORRECT_RANK)];
33273
33295
  }
33274
33296
  if (fmt.value === "null" && arg.type === 3 /* Null */ || fmt.value === "undefined" && arg.type === 10 /* Undefined */ || fmt.value === "true" && arg.type === 2 /* Boolean */ && arg.text === "true" || fmt.value === "false" && arg.type === 2 /* Boolean */ && arg.text === "false" || fmt.value === "NaN" && arg.type === 9 /* NaN */) {
33275
- return makeSuccess(argIdx + 1, CORRECT_RANK);
33297
+ return [makeSuccess(argIdx + 1, CORRECT_RANK)];
33276
33298
  }
33277
- return makeError(1 /* Failure */, `Expected literal '${fmt.value}'`, argIdx, CORRECT_TYPE_RANK);
33299
+ return [makeError(1 /* Failure */, `Expected literal '${fmt.value}'`, argIdx, CORRECT_TYPE_RANK)];
33278
33300
  }
33279
33301
  case 0 /* Type */: {
33280
33302
  const arg = args2[argIdx];
33281
33303
  const typeDef = fmt.type;
33282
33304
  if (arg === void 0) {
33283
- return makeError(
33305
+ return [makeError(
33284
33306
  0 /* NotFound */,
33285
33307
  `Expected type '${typeDef.name[0]}' but no argument found`,
33286
33308
  argIdx
33287
- );
33309
+ )];
33288
33310
  }
33289
33311
  if (isAlwaysArg(arg)) {
33290
- return makeSuccess(argIdx + 1, CORRECT_RANK);
33312
+ return [makeSuccess(argIdx + 1, CORRECT_RANK)];
33291
33313
  }
33292
33314
  const result2 = typeDef.validate(arg, args2, argIdx, state);
33293
33315
  if (result2 instanceof TypeError2) {
33294
- return {
33316
+ return [{
33295
33317
  status: 1 /* Failure */,
33296
33318
  rank: 0,
33297
33319
  argIndex: argIdx,
33298
33320
  errors: [{ message: result2.message, argIndex: argIdx }],
33299
33321
  warnings: []
33300
- };
33322
+ }];
33301
33323
  }
33302
33324
  if (result2 instanceof TypeWarning) {
33303
- return makeSuccess(argIdx + 1, CORRECT_RANK, [
33325
+ return [makeSuccess(argIdx + 1, CORRECT_RANK, [
33304
33326
  { message: result2.message, argIndex: argIdx }
33305
- ]);
33327
+ ])];
33306
33328
  }
33307
- return makeSuccess(argIdx + 1, CORRECT_RANK);
33329
+ return [makeSuccess(argIdx + 1, CORRECT_RANK)];
33308
33330
  }
33309
33331
  }
33310
33332
  }
33311
- return crawl(format, startIndex);
33333
+ const results = crawl(format, startIndex);
33334
+ return results.find((result2) => !isFail(result2.status) && result2.argIndex === args2.length) ?? results.reduce((best, result2) => result2.rank > best.rank ? result2 : best);
33312
33335
  }
33313
33336
  var TypeError2, TypeWarning, parameterTypes, LEX_LIMIT, wsRe, identStartRe, identStopRe, quoteRe, digitRe, PARSE_LIMIT, DESCRIBE_ALTERNATIVE_LIMIT, VALIDATE_LIMIT, CORRECT_TYPE_RANK, CORRECT_RANK, Parameters;
33314
33337
  var init_parameter_validator = __esm({
@@ -33820,7 +33843,7 @@ function computeDiagnostics(uri, workspace, options) {
33820
33843
  }
33821
33844
  function storyReport(workspace) {
33822
33845
  const documents = [];
33823
- for (const uri of workspace.documents.getUris()) {
33846
+ for (const uri of workspace.passages.getDocumentUris()) {
33824
33847
  if (isMacroSource(uri)) continue;
33825
33848
  const doc = workspace.markup.get(uri);
33826
33849
  if (doc) documents.push(doc);
@@ -34426,8 +34449,6 @@ var init_diagnostics = __esm({
34426
34449
  }
34427
34450
  };
34428
34451
  ctx.workspace.on("modelReady", publishAll);
34429
- ctx.workspace.on("storyFormatChanged", publishAll);
34430
- ctx.workspace.on("documentChanged", publishFor);
34431
34452
  ctx.workspace.on("documentClosed", (uri) => {
34432
34453
  ctx.connection.sendDiagnostics({ uri, diagnostics: [] });
34433
34454
  });
@@ -80071,6 +80092,7 @@ function computeRename(uri, position, newName, workspace) {
80071
80092
  addEdit(declaration.uri, declaration.nameRange, escapePassageName(newName));
80072
80093
  }
80073
80094
  for (const { uri: refUri, ref: ref2, text: text3 } of refEdits) addEdit(refUri, ref2.range, text3);
80095
+ preserveEntryPassage(symbol2.name, newName, workspace, addEdit);
80074
80096
  break;
80075
80097
  }
80076
80098
  case "variable": {
@@ -80143,6 +80165,54 @@ function computeRename(uri, position, newName, workspace) {
80143
80165
  }
80144
80166
  return edits;
80145
80167
  }
80168
+ function preserveEntryPassage(oldName, newName, workspace, addEdit) {
80169
+ const storyData = workspace.passages.getPassages("StoryData");
80170
+ for (const declaration of storyData) {
80171
+ const passage = workspace.markup.get(declaration.uri).passages.find((p) => p.passage === declaration);
80172
+ let data2;
80173
+ try {
80174
+ data2 = JSON.parse(passage.content);
80175
+ if (!data2 || typeof data2 !== "object" || Array.isArray(data2)) throw new Error();
80176
+ } catch {
80177
+ throw new RenameError("Cannot preserve the entry passage: StoryData must contain a valid JSON object.").at(declaration.uri, declaration.nameRange);
80178
+ }
80179
+ const start = typeof data2.start === "string" && data2.start ? data2.start : "Start";
80180
+ if (start !== oldName) continue;
80181
+ if (Object.hasOwn(data2, "start") && typeof data2.start !== "string") {
80182
+ throw new RenameError("Cannot preserve the entry passage: StoryData.start must be a string.").at(declaration.uri, declaration.nameRange);
80183
+ }
80184
+ let depth = 0;
80185
+ let startValue = false;
80186
+ let replaced = false;
80187
+ lexJs(passage.content, {
80188
+ code(ch) {
80189
+ if (ch === "{" || ch === "[") depth++;
80190
+ if (ch === "}" || ch === "]") depth--;
80191
+ if (ch === ",") startValue = false;
80192
+ },
80193
+ literal(text3, index) {
80194
+ if (depth !== 1 || !text3.startsWith('"')) return;
80195
+ const isKey = /^\s*:/.test(passage.content.slice(index + text3.length));
80196
+ if (isKey) startValue = JSON.parse(text3) === "start";
80197
+ else if (startValue) {
80198
+ addEdit(declaration.uri, passage.range(index, index + text3.length), JSON.stringify(newName));
80199
+ replaced = true;
80200
+ }
80201
+ }
80202
+ });
80203
+ if (!replaced) {
80204
+ const index = passage.content.indexOf("{") + 1;
80205
+ addEdit(declaration.uri, passage.range(index, index), `"start":${JSON.stringify(newName)}${Object.keys(data2).length ? "," : ""}`);
80206
+ }
80207
+ }
80208
+ if (storyData.length === 0 && oldName === "Start") {
80209
+ const declaration = workspace.passages.getPassage(oldName);
80210
+ const doc = workspace.markup.get(declaration.uri);
80211
+ const end = doc.passages.at(-1).position(doc.passages.at(-1).content.length);
80212
+ const eol = doc.text.includes("\r\n") ? "\r\n" : "\n";
80213
+ addEdit(declaration.uri, { start: end, end }, `${eol}${eol}:: StoryData${eol}${JSON.stringify({ start: newName })}${eol}`);
80214
+ }
80215
+ }
80146
80216
  function passageHeaderProblem(name) {
80147
80217
  if (name.trim() === "") return "a passage name cannot be empty";
80148
80218
  if (/[\r\n]/.test(name)) return "a passage header is a single line, so the name cannot contain a line break";
@@ -81366,7 +81436,7 @@ function startServer(args2) {
81366
81436
  connection.onInitialize((params) => {
81367
81437
  console.error("[spindle-lsp] onInitialize called");
81368
81438
  const initOptions = params.initializationOptions ?? {};
81369
- workspaceRoot = initOptions.workspaceRoot ?? (params.workspaceFolders?.[0]?.uri ? uriToFsPath(params.workspaceFolders[0].uri) : void 0) ?? (params.rootUri ? uriToFsPath(params.rootUri) : void 0);
81439
+ workspaceRoot = (initOptions.workspaceRoot !== void 0 ? uriToFsPath(initOptions.workspaceRoot) : void 0) ?? (params.workspaceFolders?.[0]?.uri ? uriToFsPath(params.workspaceFolders[0].uri) : void 0) ?? (params.rootUri ? uriToFsPath(params.rootUri) : void 0);
81370
81440
  let projectConfig = { macros: {} };
81371
81441
  try {
81372
81442
  if (workspaceRoot) projectConfig = loadConfigFromDisk(workspaceRoot);