eclipse-theia / theia

Eclipse Theia is a cloud & desktop IDE framework implemented in TypeScript.
http://theia-ide.org
Eclipse Public License 2.0
19.87k stars 2.48k forks source link

"vscode.TreeItemCollapsibleState" Never Changes #13946

Open rroessler opened 1 month ago

rroessler commented 1 month ago

Bug Description:

In VSC extensions, whenever a refresh is requested by a vscode.TreeDataProvider, trees are refreshed to an "initialized" collapsible state instead of maintaining their current collapsible state.

This is an issue for tree-views that show real-time data (eg: custom debugger panels) as refreshes will force all nodes to snap back to their original collapsible state.

Steps to Reproduce:

This issue can be reproduced by implementing a simple tree-view provider that refreshes every 5-seconds. If the root-node is clicked, anytime the interval fires the root-node will reset to the original collapsed state as opposed to adhering to the current expanded state.

import vscode from 'vscode';

class Provider implements vscode.TreeDataProvider {
    private m_root = new vscode.TreeItem('Root', vscode.TreeItemCollapsibleState.Collapsed);
    private m_children = Array.from(Array(5), (_, ii) => new vscode.TreeItem(`Node: ${ii}`));

    readonly emitter = new vscode.EventEmitter<vscode.TreeItem | vscode.TreeItem[] | undefined | null | void>();
    readonly onDidChangeTreeData = this.emitter.event.bind(this.emitter);

    getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
        return element;
    }

    getChildren(element?: vscode.TreeItem | undefined): vscode.ProviderResult<vscode.TreeItem[]> {
        if (typeof element === 'undefined') return [this.m_root];
        return element === this.m_root ? this.m_children : [];
    }
}

export function activate(context: vscode.ExtensionContext) {
    const treeDataProvider = new Provider();
    context.subscriptions.push(vscode.window.createTreeView('exampleTreeProvider', { treeDataProvider }));

    // set an interval to trigger refreshes every 5 seconds
    const interval = setInterval(() => treeDataProvider.emitter.fire(), 5000);
    context.subscriptions.push(() => clearInterval(interval));
}

Steps to Fix:

This could be fixed by adding a toggle for expanded and collapsed states of nodes at the following:

 async onExpanded(treeItemId: string): Promise<any> {
    // get element from a cache
    const cachedElement = this.getElement(treeItemId);

    // fire an event
    if (cachedElement) {
        // ensure we cache the expanded state now
        if (cachedElement.collapsibleState) cachedElement.collapsibleState = TreeItemCollapsibleState.Expanded;

        this.onDidExpandElementEmitter.fire({
            element: cachedElement
        });
    }
}

async onCollapsed(treeItemId: string): Promise<any> {
    // get element from a cache
    const cachedElement = this.getElement(treeItemId);

    // fire an event
    if (cachedElement) {
        // ensure we cache the collapsed state now
        if (cachedElement.collapsibleState) cachedElement.collapsibleState = TreeItemCollapsibleState.Collapsed;

        this.onDidCollapseElementEmitter.fire({
            element: cachedElement
        });
    }
}
rroessler commented 1 month ago

Alongside this, triggering refreshes via "onDidChangeTreeData" causes the current scroll-state to reset to an initial position.

tsmaeder commented 1 month ago

@rroessler do you know what the behavior in VS Code is?

rroessler commented 1 month ago

@tsmaeder The original behaviour in VS Code is that when onDidChangeTreeData is called with specific node as arguments, only those nodes are refreshed. This means that parent nodes would not be refreshed, maintaining their current expansion state.

Currently the Theia extension wrapper forces a refresh on the entire TreeView widget. As such, nodes that may have been created with a collapsed state (eg: at getChildren or getTreeItem) but where then expanded before a refresh occurs, are all refreshed causing flickering/odd effects if refreshing occurs frequently.

Alongside this, the VS Code behaviour has the added bonus of being more efficient as only the nodes requested are refreshed, as opposed to the Theia implementation of refresh all from the root.

tsmaeder commented 1 month ago

@rroessler just so we're talking about the same thing: the example extension you give refreshes the root of the tree, though, right?

rroessler commented 1 month ago

@tsmaeder you are correct as the issue also arises from here too. The difference between the two behaviours stems from:

VS Code:

Theia:

tsmaeder commented 1 month ago

@rroessler since you seem to know how what to do, would you be ready to contribute the fix?

rroessler commented 1 month ago

@tsmaeder I will try my best to get a fix soon.