This is an automated email from the ASF dual-hosted git repository. sdedic pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push: new d1247d5 Delay change events for node after the node's pending queries are resolved (#3382) d1247d5 is described below commit d1247d5a162d35e89405576ca181812295d0c553 Author: Svatopluk Dedic <svatopluk.de...@oracle.com> AuthorDate: Tue Dec 21 08:53:42 2021 +0100 Delay change events for node after the node's pending queries are resolved (#3382) Delay change events for node after the node's pending queries are resolved. --- java/java.lsp.server/vscode/src/explorer.ts | 222 ++++++++++++++++++++------- java/java.lsp.server/vscode/src/extension.ts | 5 +- 2 files changed, 173 insertions(+), 54 deletions(-) diff --git a/java/java.lsp.server/vscode/src/explorer.ts b/java/java.lsp.server/vscode/src/explorer.ts index 0cd451b..f8ab9ec 100644 --- a/java/java.lsp.server/vscode/src/explorer.ts +++ b/java/java.lsp.server/vscode/src/explorer.ts @@ -4,14 +4,19 @@ import { LanguageClient } from 'vscode-languageclient/node'; import { NbLanguageClient } from './extension'; import { NodeChangedParams, NodeInfoNotification, NodeInfoRequest } from './protocol'; +const doLog : boolean = false; export class TreeViewService extends vscode.Disposable { + private handler : vscode.Disposable | undefined; private client : NbLanguageClient; private trees : Map<string, vscode.TreeView<Visualizer>> = new Map(); private images : Map<number, vscode.Uri> = new Map(); private providers : Map<number, VisualizerProvider> = new Map(); - constructor (c : NbLanguageClient, disposeFunc : () => void) { + log : vscode.OutputChannel; + + constructor (log : vscode.OutputChannel, c : NbLanguageClient, disposeFunc : () => void) { super(() => { this.disposeAllViews(); disposeFunc(); }); + this.log = log; this.client = c; } @@ -92,11 +97,11 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa private root: Visualizer; private treeData : Map<number, Visualizer> = new Map(); private decorators : TreeItemDecorator<Visualizer>[] = []; - private pendingRefresh : Set<number> = new Set(); - + constructor( private client: LanguageClient, private ts : TreeViewService, + private log : vscode.OutputChannel, id : string, rootData : NodeInfoRequest.Data ) { @@ -115,8 +120,11 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa } } - fireItemChange(item : Visualizer) : void { - if (!item) { + fireItemChange(item : Visualizer | undefined) : void { + if (doLog) { + this.log.appendLine(`Firing change on ${item?.idstring()}`); + } + if (!item || item == this.root) { this._onDidChangeTreeData.fire(); } else { this._onDidChangeTreeData.fire(item); @@ -137,13 +145,20 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa refresh(params : NodeChangedParams): void { if (this.root.data.id === params.rootId) { - if (this.root.data.id == params.nodeId || !params.nodeId) { - this._onDidChangeTreeData.fire(); + let v : Visualizer | undefined; + if (this. root.data.id == params.nodeId || !params.nodeId) { + v = this.root; } else { - this.pendingRefresh.add(params.nodeId); - let v : Visualizer | undefined = this.treeData.get(params.nodeId); - if (v) { - this._onDidChangeTreeData.fire(v); + v = this.treeData.get(params.nodeId); + } + if (v) { + if (this.delayedFire.has(v)) { + if (doLog) { + this.log.appendLine(`Delaying change on ${v.idstring()}`); + } + v.pendingChange = true; + } else { + this.fireItemChange(v); } } } @@ -154,42 +169,130 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa } getTreeItem(element: Visualizer): vscode.TreeItem | Thenable<vscode.TreeItem> { - const n = Number(element.id); - if (this.pendingRefresh.delete(n)) { - return this.fetchItem(n).then((newV) => { - element.update(newV); - return element; - }); + const n : number = Number(element.id); + const self = this; + if (doLog) { + this.log.appendLine(`Doing getTreeItem on ${element.idstring()}`); + } + + return this.wrap(async (arr) => { + let fetched = await this.queryVisualizer(element, arr, () => this.fetchItem(n)); + element.update(fetched); + return self.getTreeItem2(fetched); + }); + } + + /** + * Wraps code that queries individual Visualizers so that blocked changes are fired after + * the code terminated. + * + * Usage: + * wrap(() => { ... code ... ; queryVisualizer(vis, () => { ... })}); + * @param fn the code to execute + * @returns value of the code function + */ + async wrap<X>(fn : (pending : Visualizer[]) => Thenable<X>) : Promise<X> { + let arr : Visualizer[] = []; + try { + return await fn(arr); + } finally { + this.releaseVisualizersAndFire(arr); + } + } + + /** + * Just creates a string list from visualizer IDs. Diagnostics only. + */ + private visualizerList(arr : Visualizer[]) : string { + let s = ""; + for (let v of arr) { + s += v.idstring() + " "; + } + return s; + } + + /** + * Do not use directly, use wrap(). Fires delayed events for visualizers that have no pending queries. + */ + private releaseVisualizersAndFire(list : Visualizer[] | undefined) { + if (!list) { + list = Array.from(this.delayedFire); + } + if (doLog) { + this.log.appendLine(`Done with ${this.visualizerList(list)}`); + } + // v can be in list several times, each push increased its counter, so we need to decrease it. + for (let v of list) { + if (this.treeData?.get(Number(v.id || -1)) === v) { + if (--v.pendingQueries) { + if (doLog) { + this.log.appendLine(`${v.idstring()} has pending ${v.pendingQueries} queries`); + } + continue; + } + if (v.pendingChange) { + if (doLog) { + this.log.appendLine(`Fire delayed change on ${v.idstring()}`); + } + this.fireItemChange(v); + v.pendingChange = false; + } + } + this.delayedFire.delete(v); } + if (doLog) { + this.log.appendLine("Pending queue: " + this.visualizerList(Array.from(this.delayedFire))); + this.log.appendLine("---------------"); + } + } + + /** + * Should wrap calls to NBLS for individual visualizers (info, children). Puts visualizer on the delayed fire list. + * Must be itself wrapped in wrap() -- wrap(... queryVisualizer()). + * @param element visualizer to be queried, possibly undefined (new item is expected) + * @param fn code to execute + * @returns code's result + */ + async queryVisualizer<X>(element : Visualizer | undefined, pending : Visualizer[], fn : () => Promise<X>) : Promise<X> { + if (!element) { + return fn(); + } + this.delayedFire.add(element); + pending.push(element); + element.pendingQueries++; + if (doLog) { + this.log.appendLine(`Delaying visualizer ${element.idstring()}, queries = ${element.pendingQueries}`) + } + return fn(); + } + + async getTreeItem2(element: Visualizer): Promise<vscode.TreeItem> { + const n = Number(element.id); if (this.decorators.length == 0) { - return element; + return element; } let list : TreeItemDecorator<Visualizer>[] = [...this.decorators]; - function f(item : vscode.TreeItem) : vscode.TreeItem | Thenable<vscode.TreeItem> { + async function f(item : vscode.TreeItem) : Promise<vscode.TreeItem> { const deco = list.shift(); if (!deco) { - return item; + return item; } const decorated = deco.decorateTreeItem(element, item); if (decorated instanceof vscode.TreeItem) { return f(decorated); } else { - return (decorated as Thenable<vscode.TreeItem>).then(f); + return (decorated as Thenable<vscode.TreeItem>).then(f); } } - return f(element.copy()); } + delayedFire : Set<Visualizer> = new Set<Visualizer>(); + async fetchItem(n : number) : Promise<Visualizer> { let d = await this.client.sendRequest(NodeInfoRequest.info, { nodeId : n }); - if (this.pendingRefresh.delete(n)) { - // and again - return this.fetchItem(n); - } let v = new Visualizer(d, this.ts.imageUri(d)); - // console.log('Nodeid ' + d.id + ': visualizer ' + v.visId); if (d.command) { // PENDING: provide an API to register command (+ parameters) -> command translators. if (d.command === 'vscode.open') { @@ -203,31 +306,42 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa getChildren(e?: Visualizer): Thenable<Visualizer[]> { const self = this; - async function collectResults(arr: any, element: Visualizer): Promise<Visualizer[]> { + + if (doLog) { + this.log.appendLine(`Doing getChildren on ${e?.idstring()}`); + } + + async function collectResults(list : Visualizer[], arr: any, element: Visualizer): Promise<Visualizer[]> { let res : Visualizer[] = []; - let refreshAgain : Visualizer[] = []; + let now : Visualizer[] | undefined; for (let i = 0; i < arr.length; i++) { - res.push(await self.fetchItem(arr[i])); + const old : Visualizer | undefined = self.treeData.get(arr[i]); + res.push( + await self.queryVisualizer(old, list, () => self.fetchItem(arr[i])) + ); } - const now : Visualizer[] = element.updateChildren(res, self); + now = element.updateChildren(res, self); for (let i = 0; i < arr.length; i++) { - const v = res[i]; + const v = now[i]; const n : number = Number(v.id || -1); self.treeData.set(n, v); v.parent = element; } - return now; + return now || []; } - if (e) { - return this.client.sendRequest(NodeInfoRequest.children, { nodeId : e.data.id}).then(async (arr) => { - return collectResults(arr, e); - }); - } else { - return this.client.sendRequest(NodeInfoRequest.children, { nodeId: this.root.data.id}).then(async (arr) => { - return collectResults(arr, this.root); - }); - } + return self.wrap((list) => self.queryVisualizer(e, list, () => { + if (e) { + return this.client.sendRequest(NodeInfoRequest.children, { nodeId : e.data.id}).then(async (arr) => { + return collectResults(list, arr, e); + }); + } else { + return this.client.sendRequest(NodeInfoRequest.children, { nodeId: this.root.data.id}).then(async (arr) => { + return collectResults(list, arr, this.root); + }); + } + } + )); } removeVisualizers(vis : number[]) { @@ -246,19 +360,20 @@ class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDa } } -// let visualizerSerial = 1; +let visualizerSerial = 1; export class Visualizer extends vscode.TreeItem { - // visId : number; - + visId : number; + pendingQueries : number = 0; + pendingChange : boolean = false; constructor( public data : NodeInfoRequest.Data, public image : vscode.Uri | undefined ) { super(data.label, data.collapsibleState); + this.visId = visualizerSerial++; this.id = "" + data.id; - // this.visId = visualizerSerial++; this.label = data.label; this.description = data.description; this.tooltip = data.tooltip; @@ -285,9 +400,11 @@ export class Visualizer extends vscode.TreeItem { parent: Visualizer | null = null; children: Map<number, Visualizer> | null = null; - update(other : Visualizer) { - this.id = "" + other.id; - // this.visId = visualizerSerial++; + idstring() : string { + return `[${this.id} : ${this.visId} - "${this.label}"]`; + } + + update(other : Visualizer) : Visualizer { this.label = other.label; this.description = other.description; this.tooltip = other.tooltip; @@ -299,6 +416,7 @@ export class Visualizer extends vscode.TreeItem { this.image = other.image; this.collapsibleState = other.collapsibleState; this.command = other.command; + return this; } updateChildren(newChildren : Visualizer[], provider : VisualizerProvider) : Visualizer[] { @@ -338,7 +456,7 @@ export async function createViewProvider(c : NbLanguageClient, id : string) : Pr if (!node) { throw "Unsupported view: " + id; } - return new VisualizerProvider(client, ts, id, node); + return new VisualizerProvider(client, ts, ts.log, id, node); }); if (!res) { throw "Unsupported view: " + id; @@ -361,7 +479,7 @@ export async function createTreeView<T>(c: NbLanguageClient, viewId: string, vie /** * Registers the treeview service with the language server. */ -export function createTreeViewService(c : NbLanguageClient): TreeViewService { +export function createTreeViewService(log : vscode.OutputChannel, c : NbLanguageClient): TreeViewService { const d = vscode.commands.registerCommand("foundProjects.deleteEntry", async function (this: any, args: any) { let v = args as Visualizer; let ok = await c.sendRequest(NodeInfoRequest.destroy, { nodeId : v.data.id }); @@ -369,7 +487,7 @@ export function createTreeViewService(c : NbLanguageClient): TreeViewService { vscode.window.showErrorMessage('Cannot delete node ' + v.label); } }); - const ts : TreeViewService = new TreeViewService(c, () => { + const ts : TreeViewService = new TreeViewService(log, c, () => { d.dispose() }); return ts; diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts index 99f076e..c119e41 100644 --- a/java/java.lsp.server/vscode/src/extension.ts +++ b/java/java.lsp.server/vscode/src/extension.ts @@ -63,9 +63,9 @@ let consoleLog: boolean = !!process.env['ENABLE_CONSOLE_LOG']; export class NbLanguageClient extends LanguageClient { private _treeViewService: TreeViewService; - constructor (id : string, name: string, s : ServerOptions, c : LanguageClientOptions) { + constructor (id : string, name: string, s : ServerOptions, log : vscode.OutputChannel, c : LanguageClientOptions) { super(id, name, s, c); - this._treeViewService = createTreeViewService(this); + this._treeViewService = createTreeViewService(log, this); } findTreeViewService(): TreeViewService { @@ -742,6 +742,7 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex 'java', 'NetBeans Java', connection, + log, clientOptions ); handleLog(log, 'Language Client: Starting'); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists