Skip to content

Commit 376ca03

Browse files
authored
refactor: Use trie as a node cache (#293)
1 parent af06e5d commit 376ca03

File tree

9 files changed

+314
-87
lines changed

9 files changed

+314
-87
lines changed

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { LibraryController } from "./controllers/libraryController";
1010
import { ProjectController } from "./controllers/projectController";
1111
import { init as initExpService } from "./ExperimentationService";
1212
import { Settings } from "./settings";
13+
import { syncHandler } from "./syncHandler";
1314
import { DependencyExplorer } from "./views/dependencyExplorer";
1415

1516
export async function activate(context: ExtensionContext): Promise<any> {
@@ -58,6 +59,7 @@ async function activateExtension(_operationId: string, context: ExtensionContext
5859
context.subscriptions.push(new LibraryController(context));
5960
context.subscriptions.push(new DependencyExplorer(context));
6061
context.subscriptions.push(contextManager);
62+
context.subscriptions.push(syncHandler);
6163
contextManager.setContextValue(Context.EXTENSION_ACTIVATED, true);
6264

6365
initExpService(context);

src/fileWather.ts

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/settings.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from "vscode";
88
import { instrumentOperationAsVsCodeCommand } from "vscode-extension-telemetry-wrapper";
99
import { Commands } from "./commands";
10-
import { SyncHandler } from "./fileWather";
10+
import { syncHandler } from "./syncHandler";
1111

1212
export class Settings {
1313

@@ -28,15 +28,12 @@ export class Settings {
2828
|| (updatedConfig.syncWithFolderExplorer !== oldConfig.syncWithFolderExplorer
2929
&& updatedConfig.syncWithFolderExplorer)) {
3030
commands.executeCommand(Commands.VIEW_PACKAGE_REFRESH);
31-
}
32-
});
33-
this.registerConfigurationListener((updatedConfig, oldConfig) => {
34-
if (updatedConfig.autoRefresh !== oldConfig.autoRefresh) {
35-
SyncHandler.updateFileWatcher(updatedConfig.autoRefresh);
31+
} else if (updatedConfig.autoRefresh !== oldConfig.autoRefresh) {
32+
syncHandler.updateFileWatcher(updatedConfig.autoRefresh);
3633
}
3734
});
3835

39-
SyncHandler.updateFileWatcher(Settings.autoRefresh());
36+
syncHandler.updateFileWatcher(Settings.autoRefresh());
4037

4138
context.subscriptions.push({ dispose: () => { this._configurationListeners = []; } });
4239

src/syncHandler.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import * as path from "path";
5+
import { commands, Disposable, FileSystemWatcher, Uri, workspace } from "vscode";
6+
import { instrumentOperation } from "vscode-extension-telemetry-wrapper";
7+
import { Commands } from "./commands";
8+
import { Settings } from "./settings";
9+
import { DataNode } from "./views/dataNode";
10+
import { ExplorerNode } from "./views/explorerNode";
11+
import { HierarchicalPackageRootNode } from "./views/hierarchicalPackageRootNode";
12+
import { explorerNodeCache } from "./views/nodeCache/explorerNodeCache";
13+
import { PackageRootNode } from "./views/packageRootNode";
14+
15+
const ENABLE_AUTO_REFRESH: string = "java.view.package.enableAutoRefresh";
16+
const DISABLE_AUTO_REFRESH: string = "java.view.package.disableAutoRefresh";
17+
18+
class SyncHandler implements Disposable {
19+
20+
private disposables: Disposable[] = [];
21+
22+
public updateFileWatcher(autoRefresh: boolean): void {
23+
if (autoRefresh) {
24+
instrumentOperation(ENABLE_AUTO_REFRESH, () => this.enableAutoRefresh())();
25+
} else {
26+
instrumentOperation(DISABLE_AUTO_REFRESH, () => this.dispose())();
27+
}
28+
}
29+
30+
public dispose(): void {
31+
for (const disposable of this.disposables) {
32+
if (disposable) {
33+
disposable.dispose();
34+
}
35+
}
36+
this.disposables = [];
37+
}
38+
39+
private async enableAutoRefresh() {
40+
this.disposables.push(workspace.onDidChangeWorkspaceFolders(() => {
41+
this.refresh();
42+
}));
43+
44+
const fileSystemWatcher: FileSystemWatcher = workspace.createFileSystemWatcher("**/{*.java,src/**}");
45+
this.setupWatchers(fileSystemWatcher);
46+
this.disposables.push(fileSystemWatcher);
47+
}
48+
49+
private setupWatchers(watcher: FileSystemWatcher): void {
50+
this.disposables.push(watcher.onDidChange((uri: Uri) => {
51+
if (path.extname(uri.fsPath) !== ".java" || !Settings.showMembers()) {
52+
return;
53+
}
54+
const node: DataNode | undefined = explorerNodeCache.getDataNode(uri);
55+
this.refresh(node);
56+
}));
57+
58+
this.disposables.push(watcher.onDidCreate((uri: Uri) => {
59+
this.refresh(this.getParentNodeInExplorer(uri));
60+
}));
61+
62+
this.disposables.push(watcher.onDidDelete((uri: Uri) => {
63+
this.refresh(this.getParentNodeInExplorer(uri));
64+
}));
65+
66+
}
67+
68+
private getParentNodeInExplorer(uri: Uri): ExplorerNode {
69+
let node: DataNode | undefined = explorerNodeCache.findBestMatchNodeByUri(uri);
70+
71+
if (!node) {
72+
return undefined;
73+
}
74+
75+
if (Settings.isHierarchicalView()) {
76+
// TODO: has to get the hierarchical package root node due to the java side implementation
77+
// because currently it will only get the types for a package node but no child packages.
78+
while (!(node instanceof HierarchicalPackageRootNode)) {
79+
node = <DataNode>node.getParent();
80+
}
81+
return node;
82+
} else {
83+
// in flat view
84+
if (path.extname(uri.fsPath) === ".java" &&
85+
Uri.parse(node.uri).fsPath === path.dirname(uri.fsPath)) {
86+
// if the returned node is direct parent of the input uri, refresh it.
87+
return node;
88+
} else {
89+
// the direct parent is not rendered in the explorer, the returned node
90+
// is other package fragment, we need to refresh the package fragment root.
91+
while (!(node instanceof PackageRootNode)) {
92+
node = <DataNode>node.getParent();
93+
}
94+
return node;
95+
}
96+
}
97+
}
98+
99+
private refresh(node?: ExplorerNode): void {
100+
commands.executeCommand(Commands.VIEW_PACKAGE_REFRESH, /* debounce = */true, node);
101+
}
102+
}
103+
104+
export const syncHandler: SyncHandler = new SyncHandler();

src/views/dependencyDataProvider.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,25 @@ import { Settings } from "../settings";
1616
import { DataNode } from "./dataNode";
1717
import { ExplorerNode } from "./explorerNode";
1818
import { LightWeightNode } from "./lightWeightNode";
19+
import { explorerNodeCache } from "./nodeCache/explorerNodeCache";
1920
import { ProjectNode } from "./projectNode";
2021
import { WorkspaceNode } from "./workspaceNode";
2122

2223
export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
2324

24-
private _onDidChangeTreeData: EventEmitter<null> = new EventEmitter<null>();
25+
private _onDidChangeTreeData: EventEmitter<ExplorerNode | null | undefined> = new EventEmitter<ExplorerNode | null | undefined>();
2526

2627
// tslint:disable-next-line:member-ordering
27-
public onDidChangeTreeData: Event<null> = this._onDidChangeTreeData.event;
28+
public onDidChangeTreeData: Event<ExplorerNode | null | undefined> = this._onDidChangeTreeData.event;
2829

2930
private _rootItems: ExplorerNode[] = null;
30-
private _refreshDelayTrigger: (() => void) & _.Cancelable;
31+
private _refreshDelayTrigger: ((element?: ExplorerNode) => void) & _.Cancelable;
3132

3233
constructor(public readonly context: ExtensionContext) {
33-
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_REFRESH, (debounce?: boolean) => this.refreshWithLog(debounce)));
34+
context.subscriptions.push(commands.registerCommand(Commands.VIEW_PACKAGE_REFRESH, (debounce?: boolean, element?: ExplorerNode) =>
35+
this.refreshWithLog(debounce, element)));
3436
context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_EXPORT_JAR, (node: INodeData) => createJarFile(node)));
35-
context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_REVEAL_FILE_OS, (node?: INodeData) =>
37+
context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_REVEAL_FILE_OS, (node: INodeData) =>
3638
commands.executeCommand("revealFileInOS", Uri.parse(node.uri))));
3739
context.subscriptions.push(instrumentOperationAsVsCodeCommand(Commands.VIEW_PACKAGE_COPY_FILE_PATH, (node: INodeData) =>
3840
commands.executeCommand("copyFilePath", Uri.parse(node.uri))));
@@ -48,16 +50,16 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
4850
this.setRefreshDelay();
4951
}
5052

