Skip to content

Commit 91a5211

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 3ca663e commit 91a5211

8 files changed

+366
-113
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
@@ -41,7 +41,7 @@ import { activateWrapperCommands } from "./bazel_wrapper_commands";
4141
* @param context The extension context.
4242
*/
4343
export async function activate(context: vscode.ExtensionContext) {
44-
const workspaceTreeProvider = new BazelWorkspaceTreeProvider();
44+
const workspaceTreeProvider = await BazelWorkspaceTreeProvider.forExtension();
4545
context.subscriptions.push(workspaceTreeProvider);
4646

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

src/workspace-tree/bazel_package_tree_item.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class BazelPackageTreeItem
3333
/**
3434
* Initializes a new tree item with the given workspace path and package path.
3535
*
36+
* @param querier Querier for getting information inside a Bazel workspace.
3637
* @param workspacePath The path to the VS Code workspace folder.
3738
* @param packagePath The path to the build package that this item represents.
3839
* @param parentPackagePath The path to the build package of the tree item
@@ -43,7 +44,7 @@ export class BazelPackageTreeItem
4344
private readonly querier: IBazelQuerier,
4445
private readonly workspaceInfo: BazelWorkspaceInfo,
4546
private readonly packagePath: string,
46-
private readonly parentPackagePath: string,
47+
private readonly parentPackagePath?: string,
4748
) {}
4849

4950
public mightHaveChildren(): boolean {
@@ -62,26 +63,35 @@ export class BazelPackageTreeItem
6263
}
6364

6465
public getLabel(): string {
65-
// If this is a top-level package, include the leading double-slash on the
66-
// label.
67-
if (this.parentPackagePath.length === 0) {
68-
return `//${this.packagePath}`;
66+
if (this.parentPackagePath === undefined) {
67+
return this.packagePath;
6968
}
70-
// Otherwise, strip off the part of the package path that came from the
71-
// parent item (along with the slash).
72-
return this.packagePath.substring(this.parentPackagePath.length + 1);
69+
// Strip off the part of the package path that came from the
70+
// parent item.
71+
const parentLength = this.parentPackagePath.length;
72+
// (null)
73+
// //a
74+
//
75+
// @repo//foo
76+
// @repo//foo/bar
77+
//
78+
// @repo//
79+
// @repo//foo
80+
const diffIsLeadingSlash = this.packagePath[parentLength] === "/";
81+
const prefixLength = diffIsLeadingSlash ? parentLength + 1 : parentLength;
82+
return this.packagePath.substring(prefixLength);
7383
}
7484

7585
public getIcon(): vscode.ThemeIcon {
7686
return vscode.ThemeIcon.Folder;
7787
}
7888

7989
public getTooltip(): string {
80-
return `//${this.packagePath}`;
90+
return this.packagePath;
8191
}
8292

83-
public getCommand(): vscode.Command | undefined {
84-
return undefined;
93+
public getCommand(): Thenable<vscode.Command | undefined> {
94+
return Promise.resolve(undefined);
8595
}
8696

8797
public getContextValue(): string {
@@ -91,7 +101,7 @@ export class BazelPackageTreeItem
91101
public getBazelCommandOptions(): IBazelCommandOptions {
92102
return {
93103
options: [],
94-
targets: [`//${this.packagePath}`],
104+
targets: [this.packagePath],
95105
workspaceInfo: this.workspaceInfo,
96106
};
97107
}

src/workspace-tree/bazel_target_tree_item.ts

Lines changed: 31 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

2225
/** A tree item representing a build target. */
2326
export class BazelTargetTreeItem
@@ -27,6 +30,7 @@ export class BazelTargetTreeItem
2730
* Initializes a new tree item with the given query result representing a
2831
* build target.
2932
*
33+
* @param querier Querier for getting information inside a Bazel workspace.
3034
* @param target An object representing a build target that was produced by a
3135
* query.
3236
*/
@@ -55,16 +59,36 @@ export class BazelTargetTreeItem
5559
}
5660

5761
public getTooltip(): string {
58-
return `${this.target.rule.name}`;
62+
return this.target.rule.name;
5963
}
6064

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

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(): Thenable<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
@@ -25,6 +25,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
2525
/**
2626
* Initializes a new tree item with the given workspace folder.
2727
*
28+
* @param querier Querier for getting information inside a Bazel workspace.
2829
* @param workspaceFolder The workspace folder that the tree item represents.
2930
*/
3031
constructor(
@@ -52,8 +53,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
5253
return this.workspaceInfo.workspaceFolder.uri.fsPath;
5354
}
5455

55-
public getCommand(): vscode.Command | undefined {
56-
return undefined;
56+
public getCommand(): Thenable<vscode.Command | undefined> {
57+
return Promise.resolve(undefined);
5758
}
5859

5960
public getContextValue(): string {
@@ -81,7 +82,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
8182
startIndex: number,
8283
endIndex: number,
8384
treeItems: IBazelTreeItem[],
84-
parentPackagePath: string,
85+
parentPackagePath?: string,
8586
) {
8687
// We can assume that the caller has sorted the packages, so we scan them to
8788
// find groupings into which we should traverse more deeply. For example, if
@@ -115,7 +116,9 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
115116
// erroneously collapse something like "foo" and "foobar".
116117
while (
117118
groupEnd < endIndex &&
118-
packagePaths[groupEnd].startsWith(packagePath + "/")
119+
(packagePaths[groupEnd].startsWith(packagePath + "/") ||
120+
(packagePaths[groupEnd].startsWith(packagePath) &&
121+
packagePath.endsWith("//")))
119122
) {
120123
groupEnd++;
121124
}
@@ -160,14 +163,8 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
160163
return Promise.resolve([] as IBazelTreeItem[]);
161164
}
162165
const packagePaths = await this.querier.queryPackages(this.workspaceInfo);
163-
const topLevelItems: BazelPackageTreeItem[] = [];
164-
this.buildPackageTree(
165-
packagePaths,
166-
0,
167-
packagePaths.length,
168-
topLevelItems,
169-
"",
170-
);
166+
const topLevelItems: IBazelTreeItem[] = [];
167+
this.buildPackageTree(packagePaths, 0, packagePaths.length, topLevelItems);
171168

172169
// Now collect any targets in the directory also (this can fail since
173170
// there might not be a BUILD files at this level (but down levels)).
@@ -179,6 +176,6 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
179176
return new BazelTargetTreeItem(this.workspaceInfo, target);
180177
});
181178

182-
return Promise.resolve((topLevelItems as IBazelTreeItem[]).concat(targets));
179+
return Promise.resolve(topLevelItems.concat(targets));
183180
}
184181
}

0 commit comments

Comments
 (0)