Skip to content

Commit af53df2

Browse files
committed
feat(workspace-tree): sync open editor with workspace tree
The implementation of reveal() in the tree view required proper parent-child relationships and package path resolution. The changes ensure that: 1. The tree view can properly navigate and reveal items by maintaining correct parent references 2. Package paths are consistently resolved through the parent chain 3. The selected item in the tree view is synchronized with the currently open editor A cache of BazelPackageTreeItems was added to improve performance by: - Reducing redundant package path calculations - Avoiding bazel queries performed by existing getChildren operator - Minimizing repeated string operations when resolving package paths Part of #353
1 parent e751280 commit af53df2

File tree

12 files changed

+424
-27
lines changed

12 files changed

+424
-27
lines changed

.github/workflows/build.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ jobs:
4040
- name: Lint
4141
run: npm run check-lint
4242

43+
- uses: bazel-contrib/[email protected]
44+
with:
45+
bazelisk-cache: true
46+
disk-cache: false
47+
repository-cache: false
48+
module-root: ${{ github.workspace }}/test/bazel_workspace
49+
50+
- name: Build Bazel workspace to assure it is working & warm up cache
51+
run: bazel build //...
52+
working-directory: ${{ github.workspace }}/test/bazel_workspace/
53+
4354
# Required for the test cases
4455
- name: Install system dependencies
4556
run: sudo apt install -y binutils rustfilt

