Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/vs/base/browser/ui/tree/abstractTree.ts')
-rw-r--r--src/vs/base/browser/ui/tree/abstractTree.ts550
1 files changed, 278 insertions, 272 deletions
diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts
index 124d926541f..95032ae5ee4 100644
--- a/src/vs/base/browser/ui/tree/abstractTree.ts
+++ b/src/vs/base/browser/ui/tree/abstractTree.ts
@@ -3,27 +3,34 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { DragAndDropData, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd';
-import { $, addDisposableListener, append, clearNode, createStyleSheet, getDomNodePagePosition, hasParentWithClass } from 'vs/base/browser/dom';
+import { IDragAndDropData } from 'vs/base/browser/dnd';
+import { $, append, clearNode, createStyleSheet, h, hasParentWithClass } from 'vs/base/browser/dom';
import { DomEmitter } from 'vs/base/browser/event';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
-import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
+import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
+import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
+import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
+import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
+import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
-import { DefaultKeyboardNavigationDelegate, IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController } from 'vs/base/browser/ui/list/listWidget';
+import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';
+import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
+import { Action } from 'vs/base/common/actions';
import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays';
-import { disposableTimeout } from 'vs/base/common/async';
+import { disposableTimeout, timeout } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
import { SetMap } from 'vs/base/common/collections';
+import { Color } from 'vs/base/common/color';
import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event';
import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
-import { isMacintosh } from 'vs/base/common/platform';
import { ScrollEvent } from 'vs/base/common/scrollable';
import { ISpliceable } from 'vs/base/common/sequence';
+import { isNumber } from 'vs/base/common/types';
import 'vs/css!./media/tree';
import { localize } from 'vs/nls';
@@ -194,8 +201,7 @@ function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T,
getKeyboardNavigationLabel(node) {
return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(node.element);
}
- },
- enableKeyboardNavigation: options.simpleKeyboardNavigation
+ }
};
}
@@ -563,7 +569,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
export type LabelFuzzyScore = { label: string; score: FuzzyScore };
-class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
+class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
private _totalCount = 0;
get totalCount(): number { return this._totalCount; }
private _matchCount = 0;
@@ -590,10 +596,6 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
if (this._filter) {
const result = this._filter.filter(element, parentVisibility);
- if (this.tree.options.simpleKeyboardNavigation) {
- return result;
- }
-
let visibility: TreeVisibility;
if (typeof result === 'boolean') {
@@ -611,7 +613,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
this._totalCount++;
- if (this.tree.options.simpleKeyboardNavigation || !this._pattern) {
+ if (!this._pattern) {
this._matchCount++;
return { data: FuzzyScore.Default, visibility: true };
}
@@ -634,7 +636,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
}
}
- if (this.tree.options.filterOnType) {
+ if (this.tree.findMode === TreeFindMode.Filter) {
return TreeVisibility.Recurse;
} else {
return { data: FuzzyScore.Default, visibility: true };
@@ -651,170 +653,259 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi
}
}
-class TypeFilterController<T, TFilterData> implements IDisposable {
+export interface ICaseSensitiveToggleOpts {
+ readonly isChecked: boolean;
+ readonly inputActiveOptionBorder?: Color;
+ readonly inputActiveOptionForeground?: Color;
+ readonly inputActiveOptionBackground?: Color;
+}
- private _enabled = false;
- get enabled(): boolean { return this._enabled; }
+export class ModeToggle extends Toggle {
+ constructor(opts?: ICaseSensitiveToggleOpts) {
+ super({
+ icon: Codicon.filter,
+ title: localize('filter', "Filter"),
+ isChecked: opts?.isChecked ?? false,
+ inputActiveOptionBorder: opts?.inputActiveOptionBorder,
+ inputActiveOptionForeground: opts?.inputActiveOptionForeground,
+ inputActiveOptionBackground: opts?.inputActiveOptionBackground
+ });
+ }
+}
- private _pattern = '';
- get pattern(): string { return this._pattern; }
+export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { }
- private _filterOnType: boolean;
- get filterOnType(): boolean { return this._filterOnType; }
+export interface IFindWidgetOpts extends IFindWidgetStyles { }
- private _empty: boolean = false;
- get empty(): boolean { return this._empty; }
+export enum TreeFindMode {
+ Highlight,
+ Filter
+}
- private readonly _onDidChangeEmptyState = new Emitter<boolean>();
- readonly onDidChangeEmptyState: Event<boolean> = Event.latch(this._onDidChangeEmptyState.event);
+class FindWidget<T, TFilterData> extends Disposable {
- private positionClassName = 'ne';
- private domNode: HTMLElement;
- private messageDomNode: HTMLElement;
- private labelDomNode: HTMLElement;
- private filterOnTypeDomNode: HTMLInputElement;
- private clearDomNode: HTMLElement;
- private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
+ private readonly elements = h('.monaco-tree-type-filter', [
+ h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab'),
+ h('.monaco-tree-type-filter-input@findInput'),
+ h('.monaco-tree-type-filter-actionbar@actionbar'),
+ ]);
- private automaticKeyboardNavigation = true;
- private triggered = false;
+ set mode(mode: TreeFindMode) {
+ this.modeToggle.checked = mode === TreeFindMode.Filter;
+ this.findInput.inputBox.setPlaceHolder(mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search"));
+ }
- private readonly _onDidChangePattern = new Emitter<string>();
- readonly onDidChangePattern = this._onDidChangePattern.event;
+ private readonly modeToggle: ModeToggle;
+ private readonly findInput: FindInput;
+ private readonly actionbar: ActionBar;
+ private width = 0;
+ private right = 4;
- private readonly enabledDisposables = new DisposableStore();
- private readonly disposables = new DisposableStore();
+ readonly _onDidDisable = new Emitter<void>();
+ readonly onDidDisable = this._onDidDisable.event;
+ readonly onDidChangeValue: Event<string>;
+ readonly onDidChangeMode: Event<TreeFindMode>;
constructor(
+ container: HTMLElement,
private tree: AbstractTree<T, TFilterData, any>,
- model: ITreeModel<T, TFilterData, any>,
- private view: List<ITreeNode<T, TFilterData>>,
- private filter: TypeFilter<T>,
- private keyboardNavigationDelegate: IKeyboardNavigationDelegate
+ contextViewProvider: IContextViewProvider,
+ mode: TreeFindMode,
+ options?: IFindWidgetOpts
) {
- this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`);
- this.domNode.draggable = true;
- this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart()));
+ super();
- this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`));
+ container.appendChild(this.elements.root);
+ this._register(toDisposable(() => container.removeChild(this.elements.root)));
- this.labelDomNode = append(this.domNode, $('span.label'));
- const controls = append(this.domNode, $('.controls'));
+ this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter }));
+ this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store);
- this._filterOnType = !!tree.options.filterOnType;
- this.filterOnTypeDomNode = append(controls, $<HTMLInputElement>('input.filter'));
- this.filterOnTypeDomNode.type = 'checkbox';
- this.filterOnTypeDomNode.checked = this._filterOnType;
- this.filterOnTypeDomNode.tabIndex = -1;
- this.updateFilterOnTypeTitleAndIcon();
- this.disposables.add(addDisposableListener(this.filterOnTypeDomNode, 'input', () => this.onDidChangeFilterOnType()));
+ this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, {
+ label: localize('type to search', "Type to search"),
+ additionalToggles: [this.modeToggle]
+ }));
- this.clearDomNode = append(controls, $<HTMLInputElement>('button.clear' + Codicon.treeFilterClear.cssSelector));
- this.clearDomNode.tabIndex = -1;
- this.clearDomNode.title = localize('clear', "Clear");
+ this.actionbar = this._register(new ActionBar(this.elements.actionbar));
+ this.mode = mode;
- this.keyboardNavigationEventFilter = tree.options.keyboardNavigationEventFilter;
+ const emitter = this._register(new DomEmitter(this.findInput.inputBox.inputElement, 'keydown'));
+ const onKeyDown = this._register(Event.chain(emitter.event))
+ .map(e => new StandardKeyboardEvent(e))
+ .event;
- model.onDidSplice(this.onDidSpliceModel, this, this.disposables);
- this.updateOptions(tree.options);
+ this._register(onKeyDown((e): any => {
+ switch (e.keyCode) {
+ case KeyCode.DownArrow:
+ e.preventDefault();
+ e.stopPropagation();
+ this.tree.domFocus();
+ return;
+ }
+ }));
+
+ const closeAction = this._register(new Action('close', localize('close', "Close"), 'codicon codicon-close', true, () => this.dispose()));
+ this.actionbar.push(closeAction, { icon: true, label: false });
+
+ const onGrabMouseDown = this._register(new DomEmitter(this.elements.grab, 'mousedown'));
+
+ this._register(onGrabMouseDown.event(e => {
+ const disposables = new DisposableStore();
+ const onWindowMouseMove = disposables.add(new DomEmitter(window, 'mousemove'));
+ const onWindowMouseUp = disposables.add(new DomEmitter(window, 'mouseup'));
+
+ const startRight = this.right;
+ const startX = e.pageX;
+ this.elements.grab.classList.add('grabbing');
+
+ const update = (e: MouseEvent) => {
+ const deltaX = e.pageX - startX;
+ this.right = startRight - deltaX;
+ this.layout();
+ };
+
+ disposables.add(onWindowMouseMove.event(update));
+ disposables.add(onWindowMouseUp.event(e => {
+ update(e);
+ this.elements.grab.classList.remove('grabbing');
+ disposables.dispose();
+ }));
+ }));
+
+
+ this.onDidChangeValue = this.findInput.onDidChange;
+ this.style(options ?? {});
}
- updateOptions(options: IAbstractTreeOptions<T, TFilterData>): void {
- if (options.simpleKeyboardNavigation) {
- this.disable();
- } else {
- this.enable();
- }
+ style(styles: IFindWidgetStyles): void {
+ this.findInput.style(styles);
- if (typeof options.filterOnType !== 'undefined') {
- this._filterOnType = !!options.filterOnType;
- this.filterOnTypeDomNode.checked = this._filterOnType;
- this.updateFilterOnTypeTitleAndIcon();
+ if (styles.listFilterWidgetBackground) {
+ this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString();
}
- if (typeof options.automaticKeyboardNavigation !== 'undefined') {
- this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
+ if (styles.listFilterWidgetShadow) {
+ this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`;
}
+ }
- this.tree.refilter();
- this.render();
+ focus() {
+ this.findInput.focus();
+ }
- if (!this.automaticKeyboardNavigation) {
- this.onEventOrInput('');
- }
+ select() {
+ this.findInput.select();
}
- toggle(): void {
- this.triggered = !this.triggered;
+ layout(width: number = this.width): void {
+ this.width = width;
+ this.right = Math.min(Math.max(20, this.right), Math.max(20, width - 170));
+ this.elements.root.style.right = `${this.right}px`;
+ }
- if (!this.triggered) {
- this.onEventOrInput('');
- }
+ showMessage(message: IMessage): void {
+ this.findInput.showMessage(message);
}
- private enable(): void {
- if (this._enabled) {
+ clearMessage(): void {
+ this.findInput.clearMessage();
+ }
+
+ override async dispose(): Promise<void> {
+ this._onDidDisable.fire();
+ this.elements.root.classList.add('disabled');
+ await timeout(300);
+ super.dispose();
+ }
+}
+
+class FindController<T, TFilterData> implements IDisposable {
+
+ private _pattern = '';
+ get pattern(): string { return this._pattern; }
+
+ private _mode: TreeFindMode;
+ get mode(): TreeFindMode { return this._mode; }
+ set mode(mode: TreeFindMode) {
+ if (mode === this._mode) {
return;
}
- const onRawKeyDown = this.enabledDisposables.add(new DomEmitter(this.view.getHTMLElement(), 'keydown'));
- const onKeyDown = Event.chain(onRawKeyDown.event)
- .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode)
- .filter(e => e.key !== 'Dead' && !/^Media/.test(e.key))
- .map(e => new StandardKeyboardEvent(e))
- .filter(this.keyboardNavigationEventFilter || (() => true))
- .filter(() => this.automaticKeyboardNavigation || this.triggered)
- .filter(e => (this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) && !(e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.RightArrow)) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey)))
- .forEach(e => { e.stopPropagation(); e.preventDefault(); })
- .event;
-
- const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click'));
+ this._mode = mode;
- Event.chain(Event.any<MouseEvent | StandardKeyboardEvent>(onKeyDown, onClearClick.event))
- .event(this.onEventOrInput, this, this.enabledDisposables);
+ if (this.widget) {
+ this.widget.mode = this._mode;
+ }
- this.filter.pattern = '';
this.tree.refilter();
this.render();
- this._enabled = true;
- this.triggered = false;
+ this._onDidChangeMode.fire(mode);
+ }
+
+ private widget: FindWidget<T, TFilterData> | undefined;
+ private styles: IFindWidgetStyles | undefined;
+ private width = 0;
+
+ private readonly _onDidChangeMode = new Emitter<TreeFindMode>();
+ readonly onDidChangeMode = this._onDidChangeMode.event;
+
+ private readonly _onDidChangePattern = new Emitter<string>();
+ readonly onDidChangePattern = this._onDidChangePattern.event;
+
+ private readonly _onDidChangeOpenState = new Emitter<boolean>();
+ readonly onDidChangeOpenState = this._onDidChangeOpenState.event;
+
+ private enabledDisposables = new DisposableStore();
+ private readonly disposables = new DisposableStore();
+
+ constructor(
+ private tree: AbstractTree<T, TFilterData, any>,
+ model: ITreeModel<T, TFilterData, any>,
+ private view: List<ITreeNode<T, TFilterData>>,
+ private filter: FindFilter<T>,
+ private readonly contextViewProvider: IContextViewProvider
+ ) {
+ this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight;
+ model.onDidSplice(this.onDidSpliceModel, this, this.disposables);
}
- private disable(): void {
- if (!this._enabled) {
+ open(): void {
+ if (this.widget) {
+ this.widget.focus();
+ this.widget.select();
return;
}
- this.domNode.remove();
- this.enabledDisposables.clear();
- this.tree.refilter();
- this.render();
- this._enabled = false;
- this.triggered = false;
+ this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this._mode, this.styles);
+ this.enabledDisposables.add(this.widget);
+
+ this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables);
+ this.widget.onDidChangeMode(mode => this.mode = mode, undefined, this.enabledDisposables);
+ this.widget.onDidDisable(this.close, this, this.enabledDisposables);
+
+ this.widget.layout(this.width);
+ this.widget.focus();
+
+ this._onDidChangeOpenState.fire(true);
}
- private onEventOrInput(e: MouseEvent | StandardKeyboardEvent | string): void {
- if (typeof e === 'string') {
- this.onInput(e);
- } else if (e instanceof MouseEvent || e.keyCode === KeyCode.Escape || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey))) {
- this.onInput('');
- } else if (e.keyCode === KeyCode.Backspace) {
- this.onInput(this.pattern.length === 0 ? '' : this.pattern.substr(0, this.pattern.length - 1));
- } else {
- this.onInput(this.pattern + e.browserEvent.key);
+ close(): void {
+ if (!this.widget) {
+ return;
}
- }
- private onInput(pattern: string): void {
- const container = this.view.getHTMLElement();
+ this.widget = undefined;
- if (pattern && !this.domNode.parentElement) {
- container.append(this.domNode);
- } else if (!pattern && this.domNode.parentElement) {
- this.domNode.remove();
- this.tree.domFocus();
- }
+ this.enabledDisposables.dispose();
+ this.enabledDisposables = new DisposableStore();
+ this.onDidChangeValue('');
+ this.tree.domFocus();
+
+ this._onDidChangeOpenState.fire(false);
+ }
+
+ private onDidChangeValue(pattern: string): void {
this._pattern = pattern;
this._onDidChangePattern.fire(pattern);
@@ -836,75 +927,10 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
}
this.render();
-
- if (!pattern) {
- this.triggered = false;
- }
- }
-
- private onDragStart(): void {
- const container = this.view.getHTMLElement();
- const { left } = getDomNodePagePosition(container);
- const containerWidth = container.clientWidth;
- const midContainerWidth = containerWidth / 2;
- const width = this.domNode.clientWidth;
- const disposables = new DisposableStore();
- let positionClassName = this.positionClassName;
-
- const updatePosition = () => {
- switch (positionClassName) {
- case 'nw':
- this.domNode.style.top = `4px`;
- this.domNode.style.left = `4px`;
- break;
- case 'ne':
- this.domNode.style.top = `4px`;
- this.domNode.style.left = `${containerWidth - width - 6}px`;
- break;
- }
- };
-
- const onDragOver = (event: DragEvent) => {
- event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
-
- const x = event.clientX - left;
- if (event.dataTransfer) {
- event.dataTransfer.dropEffect = 'none';
- }
-
- if (x < midContainerWidth) {
- positionClassName = 'nw';
- } else {
- positionClassName = 'ne';
- }
-
- updatePosition();
- };
-
- const onDragEnd = () => {
- this.positionClassName = positionClassName;
- this.domNode.className = `monaco-list-type-filter ${this.positionClassName}`;
- this.domNode.style.top = '';
- this.domNode.style.left = '';
-
- dispose(disposables);
- };
-
- updatePosition();
- this.domNode.classList.remove(positionClassName);
-
- this.domNode.classList.add('dragging');
- disposables.add(toDisposable(() => this.domNode.classList.remove('dragging')));
-
- disposables.add(addDisposableListener(document, 'dragover', e => onDragOver(e)));
- disposables.add(addDisposableListener(this.domNode, 'dragend', () => onDragEnd()));
-
- StaticDND.CurrentDragAndDropData = new DragAndDropData('vscode-ui');
- disposables.add(toDisposable(() => StaticDND.CurrentDragAndDropData = undefined));
}
private onDidSpliceModel(): void {
- if (!this._enabled || this.pattern.length === 0) {
+ if (!this.widget || this.pattern.length === 0) {
return;
}
@@ -912,46 +938,18 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
this.render();
}
- private onDidChangeFilterOnType(): void {
- this.tree.updateOptions({ filterOnType: this.filterOnTypeDomNode.checked });
- this.tree.refilter();
- this.tree.domFocus();
- this.render();
- this.updateFilterOnTypeTitleAndIcon();
- }
-
- private updateFilterOnTypeTitleAndIcon(): void {
- if (this.filterOnType) {
- this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOff.classNamesArray);
- this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOn.classNamesArray);
- this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type");
- } else {
- this.filterOnTypeDomNode.classList.remove(...Codicon.treeFilterOnTypeOn.classNamesArray);
- this.filterOnTypeDomNode.classList.add(...Codicon.treeFilterOnTypeOff.classNamesArray);
- this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type");
- }
- }
-
private render(): void {
const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0;
- if (this.pattern && this.tree.options.filterOnType && noMatches) {
- this.messageDomNode.textContent = localize('empty', "No elements found");
- this._empty = true;
+ if (this.pattern && noMatches) {
+ this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") });
} else {
- this.messageDomNode.innerText = '';
- this._empty = false;
+ this.widget?.clearMessage();
}
-
- this.domNode.classList.toggle('no-matches', noMatches);
- this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount);
- this.labelDomNode.textContent = this.pattern.length > 16 ? '…' + this.pattern.substr(this.pattern.length - 16) : this.pattern;
-
- this._onDidChangeEmptyState.fire(this._empty);
}
shouldAllowFocus(node: ITreeNode<T, TFilterData>): boolean {
- if (!this.enabled || !this.pattern || this.filterOnType) {
+ if (!this.widget || !this.pattern || this._mode === TreeFindMode.Filter) {
return true;
}
@@ -962,16 +960,20 @@ class TypeFilterController<T, TFilterData> implements IDisposable {
return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore);
}
- dispose() {
- if (this._enabled) {
- this.domNode.remove();
- this.enabledDisposables.dispose();
- this._enabled = false;
- this.triggered = false;
- }
+ style(styles: IFindWidgetStyles): void {
+ this.styles = styles;
+ this.widget?.style(styles);
+ }
+
+ layout(width: number): void {
+ this.width = width;
+ this.widget?.layout(width);
+ }
+ dispose() {
this._onDidChangePattern.dispose();
- dispose(this.disposables);
+ this.enabledDisposables.dispose();
+ this.disposables.dispose();
}
}
@@ -982,6 +984,8 @@ function asTreeMouseEvent<T>(event: IListMouseEvent<ITreeNode<T, any>>): ITreeMo
target = TreeMouseEventTarget.Twistie;
} else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tl-contents', 'monaco-tl-row')) {
target = TreeMouseEventTarget.Element;
+ } else if (hasParentWithClass(event.browserEvent.target as HTMLElement, 'monaco-tree-type-filter', 'monaco-list')) {
+ target = TreeMouseEventTarget.Filter;
}
return {
@@ -1005,9 +1009,9 @@ export interface IKeyboardNavigationEventFilter {
export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
readonly multipleSelectionSupport?: boolean;
- readonly automaticKeyboardNavigation?: boolean;
- readonly simpleKeyboardNavigation?: boolean;
- readonly filterOnType?: boolean;
+ readonly typeNavigationEnabled?: boolean;
+ readonly typeNavigationMode?: TypeNavigationMode;
+ readonly defaultFindMode?: TreeFindMode;
readonly smoothScrolling?: boolean;
readonly horizontalScrolling?: boolean;
readonly mouseWheelScrollSensitivity?: number;
@@ -1017,6 +1021,7 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
}
export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTreeOptionsUpdate, IListOptions<T> {
+ readonly contextViewProvider?: IContextViewProvider;
readonly collapseByDefault?: boolean; // defaults to false
readonly filter?: ITreeFilter<T, TFilterData>;
readonly dnd?: ITreeDragAndDrop<T>;
@@ -1318,7 +1323,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
private selection: Trait<T>;
private anchor: Trait<T>;
private eventBufferer = new EventBufferer();
- private typeFilterController?: TypeFilterController<T, TFilterData>;
+ private findController?: FindController<T, TFilterData>;
+ readonly onDidChangeFindOpenState: Event<boolean> = Event.None;
private focusNavigationFilter: ((node: ITreeNode<T, TFilterData>) => boolean) | undefined;
private styleElement: HTMLStyleElement;
protected readonly disposables = new DisposableStore();
@@ -1329,7 +1335,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
get onDidChangeSelection(): Event<ITreeEvent<T>> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); }
get onMouseClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); }
- get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); }
+ get onMouseDblClick(): Event<ITreeMouseEvent<T>> { return Event.filter(Event.map(this.view.onMouseDblClick, asTreeMouseEvent), e => e.target !== TreeMouseEventTarget.Filter); }
get onContextMenu(): Event<ITreeContextMenuEvent<T>> { return Event.map(this.view.onContextMenu, asTreeContextMenuEvent); }
get onTap(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onTap, asTreeMouseEvent); }
get onPointer(): Event<ITreeMouseEvent<T>> { return Event.map(this.view.onPointer, asTreeMouseEvent); }
@@ -1348,8 +1354,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
private readonly _onWillRefilter = new Emitter<void>();
readonly onWillRefilter: Event<void> = this._onWillRefilter.event;
- get filterOnType(): boolean { return !!this._options.filterOnType; }
- get onDidChangeTypeFilterPattern(): Event<string> { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; }
+ get findMode(): TreeFindMode { return this.findController?.mode ?? TreeFindMode.Highlight; }
+ set findMode(findMode: TreeFindMode) { if (this.findController) { this.findController.mode = findMode; } }
+ readonly onDidChangeFindMode: Event<TreeFindMode>;
+
+ get onDidChangeFindPattern(): Event<string> { return this.findController ? this.findController.onDidChangePattern : Event.None; }
get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; }
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? true : this._options.expandOnlyOnTwistieClick; }
@@ -1376,10 +1385,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
this.disposables.add(r);
}
- let filter: TypeFilter<T> | undefined;
+ let filter: FindFilter<T> | undefined;
if (_options.keyboardNavigationLabelProvider) {
- filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
+ filter = new FindFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter<T, FuzzyScore>);
_options = { ..._options, filter: filter as ITreeFilter<T, TFilterData> }; // TODO need typescript help here
this.disposables.add(filter);
}
@@ -1432,11 +1441,14 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
onKeyDown.filter(e => e.keyCode === KeyCode.Space).on(this.onSpace, this, this.disposables);
}
- if (_options.keyboardNavigationLabelProvider) {
- const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
- this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate);
- this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node);
- this.disposables.add(this.typeFilterController!);
+ if (_options.keyboardNavigationLabelProvider && _options.contextViewProvider) {
+ this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider);
+ this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node);
+ this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState;
+ this.disposables.add(this.findController!);
+ this.onDidChangeFindMode = this.findController.onDidChangeMode;
+ } else {
+ this.onDidChangeFindMode = Event.None;
}
this.styleElement = createStyleSheet(this.view.getHTMLElement());
@@ -1450,13 +1462,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
renderer.updateOptions(optionsUpdate);
}
- this.view.updateOptions({
- ...this._options,
- enableKeyboardNavigation: this._options.simpleKeyboardNavigation,
- });
-
- this.typeFilterController?.updateOptions(this._options);
-
+ this.view.updateOptions(this._options);
this._onDidUpdateOptions.fire(this._options);
this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always);
@@ -1483,21 +1489,11 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
get contentHeight(): number {
- if (this.typeFilterController && this.typeFilterController.filterOnType && this.typeFilterController.empty) {
- return 100;
- }
-
return this.view.contentHeight;
}
get onDidChangeContentHeight(): Event<number> {
- let result = this.view.onDidChangeContentHeight;
-
- if (this.typeFilterController) {
- result = Event.any(result, Event.map(this.typeFilterController.onDidChangeEmptyState, () => this.contentHeight));
- }
-
- return result;
+ return this.view.onDidChangeContentHeight;
}
get scrollTop(): number {
@@ -1559,6 +1555,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
layout(height?: number, width?: number): void {
this.view.layout(height, width);
+
+ if (isNumber(width)) {
+ this.findController?.layout(width);
+ }
}
style(styles: IListStyles): void {
@@ -1571,6 +1571,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
}
this.styleElement.textContent = content.join('\n');
+
+ this.findController?.style(styles);
this.view.style(styles);
}
@@ -1624,12 +1626,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
return this.model.isCollapsed(location);
}
- toggleKeyboardNavigation(): void {
- this.view.toggleKeyboardNavigation();
+ triggerTypeNavigation(): void {
+ this.view.triggerTypeNavigation();
+ }
- if (this.typeFilterController) {
- this.typeFilterController.toggle();
- }
+ openFind(): void {
+ this.findController?.open();
+ }
+
+ closeFind(): void {
+ this.findController?.close();
}
refilter(): void {