Skip to content

Commit 6db4981

Browse files
committed
GH-1643: keep expanded/collapsed state of structure tree nodes between refreshes
1 parent 79a904a commit 6db4981

File tree

3 files changed

+61
-11
lines changed

3 files changed

+61
-11
lines changed

vscode-extensions/vscode-spring-boot/lib/Main.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import {
77
workspace,
88
ExtensionContext,
99
Uri,
10-
lm
11-
} from 'vscode';
10+
lm,
11+
TreeItemCollapsibleState
12+
} from 'vscode';
1213

1314
import * as commons from '@pivotal-tools/commons-vscode';
1415
import * as liveHoverUi from './live-hover-connect-ui';
@@ -186,7 +187,20 @@ export function activate(context: ExtensionContext): Thenable<ExtensionAPI> {
186187
*/
187188
const structureManager = new StructureManager();
188189
const explorerTreeProvider = new ExplorerTreeProvider(structureManager);
189-
context.subscriptions.push(window.createTreeView('explorer.spring', { treeDataProvider: explorerTreeProvider, showCollapseAll: true }));
190+
const treeView = window.createTreeView('explorer.spring', { treeDataProvider: explorerTreeProvider, showCollapseAll: true });
191+
192+
// Track expansion/collapse events to preserve state across refreshes
193+
context.subscriptions.push(treeView.onDidExpandElement(e => {
194+
const nodeId = e.element.getNodeId();
195+
explorerTreeProvider.setExpansionState(nodeId, TreeItemCollapsibleState.Expanded);
196+
}));
197+
198+
context.subscriptions.push(treeView.onDidCollapseElement(e => {
199+
const nodeId = e.element.getNodeId();
200+
explorerTreeProvider.setExpansionState(nodeId, TreeItemCollapsibleState.Collapsed);
201+
}));
202+
203+
context.subscriptions.push(treeView);
190204
context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.refresh", () => structureManager.refresh(true)));
191205
context.subscriptions.push(commands.registerCommand("vscode-spring-boot.structure.openReference", (node) => {
192206
if (node && node.getReferenceValue) {

vscode-extensions/vscode-spring-boot/lib/explorer/explorer-tree-provider.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
import { Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem } from "vscode";
1+
import { Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState } from "vscode";
22
import { StructureManager } from "./structure-tree-manager";
33
import { SpringNode } from "./nodes";
44

55
export class ExplorerTreeProvider implements TreeDataProvider<SpringNode> {
66

77
private emitter: EventEmitter<undefined | SpringNode | SpringNode[]>;
88
public readonly onDidChangeTreeData: Event<undefined | SpringNode | SpringNode[]>;
9+
private expansionStates: Map<string, TreeItemCollapsibleState> = new Map();
910

1011
constructor(private manager: StructureManager) {
1112
this.emitter = new EventEmitter<undefined | SpringNode | SpringNode[]>();
1213
this.onDidChangeTreeData = this.emitter.event;
13-
this.manager.onDidChange(e => this.emitter.fire(e));
14+
this.manager.onDidChange(e => {
15+
// Expansion states are tracked via onDidExpandElement/onDidCollapseElement events
16+
this.emitter.fire(e);
17+
});
1418
}
1519

1620
getTreeItem(element: SpringNode): TreeItem | Thenable<TreeItem> {
17-
return element.getTreeItem();
21+
const nodeId = element.getNodeId();
22+
const savedState = this.expansionStates.get(nodeId);
23+
return element.getTreeItem(savedState);
1824
}
1925

2026
getChildren(element?: SpringNode): ProviderResult<SpringNode[]> {
@@ -28,6 +34,15 @@ export class ExplorerTreeProvider implements TreeDataProvider<SpringNode> {
2834
return this.manager.rootElements;
2935
}
3036

37+
38+
getExpansionState(nodeId: string): TreeItemCollapsibleState | undefined {
39+
return this.expansionStates.get(nodeId);
40+
}
41+
42+
setExpansionState(nodeId: string, state: TreeItemCollapsibleState): void {
43+
this.expansionStates.set(nodeId, state);
44+
}
45+
3146
// getParent?(element: SpringNode): ProviderResult<SpringNode> {
3247
// throw new Error("Method not implemented.");
3348
// }

vscode-extensions/vscode-spring-boot/lib/explorer/nodes.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@ import { LsStereoTypedNode } from "./structure-tree-manager";
44

55
export class SpringNode {
66
constructor(readonly children: SpringNode[]) {}
7-
getTreeItem(): TreeItem {
8-
return new TreeItem("<node>", this.computeState(TreeItemCollapsibleState.Expanded));
7+
8+
getTreeItem(savedState?: TreeItemCollapsibleState): TreeItem {
9+
const defaultState = savedState !== undefined ? savedState : TreeItemCollapsibleState.Collapsed;
10+
return new TreeItem("<node>", this.computeState(defaultState));
911
}
10-
computeState(defaultState: TreeItemCollapsibleState.Collapsed | TreeItemCollapsibleState.Expanded): TreeItemCollapsibleState {
12+
13+
computeState(defaultState: TreeItemCollapsibleState): TreeItemCollapsibleState {
1114
return Array.isArray(this.children) && this.children.length ? defaultState : TreeItemCollapsibleState.None;
1215
}
16+
17+
getNodeId(): string {
18+
return "<base-node>";
19+
}
1320
}
1421

1522
export class StereotypedNode extends SpringNode {
1623
constructor(private n: LsStereoTypedNode, children: SpringNode[]) {
1724
super(children);
1825
}
19-
getTreeItem(): TreeItem {
20-
const item = super.getTreeItem();
26+
27+
getTreeItem(savedState?: TreeItemCollapsibleState): TreeItem {
28+
const item = super.getTreeItem(savedState);
2129
item.label = this.n.attributes.text;
2230
item.iconPath = this.computeIcon();
2331

@@ -39,6 +47,19 @@ export class StereotypedNode extends SpringNode {
3947
}
4048
return item;
4149
}
50+
51+
getNodeId(): string {
52+
// Create a unique identifier based on node attributes
53+
// Use text, icon, and location as identifying factors
54+
const textId = this.n.attributes.text || '';
55+
const iconId = this.n.attributes.icon || '';
56+
const locationId = this.n.attributes.location ?
57+
`${this.n.attributes.location.uri}:${this.n.attributes.location.range.start.line}:${this.n.attributes.location.range.start.character}` : '';
58+
const referenceId = this.n.attributes.reference ? String(this.n.attributes.reference) : '';
59+
60+
// Create a stable ID that can be used to match nodes across refreshes
61+
return `${textId}|${iconId}|${locationId}|${referenceId}`.replace(/\|+$/, ''); // Remove trailing separators
62+
}
4263

4364
getReferenceValue(): any {
4465
return this.n.attributes.reference;

0 commit comments

Comments
 (0)