diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index 05423c954193d051914cb07de7ffbb20472daa4e..344669fc1c497c986aafb8d3a11f758db6c58767 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -70,6 +70,7 @@ } .monaco-list-type-filter { + display: flex; position: absolute; border-radius: 2px; border-width: 1px; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index e21f3373dc595671903bf69bfe626f7f4f766c07..69b97c742622a942f6f8b39566b68242f3c62fcc 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -278,6 +278,7 @@ class TypeFilter implements ITreeFilter { } constructor( + private tree: AbstractTree, private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, private _filter?: ITreeFilter ) { } @@ -308,14 +309,14 @@ class TypeFilter implements ITreeFilter { const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, label, label.toLowerCase(), 0, true); if (!score) { - // DEMO: just highlights - return { data: FuzzyScore.Default, visibility: true }; + if (this.tree.configuration.filterOnType) { + return parentVisibility === TreeVisibility.Visible ? true : TreeVisibility.Recurse; + } else { + return { data: FuzzyScore.Default, visibility: true }; + } // DEMO: filter // return TreeVisibility.Recurse; - - // DEMO: smarter filter - // return parentVisibility === TreeVisibility.Visible ? true : TreeVisibility.Recurse; } return { data: score, visibility: true }; @@ -333,6 +334,7 @@ class TypeFilterController implements IDisposable { private positionClassName = 'ne'; private domNode: HTMLElement; + private label: HTMLElement; private _pattern = ''; private disposables: IDisposable[] = []; @@ -347,6 +349,14 @@ class TypeFilterController implements IDisposable { this.domNode.draggable = true; domEvent(this.domNode, 'dragstart')(this.onDragStart, this, this.disposables); + this.label = append(this.domNode, $('span.label')); + + const controls = append(this.domNode, $('.controls')); + const filterOnType = append(controls, $('input')); + filterOnType.type = 'checkbox'; + filterOnType.checked = !!tree.configuration.filterOnType; + Event.map(domEvent(filterOnType, 'input'), _ => filterOnType.checked)(this.onDidChangeFilterOnType, this, this.disposables); + const isPrintableCharEvent = keyboardNavigationLabelProvider.mightProducePrintableCharacter ? (e: IKeyboardEvent) => keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : (e: IKeyboardEvent) => mightProducePrintableCharacter(e); const onInput = Event.chain(domEvent(view.getHTMLElement(), 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement)) @@ -378,7 +388,7 @@ class TypeFilterController implements IDisposable { this.domNode.remove(); } - this.domNode.textContent = pattern.length > 16 + this.label.textContent = pattern.length > 16 ? '…' + pattern.substr(pattern.length - 16) : pattern; @@ -389,7 +399,7 @@ class TypeFilterController implements IDisposable { this._onDidChangePattern.fire(pattern); } - private onDragStart(event: DragEvent): void { + private onDragStart(): void { const container = this.view.getHTMLElement(); const { top, left } = getDomNodePagePosition(container); const containerWidth = container.clientWidth; @@ -461,6 +471,10 @@ class TypeFilterController implements IDisposable { disposables.push(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined)); } + onDidChangeFilterOnType(filterOnType: boolean): void { + this.tree.updateConfiguration({ filterOnType }); + } + dispose() { this.disposables = dispose(this.disposables); } @@ -492,7 +506,9 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends IAbstractTreeOptionsUpdate, IListOptions { readonly collapseByDefault?: boolean; // defaults to false @@ -501,8 +517,8 @@ export interface IAbstractTreeOptions extends IAbstractTr readonly autoExpandSingleChildren?: boolean; } -export interface IAbstractTreeWithTypeFilterOptions extends IAbstractTreeOptions { - readonly keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider; +export interface IAbstractTreeConfiguration { + readonly filterOnType: boolean; } export abstract class AbstractTree implements IDisposable { @@ -510,6 +526,7 @@ export abstract class AbstractTree implements IDisposable private view: List>; private renderers: TreeRenderer[]; private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; + private _configuration: IAbstractTreeConfiguration; protected model: ITreeModel; protected disposables: IDisposable[] = []; @@ -545,10 +562,14 @@ export abstract class AbstractTree implements IDisposable this.renderers = renderers.map(r => new TreeRenderer(r, onDidChangeCollapseStateRelay.event, options)); this.disposables.push(...this.renderers); + this._configuration = { + filterOnType: typeof options.filterOnType === 'boolean' ? options.filterOnType : false + }; + let filter: ITreeFilter | undefined; if (options.keyboardNavigationLabelProvider) { - filter = new TypeFilter(options.keyboardNavigationLabelProvider, options.filter as ITreeFilter) as ITreeFilter; // TODO need typescript help here + filter = new TypeFilter(this, options.keyboardNavigationLabelProvider, options.filter as ITreeFilter) as ITreeFilter; // TODO need typescript help here options = { ...options, filter }; } @@ -584,6 +605,15 @@ export abstract class AbstractTree implements IDisposable } } + updateConfiguration(update: Partial): void { + this._configuration = { ...this._configuration, ...update }; + this.refilter(); + } + + get configuration(): IAbstractTreeConfiguration { + return this._configuration; + } + // Widget getHTMLElement(): HTMLElement {