51-
public refreshWithLog(debounce?: boolean) {
53+
public refreshWithLog(debounce?: boolean, element?: ExplorerNode) {
5254
if (Settings.autoRefresh()) {
53-
this.refresh(debounce);
55+
this.refresh(debounce, element);
5456
} else {
55-
instrumentOperation(Commands.VIEW_PACKAGE_REFRESH, () => this.refresh(debounce))();
57+
instrumentOperation(Commands.VIEW_PACKAGE_REFRESH, () => this.refresh(debounce, element))();
5658
}
5759
}
5860

59-
public refresh(debounce = false) {
60-
this._refreshDelayTrigger();
61+
public refresh(debounce = false, element?: ExplorerNode) {
62+
this._refreshDelayTrigger(element);
6163
if (!debounce) { // Immediately refresh
6264
this._refreshDelayTrigger.flush();
6365
}
@@ -70,7 +72,7 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
7072
if (this._refreshDelayTrigger) {
7173
this._refreshDelayTrigger.cancel();
7274
}
73-
this._refreshDelayTrigger = _.debounce(() => this.doRefresh(), wait);
75+
this._refreshDelayTrigger = _.debounce(this.doRefresh, wait);
7476
}
7577

7678
public openFile(uri: string) {
@@ -107,7 +109,9 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
107109
if (!this._rootItems || !element) {
108110
return this.getRootNodes();
109111
} else {
110-
return element.getChildren();
112+
const children: ExplorerNode[] = await element.getChildren();
113+
explorerNodeCache.saveNodes(children);
114+
return children;
111115
}
112116
}
113117

@@ -123,9 +127,9 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
123127
return project ? project.revealPaths(paths) : null;
124128
}
125129

126-
private doRefresh(): void {
127-
this._rootItems = null;
128-
this._onDidChangeTreeData.fire();
130+
private doRefresh(element?: ExplorerNode): void {
131+
explorerNodeCache.removeNodeChildren(element);
132+
this._onDidChangeTreeData.fire(element);
129133
}
130134

131135
private async getRootProjects(): Promise<ExplorerNode[]> {

src/views/dependencyExplorer.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { isStandardServerReady } from "../extension";
66
import { Jdtls } from "../java/jdtls";
77
import { INodeData } from "../java/nodeData";
88
import { Settings } from "../settings";
9+
import { DataNode } from "./dataNode";
910
import { DependencyDataProvider } from "./dependencyDataProvider";
1011
import { ExplorerNode } from "./explorerNode";
12+
import { explorerNodeCache } from "./nodeCache/explorerNodeCache";
1113

1214
export class DependencyExplorer implements Disposable {
1315

@@ -55,11 +57,14 @@ export class DependencyExplorer implements Disposable {
5557
return;
5658
}
5759

58-
const paths: INodeData[] = await Jdtls.resolvePath(uri.toString());
59-
if (!paths || paths.length === 0) {
60-
return;
60+
let node: DataNode | undefined = explorerNodeCache.getDataNode(uri);
61+
if (!node) {
62+
const paths: INodeData[] = await Jdtls.resolvePath(uri.toString());
63+
if (!paths || paths.length === 0) {
64+
return;
65+
}
66+
node = await this._dataProvider.revealPaths(paths);
6167
}
62-
const node = await this._dataProvider.revealPaths(paths);
6368

6469
if (this._dependencyViewer.visible) {
6570
this._dependencyViewer.reveal(node);

src/views/hierarchicalPackageNode.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ export class HierarchicalPackageNode extends PackageNode {
2727

2828
public getChildren(): ProviderResult<ExplorerNode[]> {
2929
return this.loadData().then((res) => {
30-
if (!!res) {
31-
// Combine hierarchical children and normal package node children
32-
res.forEach((node) => this.nodeData.children.push(node));
30+
if (res) {
31+
if (this.nodeData?.children) {
32+
this.nodeData.children.push(...res);
33+
} else {
34+
this.nodeData.children = res;
35+
}
3336
}
3437
return this.createChildNodeList();
3538
});

0 commit comments

Comments
 (0)