gdharness 1.1.31 → 1.1.33
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
|
@@ -8733,6 +8733,398 @@ var init_issues = __esm(() => {
|
|
|
8733
8733
|
ENHANCEMENT_URL = `${NEW_ISSUE}?template=feature_request.md`;
|
|
8734
8734
|
});
|
|
8735
8735
|
|
|
8736
|
+
// src/gdscript-source.ts
|
|
8737
|
+
function isIdentifierStart(character) {
|
|
8738
|
+
return character !== undefined && IDENTIFIER_START.test(character);
|
|
8739
|
+
}
|
|
8740
|
+
function isIdentifierPart(character) {
|
|
8741
|
+
return character !== undefined && IDENTIFIER_PART.test(character);
|
|
8742
|
+
}
|
|
8743
|
+
function percentIsUniqueNode(source, at) {
|
|
8744
|
+
const next = source[at + 1];
|
|
8745
|
+
if (!isIdentifierStart(next) && next !== '"' && next !== "'") {
|
|
8746
|
+
return false;
|
|
8747
|
+
}
|
|
8748
|
+
let back = at - 1;
|
|
8749
|
+
while (back >= 0 && (source[back] === " " || source[back] === "\t")) {
|
|
8750
|
+
back -= 1;
|
|
8751
|
+
}
|
|
8752
|
+
const before = source[back];
|
|
8753
|
+
if (before === undefined || before === `
|
|
8754
|
+
`) {
|
|
8755
|
+
return true;
|
|
8756
|
+
}
|
|
8757
|
+
if (isIdentifierPart(before) || before === ")" || before === "]" || before === '"' || before === "'") {
|
|
8758
|
+
let wordStart = back;
|
|
8759
|
+
while (wordStart > 0 && isIdentifierPart(source[wordStart - 1])) {
|
|
8760
|
+
wordStart -= 1;
|
|
8761
|
+
}
|
|
8762
|
+
const word = source.slice(wordStart, back + 1);
|
|
8763
|
+
return ["return", "and", "or", "not", "in", "is", "as", "if", "elif", "else", "while", "await"].includes(word);
|
|
8764
|
+
}
|
|
8765
|
+
return true;
|
|
8766
|
+
}
|
|
8767
|
+
function stringEnd(source, at) {
|
|
8768
|
+
const quote = source[at] ?? '"';
|
|
8769
|
+
const fence = source.startsWith(quote.repeat(3), at) ? quote.repeat(3) : quote;
|
|
8770
|
+
let end = at + fence.length;
|
|
8771
|
+
while (end < source.length && !source.startsWith(fence, end)) {
|
|
8772
|
+
if (fence.length === 1 && source[end] === `
|
|
8773
|
+
`) {
|
|
8774
|
+
return end;
|
|
8775
|
+
}
|
|
8776
|
+
end += source[end] === "\\" ? 2 : 1;
|
|
8777
|
+
}
|
|
8778
|
+
return Math.min(end + fence.length, source.length);
|
|
8779
|
+
}
|
|
8780
|
+
function regionsOf(source) {
|
|
8781
|
+
const regions = [];
|
|
8782
|
+
let codeStart = 0;
|
|
8783
|
+
let at = 0;
|
|
8784
|
+
const close = (kind, start, end) => {
|
|
8785
|
+
if (codeStart < start) {
|
|
8786
|
+
regions.push({ kind: "code", start: codeStart, end: start });
|
|
8787
|
+
}
|
|
8788
|
+
regions.push({ kind, start, end });
|
|
8789
|
+
codeStart = end;
|
|
8790
|
+
at = end;
|
|
8791
|
+
};
|
|
8792
|
+
while (at < source.length) {
|
|
8793
|
+
const here = source[at];
|
|
8794
|
+
if (here === "#") {
|
|
8795
|
+
const lineEnd = source.indexOf(`
|
|
8796
|
+
`, at);
|
|
8797
|
+
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
8798
|
+
const lineStart = source.lastIndexOf(`
|
|
8799
|
+
`, at - 1) + 1;
|
|
8800
|
+
const alone = source.slice(lineStart, at).trim() === "";
|
|
8801
|
+
close(alone && source.startsWith("##", at) ? "doc" : "comment", at, end);
|
|
8802
|
+
continue;
|
|
8803
|
+
}
|
|
8804
|
+
if (here === '"' || here === "'") {
|
|
8805
|
+
const prefixed = at > 0 && ["r", "&", "^"].includes(source[at - 1] ?? "") && !isIdentifierPart(source[at - 2]);
|
|
8806
|
+
const start = prefixed ? at - 1 : at;
|
|
8807
|
+
const dollar = source[start - 1] === "$" || source[start - 1] === "%" && percentIsUniqueNode(source, start - 1);
|
|
8808
|
+
close(dollar ? "nodePath" : "string", dollar ? start - 1 : start, stringEnd(source, at));
|
|
8809
|
+
continue;
|
|
8810
|
+
}
|
|
8811
|
+
if (here === "$" || here === "%" && percentIsUniqueNode(source, at)) {
|
|
8812
|
+
if (source[at + 1] === '"' || source[at + 1] === "'") {
|
|
8813
|
+
at += 1;
|
|
8814
|
+
continue;
|
|
8815
|
+
}
|
|
8816
|
+
let end = at + 1;
|
|
8817
|
+
while (end < source.length && (isIdentifierPart(source[end]) || source[end] === "/" || source[end] === "%")) {
|
|
8818
|
+
end += 1;
|
|
8819
|
+
}
|
|
8820
|
+
if (end > at + 1) {
|
|
8821
|
+
close("nodePath", at, end);
|
|
8822
|
+
continue;
|
|
8823
|
+
}
|
|
8824
|
+
}
|
|
8825
|
+
if (isIdentifierStart(here)) {
|
|
8826
|
+
while (at < source.length && isIdentifierPart(source[at])) {
|
|
8827
|
+
at += 1;
|
|
8828
|
+
}
|
|
8829
|
+
continue;
|
|
8830
|
+
}
|
|
8831
|
+
at += 1;
|
|
8832
|
+
}
|
|
8833
|
+
if (codeStart < source.length) {
|
|
8834
|
+
regions.push({ kind: "code", start: codeStart, end: source.length });
|
|
8835
|
+
}
|
|
8836
|
+
return regions;
|
|
8837
|
+
}
|
|
8838
|
+
function codeOf(source, regions = regionsOf(source)) {
|
|
8839
|
+
let code = "";
|
|
8840
|
+
for (const region of regions) {
|
|
8841
|
+
const text = source.slice(region.start, region.end);
|
|
8842
|
+
code += region.kind === "code" ? text : text.replace(/[^\n]/g, " ");
|
|
8843
|
+
}
|
|
8844
|
+
return code;
|
|
8845
|
+
}
|
|
8846
|
+
|
|
8847
|
+
class Lines {
|
|
8848
|
+
starts = [0];
|
|
8849
|
+
text;
|
|
8850
|
+
constructor(text) {
|
|
8851
|
+
this.text = text;
|
|
8852
|
+
for (let at = text.indexOf(`
|
|
8853
|
+
`);at !== -1; at = text.indexOf(`
|
|
8854
|
+
`, at + 1)) {
|
|
8855
|
+
this.starts.push(at + 1);
|
|
8856
|
+
}
|
|
8857
|
+
}
|
|
8858
|
+
positionOf(offset) {
|
|
8859
|
+
let low = 0;
|
|
8860
|
+
let high = this.starts.length - 1;
|
|
8861
|
+
while (low < high) {
|
|
8862
|
+
const middle = low + high + 1 >> 1;
|
|
8863
|
+
if ((this.starts[middle] ?? 0) <= offset) {
|
|
8864
|
+
low = middle;
|
|
8865
|
+
} else {
|
|
8866
|
+
high = middle - 1;
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
return { line: low, character: offset - (this.starts[low] ?? 0) };
|
|
8870
|
+
}
|
|
8871
|
+
offsetOf(position) {
|
|
8872
|
+
const start = this.starts[position.line];
|
|
8873
|
+
if (start === undefined) {
|
|
8874
|
+
return null;
|
|
8875
|
+
}
|
|
8876
|
+
const offset = start + position.character;
|
|
8877
|
+
return offset > this.text.length ? null : offset;
|
|
8878
|
+
}
|
|
8879
|
+
lineAt(offset) {
|
|
8880
|
+
const { line } = this.positionOf(offset);
|
|
8881
|
+
const start = this.starts[line] ?? 0;
|
|
8882
|
+
const end = this.starts[line + 1] ?? this.text.length + 1;
|
|
8883
|
+
return this.text.slice(start, end - 1).replace(/\r$/, "");
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8886
|
+
function escapedForPattern(text) {
|
|
8887
|
+
return text.replace(/[\\^$.*+?()[\]{}|/]/g, "\\$&");
|
|
8888
|
+
}
|
|
8889
|
+
function wholeWord(name, flags = "gu") {
|
|
8890
|
+
return new RegExp(`(?<![\\p{L}\\p{N}_])${escapedForPattern(name)}(?![\\p{L}\\p{N}_])`, flags);
|
|
8891
|
+
}
|
|
8892
|
+
function occurrencesOf(source, name, regions = regionsOf(source)) {
|
|
8893
|
+
const found = [];
|
|
8894
|
+
let index = 0;
|
|
8895
|
+
let code = null;
|
|
8896
|
+
for (const match of source.matchAll(wholeWord(name))) {
|
|
8897
|
+
const offset = match.index;
|
|
8898
|
+
while (index < regions.length - 1 && (regions[index]?.end ?? 0) <= offset) {
|
|
8899
|
+
index += 1;
|
|
8900
|
+
}
|
|
8901
|
+
const kind = regions[index]?.kind ?? "code";
|
|
8902
|
+
let afterDot = false;
|
|
8903
|
+
let declaredAs = null;
|
|
8904
|
+
if (kind === "code") {
|
|
8905
|
+
code ??= codeOf(source, regions);
|
|
8906
|
+
let back = offset - 1;
|
|
8907
|
+
while (back >= 0 && /[\s\\]/.test(code[back] ?? "")) {
|
|
8908
|
+
back -= 1;
|
|
8909
|
+
}
|
|
8910
|
+
afterDot = code[back] === "." && code[back - 1] !== ".";
|
|
8911
|
+
let wordStart = back + 1;
|
|
8912
|
+
while (wordStart > 0 && isIdentifierPart(code[wordStart - 1])) {
|
|
8913
|
+
wordStart -= 1;
|
|
8914
|
+
}
|
|
8915
|
+
const word = code.slice(wordStart, back + 1);
|
|
8916
|
+
declaredAs = DECLARING.has(word) ? word : null;
|
|
8917
|
+
}
|
|
8918
|
+
found.push({ offset, kind, afterDot, declaredAs });
|
|
8919
|
+
}
|
|
8920
|
+
return found;
|
|
8921
|
+
}
|
|
8922
|
+
function extendsTarget(source, code, after) {
|
|
8923
|
+
let from = after;
|
|
8924
|
+
while (source[from] === " " || source[from] === "\t") {
|
|
8925
|
+
from += 1;
|
|
8926
|
+
}
|
|
8927
|
+
const quote = source[from];
|
|
8928
|
+
if (quote === '"' || quote === "'") {
|
|
8929
|
+
const end = source.indexOf(quote, from + 1);
|
|
8930
|
+
return end === -1 ? null : source.slice(from, end + 1);
|
|
8931
|
+
}
|
|
8932
|
+
const dotted = new RegExp(`^${NAME}(?:\\.${NAME})*`, "u").exec(code.slice(from));
|
|
8933
|
+
return dotted?.[0] ?? null;
|
|
8934
|
+
}
|
|
8935
|
+
function shapeOf(source) {
|
|
8936
|
+
const regions = regionsOf(source);
|
|
8937
|
+
const code = codeOf(source, regions);
|
|
8938
|
+
const lines = [];
|
|
8939
|
+
let start = 0;
|
|
8940
|
+
for (const text of code.split(`
|
|
8941
|
+
`)) {
|
|
8942
|
+
const indent = /^[ \t]*/.exec(text)?.[0].length ?? 0;
|
|
8943
|
+
lines.push({ start, text, indent });
|
|
8944
|
+
start += text.length + 1;
|
|
8945
|
+
}
|
|
8946
|
+
let className = null;
|
|
8947
|
+
const parseBody = (name, extendsAs, from, indent, bodyStart) => {
|
|
8948
|
+
const declarations = [];
|
|
8949
|
+
const inner = [];
|
|
8950
|
+
let index = from;
|
|
8951
|
+
let openEnum = null;
|
|
8952
|
+
let topExtends = extendsAs;
|
|
8953
|
+
while (index < lines.length) {
|
|
8954
|
+
const line = lines[index];
|
|
8955
|
+
if (line === undefined) {
|
|
8956
|
+
break;
|
|
8957
|
+
}
|
|
8958
|
+
if (openEnum !== null) {
|
|
8959
|
+
openEnum.text += `
|
|
8960
|
+
${line.text}`;
|
|
8961
|
+
if (line.text.includes("}")) {
|
|
8962
|
+
collectEnumValues(openEnum.text, openEnum.from, declarations);
|
|
8963
|
+
openEnum = null;
|
|
8964
|
+
}
|
|
8965
|
+
index += 1;
|
|
8966
|
+
continue;
|
|
8967
|
+
}
|
|
8968
|
+
if (line.text.trim() === "") {
|
|
8969
|
+
index += 1;
|
|
8970
|
+
continue;
|
|
8971
|
+
}
|
|
8972
|
+
if (line.indent < indent) {
|
|
8973
|
+
break;
|
|
8974
|
+
}
|
|
8975
|
+
if (line.indent > indent) {
|
|
8976
|
+
index += 1;
|
|
8977
|
+
continue;
|
|
8978
|
+
}
|
|
8979
|
+
const body = line.text.slice(line.indent);
|
|
8980
|
+
const at = line.start + line.indent;
|
|
8981
|
+
if (name === null) {
|
|
8982
|
+
const declared = CLASS_NAME_LINE.exec(body);
|
|
8983
|
+
if (declared?.[1] !== undefined) {
|
|
8984
|
+
className = { name: declared[1], offset: at + declared[0].length - declared[1].length };
|
|
8985
|
+
}
|
|
8986
|
+
if (EXTENDS_LINE.test(body) || CLASS_NAME_LINE.test(body)) {
|
|
8987
|
+
const clause = EXTENDS_CLAUSE.exec(body);
|
|
8988
|
+
if (clause !== null && topExtends === null) {
|
|
8989
|
+
topExtends = extendsTarget(source, code, at + clause.index + clause[0].length);
|
|
8990
|
+
}
|
|
8991
|
+
}
|
|
8992
|
+
}
|
|
8993
|
+
const declared = DECLARATION_LINE.exec(body);
|
|
8994
|
+
if (declared !== null) {
|
|
8995
|
+
const [whole, , isStatic, keyword, declaredName] = declared;
|
|
8996
|
+
if (keyword !== undefined && declaredName !== undefined) {
|
|
8997
|
+
const offset = at + whole.length - declaredName.length;
|
|
8998
|
+
const kind = keyword;
|
|
8999
|
+
declarations.push({ name: declaredName, kind, offset, isStatic: isStatic !== undefined });
|
|
9000
|
+
if (kind === "enum") {
|
|
9001
|
+
const brace = body.indexOf("{");
|
|
9002
|
+
if (brace !== -1) {
|
|
9003
|
+
openEnum = { from: at + brace, text: body.slice(brace) };
|
|
9004
|
+
if (body.includes("}", brace)) {
|
|
9005
|
+
collectEnumValues(openEnum.text, openEnum.from, declarations);
|
|
9006
|
+
openEnum = null;
|
|
9007
|
+
}
|
|
9008
|
+
}
|
|
9009
|
+
}
|
|
9010
|
+
if (kind === "class") {
|
|
9011
|
+
const rest = body.slice(whole.length);
|
|
9012
|
+
const clause = EXTENDS_CLAUSE.exec(rest);
|
|
9013
|
+
const innerExtends = clause === null ? null : extendsTarget(source, code, at + whole.length + clause.index + clause[0].length);
|
|
9014
|
+
const childIndent = lines.slice(index + 1).find((next) => next.text.trim() !== "")?.indent ?? indent + 1;
|
|
9015
|
+
if (childIndent > indent) {
|
|
9016
|
+
const parsed = parseBody(declaredName, innerExtends, index + 1, childIndent, line.start);
|
|
9017
|
+
inner.push(parsed.body);
|
|
9018
|
+
index = parsed.next;
|
|
9019
|
+
continue;
|
|
9020
|
+
}
|
|
9021
|
+
inner.push({
|
|
9022
|
+
name: declaredName,
|
|
9023
|
+
extendsAs: innerExtends,
|
|
9024
|
+
declarations: [],
|
|
9025
|
+
inner: [],
|
|
9026
|
+
start: line.start,
|
|
9027
|
+
end: line.start + line.text.length
|
|
9028
|
+
});
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
} else if (/^enum\s*\{/u.test(body)) {
|
|
9032
|
+
const brace = body.indexOf("{");
|
|
9033
|
+
openEnum = { from: at + brace, text: body.slice(brace) };
|
|
9034
|
+
if (body.includes("}", brace)) {
|
|
9035
|
+
collectEnumValues(openEnum.text, openEnum.from, declarations);
|
|
9036
|
+
openEnum = null;
|
|
9037
|
+
}
|
|
9038
|
+
}
|
|
9039
|
+
index += 1;
|
|
9040
|
+
}
|
|
9041
|
+
const last = lines[index - 1];
|
|
9042
|
+
return {
|
|
9043
|
+
body: {
|
|
9044
|
+
name,
|
|
9045
|
+
extendsAs: topExtends,
|
|
9046
|
+
declarations,
|
|
9047
|
+
inner,
|
|
9048
|
+
start: bodyStart,
|
|
9049
|
+
end: last === undefined ? bodyStart : last.start + last.text.length
|
|
9050
|
+
},
|
|
9051
|
+
next: index
|
|
9052
|
+
};
|
|
9053
|
+
};
|
|
9054
|
+
const { body } = parseBody(null, null, 0, 0, 0);
|
|
9055
|
+
return { className, body };
|
|
9056
|
+
}
|
|
9057
|
+
function collectEnumValues(text, from, into) {
|
|
9058
|
+
const close = text.indexOf("}");
|
|
9059
|
+
const inside = text.slice(1, close === -1 ? text.length : close);
|
|
9060
|
+
for (const match of inside.matchAll(new RegExp(`(^|,)\\s*(${NAME})`, "gu"))) {
|
|
9061
|
+
const valueName = match[2];
|
|
9062
|
+
if (valueName === undefined) {
|
|
9063
|
+
continue;
|
|
9064
|
+
}
|
|
9065
|
+
const offset = from + 1 + match.index + match[0].length - valueName.length;
|
|
9066
|
+
into.push({ name: valueName, kind: "enumValue", offset, isStatic: true });
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
function isIdentifier(name) {
|
|
9070
|
+
return new RegExp(`^${NAME}$`, "u").test(name) && !KEYWORDS.has(name);
|
|
9071
|
+
}
|
|
9072
|
+
var IDENTIFIER_START, IDENTIFIER_PART, DECLARING, NAME = "[\\p{L}_][\\p{L}\\p{N}_]*", ANNOTATIONS = "(?:@[\\p{L}_][\\p{L}\\p{N}_]*(?:\\([^)]*\\))?\\s+)*", DECLARATION_LINE, CLASS_NAME_LINE, EXTENDS_LINE, EXTENDS_CLAUSE, KEYWORDS;
|
|
9073
|
+
var init_gdscript_source = __esm(() => {
|
|
9074
|
+
IDENTIFIER_START = /[\p{L}_]/u;
|
|
9075
|
+
IDENTIFIER_PART = /[\p{L}\p{N}_]/u;
|
|
9076
|
+
DECLARING = new Set(["var", "const", "func", "signal", "enum", "class", "for", "class_name"]);
|
|
9077
|
+
DECLARATION_LINE = new RegExp(`^(${ANNOTATIONS})(static\\s+)?(func|var|const|signal|enum|class)\\s+(${NAME})`, "u");
|
|
9078
|
+
CLASS_NAME_LINE = new RegExp(`^${ANNOTATIONS}class_name\\s+(${NAME})`, "u");
|
|
9079
|
+
EXTENDS_LINE = new RegExp(`^${ANNOTATIONS}(?:class_name\\s+${NAME}\\s+)?extends\\s`, "u");
|
|
9080
|
+
EXTENDS_CLAUSE = /\bextends(?=\s)/u;
|
|
9081
|
+
KEYWORDS = new Set([
|
|
9082
|
+
"and",
|
|
9083
|
+
"as",
|
|
9084
|
+
"assert",
|
|
9085
|
+
"await",
|
|
9086
|
+
"break",
|
|
9087
|
+
"breakpoint",
|
|
9088
|
+
"class",
|
|
9089
|
+
"class_name",
|
|
9090
|
+
"const",
|
|
9091
|
+
"continue",
|
|
9092
|
+
"elif",
|
|
9093
|
+
"else",
|
|
9094
|
+
"enum",
|
|
9095
|
+
"extends",
|
|
9096
|
+
"false",
|
|
9097
|
+
"for",
|
|
9098
|
+
"func",
|
|
9099
|
+
"if",
|
|
9100
|
+
"in",
|
|
9101
|
+
"is",
|
|
9102
|
+
"match",
|
|
9103
|
+
"namespace",
|
|
9104
|
+
"not",
|
|
9105
|
+
"null",
|
|
9106
|
+
"or",
|
|
9107
|
+
"pass",
|
|
9108
|
+
"preload",
|
|
9109
|
+
"return",
|
|
9110
|
+
"self",
|
|
9111
|
+
"signal",
|
|
9112
|
+
"static",
|
|
9113
|
+
"super",
|
|
9114
|
+
"trait",
|
|
9115
|
+
"true",
|
|
9116
|
+
"var",
|
|
9117
|
+
"void",
|
|
9118
|
+
"when",
|
|
9119
|
+
"while",
|
|
9120
|
+
"yield",
|
|
9121
|
+
"INF",
|
|
9122
|
+
"NAN",
|
|
9123
|
+
"PI",
|
|
9124
|
+
"TAU"
|
|
9125
|
+
]);
|
|
9126
|
+
});
|
|
9127
|
+
|
|
8736
9128
|
// src/class-cache.ts
|
|
8737
9129
|
import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
8738
9130
|
import { join as join9 } from "node:path";
|
|
@@ -8783,33 +9175,6 @@ function scriptsNaming(projectPath, classes) {
|
|
|
8783
9175
|
});
|
|
8784
9176
|
return naming.sort((a, b) => a.script.localeCompare(b.script));
|
|
8785
9177
|
}
|
|
8786
|
-
function codeOf(source) {
|
|
8787
|
-
let code = "";
|
|
8788
|
-
let at = 0;
|
|
8789
|
-
while (at < source.length) {
|
|
8790
|
-
const here = source[at] ?? "";
|
|
8791
|
-
if (here === "#") {
|
|
8792
|
-
const end = source.indexOf(`
|
|
8793
|
-
`, at);
|
|
8794
|
-
at = end === -1 ? source.length : end;
|
|
8795
|
-
continue;
|
|
8796
|
-
}
|
|
8797
|
-
if (here === '"' || here === "'") {
|
|
8798
|
-
const fence = source.startsWith(here.repeat(3), at) ? here.repeat(3) : here;
|
|
8799
|
-
let end = at + fence.length;
|
|
8800
|
-
while (end < source.length && !source.startsWith(fence, end)) {
|
|
8801
|
-
end += source[end] === "\\" ? 2 : 1;
|
|
8802
|
-
}
|
|
8803
|
-
const literal = source.slice(at, Math.min(end + fence.length, source.length));
|
|
8804
|
-
code += literal.replace(/[^\n]/g, " ");
|
|
8805
|
-
at = end + fence.length;
|
|
8806
|
-
continue;
|
|
8807
|
-
}
|
|
8808
|
-
code += here;
|
|
8809
|
-
at += 1;
|
|
8810
|
-
}
|
|
8811
|
-
return code;
|
|
8812
|
-
}
|
|
8813
9178
|
function cachedClasses(projectPath) {
|
|
8814
9179
|
const cache = join9(projectPath, ".godot", "global_script_class_cache.cfg");
|
|
8815
9180
|
if (!existsSync9(cache)) {
|
|
@@ -9068,6 +9433,7 @@ function unseenByEditor(projectPath, editorHolds) {
|
|
|
9068
9433
|
}
|
|
9069
9434
|
var DECLARATION, TOOL;
|
|
9070
9435
|
var init_class_cache = __esm(() => {
|
|
9436
|
+
init_gdscript_source();
|
|
9071
9437
|
DECLARATION = /^(?:@[a-z_]+(?:\([^)\n]*\))?\s+)*class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m;
|
|
9072
9438
|
TOOL = /^[ \t]*(?:@[a-z_]+(?:\([^)\n]*\))?[ \t]+)*@tool\b/m;
|
|
9073
9439
|
});
|
|
@@ -17174,7 +17540,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17174
17540
|
requires: ["resourcePath", "options"]
|
|
17175
17541
|
},
|
|
17176
17542
|
reimport: {
|
|
17177
|
-
summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. Answered from a second status read: reimported lists what is current now, notReimported what is not,
|
|
17543
|
+
summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. The editor scans first, since its reimport passes over a file it has not taken in, such as one an installer has just written; what that scan imported is named under importedByTheScan and is not imported again unless force asks. Answered from a second status read: reimported lists what is current now, notReimported what is not, one entry per status and reason with the paths sharing them, and via says which of the two did the work. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, whether or not anything needed reimporting; one resourcePath is not a walk of the project and leaves it out. stillImporting is an editor that had not finished within timeoutMs",
|
|
17178
17544
|
requires: []
|
|
17179
17545
|
},
|
|
17180
17546
|
uid: {
|
|
@@ -17392,7 +17758,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17392
17758
|
},
|
|
17393
17759
|
{
|
|
17394
17760
|
name: "script_edit",
|
|
17395
|
-
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.",
|
|
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: 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 word in a comment, a node named like the class, a call on a value whose type the analyser could not tell. Read leftAlone before calling the rename done; nothing in it 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.",
|
|
17396
17762
|
parameters: {
|
|
17397
17763
|
projectPath: PROJECT_PATH,
|
|
17398
17764
|
scriptPath: SCRIPT_PATH,
|
|
@@ -17407,12 +17773,32 @@ var init_tool_definitions = __esm(() => {
|
|
|
17407
17773
|
enum: ["singleton", "state_machine", "component", "resource"],
|
|
17408
17774
|
description: "create: a starting shape."
|
|
17409
17775
|
},
|
|
17410
|
-
modifications: SCRIPT_MODIFICATIONS
|
|
17776
|
+
modifications: SCRIPT_MODIFICATIONS,
|
|
17777
|
+
symbol: {
|
|
17778
|
+
type: "string",
|
|
17779
|
+
ops: ["rename"],
|
|
17780
|
+
description: "rename: what to rename, declared by the script at scriptPath: its class_name, or a method, variable, constant, signal, enum, enum value or inner class at its top level. A class is renamed from the files and needs no editor: every bare use in code, the declaration, doc links such as [Told] and [method Told.say], and the script_class a .tres records. A script that declares something of its own by the old name, and a use after a dot, are left alone, since which of those mean the class cannot be told from the text. A member is renamed from what the editor's language server resolves to it, so it needs the editor open on this project; a method is renamed together with every override of it in the classes inheriting it, and from the topmost class declaring it when scriptPath's is itself an override. Doc links naming the member through any class inheriting it are renamed too, and so are a scene's or resource's saved value of a variable on a node or resource whose script inherits it, and a connection naming the signal on its emitter or the method on its receiver. A name the engine itself declares on the native base, such as _ready, is refused, as is a new name already taken: by a class, an autoload, an identifier in code, a native class or built-in type, or a member anywhere in the class's chain."
|
|
17781
|
+
},
|
|
17782
|
+
newName: { type: "string", ops: ["rename"], description: "rename: the new name." },
|
|
17783
|
+
newScriptPath: {
|
|
17784
|
+
type: "string",
|
|
17785
|
+
ops: ["rename"],
|
|
17786
|
+
description: "rename: also move the declaring script here, with its .uid beside it, rewriting every string that names it by path in the same style it was written (res:// or relative), and the relative paths in the moved script itself."
|
|
17787
|
+
},
|
|
17788
|
+
preview: {
|
|
17789
|
+
type: "boolean",
|
|
17790
|
+
ops: ["rename"],
|
|
17791
|
+
description: "rename: answer with what would change and what would be left alone, and write nothing. Default false."
|
|
17792
|
+
}
|
|
17411
17793
|
},
|
|
17412
17794
|
requires: ["projectPath", "scriptPath"],
|
|
17413
17795
|
operations: {
|
|
17414
17796
|
create: { summary: "a new script file", requires: [] },
|
|
17415
|
-
modify: { summary: "add to an existing script", requires: ["modifications"] }
|
|
17797
|
+
modify: { summary: "add to an existing script", requires: ["modifications"] },
|
|
17798
|
+
rename: {
|
|
17799
|
+
summary: "rename a class or a member wherever the engine uses it, across the project",
|
|
17800
|
+
requires: ["symbol", "newName"]
|
|
17801
|
+
}
|
|
17416
17802
|
}
|
|
17417
17803
|
},
|
|
17418
17804
|
{
|
|
@@ -17647,7 +18033,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17647
18033
|
},
|
|
17648
18034
|
{
|
|
17649
18035
|
name: "editor_status",
|
|
17650
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it; null when there is none. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option.",
|
|
18036
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it; null when there is none. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs gdharness upgrade, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. Every runtime_* answer from such a game carries addonIsStale and staleNote too. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and while that is read identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window.",
|
|
17651
18037
|
parameters: {},
|
|
17652
18038
|
requires: []
|
|
17653
18039
|
},
|
|
@@ -34440,7 +34826,7 @@ function theFirstOf(path) {
|
|
|
34440
34826
|
closeSync(handle);
|
|
34441
34827
|
}
|
|
34442
34828
|
}
|
|
34443
|
-
function
|
|
34829
|
+
function shapeOf2(text) {
|
|
34444
34830
|
const values = [];
|
|
34445
34831
|
let shape = "";
|
|
34446
34832
|
let at = 0;
|
|
@@ -34470,7 +34856,7 @@ function shapeOf(text) {
|
|
|
34470
34856
|
}
|
|
34471
34857
|
function bursts(entries, before, keep) {
|
|
34472
34858
|
const shapes = new Map;
|
|
34473
|
-
const shaped = entries.map((entry) =>
|
|
34859
|
+
const shaped = entries.map((entry) => shapeOf2(entry.text));
|
|
34474
34860
|
for (const [at, entry] of entries.entries()) {
|
|
34475
34861
|
const key = `${entry.severity}\x00${shaped[at]?.shape ?? entry.text}`;
|
|
34476
34862
|
const group = shapes.get(key) ?? { indices: [], values: [] };
|
|
@@ -37648,6 +38034,10 @@ var init_godot_bridge = __esm(() => {
|
|
|
37648
38034
|
isConnected() {
|
|
37649
38035
|
return this.socket?.readyState === import_websocket.default.OPEN && this.connectionInfo?.identifying !== true;
|
|
37650
38036
|
}
|
|
38037
|
+
isArriving() {
|
|
38038
|
+
const info = this.connectionInfo;
|
|
38039
|
+
return this.socket?.readyState === import_websocket.default.OPEN && info !== null && (info.greeted !== true || info.identifying === true);
|
|
38040
|
+
}
|
|
37651
38041
|
getStatus() {
|
|
37652
38042
|
const identifying = this.connectionInfo?.identifying === true;
|
|
37653
38043
|
const editor = identifying ? null : this.connectionInfo;
|
|
@@ -37816,6 +38206,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
37816
38206
|
this.connectionInfo.startedByAnEditor = typeof message.started_by_an_editor === "boolean" ? message.started_by_an_editor : undefined;
|
|
37817
38207
|
this.connectionInfo.syncsBreakpoints = typeof message.syncs_breakpoints === "boolean" ? message.syncs_breakpoints : undefined;
|
|
37818
38208
|
this.log("info", `Godot ready: ${message.project_path}`);
|
|
38209
|
+
this.connectionInfo.greeted = true;
|
|
37819
38210
|
const pid = this.connectionInfo.editorPid;
|
|
37820
38211
|
if (!this.connectionInfo.openedByAServer && pid !== undefined && this.oneShotReader !== null) {
|
|
37821
38212
|
this.identify(pid);
|
|
@@ -38875,6 +39266,39 @@ class GodotLSPClient {
|
|
|
38875
39266
|
}
|
|
38876
39267
|
});
|
|
38877
39268
|
}
|
|
39269
|
+
async getReferences(filePath, content, line, character) {
|
|
39270
|
+
await this.ensureConnected();
|
|
39271
|
+
await this.ensureInitializedForFile(filePath);
|
|
39272
|
+
const result = await this.withDocument(filePath, async () => {
|
|
39273
|
+
const uri = this.syncDocument(filePath, content);
|
|
39274
|
+
try {
|
|
39275
|
+
return await this.sendRequest("textDocument/references", {
|
|
39276
|
+
textDocument: { uri },
|
|
39277
|
+
position: { line, character },
|
|
39278
|
+
context: { includeDeclaration: true }
|
|
39279
|
+
});
|
|
39280
|
+
} finally {
|
|
39281
|
+
this.closeDocument(uri);
|
|
39282
|
+
}
|
|
39283
|
+
});
|
|
39284
|
+
return locationsIn(result);
|
|
39285
|
+
}
|
|
39286
|
+
async getDefinitions(filePath, content, line, character) {
|
|
39287
|
+
await this.ensureConnected();
|
|
39288
|
+
await this.ensureInitializedForFile(filePath);
|
|
39289
|
+
const result = await this.withDocument(filePath, async () => {
|
|
39290
|
+
const uri = this.syncDocument(filePath, content);
|
|
39291
|
+
try {
|
|
39292
|
+
return await this.sendRequest("textDocument/definition", {
|
|
39293
|
+
textDocument: { uri },
|
|
39294
|
+
position: { line, character }
|
|
39295
|
+
});
|
|
39296
|
+
} finally {
|
|
39297
|
+
this.closeDocument(uri);
|
|
39298
|
+
}
|
|
39299
|
+
});
|
|
39300
|
+
return locationsIn(result);
|
|
39301
|
+
}
|
|
38878
39302
|
async getDocumentSymbols(filePath, content) {
|
|
38879
39303
|
await this.ensureConnected();
|
|
38880
39304
|
await this.ensureInitializedForFile(filePath);
|
|
@@ -38894,6 +39318,22 @@ class GodotLSPClient {
|
|
|
38894
39318
|
return this.connected;
|
|
38895
39319
|
}
|
|
38896
39320
|
}
|
|
39321
|
+
function locationsIn(result) {
|
|
39322
|
+
const entries = Array.isArray(result) ? result : result === null || result === undefined ? [] : [result];
|
|
39323
|
+
const found = [];
|
|
39324
|
+
for (const entry of entries) {
|
|
39325
|
+
const location = entry;
|
|
39326
|
+
const uri = location.uri ?? location.targetUri;
|
|
39327
|
+
const start = location.range?.start ?? location.targetSelectionRange?.start;
|
|
39328
|
+
if (typeof uri !== "string" || typeof start?.line !== "number" || typeof start.character !== "number") {
|
|
39329
|
+
continue;
|
|
39330
|
+
}
|
|
39331
|
+
try {
|
|
39332
|
+
found.push({ file: fileURLToPath3(uri), line: start.line, character: start.character });
|
|
39333
|
+
} catch {}
|
|
39334
|
+
}
|
|
39335
|
+
return found;
|
|
39336
|
+
}
|
|
38897
39337
|
function asToolResponse(payload) {
|
|
38898
39338
|
return {
|
|
38899
39339
|
content: [
|
|
@@ -39187,6 +39627,62 @@ function scriptsWithoutUid(projectPath) {
|
|
|
39187
39627
|
function isBinary(bytes) {
|
|
39188
39628
|
return bytes.subarray(0, 8000).includes(0);
|
|
39189
39629
|
}
|
|
39630
|
+
function projectTexts(projectPath) {
|
|
39631
|
+
const found = [];
|
|
39632
|
+
const visit = (directory, prefix) => {
|
|
39633
|
+
if (steppedOver(directory)) {
|
|
39634
|
+
return;
|
|
39635
|
+
}
|
|
39636
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
39637
|
+
if (skipped(entry.name)) {
|
|
39638
|
+
continue;
|
|
39639
|
+
}
|
|
39640
|
+
const absolute = join20(directory, entry.name);
|
|
39641
|
+
const spelled = `${prefix}${entry.name}`;
|
|
39642
|
+
if (entry.isDirectory()) {
|
|
39643
|
+
visit(absolute, `${spelled}/`);
|
|
39644
|
+
continue;
|
|
39645
|
+
}
|
|
39646
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
39647
|
+
if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
|
|
39648
|
+
continue;
|
|
39649
|
+
}
|
|
39650
|
+
const bytes = readFileSync17(absolute);
|
|
39651
|
+
if (!isBinary(bytes)) {
|
|
39652
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
39653
|
+
}
|
|
39654
|
+
}
|
|
39655
|
+
};
|
|
39656
|
+
visit(projectPath, "");
|
|
39657
|
+
return found;
|
|
39658
|
+
}
|
|
39659
|
+
function ignoredTexts(projectPath) {
|
|
39660
|
+
const found = [];
|
|
39661
|
+
const visit = (directory, prefix, ignored) => {
|
|
39662
|
+
const under = ignored || steppedOver(directory);
|
|
39663
|
+
for (const entry of readdirSync7(directory, { withFileTypes: true })) {
|
|
39664
|
+
if (skipped(entry.name)) {
|
|
39665
|
+
continue;
|
|
39666
|
+
}
|
|
39667
|
+
const absolute = join20(directory, entry.name);
|
|
39668
|
+
const spelled = `${prefix}${entry.name}`;
|
|
39669
|
+
if (entry.isDirectory()) {
|
|
39670
|
+
visit(absolute, `${spelled}/`, under);
|
|
39671
|
+
continue;
|
|
39672
|
+
}
|
|
39673
|
+
const extension = entry.name.includes(".") ? entry.name.split(".").pop()?.toLowerCase() ?? "" : "";
|
|
39674
|
+
if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
|
|
39675
|
+
continue;
|
|
39676
|
+
}
|
|
39677
|
+
const bytes = readFileSync17(absolute);
|
|
39678
|
+
if (!isBinary(bytes)) {
|
|
39679
|
+
found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
|
|
39680
|
+
}
|
|
39681
|
+
}
|
|
39682
|
+
};
|
|
39683
|
+
visit(projectPath, "", false);
|
|
39684
|
+
return found;
|
|
39685
|
+
}
|
|
39190
39686
|
function searchProject(projectPath, options) {
|
|
39191
39687
|
const extensions = options.fileTypes === undefined ? null : new Set(options.fileTypes.map((ext) => ext.replace(/^\./, "").toLowerCase()).filter(Boolean));
|
|
39192
39688
|
const result = {
|
|
@@ -39245,7 +39741,7 @@ function searchProject(projectPath, options) {
|
|
|
39245
39741
|
visit(projectPath);
|
|
39246
39742
|
return result;
|
|
39247
39743
|
}
|
|
39248
|
-
var SKIPPED, SCENE_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID;
|
|
39744
|
+
var SKIPPED, SCENE_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
39249
39745
|
var init_project_scan = __esm(() => {
|
|
39250
39746
|
SKIPPED = new Set(["node_modules"]);
|
|
39251
39747
|
SCENE_EXTENSIONS = new Set(["tscn", "scn"]);
|
|
@@ -39280,19 +39776,1124 @@ var init_project_scan = __esm(() => {
|
|
|
39280
39776
|
"shader"
|
|
39281
39777
|
]);
|
|
39282
39778
|
CARRIES_A_UID = new Set(["gd", "shader", "gdshader"]);
|
|
39779
|
+
BINARY_EXTENSIONS = new Set([
|
|
39780
|
+
...[...ASSET_EXTENSIONS].filter((extension) => !["svg", "gltf", "gdshader", "shader", "fnt"].includes(extension)),
|
|
39781
|
+
"scn",
|
|
39782
|
+
"res",
|
|
39783
|
+
"zip",
|
|
39784
|
+
"pck",
|
|
39785
|
+
"exe",
|
|
39786
|
+
"dll",
|
|
39787
|
+
"so",
|
|
39788
|
+
"dylib"
|
|
39789
|
+
]);
|
|
39790
|
+
});
|
|
39791
|
+
|
|
39792
|
+
// src/rename.ts
|
|
39793
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync18, renameSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
|
|
39794
|
+
import { dirname as dirname14, posix } from "node:path";
|
|
39795
|
+
|
|
39796
|
+
class Script {
|
|
39797
|
+
path;
|
|
39798
|
+
text;
|
|
39799
|
+
regions;
|
|
39800
|
+
shape;
|
|
39801
|
+
lines;
|
|
39802
|
+
constructor(path, text) {
|
|
39803
|
+
this.path = path;
|
|
39804
|
+
this.text = text;
|
|
39805
|
+
this.regions = regionsOf(text);
|
|
39806
|
+
this.shape = shapeOf(text);
|
|
39807
|
+
this.lines = new Lines(text);
|
|
39808
|
+
}
|
|
39809
|
+
occurrences(name) {
|
|
39810
|
+
return occurrencesOf(this.text, name, this.regions);
|
|
39811
|
+
}
|
|
39812
|
+
}
|
|
39813
|
+
function classId(file, inner) {
|
|
39814
|
+
return `${file}#${inner.join(".")}`;
|
|
39815
|
+
}
|
|
39816
|
+
function fileOf(id) {
|
|
39817
|
+
return id.slice(0, id.lastIndexOf("#"));
|
|
39818
|
+
}
|
|
39819
|
+
|
|
39820
|
+
class ClassGraph {
|
|
39821
|
+
nodes = new Map;
|
|
39822
|
+
declared;
|
|
39823
|
+
constructor(scripts, declared) {
|
|
39824
|
+
this.declared = declared;
|
|
39825
|
+
for (const script of scripts.values()) {
|
|
39826
|
+
const add = (body, inner) => {
|
|
39827
|
+
const id = classId(script.path, inner);
|
|
39828
|
+
this.nodes.set(id, { id, file: script.path, inner, body, parent: null, native: null });
|
|
39829
|
+
for (const child of body.inner) {
|
|
39830
|
+
if (child.name !== null) {
|
|
39831
|
+
add(child, [...inner, child.name]);
|
|
39832
|
+
}
|
|
39833
|
+
}
|
|
39834
|
+
};
|
|
39835
|
+
add(script.shape.body, []);
|
|
39836
|
+
}
|
|
39837
|
+
for (const node of this.nodes.values()) {
|
|
39838
|
+
const resolved = this.resolve(node, node.body.extendsAs);
|
|
39839
|
+
node.parent = resolved.id;
|
|
39840
|
+
node.native = resolved.native;
|
|
39841
|
+
}
|
|
39842
|
+
}
|
|
39843
|
+
resolve(node, written) {
|
|
39844
|
+
if (written === null) {
|
|
39845
|
+
return { id: null, native: "RefCounted" };
|
|
39846
|
+
}
|
|
39847
|
+
if (written.startsWith('"') || written.startsWith("'")) {
|
|
39848
|
+
const path = resolvedPath(written.slice(1, -1), node.file);
|
|
39849
|
+
const id = classId(path, []);
|
|
39850
|
+
return this.nodes.has(id) ? { id, native: null } : { id: null, native: null };
|
|
39851
|
+
}
|
|
39852
|
+
const [first = "", ...rest] = written.split(".");
|
|
39853
|
+
for (let depth = node.inner.length;depth >= 0; depth -= 1) {
|
|
39854
|
+
const scope = this.nodes.get(classId(node.file, node.inner.slice(0, depth)));
|
|
39855
|
+
if (scope?.body.inner.some((child) => child.name === first) === true) {
|
|
39856
|
+
const id = classId(node.file, [...node.inner.slice(0, depth), first, ...rest]);
|
|
39857
|
+
return this.nodes.has(id) ? { id, native: null } : { id: null, native: null };
|
|
39858
|
+
}
|
|
39859
|
+
}
|
|
39860
|
+
const globalFile = this.declared.get(first);
|
|
39861
|
+
if (globalFile !== undefined) {
|
|
39862
|
+
const id = classId(globalFile, rest);
|
|
39863
|
+
return this.nodes.has(id) ? { id, native: null } : { id: null, native: null };
|
|
39864
|
+
}
|
|
39865
|
+
return { id: null, native: rest.length === 0 ? first : null };
|
|
39866
|
+
}
|
|
39867
|
+
ancestors(id) {
|
|
39868
|
+
const found = [];
|
|
39869
|
+
let at = this.nodes.get(id)?.parent ?? null;
|
|
39870
|
+
while (at !== null && !found.includes(at)) {
|
|
39871
|
+
found.push(at);
|
|
39872
|
+
at = this.nodes.get(at)?.parent ?? null;
|
|
39873
|
+
}
|
|
39874
|
+
return found;
|
|
39875
|
+
}
|
|
39876
|
+
nativeBase(id) {
|
|
39877
|
+
const chain = [id, ...this.ancestors(id)];
|
|
39878
|
+
return this.nodes.get(chain[chain.length - 1] ?? id)?.native ?? null;
|
|
39879
|
+
}
|
|
39880
|
+
descendants(id) {
|
|
39881
|
+
const found = new Set([id]);
|
|
39882
|
+
for (const node of this.nodes.values()) {
|
|
39883
|
+
if (this.ancestors(node.id).includes(id)) {
|
|
39884
|
+
found.add(node.id);
|
|
39885
|
+
}
|
|
39886
|
+
}
|
|
39887
|
+
return found;
|
|
39888
|
+
}
|
|
39889
|
+
bodyAt(file, offset) {
|
|
39890
|
+
let best = classId(file, []);
|
|
39891
|
+
let depth = 0;
|
|
39892
|
+
for (const node of this.nodes.values()) {
|
|
39893
|
+
if (node.file === file && node.inner.length > depth && node.body.start <= offset && offset <= node.body.end) {
|
|
39894
|
+
best = node.id;
|
|
39895
|
+
depth = node.inner.length;
|
|
39896
|
+
}
|
|
39897
|
+
}
|
|
39898
|
+
return best;
|
|
39899
|
+
}
|
|
39900
|
+
declaration(id, name) {
|
|
39901
|
+
const found = this.nodes.get(id)?.body.declarations.find((one) => one.name === name);
|
|
39902
|
+
return found === undefined ? undefined : { kind: found.kind, offset: found.offset };
|
|
39903
|
+
}
|
|
39904
|
+
}
|
|
39905
|
+
function resolvedPath(written, from) {
|
|
39906
|
+
if (written.startsWith("res://")) {
|
|
39907
|
+
return `res://${posix.normalize(written.slice("res://".length))}`;
|
|
39908
|
+
}
|
|
39909
|
+
const directory = posix.dirname(from.slice("res://".length));
|
|
39910
|
+
return `res://${posix.normalize(posix.join(directory, written))}`;
|
|
39911
|
+
}
|
|
39912
|
+
function relativeTo(target, from) {
|
|
39913
|
+
return posix.relative(posix.dirname(from.slice("res://".length)), target.slice("res://".length));
|
|
39914
|
+
}
|
|
39915
|
+
function scriptsOf(texts) {
|
|
39916
|
+
const scripts = new Map;
|
|
39917
|
+
for (const file of texts) {
|
|
39918
|
+
if (file.path.endsWith(".gd")) {
|
|
39919
|
+
scripts.set(file.path, new Script(file.path, file.text));
|
|
39920
|
+
}
|
|
39921
|
+
}
|
|
39922
|
+
return scripts;
|
|
39923
|
+
}
|
|
39924
|
+
function declaredIn(scripts) {
|
|
39925
|
+
const declared = new Map;
|
|
39926
|
+
for (const script of scripts.values()) {
|
|
39927
|
+
if (script.shape.className !== null) {
|
|
39928
|
+
declared.set(script.shape.className.name, script.path);
|
|
39929
|
+
}
|
|
39930
|
+
}
|
|
39931
|
+
return declared;
|
|
39932
|
+
}
|
|
39933
|
+
function docBlockAt(text, offset) {
|
|
39934
|
+
const lineStartOf = (at) => text.lastIndexOf(`
|
|
39935
|
+
`, at - 1) + 1;
|
|
39936
|
+
const contentOf = (lineStart) => {
|
|
39937
|
+
let at = lineStart;
|
|
39938
|
+
while (text[at] === " " || text[at] === "\t") {
|
|
39939
|
+
at += 1;
|
|
39940
|
+
}
|
|
39941
|
+
return text.startsWith("##", at) ? at + 2 : null;
|
|
39942
|
+
};
|
|
39943
|
+
let top = lineStartOf(offset);
|
|
39944
|
+
if (contentOf(top) === null) {
|
|
39945
|
+
return null;
|
|
39946
|
+
}
|
|
39947
|
+
while (top > 0 && contentOf(lineStartOf(top - 1)) !== null) {
|
|
39948
|
+
top = lineStartOf(top - 1);
|
|
39949
|
+
}
|
|
39950
|
+
let joined = "";
|
|
39951
|
+
const from = [];
|
|
39952
|
+
for (let line = top;; ) {
|
|
39953
|
+
const content = contentOf(line);
|
|
39954
|
+
if (content === null) {
|
|
39955
|
+
break;
|
|
39956
|
+
}
|
|
39957
|
+
const nextBreak = text.indexOf(`
|
|
39958
|
+
`, line);
|
|
39959
|
+
let end = nextBreak < 0 ? text.length : nextBreak;
|
|
39960
|
+
if (text[end - 1] === "\r") {
|
|
39961
|
+
end -= 1;
|
|
39962
|
+
}
|
|
39963
|
+
if (joined.length > 0) {
|
|
39964
|
+
joined += " ";
|
|
39965
|
+
from.push(-1);
|
|
39966
|
+
}
|
|
39967
|
+
for (let at = content;at < end; at += 1) {
|
|
39968
|
+
joined += text[at];
|
|
39969
|
+
from.push(at);
|
|
39970
|
+
}
|
|
39971
|
+
if (nextBreak < 0) {
|
|
39972
|
+
break;
|
|
39973
|
+
}
|
|
39974
|
+
line = nextBreak + 1;
|
|
39975
|
+
}
|
|
39976
|
+
return { joined, from };
|
|
39977
|
+
}
|
|
39978
|
+
function docLinkAt(text, offset) {
|
|
39979
|
+
const block = docBlockAt(text, offset);
|
|
39980
|
+
const at = block?.from.indexOf(offset) ?? -1;
|
|
39981
|
+
if (block === null || at < 0) {
|
|
39982
|
+
return null;
|
|
39983
|
+
}
|
|
39984
|
+
const { joined } = block;
|
|
39985
|
+
const open = joined.lastIndexOf("[", at);
|
|
39986
|
+
const close = joined.indexOf("]", at);
|
|
39987
|
+
if (open < 0 || close < 0) {
|
|
39988
|
+
return null;
|
|
39989
|
+
}
|
|
39990
|
+
const inside = joined.slice(open + 1, close);
|
|
39991
|
+
if (inside.includes("[") || inside.includes("]")) {
|
|
39992
|
+
return null;
|
|
39993
|
+
}
|
|
39994
|
+
const match = /^\s*(?:([a-z_]+)\s+)?([\p{L}_][\p{L}\p{N}_]*(?:\.[\p{L}_][\p{L}\p{N}_]*)*)\s*$/u.exec(inside);
|
|
39995
|
+
if (match === null || match[1] !== undefined && !DOC_LINK_KINDS.has(match[1])) {
|
|
39996
|
+
return null;
|
|
39997
|
+
}
|
|
39998
|
+
const path = match[2] ?? "";
|
|
39999
|
+
const parts = path.split(".");
|
|
40000
|
+
let partAt = open + 1 + inside.trimEnd().length - path.length;
|
|
40001
|
+
for (const [index, part] of parts.entries()) {
|
|
40002
|
+
if (partAt === at) {
|
|
40003
|
+
return { kind: match[1] ?? null, parts, index };
|
|
40004
|
+
}
|
|
40005
|
+
partAt += part.length + 1;
|
|
40006
|
+
}
|
|
40007
|
+
return null;
|
|
40008
|
+
}
|
|
40009
|
+
|
|
40010
|
+
class PlanBuilder {
|
|
40011
|
+
edits = new Map;
|
|
40012
|
+
mentions = [];
|
|
40013
|
+
oldName;
|
|
40014
|
+
newName;
|
|
40015
|
+
constructor(oldName, newName) {
|
|
40016
|
+
this.oldName = oldName;
|
|
40017
|
+
this.newName = newName;
|
|
40018
|
+
}
|
|
40019
|
+
edit(file, offset, kind, length = this.oldName.length, replacement = this.newName) {
|
|
40020
|
+
const key = `${file}:${offset}`;
|
|
40021
|
+
if (!this.edits.has(key)) {
|
|
40022
|
+
this.edits.set(key, { file, offset, length, replacement, kind });
|
|
40023
|
+
}
|
|
40024
|
+
}
|
|
40025
|
+
edited(file, offset) {
|
|
40026
|
+
return this.edits.has(`${file}:${offset}`);
|
|
40027
|
+
}
|
|
40028
|
+
mention(file, lines, offset, kind, why) {
|
|
40029
|
+
this.mentions.push({
|
|
40030
|
+
file,
|
|
40031
|
+
line: lines.positionOf(offset).line + 1,
|
|
40032
|
+
kind,
|
|
40033
|
+
text: lines.lineAt(offset).trim(),
|
|
40034
|
+
...why === undefined ? {} : { why }
|
|
40035
|
+
});
|
|
40036
|
+
}
|
|
40037
|
+
mentionTheRest(texts, scripts, whyFor) {
|
|
40038
|
+
const already = new Set(this.mentions.map((one) => `${one.file}:${one.line}:${one.kind}:${one.text}`));
|
|
40039
|
+
for (const file of texts) {
|
|
40040
|
+
const script = scripts.get(file.path);
|
|
40041
|
+
const lines = script?.lines ?? new Lines(file.text);
|
|
40042
|
+
const occurrences = script?.occurrences(this.oldName) ?? occurrencesOf(file.text, this.oldName, [{ kind: "code", start: 0, end: file.text.length }]);
|
|
40043
|
+
for (const occurrence of occurrences) {
|
|
40044
|
+
if (this.edited(file.path, occurrence.offset)) {
|
|
40045
|
+
continue;
|
|
40046
|
+
}
|
|
40047
|
+
const kind = script !== undefined ? occurrence.kind : /\.(tscn|tres)$/.test(file.path) ? "scene" : "text";
|
|
40048
|
+
const why = whyFor(file.path, occurrence, kind);
|
|
40049
|
+
const key = `${file.path}:${lines.positionOf(occurrence.offset).line + 1}:${kind}:${lines.lineAt(occurrence.offset).trim()}`;
|
|
40050
|
+
if (already.has(key)) {
|
|
40051
|
+
continue;
|
|
40052
|
+
}
|
|
40053
|
+
already.add(key);
|
|
40054
|
+
this.mention(file.path, lines, occurrence.offset, kind, why);
|
|
40055
|
+
}
|
|
40056
|
+
}
|
|
40057
|
+
}
|
|
40058
|
+
build() {
|
|
40059
|
+
const edits = [...this.edits.values()].sort((a, b) => a.file.localeCompare(b.file) || a.offset - b.offset);
|
|
40060
|
+
const mentions = [...this.mentions].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
40061
|
+
return { edits, mentions };
|
|
40062
|
+
}
|
|
40063
|
+
}
|
|
40064
|
+
function declaresItsOwn(script, name, except) {
|
|
40065
|
+
return script.occurrences(name).some((occurrence) => occurrence.kind === "code" && occurrence.offset !== except && occurrence.declaredAs !== null && occurrence.declaredAs !== "class_name");
|
|
40066
|
+
}
|
|
40067
|
+
function planClassRename(args) {
|
|
40068
|
+
const { texts, scripts, declaring, oldName, newName } = args;
|
|
40069
|
+
const plan = new PlanBuilder(oldName, newName);
|
|
40070
|
+
const declarationAt = declaring.shape.className?.offset ?? null;
|
|
40071
|
+
for (const script of scripts.values()) {
|
|
40072
|
+
const ownDeclaration = script.path === declaring.path ? declarationAt : null;
|
|
40073
|
+
const shadowed = declaresItsOwn(script, oldName, ownDeclaration);
|
|
40074
|
+
for (const occurrence of script.occurrences(oldName)) {
|
|
40075
|
+
if (occurrence.kind === "code") {
|
|
40076
|
+
if (occurrence.offset === ownDeclaration) {
|
|
40077
|
+
plan.edit(script.path, occurrence.offset, "declaration");
|
|
40078
|
+
} else if (!shadowed && !occurrence.afterDot && occurrence.declaredAs === null) {
|
|
40079
|
+
plan.edit(script.path, occurrence.offset, "code");
|
|
40080
|
+
}
|
|
40081
|
+
} else if (occurrence.kind === "doc") {
|
|
40082
|
+
const link = docLinkAt(script.text, occurrence.offset);
|
|
40083
|
+
if (link?.index === 0 && !shadowed) {
|
|
40084
|
+
plan.edit(script.path, occurrence.offset, "doc link");
|
|
40085
|
+
}
|
|
40086
|
+
}
|
|
40087
|
+
}
|
|
40088
|
+
}
|
|
40089
|
+
for (const file of texts) {
|
|
40090
|
+
if (!/\.(tscn|tres)$/.test(file.path)) {
|
|
40091
|
+
continue;
|
|
40092
|
+
}
|
|
40093
|
+
for (const section of sectionsOf(file.text)) {
|
|
40094
|
+
if (section.kind === "gd_resource" || section.kind === "gd_scene") {
|
|
40095
|
+
const recorded = section.attributes.get("script_class");
|
|
40096
|
+
if (recorded?.value === oldName) {
|
|
40097
|
+
plan.edit(file.path, recorded.offset, "resource class");
|
|
40098
|
+
}
|
|
40099
|
+
}
|
|
40100
|
+
}
|
|
40101
|
+
}
|
|
40102
|
+
plan.mentionTheRest(texts, scripts, (file, occurrence, kind) => {
|
|
40103
|
+
const script = scripts.get(file);
|
|
40104
|
+
if (kind !== "code" || script === undefined) {
|
|
40105
|
+
return;
|
|
40106
|
+
}
|
|
40107
|
+
if (occurrence.declaredAs === "class_name") {
|
|
40108
|
+
return `this script declares a class of the same name`;
|
|
40109
|
+
}
|
|
40110
|
+
if (occurrence.declaredAs !== null || declaresItsOwn(script, oldName, null)) {
|
|
40111
|
+
return `this script declares something of its own named ${oldName}, so which uses mean the class cannot be told from the text`;
|
|
40112
|
+
}
|
|
40113
|
+
if (occurrence.afterDot) {
|
|
40114
|
+
return `after a dot, a member of something else rather than the class`;
|
|
40115
|
+
}
|
|
40116
|
+
return;
|
|
40117
|
+
});
|
|
40118
|
+
return plan.build();
|
|
40119
|
+
}
|
|
40120
|
+
function planMemberRename(args) {
|
|
40121
|
+
const { texts, scripts, graph, root, family, kind, oldName, newName, resolved } = args;
|
|
40122
|
+
const plan = new PlanBuilder(oldName, newName);
|
|
40123
|
+
const heirs = graph.descendants(root);
|
|
40124
|
+
const docKind = DOC_KIND[kind];
|
|
40125
|
+
const familyAt = new Set(family.map((one) => `${one.file}:${one.offset}`));
|
|
40126
|
+
for (const one of family) {
|
|
40127
|
+
plan.edit(one.file, one.offset, "declaration");
|
|
40128
|
+
}
|
|
40129
|
+
for (const one of resolved) {
|
|
40130
|
+
const script = scripts.get(one.file);
|
|
40131
|
+
if (script === undefined || script.text.slice(one.offset, one.offset + oldName.length) !== oldName) {
|
|
40132
|
+
continue;
|
|
40133
|
+
}
|
|
40134
|
+
const occurrence = script.occurrences(oldName).find((each) => each.offset === one.offset);
|
|
40135
|
+
if (occurrence === undefined) {
|
|
40136
|
+
continue;
|
|
40137
|
+
}
|
|
40138
|
+
if (occurrence.kind === "code") {
|
|
40139
|
+
plan.edit(one.file, one.offset, familyAt.has(`${one.file}:${one.offset}`) ? "declaration" : "code");
|
|
40140
|
+
} else if (occurrence.kind === "doc" && docLinkAt(script.text, one.offset) !== null) {
|
|
40141
|
+
plan.edit(one.file, one.offset, "doc link");
|
|
40142
|
+
} else {
|
|
40143
|
+
plan.mention(one.file, script.lines, one.offset, occurrence.kind, `the language server resolved this to the ${kind === "func" ? "method" : "member"}, and it is inside ${occurrence.kind === "string" ? "a string" : occurrence.kind === "nodePath" ? "a node path" : "a comment"}, so it is left for you`);
|
|
40144
|
+
}
|
|
40145
|
+
}
|
|
40146
|
+
for (const script of scripts.values()) {
|
|
40147
|
+
for (const occurrence of script.occurrences(oldName)) {
|
|
40148
|
+
if (occurrence.kind !== "doc") {
|
|
40149
|
+
continue;
|
|
40150
|
+
}
|
|
40151
|
+
const link = docLinkAt(script.text, occurrence.offset);
|
|
40152
|
+
if (link === null || link.index !== link.parts.length - 1 || link.kind !== docKind || docKind === null) {
|
|
40153
|
+
continue;
|
|
40154
|
+
}
|
|
40155
|
+
const qualifiers = link.parts.slice(0, -1);
|
|
40156
|
+
let owner = null;
|
|
40157
|
+
if (qualifiers.length === 0) {
|
|
40158
|
+
owner = graph.bodyAt(script.path, occurrence.offset);
|
|
40159
|
+
} else {
|
|
40160
|
+
const [first = "", ...rest] = qualifiers;
|
|
40161
|
+
const file = graph.declared.get(first);
|
|
40162
|
+
owner = file === undefined ? null : `${file}#${rest.join(".")}`;
|
|
40163
|
+
}
|
|
40164
|
+
if (owner !== null && heirs.has(owner)) {
|
|
40165
|
+
plan.edit(script.path, occurrence.offset, "doc link");
|
|
40166
|
+
}
|
|
40167
|
+
}
|
|
40168
|
+
}
|
|
40169
|
+
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);
|
|
40171
|
+
return plan.build();
|
|
40172
|
+
}
|
|
40173
|
+
function sectionsOf(text) {
|
|
40174
|
+
const sections = [];
|
|
40175
|
+
let current = null;
|
|
40176
|
+
let depth = 0;
|
|
40177
|
+
let inString = false;
|
|
40178
|
+
let offset = 0;
|
|
40179
|
+
for (const line of text.split(`
|
|
40180
|
+
`)) {
|
|
40181
|
+
const atTop = depth === 0 && !inString;
|
|
40182
|
+
const header = atTop ? /^\[([a-z_]+)(.*)\]\s*$/.exec(line.replace(/\r$/, "")) : null;
|
|
40183
|
+
if (header !== null) {
|
|
40184
|
+
const attributes = new Map;
|
|
40185
|
+
const rest = header[2] ?? "";
|
|
40186
|
+
const restAt = offset + 1 + (header[1]?.length ?? 0);
|
|
40187
|
+
for (const attribute of rest.matchAll(/([a-z_]+)=("(?:[^"\\]|\\.)*"|[A-Za-z]+\("(?:[^"\\]|\\.)*"\)|[^\s\]]+)/g)) {
|
|
40188
|
+
const raw = attribute[2] ?? "";
|
|
40189
|
+
const quoted = raw.startsWith('"');
|
|
40190
|
+
attributes.set(attribute[1] ?? "", {
|
|
40191
|
+
value: quoted ? raw.slice(1, -1) : raw,
|
|
40192
|
+
offset: restAt + attribute.index + (attribute[1]?.length ?? 0) + 1 + (quoted ? 1 : 0)
|
|
40193
|
+
});
|
|
40194
|
+
}
|
|
40195
|
+
current = { kind: header[1] ?? "", attributes, properties: [] };
|
|
40196
|
+
sections.push(current);
|
|
40197
|
+
} else if (atTop && current !== null) {
|
|
40198
|
+
const property = /^([A-Za-z_][\w/:]*) = (.*)$/.exec(line.replace(/\r$/, ""));
|
|
40199
|
+
if (property !== null) {
|
|
40200
|
+
current.properties.push({ key: property[1] ?? "", offset, value: property[2] ?? "" });
|
|
40201
|
+
}
|
|
40202
|
+
}
|
|
40203
|
+
for (let at = 0;at < line.length; at += 1) {
|
|
40204
|
+
const character = line[at];
|
|
40205
|
+
if (inString) {
|
|
40206
|
+
if (character === "\\") {
|
|
40207
|
+
at += 1;
|
|
40208
|
+
} else if (character === '"') {
|
|
40209
|
+
inString = false;
|
|
40210
|
+
}
|
|
40211
|
+
} else if (character === '"') {
|
|
40212
|
+
inString = true;
|
|
40213
|
+
} else if (character === "[" || character === "{" || character === "(") {
|
|
40214
|
+
depth += 1;
|
|
40215
|
+
} else if (character === "]" || character === "}" || character === ")") {
|
|
40216
|
+
depth = Math.max(0, depth - 1);
|
|
40217
|
+
}
|
|
40218
|
+
}
|
|
40219
|
+
if (header !== null) {
|
|
40220
|
+
depth = 0;
|
|
40221
|
+
}
|
|
40222
|
+
offset += line.length + 1;
|
|
40223
|
+
}
|
|
40224
|
+
return sections;
|
|
40225
|
+
}
|
|
40226
|
+
function resourceReference(value) {
|
|
40227
|
+
const match = /^(Ext|Sub)Resource\(\s*"([^"]*)"\s*\)$/.exec(value.trim());
|
|
40228
|
+
return match === null ? null : { external: match[1] === "Ext", id: match[2] ?? "" };
|
|
40229
|
+
}
|
|
40230
|
+
|
|
40231
|
+
class SceneReader {
|
|
40232
|
+
cache = new Map;
|
|
40233
|
+
texts;
|
|
40234
|
+
uids;
|
|
40235
|
+
constructor(texts, uids) {
|
|
40236
|
+
this.texts = texts;
|
|
40237
|
+
this.uids = uids;
|
|
40238
|
+
}
|
|
40239
|
+
sections(path) {
|
|
40240
|
+
let found = this.cache.get(path);
|
|
40241
|
+
if (found === undefined) {
|
|
40242
|
+
const text = this.texts.get(path);
|
|
40243
|
+
found = text === undefined ? [] : sectionsOf(text);
|
|
40244
|
+
this.cache.set(path, found);
|
|
40245
|
+
}
|
|
40246
|
+
return found;
|
|
40247
|
+
}
|
|
40248
|
+
external(path, id) {
|
|
40249
|
+
const entry = this.sections(path).find((section) => section.kind === "ext_resource" && section.attributes.get("id")?.value === id);
|
|
40250
|
+
if (entry === undefined) {
|
|
40251
|
+
return null;
|
|
40252
|
+
}
|
|
40253
|
+
const written = entry.attributes.get("path")?.value;
|
|
40254
|
+
if (written !== undefined) {
|
|
40255
|
+
return resolvedPath(written, path);
|
|
40256
|
+
}
|
|
40257
|
+
const uid = entry.attributes.get("uid")?.value;
|
|
40258
|
+
return uid === undefined ? null : this.uids.get(uid) ?? null;
|
|
40259
|
+
}
|
|
40260
|
+
scriptOf(path, section, depth = 0) {
|
|
40261
|
+
const script = section.properties.find((property) => property.key === "script");
|
|
40262
|
+
if (script !== undefined) {
|
|
40263
|
+
const reference = resourceReference(script.value);
|
|
40264
|
+
return reference?.external === true ? this.external(path, reference.id) : null;
|
|
40265
|
+
}
|
|
40266
|
+
const instance = section.attributes.get("instance")?.value;
|
|
40267
|
+
const reference = instance === undefined ? null : resourceReference(instance);
|
|
40268
|
+
if (reference?.external !== true || depth > 8) {
|
|
40269
|
+
return null;
|
|
40270
|
+
}
|
|
40271
|
+
const scene = this.external(path, reference.id);
|
|
40272
|
+
if (scene === null) {
|
|
40273
|
+
return null;
|
|
40274
|
+
}
|
|
40275
|
+
const rootNode = this.sections(scene).find((candidate) => candidate.kind === "node" && !candidate.attributes.has("parent"));
|
|
40276
|
+
return rootNode === undefined ? null : this.scriptOf(scene, rootNode, depth + 1);
|
|
40277
|
+
}
|
|
40278
|
+
nodesByPath(path) {
|
|
40279
|
+
const nodes = new Map;
|
|
40280
|
+
for (const section of this.sections(path)) {
|
|
40281
|
+
if (section.kind !== "node") {
|
|
40282
|
+
continue;
|
|
40283
|
+
}
|
|
40284
|
+
const name = section.attributes.get("name")?.value ?? "";
|
|
40285
|
+
const parent = section.attributes.get("parent")?.value;
|
|
40286
|
+
nodes.set(parent === undefined ? "." : parent === "." ? name : `${parent}/${name}`, section);
|
|
40287
|
+
}
|
|
40288
|
+
return nodes;
|
|
40289
|
+
}
|
|
40290
|
+
}
|
|
40291
|
+
function planScenes(texts, heirs, kind, oldName, plan) {
|
|
40292
|
+
const byPath = new Map(texts.map((file) => [file.path, file.text]));
|
|
40293
|
+
const uids = new Map;
|
|
40294
|
+
for (const file of texts) {
|
|
40295
|
+
if (file.path.endsWith(".uid")) {
|
|
40296
|
+
uids.set(file.text.trim(), file.path.slice(0, -".uid".length));
|
|
40297
|
+
}
|
|
40298
|
+
}
|
|
40299
|
+
const reader = new SceneReader(byPath, uids);
|
|
40300
|
+
const inherits = (script) => script !== null && heirs.has(`${script}#`);
|
|
40301
|
+
for (const file of texts) {
|
|
40302
|
+
if (!/\.(tscn|tres)$/.test(file.path) || !file.text.includes(oldName)) {
|
|
40303
|
+
continue;
|
|
40304
|
+
}
|
|
40305
|
+
const sections = reader.sections(file.path);
|
|
40306
|
+
if (kind === "var") {
|
|
40307
|
+
for (const section of sections) {
|
|
40308
|
+
if (!["node", "resource", "sub_resource"].includes(section.kind)) {
|
|
40309
|
+
continue;
|
|
40310
|
+
}
|
|
40311
|
+
const property = section.properties.find((one) => one.key === oldName);
|
|
40312
|
+
if (property !== undefined && inherits(reader.scriptOf(file.path, section))) {
|
|
40313
|
+
plan.edit(file.path, property.offset, "scene property");
|
|
40314
|
+
}
|
|
40315
|
+
}
|
|
40316
|
+
}
|
|
40317
|
+
if (kind === "signal" || kind === "func") {
|
|
40318
|
+
const nodes = reader.nodesByPath(file.path);
|
|
40319
|
+
for (const section of sections) {
|
|
40320
|
+
if (section.kind !== "connection") {
|
|
40321
|
+
continue;
|
|
40322
|
+
}
|
|
40323
|
+
const attribute = section.attributes.get(kind === "signal" ? "signal" : "method");
|
|
40324
|
+
const node = nodes.get(section.attributes.get(kind === "signal" ? "from" : "to")?.value ?? "");
|
|
40325
|
+
if (attribute?.value === oldName && node !== undefined && inherits(reader.scriptOf(file.path, node))) {
|
|
40326
|
+
plan.edit(file.path, attribute.offset, "scene connection");
|
|
40327
|
+
}
|
|
40328
|
+
}
|
|
40329
|
+
}
|
|
40330
|
+
}
|
|
40331
|
+
}
|
|
40332
|
+
function literalsOf(file, script) {
|
|
40333
|
+
const found = [];
|
|
40334
|
+
if (script !== undefined) {
|
|
40335
|
+
for (const region of script.regions) {
|
|
40336
|
+
if (region.kind !== "string") {
|
|
40337
|
+
continue;
|
|
40338
|
+
}
|
|
40339
|
+
const literal = file.text.slice(region.start, region.end);
|
|
40340
|
+
const quote = /["']/.exec(literal);
|
|
40341
|
+
if (quote === null || literal.startsWith('"""', quote.index) || literal.startsWith("'''", quote.index)) {
|
|
40342
|
+
continue;
|
|
40343
|
+
}
|
|
40344
|
+
found.push({ offset: region.start + quote.index + 1, value: literal.slice(quote.index + 1, -1) });
|
|
40345
|
+
}
|
|
40346
|
+
return found;
|
|
40347
|
+
}
|
|
40348
|
+
for (const match of file.text.matchAll(/"((?:[^"\\\n]|\\.)*)"/g)) {
|
|
40349
|
+
found.push({ offset: match.index + 1, value: match[1] ?? "" });
|
|
40350
|
+
}
|
|
40351
|
+
return found;
|
|
40352
|
+
}
|
|
40353
|
+
function planMove(args, plan) {
|
|
40354
|
+
const { texts, scripts, from, to, exists } = args;
|
|
40355
|
+
const edits = [...plan.edits];
|
|
40356
|
+
const mentions = [...plan.mentions];
|
|
40357
|
+
const editedAt = new Set(edits.map((one) => `${one.file}:${one.offset}`));
|
|
40358
|
+
const add = (file, offset, length, replacement) => {
|
|
40359
|
+
if (!editedAt.has(`${file}:${offset}`)) {
|
|
40360
|
+
editedAt.add(`${file}:${offset}`);
|
|
40361
|
+
edits.push({ file, offset, length, replacement, kind: "path" });
|
|
40362
|
+
}
|
|
40363
|
+
};
|
|
40364
|
+
for (const file of texts) {
|
|
40365
|
+
const script = scripts.get(file.path);
|
|
40366
|
+
for (const literal of literalsOf(file, script)) {
|
|
40367
|
+
const autoload = literal.value.startsWith("*") ? "*" : "";
|
|
40368
|
+
const written = literal.value.slice(autoload.length);
|
|
40369
|
+
if (written === "" || written.startsWith("uid://") || written.includes(`
|
|
40370
|
+
`)) {
|
|
40371
|
+
continue;
|
|
40372
|
+
}
|
|
40373
|
+
const isAbsolute = written.startsWith("res://");
|
|
40374
|
+
const target = resolvedPath(written, file.path);
|
|
40375
|
+
if (target === from) {
|
|
40376
|
+
const replacement = isAbsolute ? to : relativeTo(to, file.path === from ? to : file.path);
|
|
40377
|
+
add(file.path, literal.offset + autoload.length, written.length, replacement);
|
|
40378
|
+
} else if (file.path === from && !isAbsolute && /\.[a-z0-9]+$/i.test(written) && exists(target)) {
|
|
40379
|
+
add(file.path, literal.offset, written.length, relativeTo(target, to));
|
|
40380
|
+
}
|
|
40381
|
+
}
|
|
40382
|
+
const lines = script?.lines ?? new Lines(file.text);
|
|
40383
|
+
for (const match of file.text.matchAll(new RegExp(from.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&"), "g"))) {
|
|
40384
|
+
const offset = match.index;
|
|
40385
|
+
const covered = edits.some((one) => one.file === file.path && one.offset <= offset && offset < one.offset + one.length);
|
|
40386
|
+
if (!covered) {
|
|
40387
|
+
const region = script?.regions.find((one) => one.start <= offset && offset < one.end);
|
|
40388
|
+
mentions.push({
|
|
40389
|
+
file: file.path,
|
|
40390
|
+
line: lines.positionOf(offset).line + 1,
|
|
40391
|
+
kind: region !== undefined ? region.kind : /\.(tscn|tres)$/.test(file.path) ? "scene" : "text",
|
|
40392
|
+
text: lines.lineAt(offset).trim(),
|
|
40393
|
+
why: `names ${from} outside a string the engine reads as a path`
|
|
40394
|
+
});
|
|
40395
|
+
}
|
|
40396
|
+
}
|
|
40397
|
+
}
|
|
40398
|
+
return {
|
|
40399
|
+
edits: edits.sort((a, b) => a.file.localeCompare(b.file) || a.offset - b.offset),
|
|
40400
|
+
mentions: mentions.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line)
|
|
40401
|
+
};
|
|
40402
|
+
}
|
|
40403
|
+
function mentionIgnored(plan, ignored, name, movedFrom) {
|
|
40404
|
+
const mentions = [...plan.mentions];
|
|
40405
|
+
const why = "under a directory holding a .gdignore, which the engine does not load, so it was not changed";
|
|
40406
|
+
for (const file of ignored) {
|
|
40407
|
+
const lines = new Lines(file.text);
|
|
40408
|
+
const offsets = occurrencesOf(file.text, name, [{ kind: "code", start: 0, end: file.text.length }]).map((one) => one.offset);
|
|
40409
|
+
if (movedFrom !== null) {
|
|
40410
|
+
let at = file.text.indexOf(movedFrom);
|
|
40411
|
+
while (at >= 0) {
|
|
40412
|
+
offsets.push(at);
|
|
40413
|
+
at = file.text.indexOf(movedFrom, at + movedFrom.length);
|
|
40414
|
+
}
|
|
40415
|
+
}
|
|
40416
|
+
const seen = new Set;
|
|
40417
|
+
for (const offset of offsets) {
|
|
40418
|
+
const line = lines.positionOf(offset).line + 1;
|
|
40419
|
+
if (!seen.has(line)) {
|
|
40420
|
+
seen.add(line);
|
|
40421
|
+
mentions.push({ file: file.path, line, kind: "ignored", text: lines.lineAt(offset).trim(), why });
|
|
40422
|
+
}
|
|
40423
|
+
}
|
|
40424
|
+
}
|
|
40425
|
+
return {
|
|
40426
|
+
edits: plan.edits,
|
|
40427
|
+
mentions: mentions.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line)
|
|
40428
|
+
};
|
|
40429
|
+
}
|
|
40430
|
+
function rewritesOf(plan, texts, move) {
|
|
40431
|
+
const byPath = new Map(texts.map((file) => [file.path, file]));
|
|
40432
|
+
const byFile = new Map;
|
|
40433
|
+
for (const edit of plan.edits) {
|
|
40434
|
+
byFile.set(edit.file, [...byFile.get(edit.file) ?? [], edit]);
|
|
40435
|
+
}
|
|
40436
|
+
if (move !== null && !byFile.has(move.from)) {
|
|
40437
|
+
byFile.set(move.from, []);
|
|
40438
|
+
}
|
|
40439
|
+
const rewrites = [];
|
|
40440
|
+
for (const [path, edits] of byFile) {
|
|
40441
|
+
const file = byPath.get(path);
|
|
40442
|
+
if (file === undefined) {
|
|
40443
|
+
continue;
|
|
40444
|
+
}
|
|
40445
|
+
let after = file.text;
|
|
40446
|
+
let lastStart = Number.POSITIVE_INFINITY;
|
|
40447
|
+
for (const edit of [...edits].sort((a, b) => b.offset - a.offset)) {
|
|
40448
|
+
if (edit.offset + edit.length > lastStart) {
|
|
40449
|
+
throw new Error(`two edits overlap in ${path} at offset ${edit.offset}`);
|
|
40450
|
+
}
|
|
40451
|
+
after = after.slice(0, edit.offset) + edit.replacement + after.slice(edit.offset + edit.length);
|
|
40452
|
+
lastStart = edit.offset;
|
|
40453
|
+
}
|
|
40454
|
+
rewrites.push({
|
|
40455
|
+
path,
|
|
40456
|
+
absolute: file.absolute,
|
|
40457
|
+
before: file.text,
|
|
40458
|
+
after,
|
|
40459
|
+
writeTo: move?.from === path ? move.absoluteTo : file.absolute
|
|
40460
|
+
});
|
|
40461
|
+
}
|
|
40462
|
+
return rewrites.sort((a, b) => a.path.localeCompare(b.path));
|
|
40463
|
+
}
|
|
40464
|
+
function applyRewrites(rewrites) {
|
|
40465
|
+
for (const rewrite of rewrites) {
|
|
40466
|
+
const now = readFileSync18(rewrite.absolute, "utf8");
|
|
40467
|
+
if (now !== rewrite.before) {
|
|
40468
|
+
throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
|
|
40469
|
+
}
|
|
40470
|
+
if (rewrite.writeTo !== rewrite.absolute && existsSync17(rewrite.writeTo)) {
|
|
40471
|
+
throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
|
|
40472
|
+
}
|
|
40473
|
+
}
|
|
40474
|
+
const done = [];
|
|
40475
|
+
try {
|
|
40476
|
+
for (const rewrite of rewrites) {
|
|
40477
|
+
mkdirSync10(dirname14(rewrite.writeTo), { recursive: true });
|
|
40478
|
+
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
40479
|
+
writeFileSync12(staging, rewrite.after, "utf8");
|
|
40480
|
+
renameSync(staging, rewrite.writeTo);
|
|
40481
|
+
let movedUid = false;
|
|
40482
|
+
if (rewrite.writeTo !== rewrite.absolute) {
|
|
40483
|
+
unlinkSync2(rewrite.absolute);
|
|
40484
|
+
if (existsSync17(`${rewrite.absolute}.uid`)) {
|
|
40485
|
+
renameSync(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
|
|
40486
|
+
movedUid = true;
|
|
40487
|
+
}
|
|
40488
|
+
}
|
|
40489
|
+
done.push({ rewrite, movedUid });
|
|
40490
|
+
}
|
|
40491
|
+
} catch (error) {
|
|
40492
|
+
for (const { rewrite, movedUid } of done.reverse()) {
|
|
40493
|
+
try {
|
|
40494
|
+
writeFileSync12(rewrite.absolute, rewrite.before, "utf8");
|
|
40495
|
+
if (rewrite.writeTo !== rewrite.absolute) {
|
|
40496
|
+
unlinkSync2(rewrite.writeTo);
|
|
40497
|
+
if (movedUid) {
|
|
40498
|
+
renameSync(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
|
|
40499
|
+
}
|
|
40500
|
+
}
|
|
40501
|
+
} catch {}
|
|
40502
|
+
}
|
|
40503
|
+
throw error;
|
|
40504
|
+
}
|
|
40505
|
+
}
|
|
40506
|
+
var DOC_LINK_KINDS, DOC_KIND;
|
|
40507
|
+
var init_rename = __esm(() => {
|
|
40508
|
+
init_gdscript_source();
|
|
40509
|
+
DOC_LINK_KINDS = new Set([
|
|
40510
|
+
"method",
|
|
40511
|
+
"member",
|
|
40512
|
+
"signal",
|
|
40513
|
+
"constant",
|
|
40514
|
+
"enum",
|
|
40515
|
+
"annotation",
|
|
40516
|
+
"theme_item"
|
|
40517
|
+
]);
|
|
40518
|
+
DOC_KIND = {
|
|
40519
|
+
func: "method",
|
|
40520
|
+
var: "member",
|
|
40521
|
+
signal: "signal",
|
|
40522
|
+
const: "constant",
|
|
40523
|
+
enumValue: "constant",
|
|
40524
|
+
enum: "enum",
|
|
40525
|
+
class: null
|
|
40526
|
+
};
|
|
40527
|
+
});
|
|
40528
|
+
|
|
40529
|
+
// src/rename-symbol.ts
|
|
40530
|
+
import { existsSync as existsSync18, realpathSync as realpathSync5 } from "node:fs";
|
|
40531
|
+
import { basename as basename5, dirname as dirname15, join as join21, relative as relative4, resolve as resolve6 } from "node:path";
|
|
40532
|
+
function resourcePathOf(projectPath, absolute) {
|
|
40533
|
+
const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
40534
|
+
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
40535
|
+
return null;
|
|
40536
|
+
}
|
|
40537
|
+
return `res://${inside.replace(/\\/g, "/")}`;
|
|
40538
|
+
}
|
|
40539
|
+
function onDiskSpelling(path) {
|
|
40540
|
+
const missing = [];
|
|
40541
|
+
let at = resolve6(path);
|
|
40542
|
+
for (;; ) {
|
|
40543
|
+
try {
|
|
40544
|
+
return join21(realpathSync5.native(at), ...missing.reverse());
|
|
40545
|
+
} catch {
|
|
40546
|
+
const parent = dirname15(at);
|
|
40547
|
+
if (parent === at) {
|
|
40548
|
+
return resolve6(path);
|
|
40549
|
+
}
|
|
40550
|
+
missing.push(basename5(at));
|
|
40551
|
+
at = parent;
|
|
40552
|
+
}
|
|
40553
|
+
}
|
|
40554
|
+
}
|
|
40555
|
+
function usedAsIdentifier(scripts, name, most) {
|
|
40556
|
+
const found = [];
|
|
40557
|
+
for (const script of scripts.values()) {
|
|
40558
|
+
for (const occurrence of script.occurrences(name)) {
|
|
40559
|
+
if (occurrence.kind === "code" && !occurrence.afterDot) {
|
|
40560
|
+
found.push(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
|
|
40561
|
+
if (found.length >= most) {
|
|
40562
|
+
return found;
|
|
40563
|
+
}
|
|
40564
|
+
}
|
|
40565
|
+
}
|
|
40566
|
+
}
|
|
40567
|
+
return found;
|
|
40568
|
+
}
|
|
40569
|
+
function autoloadNames(texts) {
|
|
40570
|
+
const settings = texts.find((file) => file.path === "res://project.godot");
|
|
40571
|
+
return settings === undefined ? [] : Object.keys(parseProjectGodot(settings.text)["autoload"] ?? {});
|
|
40572
|
+
}
|
|
40573
|
+
async function renameSymbol(request, services) {
|
|
40574
|
+
const { projectPath, symbol, newName } = request;
|
|
40575
|
+
if (!isIdentifier(newName)) {
|
|
40576
|
+
return {
|
|
40577
|
+
ok: false,
|
|
40578
|
+
reason: `${newName} is not a name GDScript accepts: a letter or underscore, then letters, digits and underscores, and not a keyword.`
|
|
40579
|
+
};
|
|
40580
|
+
}
|
|
40581
|
+
if (newName === symbol) {
|
|
40582
|
+
return { ok: false, reason: `${symbol} is already called ${newName}; nothing to rename.` };
|
|
40583
|
+
}
|
|
40584
|
+
const declaringPath = resourcePathOf(projectPath, request.scriptPath);
|
|
40585
|
+
if (declaringPath === null) {
|
|
40586
|
+
return { ok: false, reason: `${request.scriptPath} is not inside the project.` };
|
|
40587
|
+
}
|
|
40588
|
+
let moveTo = null;
|
|
40589
|
+
if (request.newScriptPath !== null) {
|
|
40590
|
+
moveTo = resourcePathOf(projectPath, request.newScriptPath);
|
|
40591
|
+
if (moveTo === null || !moveTo.endsWith(".gd")) {
|
|
40592
|
+
return {
|
|
40593
|
+
ok: false,
|
|
40594
|
+
reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
|
|
40595
|
+
};
|
|
40596
|
+
}
|
|
40597
|
+
if (existsSync18(request.newScriptPath)) {
|
|
40598
|
+
return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
|
|
40599
|
+
}
|
|
40600
|
+
}
|
|
40601
|
+
const texts = projectTexts(projectPath);
|
|
40602
|
+
const scripts = scriptsOf(texts);
|
|
40603
|
+
const declaring = scripts.get(declaringPath);
|
|
40604
|
+
if (declaring === undefined) {
|
|
40605
|
+
return {
|
|
40606
|
+
ok: false,
|
|
40607
|
+
reason: `${declaringPath} is not among the scripts the engine sees: it is under a directory holding a .gdignore, or one spelled with a dot.`
|
|
40608
|
+
};
|
|
40609
|
+
}
|
|
40610
|
+
const declared = declaredIn(scripts);
|
|
40611
|
+
const isClass = declaring.shape.className?.name === symbol;
|
|
40612
|
+
let plan;
|
|
40613
|
+
let what;
|
|
40614
|
+
let resolvedBy;
|
|
40615
|
+
let overrides = [];
|
|
40616
|
+
let declaringFiles = [declaringPath];
|
|
40617
|
+
if (isClass) {
|
|
40618
|
+
what = "class";
|
|
40619
|
+
resolvedBy = "the files";
|
|
40620
|
+
const conflicts = [];
|
|
40621
|
+
const holder = declared.get(newName);
|
|
40622
|
+
if (holder !== undefined) {
|
|
40623
|
+
conflicts.push(`${newName} is already the class_name of ${holder}`);
|
|
40624
|
+
}
|
|
40625
|
+
if (autoloadNames(texts).includes(newName)) {
|
|
40626
|
+
conflicts.push(`${newName} is already an autoload in project.godot`);
|
|
40627
|
+
}
|
|
40628
|
+
for (const place of usedAsIdentifier(scripts, newName, 10)) {
|
|
40629
|
+
conflicts.push(`${newName} is already an identifier at ${place}`);
|
|
40630
|
+
}
|
|
40631
|
+
const taken = (await services.namesTaken([newName], null))[newName];
|
|
40632
|
+
if (taken !== undefined && taken.as.length > 0) {
|
|
40633
|
+
conflicts.push(`${newName} is ${taken.as.join(" and ")}`);
|
|
40634
|
+
}
|
|
40635
|
+
if (conflicts.length > 0) {
|
|
40636
|
+
return {
|
|
40637
|
+
ok: false,
|
|
40638
|
+
reason: `${symbol} cannot be renamed to ${newName}: the name is taken.`,
|
|
40639
|
+
conflicts
|
|
40640
|
+
};
|
|
40641
|
+
}
|
|
40642
|
+
plan = planClassRename({ texts, scripts, declaring, oldName: symbol, newName });
|
|
40643
|
+
} else {
|
|
40644
|
+
const declaration = declaring.shape.body.declarations.find((one) => one.name === symbol);
|
|
40645
|
+
if (declaration === undefined) {
|
|
40646
|
+
const names = [
|
|
40647
|
+
...declaring.shape.className === null ? [] : [declaring.shape.className.name],
|
|
40648
|
+
...declaring.shape.body.declarations.map((one) => one.name)
|
|
40649
|
+
];
|
|
40650
|
+
return {
|
|
40651
|
+
ok: false,
|
|
40652
|
+
reason: `${declaringPath} declares no ${symbol} at its top level. What it declares there: ${names.join(", ") || "nothing"}. A local variable or a member of an inner class is not renamed by this call.`
|
|
40653
|
+
};
|
|
40654
|
+
}
|
|
40655
|
+
const kind = declaration.kind;
|
|
40656
|
+
what = WHAT[kind];
|
|
40657
|
+
resolvedBy = "the editor's language server";
|
|
40658
|
+
const graph = new ClassGraph(scripts, declared);
|
|
40659
|
+
const own = `${declaringPath}#`;
|
|
40660
|
+
let root = own;
|
|
40661
|
+
if (kind === "func") {
|
|
40662
|
+
for (const ancestor of graph.ancestors(own)) {
|
|
40663
|
+
if (graph.declaration(ancestor, symbol)?.kind === "func") {
|
|
40664
|
+
root = ancestor;
|
|
40665
|
+
}
|
|
40666
|
+
}
|
|
40667
|
+
}
|
|
40668
|
+
const native = graph.nativeBase(root);
|
|
40669
|
+
const taken = await services.namesTaken([symbol, newName], native);
|
|
40670
|
+
const engineOwned = taken[symbol]?.declaredBy ?? null;
|
|
40671
|
+
if (engineOwned !== null) {
|
|
40672
|
+
return {
|
|
40673
|
+
ok: false,
|
|
40674
|
+
reason: `${symbol} is declared by the engine's ${engineOwned}, which ${root === own ? declaringPath : fileOf(root)} inherits from, so the script ${kind === "func" ? "overrides" : "shadows"} an engine name rather than declaring its own, and the engine calls it by that name.`
|
|
40675
|
+
};
|
|
40676
|
+
}
|
|
40677
|
+
const conflicts = [];
|
|
40678
|
+
const newOwner = taken[newName]?.declaredBy ?? null;
|
|
40679
|
+
if (newOwner !== null) {
|
|
40680
|
+
conflicts.push(`${newName} is declared by the engine's ${newOwner}, which this class inherits from`);
|
|
40681
|
+
}
|
|
40682
|
+
for (const id of new Set([root, ...graph.ancestors(root), ...graph.descendants(root)])) {
|
|
40683
|
+
if (graph.declaration(id, newName) !== undefined) {
|
|
40684
|
+
conflicts.push(`${newName} is already declared in ${id.endsWith("#") ? fileOf(id) : `${fileOf(id)}, class ${id.slice(id.lastIndexOf("#") + 1)}`}`);
|
|
40685
|
+
}
|
|
40686
|
+
}
|
|
40687
|
+
if (conflicts.length > 0) {
|
|
40688
|
+
return {
|
|
40689
|
+
ok: false,
|
|
40690
|
+
reason: `${symbol} cannot be renamed to ${newName}: the name is taken.`,
|
|
40691
|
+
conflicts
|
|
40692
|
+
};
|
|
40693
|
+
}
|
|
40694
|
+
const family = [];
|
|
40695
|
+
if (kind === "func") {
|
|
40696
|
+
for (const id of graph.descendants(root)) {
|
|
40697
|
+
const found = graph.declaration(id, symbol);
|
|
40698
|
+
if (found?.kind === "func") {
|
|
40699
|
+
family.push({ file: fileOf(id), offset: found.offset });
|
|
40700
|
+
}
|
|
40701
|
+
}
|
|
40702
|
+
} else {
|
|
40703
|
+
family.push({ file: declaringPath, offset: declaration.offset });
|
|
40704
|
+
}
|
|
40705
|
+
overrides = family.map((one) => one.file).filter((file) => file !== fileOf(root));
|
|
40706
|
+
declaringFiles = family.map((one) => one.file);
|
|
40707
|
+
const resolved = await resolveMember(projectPath, texts, scripts, family, symbol, services);
|
|
40708
|
+
if (resolved === null) {
|
|
40709
|
+
const first = family[0];
|
|
40710
|
+
const script = first === undefined ? undefined : scripts.get(first.file);
|
|
40711
|
+
const line = first === undefined || script === undefined ? 0 : script.lines.positionOf(first.offset).line + 1;
|
|
40712
|
+
return {
|
|
40713
|
+
ok: false,
|
|
40714
|
+
reason: `The language server resolved nothing at ${first?.file ?? declaringPath}:${line}, where ${symbol} is declared, not even the declaration, so it is not analysing that script and its answer would rename nothing.`,
|
|
40715
|
+
advice: [
|
|
40716
|
+
"script_diagnostics on that script says whether the editor can parse it",
|
|
40717
|
+
"editor_status says whether the editor connected is this project's"
|
|
40718
|
+
]
|
|
40719
|
+
};
|
|
40720
|
+
}
|
|
40721
|
+
plan = planMemberRename({
|
|
40722
|
+
texts,
|
|
40723
|
+
scripts,
|
|
40724
|
+
graph,
|
|
40725
|
+
root,
|
|
40726
|
+
family,
|
|
40727
|
+
kind,
|
|
40728
|
+
oldName: symbol,
|
|
40729
|
+
newName,
|
|
40730
|
+
resolved
|
|
40731
|
+
});
|
|
40732
|
+
}
|
|
40733
|
+
if (moveTo !== null) {
|
|
40734
|
+
plan = planMove({
|
|
40735
|
+
texts,
|
|
40736
|
+
scripts,
|
|
40737
|
+
from: declaringPath,
|
|
40738
|
+
to: moveTo,
|
|
40739
|
+
exists: (path) => existsSync18(`${projectPath}/${path.slice("res://".length)}`)
|
|
40740
|
+
}, plan);
|
|
40741
|
+
}
|
|
40742
|
+
plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
|
|
40743
|
+
const rewrites = rewritesOf(plan, texts, moveTo === null || request.newScriptPath === null ? null : { from: declaringPath, to: moveTo, absoluteTo: request.newScriptPath });
|
|
40744
|
+
const graph = new ClassGraph(scripts, declared);
|
|
40745
|
+
const rank = (path) => (declaringFiles.includes(path) ? 0 : 1000) + graph.ancestors(`${path}#`).length;
|
|
40746
|
+
const scriptsInOrder = rewrites.map((rewrite) => rewrite.path).filter((path) => path.endsWith(".gd")).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b)).map((path) => path === declaringPath && moveTo !== null ? moveTo : path);
|
|
40747
|
+
return {
|
|
40748
|
+
ok: true,
|
|
40749
|
+
isClass,
|
|
40750
|
+
moved: moveTo === null ? null : { from: declaringPath, to: moveTo },
|
|
40751
|
+
scriptsInOrder,
|
|
40752
|
+
rewrites,
|
|
40753
|
+
report: {
|
|
40754
|
+
renamed: { what, from: symbol, to: newName, declaredIn: declaringPath, resolvedBy },
|
|
40755
|
+
...overrides.length > 0 ? { overridesRenamed: overrides } : {},
|
|
40756
|
+
...moveTo === null ? {} : { moved: { from: declaringPath, to: moveTo } },
|
|
40757
|
+
changed: changedReport(plan.edits, rewrites),
|
|
40758
|
+
leftAlone: plan.mentions,
|
|
40759
|
+
summary: summaryOf(plan.edits, plan.mentions, rewrites)
|
|
40760
|
+
}
|
|
40761
|
+
};
|
|
40762
|
+
}
|
|
40763
|
+
async function resolveMember(projectPath, texts, scripts, family, name, services) {
|
|
40764
|
+
const absoluteOf = new Map(texts.map((file) => [file.path, file.absolute]));
|
|
40765
|
+
const key = (place) => `${place.file}:${place.offset}`;
|
|
40766
|
+
const toResolved = (located) => {
|
|
40767
|
+
const file = resourcePathOf(projectPath, located.file);
|
|
40768
|
+
const offset = file === null ? null : scripts.get(file)?.lines.offsetOf(located);
|
|
40769
|
+
return file === null || offset === null || offset === undefined ? null : { file, offset };
|
|
40770
|
+
};
|
|
40771
|
+
const ask = async (question, place) => {
|
|
40772
|
+
const script = scripts.get(place.file);
|
|
40773
|
+
const absolute = absoluteOf.get(place.file);
|
|
40774
|
+
if (script === undefined || absolute === undefined) {
|
|
40775
|
+
return [];
|
|
40776
|
+
}
|
|
40777
|
+
const answered = await services[question](absolute, script.text, script.lines.positionOf(place.offset));
|
|
40778
|
+
return answered.map(toResolved).filter((one) => one !== null);
|
|
40779
|
+
};
|
|
40780
|
+
const ours = new Map;
|
|
40781
|
+
const settled = new Set;
|
|
40782
|
+
const root = family[0];
|
|
40783
|
+
if (root === undefined) {
|
|
40784
|
+
return [];
|
|
40785
|
+
}
|
|
40786
|
+
const seeded = await ask("references", root);
|
|
40787
|
+
if (seeded.length === 0) {
|
|
40788
|
+
return null;
|
|
40789
|
+
}
|
|
40790
|
+
for (const place of seeded) {
|
|
40791
|
+
ours.set(key(place), place);
|
|
40792
|
+
}
|
|
40793
|
+
const declarations = new Set(family.map(key));
|
|
40794
|
+
for (const script of scripts.values()) {
|
|
40795
|
+
for (const occurrence of script.occurrences(name)) {
|
|
40796
|
+
const place = { file: script.path, offset: occurrence.offset };
|
|
40797
|
+
if (occurrence.kind !== "code" || ours.has(key(place)) || settled.has(key(place))) {
|
|
40798
|
+
continue;
|
|
40799
|
+
}
|
|
40800
|
+
const declared = await ask("definitions", place);
|
|
40801
|
+
const only = declared.length === 1 ? declared[0] : undefined;
|
|
40802
|
+
if (only === undefined) {
|
|
40803
|
+
settled.add(key(place));
|
|
40804
|
+
continue;
|
|
40805
|
+
}
|
|
40806
|
+
const isOurs = declarations.has(key(only));
|
|
40807
|
+
const same = [place, ...await ask("references", place)];
|
|
40808
|
+
for (const one of same) {
|
|
40809
|
+
if (isOurs) {
|
|
40810
|
+
ours.set(key(one), one);
|
|
40811
|
+
} else {
|
|
40812
|
+
settled.add(key(one));
|
|
40813
|
+
}
|
|
40814
|
+
}
|
|
40815
|
+
}
|
|
40816
|
+
}
|
|
40817
|
+
return [...ours.values()];
|
|
40818
|
+
}
|
|
40819
|
+
function changedReport(edits, rewrites) {
|
|
40820
|
+
return rewrites.map((rewrite) => {
|
|
40821
|
+
const lines = rewrite.after.split(`
|
|
40822
|
+
`);
|
|
40823
|
+
const byLine = new Map;
|
|
40824
|
+
const starts = [0];
|
|
40825
|
+
for (let at = rewrite.before.indexOf(`
|
|
40826
|
+
`);at !== -1; at = rewrite.before.indexOf(`
|
|
40827
|
+
`, at + 1)) {
|
|
40828
|
+
starts.push(at + 1);
|
|
40829
|
+
}
|
|
40830
|
+
let count = 0;
|
|
40831
|
+
for (const edit of edits) {
|
|
40832
|
+
if (edit.file !== rewrite.path) {
|
|
40833
|
+
continue;
|
|
40834
|
+
}
|
|
40835
|
+
count += 1;
|
|
40836
|
+
let line = 0;
|
|
40837
|
+
while (line + 1 < starts.length && (starts[line + 1] ?? 0) <= edit.offset) {
|
|
40838
|
+
line += 1;
|
|
40839
|
+
}
|
|
40840
|
+
byLine.set(line, (byLine.get(line) ?? new Set).add(edit.kind));
|
|
40841
|
+
}
|
|
40842
|
+
return {
|
|
40843
|
+
file: rewrite.path,
|
|
40844
|
+
edits: count,
|
|
40845
|
+
lines: [...byLine.entries()].sort(([a], [b]) => a - b).map(([line, kinds]) => ({
|
|
40846
|
+
line: line + 1,
|
|
40847
|
+
kinds: [...kinds],
|
|
40848
|
+
text: (lines[line] ?? "").replace(/\r$/, "").trim()
|
|
40849
|
+
}))
|
|
40850
|
+
};
|
|
40851
|
+
});
|
|
40852
|
+
}
|
|
40853
|
+
function summaryOf(edits, mentions, rewrites) {
|
|
40854
|
+
const count = (values) => {
|
|
40855
|
+
const counted = {};
|
|
40856
|
+
for (const value of values) {
|
|
40857
|
+
counted[value] = (counted[value] ?? 0) + 1;
|
|
40858
|
+
}
|
|
40859
|
+
return counted;
|
|
40860
|
+
};
|
|
40861
|
+
return {
|
|
40862
|
+
filesChanged: rewrites.length,
|
|
40863
|
+
edits: edits.length,
|
|
40864
|
+
editsByKind: count(edits.map((edit) => edit.kind)),
|
|
40865
|
+
leftAlone: mentions.length,
|
|
40866
|
+
leftAloneByKind: count(mentions.map((mention) => mention.kind))
|
|
40867
|
+
};
|
|
40868
|
+
}
|
|
40869
|
+
var WHAT;
|
|
40870
|
+
var init_rename_symbol = __esm(() => {
|
|
40871
|
+
init_gdscript_source();
|
|
40872
|
+
init_project_scan();
|
|
40873
|
+
init_rename();
|
|
40874
|
+
init_resources();
|
|
40875
|
+
WHAT = {
|
|
40876
|
+
func: "method",
|
|
40877
|
+
var: "variable",
|
|
40878
|
+
const: "constant",
|
|
40879
|
+
signal: "signal",
|
|
40880
|
+
enum: "enum",
|
|
40881
|
+
class: "inner class",
|
|
40882
|
+
enumValue: "enum value"
|
|
40883
|
+
};
|
|
39283
40884
|
});
|
|
39284
40885
|
|
|
39285
40886
|
// src/restart-note.ts
|
|
39286
|
-
import { mkdirSync as
|
|
39287
|
-
import { dirname as
|
|
40887
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync13 } from "node:fs";
|
|
40888
|
+
import { dirname as dirname16, join as join22 } from "node:path";
|
|
39288
40889
|
function restartNotePath(projectPath) {
|
|
39289
|
-
return
|
|
40890
|
+
return join22(projectPath, ".godot", "gdharness-restart.json");
|
|
39290
40891
|
}
|
|
39291
40892
|
function noteRestartBegun(note) {
|
|
39292
40893
|
const path = restartNotePath(note.projectPath);
|
|
39293
40894
|
try {
|
|
39294
|
-
|
|
39295
|
-
|
|
40895
|
+
mkdirSync11(dirname16(path), { recursive: true });
|
|
40896
|
+
writeFileSync13(path, `${JSON.stringify(note, null, 2)}
|
|
39296
40897
|
`, "utf8");
|
|
39297
40898
|
} catch (error) {
|
|
39298
40899
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -39300,7 +40901,7 @@ function noteRestartBegun(note) {
|
|
|
39300
40901
|
}
|
|
39301
40902
|
function restartOwed(projectPath) {
|
|
39302
40903
|
try {
|
|
39303
|
-
const parsed = JSON.parse(
|
|
40904
|
+
const parsed = JSON.parse(readFileSync19(restartNotePath(projectPath), "utf8"));
|
|
39304
40905
|
if (typeof parsed !== "object" || parsed === null) {
|
|
39305
40906
|
return null;
|
|
39306
40907
|
}
|
|
@@ -39328,62 +40929,62 @@ var init_restart_note = __esm(() => {
|
|
|
39328
40929
|
import { execFile as execFile4 } from "node:child_process";
|
|
39329
40930
|
import { createHash as createHash4 } from "node:crypto";
|
|
39330
40931
|
import {
|
|
39331
|
-
mkdirSync as
|
|
40932
|
+
mkdirSync as mkdirSync12,
|
|
39332
40933
|
openSync as openSync2,
|
|
39333
40934
|
readdirSync as readdirSync8,
|
|
39334
|
-
readFileSync as
|
|
39335
|
-
realpathSync as
|
|
40935
|
+
readFileSync as readFileSync20,
|
|
40936
|
+
realpathSync as realpathSync6,
|
|
39336
40937
|
rmSync as rmSync10,
|
|
39337
40938
|
statSync as statSync8,
|
|
39338
|
-
writeFileSync as
|
|
40939
|
+
writeFileSync as writeFileSync14
|
|
39339
40940
|
} from "node:fs";
|
|
39340
|
-
import { basename as
|
|
40941
|
+
import { basename as basename6, dirname as dirname17, join as join23, resolve as resolve7 } from "node:path";
|
|
39341
40942
|
import { promisify as promisify4 } from "node:util";
|
|
39342
40943
|
function runsDirectory() {
|
|
39343
|
-
return
|
|
40944
|
+
return join23(runtimeDirectory(), "runs");
|
|
39344
40945
|
}
|
|
39345
40946
|
function realAsFarAsItExists(path) {
|
|
39346
40947
|
const missing = [];
|
|
39347
40948
|
let at = path;
|
|
39348
40949
|
for (;; ) {
|
|
39349
40950
|
try {
|
|
39350
|
-
return
|
|
40951
|
+
return join23(realpathSync6.native(at), ...missing);
|
|
39351
40952
|
} catch {
|
|
39352
|
-
const parent =
|
|
40953
|
+
const parent = dirname17(at);
|
|
39353
40954
|
if (parent === at) {
|
|
39354
40955
|
return path;
|
|
39355
40956
|
}
|
|
39356
|
-
missing.unshift(
|
|
40957
|
+
missing.unshift(basename6(at));
|
|
39357
40958
|
at = parent;
|
|
39358
40959
|
}
|
|
39359
40960
|
}
|
|
39360
40961
|
}
|
|
39361
40962
|
function projectKey(projectPath) {
|
|
39362
|
-
const real = realAsFarAsItExists(
|
|
40963
|
+
const real = realAsFarAsItExists(resolve7(projectPath)).replace(/[\\/]+$/, "");
|
|
39363
40964
|
const folded = process.platform === "win32" || process.platform === "darwin" ? real.toLowerCase() : real;
|
|
39364
40965
|
return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
39365
40966
|
}
|
|
39366
40967
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
39367
|
-
return
|
|
40968
|
+
return join23(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
39368
40969
|
}
|
|
39369
40970
|
function openTranscript(startedAt) {
|
|
39370
|
-
|
|
39371
|
-
const path =
|
|
40971
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
40972
|
+
const path = join23(runsDirectory(), `run-${startedAt}.log`);
|
|
39372
40973
|
return { path, fd: openSync2(path, "a") };
|
|
39373
40974
|
}
|
|
39374
40975
|
function noteIsAbout(note, playing) {
|
|
39375
40976
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
39376
40977
|
}
|
|
39377
40978
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
39378
|
-
return
|
|
40979
|
+
return join23(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
39379
40980
|
}
|
|
39380
40981
|
function writeEditorRunNote(note) {
|
|
39381
|
-
|
|
39382
|
-
|
|
40982
|
+
mkdirSync12(runsDirectory(), { recursive: true });
|
|
40983
|
+
writeFileSync14(editorNotePath(note.projectPath), JSON.stringify(note, null, 2), "utf8");
|
|
39383
40984
|
}
|
|
39384
40985
|
function readEditorRunNote(projectPath) {
|
|
39385
40986
|
for (const directory of runtimeDirectories()) {
|
|
39386
|
-
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(
|
|
40987
|
+
const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join23(directory, "runs", "editor-run.json"));
|
|
39387
40988
|
if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
|
|
39388
40989
|
return note;
|
|
39389
40990
|
}
|
|
@@ -39393,7 +40994,7 @@ function readEditorRunNote(projectPath) {
|
|
|
39393
40994
|
function editorNoteAt(path) {
|
|
39394
40995
|
let parsed;
|
|
39395
40996
|
try {
|
|
39396
|
-
parsed = JSON.parse(
|
|
40997
|
+
parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
39397
40998
|
} catch {
|
|
39398
40999
|
return null;
|
|
39399
41000
|
}
|
|
@@ -39417,7 +41018,7 @@ function readRunRecord(projectPath) {
|
|
|
39417
41018
|
if (own !== null) {
|
|
39418
41019
|
return own;
|
|
39419
41020
|
}
|
|
39420
|
-
const shared = recordAt(
|
|
41021
|
+
const shared = recordAt(join23(directory, "runs", SHARED_NOTE));
|
|
39421
41022
|
if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
|
|
39422
41023
|
return shared;
|
|
39423
41024
|
}
|
|
@@ -39429,13 +41030,13 @@ function everyRunRecord() {
|
|
|
39429
41030
|
for (const directory of runtimeDirectories()) {
|
|
39430
41031
|
let entries;
|
|
39431
41032
|
try {
|
|
39432
|
-
entries = readdirSync8(
|
|
41033
|
+
entries = readdirSync8(join23(directory, "runs"));
|
|
39433
41034
|
} catch {
|
|
39434
41035
|
continue;
|
|
39435
41036
|
}
|
|
39436
41037
|
for (const entry of entries) {
|
|
39437
41038
|
if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
39438
|
-
const record = recordAt(
|
|
41039
|
+
const record = recordAt(join23(directory, "runs", entry));
|
|
39439
41040
|
if (record !== null) {
|
|
39440
41041
|
found.push(record);
|
|
39441
41042
|
}
|
|
@@ -39447,7 +41048,7 @@ function everyRunRecord() {
|
|
|
39447
41048
|
function recordAt(path) {
|
|
39448
41049
|
let parsed;
|
|
39449
41050
|
try {
|
|
39450
|
-
parsed = JSON.parse(
|
|
41051
|
+
parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
39451
41052
|
} catch {
|
|
39452
41053
|
return null;
|
|
39453
41054
|
}
|
|
@@ -39498,7 +41099,7 @@ async function runningAs(pid) {
|
|
|
39498
41099
|
const began = await startedAt(pid, null);
|
|
39499
41100
|
const when = began === null ? {} : { startedAt: began };
|
|
39500
41101
|
if (process.platform === "linux") {
|
|
39501
|
-
const raw =
|
|
41102
|
+
const raw = readFileSync20(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
|
|
39502
41103
|
return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
|
|
39503
41104
|
}
|
|
39504
41105
|
const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
|
|
@@ -39604,7 +41205,7 @@ function whyNotTheRun(record, running, needed) {
|
|
|
39604
41205
|
if (after > SAME_RUN_WINDOW_MS) {
|
|
39605
41206
|
return `that process started ${Math.round(after / 1000)} seconds after the run had`;
|
|
39606
41207
|
}
|
|
39607
|
-
const engine = record.command === undefined ? null :
|
|
41208
|
+
const engine = record.command === undefined ? null : basename6(record.command);
|
|
39608
41209
|
const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
|
|
39609
41210
|
const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
|
|
39610
41211
|
if (wanted !== null && !said.includes(wanted)) {
|
|
@@ -39643,7 +41244,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
|
|
|
39643
41244
|
}
|
|
39644
41245
|
function clearRunRecord(projectPath) {
|
|
39645
41246
|
for (const directory of runtimeDirectories()) {
|
|
39646
|
-
const shared =
|
|
41247
|
+
const shared = join23(directory, "runs", SHARED_NOTE);
|
|
39647
41248
|
const sharedRecord = recordAt(shared);
|
|
39648
41249
|
const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
|
|
39649
41250
|
for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
|
|
@@ -39673,7 +41274,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
39673
41274
|
if (!entry.startsWith("run-") || !entry.endsWith(".log")) {
|
|
39674
41275
|
continue;
|
|
39675
41276
|
}
|
|
39676
|
-
const path =
|
|
41277
|
+
const path = join23(directory, entry);
|
|
39677
41278
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
39678
41279
|
continue;
|
|
39679
41280
|
}
|
|
@@ -39700,17 +41301,17 @@ import {
|
|
|
39700
41301
|
closeSync as closeSync2,
|
|
39701
41302
|
constants,
|
|
39702
41303
|
copyFileSync,
|
|
39703
|
-
existsSync as
|
|
41304
|
+
existsSync as existsSync19,
|
|
39704
41305
|
fstatSync,
|
|
39705
|
-
mkdirSync as
|
|
41306
|
+
mkdirSync as mkdirSync13,
|
|
39706
41307
|
openSync as openSync3,
|
|
39707
41308
|
readdirSync as readdirSync9,
|
|
39708
|
-
readFileSync as
|
|
41309
|
+
readFileSync as readFileSync21,
|
|
39709
41310
|
readSync as readSync2,
|
|
39710
41311
|
rmdirSync as rmdirSync2,
|
|
39711
41312
|
statSync as statSync9
|
|
39712
41313
|
} from "node:fs";
|
|
39713
|
-
import { basename as
|
|
41314
|
+
import { basename as basename7, dirname as dirname18, extname as extname2, isAbsolute as isAbsolute3, join as join24, normalize as normalize2, relative as relative5 } from "node:path";
|
|
39714
41315
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
39715
41316
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
39716
41317
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -39731,16 +41332,16 @@ function captureDestinationRefusal(outputPath, projects) {
|
|
|
39731
41332
|
if (extname2(outputPath).toLowerCase() !== ".png") {
|
|
39732
41333
|
return `outputPath ${outputPath} does not end in .png, which is what a capture is.`;
|
|
39733
41334
|
}
|
|
39734
|
-
const directory = realPathOr(
|
|
39735
|
-
if (!
|
|
39736
|
-
return `outputPath's directory ${
|
|
41335
|
+
const directory = realPathOr(dirname18(outputPath));
|
|
41336
|
+
if (!existsSync19(directory)) {
|
|
41337
|
+
return `outputPath's directory ${dirname18(outputPath)} does not exist; a capture creates no directories.`;
|
|
39737
41338
|
}
|
|
39738
|
-
const where =
|
|
41339
|
+
const where = join24(directory, basename7(outputPath));
|
|
39739
41340
|
const inside = projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
|
|
39740
41341
|
if (inside !== undefined) {
|
|
39741
41342
|
return `outputPath ${outputPath} is inside the project at ${inside}; a capture is saved outside it, so it never becomes part of the game.`;
|
|
39742
41343
|
}
|
|
39743
|
-
if (
|
|
41344
|
+
if (existsSync19(where)) {
|
|
39744
41345
|
return `outputPath ${outputPath} already exists; a capture is not written over a file. Name a new one.`;
|
|
39745
41346
|
}
|
|
39746
41347
|
return null;
|
|
@@ -39768,7 +41369,7 @@ function patienceForFrames(frames, atLeast) {
|
|
|
39768
41369
|
return Math.max(atLeast, frames / SLOWEST_FRAME_RATE * 1000 + atLeast);
|
|
39769
41370
|
}
|
|
39770
41371
|
function projectSpelling(projectPath, absolutePath) {
|
|
39771
|
-
return `res://${
|
|
41372
|
+
return `res://${relative5(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
|
|
39772
41373
|
}
|
|
39773
41374
|
function whichGame(args) {
|
|
39774
41375
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -40266,7 +41867,7 @@ function aboveTheRunner(entry) {
|
|
|
40266
41867
|
}
|
|
40267
41868
|
function elsewhereIn(projectPath, asked) {
|
|
40268
41869
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
40269
|
-
if (wanted === "" ||
|
|
41870
|
+
if (wanted === "" || existsSync19(join24(projectPath, wanted))) {
|
|
40270
41871
|
return "";
|
|
40271
41872
|
}
|
|
40272
41873
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -40280,7 +41881,7 @@ function elsewhereIn(projectPath, asked) {
|
|
|
40280
41881
|
return near.length === 0 ? "" : ` The project does have ${there}.`;
|
|
40281
41882
|
}
|
|
40282
41883
|
function hasMainScene(projectFile) {
|
|
40283
|
-
const scene = parseProjectGodot(
|
|
41884
|
+
const scene = parseProjectGodot(readFileSync21(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
40284
41885
|
return typeof scene === "string" && scene !== "";
|
|
40285
41886
|
}
|
|
40286
41887
|
function runtimeAddonAnnouncesEditors(projectPath) {
|
|
@@ -40288,13 +41889,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
|
|
|
40288
41889
|
return false;
|
|
40289
41890
|
}
|
|
40290
41891
|
try {
|
|
40291
|
-
return
|
|
41892
|
+
return readFileSync21(join24(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
|
|
40292
41893
|
} catch {
|
|
40293
41894
|
return false;
|
|
40294
41895
|
}
|
|
40295
41896
|
}
|
|
40296
41897
|
function editorPlaysHeadless(projectFile) {
|
|
40297
|
-
const runArgs = parseProjectGodot(
|
|
41898
|
+
const runArgs = parseProjectGodot(readFileSync21(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
40298
41899
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
40299
41900
|
}
|
|
40300
41901
|
function camelCased(params) {
|
|
@@ -40313,7 +41914,7 @@ function needsItsOwnProcess(asked) {
|
|
|
40313
41914
|
class GodotServer {
|
|
40314
41915
|
mcp;
|
|
40315
41916
|
locator = new GodotLocator;
|
|
40316
|
-
operationsScript =
|
|
41917
|
+
operationsScript = join24(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
40317
41918
|
godotBridge;
|
|
40318
41919
|
tools = buildToolDefinitions();
|
|
40319
41920
|
activeProcess = null;
|
|
@@ -40616,7 +42217,7 @@ class GodotServer {
|
|
|
40616
42217
|
return checked.response;
|
|
40617
42218
|
}
|
|
40618
42219
|
const named = args["projectPath"];
|
|
40619
|
-
if (typeof named === "string" &&
|
|
42220
|
+
if (typeof named === "string" && existsSync19(join24(named, "project.godot"))) {
|
|
40620
42221
|
this.lastProjectPath = named;
|
|
40621
42222
|
}
|
|
40622
42223
|
this.updates.refresh();
|
|
@@ -40809,6 +42410,9 @@ class GodotServer {
|
|
|
40809
42410
|
return { ok: true, op };
|
|
40810
42411
|
}
|
|
40811
42412
|
async dispatch(tool, op, args) {
|
|
42413
|
+
if (tool !== "editor_status" && this.godotBridge.isArriving()) {
|
|
42414
|
+
await this.waitForBridge(() => !this.godotBridge.isArriving(), Date.now() + GREETING_WAIT_MS);
|
|
42415
|
+
}
|
|
40812
42416
|
if (ENGINE_PASSES[tool]?.[op] !== undefined) {
|
|
40813
42417
|
return op === "reimport" ? await this.handleReimport(args) : await this.handleRefreshUids(args);
|
|
40814
42418
|
}
|
|
@@ -40909,6 +42513,8 @@ class GodotServer {
|
|
|
40909
42513
|
default:
|
|
40910
42514
|
return await bridge("connect_animation_states");
|
|
40911
42515
|
}
|
|
42516
|
+
case "script_edit":
|
|
42517
|
+
return await this.handleRenameSymbol(args);
|
|
40912
42518
|
case "script_info":
|
|
40913
42519
|
switch (op) {
|
|
40914
42520
|
case "symbols":
|
|
@@ -41084,8 +42690,8 @@ class GodotServer {
|
|
|
41084
42690
|
if (path === undefined) {
|
|
41085
42691
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
41086
42692
|
}
|
|
41087
|
-
const file =
|
|
41088
|
-
if (!
|
|
42693
|
+
const file = join24(path, "project.godot");
|
|
42694
|
+
if (!existsSync19(file)) {
|
|
41089
42695
|
return {
|
|
41090
42696
|
ok: false,
|
|
41091
42697
|
response: this.createErrorResponse(`Not a Godot project: ${path}`, [
|
|
@@ -41525,8 +43131,39 @@ class GodotServer {
|
|
|
41525
43131
|
}
|
|
41526
43132
|
const viaEditor = this.editorHasOpen(projectPath);
|
|
41527
43133
|
const extra = {};
|
|
43134
|
+
const statusAsk = resourcePath === undefined ? { includeUpToDate: true } : { resourcePath };
|
|
41528
43135
|
if (viaEditor) {
|
|
41529
|
-
const
|
|
43136
|
+
const scan = await this.handleRescanFilesystem({ projectPath, timeoutMs });
|
|
43137
|
+
if (scan.isError === true) {
|
|
43138
|
+
return { ok: false, response: scan };
|
|
43139
|
+
}
|
|
43140
|
+
const scanned = asParams(JSON.parse(scan.content[0]?.text ?? "{}"));
|
|
43141
|
+
if (scanned["stillWorking"] === true) {
|
|
43142
|
+
return {
|
|
43143
|
+
ok: true,
|
|
43144
|
+
payload: {
|
|
43145
|
+
via: "editor",
|
|
43146
|
+
stillImporting: true,
|
|
43147
|
+
asked: targets,
|
|
43148
|
+
...sourceless,
|
|
43149
|
+
note: `The editor's scan of the project had not finished after ${timeoutMs} ms, and the reimport waits for it. project_import status says what it has done so far; pass a longer timeoutMs to wait it out.`
|
|
43150
|
+
}
|
|
43151
|
+
};
|
|
43152
|
+
}
|
|
43153
|
+
let remaining = targets;
|
|
43154
|
+
if (!force) {
|
|
43155
|
+
const between = await this.operation("get_import_status", statusAsk, projectPath);
|
|
43156
|
+
if (!between.ok) {
|
|
43157
|
+
return { ok: false, response: this.answer(between) };
|
|
43158
|
+
}
|
|
43159
|
+
const fresh = new Map(importStatuses(between.payload).map((one) => [one.path, one.status]));
|
|
43160
|
+
const caughtUp = targets.filter((path) => fresh.get(path) === "up_to_date");
|
|
43161
|
+
remaining = targets.filter((path) => !caughtUp.includes(path));
|
|
43162
|
+
if (caughtUp.length > 0) {
|
|
43163
|
+
extra["importedByTheScan"] = caughtUp;
|
|
43164
|
+
}
|
|
43165
|
+
}
|
|
43166
|
+
const inEditor = remaining.length === 0 ? { ok: true, finished: true } : await this.reimportInTheEditor(remaining, timeoutMs);
|
|
41530
43167
|
if (!inEditor.ok) {
|
|
41531
43168
|
return inEditor;
|
|
41532
43169
|
}
|
|
@@ -41557,17 +43194,22 @@ class GodotServer {
|
|
|
41557
43194
|
extra["extensionNote"] = imported.extensionNote;
|
|
41558
43195
|
}
|
|
41559
43196
|
}
|
|
41560
|
-
const after = await this.operation("get_import_status",
|
|
43197
|
+
const after = await this.operation("get_import_status", statusAsk, projectPath);
|
|
41561
43198
|
if (!after.ok) {
|
|
41562
43199
|
return { ok: false, response: this.answer(after) };
|
|
41563
43200
|
}
|
|
41564
43201
|
const now = new Map(importStatuses(after.payload).map((one) => [one.path, one]));
|
|
41565
43202
|
const reimported = targets.filter((path) => now.get(path)?.status === "up_to_date");
|
|
41566
|
-
const
|
|
41567
|
-
|
|
41568
|
-
status
|
|
41569
|
-
|
|
41570
|
-
|
|
43203
|
+
const groups = new Map;
|
|
43204
|
+
for (const path of targets.filter((one) => now.get(one)?.status !== "up_to_date")) {
|
|
43205
|
+
const status = now.get(path)?.status ?? "unknown";
|
|
43206
|
+
const reason = now.get(path)?.reason;
|
|
43207
|
+
const key = `${status}\x00${reason ?? ""}`;
|
|
43208
|
+
const group = groups.get(key) ?? { status, ...reason === undefined ? {} : { reason }, paths: [] };
|
|
43209
|
+
group.paths.push(path);
|
|
43210
|
+
groups.set(key, group);
|
|
43211
|
+
}
|
|
43212
|
+
const notReimported = [...groups.values()];
|
|
41571
43213
|
return {
|
|
41572
43214
|
ok: true,
|
|
41573
43215
|
payload: {
|
|
@@ -41659,11 +43301,11 @@ class GodotServer {
|
|
|
41659
43301
|
if (!engine.ok) {
|
|
41660
43302
|
return engine.response;
|
|
41661
43303
|
}
|
|
41662
|
-
const application = parseProjectGodot(
|
|
43304
|
+
const application = parseProjectGodot(readFileSync21(project.value.file, "utf8"))["application"] ?? {};
|
|
41663
43305
|
const name = application["config/name"];
|
|
41664
43306
|
const mainScene = application["run/main_scene"];
|
|
41665
43307
|
const info = {
|
|
41666
|
-
name: typeof name === "string" && name !== "" ? name :
|
|
43308
|
+
name: typeof name === "string" && name !== "" ? name : basename7(project.value.path),
|
|
41667
43309
|
path: project.value.path,
|
|
41668
43310
|
mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
|
|
41669
43311
|
godotVersion: await this.godotVersion(engine.value),
|
|
@@ -41715,18 +43357,18 @@ class GodotServer {
|
|
|
41715
43357
|
if (!engine.ok) {
|
|
41716
43358
|
return engine.response;
|
|
41717
43359
|
}
|
|
41718
|
-
const folder =
|
|
43360
|
+
const folder = dirname18(output.absolutePath);
|
|
41719
43361
|
try {
|
|
41720
|
-
|
|
43362
|
+
mkdirSync13(folder, { recursive: true });
|
|
41721
43363
|
} catch (error) {
|
|
41722
|
-
return this.createErrorResponse(`Cannot create ${
|
|
43364
|
+
return this.createErrorResponse(`Cannot create ${dirname18(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
41723
43365
|
}
|
|
41724
43366
|
const debug = readBoolean(args, "debug") ?? false;
|
|
41725
43367
|
const exportLogs = scratchDirectory("export");
|
|
41726
43368
|
const exportArgs = [
|
|
41727
43369
|
"--headless",
|
|
41728
43370
|
"--log-file",
|
|
41729
|
-
|
|
43371
|
+
join24(exportLogs, "engine.log"),
|
|
41730
43372
|
"--path",
|
|
41731
43373
|
project.value.path,
|
|
41732
43374
|
debug ? "--export-debug" : "--export-release",
|
|
@@ -41734,7 +43376,7 @@ class GodotServer {
|
|
|
41734
43376
|
output.absolutePath
|
|
41735
43377
|
];
|
|
41736
43378
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
41737
|
-
const writtenAt = () =>
|
|
43379
|
+
const writtenAt = () => existsSync19(output.absolutePath) ? statSync9(output.absolutePath).mtimeMs : null;
|
|
41738
43380
|
const before = writtenAt();
|
|
41739
43381
|
let ending;
|
|
41740
43382
|
try {
|
|
@@ -41757,7 +43399,7 @@ class GodotServer {
|
|
|
41757
43399
|
return contained.response;
|
|
41758
43400
|
}
|
|
41759
43401
|
const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
|
|
41760
|
-
if (!
|
|
43402
|
+
if (!existsSync19(join24(project.value.path, runner))) {
|
|
41761
43403
|
return this.createErrorResponse(`gdUnit4 is not installed in this project: no ${runner}.`, [
|
|
41762
43404
|
"Install gdUnit4 under addons/gdUnit4, from https://github.com/godot-gdunit-labs/gdUnit4"
|
|
41763
43405
|
]);
|
|
@@ -41824,21 +43466,21 @@ class GodotServer {
|
|
|
41824
43466
|
await untilGone([run.process.pid]);
|
|
41825
43467
|
}
|
|
41826
43468
|
await run.settled;
|
|
41827
|
-
const reportsDir =
|
|
43469
|
+
const reportsDir = join24(project.value.path, ".godot", ...ours.split("/"));
|
|
41828
43470
|
let report = null;
|
|
41829
43471
|
let reportProblem = null;
|
|
41830
43472
|
try {
|
|
41831
|
-
const written =
|
|
43473
|
+
const written = existsSync19(reportsDir) ? readdirSync9(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
41832
43474
|
const newest = written.at(-1);
|
|
41833
43475
|
if (newest !== undefined) {
|
|
41834
|
-
report = parseJUnit(
|
|
43476
|
+
report = parseJUnit(readFileSync21(join24(reportsDir, newest, "results.xml"), "utf8"));
|
|
41835
43477
|
}
|
|
41836
43478
|
} catch (error) {
|
|
41837
43479
|
reportProblem = errorMessage(error);
|
|
41838
43480
|
} finally {
|
|
41839
43481
|
discard(reportsDir, userData);
|
|
41840
43482
|
try {
|
|
41841
|
-
rmdirSync2(
|
|
43483
|
+
rmdirSync2(dirname18(reportsDir));
|
|
41842
43484
|
} catch {}
|
|
41843
43485
|
}
|
|
41844
43486
|
const engineEntries = run.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries.map(aboveTheRunner);
|
|
@@ -42016,11 +43658,11 @@ class GodotServer {
|
|
|
42016
43658
|
const cached = cachedClasses(projectPath);
|
|
42017
43659
|
const contradicted = cached === null ? [] : contradictedDiagnostics(messages, cached, (resourcePath) => {
|
|
42018
43660
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
42019
|
-
if (!contained.ok || !
|
|
43661
|
+
if (!contained.ok || !existsSync19(contained.absolutePath)) {
|
|
42020
43662
|
return null;
|
|
42021
43663
|
}
|
|
42022
43664
|
try {
|
|
42023
|
-
return
|
|
43665
|
+
return readFileSync21(contained.absolutePath, "utf8");
|
|
42024
43666
|
} catch {
|
|
42025
43667
|
return null;
|
|
42026
43668
|
}
|
|
@@ -42076,23 +43718,26 @@ class GodotServer {
|
|
|
42076
43718
|
}
|
|
42077
43719
|
sourceOfClass(projectPath, resourcePath) {
|
|
42078
43720
|
const contained = resolveWithinProject(projectPath, resourcePath);
|
|
42079
|
-
if (!contained.ok || !
|
|
43721
|
+
if (!contained.ok || !existsSync19(contained.absolutePath)) {
|
|
42080
43722
|
return null;
|
|
42081
43723
|
}
|
|
42082
43724
|
try {
|
|
42083
|
-
return
|
|
43725
|
+
return readFileSync21(contained.absolutePath, "utf8");
|
|
42084
43726
|
} catch {
|
|
42085
43727
|
return null;
|
|
42086
43728
|
}
|
|
42087
43729
|
}
|
|
42088
43730
|
async handleLSP(toolName, args) {
|
|
43731
|
+
return handleLSPTool(await this.lsp(), toolName, args);
|
|
43732
|
+
}
|
|
43733
|
+
async lsp() {
|
|
42089
43734
|
const port = this.editorServes("lspPort", "GDHARNESS_LSP_PORT", DEFAULT_LSP_PORT);
|
|
42090
43735
|
if (this.lspClient !== null && this.lspClient.port !== port) {
|
|
42091
43736
|
await this.lspClient.disconnect();
|
|
42092
43737
|
this.lspClient = null;
|
|
42093
43738
|
}
|
|
42094
43739
|
this.lspClient ??= new GodotLSPClient(port);
|
|
42095
|
-
return
|
|
43740
|
+
return this.lspClient;
|
|
42096
43741
|
}
|
|
42097
43742
|
async handleDAP(toolName, args) {
|
|
42098
43743
|
return handleDAPTool(this.dap(), toolName, args);
|
|
@@ -42388,7 +44033,7 @@ class GodotServer {
|
|
|
42388
44033
|
return null;
|
|
42389
44034
|
}
|
|
42390
44035
|
try {
|
|
42391
|
-
return settingKeys(
|
|
44036
|
+
return settingKeys(readFileSync21(join24(projectPath, "project.godot"), "utf8"));
|
|
42392
44037
|
} catch {
|
|
42393
44038
|
return null;
|
|
42394
44039
|
}
|
|
@@ -42402,14 +44047,7 @@ class GodotServer {
|
|
|
42402
44047
|
]);
|
|
42403
44048
|
}
|
|
42404
44049
|
if (!hasSaidWhoItIs(before)) {
|
|
42405
|
-
|
|
42406
|
-
if (!settled) {
|
|
42407
|
-
return this.createErrorResponse(`The editor has connected and has not said who it is within ${GREETING_WAIT_MS / 1000}s, so whether this server or the editor itself restarts it cannot be told.`, [
|
|
42408
|
-
"editor_status says when it has, under greeting while it has not",
|
|
42409
|
-
"Then ask for the restart again"
|
|
42410
|
-
]);
|
|
42411
|
-
}
|
|
42412
|
-
return this.handleRestartEditor(args);
|
|
44050
|
+
return this.createErrorResponse(`The editor has connected and has not said who it is within ${GREETING_WAIT_MS / 1000}s, so whether this server or the editor itself restarts it cannot be told.`, ["editor_status says when it has, under greeting while it has not", "Then ask for the restart again"]);
|
|
42413
44051
|
}
|
|
42414
44052
|
const mine = before.openedByAServer === true && before.projectPath !== undefined;
|
|
42415
44053
|
if (!mine && args["hidden"] === true) {
|
|
@@ -42717,7 +44355,7 @@ class GodotServer {
|
|
|
42717
44355
|
}
|
|
42718
44356
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
42719
44357
|
const budgetMs = budget.ms;
|
|
42720
|
-
if (!
|
|
44358
|
+
if (!existsSync19(join24(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
42721
44359
|
return runtimeVerdict(null, { addon: false, budgetMs, heldAt: null, running: false, withArgs });
|
|
42722
44360
|
}
|
|
42723
44361
|
let asking = null;
|
|
@@ -42968,7 +44606,7 @@ class GodotServer {
|
|
|
42968
44606
|
return 0;
|
|
42969
44607
|
}
|
|
42970
44608
|
try {
|
|
42971
|
-
return reportAlreadyInTranscript(
|
|
44609
|
+
return reportAlreadyInTranscript(readFileSync21(game.transcript), readFileSync21(report));
|
|
42972
44610
|
} catch (error) {
|
|
42973
44611
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
42974
44612
|
return 0;
|
|
@@ -43726,7 +45364,7 @@ class GodotServer {
|
|
|
43726
45364
|
notes.push(endedWithoutACode({
|
|
43727
45365
|
throughEditor: run.throughEditor,
|
|
43728
45366
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
43729
|
-
couldAnnounce: run.projectPath !== null &&
|
|
45367
|
+
couldAnnounce: run.projectPath !== null && existsSync19(join24(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
43730
45368
|
endedHere: typeof run.endedHere === "string",
|
|
43731
45369
|
pickedUp: run.pickedUp === true
|
|
43732
45370
|
}));
|
|
@@ -43973,11 +45611,9 @@ class GodotServer {
|
|
|
43973
45611
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 30000;
|
|
43974
45612
|
const started = Date.now();
|
|
43975
45613
|
const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
|
|
43976
|
-
|
|
43977
|
-
|
|
43978
|
-
|
|
43979
|
-
const atRisk = before === null ? [] : blind.unseen.filter((one) => before.has(one.className));
|
|
43980
|
-
const ghosts = blind.stillHeld ?? [];
|
|
45614
|
+
let before = null;
|
|
45615
|
+
let writtenBefore = null;
|
|
45616
|
+
let blind = { unseen: [] };
|
|
43981
45617
|
let waitedForEditorMs = 0;
|
|
43982
45618
|
let first;
|
|
43983
45619
|
let completedBefore;
|
|
@@ -43993,6 +45629,9 @@ class GodotServer {
|
|
|
43993
45629
|
if (wasBusy) {
|
|
43994
45630
|
waitedForEditorMs += Date.now() - busySince;
|
|
43995
45631
|
}
|
|
45632
|
+
before = projectPath === "" ? null : cachedClasses(projectPath);
|
|
45633
|
+
writtenBefore = projectPath === "" ? null : cacheWrittenAt(projectPath);
|
|
45634
|
+
blind = await this.classesTheEditorCannotSee(args);
|
|
43996
45635
|
first = await this.handleViaBridge("rescan_filesystem", args);
|
|
43997
45636
|
if (first.isError) {
|
|
43998
45637
|
return first;
|
|
@@ -44009,6 +45648,9 @@ class GodotServer {
|
|
|
44009
45648
|
}
|
|
44010
45649
|
await new Promise((settle) => setTimeout(settle, 100));
|
|
44011
45650
|
}
|
|
45651
|
+
const held = before;
|
|
45652
|
+
const atRisk = held === null ? [] : blind.unseen.filter((one) => held.has(one.className));
|
|
45653
|
+
const ghosts = blind.stillHeld ?? [];
|
|
44012
45654
|
let busy = true;
|
|
44013
45655
|
let idleSince = null;
|
|
44014
45656
|
while (Date.now() - started < timeoutMs) {
|
|
@@ -44146,9 +45788,12 @@ class GodotServer {
|
|
|
44146
45788
|
});
|
|
44147
45789
|
}
|
|
44148
45790
|
async reloadDependents(projectPath, classes) {
|
|
45791
|
+
return await this.reloadInEditor(scriptsNaming(projectPath, classes));
|
|
45792
|
+
}
|
|
45793
|
+
async reloadInEditor(scripts) {
|
|
44149
45794
|
const reloaded = [];
|
|
44150
45795
|
const notReloaded = [];
|
|
44151
|
-
for (const { script: scriptPath, tool } of
|
|
45796
|
+
for (const { script: scriptPath, tool } of scripts) {
|
|
44152
45797
|
if (tool) {
|
|
44153
45798
|
notReloaded.push({
|
|
44154
45799
|
scriptPath,
|
|
@@ -44265,6 +45910,159 @@ class GodotServer {
|
|
|
44265
45910
|
});
|
|
44266
45911
|
return this.godotBridge.isConnected() ? await this.alsoSayWhatTheEditorCannotSee(answered, args) : answered;
|
|
44267
45912
|
}
|
|
45913
|
+
async handleRenameSymbol(given) {
|
|
45914
|
+
const project = this.project(given);
|
|
45915
|
+
if (!project.ok) {
|
|
45916
|
+
return project.response;
|
|
45917
|
+
}
|
|
45918
|
+
const contained = this.containProjectFiles(given);
|
|
45919
|
+
if (!contained.ok) {
|
|
45920
|
+
return contained.response;
|
|
45921
|
+
}
|
|
45922
|
+
const args = contained.value;
|
|
45923
|
+
const projectPath = project.value.path;
|
|
45924
|
+
const scriptPath = resolveWithinProject(projectPath, readNonEmptyString(args, "scriptPath") ?? "");
|
|
45925
|
+
if (!scriptPath.ok) {
|
|
45926
|
+
return this.createErrorResponse(scriptPath.reason);
|
|
45927
|
+
}
|
|
45928
|
+
if (!existsSync19(scriptPath.absolutePath)) {
|
|
45929
|
+
return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
|
|
45930
|
+
}
|
|
45931
|
+
const asked = readNonEmptyString(args, "newScriptPath");
|
|
45932
|
+
const moveTo = asked === undefined ? null : resolveWithinProject(projectPath, asked);
|
|
45933
|
+
if (moveTo !== null && !moveTo.ok) {
|
|
45934
|
+
return this.createErrorResponse(moveTo.reason);
|
|
45935
|
+
}
|
|
45936
|
+
const languageServer = {
|
|
45937
|
+
problem: null,
|
|
45938
|
+
initialized: false
|
|
45939
|
+
};
|
|
45940
|
+
const askTheLanguageServer = async (question) => {
|
|
45941
|
+
const client = await this.lsp();
|
|
45942
|
+
try {
|
|
45943
|
+
if (!languageServer.initialized) {
|
|
45944
|
+
await client.initialize(projectPath);
|
|
45945
|
+
languageServer.initialized = true;
|
|
45946
|
+
}
|
|
45947
|
+
return await question(client);
|
|
45948
|
+
} catch (error) {
|
|
45949
|
+
languageServer.problem = normalizeLSPError(error, client.port);
|
|
45950
|
+
throw error;
|
|
45951
|
+
}
|
|
45952
|
+
};
|
|
45953
|
+
const symbol = readNonEmptyString(args, "symbol") ?? "";
|
|
45954
|
+
const newName = readNonEmptyString(args, "newName") ?? "";
|
|
45955
|
+
const outcome = await renameSymbol({
|
|
45956
|
+
projectPath,
|
|
45957
|
+
scriptPath: scriptPath.absolutePath,
|
|
45958
|
+
symbol,
|
|
45959
|
+
newName,
|
|
45960
|
+
newScriptPath: moveTo === null ? null : moveTo.absolutePath
|
|
45961
|
+
}, {
|
|
45962
|
+
namesTaken: async (names, base) => {
|
|
45963
|
+
const asked = await this.operation("names_taken", { names: [...names], base: base ?? "" }, projectPath);
|
|
45964
|
+
if (!asked.ok) {
|
|
45965
|
+
throw new Error(`the engine could not be asked whether ${names.join(" or ")} is taken: ${asked.message}`);
|
|
45966
|
+
}
|
|
45967
|
+
return asParams(asked.payload["taken"]);
|
|
45968
|
+
},
|
|
45969
|
+
references: async (absolute, text, at) => await askTheLanguageServer((client) => client.getReferences(absolute, text, at.line, at.character)),
|
|
45970
|
+
definitions: async (absolute, text, at) => await askTheLanguageServer((client) => client.getDefinitions(absolute, text, at.line, at.character))
|
|
45971
|
+
}).catch((error) => ({ ok: false, reason: errorMessage(error) }));
|
|
45972
|
+
if (!outcome.ok) {
|
|
45973
|
+
if (languageServer.problem !== null) {
|
|
45974
|
+
return this.createErrorResponse(`Renaming a member needs the editor's language server, because which uses of the name are this class's depends on the types only the engine's analyser knows. ${languageServer.problem}`, [
|
|
45975
|
+
"editor_launch opens the editor, whose language server this uses",
|
|
45976
|
+
"A class_name is renamed from the files and needs no editor"
|
|
45977
|
+
]);
|
|
45978
|
+
}
|
|
45979
|
+
const said = outcome.conflicts === undefined ? "" : ` ${outcome.conflicts.join("; ")}.`;
|
|
45980
|
+
return this.createErrorResponse(`${outcome.reason}${said}`, outcome.advice ?? []);
|
|
45981
|
+
}
|
|
45982
|
+
if (readBoolean(args, "preview") === true) {
|
|
45983
|
+
return this.jsonTextResponse({ preview: true, ...outcome.report });
|
|
45984
|
+
}
|
|
45985
|
+
try {
|
|
45986
|
+
applyRewrites(outcome.rewrites);
|
|
45987
|
+
} catch (error) {
|
|
45988
|
+
return this.createErrorResponse(`Nothing was renamed: ${errorMessage(error)}`);
|
|
45989
|
+
}
|
|
45990
|
+
const settled = this.godotBridge.isConnected() ? await this.settleRenameInEditor(projectPath, outcome.scriptsInOrder, {
|
|
45991
|
+
gone: outcome.moved === null ? [] : [outcome.moved.from],
|
|
45992
|
+
oldClass: outcome.isClass ? symbol : null,
|
|
45993
|
+
newClass: outcome.isClass ? newName : null
|
|
45994
|
+
}) : await this.settleRenameOnDisk(projectPath, outcome.scriptsInOrder, {
|
|
45995
|
+
rebuildCache: outcome.isClass || outcome.moved !== null,
|
|
45996
|
+
moved: outcome.moved !== null
|
|
45997
|
+
});
|
|
45998
|
+
return this.jsonTextResponse({ ok: settled.ok, ...outcome.report, ...settled.report });
|
|
45999
|
+
}
|
|
46000
|
+
async settleRenameInEditor(projectPath, changed, names) {
|
|
46001
|
+
const { gone, oldClass, newClass } = names;
|
|
46002
|
+
const scanned = asParams(JSON.parse((await this.handleRescanFilesystem({
|
|
46003
|
+
projectPath,
|
|
46004
|
+
timeoutMs: 60000,
|
|
46005
|
+
updateFiles: [...gone, ...changed]
|
|
46006
|
+
})).content[0]?.text ?? "{}"));
|
|
46007
|
+
const reloadedByTheScan = new Set((readArray(scanned, "dependentsReloaded") ?? []).map(String));
|
|
46008
|
+
const toReload = [];
|
|
46009
|
+
for (const script of changed) {
|
|
46010
|
+
if (!reloadedByTheScan.has(script)) {
|
|
46011
|
+
const source = this.sourceOfClass(projectPath, script);
|
|
46012
|
+
toReload.push({ script, tool: source !== null && isToolScript(source) });
|
|
46013
|
+
}
|
|
46014
|
+
}
|
|
46015
|
+
const done = await this.reloadInEditor(toReload);
|
|
46016
|
+
const notReloaded = done.notReloaded;
|
|
46017
|
+
const reloaded = [...reloadedByTheScan, ...done.reloaded].sort();
|
|
46018
|
+
const checked = oldClass === null ? null : await this.classesTheEditorCannotSee({ projectPath });
|
|
46019
|
+
const stillHeld = oldClass !== null && (checked?.stillHeld?.includes(oldClass) === true || (readArray(scanned, "cacheDropped") ?? []).map(String).includes(oldClass) || (readArray(scanned, "cacheAtMissingPaths") ?? []).map(String).includes(oldClass));
|
|
46020
|
+
const unseen = newClass !== null && checked?.unseen.some((one) => one.className === newClass) === true;
|
|
46021
|
+
const notes = [];
|
|
46022
|
+
if (stillHeld) {
|
|
46023
|
+
notes.push(`The editor still holds ${oldClass} after the scan, so it resolves the old name until it is restarted with editor_launch restart, and writes it back into the class cache on every scan until then.`);
|
|
46024
|
+
}
|
|
46025
|
+
if (unseen) {
|
|
46026
|
+
notes.push(`The editor does not hold ${newClass} after the scan, so every use of it reads as an unknown identifier there: editor_rescan on its own, or editor_launch restart.`);
|
|
46027
|
+
}
|
|
46028
|
+
if (scanned["stillWorking"] === true) {
|
|
46029
|
+
notes.push("The editor was still scanning when the wait ran out, so it may not hold the new names yet.");
|
|
46030
|
+
}
|
|
46031
|
+
return {
|
|
46032
|
+
ok: scanned["stillWorking"] !== true && notReloaded.length === 0 && !stillHeld && !unseen,
|
|
46033
|
+
report: {
|
|
46034
|
+
editor: {
|
|
46035
|
+
reloaded,
|
|
46036
|
+
...notReloaded.length > 0 ? { notReloaded } : {},
|
|
46037
|
+
...stillHeld ? { stillHolds: oldClass } : {},
|
|
46038
|
+
...typeof scanned["note"] === "string" ? { scanNote: scanned["note"] } : {}
|
|
46039
|
+
},
|
|
46040
|
+
...notes.length > 0 ? { note: notes.join(" ") } : {}
|
|
46041
|
+
}
|
|
46042
|
+
};
|
|
46043
|
+
}
|
|
46044
|
+
async settleRenameOnDisk(projectPath, changed, asked) {
|
|
46045
|
+
const report = {};
|
|
46046
|
+
let ok = true;
|
|
46047
|
+
if (asked.moved) {
|
|
46048
|
+
const engine = await this.engine();
|
|
46049
|
+
const imported = engine.ok ? await runImport(engine.value, projectPath) : null;
|
|
46050
|
+
const problem = imported === null ? engine.ok ? "" : engine.response.content.map((block) => block.text).join(" ") : imported.ok ? null : imported.message;
|
|
46051
|
+
report["uidTable"] = problem === null ? "rebuilt by an import pass" : `not rebuilt: ${problem}`;
|
|
46052
|
+
ok &&= problem === null;
|
|
46053
|
+
}
|
|
46054
|
+
if (asked.rebuildCache) {
|
|
46055
|
+
const rebuilt = await this.rebuildClassCache(projectPath);
|
|
46056
|
+
report["classCache"] = rebuilt.ok ? rebuilt.payload : { rebuilt: false, problem: rebuilt.message };
|
|
46057
|
+
ok &&= rebuilt.ok;
|
|
46058
|
+
}
|
|
46059
|
+
if (changed.length > 0) {
|
|
46060
|
+
const compiled = await this.operation("check_scripts", { paths: [...changed] }, projectPath);
|
|
46061
|
+
report["compiled"] = compiled.ok ? { ...compiled.payload, ...engineExtras(compiled) } : { problem: compiled.message };
|
|
46062
|
+
ok &&= compiled.ok && (readArray(compiled.payload, "failed") ?? []).length === 0;
|
|
46063
|
+
}
|
|
46064
|
+
return { ok, report };
|
|
46065
|
+
}
|
|
44268
46066
|
async alsoSayWhatTheEditorCannotSee(answered, args) {
|
|
44269
46067
|
const first = answered.content[0];
|
|
44270
46068
|
if (answered.isError || first?.type !== "text" || typeof first.text !== "string") {
|
|
@@ -44393,7 +46191,7 @@ class GodotServer {
|
|
|
44393
46191
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
44394
46192
|
const run = await this.currentRun();
|
|
44395
46193
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
44396
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !
|
|
46194
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync19(join24(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
44397
46195
|
return null;
|
|
44398
46196
|
}
|
|
44399
46197
|
const already = runtimesAnnounced().running;
|
|
@@ -44477,7 +46275,7 @@ class GodotServer {
|
|
|
44477
46275
|
}
|
|
44478
46276
|
}
|
|
44479
46277
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
44480
|
-
const screenshotPath = screenshotDir ?
|
|
46278
|
+
const screenshotPath = screenshotDir ? join24(screenshotDir, "capture.png") : null;
|
|
44481
46279
|
const staleNote = this.runtimeStaleNote(choice.endpoint);
|
|
44482
46280
|
const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
|
|
44483
46281
|
const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
|
|
@@ -44526,7 +46324,7 @@ ${staleNote}`);
|
|
|
44526
46324
|
|
|
44527
46325
|
${staleNote}`}`
|
|
44528
46326
|
},
|
|
44529
|
-
{ type: "image", data:
|
|
46327
|
+
{ type: "image", data: readFileSync21(screenshotPath).toString("base64"), mimeType: "image/png" }
|
|
44530
46328
|
]
|
|
44531
46329
|
};
|
|
44532
46330
|
} finally {
|
|
@@ -44660,6 +46458,8 @@ var init_server3 = __esm(() => {
|
|
|
44660
46458
|
init_process_children();
|
|
44661
46459
|
init_process_time();
|
|
44662
46460
|
init_project_scan();
|
|
46461
|
+
init_rename();
|
|
46462
|
+
init_rename_symbol();
|
|
44663
46463
|
init_resources();
|
|
44664
46464
|
init_restart_note();
|
|
44665
46465
|
init_run_record();
|
|
@@ -44670,7 +46470,7 @@ var init_server3 = __esm(() => {
|
|
|
44670
46470
|
init_tool_definitions();
|
|
44671
46471
|
init_update_check();
|
|
44672
46472
|
run4 = promisify5(execFile5);
|
|
44673
|
-
__dirname2 =
|
|
46473
|
+
__dirname2 = dirname18(fileURLToPath5(import.meta.url));
|
|
44674
46474
|
CONSOLE_WRAPPER = /_console(\.exe)?$/i;
|
|
44675
46475
|
LONGEST_BOOT_NOTED_MS = 5 * 60000;
|
|
44676
46476
|
VALUE_NAMES = {
|
|
@@ -44694,6 +46494,7 @@ var init_server3 = __esm(() => {
|
|
|
44694
46494
|
"scriptPath",
|
|
44695
46495
|
"resourcePath",
|
|
44696
46496
|
"newPath",
|
|
46497
|
+
"newScriptPath",
|
|
44697
46498
|
"path",
|
|
44698
46499
|
"script"
|
|
44699
46500
|
];
|
|
@@ -44719,8 +46520,8 @@ var init_server3 = __esm(() => {
|
|
|
44719
46520
|
init_bridge_announce();
|
|
44720
46521
|
init_errors();
|
|
44721
46522
|
init_godot_path();
|
|
44722
|
-
import { existsSync as
|
|
44723
|
-
import { join as
|
|
46523
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
46524
|
+
import { join as join25, resolve as resolve8 } from "node:path";
|
|
44724
46525
|
|
|
44725
46526
|
// src/harnesses.ts
|
|
44726
46527
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -45889,9 +47690,16 @@ formatting. \`script_diagnostics\` and \`script_info\` come from the editor's ow
|
|
|
45889
47690
|
\`project_info\`, \`project_dependencies\`, \`project_settings\`, \`project_import\`,
|
|
45890
47691
|
\`project_export\`, \`project_test\`, \`script_edit\`, \`script_info structure\` and
|
|
45891
47692
|
\`editor_classes\` need nothing open: they run a short headless engine and are gone before the
|
|
45892
|
-
answer is printed.
|
|
45893
|
-
|
|
45894
|
-
|
|
47693
|
+
answer is printed. The exception is \`script_edit rename\` of a method, variable, signal or other
|
|
47694
|
+
member, which asks the editor's language server which uses are that class's. \`project_settings
|
|
47695
|
+
get\` is on that list too, reading as it does like a look at a config file: it starts an engine so
|
|
47696
|
+
that a setting nobody wrote down still answers with the default the engine registers for it.
|
|
47697
|
+
|
|
47698
|
+
To rename a class or a member across the project, use \`script_edit rename\` rather than a text
|
|
47699
|
+
replacement: it changes the uses the engine resolves, doc links and what scenes record, and lists
|
|
47700
|
+
every other occurrence of the word under \`leftAlone\`, a \`find_children\` string, a word in a
|
|
47701
|
+
comment or a file under a \`.gdignore\` directory, for you to read. Pass \`preview\` first on a
|
|
47702
|
+
large rename.
|
|
45895
47703
|
|
|
45896
47704
|
Editing a file while a game is running does not change the running game. \`auto_reload\` is an
|
|
45897
47705
|
editor plugin and reloads into the editor's own process; the game is a separate process holding
|
|
@@ -46040,8 +47848,8 @@ class UsageError extends Refusal {
|
|
|
46040
47848
|
}
|
|
46041
47849
|
function projectArgument(at) {
|
|
46042
47850
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
46043
|
-
const projectPath =
|
|
46044
|
-
if (!
|
|
47851
|
+
const projectPath = resolve8(named ?? ".");
|
|
47852
|
+
if (!existsSync20(join25(projectPath, "project.godot"))) {
|
|
46045
47853
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
46046
47854
|
}
|
|
46047
47855
|
return projectPath;
|
|
@@ -46086,7 +47894,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
46086
47894
|
return;
|
|
46087
47895
|
}
|
|
46088
47896
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
46089
|
-
if (!
|
|
47897
|
+
if (!existsSync20(join25(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
46090
47898
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
46091
47899
|
}
|
|
46092
47900
|
}
|
|
@@ -46218,7 +48026,7 @@ async function setup() {
|
|
|
46218
48026
|
console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
|
|
46219
48027
|
}
|
|
46220
48028
|
if (!args.includes("--no-skill")) {
|
|
46221
|
-
const directories = skillDirectories(chosen.harnesses, projectPath,
|
|
48029
|
+
const directories = skillDirectories(chosen.harnesses, projectPath, existsSync20);
|
|
46222
48030
|
for (const written of writeSkill(directories, getLocalVersion())) {
|
|
46223
48031
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
46224
48032
|
}
|
|
@@ -46332,7 +48140,7 @@ async function upgrade() {
|
|
|
46332
48140
|
moved.push(took.from);
|
|
46333
48141
|
}
|
|
46334
48142
|
}
|
|
46335
|
-
for (const written of writeSkill(skillDirectories(already, projectPath,
|
|
48143
|
+
for (const written of writeSkill(skillDirectories(already, projectPath, existsSync20), version)) {
|
|
46336
48144
|
console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
|
|
46337
48145
|
}
|
|
46338
48146
|
console.log(`
|