Support find widget in lists/trees (#152481)

* replace list type filter and tree type label controller with list type
navigation and tree find. use proper FindInput widget

* make sure vim doesn't break

* polish outline use case

* 💄

* remove unused import
This commit is contained in:
João Moreno 2022-07-18 16:25:05 +02:00 committed by GitHub
parent 34507a8db3
commit 4b0d6f3bbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 632 additions and 594 deletions

View file

@ -6,7 +6,7 @@
import * as dom from 'vs/base/browser/dom';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle';
import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles';
import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
@ -31,6 +31,7 @@ export interface IFindInputOptions extends IFindInputStyles {
readonly appendWholeWordsLabel?: string;
readonly appendRegexLabel?: string;
readonly history?: string[];
readonly additionalToggles?: Toggle[];
readonly showHistoryHint?: () => boolean;
}
@ -74,6 +75,7 @@ export class FindInput extends Widget {
protected regex: RegexToggle;
protected wholeWords: WholeWordsToggle;
protected caseSensitive: CaseSensitiveToggle;
protected additionalToggles: Toggle[] = [];
public domNode: HTMLElement;
public inputBox: HistoryInputBox;
@ -209,10 +211,6 @@ export class FindInput extends Widget {
this._onCaseSensitiveKeyDown.fire(e);
}));
if (this._showOptionButtons) {
this.inputBox.paddingRight = this.caseSensitive.width() + this.wholeWords.width() + this.regex.width();
}
// Arrow-Key support to navigate between options
const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode];
this.onkeydown(this.domNode, (event: IKeyboardEvent) => {
@ -250,6 +248,34 @@ export class FindInput extends Widget {
this.controls.appendChild(this.wholeWords.domNode);
this.controls.appendChild(this.regex.domNode);
if (!this._showOptionButtons) {
this.caseSensitive.domNode.style.display = 'none';
this.wholeWords.domNode.style.display = 'none';
this.regex.domNode.style.display = 'none';
}
for (const toggle of options?.additionalToggles ?? []) {
this._register(toggle);
this.controls.appendChild(toggle.domNode);
this._register(toggle.onChange(viaKeyboard => {
this._onDidOptionChange.fire(viaKeyboard);
if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) {
this.inputBox.focus();
}
}));
this.additionalToggles.push(toggle);
}
if (this.additionalToggles.length > 0) {
this.controls.style.display = 'block';
}
this.inputBox.paddingRight =
(this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0)
+ this.additionalToggles.reduce((r, t) => r + t.width(), 0);
this.domNode.appendChild(this.controls);
parent?.appendChild(this.domNode);
@ -282,6 +308,10 @@ export class FindInput extends Widget {
this.regex.enable();
this.wholeWords.enable();
this.caseSensitive.enable();
for (const toggle of this.additionalToggles) {
toggle.enable();
}
}
public disable(): void {
@ -290,6 +320,10 @@ export class FindInput extends Widget {
this.regex.disable();
this.wholeWords.disable();
this.caseSensitive.disable();
for (const toggle of this.additionalToggles) {
toggle.disable();
}
}
public setFocusInputOnOptionClick(value: boolean): void {
@ -356,6 +390,10 @@ export class FindInput extends Widget {
this.wholeWords.style(toggleStyles);
this.caseSensitive.style(toggleStyles);
for (const toggle of this.additionalToggles) {
toggle.style(toggleStyles);
}
const inputBoxStyles: IInputBoxStyles = {
inputBackground: this.inputBackground,
inputForeground: this.inputForeground,

View file

@ -65,72 +65,7 @@
z-index: 1000;
}
/* Type filter */
.monaco-list-type-filter {
display: flex;
align-items: center;
position: absolute;
border-radius: 2px;
padding: 0px 3px;
max-width: calc(100% - 10px);
text-overflow: ellipsis;
overflow: hidden;
text-align: right;
box-sizing: border-box;
cursor: all-scroll;
font-size: 13px;
line-height: 18px;
height: 20px;
z-index: 1;
top: 4px;
}
.monaco-list-type-filter.dragging {
transition: top 0.2s, left 0.2s;
}
.monaco-list-type-filter.ne {
right: 4px;
}
.monaco-list-type-filter.nw {
left: 4px;
}
.monaco-list-type-filter > .controls {
display: flex;
align-items: center;
box-sizing: border-box;
transition: width 0.2s;
width: 0;
}
.monaco-list-type-filter.dragging > .controls,
.monaco-list-type-filter:hover > .controls {
width: 36px;
}
.monaco-list-type-filter > .controls > * {
border: none;
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
background: none;
width: 16px;
height: 16px;
flex-shrink: 0;
margin: 0;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.monaco-list-type-filter > .controls > .filter {
margin-left: 4px;
}
/* Filter */
.monaco-list-type-filter-message {
position: absolute;
@ -149,13 +84,3 @@
.monaco-list-type-filter-message:empty {
display: none;
}
/* Electron */
.monaco-list-type-filter {
cursor: grab;
}
.monaco-list-type-filter.dragging {
cursor: grabbing;
}

View file

@ -12,7 +12,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IThemable } from 'vs/base/common/styler';
import 'vs/css!./list';
import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list';
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List } from './listWidget';
import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget';
export interface IPagedRenderer<TElement, TTemplateData> extends IListRenderer<TElement, TTemplateData> {
renderPlaceholder(index: number, templateData: TTemplateData): void;
@ -95,8 +95,8 @@ class PagedAccessibilityProvider<T> implements IListAccessibilityProvider<number
}
export interface IPagedListOptions<T> {
readonly enableKeyboardNavigation?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
readonly ariaLabel?: string;
readonly keyboardSupport?: boolean;
readonly multipleSelectionSupport?: boolean;
@ -282,8 +282,8 @@ export class PagedList<T> implements IThemable, IDisposable {
this.list.layout(height, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.list.triggerTypeNavigation();
}
reveal(index: number, relativeTop?: number): void {

View file

@ -9,6 +9,7 @@ import { DomEmitter, stopEvent } from 'vs/base/browser/event';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Gesture } from 'vs/base/browser/touch';
import { alert } from 'vs/base/browser/ui/aria/aria';
import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions';
import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays';
@ -384,7 +385,12 @@ class KeyboardController<T> implements IDisposable {
}
}
enum TypeLabelControllerState {
export enum TypeNavigationMode {
Automatic,
Trigger
}
enum TypeNavigationControllerState {
Idle,
Typing
}
@ -402,12 +408,12 @@ export const DefaultKeyboardNavigationDelegate = new class implements IKeyboardN
}
};
class TypeLabelController<T> implements IDisposable {
class TypeNavigationController<T> implements IDisposable {
private enabled = false;
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
private state: TypeNavigationControllerState = TypeNavigationControllerState.Idle;
private automaticKeyboardNavigation = true;
private mode = TypeNavigationMode.Automatic;
private triggered = false;
private previouslyFocused = -1;
@ -424,20 +430,16 @@ class TypeLabelController<T> implements IDisposable {
}
updateOptions(options: IListOptions<T>): void {
const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation;
if (enableKeyboardNavigation) {
if (options.typeNavigationEnabled ?? true) {
this.enable();
} else {
this.disable();
}
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
}
this.mode = options.typeNavigationMode ?? TypeNavigationMode.Automatic;
}
toggle(): void {
trigger(): void {
this.triggered = !this.triggered;
}
@ -448,10 +450,10 @@ class TypeLabelController<T> implements IDisposable {
const onChar = this.enabledDisposables.add(Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event))
.filter(e => !isInputElement(e.target as HTMLElement))
.filter(() => this.automaticKeyboardNavigation || this.triggered)
.filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered)
.map(event => new StandardKeyboardEvent(event))
.filter(e => this.delegate.mightProducePrintableCharacter(e))
.forEach(e => e.preventDefault())
.forEach(e => { e.preventDefault(); e.stopPropagation(); })
.map(event => event.browserEvent.key)
.event;
@ -490,15 +492,15 @@ class TypeLabelController<T> implements IDisposable {
private onInput(word: string | null): void {
if (!word) {
this.state = TypeLabelControllerState.Idle;
this.state = TypeNavigationControllerState.Idle;
this.triggered = false;
return;
}
const focus = this.list.getFocus();
const start = focus.length > 0 ? focus[0] : 0;
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
this.state = TypeLabelControllerState.Typing;
const delta = this.state === TypeNavigationControllerState.Idle ? 1 : 0;
this.state = TypeNavigationControllerState.Typing;
for (let i = 0; i < this.list.length; i++) {
const index = (start + i + delta) % this.list.length;
@ -895,22 +897,6 @@ export class DefaultStyleController implements IStyleController {
`);
}
if (styles.listFilterWidgetBackground) {
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
}
if (styles.listFilterWidgetOutline) {
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
}
if (styles.listFilterWidgetNoMatchesOutline) {
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
}
if (styles.listMatchesShadow) {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
if (styles.tableColumnsBorder) {
content.push(`
.monaco-table:hover > .monaco-split-view2,
@ -934,8 +920,8 @@ export class DefaultStyleController implements IStyleController {
}
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
readonly enableKeyboardNavigation?: boolean;
readonly automaticKeyboardNavigation?: boolean;
readonly typeNavigationEnabled?: boolean;
readonly typeNavigationMode?: TypeNavigationMode;
readonly multipleSelectionSupport?: boolean;
}
@ -964,7 +950,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
readonly alwaysConsumeMouseWheel?: boolean;
}
export interface IListStyles {
export interface IListStyles extends IFindInputStyles {
listBackground?: Color;
listFocusBackground?: Color;
listFocusForeground?: Color;
@ -989,7 +975,7 @@ export interface IListStyles {
listFilterWidgetBackground?: Color;
listFilterWidgetOutline?: Color;
listFilterWidgetNoMatchesOutline?: Color;
listMatchesShadow?: Color;
listFilterWidgetShadow?: Color;
treeIndentGuidesStroke?: Color;
tableColumnsBorder?: Color;
tableOddRowsBackgroundColor?: Color;
@ -1247,7 +1233,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
protected view: ListView<T>;
private spliceable: ISpliceable<T>;
private styleController: IStyleController;
private typeLabelController?: TypeLabelController<T>;
private typeNavigationController?: TypeNavigationController<T>;
private accessibilityProvider?: IListAccessibilityProvider<T>;
private keyboardController: KeyboardController<T> | undefined;
private mouseController: MouseController<T>;
@ -1387,8 +1373,8 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
if (_options.keyboardNavigationLabelProvider) {
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
this.disposables.add(this.typeLabelController);
this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
this.disposables.add(this.typeNavigationController);
}
this.mouseController = this.createMouseController(_options);
@ -1413,7 +1399,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
updateOptions(optionsUpdate: IListOptionsUpdate = {}): void {
this._options = { ...this._options, ...optionsUpdate };
this.typeLabelController?.updateOptions(this._options);
this.typeNavigationController?.updateOptions(this._options);
if (this._options.multipleSelectionController !== undefined) {
if (this._options.multipleSelectionSupport) {
@ -1529,9 +1515,9 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
this.view.layout(height, width);
}
toggleKeyboardNavigation(): void {
if (this.typeLabelController) {
this.typeLabelController.toggle();
triggerTypeNavigation(): void {
if (this.typeNavigationController) {
this.typeNavigationController.trigger();
}
}

View file

@ -265,8 +265,8 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable {
this.list.layout(listHeight, width);
}
toggleKeyboardNavigation(): void {
this.list.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.list.triggerTypeNavigation();
}
style(styles: ITableStyles): void {

View file

@ -148,6 +148,7 @@ export class Toggle extends Widget {
this.checked = !this._checked;
this._onChange.fire(true);
keyboardEvent.preventDefault();
keyboardEvent.stopPropagation();
return;
}

View file

@ -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
});
}
}
export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { }
export interface IFindWidgetOpts extends IFindWidgetStyles { }
export enum TreeFindMode {
Highlight,
Filter
}
class FindWidget<T, TFilterData> extends Disposable {
private readonly elements = h('div.monaco-tree-type-filter', [
h('div.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper', { $: 'grab' }),
h('div.monaco-tree-type-filter-input', { $: 'findInput' }),
h('div.monaco-tree-type-filter-actionbar', { $: 'actionbar' }),
]);
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 modeToggle: ModeToggle;
private readonly findInput: FindInput;
private readonly actionbar: ActionBar;
private width = 0;
private right = 4;
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>,
contextViewProvider: IContextViewProvider,
mode: TreeFindMode,
options?: IFindWidgetOpts
) {
super();
container.appendChild(this.elements.root);
this._register(toDisposable(() => container.removeChild(this.elements.root)));
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.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, {
label: localize('type to search', "Type to search"),
additionalToggles: [this.modeToggle]
}));
this.actionbar = this._register(new ActionBar(this.elements.actionbar));
this.mode = mode;
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;
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 ?? {});
}
style(styles: IFindWidgetStyles): void {
this.findInput.style(styles);
if (styles.listFilterWidgetBackground) {
this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString();
}
if (styles.listFilterWidgetShadow) {
this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`;
}
}
focus() {
this.findInput.focus();
}
select() {
this.findInput.select();
}
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`;
}
showMessage(message: IMessage): void {
this.findInput.showMessage(message);
}
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 _filterOnType: boolean;
get filterOnType(): boolean { return this._filterOnType; }
private _mode: TreeFindMode;
get mode(): TreeFindMode { return this._mode; }
set mode(mode: TreeFindMode) {
if (mode === this._mode) {
return;
}
private _empty: boolean = false;
get empty(): boolean { return this._empty; }
this._mode = mode;
private readonly _onDidChangeEmptyState = new Emitter<boolean>();
readonly onDidChangeEmptyState: Event<boolean> = Event.latch(this._onDidChangeEmptyState.event);
if (this.widget) {
this.widget.mode = this._mode;
}
private positionClassName = 'ne';
private domNode: HTMLElement;
private messageDomNode: HTMLElement;
private labelDomNode: HTMLElement;
private filterOnTypeDomNode: HTMLInputElement;
private clearDomNode: HTMLElement;
private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
this.tree.refilter();
this.render();
this._onDidChangeMode.fire(mode);
}
private automaticKeyboardNavigation = true;
private triggered = false;
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 enabledDisposables = new DisposableStore();
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: TypeFilter<T>,
private keyboardNavigationDelegate: IKeyboardNavigationDelegate
private filter: FindFilter<T>,
private readonly contextViewProvider: IContextViewProvider
) {
this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`);
this.domNode.draggable = true;
this.disposables.add(addDisposableListener(this.domNode, 'dragstart', () => this.onDragStart()));
this.messageDomNode = append(view.getHTMLElement(), $(`.monaco-list-type-filter-message`));
this.labelDomNode = append(this.domNode, $('span.label'));
const controls = append(this.domNode, $('.controls'));
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.clearDomNode = append(controls, $<HTMLInputElement>('button.clear' + Codicon.treeFilterClear.cssSelector));
this.clearDomNode.tabIndex = -1;
this.clearDomNode.title = localize('clear', "Clear");
this.keyboardNavigationEventFilter = tree.options.keyboardNavigationEventFilter;
this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight;
model.onDidSplice(this.onDidSpliceModel, this, this.disposables);
this.updateOptions(tree.options);
}
updateOptions(options: IAbstractTreeOptions<T, TFilterData>): void {
if (options.simpleKeyboardNavigation) {
this.disable();
} else {
this.enable();
}
if (typeof options.filterOnType !== 'undefined') {
this._filterOnType = !!options.filterOnType;
this.filterOnTypeDomNode.checked = this._filterOnType;
this.updateFilterOnTypeTitleAndIcon();
}
if (typeof options.automaticKeyboardNavigation !== 'undefined') {
this.automaticKeyboardNavigation = options.automaticKeyboardNavigation;
}
this.tree.refilter();
this.render();
if (!this.automaticKeyboardNavigation) {
this.onEventOrInput('');
}
}
toggle(): void {
this.triggered = !this.triggered;
if (!this.triggered) {
this.onEventOrInput('');
}
}
private enable(): void {
if (this._enabled) {
open(): void {
if (this.widget) {
this.widget.focus();
this.widget.select();
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;
this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this._mode, this.styles);
this.enabledDisposables.add(this.widget);
const onClearClick = this.enabledDisposables.add(new DomEmitter(this.clearDomNode, 'click'));
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);
Event.chain(Event.any<MouseEvent | StandardKeyboardEvent>(onKeyDown, onClearClick.event))
.event(this.onEventOrInput, this, this.enabledDisposables);
this.widget.layout(this.width);
this.widget.focus();
this.filter.pattern = '';
this.tree.refilter();
this.render();
this._enabled = true;
this.triggered = false;
this._onDidChangeOpenState.fire(true);
}
private disable(): void {
if (!this._enabled) {
close(): void {
if (!this.widget) {
return;
}
this.domNode.remove();
this.enabledDisposables.clear();
this.tree.refilter();
this.render();
this._enabled = false;
this.triggered = false;
this.widget = undefined;
this.enabledDisposables.dispose();
this.enabledDisposables = new DisposableStore();
this.onDidChangeValue('');
this.tree.domFocus();
this._onDidChangeOpenState.fire(false);
}
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);
}
}
private onInput(pattern: string): void {
const container = this.view.getHTMLElement();
if (pattern && !this.domNode.parentElement) {
container.append(this.domNode);
} else if (!pattern && this.domNode.parentElement) {
this.domNode.remove();
this.tree.domFocus();
}
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 {

View file

@ -7,7 +7,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd';
import { IIdentityProvider, IListDragAndDrop, IListDragOverReaction, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree';
import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel';
import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
@ -341,7 +341,12 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
get onDidUpdateOptions(): Event<IAsyncDataTreeOptionsUpdate> { return this.tree.onDidUpdateOptions; }
get filterOnType(): boolean { return this.tree.filterOnType; }
get onDidChangeFindOpenState(): Event<boolean> { return this.tree.onDidChangeFindOpenState; }
get findMode(): TreeFindMode { return this.tree.findMode; }
set findMode(mode: TreeFindMode) { this.tree.findMode = mode; }
readonly onDidChangeFindMode: Event<TreeFindMode>;
get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) {
if (typeof this.tree.expandOnlyOnTwistieClick === 'boolean') {
return this.tree.expandOnlyOnTwistieClick;
@ -367,6 +372,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
this.collapseByDefault = options.collapseByDefault;
this.tree = this.createTree(user, container, delegate, renderers, options);
this.onDidChangeFindMode = this.tree.onDidChangeFindMode;
this.root = createAsyncDataTreeNode({
element: undefined!,
@ -616,8 +622,16 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
return this.tree.isCollapsed(this.getDataNode(element));
}
toggleKeyboardNavigation(): void {
this.tree.toggleKeyboardNavigation();
triggerTypeNavigation(): void {
this.tree.triggerTypeNavigation();
}
openFind(): void {
this.tree.openFind();
}
closeFind(): void {
this.tree.closeFind();
}
refilter(): void {

View file

@ -67,3 +67,45 @@
/* Use steps to throttle FPS to reduce CPU usage */
animation: codicon-spin 1.25s steps(30) infinite;
}
.monaco-tree-type-filter {
position: absolute;
top: 0;
display: flex;
padding: 3px;
transition: top 0.3s;
width: 160px;
z-index: 100;
}
.monaco-tree-type-filter.disabled {
top: -40px;
}
.monaco-tree-type-filter-grab {
display: flex !important;
align-items: center;
justify-content: center;
cursor: grab;
}
.monaco-tree-type-filter-grab.grabbing {
cursor: grabbing;
}
.monaco-tree-type-filter-input {
flex: 1;
}
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .input,
.monaco-tree-type-filter-input .monaco-inputbox > .ibwrapper > .mirror {
padding: 2px;
}
.monaco-tree-type-filter-actionbar {
margin-left: 4px;
}
.monaco-tree-type-filter-actionbar .monaco-action-bar .action-label {
padding: 2px;
}

View file

@ -141,7 +141,8 @@ export interface ITreeEvent<T> {
export enum TreeMouseEventTarget {
Unknown,
Twistie,
Element
Element,
Filter
}
export interface ITreeMouseEvent<T> {

View file

@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { createStyleSheet } from 'vs/base/browser/dom';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedListOptions, IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging';
import { DefaultStyleController, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget';
import { DefaultStyleController, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget';
import { ITableColumn, ITableRenderer, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table';
import { ITableOptions, ITableOptionsUpdate, Table } from 'vs/base/browser/ui/table/tableWidget';
import { IAbstractTreeOptions, IAbstractTreeOptionsUpdate, IKeyboardNavigationEventFilter, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { TreeFindMode, IAbstractTreeOptions, IAbstractTreeOptionsUpdate, IKeyboardNavigationEventFilter, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { AsyncDataTree, CompressibleAsyncDataTree, IAsyncDataTreeOptions, IAsyncDataTreeOptionsUpdate, ICompressibleAsyncDataTreeOptions, ICompressibleAsyncDataTreeOptionsUpdate, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree';
import { CompressibleObjectTree, ICompressibleObjectTreeOptions, ICompressibleObjectTreeOptionsUpdate, ICompressibleTreeRenderer, IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
@ -17,13 +18,13 @@ import { IAsyncDataSource, IDataSource, ITreeEvent, ITreeRenderer } from 'vs/bas
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { createDecorator, IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler';
@ -112,7 +113,7 @@ export class ListService implements IListService {
}
}
const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);
export const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);
export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey<boolean>('listSupportsMultiselect', true);
export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey));
export const WorkbenchListHasSelectionOrFocus = new RawContextKey<boolean>('listHasSelectionOrFocus', false);
@ -123,7 +124,13 @@ export const WorkbenchTreeElementCanCollapse = new RawContextKey<boolean>('treeE
export const WorkbenchTreeElementHasParent = new RawContextKey<boolean>('treeElementHasParent', false);
export const WorkbenchTreeElementCanExpand = new RawContextKey<boolean>('treeElementCanExpand', false);
export const WorkbenchTreeElementHasChild = new RawContextKey<boolean>('treeElementHasChild', false);
export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation';
export const WorkbenchTreeFindOpen = new RawContextKey<boolean>('treeFindOpen', false);
const WorkbenchListTypeNavigationModeKey = 'listTypeNavigationMode';
/**
* @deprecated in favor of WorkbenchListTypeNavigationModeKey
*/
const WorkbenchListAutomaticKeyboardNavigationLegacyKey = 'listAutomaticKeyboardNavigation';
function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService {
const result = contextKeyService.createScoped(widget.getHTMLElement());
@ -134,8 +141,9 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
const openModeSettingKey = 'workbench.list.openMode';
const horizontalScrollingKey = 'workbench.list.horizontalScrolling';
const defaultFindModeSettingKey = 'workbench.list.defaultFindMode';
/** @deprecated in favor of workbench.list.defaultFindMode */
const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';
const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation';
const treeIndentKey = 'workbench.tree.indent';
const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides';
const listSmoothScrolling = 'workbench.list.smoothScrolling';
@ -840,17 +848,16 @@ export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void>
delegate: IListVirtualDelegate<T>,
renderers: ITreeRenderer<T, TFilterData, any>[],
options: IWorkbenchObjectTreeOptions<T, TFilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
@IConfigurationService configurationService: IConfigurationService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble<T, TFilterData, IWorkbenchObjectTreeOptions<T, TFilterData>>(container, options, contextKeyService, configurationService, keybindingService, accessibilityService);
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any);
super(user, container, delegate, renderers, treeOptions);
this.disposables.add(disposable);
this.internals = new WorkbenchTreeInternals(this, options, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService);
this.disposables.add(this.internals);
}
@ -882,17 +889,16 @@ export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilter
delegate: IListVirtualDelegate<T>,
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
options: IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
@IConfigurationService configurationService: IConfigurationService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble<T, TFilterData, IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>>(container, options, contextKeyService, configurationService, keybindingService, accessibilityService);
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any);
super(user, container, delegate, renderers, treeOptions);
this.disposables.add(disposable);
this.internals = new WorkbenchTreeInternals(this, options, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService);
this.disposables.add(this.internals);
}
@ -930,17 +936,16 @@ export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<T
renderers: ITreeRenderer<T, TFilterData, any>[],
dataSource: IDataSource<TInput, T>,
options: IWorkbenchDataTreeOptions<T, TFilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
@IConfigurationService configurationService: IConfigurationService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble<T, TFilterData, IWorkbenchDataTreeOptions<T, TFilterData>>(container, options, contextKeyService, configurationService, keybindingService, accessibilityService);
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any);
super(user, container, delegate, renderers, dataSource, treeOptions);
this.disposables.add(disposable);
this.internals = new WorkbenchTreeInternals(this, options, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService);
this.disposables.add(this.internals);
}
@ -978,17 +983,16 @@ export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends Async
renderers: ITreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IWorkbenchAsyncDataTreeOptions<T, TFilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
@IConfigurationService configurationService: IConfigurationService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble<T, TFilterData, IWorkbenchAsyncDataTreeOptions<T, TFilterData>>(container, options, contextKeyService, configurationService, keybindingService, accessibilityService);
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any);
super(user, container, delegate, renderers, dataSource, treeOptions);
this.disposables.add(disposable);
this.internals = new WorkbenchTreeInternals(this, options, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService);
this.disposables.add(this.internals);
}
@ -1024,17 +1028,16 @@ export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> e
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
dataSource: IAsyncDataSource<TInput, T>,
options: IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
@IConfigurationService configurationService: IConfigurationService
) {
const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble<T, TFilterData, IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>>(container, options, contextKeyService, configurationService, keybindingService, accessibilityService);
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, container, options as any);
super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);
this.disposables.add(disposable);
this.internals = new WorkbenchTreeInternals(this, options, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService);
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, themeService, configurationService);
this.disposables.add(this.internals);
}
@ -1044,33 +1047,62 @@ export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> e
}
}
function getDefaultTreeFindMode(configurationService: IConfigurationService) {
const value = configurationService.getValue<'highlight' | 'filter'>(defaultFindModeSettingKey);
if (value === 'highlight') {
return TreeFindMode.Highlight;
} else if (value === 'filter') {
return TreeFindMode.Filter;
}
const deprecatedValue = configurationService.getValue<'simple' | 'highlight' | 'filter'>(keyboardNavigationSettingKey);
if (deprecatedValue === 'simple' || deprecatedValue === 'highlight') {
return TreeFindMode.Highlight;
} else if (deprecatedValue === 'filter') {
return TreeFindMode.Filter;
}
return undefined;
}
function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(
accessor: ServicesAccessor,
container: HTMLElement,
options: TOptions,
contextKeyService: IContextKeyService,
configurationService: IConfigurationService,
keybindingService: IKeybindingService,
accessibilityService: IAccessibilityService,
): { options: TOptions; getAutomaticKeyboardNavigation: () => boolean | undefined; disposable: IDisposable } {
const getAutomaticKeyboardNavigation = () => {
// give priority to the context key value to disable this completely
let automaticKeyboardNavigation = Boolean(contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey));
): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } {
const configurationService = accessor.get(IConfigurationService);
const keybindingService = accessor.get(IKeybindingService);
const contextViewService = accessor.get(IContextViewService);
const contextKeyService = accessor.get(IContextKeyService);
if (automaticKeyboardNavigation) {
automaticKeyboardNavigation = Boolean(configurationService.getValue(automaticKeyboardNavigationSettingKey));
const getTypeNavigationMode = () => {
// give priority to the context key value to specify a value
const modeString = contextKeyService.getContextKeyValue<'automatic' | 'trigger'>(WorkbenchListTypeNavigationModeKey);
if (modeString === 'automatic') {
return TypeNavigationMode.Automatic;
} else if (modeString === 'trigger') {
return TypeNavigationMode.Trigger;
}
return automaticKeyboardNavigation;
// also check the deprecated context key to set the mode to 'trigger'
const modeBoolean = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
if (modeBoolean === false) {
return TypeNavigationMode.Trigger;
}
return undefined;
};
const accessibilityOn = accessibilityService.isScreenReaderOptimized();
const keyboardNavigation = options.simpleKeyboardNavigation || accessibilityOn ? 'simple' : configurationService.getValue<string>(keyboardNavigationSettingKey);
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
const [workbenchListOptions, disposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
const additionalScrollHeight = options.additionalScrollHeight;
return {
getAutomaticKeyboardNavigation,
getTypeNavigationMode,
disposable,
options: {
// ...options, // TODO@Joao why is this not splatted here?
@ -1079,14 +1111,13 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
indent: typeof configurationService.getValue(treeIndentKey) === 'number' ? configurationService.getValue(treeIndentKey) : undefined,
renderIndentGuides: configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey),
smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),
automaticKeyboardNavigation: getAutomaticKeyboardNavigation(),
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter',
defaultFindMode: getDefaultTreeFindMode(configurationService),
horizontalScrolling,
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService),
additionalScrollHeight,
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick')
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),
contextViewProvider: contextViewService as IContextViewProvider
} as TOptions
};
}
@ -1106,6 +1137,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
private treeElementHasParent: IContextKey<boolean>;
private treeElementCanExpand: IContextKey<boolean>;
private treeElementHasChild: IContextKey<boolean>;
private treeFindOpen: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
private disposables: IDisposable[] = [];
private styler: IDisposable | undefined;
@ -1116,13 +1148,12 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
constructor(
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
options: IWorkbenchObjectTreeOptions<T, TFilterData> | IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> | IWorkbenchDataTreeOptions<T, TFilterData> | IWorkbenchAsyncDataTreeOptions<T, TFilterData> | IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
getAutomaticKeyboardNavigation: () => boolean | undefined,
getTypeNavigationMode: () => TypeNavigationMode | undefined,
overrideStyles: IColorMapping | undefined,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService private themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IConfigurationService configurationService: IConfigurationService
) {
this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);
@ -1140,20 +1171,10 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService);
this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService);
this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService);
this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
const interestingContextKeys = new Set();
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey);
const updateKeyboardNavigation = () => {
const accessibilityOn = accessibilityService.isScreenReaderOptimized();
const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue<string>(keyboardNavigationSettingKey);
tree.updateOptions({
simpleKeyboardNavigation: keyboardNavigation === 'simple',
filterOnType: keyboardNavigation === 'filter'
});
};
this.updateStyleOverrides(overrideStyles);
const updateCollapseContextKeys = () => {
@ -1170,6 +1191,10 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
this.treeElementHasChild.set(!!tree.getFirstElementChild(focus));
};
const interestingContextKeys = new Set();
interestingContextKeys.add(WorkbenchListTypeNavigationModeKey);
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
this.disposables.push(
this.contextKeyService,
(listService as ListService).register(tree),
@ -1192,6 +1217,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
}),
tree.onDidChangeCollapseState(updateCollapseContextKeys),
tree.onDidChangeModel(updateCollapseContextKeys),
tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)),
configurationService.onDidChangeConfiguration(e => {
let newOptions: IAbstractTreeOptionsUpdate = {};
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
@ -1209,11 +1235,8 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
newOptions = { ...newOptions, smoothScrolling };
}
if (e.affectsConfiguration(keyboardNavigationSettingKey)) {
updateKeyboardNavigation();
}
if (e.affectsConfiguration(automaticKeyboardNavigationSettingKey)) {
newOptions = { ...newOptions, automaticKeyboardNavigation: getAutomaticKeyboardNavigation() };
if (e.affectsConfiguration(defaultFindModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {
tree.updateOptions({ defaultFindMode: getDefaultTreeFindMode(configurationService) });
}
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
@ -1236,10 +1259,9 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
}),
this.contextKeyService.onDidChangeContext(e => {
if (e.affectsSome(interestingContextKeys)) {
tree.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() });
tree.updateOptions({ typeNavigationMode: getTypeNavigationMode() });
}
}),
accessibilityService.onDidChangeScreenReaderOptimized(() => updateKeyboardNavigation())
})
);
this.navigator = new TreeResourceNavigator(tree, { configurationService, ...options });
@ -1334,6 +1356,16 @@ configurationRegistry.registerConfiguration({
default: 5,
description: localize('Fast Scroll Sensitivity', "Scrolling speed multiplier when pressing `Alt`.")
},
[defaultFindModeSettingKey]: {
type: 'string',
enum: ['highlight', 'filter'],
enumDescriptions: [
localize('defaultFindModeSettingKey.highlight', "Highlight elements when searching. Further up and down navigation will traverse only the highlighted elements."),
localize('defaultFindModeSettingKey.filter', "Filter elements when searching.")
],
default: 'highlight',
description: localize('defaultFindModeSettingKey', "Controls the default find mode for lists and trees in the workbench.")
},
[keyboardNavigationSettingKey]: {
type: 'string',
enum: ['simple', 'highlight', 'filter'],
@ -1343,12 +1375,9 @@ configurationRegistry.registerConfiguration({
localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")
],
default: 'highlight',
description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.")
},
[automaticKeyboardNavigationSettingKey]: {
type: 'boolean',
default: true,
markdownDescription: localize('automatic keyboard navigation setting', "Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to `false`, keyboard navigation is only triggered when executing the `list.toggleKeyboardNavigation` command, for which you can assign a keyboard shortcut.")
description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter."),
deprecated: true,
deprecationMessage: localize('keyboardNavigationSettingKeyDeprecated', "Please use 'workbench.list.defaultFindMode' instead.")
},
[treeExpandMode]: {
type: 'string',

View file

@ -445,9 +445,10 @@ export const listFocusHighlightForeground = registerColor('list.focusHighlightFo
export const listInvalidItemForeground = registerColor('list.invalidItemForeground', { dark: '#B89500', light: '#B89500', hcDark: '#B89500', hcLight: '#B5200D' }, nls.localize('invalidItemForeground', 'List/Tree foreground color for invalid items, for example an unresolved root in explorer.'));
export const listErrorForeground = registerColor('list.errorForeground', { dark: '#F88070', light: '#B01011', hcDark: null, hcLight: null }, nls.localize('listErrorForeground', 'Foreground color of list items containing errors.'));
export const listWarningForeground = registerColor('list.warningForeground', { dark: '#CCA700', light: '#855F00', hcDark: null, hcLight: null }, nls.localize('listWarningForeground', 'Foreground color of list items containing warnings.'));
export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: '#efc1ad', dark: '#653723', hcDark: Color.black, hcLight: Color.white }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.'));
export const listFilterWidgetBackground = registerColor('listFilterWidget.background', { light: darken(editorWidgetBackground, 0), dark: lighten(editorWidgetBackground, 0), hcDark: editorWidgetBackground, hcLight: editorWidgetBackground }, nls.localize('listFilterWidgetBackground', 'Background color of the type filter widget in lists and trees.'));
export const listFilterWidgetOutline = registerColor('listFilterWidget.outline', { dark: Color.transparent, light: Color.transparent, hcDark: '#f38518', hcLight: '#007ACC' }, nls.localize('listFilterWidgetOutline', 'Outline color of the type filter widget in lists and trees.'));
export const listFilterWidgetNoMatchesOutline = registerColor('listFilterWidget.noMatchesOutline', { dark: '#BE1100', light: '#BE1100', hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('listFilterWidgetNoMatchesOutline', 'Outline color of the type filter widget in lists and trees, when there are no matches.'));
export const listFilterWidgetShadow = registerColor('listFilterWidget.shadow', { dark: widgetShadow, light: widgetShadow, hcDark: widgetShadow, hcLight: widgetShadow }, nls.localize('listFilterWidgetShadow', 'Shadown color of the type filter widget in lists and trees.'));
export const listFilterMatchHighlight = registerColor('list.filterMatchBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hcDark: null, hcLight: null }, nls.localize('listFilterMatchHighlight', 'Background color of the filtered match.'));
export const listFilterMatchHighlightBorder = registerColor('list.filterMatchBorder', { dark: editorFindMatchHighlightBorder, light: editorFindMatchHighlightBorder, hcDark: contrastBorder, hcLight: activeContrastBorder }, nls.localize('listFilterMatchHighlightBorder', 'Border color of the filtered match.'));
export const treeIndentGuidesStroke = registerColor('tree.indentGuidesStroke', { dark: '#585858', light: '#a9a9a9', hcDark: '#a9a9a9', hcLight: '#a5a5a5' }, nls.localize('treeIndentGuidesStroke', "Tree stroke color for the indentation guides."));

View file

@ -6,7 +6,7 @@
import { Color } from 'vs/base/common/color';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IThemable, styleFn } from 'vs/base/common/styler';
import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, checkboxBackground, checkboxBorder, checkboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline, buttonSeparator } from 'vs/platform/theme/common/colorRegistry';
import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, checkboxBackground, checkboxBorder, checkboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline, listFilterWidgetShadow, buttonSeparator } from 'vs/platform/theme/common/colorRegistry';
import { isHighContrast } from 'vs/platform/theme/common/theme';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
@ -184,7 +184,7 @@ export interface IListStyleOverrides extends IStyleOverrides {
listFilterWidgetBackground?: ColorIdentifier;
listFilterWidgetOutline?: ColorIdentifier;
listFilterWidgetNoMatchesOutline?: ColorIdentifier;
listMatchesShadow?: ColorIdentifier;
listFilterWidgetShadow?: ColorIdentifier;
treeIndentGuidesStroke?: ColorIdentifier;
tableColumnsBorder?: ColorIdentifier;
tableOddRowsBackgroundColor?: ColorIdentifier;
@ -217,10 +217,25 @@ export const defaultListStyles: IColorMapping = {
listFilterWidgetBackground,
listFilterWidgetOutline,
listFilterWidgetNoMatchesOutline,
listMatchesShadow: widgetShadow,
listFilterWidgetShadow,
treeIndentGuidesStroke,
tableColumnsBorder,
tableOddRowsBackgroundColor
tableOddRowsBackgroundColor,
inputActiveOptionBorder,
inputActiveOptionForeground,
inputActiveOptionBackground,
inputBackground,
inputForeground,
inputBorder,
inputValidationInfoBackground,
inputValidationInfoForeground,
inputValidationInfoBorder,
inputValidationWarningBackground,
inputValidationWarningForeground,
inputValidationWarningBorder,
inputValidationErrorBackground,
inputValidationErrorForeground,
inputValidationErrorBorder,
};
export interface IButtonStyleOverrides extends IStyleOverrides {

View file

@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand } from 'vs/platform/list/browser/listService';
import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen } from 'vs/platform/list/browser/listService';
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { equals, range } from 'vs/base/common/arrays';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@ -17,6 +17,7 @@ import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Table } from 'vs/base/browser/ui/table/tableWidget';
import { AbstractTree, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
@ -607,27 +608,62 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
CommandsRegistry.registerCommand({
id: 'list.toggleKeyboardNavigation',
id: 'list.triggerTypeNavigation',
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
widget?.toggleKeyboardNavigation();
widget?.triggerTypeNavigation();
}
});
CommandsRegistry.registerCommand({
id: 'list.toggleFilterOnType',
id: 'list.toggleFindMode',
handler: (accessor) => {
const focused = accessor.get(IListService).lastFocusedList;
const widget = accessor.get(IListService).lastFocusedList;
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
const tree = widget;
tree.findMode = tree.findMode === TreeFindMode.Filter ? TreeFindMode.Highlight : TreeFindMode.Filter;
}
}
});
// Deprecated commands
CommandsRegistry.registerCommandAlias('list.toggleKeyboardNavigation', 'list.triggerTypeNavigation');
CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFindMode');
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.find',
weight: KeybindingWeight.WorkbenchContrib,
when: RawWorkbenchListFocusContextKey,
primary: KeyMod.CtrlCmd | KeyCode.KeyF,
secondary: [KeyCode.F3],
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
// List
if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
if (widget instanceof List || widget instanceof PagedList || widget instanceof Table) {
// TODO@joao
}
// Tree
else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
const tree = focused;
tree.updateOptions({ filterOnType: !tree.filterOnType });
else if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
const tree = widget;
tree.openFind();
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'list.closeFind',
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen),
primary: KeyCode.Escape,
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
const tree = widget;
tree.closeFind();
}
}
});

View file

@ -13,8 +13,6 @@ import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel';
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchAsyncDataTree, IListService, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
@ -266,8 +264,6 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
) {
const delegate = new CommentsModelVirualDelegate();
const dataSource = new CommentsAsyncDataSource();
@ -311,12 +307,11 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
},
overrideStyles: options.overrideStyles
},
instantiationService,
contextKeyService,
listService,
themeService,
configurationService,
keybindingService,
accessibilityService
configurationService
);
}
}

View file

@ -116,8 +116,6 @@ export class DebugHoverWidget implements IContentWidget {
horizontalScrolling: true,
useShadows: false,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e.name },
filterOnType: false,
simpleKeyboardNavigation: true,
overrideStyles: {
listBackground: editorHoverBackground
}

View file

@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
const NEW_STYLE_COMPRESS = true;
@ -585,8 +586,8 @@ export class LoadedScriptsView extends ViewPane {
// feature: expand all nodes when filtering (not when finding)
let viewState: IViewState | undefined;
this._register(this.tree.onDidChangeTypeFilterPattern(pattern => {
if (!this.tree.options.filterOnType) {
this._register(this.tree.onDidChangeFindPattern(pattern => {
if (this.tree.findMode === TreeFindMode.Highlight) {
return;
}

View file

@ -14,10 +14,8 @@ import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/l
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IColorMapping } from 'vs/platform/theme/common/styler';
@ -244,8 +242,6 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IExtensionsWorkbenchService extensionsWorkdbenchService: IExtensionsWorkbenchService
) {
const delegate = new VirualDelegate();
@ -278,7 +274,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
}
}
},
contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService
instantiationService, contextKeyService, listService, themeService, configurationService
);
this.setInput(input);

View file

@ -3,22 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchListAutomaticKeyboardNavigationKey } from 'vs/platform/list/browser/listService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey<boolean>('listSupportsKeyboardNavigation', true);
export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey<boolean>(WorkbenchListAutomaticKeyboardNavigationKey, true);
export class ListContext implements IWorkbenchContribution {
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
contextKeyService.createKey<boolean>('listSupportsTypeNavigation', true);
// @deprecated in favor of listSupportsTypeNavigation
contextKeyService.createKey('listSupportsKeyboardNavigation', true);
}
}

View file

@ -40,7 +40,6 @@ import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/marke
import { withUndefinedAsNull } from 'vs/base/common/types';
import { MementoObject, Memento } from 'vs/workbench/common/memento';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { KeyCode } from 'vs/base/common/keyCodes';
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
@ -922,14 +921,13 @@ class MarkersTree extends WorkbenchObjectTree<MarkerElement, FilterData> impleme
delegate: IListVirtualDelegate<MarkerElement>,
renderers: ITreeRenderer<MarkerElement, FilterData, any>[],
options: IWorkbenchObjectTreeOptions<MarkerElement, FilterData>,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService
) {
super(user, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService);
super(user, container, delegate, renderers, options, instantiationService, contextKeyService, listService, themeService, configurationService);
this.visibilityContextKey = MarkersContextKeys.MarkersTreeVisibilityContextKey.bindTo(contextKeyService);
}

View file

@ -232,7 +232,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
keyboardSupport: false,
mouseSupport: true,
multipleSelectionSupport: false,
enableKeyboardNavigation: true,
typeNavigationEnabled: true,
additionalScrollHeight: 0,
// transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
styleController: (_suffix: string) => { return this._list!; },

View file

@ -445,22 +445,6 @@ export class NotebookTextDiffList extends WorkbenchList<DiffElementViewModelBase
`);
}
if (styles.listFilterWidgetBackground) {
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
}
if (styles.listFilterWidgetOutline) {
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
}
if (styles.listFilterWidgetNoMatchesOutline) {
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
}
if (styles.listMatchesShadow) {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.textContent) {
this.styleElement.textContent = newStyles;

View file

@ -915,7 +915,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
mouseSupport: true,
multipleSelectionSupport: true,
selectionNavigation: true,
enableKeyboardNavigation: true,
typeNavigationEnabled: true,
additionalScrollHeight: 0,
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
styleController: (_suffix: string) => { return this._list; },

View file

@ -1393,22 +1393,6 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
`);
}
if (styles.listFilterWidgetBackground) {
content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
}
if (styles.listFilterWidgetOutline) {
content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
}
if (styles.listFilterWidgetNoMatchesOutline) {
content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
}
if (styles.listMatchesShadow) {
content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.textContent) {
this.styleElement.textContent = newStyles;

View file

@ -366,7 +366,6 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe
{
supportDynamicHeights: true,
multipleSelectionSupport: true,
enableKeyboardNavigation: true,
focusNextPreviousDelegate: {
onFocusNext: (applyFocusNext: () => void) => { applyFocusNext(); },
onFocusPrevious: (applyFocusPrevious: () => void) => { applyFocusPrevious(); },

View file

@ -35,7 +35,7 @@ import { EditorResourceAccessor, IEditorPane } from 'vs/workbench/common/editor'
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { ITreeSorter } from 'vs/base/browser/ui/tree/tree';
import { AbstractTreeViewState, IAbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree';
import { AbstractTreeViewState, IAbstractTreeViewState, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
const _ctxFollowsCursor = new RawContextKey('outlineFollowsCursor', false);
const _ctxFilterOnType = new RawContextKey('outlineFiltersOnType', false);
@ -259,7 +259,7 @@ export class OutlinePane extends ViewPane {
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
hideTwistiesOfChildlessElements: true,
filterOnType: this._outlineViewState.filterOnType,
defaultFindMode: this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight,
overrideStyles: { listBackground: this.getBackgroundColor() }
}
);
@ -286,6 +286,7 @@ export class OutlinePane extends ViewPane {
};
updateTree();
this._editorControlDisposables.add(newOutline.onDidChange(updateTree));
tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight;
// feature: apply panel background to tree
this._editorControlDisposables.add(this.viewDescriptorService.onDidChangeLocation(({ views }) => {
@ -295,7 +296,7 @@ export class OutlinePane extends ViewPane {
}));
// feature: filter on type - keep tree and menu in sync
this._editorControlDisposables.add(tree.onDidUpdateOptions(e => this._outlineViewState.filterOnType = Boolean(e.filterOnType)));
this._editorControlDisposables.add(tree.onDidChangeFindMode(mode => this._outlineViewState.filterOnType = mode === TreeFindMode.Filter));
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
@ -328,7 +329,7 @@ export class OutlinePane extends ViewPane {
this._editorControlDisposables.add(this._outlineViewState.onDidChange((e: { followCursor?: boolean; sortBy?: boolean; filterOnType?: boolean }) => {
this._outlineViewState.persist(this._storageService);
if (e.filterOnType) {
tree.updateOptions({ filterOnType: this._outlineViewState.filterOnType });
tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight;
}
if (e.followCursor) {
revealActiveElement();
@ -341,8 +342,8 @@ export class OutlinePane extends ViewPane {
// feature: expand all nodes when filtering (not when finding)
let viewState: AbstractTreeViewState | undefined;
this._editorControlDisposables.add(tree.onDidChangeTypeFilterPattern(pattern => {
if (!tree.options.filterOnType) {
this._editorControlDisposables.add(tree.onDidChangeFindPattern(pattern => {
if (tree.findMode === TreeFindMode.Highlight) {
return;
}
if (!viewState && pattern) {

View file

@ -54,7 +54,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ILogService } from 'vs/platform/log/common/log';
import { settingsMoreActionIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
@ -2334,8 +2333,6 @@ export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IInstantiationService instantiationService: IInstantiationService,
@ILanguageService languageService: ILanguageService
) {
@ -2356,12 +2353,11 @@ export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: false,
},
instantiationService,
contextKeyService,
listService,
themeService,
configurationService,
keybindingService,
accessibilityService,
);
this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {

View file

@ -9,11 +9,9 @@ import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/brow
import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { Iterable } from 'vs/base/common/iterator';
import { localize } from 'vs/nls';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IListService, IWorkbenchObjectTreeOptions, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { editorBackground, focusBorder, foreground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler } from 'vs/platform/theme/common/styler';
@ -203,8 +201,6 @@ export class TOCTree extends WorkbenchObjectTree<SettingsTreeGroupElement> {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
@IKeybindingService keybindingService: IKeybindingService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@IInstantiationService instantiationService: IInstantiationService,
) {
// test open mode
@ -231,12 +227,11 @@ export class TOCTree extends WorkbenchObjectTree<SettingsTreeGroupElement> {
new TOCTreeDelegate(),
[new TOCRenderer()],
options,
instantiationService,
contextKeyService,
listService,
themeService,
configurationService,
keybindingService,
accessibilityService,
);
this.disposables.add(attachStyler(themeService, {

View file

@ -485,7 +485,6 @@ export class TestingExplorerViewModel extends Disposable {
instantiationService.createInstance(ErrorRenderer),
],
{
simpleKeyboardNavigation: true,
identityProvider: instantiationService.createInstance(IdentityProvider),
hideTwistiesOfChildlessElements: false,
sorter: instantiationService.createInstance(TreeSorter, this),