Skip to content

Commit 4d00f9f

Browse files
authored
Add support for uid:// references to hovers and document links (#841)
1 parent 6a3b1b6 commit 4d00f9f

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed

src/lsp/GDScriptLanguageClient.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,26 @@ type ChangeWorkspaceNotification = {
6565
};
6666
};
6767

68+
type DocumentLinkResult = {
69+
range: {
70+
end: {
71+
character: number;
72+
line: number;
73+
};
74+
start: {
75+
character: number;
76+
line: number;
77+
};
78+
};
79+
target: string;
80+
};
81+
82+
type DocumentLinkResponseMessage = {
83+
id: number;
84+
jsonrpc: string;
85+
result: DocumentLinkResult[];
86+
};
87+
6888
export default class GDScriptLanguageClient extends LanguageClient {
6989
public io: MessageIO = new MessageIO();
7090

@@ -148,12 +168,28 @@ export default class GDScriptLanguageClient extends LanguageClient {
148168
showNotification?: boolean,
149169
): T {
150170
if (type.method === "textDocument/documentSymbol") {
151-
if (error.message.includes("selectionRange must be contained in fullRange")) {
152-
log.warn(`Request failed for method "${type.method}", suppressing notification - see issue #820`);
153-
return super.handleFailedRequest(type, token, error, defaultValue, false);
171+
if (
172+
error.message.includes("selectionRange must be contained in fullRange")
173+
) {
174+
log.warn(
175+
`Request failed for method "${type.method}", suppressing notification - see issue #820`
176+
);
177+
return super.handleFailedRequest(
178+
type,
179+
token,
180+
error,
181+
defaultValue,
182+
false
183+
);
154184
}
155185
}
156-
return super.handleFailedRequest(type, token, error, defaultValue, showNotification);
186+
return super.handleFailedRequest(
187+
type,
188+
token,
189+
error,
190+
defaultValue,
191+
showNotification
192+
);
157193
}
158194

159195
private request_filter(message: RequestMessage) {
@@ -209,6 +245,32 @@ export default class GDScriptLanguageClient extends LanguageClient {
209245

210246
(message as HoverResponseMesssage).result.contents.value = value;
211247
}
248+
} else if (sentMessage.method === "textDocument/documentLink") {
249+
const results: DocumentLinkResult[] = (
250+
message as DocumentLinkResponseMessage
251+
).result;
252+
253+
if (!results) {
254+
return message;
255+
}
256+
257+
const final_result: DocumentLinkResult[] = [];
258+
// at this point, Godot's LSP server does not
259+
// return a valid path for resources identified
260+
// by "uid://""
261+
//
262+
// this is a dirty hack to remove any "uid://"
263+
// document links.
264+
//
265+
// to provide links for these, we will be relying on
266+
// the internal DocumentLinkProvider instead.
267+
for (const result of results) {
268+
if (!result.target.startsWith("uid://")) {
269+
final_result.push(result);
270+
}
271+
}
272+
273+
(message as DocumentLinkResponseMessage).result = final_result;
212274
}
213275

214276
return message;
@@ -248,7 +310,10 @@ export default class GDScriptLanguageClient extends LanguageClient {
248310
return message;
249311
}
250312

251-
public async get_symbol_at_position(uri: vscode.Uri, position: vscode.Position) {
313+
public async get_symbol_at_position(
314+
uri: vscode.Uri,
315+
position: vscode.Position
316+
) {
252317
const params = {
253318
textDocument: { uri: uri.toString() },
254319
position: { line: position.line, character: position.character },

src/providers/document_link.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type ExtensionContext,
1010
} from "vscode";
1111
import { SceneParser } from "../scene_tools";
12-
import { convert_resource_path_to_uri, createLogger } from "../utils";
12+
import { convert_resource_path_to_uri, convert_uids_to_uris, createLogger } from "../utils";
1313

1414
const log = createLogger("providers.document_links");
1515

@@ -70,6 +70,22 @@ export class GDDocumentLinkProvider implements DocumentLinkProvider {
7070
}
7171
}
7272

73+
const uids: Set<string> = new Set();
74+
const uid_matches: Array<[string, Range]> = [];
75+
for (const match of text.matchAll(/uid:\/\/([0-9a-z]*)/g)) {
76+
const r = this.create_range(document, match);
77+
uids.add(match[0]);
78+
uid_matches.push([match[0], r]);
79+
}
80+
81+
const uid_map = await convert_uids_to_uris(Array.from(uids));
82+
for (const uid of uid_matches) {
83+
const uri = uid_map.get(uid[0]);
84+
if (uri instanceof vscode.Uri) {
85+
links.push(new DocumentLink(uid[1], uri));
86+
}
87+
}
88+
7389
return links;
7490
}
7591

src/providers/hover.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Hover,
1111
} from "vscode";
1212
import { SceneParser } from "../scene_tools";
13-
import { convert_resource_path_to_uri, createLogger } from "../utils";
13+
import { convert_resource_path_to_uri, createLogger, convert_uid_to_uri, convert_uri_to_resource_path } from "../utils";
1414

1515
const log = createLogger("providers.hover");
1616

@@ -36,6 +36,12 @@ export class GDHoverProvider implements HoverProvider {
3636
links += `* [${match[0]}](${uri})\n`;
3737
}
3838
}
39+
for (const match of text.matchAll(/uid:\/\/[0-9a-z]*/g)) {
40+
const uri = await convert_uid_to_uri(match[0]);
41+
if (uri instanceof Uri) {
42+
links += `* [${match[0]}](${uri})\n`;
43+
}
44+
}
3945
return links;
4046
}
4147

