Skip to content

Commit 7e5ee8f

Browse files
authored
feat: Add URI handler for jumping to targets (#431)
This feature will allow us to add links to documents, and tool output which jumps to specific targets, reducing friction and wasted time. URI handler works such that: vscode://bazelbuild.vscode-bazel//my/path/to/tests:target (unfortunately github itself won't link out to non-http urls) Will jump to said target given vscode is opened to a workspace which contains a bazel workspace with that target as the first workspaceFolder I imagine we could handle more situations like: vscode://bazelbuild.vscode-bazel/fspath/to/workspace//my/path/to/tests:target or vscode://bazelbuild.vscode-bazel/test//my/path/to/tests:target But I imagine anything of the sort would be more controversial.
1 parent b83b99c commit 7e5ee8f

File tree

2 files changed

+74
-29
lines changed

2 files changed

+74
-29
lines changed

src/definition/bazel_goto_definition_provider.ts

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,37 @@ import { blaze_query } from "../protos";
2828
// LABEL_REGEX matches label strings, e.g. @r//x/y/z:abc
2929
const LABEL_REGEX = /"((?:@\w+)?\/\/|(?:.+\/)?[^:]*(?::[^:]+)?)"/;
3030

31+
export async function targetToUri(
32+
targetText: string,
33+
workingDirectory: Uri,
34+
): Promise<QueryLocation | undefined> {
35+
const match = LABEL_REGEX.exec(targetText);
36+
37+
const targetName = match[1];
38+
// don't try to process visibility targets.
39+
if (targetName.startsWith("//visibility")) {
40+
return null;
41+
}
42+
43+
const queryResult = await new BazelQuery(
44+
getDefaultBazelExecutablePath(),
45+
workingDirectory.fsPath,
46+
).queryTargets(`kind(rule, "${targetName}") + kind(file, "${targetName}")`);
47+
48+
if (!queryResult.target.length) {
49+
return null;
50+
}
51+
const result = queryResult.target[0];
52+
let location;
53+
if (result.type === blaze_query.Target.Discriminator.RULE) {
54+
location = new QueryLocation(result.rule.location);
55+
} else {
56+
location = new QueryLocation(result.sourceFile.location);
57+
}
58+
59+
return location;
60+
}
61+
3162
export class BazelGotoDefinitionProvider implements DefinitionProvider {
3263
public async provideDefinition(
3364
document: TextDocument,
@@ -41,35 +72,19 @@ export class BazelGotoDefinitionProvider implements DefinitionProvider {
4172

4273
const range = document.getWordRangeAtPosition(position, LABEL_REGEX);
4374
const targetText = document.getText(range);
44-
const match = LABEL_REGEX.exec(targetText);
45-
46-
const targetName = match[1];
47-
// don't try to process visibility targets.
48-
if (targetName.startsWith("//visibility")) {
49-
return null;
50-
}
5175

52-
const queryResult = await new BazelQuery(
53-
getDefaultBazelExecutablePath(),
54-
Utils.dirname(document.uri).fsPath,
55-
).queryTargets(`kind(rule, "${targetName}") + kind(file, "${targetName}")`);
76+
const location = await targetToUri(targetText, Utils.dirname(document.uri));
5677

57-
if (!queryResult.target.length) {
58-
return null;
59-
}
60-
const result = queryResult.target[0];
61-
let location;
62-
if (result.type === blaze_query.Target.Discriminator.RULE) {
63-
location = new QueryLocation(result.rule.location);
64-
} else {
65-
location = new QueryLocation(result.sourceFile.location);
66-
}
67-
return [
68-
{
69-
originSelectionRange: range,
70-
targetUri: Uri.file(location.path),
71-
targetRange: location.range,
72-
},
73-
];
78+
return location
79+
? [
80+
{
81+
originSelectionRange: range,
82+
targetUri: Uri.file(location.path).with({
83+
fragment: `${location.line}:${location.column}`,
84+
}),
85+
targetRange: location.range,
86+
},
87+
]
88+
: null;
7489
}
7590
}

src/extension/extension.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import {
2727
} from "../buildifier";
2828
import { BazelBuildCodeLensProvider } from "../codelens";
2929
import { BazelCompletionItemProvider } from "../completion-provider";
30-
import { BazelGotoDefinitionProvider } from "../definition/bazel_goto_definition_provider";
30+
import {
31+
BazelGotoDefinitionProvider,
32+
targetToUri,
33+
} from "../definition/bazel_goto_definition_provider";
3134
import { BazelTargetSymbolProvider } from "../symbols";
3235
import { BazelWorkspaceTreeProvider } from "../workspace-tree";
3336
import { activateCommandVariables } from "./command_variables";
@@ -105,6 +108,33 @@ export async function activate(context: vscode.ExtensionContext) {
105108
"bazel.copyTargetToClipboard",
106109
bazelCopyTargetToClipboard,
107110
),
111+
// URI handler
112+
vscode.window.registerUriHandler({
113+
async handleUri(uri: vscode.Uri) {
114+
try {
115+
const workspace = vscode.workspace.workspaceFolders[0];
116+
const quotedUriPath = `"${uri.path}"`;
117+
const location = await targetToUri(quotedUriPath, workspace.uri);
118+
119+
vscode.commands
120+
.executeCommand(
121+
"vscode.open",
122+
vscode.Uri.file(location.path).with({
123+
fragment: `${location.line}:${location.column}`,
124+
}),
125+
)
126+
.then(undefined, (err) => {
127+
void vscode.window.showErrorMessage(
128+
`Could not open file: ${location.path} Error: ${err}`,
129+
);
130+
});
131+
} catch (err: any) {
132+
void vscode.window.showErrorMessage(
133+
`While handling URI: ${JSON.stringify(uri)} Error: ${err}`,
134+
);
135+
}
136+
},
137+
}),
108138
// CodeLens provider for BUILD files
109139
vscode.languages.registerCodeLensProvider(
110140
[{ pattern: "**/BUILD" }, { pattern: "**/BUILD.bazel" }],

0 commit comments

Comments
 (0)