mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Add basic inlay hint support for GDScript (#589)
* Add support for inlay hints in GDScript files * Add "godotTools.inlayHints.gdscript" and "godotTools.inlayHints.gdresource" settings * GDScript inlay hints are disabled by default, and marked as experimental
This commit is contained in:
10
package.json
10
package.json
@@ -303,6 +303,16 @@
|
||||
],
|
||||
"default": "sameFolder",
|
||||
"description": "Controls where the Scene Preview will search for related scenes when viewing a script file."
|
||||
},
|
||||
"godotTools.inlayHints.gdscript": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to enable inlay hints in GDScript files (experimental)"
|
||||
},
|
||||
"godotTools.inlayHints.gdresource": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Whether to enable inlay hints in GDResource (.tscn, .tres, etc) files"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -10,10 +10,39 @@ import {
|
||||
ExtensionContext,
|
||||
} from "vscode";
|
||||
import { SceneParser } from "../scene_tools";
|
||||
import { createLogger } from "../utils";
|
||||
import { createLogger, get_configuration } from "../utils";
|
||||
import { globals } from "../extension";
|
||||
|
||||
const log = createLogger("providers.inlay_hints");
|
||||
|
||||
/**
|
||||
* Returns a label from a detail string.
|
||||
* E.g. `var a: int` gets parsed to ` int `.
|
||||
*/
|
||||
function fromDetail(detail: string): string {
|
||||
const labelRegex = /: ([\w\d_]+)/;
|
||||
const labelMatch = detail.match(labelRegex);
|
||||
const label = labelMatch ? labelMatch[1] : "unknown";
|
||||
return ` ${label} `;
|
||||
}
|
||||
|
||||
async function addByHover(document: TextDocument, hoverPosition: vscode.Position, start: vscode.Position): Promise<InlayHint | undefined> {
|
||||
const response = await globals.lsp.client.sendRequest("textDocument/hover", {
|
||||
textDocument: { uri: document.uri.toString() },
|
||||
position: {
|
||||
line: hoverPosition.line,
|
||||
character: hoverPosition.character,
|
||||
}
|
||||
});
|
||||
|
||||
// check if contents is an empty array; if it is, we have no hover information
|
||||
if (Array.isArray(response["contents"]) && response["contents"].length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new InlayHint(start, fromDetail(response["contents"].value), InlayHintKind.Type);
|
||||
}
|
||||
|
||||
export class GDInlayHintsProvider implements InlayHintsProvider {
|
||||
public parser = new SceneParser();
|
||||
|
||||
@@ -28,11 +57,71 @@ export class GDInlayHintsProvider implements InlayHintsProvider {
|
||||
);
|
||||
}
|
||||
|
||||
provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): ProviderResult<InlayHint[]> {
|
||||
const scene = this.parser.parse_scene(document);
|
||||
const text = document.getText();
|
||||
|
||||
async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise<InlayHint[]> {
|
||||
const hints: InlayHint[] = [];
|
||||
const text = document.getText(range);
|
||||
|
||||
if (document.fileName.endsWith(".gd")) {
|
||||
if (!get_configuration("inlayHints.gdscript", true)) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
await globals.lsp.client.onReady();
|
||||
|
||||
const symbolsRequest = await globals.lsp.client.sendRequest("textDocument/documentSymbol", {
|
||||
textDocument: { uri: document.uri.toString() },
|
||||
}) as unknown[];
|
||||
|
||||
if (symbolsRequest.length === 0) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const symbols = (typeof symbolsRequest[0] === "object" && "children" in symbolsRequest[0])
|
||||
? (symbolsRequest[0].children as unknown[]) // godot 4.0+ returns an array of children
|
||||
: symbolsRequest; // godot 3.2 and below returns an array of symbols
|
||||
|
||||
const hasDetail = symbols.some((s: any) => s.detail);
|
||||
|
||||
// TODO: make sure godot reports the correct location for variable declaration symbols
|
||||
// (allowing the use of regex only on ranges provided by the LSP (textDocument/documentSymbol))
|
||||
|
||||
// since neither LSP or the grammar know whether a variable is inferred or not,
|
||||
// we still need to use regex to find all inferred variable declarations.
|
||||
const regex = /((var|const)\s+)([\w\d_]+)\s*:=/g;
|
||||
|
||||
for (const match of text.matchAll(regex)) {
|
||||
if (token.isCancellationRequested) break;
|
||||
// TODO: until godot supports nested document symbols, we need to send
|
||||
// a hover request for each variable declaration that is nested
|
||||
const start = document.positionAt(match.index + match[0].length - 1);
|
||||
const hoverPosition = document.positionAt(match.index + match[1].length);
|
||||
|
||||
if (hasDetail) {
|
||||
const symbol = symbols.find((s: any) => s.name === match[3]);
|
||||
if (symbol && symbol["detail"]) {
|
||||
const hint = new InlayHint(start, fromDetail(symbol["detail"]), InlayHintKind.Type);
|
||||
hints.push(hint);
|
||||
} else {
|
||||
const hint = await addByHover(document, hoverPosition, start);
|
||||
if (hint) {
|
||||
hints.push(hint);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const hint = await addByHover(document, hoverPosition, start);
|
||||
if (hint) {
|
||||
hints.push(hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
|
||||
if (!get_configuration("inlayHints.gdresource", true)) {
|
||||
return hints;
|
||||
}
|
||||
|
||||
const scene = this.parser.parse_scene(document);
|
||||
|
||||
for (const match of text.matchAll(/ExtResource\(\s?"?(\w+)\s?"?\)/g)) {
|
||||
const id = match[1];
|
||||
|
||||
Reference in New Issue
Block a user