@@ -88,7 +94,15 @@ export class GDHoverProvider implements HoverProvider {
8894
}
8995
}
9096

91-
const link = document.getText(document.getWordRangeAtPosition(position, /res:\/\/[^"^']*/));
97+
let link = document.getText(document.getWordRangeAtPosition(position, /res:\/\/[^"^']*/));
98+
if (!link.startsWith("res://")) {
99+
link = document.getText(document.getWordRangeAtPosition(position, /uid:\/\/[0-9a-z]*/));
100+
if (link.startsWith("uid://")) {
101+
const uri = await convert_uid_to_uri(link);
102+
link = await convert_uri_to_resource_path(uri);
103+
}
104+
}
105+
92106
if (link.startsWith("res://")) {
93107
let type = "";
94108
if (link.endsWith(".gd")) {

src/utils/godot_utils.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,69 @@ export async function convert_uri_to_resource_path(uri: vscode.Uri): Promise<str
127127
return `res://${relative_path}`;
128128
}
129129

130+
const uidCache: Map<string, vscode.Uri | null> = new Map();
131+
132+
export async function convert_uids_to_uris(uids: string[]): Promise<Map<string, vscode.Uri>> {
133+
const not_found_uids: string[] = [];
134+
const uris: Map<string, vscode.Uri> = new Map();
135+
136+
let found_all: boolean = true;
137+
for (const uid of uids) {
138+
if (!uid.startsWith("uid://")) {
139+
continue;
140+
}
141+
142+
if (uidCache.has(uid)) {
143+
const uri = uidCache.get(uid);
144+
if (fs.existsSync(uri.fsPath)) {
145+
uris.set(uid, uri);
146+
continue;
147+
}
148+
149+
uidCache.delete(uid);
150+
}
151+
152+
found_all = false;
153+
not_found_uids.push(uid);
154+
}
155+
156+
if (found_all) {
157+
return uris;
158+
}
159+
160+
const files = await vscode.workspace.findFiles("**/*.uid", null);
161+
162+
for (const file of files) {
163+
const document = await vscode.workspace.openTextDocument(file);
164+
const text = document.getText();
165+
const match = text.match(/uid:\/\/([0-9a-z]*)/);
166+
if (!match) {
167+
continue;
168+
}
169+
170+
const found_match = not_found_uids.indexOf(match[0]) >= 0;
171+
172+
const file_path = file.fsPath.substring(0, file.fsPath.length - ".uid".length);
173+
if (!fs.existsSync(file_path)) {
174+
continue;
175+
}
176+
177+
const file_uri = vscode.Uri.file(file_path);
178+
uidCache.set(match[0], file_uri);
179+
180+
if (found_match) {
181+
uris.set(match[0], file_uri);
182+
}
183+
}
184+
185+
return uris;
186+
}
187+
188+
export async function convert_uid_to_uri(uid: string): Promise<vscode.Uri | undefined> {
189+
const uris = await convert_uids_to_uris([uid]);
190+
return uris.get(uid);
191+
}
192+
130193
export type VERIFY_STATUS = "SUCCESS" | "WRONG_VERSION" | "INVALID_EXE";
131194
export type VERIFY_RESULT = {
132195
status: VERIFY_STATUS;

0 commit comments

Comments
 (0)