Skip to content

Commit 88ddee7

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 f2aaf2b commit 88ddee7

File tree

9 files changed

+378
-19
lines changed

9 files changed

+378
-19
lines changed

src/extension/extension.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,22 @@ export async function activate(context: vscode.ExtensionContext) {
8989
// eslint-disable-next-line @typescript-eslint/no-floating-promises
9090
vscode.commands.executeCommand("setContext", "bazel.lsp.enabled", lspEnabled);
9191

92+
// Create and register the tree view
93+
const treeView = vscode.window.createTreeView("bazelWorkspace", {
94+
treeDataProvider: workspaceTreeProvider,
95+
showCollapseAll: true,
96+
});
97+
workspaceTreeProvider.setTreeView(treeView);
98+
9299
context.subscriptions.push(
93-
vscode.window.registerTreeDataProvider(
94-
"bazelWorkspace",
95-
workspaceTreeProvider,
96-
),
100+
treeView,
97101
// Commands
98102
...activateWrapperCommands(),
103+
104+
// Register command to manually refresh the tree view
105+
vscode.commands.registerCommand("bazel.workspaceTree.refresh", () => {
106+
workspaceTreeProvider.refresh();
107+
}),
99108
vscode.commands.registerCommand("bazel.refreshBazelBuildTargets", () => {
100109
// eslint-disable-next-line @typescript-eslint/no-floating-promises
101110
completionItemProvider?.refresh();

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: 68 additions & 7 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
*
@@ -41,6 +47,10 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
4147
return this.getDirectoryItems();
4248
}
4349

50+
public getParent(): vscode.ProviderResult<IBazelTreeItem> {
51+
return undefined;
52+
}
53+
4454
public getLabel(): string {
4555
return this.workspaceInfo.workspaceFolder.name;
4656
}
@@ -61,6 +71,31 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
6171
return "workspaceFolder";
6272
}
6373

74+
public getWorkspaceInfo(): BazelWorkspaceInfo {
75+
return this.workspaceInfo;
76+
}
77+
78+
public getPackagePath(): string {
79+
return "";
80+
}
81+
82+
/**
83+
* Finds the package that contains the given relative file path.
84+
* Uses the presorted list of package items for efficient lookups.
85+
* Find the first package that is a prefix of the relative path
86+
*
87+
* @param relativeFilePath The filepath relative to the workspace folder.
88+
* @returns The package tree item that contains the given relative file path,
89+
* or undefined if no such package exists.
90+
*/
91+
public getClosestPackageTreeItem(
92+
relativeFilePath: string,
93+
): BazelPackageTreeItem | undefined {
94+
return this.sortedPackageTreeItems.find((pkg) =>
95+
relativeFilePath.startsWith(pkg.getPackagePath()),
96+
);
97+
}
98+
6499
/**
65100
* Recursively creates the tree items that represent packages found in a Bazel
66101
* query.
@@ -73,16 +108,15 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
73108
* common prefixes should be searched.
74109
* @param treeItems An array into which the tree items created at this level
75110
* 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.
111+
* @param parent The parent tree item of the items being created by this call,
112+
* which is used to trim the package prefix from labels in the tree items.
79113
*/
80114
private buildPackageTree(
81115
packagePaths: string[],
82116
startIndex: number,
83117
endIndex: number,
84118
treeItems: BazelPackageTreeItem[],
85-
parentPackagePath: string,
119+
parent: IBazelTreeItem,
86120
) {
87121
// We can assume that the caller has sorted the packages, so we scan them to
88122
// find groupings into which we should traverse more deeply. For example, if
@@ -128,16 +162,16 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
128162
const item = new BazelPackageTreeItem(
129163
this.resources,
130164
this.workspaceInfo,
165+
parent,
131166
packagePath,
132-
parentPackagePath,
133167
);
134168
treeItems.push(item);
135169
this.buildPackageTree(
136170
packagePaths,
137171
groupStart + 1,
138172
groupEnd,
139173
item.directSubpackages,
140-
packagePath,
174+
item,
141175
);
142176

143177
// Move our index to start looking for more groups in the next iteration
@@ -175,7 +209,7 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
175209
0,
176210
packagePaths.length,
177211
topLevelItems,
178-
"",
212+
this,
179213
);
180214

181215
// Now collect any targets in the directory also (this can fail since
@@ -191,10 +225,37 @@ export class BazelWorkspaceFolderTreeItem implements IBazelTreeItem {
191225
return new BazelTargetTreeItem(
192226
this.resources,
193227
this.workspaceInfo,
228+
this as unknown as IBazelTreeItem,
194229
target,
195230
);
196231
});
197232

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

0 commit comments

Comments
 (0)