Change:
Type: <bBug</bWhen pressing to collapse all nodes in a list view it leaves selection in such a state that you cannot expand nodes with until you do something to change the selection. ! I believe what's happening is is leaving the originally selected node selected. So after executing the command you end up with 2 selections, your original selection plus the last root node in the list. The original selection is the primary selection, and if it has no children does not have anything to expand and is a no-op. As a user it looks like you have the root node selected, which has children to expand, but pressing does nothing. Pressing then expands the list to reveal your original selection and you see that you unexpectedly have multiple selections. The current behavior of appears to be: Leave the originally selected node selected Add a second selection to the last root node in the list The expected behavior here is: Move selection to the root node of your currently selected node Ideally this also retains multiple selection: each selection is moved to the root node of that selection. VS Code version: Code - Insiders 1.76.0-insider (, 2023-02-10T16:22:19.445Z) OS version: WindowsNT x64 10.0. Modes: Sandboxed: Yes <details<summarySystem Info</summary </detailsExtensions: none<details<summaryA/B Experiments</summary</details<!-- generated by issue reporter --
Looking at this again in the File Explorer it looks like the selection highlight is left on the where the original selection was despite the fact that the node is now in a different position. However, the "real" selection is still on the node. This means it looks like one thing is highlighted and pressing opens a different thing.
Just submitted a PR for this. Could you please review it?
What is the behavior supposed to be? Here's what I see: keyboard to focus a tree node that has a parent to collapse all moves to the whole tree and arrow down takes me to the first tree element. Right arrow does nothing.
The current behavior sort-of fixes the issue, insofar as is dodges it by doing something less desirable - moves selection to the whole tree. Imo, selection should remain on the root node(s) of whichever tree branch selection is currently in. I think this is what the previous behavior was trying to do, it just had an issue with leaving selection in a bad state. A couple examples ([x] denotes current selection): Start state End state, after Stat state End state
I agree, feel free to send an issue for it. Your observations are the fixed state. Here's the entire story: Marking it as verified.

Discussion:
class Trait<T> implements ISpliceable<boolean>, IDisposable { <del> private length = 0; private indexes: number[] = []; private sortedIndexes: number[] = []; </del> <ins> protected indexes: number[] = []; protected sortedIndexes: number[] = []; </ins> private readonly _onChange = new Emitter<ITraitChangeEvent>(); readonly onChange: Event<ITraitChangeEvent> = this._onChange.event;

constructor(private _trait: string) { } splice(start: number, deleteCount: number, elements: boolean[]): void { <del> deleteCount = Math.max(0, Math.min(deleteCount, this.length - start)); </del> const diff = elements.length - deleteCount; const end = start + deleteCount; const sortedIndexes: number[] = [];

sortedIndexes.push(this.sortedIndexes[i++] + diff); } <del> const length = this.length + diff; if (this.sortedIndexes.length > 0 && sortedIndexes.length === 0 && length > 0) { const first = this.sortedIndexes.find(index => index >= start) ?? length - 1; sortedIndexes.push(Math.min(first, length - 1)); } </del> this.renderer.splice(start, deleteCount, elements.length); this._set(sortedIndexes, sortedIndexes); <del> this.length = length; </del> } renderIndex(index: number, container: HTMLElement): void {

// Remove Element from Parent (Model) const parent = modelElement.parent; parent.removeChild(modelElement); <ins> this.view?.focusNext(); </ins> const oldNestedParent = modelElement.nestedParent; if (oldNestedParent) {

} // Refresh Parent (View) await this.view?.refresh(shouldDeepRefresh, parent); <ins> if (this.view?.getFocus().length === 0) { this.view?.focusLast(); } </ins> } })); }

isItemVisible(item: ExplorerItem): boolean; isItemCollapsed(item: ExplorerItem): boolean; hasFocus(): boolean; <ins> getFocus(): ExplorerItem[]; focusNext(): void; focusLast(): void; </ins> } function getFocus(listService: IListService): unknown | undefined {

return DOM.isAncestorOfActiveElement(this.container); } <ins> getFocus(): ExplorerItem[] { return this.tree.getFocus(); } focusNext(): void { this.tree.focusNext(); } focusLast(): void { this.tree.focusLast(); } </ins> getContext(respectMultiSelection: boolean): ExplorerItem[] { return getContext(this.tree.getFocus(), this.tree.getSelection(), respectMultiSelection, this.renderer); }

return localize('trustedFolderWithHostAriaLabel', "{0} on {1}, trusted", this.labelService.getUriLabel(item.uri), hostLabel); }, getWidgetAriaLabel: () => localize('trustedFoldersAndWorkspaces', "Trusted Folders & Workspaces") <ins> }, identityProvider: { getId(element: ITrustedUriItem) { return element.uri.toString(); }, </ins> } } ) as WorkbenchTable<ITrustedUriItem>;

} async delete(item: ITrustedUriItem) { <ins> this.table.focusNext(); </ins> await this.workspaceTrustManagementService.setUrisTrust([item.uri], false); <ins> if (this.table.getFocus().length === 0) { this.table.focusLast(); } </ins> this._onDelete.fire(item); <ins> this.table.domFocus(); </ins> } async edit(item: ITrustedUriItem, usePickerIfPossible?: boolean) {