Change:
One pattern I am seeing myself doing a lot is invoking the "Collapse" action on the search viewlet and now that I have multi root results, I see no results until I expand a root: ! Could we maybe collapse all but leave the roots expanded? Or, upon first collapse call we do that and upon the second we collapse also the roots?
hmmm...
This still annoys me a lot, especially if the results are only coming from one folder: !
Agreed, and there should be an external PR incoming for this. If not I'll fix it...
this behaves very nice now, thanks!

Discussion:
collapseAll(elements?: any[], recursive?: boolean): WinJS.Promise; /** <ins> * Collapses several elements. * Collapses all elements at the greatest tree depth that has expanded elements. * The returned promise returns a boolean for whether the elements were collapsed or not. */ collapseDeepestExpandedLevel(): WinJS.Promise; /** </ins> * Toggles an element's expansion state. */ toggleExpansion(element: any, recursive?: boolean): WinJS.Promise;

return this.model.collapseAll(elements, recursive); } <ins> public collapseDeepestExpandedLevel(): WinJS.Promise { return this.model.collapseDeepestExpandedLevel(); } </ins> public toggleExpansion(element: any, recursive: boolean = false): WinJS.Promise { return this.model.toggleExpansion(element, recursive); }

return result; } <ins> public getChildren(): Item[] { var child = this.firstChild; var results = []; while (child) { results.push(child); child = child.next; } return results; } </ins> private isAncestorOf(item: Item): boolean { while (item) { if (item.id === this.id) {

return WinJS.Promise.join(promises); } <ins> public collapseDeepestExpandedLevel(): WinJS.Promise { var levelToCollapse = this.findDeepestExpandedLevel(this.input, 0); var items = [this.input]; for (var i = 0; i < levelToCollapse; i++) { items = arrays.flatten(items.map(node => node.getChildren())); } var promises = items.map(child => this.collapse(child, false)); return WinJS.Promise.join(promises); } private findDeepestExpandedLevel(item: Item, currentLevel: number): number { var expandedChildren = item.getChildren().filter(child => child.isExpanded()); if (!expandedChildren.length) { return currentLevel; } return Math.max(...expandedChildren.map(child => this.findDeepestExpandedLevel(child, currentLevel + 1))); } </ins> public toggleExpansion(element: any, recursive: boolean = false): WinJS.Promise { return this.isExpanded(element) ? this.collapse(element, recursive) : this.expand(element); }

}); }); <ins> test('collapseDeepestExpandedLevel', (done) => { model.setInput(SAMPLE.DEEP2).done(() => { model.expand(SAMPLE.DEEP2.children[0]).done(() => { model.expand(SAMPLE.DEEP2.children[0].children[0]).done(() => { assert(model.isExpanded(SAMPLE.DEEP2.children[0])); assert(model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); model.collapseDeepestExpandedLevel().done(() => { assert(model.isExpanded(SAMPLE.DEEP2.children[0])); assert(!model.isExpanded(SAMPLE.DEEP2.children[0].children[0])); done(); }); }); }); }); }); </ins> test('auto expand single child folders', (done) => { model.setInput(SAMPLE.DEEP).done(() => { model.expand(SAMPLE.DEEP.children[0]).done(() => {

import { Match, FileMatch, FileMatchOrMatch, FolderMatch, RenderableMatch } from 'vs/workbench/parts/search/common/searchModel'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import * as Constants from 'vs/workbench/parts/search/common/constants'; <del> import { CollapseAllAction as TreeCollapseAction } from 'vs/base/parts/tree/browser/treeDefaults'; </del> import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ResolvedKeybinding, createKeybinding } from 'vs/base/common/keyCodes';

} } <del> export class CollapseAllAction extends TreeCollapseAction { </del> <ins> export class CollapseDeepestExpandedLevelAction extends Action { private viewer: ITree; </ins> <del> constructor(viewlet: SearchViewlet) { super(viewlet.getControl(), false); </del> <ins> constructor(viewlet: SearchViewlet, enabled: boolean = false) { super('vs.tree.collapse', nls.localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', enabled); this.viewer = viewlet.getControl(); </ins> this.class = 'search-action collapse'; } <ins> public run(context?: any): TPromise<any> { if (this.viewer.getHighlight()) { return TPromise.as(null); // Global action disabled if user is in edit mode from another action } this.viewer.collapseDeepestExpandedLevel(); this.viewer.clearSelection(); this.viewer.clearFocus(); this.viewer.DOMFocus(); this.viewer.focusFirst(); return TPromise.as(null); } </ins> } export class ClearSearchResultsAction extends Action {

import { PatternInputWidget, ExcludePatternInputWidget } from 'vs/workbench/parts/search/browser/patternInputWidget'; import { SearchRenderer, SearchDataSource, SearchSorter, SearchAccessibilityProvider, SearchFilter } from 'vs/workbench/parts/search/browser/searchResultsView'; import { SearchWidget, ISearchWidgetOptions } from 'vs/workbench/parts/search/browser/searchWidget'; <del> import { RefreshAction, CollapseAllAction, ClearSearchResultsAction } from 'vs/workbench/parts/search/browser/searchActions'; </del> <ins> import { RefreshAction, CollapseDeepestExpandedLevelAction, ClearSearchResultsAction } from 'vs/workbench/parts/search/browser/searchActions'; </ins> import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import Severity from 'vs/base/common/severity'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';

this.createSearchResultsView(builder); this.actionRegistry = <any>{}; <del> let actions: Action[] = [new CollapseAllAction(this), new RefreshAction(this), new ClearSearchResultsAction(this)]; </del> <ins> let actions: Action[] = [new CollapseDeepestExpandedLevelAction(this), new RefreshAction(this), new ClearSearchResultsAction(this)]; </ins> actions.forEach((action) => { this.actionRegistry[action.id] = action; });