Add support for uid:// references to hovers and document links (#841)

This commit is contained in:
anthonyme00
2025-04-27 03:39:46 +07:00
committed by GitHub
parent 6a3b1b6274
commit 4d00f9f41a
4 changed files with 166 additions and 8 deletions

View File

@@ -65,6 +65,26 @@ type ChangeWorkspaceNotification = {
};
};
type DocumentLinkResult = {
range: {
end: {
character: number;
line: number;
};
start: {
character: number;
line: number;
};
};
target: string;
};
type DocumentLinkResponseMessage = {
id: number;
jsonrpc: string;
result: DocumentLinkResult[];
};
export default class GDScriptLanguageClient extends LanguageClient {
public io: MessageIO = new MessageIO();
@@ -148,12 +168,28 @@ export default class GDScriptLanguageClient extends LanguageClient {
showNotification?: boolean,
): T {
if (type.method === "textDocument/documentSymbol") {
if (error.message.includes("selectionRange must be contained in fullRange")) {
log.warn(`Request failed for method "${type.method}", suppressing notification - see issue #820`);
return super.handleFailedRequest(type, token, error, defaultValue, false);
if (
error.message.includes("selectionRange must be contained in fullRange")
) {
log.warn(
`Request failed for method "${type.method}", suppressing notification - see issue #820`
);
return super.handleFailedRequest(
type,
token,
error,
defaultValue,
false
);
}
}
return super.handleFailedRequest(type, token, error, defaultValue, showNotification);
return super.handleFailedRequest(
type,
token,
error,
defaultValue,
showNotification
);
}
private request_filter(message: RequestMessage) {
@@ -209,6 +245,32 @@ export default class GDScriptLanguageClient extends LanguageClient {
(message as HoverResponseMesssage).result.contents.value = value;
}
} else if (sentMessage.method === "textDocument/documentLink") {
const results: DocumentLinkResult[] = (
message as DocumentLinkResponseMessage
).result;
if (!results) {
return message;
}
const final_result: DocumentLinkResult[] = [];
// at this point, Godot's LSP server does not
// return a valid path for resources identified
// by "uid://""
//
// this is a dirty hack to remove any "uid://"
// document links.
//
// to provide links for these, we will be relying on
// the internal DocumentLinkProvider instead.
for (const result of results) {
if (!result.target.startsWith("uid://")) {
final_result.push(result);
}
}
(message as DocumentLinkResponseMessage).result = final_result;
}
return message;
@@ -248,7 +310,10 @@ export default class GDScriptLanguageClient extends LanguageClient {
return message;
}
public async get_symbol_at_position(uri: vscode.Uri, position: vscode.Position) {
public async get_symbol_at_position(
uri: vscode.Uri,
position: vscode.Position
) {
const params = {
textDocument: { uri: uri.toString() },
position: { line: position.line, character: position.character },

View File

@@ -9,7 +9,7 @@ import {
type ExtensionContext,
} from "vscode";
import { SceneParser } from "../scene_tools";
import { convert_resource_path_to_uri, createLogger } from "../utils";
import { convert_resource_path_to_uri, convert_uids_to_uris, createLogger } from "../utils";
const log = createLogger("providers.document_links");
@@ -70,6 +70,22 @@ export class GDDocumentLinkProvider implements DocumentLinkProvider {
}
}
const uids: Set<string> = new Set();
const uid_matches: Array<[string, Range]> = [];
for (const match of text.matchAll(/uid:\/\/([0-9a-z]*)/g)) {
const r = this.create_range(document, match);
uids.add(match[0]);
uid_matches.push([match[0], r]);
}
const uid_map = await convert_uids_to_uris(Array.from(uids));
for (const uid of uid_matches) {
const uri = uid_map.get(uid[0]);
if (uri instanceof vscode.Uri) {
links.push(new DocumentLink(uid[1], uri));
}
}
return links;
}

View File

@@ -10,7 +10,7 @@ import {
Hover,
} from "vscode";
import { SceneParser } from "../scene_tools";
import { convert_resource_path_to_uri, createLogger } from "../utils";
import { convert_resource_path_to_uri, createLogger, convert_uid_to_uri, convert_uri_to_resource_path } from "../utils";
const log = createLogger("providers.hover");
@@ -36,6 +36,12 @@ export class GDHoverProvider implements HoverProvider {
links += `* [${match[0]}](${uri})\n`;
}
}
for (const match of text.matchAll(/uid:\/\/[0-9a-z]*/g)) {
const uri = await convert_uid_to_uri(match[0]);
if (uri instanceof Uri) {
links += `* [${match[0]}](${uri})\n`;
}
}
return links;
}
@@ -88,7 +94,15 @@ export class GDHoverProvider implements HoverProvider {
}
}
const link = document.getText(document.getWordRangeAtPosition(position, /res:\/\/[^"^']*/));
let link = document.getText(document.getWordRangeAtPosition(position, /res:\/\/[^"^']*/));
if (!link.startsWith("res://")) {
link = document.getText(document.getWordRangeAtPosition(position, /uid:\/\/[0-9a-z]*/));
if (link.startsWith("uid://")) {
const uri = await convert_uid_to_uri(link);
link = await convert_uri_to_resource_path(uri);
}
}
if (link.startsWith("res://")) {
let type = "";
if (link.endsWith(".gd")) {

View File

@@ -127,6 +127,69 @@ export async function convert_uri_to_resource_path(uri: vscode.Uri): Promise<str
return `res://${relative_path}`;
}
const uidCache: Map<string, vscode.Uri | null> = new Map();
export async function convert_uids_to_uris(uids: string[]): Promise<Map<string, vscode.Uri>> {
const not_found_uids: string[] = [];
const uris: Map<string, vscode.Uri> = new Map();
let found_all: boolean = true;
for (const uid of uids) {
if (!uid.startsWith("uid://")) {
continue;
}
if (uidCache.has(uid)) {
const uri = uidCache.get(uid);
if (fs.existsSync(uri.fsPath)) {
uris.set(uid, uri);
continue;
}
uidCache.delete(uid);
}
found_all = false;
not_found_uids.push(uid);
}
if (found_all) {
return uris;
}
const files = await vscode.workspace.findFiles("**/*.uid", null);
for (const file of files) {
const document = await vscode.workspace.openTextDocument(file);
const text = document.getText();
const match = text.match(/uid:\/\/([0-9a-z]*)/);
if (!match) {
continue;
}
const found_match = not_found_uids.indexOf(match[0]) >= 0;
const file_path = file.fsPath.substring(0, file.fsPath.length - ".uid".length);
if (!fs.existsSync(file_path)) {
continue;
}
const file_uri = vscode.Uri.file(file_path);
uidCache.set(match[0], file_uri);
if (found_match) {
uris.set(match[0], file_uri);
}
}
return uris;
}
export async function convert_uid_to_uri(uid: string): Promise<vscode.Uri | undefined> {
const uris = await convert_uids_to_uris([uid]);
return uris.get(uid);
}
export type VERIFY_STATUS = "SUCCESS" | "WRONG_VERSION" | "INVALID_EXE";
export type VERIFY_RESULT = {
status: VERIFY_STATUS;