gdharness 1.1.34 → 1.1.35

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/build/cli.js CHANGED
@@ -17758,7 +17758,7 @@ var init_tool_definitions = __esm(() => {
17758
17758
  },
17759
17759
  {
17760
17760
  name: "script_edit",
17761
- description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a find_children string, a node named like the class, a call on a value whose type the analyser could not tell. A comment or a line of a text file with nothing more to say is given under leftAloneInProse by file and line instead. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17761
+ description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment or a line of a text file with nothing more to say is given under leftAloneInProse by file and line instead. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17762
17762
  parameters: {
17763
17763
  projectPath: PROJECT_PATH,
17764
17764
  scriptPath: SCRIPT_PATH,
@@ -38854,9 +38854,11 @@ class GodotLSPClient {
38854
38854
  diagnosticsWaiters = new Map;
38855
38855
  documentVersions = new Map;
38856
38856
  documentTurns = new Map;
38857
- constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1") {
38857
+ timeouts;
38858
+ constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1", timeouts = { requestMs: REQUEST_TIMEOUT_MS2, referencesMs: REFERENCES_TIMEOUT_MS }) {
38858
38859
  this.port = port;
38859
38860
  this.host = host;
38861
+ this.timeouts = timeouts;
38860
38862
  this.pendingRequests = new Map;
38861
38863
  }
38862
38864
  async connect() {
@@ -38953,7 +38955,7 @@ class GodotLSPClient {
38953
38955
  const rootPath = this.rootPath ?? dirname13(resolve5(filePath));
38954
38956
  await this.initialize(rootPath);
38955
38957
  }
38956
- async sendRequest(method, params) {
38958
+ async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
38957
38959
  await this.ensureConnected();
38958
38960
  if (!this.socket) {
38959
38961
  throw new Refusal("Not connected to Godot LSP");
@@ -38970,8 +38972,8 @@ class GodotLSPClient {
38970
38972
  return new Promise((resolveRequest, rejectRequest) => {
38971
38973
  const timer = setTimeout(() => {
38972
38974
  this.pendingRequests.delete(id);
38973
- rejectRequest(new Error(`LSP request timed out after 10s: ${method}`));
38974
- }, 1e4);
38975
+ rejectRequest(new LSPTimeout(method, timeoutMs));
38976
+ }, timeoutMs);
38975
38977
  this.pendingRequests.set(id, {
38976
38978
  resolve: resolveRequest,
38977
38979
  reject: rejectRequest,
@@ -39276,7 +39278,7 @@ class GodotLSPClient {
39276
39278
  textDocument: { uri },
39277
39279
  position: { line, character },
39278
39280
  context: { includeDeclaration: true }
39279
- });
39281
+ }, this.timeouts.referencesMs);
39280
39282
  } finally {
39281
39283
  this.closeDocument(uri);
39282
39284
  }
@@ -39434,12 +39436,21 @@ async function handleLSPTool(client, toolName, args) {
39434
39436
  });
39435
39437
  }
39436
39438
  }
39437
- var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, ArgumentRefusal, AnotherProjectsServer;
39439
+ var DEFAULT_LSP_PORT = 6005, DIAGNOSTICS_TIMEOUT_MS = 5000, REQUEST_TIMEOUT_MS2 = 1e4, REFERENCES_TIMEOUT_MS = 180000, LSPTimeout, ArgumentRefusal, AnotherProjectsServer;
39438
39440
  var init_lsp_client = __esm(() => {
39439
39441
  init_errors();
39440
39442
  init_framing();
39441
39443
  init_paths();
39442
39444
  init_ports();
39445
+ LSPTimeout = class LSPTimeout extends Error {
39446
+ method;
39447
+ seconds;
39448
+ constructor(method, timeoutMs) {
39449
+ super(`LSP request timed out after ${String(timeoutMs / 1000)}s: ${method}`);
39450
+ this.method = method;
39451
+ this.seconds = timeoutMs / 1000;
39452
+ }
39453
+ };
39443
39454
  ArgumentRefusal = class ArgumentRefusal extends Refusal {
39444
39455
  };
39445
39456
  AnotherProjectsServer = class AnotherProjectsServer extends Refusal {
@@ -40061,6 +40072,10 @@ class PlanBuilder {
40061
40072
  return { edits, mentions };
40062
40073
  }
40063
40074
  }
40075
+ function isWholeString(text, offset, name) {
40076
+ const before = text[offset - 1];
40077
+ return (before === '"' || before === "'") && text[offset + name.length] === before;
40078
+ }
40064
40079
  function declaresItsOwn(script, name, except) {
40065
40080
  return script.occurrences(name).some((occurrence) => occurrence.kind === "code" && occurrence.offset !== except && occurrence.declaredAs !== null && occurrence.declaredAs !== "class_name");
40066
40081
  }
@@ -40101,6 +40116,9 @@ function planClassRename(args) {
40101
40116
  }
40102
40117
  plan.mentionTheRest(texts, scripts, (file, occurrence, kind) => {
40103
40118
  const script = scripts.get(file);
40119
+ if (kind === "string" && script !== undefined && isWholeString(script.text, occurrence.offset, oldName)) {
40120
+ return WHOLE_CLASS_NAME;
40121
+ }
40104
40122
  if (kind !== "code" || script === undefined) {
40105
40123
  return;
40106
40124
  }
@@ -40167,7 +40185,16 @@ function planMemberRename(args) {
40167
40185
  }
40168
40186
  }
40169
40187
  planScenes(texts, heirs, kind, oldName, plan);
40170
- plan.mentionTheRest(texts, scripts, (_file, occurrence, mentionKind) => mentionKind === "code" ? occurrence.afterDot ? "not resolved to this member by the language server: another class with a member of the same name, or a value whose type the analyser could not tell" : "not resolved to this member by the language server" : undefined);
40188
+ plan.mentionTheRest(texts, scripts, (file, occurrence, mentionKind) => {
40189
+ const script = scripts.get(file);
40190
+ if (mentionKind === "string" && script !== undefined && isWholeString(script.text, occurrence.offset, oldName)) {
40191
+ return WHOLE_MEMBER_NAME;
40192
+ }
40193
+ if (mentionKind !== "code") {
40194
+ return;
40195
+ }
40196
+ return occurrence.afterDot ? "not resolved to this member by the language server: another class with a member of the same name, or a value whose type the analyser could not tell" : "not resolved to this member by the language server";
40197
+ });
40171
40198
  return plan.build();
40172
40199
  }
40173
40200
  function sectionsOf(text) {
@@ -40503,7 +40530,7 @@ function applyRewrites(rewrites) {
40503
40530
  throw error;
40504
40531
  }
40505
40532
  }
40506
- var DOC_LINK_KINDS, DOC_KIND;
40533
+ var DOC_LINK_KINDS, DOC_KIND, WHOLE_CLASS_NAME = "the whole string is the class's name, as find_children's type, a comparison with get_global_name() or a list of accepted type names takes it, and it was not changed: if it names this class, it no longer matches anything after the rename, and no compile says so", WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get or set take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so";
40507
40534
  var init_rename = __esm(() => {
40508
40535
  init_gdscript_source();
40509
40536
  DOC_LINK_KINDS = new Set([
@@ -40612,6 +40639,7 @@ async function renameSymbol(request, services) {
40612
40639
  let plan;
40613
40640
  let what;
40614
40641
  let resolvedBy;
40642
+ let took = null;
40615
40643
  let overrides = [];
40616
40644
  let declaringFiles = [declaringPath];
40617
40645
  if (isClass) {
@@ -40718,7 +40746,23 @@ async function renameSymbol(request, services) {
40718
40746
  }
40719
40747
  overrides = family.map((one) => one.file).filter((file) => file !== fileOf(root));
40720
40748
  declaringFiles = family.map((one) => one.file);
40721
- const resolved = await resolveMember(projectPath, texts, scripts, family, symbol, services);
40749
+ took = { references: { asked: 0, ms: 0 }, definitions: { asked: 0, ms: 0 } };
40750
+ let resolved;
40751
+ try {
40752
+ resolved = await resolveMember(projectPath, texts, scripts, family, symbol, services, took);
40753
+ } catch (error) {
40754
+ if (!(error instanceof LSPTimeout)) {
40755
+ throw error;
40756
+ }
40757
+ return {
40758
+ ok: false,
40759
+ reason: `The editor's language server took the ${error.method} request for ${symbol} and had not answered it after ${String(error.seconds)}s, so nothing was renamed.${error.method === "textDocument/references" ? " Godot answers a references request by looking for the name's text in every script and resolving each match, so a name also written as a common local takes longest." : ""}`,
40760
+ advice: [
40761
+ "script_diagnostics on the declaring script says whether the language server is answering other requests",
40762
+ "The request that ran out is still being answered by the editor, and another sent now waits behind it"
40763
+ ]
40764
+ };
40765
+ }
40722
40766
  if (resolved === null) {
40723
40767
  const first = family[0];
40724
40768
  const script = first === undefined ? undefined : scripts.get(first.file);
@@ -40766,6 +40810,7 @@ async function renameSymbol(request, services) {
40766
40810
  rewrites,
40767
40811
  report: {
40768
40812
  renamed: { what, from: symbol, to: newName, declaredIn: declaringPath, resolvedBy },
40813
+ ...took === null ? {} : { languageServer: took },
40769
40814
  ...overrides.length > 0 ? { overridesRenamed: overrides } : {},
40770
40815
  ...moveTo === null ? {} : { moved: { from: declaringPath, to: moveTo } },
40771
40816
  changed: changedReport(plan.edits, rewrites),
@@ -40774,7 +40819,7 @@ async function renameSymbol(request, services) {
40774
40819
  }
40775
40820
  };
40776
40821
  }
40777
- async function resolveMember(projectPath, texts, scripts, family, name, services) {
40822
+ async function resolveMember(projectPath, texts, scripts, family, name, services, took) {
40778
40823
  const absoluteOf = new Map(texts.map((file) => [file.path, file.absolute]));
40779
40824
  const key = (place) => `${place.file}:${place.offset}`;
40780
40825
  const toResolved = (located) => {
@@ -40788,11 +40833,16 @@ async function resolveMember(projectPath, texts, scripts, family, name, services
40788
40833
  if (script === undefined || absolute === undefined) {
40789
40834
  return [];
40790
40835
  }
40791
- const answered = await services[question](absolute, script.text, script.lines.positionOf(place.offset));
40792
- return answered.map(toResolved).filter((one) => one !== null);
40836
+ const started = performance.now();
40837
+ try {
40838
+ const answered = await services[question](absolute, script.text, script.lines.positionOf(place.offset));
40839
+ return answered.map(toResolved).filter((one) => one !== null);
40840
+ } finally {
40841
+ took[question].asked += 1;
40842
+ took[question].ms += Math.round(performance.now() - started);
40843
+ }
40793
40844
  };
40794
40845
  const ours = new Map;
40795
- const settled = new Set;
40796
40846
  const root = family[0];
40797
40847
  if (root === undefined) {
40798
40848
  return [];
@@ -40808,23 +40858,13 @@ async function resolveMember(projectPath, texts, scripts, family, name, services
40808
40858
  for (const script of scripts.values()) {
40809
40859
  for (const occurrence of script.occurrences(name)) {
40810
40860
  const place = { file: script.path, offset: occurrence.offset };
40811
- if (occurrence.kind !== "code" || ours.has(key(place)) || settled.has(key(place))) {
40861
+ if (occurrence.kind !== "code" || ours.has(key(place))) {
40812
40862
  continue;
40813
40863
  }
40814
40864
  const declared = await ask("definitions", place);
40815
40865
  const only = declared.length === 1 ? declared[0] : undefined;
40816
- if (only === undefined) {
40817
- settled.add(key(place));
40818
- continue;
40819
- }
40820
- const isOurs = declarations.has(key(only));
40821
- const same = [place, ...await ask("references", place)];
40822
- for (const one of same) {
40823
- if (isOurs) {
40824
- ours.set(key(one), one);
40825
- } else {
40826
- settled.add(key(one));
40827
- }
40866
+ if (only !== undefined && declarations.has(key(only))) {
40867
+ ours.set(key(place), place);
40828
40868
  }
40829
40869
  }
40830
40870
  }
@@ -40880,8 +40920,9 @@ ${mention.kind}`;
40880
40920
  full.push(mention);
40881
40921
  }
40882
40922
  }
40923
+ const loudFirst = (mention) => mention.kind === "string" && mention.why !== undefined ? 0 : 1;
40883
40924
  return {
40884
- leftAlone: full,
40925
+ leftAlone: full.sort((a, b) => loudFirst(a) - loudFirst(b)),
40885
40926
  ...prose.size > 0 ? { leftAloneInProse: [...prose.values()] } : {}
40886
40927
  };
40887
40928
  }
@@ -40904,6 +40945,7 @@ function summaryOf(edits, mentions, rewrites) {
40904
40945
  var WHAT;
40905
40946
  var init_rename_symbol = __esm(() => {
40906
40947
  init_gdscript_source();
40948
+ init_lsp_client();
40907
40949
  init_project_scan();
40908
40950
  init_rename();
40909
40951
  init_resources();
@@ -45981,7 +46023,9 @@ class GodotServer {
45981
46023
  }
45982
46024
  return await question(client);
45983
46025
  } catch (error) {
45984
- languageServer.problem = normalizeLSPError(error, client.port);
46026
+ if (!(error instanceof LSPTimeout)) {
46027
+ languageServer.problem = normalizeLSPError(error, client.port);
46028
+ }
45985
46029
  throw error;
45986
46030
  }
45987
46031
  };
@@ -47743,8 +47787,9 @@ that a setting nobody wrote down still answers with the default the engine regis
47743
47787
 
47744
47788
  To rename a class or a member across the project, use \`script_edit rename\` rather than a text
47745
47789
  replacement: it changes the uses the engine resolves, doc links and what scenes record, and lists
47746
- every other occurrence of the word for you to read: a \`find_children\` string or a file under a
47747
- \`.gdignore\` directory under \`leftAlone\`, and a word in a comment or a text file by line under
47790
+ every other occurrence of the word for you to read: a string that is the name whole, such as a
47791
+ \`find_children\` type, first under \`leftAlone\` since nothing compiles it, a file under a
47792
+ \`.gdignore\` directory there too, and a word in a comment or a text file by line under
47748
47793
  \`leftAloneInProse\`. Pass \`preview\` first on a large rename. If the answer has \`ok: false\`
47749
47794
  beside \`written: true\`, the files were changed and a script did not compile afterwards.
47750
47795
 
package/build/index.js CHANGED
@@ -29540,6 +29540,18 @@ import { dirname as dirname8, join as join14, resolve as resolve3 } from "node:p
29540
29540
  import { fileURLToPath, pathToFileURL } from "node:url";
29541
29541
  var DEFAULT_LSP_PORT = 6005;
29542
29542
  var DIAGNOSTICS_TIMEOUT_MS = 5000;
29543
+ var REQUEST_TIMEOUT_MS2 = 1e4;
29544
+ var REFERENCES_TIMEOUT_MS = 180000;
29545
+
29546
+ class LSPTimeout extends Error {
29547
+ method;
29548
+ seconds;
29549
+ constructor(method, timeoutMs) {
29550
+ super(`LSP request timed out after ${String(timeoutMs / 1000)}s: ${method}`);
29551
+ this.method = method;
29552
+ this.seconds = timeoutMs / 1000;
29553
+ }
29554
+ }
29543
29555
 
29544
29556
  class ArgumentRefusal extends Refusal {
29545
29557
  }
@@ -29583,9 +29595,11 @@ class GodotLSPClient {
29583
29595
  diagnosticsWaiters = new Map;
29584
29596
  documentVersions = new Map;
29585
29597
  documentTurns = new Map;
29586
- constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1") {
29598
+ timeouts;
29599
+ constructor(port = portFromEnv("GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT), host = "127.0.0.1", timeouts = { requestMs: REQUEST_TIMEOUT_MS2, referencesMs: REFERENCES_TIMEOUT_MS }) {
29587
29600
  this.port = port;
29588
29601
  this.host = host;
29602
+ this.timeouts = timeouts;
29589
29603
  this.pendingRequests = new Map;
29590
29604
  }
29591
29605
  async connect() {
@@ -29682,7 +29696,7 @@ class GodotLSPClient {
29682
29696
  const rootPath = this.rootPath ?? dirname8(resolve3(filePath));
29683
29697
  await this.initialize(rootPath);
29684
29698
  }
29685
- async sendRequest(method, params) {
29699
+ async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
29686
29700
  await this.ensureConnected();
29687
29701
  if (!this.socket) {
29688
29702
  throw new Refusal("Not connected to Godot LSP");
@@ -29699,8 +29713,8 @@ class GodotLSPClient {
29699
29713
  return new Promise((resolveRequest, rejectRequest) => {
29700
29714
  const timer = setTimeout(() => {
29701
29715
  this.pendingRequests.delete(id);
29702
- rejectRequest(new Error(`LSP request timed out after 10s: ${method}`));
29703
- }, 1e4);
29716
+ rejectRequest(new LSPTimeout(method, timeoutMs));
29717
+ }, timeoutMs);
29704
29718
  this.pendingRequests.set(id, {
29705
29719
  resolve: resolveRequest,
29706
29720
  reject: rejectRequest,
@@ -30005,7 +30019,7 @@ class GodotLSPClient {
30005
30019
  textDocument: { uri },
30006
30020
  position: { line, character },
30007
30021
  context: { includeDeclaration: true }
30008
- });
30022
+ }, this.timeouts.referencesMs);
30009
30023
  } finally {
30010
30024
  this.closeDocument(uri);
30011
30025
  }
@@ -30782,6 +30796,12 @@ class PlanBuilder {
30782
30796
  return { edits, mentions };
30783
30797
  }
30784
30798
  }
30799
+ function isWholeString(text, offset, name) {
30800
+ const before = text[offset - 1];
30801
+ return (before === '"' || before === "'") && text[offset + name.length] === before;
30802
+ }
30803
+ var WHOLE_CLASS_NAME = "the whole string is the class's name, as find_children's type, a comparison with get_global_name() or a list of accepted type names takes it, and it was not changed: if it names this class, it no longer matches anything after the rename, and no compile says so";
30804
+ var WHOLE_MEMBER_NAME = "the whole string is the member's name, as call, connect, has_method, get or set take it, and it was not changed: if it names this member, it no longer finds it after the rename, and no compile says so";
30785
30805
  function declaresItsOwn(script, name, except) {
30786
30806
  return script.occurrences(name).some((occurrence) => occurrence.kind === "code" && occurrence.offset !== except && occurrence.declaredAs !== null && occurrence.declaredAs !== "class_name");
30787
30807
  }
@@ -30822,6 +30842,9 @@ function planClassRename(args) {
30822
30842
  }
30823
30843
  plan.mentionTheRest(texts, scripts, (file, occurrence, kind) => {
30824
30844
  const script = scripts.get(file);
30845
+ if (kind === "string" && script !== undefined && isWholeString(script.text, occurrence.offset, oldName)) {
30846
+ return WHOLE_CLASS_NAME;
30847
+ }
30825
30848
  if (kind !== "code" || script === undefined) {
30826
30849
  return;
30827
30850
  }
@@ -30888,7 +30911,16 @@ function planMemberRename(args) {
30888
30911
  }
30889
30912
  }
30890
30913
  planScenes(texts, heirs, kind, oldName, plan);
30891
- plan.mentionTheRest(texts, scripts, (_file, occurrence, mentionKind) => mentionKind === "code" ? occurrence.afterDot ? "not resolved to this member by the language server: another class with a member of the same name, or a value whose type the analyser could not tell" : "not resolved to this member by the language server" : undefined);
30914
+ plan.mentionTheRest(texts, scripts, (file, occurrence, mentionKind) => {
30915
+ const script = scripts.get(file);
30916
+ if (mentionKind === "string" && script !== undefined && isWholeString(script.text, occurrence.offset, oldName)) {
30917
+ return WHOLE_MEMBER_NAME;
30918
+ }
30919
+ if (mentionKind !== "code") {
30920
+ return;
30921
+ }
30922
+ return occurrence.afterDot ? "not resolved to this member by the language server: another class with a member of the same name, or a value whose type the analyser could not tell" : "not resolved to this member by the language server";
30923
+ });
30892
30924
  return plan.build();
30893
30925
  }
30894
30926
  function sectionsOf(text) {
@@ -31578,6 +31610,7 @@ async function renameSymbol(request, services) {
31578
31610
  let plan;
31579
31611
  let what;
31580
31612
  let resolvedBy;
31613
+ let took = null;
31581
31614
  let overrides = [];
31582
31615
  let declaringFiles = [declaringPath];
31583
31616
  if (isClass) {
@@ -31684,7 +31717,23 @@ async function renameSymbol(request, services) {
31684
31717
  }
31685
31718
  overrides = family.map((one) => one.file).filter((file) => file !== fileOf(root));
31686
31719
  declaringFiles = family.map((one) => one.file);
31687
- const resolved = await resolveMember(projectPath, texts, scripts, family, symbol, services);
31720
+ took = { references: { asked: 0, ms: 0 }, definitions: { asked: 0, ms: 0 } };
31721
+ let resolved;
31722
+ try {
31723
+ resolved = await resolveMember(projectPath, texts, scripts, family, symbol, services, took);
31724
+ } catch (error) {
31725
+ if (!(error instanceof LSPTimeout)) {
31726
+ throw error;
31727
+ }
31728
+ return {
31729
+ ok: false,
31730
+ reason: `The editor's language server took the ${error.method} request for ${symbol} and had not answered it after ${String(error.seconds)}s, so nothing was renamed.${error.method === "textDocument/references" ? " Godot answers a references request by looking for the name's text in every script and resolving each match, so a name also written as a common local takes longest." : ""}`,
31731
+ advice: [
31732
+ "script_diagnostics on the declaring script says whether the language server is answering other requests",
31733
+ "The request that ran out is still being answered by the editor, and another sent now waits behind it"
31734
+ ]
31735
+ };
31736
+ }
31688
31737
  if (resolved === null) {
31689
31738
  const first = family[0];
31690
31739
  const script = first === undefined ? undefined : scripts.get(first.file);
@@ -31732,6 +31781,7 @@ async function renameSymbol(request, services) {
31732
31781
  rewrites,
31733
31782
  report: {
31734
31783
  renamed: { what, from: symbol, to: newName, declaredIn: declaringPath, resolvedBy },
31784
+ ...took === null ? {} : { languageServer: took },
31735
31785
  ...overrides.length > 0 ? { overridesRenamed: overrides } : {},
31736
31786
  ...moveTo === null ? {} : { moved: { from: declaringPath, to: moveTo } },
31737
31787
  changed: changedReport(plan.edits, rewrites),
@@ -31740,7 +31790,7 @@ async function renameSymbol(request, services) {
31740
31790
  }
31741
31791
  };
31742
31792
  }
31743
- async function resolveMember(projectPath, texts, scripts, family, name, services) {
31793
+ async function resolveMember(projectPath, texts, scripts, family, name, services, took) {
31744
31794
  const absoluteOf = new Map(texts.map((file) => [file.path, file.absolute]));
31745
31795
  const key = (place) => `${place.file}:${place.offset}`;
31746
31796
  const toResolved = (located) => {
@@ -31754,11 +31804,16 @@ async function resolveMember(projectPath, texts, scripts, family, name, services
31754
31804
  if (script === undefined || absolute === undefined) {
31755
31805
  return [];
31756
31806
  }
31757
- const answered = await services[question](absolute, script.text, script.lines.positionOf(place.offset));
31758
- return answered.map(toResolved).filter((one) => one !== null);
31807
+ const started = performance.now();
31808
+ try {
31809
+ const answered = await services[question](absolute, script.text, script.lines.positionOf(place.offset));
31810
+ return answered.map(toResolved).filter((one) => one !== null);
31811
+ } finally {
31812
+ took[question].asked += 1;
31813
+ took[question].ms += Math.round(performance.now() - started);
31814
+ }
31759
31815
  };
31760
31816
  const ours = new Map;
31761
- const settled = new Set;
31762
31817
  const root = family[0];
31763
31818
  if (root === undefined) {
31764
31819
  return [];
@@ -31774,23 +31829,13 @@ async function resolveMember(projectPath, texts, scripts, family, name, services
31774
31829
  for (const script of scripts.values()) {
31775
31830
  for (const occurrence of script.occurrences(name)) {
31776
31831
  const place = { file: script.path, offset: occurrence.offset };
31777
- if (occurrence.kind !== "code" || ours.has(key(place)) || settled.has(key(place))) {
31832
+ if (occurrence.kind !== "code" || ours.has(key(place))) {
31778
31833
  continue;
31779
31834
  }
31780
31835
  const declared = await ask("definitions", place);
31781
31836
  const only = declared.length === 1 ? declared[0] : undefined;
31782
- if (only === undefined) {
31783
- settled.add(key(place));
31784
- continue;
31785
- }
31786
- const isOurs = declarations.has(key(only));
31787
- const same = [place, ...await ask("references", place)];
31788
- for (const one of same) {
31789
- if (isOurs) {
31790
- ours.set(key(one), one);
31791
- } else {
31792
- settled.add(key(one));
31793
- }
31837
+ if (only !== undefined && declarations.has(key(only))) {
31838
+ ours.set(key(place), place);
31794
31839
  }
31795
31840
  }
31796
31841
  }
@@ -31846,8 +31891,9 @@ ${mention.kind}`;
31846
31891
  full.push(mention);
31847
31892
  }
31848
31893
  }
31894
+ const loudFirst = (mention) => mention.kind === "string" && mention.why !== undefined ? 0 : 1;
31849
31895
  return {
31850
- leftAlone: full,
31896
+ leftAlone: full.sort((a, b) => loudFirst(a) - loudFirst(b)),
31851
31897
  ...prose.size > 0 ? { leftAloneInProse: [...prose.values()] } : {}
31852
31898
  };
31853
31899
  }
@@ -33037,7 +33083,7 @@ var TOOL_SPECS = [
33037
33083
  },
33038
33084
  {
33039
33085
  name: "script_edit",
33040
- description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a find_children string, a node named like the class, a call on a value whose type the analyser could not tell. A comment or a line of a text file with nothing more to say is given under leftAloneInProse by file and line instead. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
33086
+ description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there. modify makes every addition or none, and answers with the line each one is on in the file written. rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment or a line of a text file with nothing more to say is given under leftAloneInProse by file and line instead. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
33041
33087
  parameters: {
33042
33088
  projectPath: PROJECT_PATH,
33043
33089
  scriptPath: SCRIPT_PATH,
@@ -38553,7 +38599,9 @@ class GodotServer {
38553
38599
  }
38554
38600
  return await question(client);
38555
38601
  } catch (error) {
38556
- languageServer.problem = normalizeLSPError(error, client.port);
38602
+ if (!(error instanceof LSPTimeout)) {
38603
+ languageServer.problem = normalizeLSPError(error, client.port);
38604
+ }
38557
38605
  throw error;
38558
38606
  }
38559
38607
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.1.34",
3
+ "version": "1.1.35",
4
4
  "mcpName": "io.github.Aureliolo/gdharness",
5
5
  "description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
6
6
  "type": "module",