Skip to content

Commit 895bb15

Browse files
committed
feat(workspace-tree): Workspace tree can show external local repository
Besides the existing internal packages, handles handles external local repository: the packages in pattern `@repo//package/path:target`. We made the WorkspaceTreeProvider able to process only the following two patterns: * `//internal/package/path` * `@external//package/path` And normalize the patterns queried in IBazelQuerier. Simple test cases also added for package path handling.
1 parent 61a1717 commit 895bb15

8 files changed

+371
-118
lines changed

src/bazel/bazel_workspace_info.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ export class BazelWorkspaceInfo {
100100
* belong to a workspace folder (for example, a standalone file loaded
101101
* into the editor).
102102
*/
103-
private constructor(
103+
constructor(
104104
public readonly bazelWorkspacePath: string,
105-
public readonly workspaceFolder: vscode.WorkspaceFolder | undefined,
105+
public readonly workspaceFolder?: vscode.WorkspaceFolder,
106106
) {}
107107
}

src/extension/extension.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { activateWrapperCommands } from "./bazel_wrapper_commands";
4242
*/
4343
export async function activate(context: vscode.ExtensionContext) {
4444
const workspaceTreeProvider =
45-
BazelWorkspaceTreeProvider.fromExtensionContext(context);
45+
await BazelWorkspaceTreeProvider.fromExtensionContext(context);
4646
context.subscriptions.push(workspaceTreeProvider);
4747

4848
const codeLensProvider = new BazelBuildCodeLensProvider(context);
@@ -96,11 +96,15 @@ export async function activate(context: vscode.ExtensionContext) {
9696
),
9797
// Commands
9898
...activateWrapperCommands(),
99-
vscode.commands.registerCommand("bazel.refreshBazelBuildTargets", () => {
100-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
101-
completionItemProvider.refresh();
102-
workspaceTreeProvider.refresh();
103-
}),
99+
vscode.commands.registerCommand(
100+
"bazel.refreshBazelBuildTargets",
101+
async () => {
102+
await Promise.allSettled([
103+
completionItemProvider.refresh(),
104+
workspaceTreeProvider.refresh(vscode.workspace.workspaceFolders),
105+
]);
106+
},
107+
),
104108
vscode.commands.registerCommand(
105109
"bazel.copyTargetToClipboard",
106110
bazelCopyTargetToClipboard,

src/workspace-tree/bazel_package_tree_item.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class BazelPackageTreeItem
3434
/**
3535
* Initializes a new tree item with the given workspace path and package path.
3636
*
37+
* @param querier Querier for getting information inside a Bazel workspace.
3738
* @param workspacePath The path to the VS Code workspace folder.
3839
* @param packagePath The path to the build package that this item represents.
3940
* @param parentPackagePath The path to the build package of the tree item
@@ -45,7 +46,7 @@ export class BazelPackageTreeItem
4546
private readonly querier: IBazelQuerier,
4647
private readonly workspaceInfo: BazelWorkspaceInfo,
4748
private readonly packagePath: string,
48-
private readonly parentPackagePath: string,
49+
private readonly parentPackagePath?: string,
4950
) {}
5051

5152
public mightHaveChildren(): boolean {
@@ -68,26 +69,35 @@ export class BazelPackageTreeItem
6869
}
6970

7071
public getLabel(): string {
71-
// If this is a top-level package, include the leading double-slash on the
72-
// label.
73-
if (this.parentPackagePath.length === 0) {
74-
return `//${this.packagePath}`;
72+
if (this.parentPackagePath === undefined) {
73+
return this.packagePath;
7574
}
76-
// Otherwise, strip off the part of the package path that came from the
77-
// parent item (along with the slash).
78-
return this.packagePath.substring(this.parentPackagePath.length + 1);
75+
// Strip off the part of the package path that came from the
76+
// parent item.
77+
const parentLength = this.parentPackagePath.length;
78+
// (null)
79+
// //a
80+
//
81+
// @repo//foo
82+
// @repo//foo/bar
83+
//
84+
// @repo//
85+
// @repo//foo
86+
const diffIsLeadingSlash = this.packagePath[parentLength] === "/";
87+
const prefixLength = diffIsLeadingSlash ? parentLength + 1 : parentLength;
88+
return this.packagePath.substring(prefixLength);
7989
}
8090

8191
public getIcon(): vscode.ThemeIcon {
8292
return vscode.ThemeIcon.Folder;
8393
}
8494

8595
public getTooltip(): string {
86-
return `//${this.packagePath}`;
96+
return this.packagePath;
8797
}
8898

89-
public getCommand(): vscode.Command | undefined {
90-
return undefined;
99+
public getCommand(): Promise<vscode.Command | undefined> {
100+
return Promise.resolve<undefined>(undefined);
91101
}
92102

93103
public getContextValue(): string {
@@ -97,7 +107,7 @@ export class BazelPackageTreeItem
97107
public getBazelCommandOptions(): IBazelCommandOptions {
98108
return {
99109
options: [],
100-
targets: [`//${this.packagePath}`],
110+
targets: [this.packagePath],
101111
workspaceInfo: this.workspaceInfo,
102112
};
103113
}

src/workspace-tree/bazel_target_tree_item.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
// limitations under the License.
1414

1515
import * as vscode from "vscode";
16+
import * as fs from "fs/promises";
1617
import { BazelWorkspaceInfo, QueryLocation } from "../bazel";
1718
import { IBazelCommandAdapter, IBazelCommandOptions } from "../bazel";
1819
import { blaze_query } from "../protos";
1920
import { IBazelTreeItem } from "./bazel_tree_item";
2021
import { getBazelRuleIcon } from "./icons";
22+
import { BazelInfo } from "../bazel/bazel_info";
23+
import { getDefaultBazelExecutablePath } from "../extension/configuration";
2124
import { Resources } from "../extension/resources";
2225

2326
/** A tree item representing a build target. */
@@ -61,16 +64,36 @@ export class BazelTargetTreeItem
6164
}
6265

6366
public getTooltip(): string {
64-
return `${this.target.rule.name}`;
67+
return this.target.rule.name;
6568
}
6669

67-
public getCommand(): vscode.Command | undefined {
70+
public async getCommand(): Promise<vscode.Command | undefined> {
71+
// Resolve the prefix if prefix is
72+
// $(./prebuilts/bazel info output_base)/external/
6873
const location = new QueryLocation(this.target.rule.location);
74+
// Maybe we should cache this to prevent the repeating invocations.
75+
const outputBase = await new BazelInfo(
76+
getDefaultBazelExecutablePath(),
77+
this.workspaceInfo.workspaceFolder.uri.fsPath,
78+
).getOne("output_base");
79+
let locationPath = location.path;
80+
// If location is in pattern `${execRoot}/external/<repo>/...`, then it
81+
// should be a file in local_repository(). Trying to remapping it back to
82+
// the origin source folder by resolve the symlink
83+
// ${execRoot}/external/<repo>.
84+
const outputBaseExternalPath = `${outputBase}/external/`;
85+
if (location.path.startsWith(outputBaseExternalPath)) {
86+
const repoPath = location.path.substring(outputBaseExternalPath.length);
87+
const repoPathMatch = repoPath.match(/^([^/]+)\/(.*)$/);
88+
if (repoPathMatch.length === 3) {
89+
const repo = repoPathMatch[1];
90+
const rest = repoPathMatch[2];
91+
const realRepo = await fs.realpath(`${outputBaseExternalPath}${repo}`);
92+
locationPath = `${realRepo}/${rest}`;
93+
}
94+
}
6995
return {
70-
arguments: [
71-
vscode.Uri.file(location.path),
72-
{ selection: location.range },
73-
],
96+
arguments: [vscode.Uri.file(locationPath), { selection: location.range }],
7497
command: "vscode.open",
7598
title: "Jump to Build Target",
7699
};
@@ -87,7 +110,7 @@ export class BazelTargetTreeItem
87110
public getBazelCommandOptions(): IBazelCommandOptions {
88111
return {
89112
options: [],
90-
targets: [`${this.target.rule.name}`],
113+
targets: [this.target.rule.name],
91114
workspaceInfo: this.workspaceInfo,
92115
};
93116
}

src/workspace-tree/bazel_tree_item.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export interface IBazelTreeItem {
4646
getTooltip(): string | undefined;
4747

4848
/** Returns the command that should be executed when the item is selected. */
49-
getCommand(): vscode.Command | undefined;
49+
getCommand(): Promise<vscode.Command | undefined>;
5050

5151
/**
5252
* Returns an identifying string that is used to filter which commands are

src/workspace-tree/bazel_workspace_folder_tree_item.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
2626
/**
2727
* Initializes a new tree item with the given workspace folder.
2828
*
29+
* @param querier Querier for getting information inside a Bazel workspace.
2930
* @param workspaceFolder The workspace folder that the tree item represents.
3031
*/
3132
constructor(
@@ -54,8 +55,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
5455
return this.workspaceInfo.workspaceFolder.uri.fsPath;
5556
}
5657

57-
public getCommand(): vscode.Command | undefined {
58-
return undefined;
58+
public getCommand(): Promise<vscode.Command | undefined> {
59+
return Promise.resolve<undefined>(undefined);
5960
}
6061

6162
public getContextValue(): string {
@@ -83,7 +84,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
8384
startIndex: number,
8485
endIndex: number,
8586
treeItems: IBazelTreeItem[],
86-
parentPackagePath: string,
87+
parentPackagePath?: string,
8788
) {
8889
// We can assume that the caller has sorted the packages, so we scan them to
8990
// find groupings into which we should traverse more deeply. For example, if
@@ -117,7 +118,9 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
117118
// erroneously collapse something like "foo" and "foobar".
118119
while (
119120
groupEnd < endIndex &&
120-
packagePaths[groupEnd].startsWith(packagePath + "/")
121+
(packagePaths[groupEnd].startsWith(packagePath + "/") ||
122+
(packagePaths[groupEnd].startsWith(packagePath) &&
123+
packagePath.endsWith("//")))
121124
) {
122125
groupEnd++;
123126
}
@@ -163,14 +166,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
163166
return Promise.resolve([] as IBazelTreeItem[]);
164167
}
165168
const packagePaths = await this.querier.queryPackages(this.workspaceInfo);
166-
const topLevelItems: BazelPackageTreeItem[] = [];
167-
this.buildPackageTree(
168-
packagePaths,
169-
0,
170-
packagePaths.length,
171-
topLevelItems,
172-
"",
173-
);
169+
const topLevelItems: IBazelTreeItem[] = [];
170+
this.buildPackageTree(packagePaths, 0, packagePaths.length, topLevelItems);
174171

175172
// Now collect any targets in the directory also (this can fail since
176173
// there might not be a BUILD files at this level (but down levels)).
@@ -186,6 +183,6 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
186183
);
187184
});
188185

189-
return Promise.resolve((topLevelItems as IBazelTreeItem[]).concat(targets));
186+
return Promise.resolve(topLevelItems.concat(targets));
190187
}
191188
}

0 commit comments

Comments
 (0)