eslint.config.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ const jsdoc = require("eslint-plugin-jsdoc");
88
module.exports = [
99
// Global ignores
1010
{
11-
ignores: ["out/", "src/protos/protos.js", "src/protos/protos.d.ts"],
11+
ignores: [
12+
"out/",
13+
"src/protos/protos.js",
14+
"src/protos/protos.d.ts",
15+
".vscode-test/",
16+
"node_modules/",
17+
"dist/",
18+
],
1219
},
1320

1421
// TypeScript configuration

src/extension/extension.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,31 @@ import { activateCommandVariables } from "./command_variables";
3333
import { activateTesting } from "../test-explorer";
3434
import { activateWrapperCommands } from "./bazel_wrapper_commands";
3535

36+
// Global reference to the workspace tree provider for testing
37+
export let _workspaceTreeProvider: BazelWorkspaceTreeProvider;
38+
3639
/**
3740
* Called when the extension is activated; that is, when its first command is
3841
* executed.
3942
*
4043
* @param context The extension context.
4144
*/
4245
export async function activate(context: vscode.ExtensionContext) {
43-
const workspaceTreeProvider =
46+
// Initialize the workspace tree provider
47+
_workspaceTreeProvider =
4448
BazelWorkspaceTreeProvider.fromExtensionContext(context);
45-
context.subscriptions.push(workspaceTreeProvider);
49+
context.subscriptions.push(_workspaceTreeProvider);
4650

51+
// Initialize other components
4752
const codeLensProvider = new BazelBuildCodeLensProvider(context);
4853
const buildifierDiagnostics = new BuildifierDiagnosticsManager();
4954
let completionItemProvider: BazelCompletionItemProvider | null = null;
5055

56+
// Initialize other parts of the extension
5157
const config = vscode.workspace.getConfiguration("bazel");
5258
const lspEnabled = !!config.get<string>("lsp.command");
5359

60+
// Set up LSP if enabled
5461
if (lspEnabled) {
5562
const lspClient = createLsp(config);
5663

@@ -89,17 +96,26 @@ export async function activate(context: vscode.ExtensionContext) {
8996
// eslint-disable-next-line @typescript-eslint/no-floating-promises
9097
vscode.commands.executeCommand("setContext", "bazel.lsp.enabled", lspEnabled);
9198

99+
// Create and register the tree view
100+
const treeView = vscode.window.createTreeView("bazelWorkspace", {
101+
treeDataProvider: _workspaceTreeProvider,
102+
showCollapseAll: true,
103+
});
104+
_workspaceTreeProvider.setTreeView(treeView);
105+
92106
context.subscriptions.push(
93-
vscode.window.registerTreeDataProvider(
94-
"bazelWorkspace",
95-
workspaceTreeProvider,
96-
),
107+
treeView,
97108
// Commands
98109
...activateWrapperCommands(),
110+
111+
// Register command to manually refresh the tree view
112+
vscode.commands.registerCommand("bazel.workspaceTree.refresh", () => {
113+
_workspaceTreeProvider.refresh();
114+
}),
99115
vscode.commands.registerCommand("bazel.refreshBazelBuildTargets", () => {
100116
// eslint-disable-next-line @typescript-eslint/no-floating-promises
101117
completionItemProvider?.refresh();
102-
workspaceTreeProvider.refresh();
118+
_workspaceTreeProvider.refresh();
103119
}),
104120
// URI handler
105121
vscode.window.registerUriHandler({

src/workspace-tree/bazel_package_tree_item.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,16 @@ export class BazelPackageTreeItem
3838
/**
3939
* Initializes a new tree item with the given workspace path and package path.
4040
*
41-
* @param workspacePath The path to the VS Code workspace folder.
41+
* @param resources The resources for the extension.
42+
* @param workspaceInfo The workspace information.
43+
* @param parent The parent tree item of this item.
4244
* @param packagePath The path to the build package that this item represents.
43-
* @param parentPackagePath The path to the build package of the tree item
44-
* that is this item's parent, which indicates how much of
45-
* {@code packagePath} should be stripped for the item's label.
4645
*/
4746
constructor(
4847
private readonly resources: Resources,
4948
private readonly workspaceInfo: BazelWorkspaceInfo,
49+
private readonly parent: IBazelTreeItem,
5050
private readonly packagePath: string,
51-
private readonly parentPackagePath: string,
5251
) {}
5352

5453
public mightHaveChildren(): boolean {
@@ -67,21 +66,27 @@ export class BazelPackageTreeItem
6766
return new BazelTargetTreeItem(
6867
this.resources,
6968
this.workspaceInfo,
69+
this as unknown as IBazelTreeItem,
7070
target,
7171
);
7272
});
7373
return (this.directSubpackages as IBazelTreeItem[]).concat(targets);
7474
}
7575

76+
public getParent(): vscode.ProviderResult<IBazelTreeItem> {
77+
return this.parent;
78+
}
79+
7680
public getLabel(): string {
7781
// If this is a top-level package, include the leading double-slash on the
7882
// label.
79-
if (this.parentPackagePath.length === 0) {
83+
const parentPackagePath = this.parent.getPackagePath();
84+
if (parentPackagePath.length === 0) {
8085
return `//${this.packagePath}`;
8186
}
8287
// Otherwise, strip off the part of the package path that came from the
8388
// parent item (along with the slash).
84-
return this.packagePath.substring(this.parentPackagePath.length + 1);
89+
return this.packagePath.substring(parentPackagePath.length + 1);
8590
}
8691

8792
public getIcon(): vscode.ThemeIcon {
@@ -107,4 +112,8 @@ export class BazelPackageTreeItem
107112
workspaceInfo: this.workspaceInfo,
108113
};
109114
}
115+
116+
public getPackagePath(): string {
117+
return this.packagePath;
118+
}
110119
}

src/workspace-tree/bazel_target_tree_item.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ export class BazelTargetTreeItem
2828
* Initializes a new tree item with the given query result representing a
2929
* build target.
3030
*
31+
* @param resources The resources for the extension.
32+
* @param workspaceInfo The workspace information.
33+
* @param parent The parent tree item of this item.
3134
* @param target An object representing a build target that was produced by a
3235
* query.
3336
*/
3437
constructor(
3538
private readonly resources: Resources,
3639
private readonly workspaceInfo: BazelWorkspaceInfo,
40+
private readonly parent: IBazelTreeItem,
3741
private readonly target: blaze_query.ITarget,
3842
) {}
3943

@@ -45,6 +49,14 @@ export class BazelTargetTreeItem
4549
return Promise.resolve([]);
4650
}
4751

52+
public getParent(): vscode.ProviderResult<IBazelTreeItem> {
53+
return this.parent;
54+
}
55+
56+
public getPackagePath(): string {
57+
return this.parent.getPackagePath();
58+
}
59+
4860
public getLabel(): string {
4961
const fullPath = this.target.rule.name;
5062
const colonIndex = fullPath.lastIndexOf(":");

src/workspace-tree/bazel_tree_item.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export interface IBazelTreeItem {
3333
/** Returns a promise for the children of the tree item. */
3434
getChildren(): Thenable<IBazelTreeItem[]>;
3535

36+
/** Returns the parent of the tree item. */
37+
getParent(): vscode.ProviderResult<IBazelTreeItem>;
38+
3639
/** Returns the text label of the tree item. */
3740
getLabel(): string;
3841

@@ -45,6 +48,14 @@ export interface IBazelTreeItem {
4548
*/
4649
getTooltip(): string | undefined;
4750

51+
/**
52+
* Returns the package path of the tree item.
53+
* For workspace folders, this returns an empty string.
54+
* For packages, this returns the path relative to the workspace root.
55+
* For targets, this returns the path of the package that contains the target.
56+
*/
57+
getPackagePath(): string;
58+
4859
/** Returns the command that should be executed when the item is selected. */
4960
getCommand(): vscode.Command | undefined;
5061

src/workspace-tree/bazel_workspace_folder_tree_item.ts

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import { Resources } from "../extension/resources";
2323

2424
/** A tree item representing a workspace folder. */
2525
export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
26+
/**
27+
* Stores all BazelPackageTreeItems in sorted order (by path length and in descending order).
28+
* This is used to find the most specific match for a given file path.
29+
*/
30+
private sortedPackageTreeItems: BazelPackageTreeItem[] = [];
31+
2632
/**
2733
* Initializes a new tree item with the given workspace folder.
2834
*
@@ -31,7 +37,9 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
3137
constructor(
3238
private readonly resources: Resources,
3339
private readonly workspaceInfo: BazelWorkspaceInfo,
34-
) {}
40+
) {
41+
void this.getDirectoryItems(); // Initialize the tree items
42+
}
3543

3644
public mightHaveChildren(): boolean {
3745
return true;
@@ -41,6 +49,10 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
4149
return this.getDirectoryItems();
4250
}
4351

52+
public getParent(): vscode.ProviderResult<IBazelTreeItem> {
53+
return undefined;
54+
}
55+
4456
public getLabel(): string {
4557
return this.workspaceInfo.workspaceFolder.name;
4658
}
@@ -61,6 +73,31 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
6173
return "workspaceFolder";
6274
}
6375

76+
public getWorkspaceInfo(): BazelWorkspaceInfo {
77+
return this.workspaceInfo;
78+
}
79+
80+
public getPackagePath(): string {
81+
return "";
82+
}
83+
84+
/**
85+
* Finds the package that contains the given relative file path.
86+
* Uses the presorted list of package items for efficient lookups.
87+
* Find the first package that is a prefix of the relative path
88+
*
89+
* @param relativeFilePath The filepath relative to the workspace folder.
90+
* @returns The package tree item that contains the given relative file path,
91+
* or undefined if no such package exists.
92+
*/
93+
public getClosestPackageTreeItem(
94+
relativeFilePath: string,
95+
): BazelPackageTreeItem | undefined {
96+
return this.sortedPackageTreeItems.find((pkg) =>
97+
relativeFilePath.startsWith(pkg.getPackagePath()),
98+
);
99+
}
100+
64101
/**
65102
* Recursively creates the tree items that represent packages found in a Bazel
66103
* query.
@@ -73,16 +110,15 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
73110
* common prefixes should be searched.
74111
* @param treeItems An array into which the tree items created at this level
75112
* in the tree will be pushed.
76-
* @param parentPackagePath The parent package path of the items being created
77-
* by this call, which is used to trim the package prefix from labels in
78-
* the tree items.
113+
* @param parent The parent tree item of the items being created by this call,
114+
* which is used to trim the package prefix from labels in the tree items.
79115
*/
80116
private buildPackageTree(
81117
packagePaths: string[],
82118
startIndex: number,
83119
endIndex: number,
84120
treeItems: BazelPackageTreeItem[],
85-
parentPackagePath: string,
121+
parent: IBazelTreeItem,
86122
) {
87123
// We can assume that the caller has sorted the packages, so we scan them to
88124
// find groupings into which we should traverse more deeply. For example, if
@@ -128,16 +164,16 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
128164
const item = new BazelPackageTreeItem(
129165
this.resources,
130166
this.workspaceInfo,
167+
parent,
131168
packagePath,
132-
parentPackagePath,
133169
);
134170
treeItems.push(item);
135171
this.buildPackageTree(
136172
packagePaths,
137173
groupStart + 1,
138174
groupEnd,
139175
item.directSubpackages,
140-
packagePath,
176+
item,
141177
);
142178

143179
// Move our index to start looking for more groups in the next iteration
@@ -175,7 +211,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
175211
0,
176212
packagePaths.length,
177213
topLevelItems,
178-
"",
214+
this,
179215
);
180216

181217
// Now collect any targets in the directory also (this can fail since
@@ -191,10 +227,37 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
191227
return new BazelTargetTreeItem(
192228
this.resources,
193229
this.workspaceInfo,
230+
this as unknown as IBazelTreeItem,
194231
target,
195232
);
196233
});
197234

235+
// Cache all packages after building the tree
236+
this.collectAndSortPackageTreeItems(topLevelItems);
237+
198238
return Promise.resolve((topLevelItems as IBazelTreeItem[]).concat(targets));
199239
}
240+
241+
/**
242+
* Collect, sort and store packages for later lookup
243+
*/
244+
private collectAndSortPackageTreeItems(items: BazelPackageTreeItem[]): void {
245+
this.sortedPackageTreeItems = [];
246+
this.collectAllPackageTreeItems(items);
247+
this.sortedPackageTreeItems.sort(
248+
(a, b) => b.getPackagePath().length - a.getPackagePath().length,
249+
);
250+
}
251+
252+
/**
253+
* Recursively collect all children of type BazelPackageTreeItem
254+
*/
255+
private collectAllPackageTreeItems(items: BazelPackageTreeItem[]): void {
256+
for (const item of items) {
257+
this.sortedPackageTreeItems.push(item);
258+
if (item.directSubpackages) {
259+
this.collectAllPackageTreeItems(item.directSubpackages);
260+
}
261+
}
262+
}
200263
}

0 commit comments

Comments
 (0)