Skip to content

feat: URI Handler #431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 43 additions & 28 deletions src/definition/bazel_goto_definition_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,37 @@ import { blaze_query } from "../protos";
// LABEL_REGEX matches label strings, e.g. @r//x/y/z:abc
const LABEL_REGEX = /"((?:@\w+)?\/\/|(?:.+\/)?[^:]*(?::[^:]+)?)"/;

export async function targetToUri(
targetText: string,
workingDirectory: Uri,
): Promise<QueryLocation | undefined> {
const match = LABEL_REGEX.exec(targetText);

const targetName = match[1];
// don't try to process visibility targets.
if (targetName.startsWith("//visibility")) {
return null;
}

const queryResult = await new BazelQuery(
getDefaultBazelExecutablePath(),
workingDirectory.fsPath,
).queryTargets(`kind(rule, "${targetName}") + kind(file, "${targetName}")`);

if (!queryResult.target.length) {
return null;
}
const result = queryResult.target[0];
let location;
if (result.type === blaze_query.Target.Discriminator.RULE) {
location = new QueryLocation(result.rule.location);
} else {
location = new QueryLocation(result.sourceFile.location);
}

return location;
}

export class BazelGotoDefinitionProvider implements DefinitionProvider {
public async provideDefinition(
document: TextDocument,
Expand All @@ -41,35 +72,19 @@ export class BazelGotoDefinitionProvider implements DefinitionProvider {

const range = document.getWordRangeAtPosition(position, LABEL_REGEX);
const targetText = document.getText(range);
const match = LABEL_REGEX.exec(targetText);

const targetName = match[1];
// don't try to process visibility targets.
if (targetName.startsWith("//visibility")) {
return null;
}

const queryResult = await new BazelQuery(
getDefaultBazelExecutablePath(),
Utils.dirname(document.uri).fsPath,
).queryTargets(`kind(rule, "${targetName}") + kind(file, "${targetName}")`);
const location = await targetToUri(targetText, Utils.dirname(document.uri));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this case can ever realistically get hit, but to make this refactor preserve behaviour you should return null here if location is null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed with ternary. Let me know if you'd rather I used some other format.


if (!queryResult.target.length) {
return null;
}
const result = queryResult.target[0];
let location;
if (result.type === blaze_query.Target.Discriminator.RULE) {
location = new QueryLocation(result.rule.location);
} else {
location = new QueryLocation(result.sourceFile.location);
}
return [
{
originSelectionRange: range,
targetUri: Uri.file(location.path),
targetRange: location.range,
},
];
return location
? [
{
originSelectionRange: range,
targetUri: Uri.file(location.path).with({
fragment: `${location.line}:${location.column}`,
}),
targetRange: location.range,
},
]
: null;
}
}
32 changes: 31 additions & 1 deletion src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import {
} from "../buildifier";
import { BazelBuildCodeLensProvider } from "../codelens";
import { BazelCompletionItemProvider } from "../completion-provider";
import { BazelGotoDefinitionProvider } from "../definition/bazel_goto_definition_provider";
import {
BazelGotoDefinitionProvider,
targetToUri,
} from "../definition/bazel_goto_definition_provider";
import { BazelTargetSymbolProvider } from "../symbols";
import { BazelWorkspaceTreeProvider } from "../workspace-tree";
import { activateCommandVariables } from "./command_variables";
Expand Down Expand Up @@ -105,6 +108,33 @@ export async function activate(context: vscode.ExtensionContext) {
"bazel.copyTargetToClipboard",
bazelCopyTargetToClipboard,
),
// URI handler
vscode.window.registerUriHandler({
async handleUri(uri: vscode.Uri) {
try {
const workspace = vscode.workspace.workspaceFolders[0];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

presume it's in the first workspaceFolder. I suppose something more sophisticated could be done, but not sure it's necessary. Happy to accept suggested change here if someone has a specific suggestion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without any way of identifying the bazel workspace, I don't think we can do any better than assume the first workspace, so I'm happy with this.

const quotedUriPath = `"${uri.path}"`;
const location = await targetToUri(quotedUriPath, workspace.uri);

vscode.commands
.executeCommand(
"vscode.open",
vscode.Uri.file(location.path).with({
fragment: `${location.line}:${location.column}`,
}),
)
.then(undefined, (err) => {
void vscode.window.showErrorMessage(
`Could not open file: ${location.path} Error: ${err}`,
);
});
} catch (err: any) {
void vscode.window.showErrorMessage(
`While handling URI: ${JSON.stringify(uri)} Error: ${err}`,
);
}
},
}),
// CodeLens provider for BUILD files
vscode.languages.registerCodeLensProvider(
[{ pattern: "**/BUILD" }, { pattern: "**/BUILD.bazel" }],
Expand Down