Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/list/browser/listService.ts
5222 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { isActiveElement, isKeyboardEvent } from '../../../base/browser/dom.js';
7
import { IContextViewProvider } from '../../../base/browser/ui/contextview/contextview.js';
8
import { IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from '../../../base/browser/ui/list/list.js';
9
import { IPagedListOptions, IPagedRenderer, PagedList } from '../../../base/browser/ui/list/listPaging.js';
10
import { IKeyboardNavigationEventFilter, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, TypeNavigationMode } from '../../../base/browser/ui/list/listWidget.js';
11
import { ITableColumn, ITableRenderer, ITableVirtualDelegate } from '../../../base/browser/ui/table/table.js';
12
import { ITableOptions, ITableOptionsUpdate, ITableStyles, Table } from '../../../base/browser/ui/table/tableWidget.js';
13
import { IAbstractTreeOptions, IAbstractTreeOptionsUpdate, RenderIndentGuides, TreeFindMatchType, TreeFindMode } from '../../../base/browser/ui/tree/abstractTree.js';
14
import { AsyncDataTree, CompressibleAsyncDataTree, IAsyncDataTreeNode, IAsyncDataTreeOptions, IAsyncDataTreeOptionsUpdate, ICompressibleAsyncDataTreeOptions, ICompressibleAsyncDataTreeOptionsUpdate, ITreeCompressionDelegate } from '../../../base/browser/ui/tree/asyncDataTree.js';
15
import { DataTree, IDataTreeOptions } from '../../../base/browser/ui/tree/dataTree.js';
16
import { CompressibleObjectTree, ICompressibleObjectTreeOptions, ICompressibleObjectTreeOptionsUpdate, ICompressibleTreeRenderer, IObjectTreeOptions, ObjectTree } from '../../../base/browser/ui/tree/objectTree.js';
17
import { IAsyncDataSource, IDataSource, ITreeEvent, ITreeRenderer } from '../../../base/browser/ui/tree/tree.js';
18
import { Emitter, Event } from '../../../base/common/event.js';
19
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';
20
import { localize } from '../../../nls.js';
21
import { IConfigurationService } from '../../configuration/common/configuration.js';
22
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js';
23
import { ContextKeyExpr, IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';
24
import { InputFocusedContextKey } from '../../contextkey/common/contextkeys.js';
25
import { IContextViewService } from '../../contextview/browser/contextView.js';
26
import { IEditorOptions } from '../../editor/common/editor.js';
27
import { createDecorator, IInstantiationService, ServicesAccessor } from '../../instantiation/common/instantiation.js';
28
import { IKeybindingService } from '../../keybinding/common/keybinding.js';
29
import { ResultKind } from '../../keybinding/common/keybindingResolver.js';
30
import { Registry } from '../../registry/common/platform.js';
31
import { defaultFindWidgetStyles, defaultListStyles, getListStyles, IStyleOverride } from '../../theme/browser/defaultStyles.js';
32
33
export type ListWidget = List<any> | PagedList<any> | ObjectTree<any, any> | DataTree<any, any, any> | AsyncDataTree<any, any, any> | Table<any>;
34
export type WorkbenchListWidget = WorkbenchList<any> | WorkbenchPagedList<any> | WorkbenchObjectTree<any, any> | WorkbenchCompressibleObjectTree<any, any> | WorkbenchDataTree<any, any, any> | WorkbenchAsyncDataTree<any, any, any> | WorkbenchCompressibleAsyncDataTree<any, any, any> | WorkbenchTable<any>;
35
36
export const IListService = createDecorator<IListService>('listService');
37
38
export interface IListService {
39
40
readonly _serviceBrand: undefined;
41
42
/**
43
* Returns the currently focused list widget if any.
44
*/
45
readonly lastFocusedList: WorkbenchListWidget | undefined;
46
}
47
48
interface IRegisteredList {
49
widget: WorkbenchListWidget;
50
extraContextKeys?: (IContextKey<boolean>)[];
51
}
52
53
export class ListService implements IListService {
54
55
declare readonly _serviceBrand: undefined;
56
57
private readonly disposables = new DisposableStore();
58
private lists: IRegisteredList[] = [];
59
private _lastFocusedWidget: WorkbenchListWidget | undefined = undefined;
60
61
get lastFocusedList(): WorkbenchListWidget | undefined {
62
return this._lastFocusedWidget;
63
}
64
65
constructor() { }
66
67
private setLastFocusedList(widget: WorkbenchListWidget | undefined): void {
68
if (widget === this._lastFocusedWidget) {
69
return;
70
}
71
72
this._lastFocusedWidget?.getHTMLElement().classList.remove('last-focused');
73
this._lastFocusedWidget = widget;
74
this._lastFocusedWidget?.getHTMLElement().classList.add('last-focused');
75
}
76
77
register(widget: WorkbenchListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {
78
if (this.lists.some(l => l.widget === widget)) {
79
throw new Error('Cannot register the same widget multiple times');
80
}
81
82
// Keep in our lists list
83
const registeredList: IRegisteredList = { widget, extraContextKeys };
84
this.lists.push(registeredList);
85
86
// Check for currently being focused
87
if (isActiveElement(widget.getHTMLElement())) {
88
this.setLastFocusedList(widget);
89
}
90
91
return combinedDisposable(
92
widget.onDidFocus(() => this.setLastFocusedList(widget)),
93
toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)),
94
widget.onDidDispose(() => {
95
this.lists = this.lists.filter(l => l !== registeredList);
96
if (this._lastFocusedWidget === widget) {
97
this.setLastFocusedList(undefined);
98
}
99
})
100
);
101
}
102
103
dispose(): void {
104
this.disposables.dispose();
105
}
106
}
107
108
export const RawWorkbenchListScrollAtBoundaryContextKey = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('listScrollAtBoundary', 'none');
109
export const WorkbenchListScrollAtTopContextKey = ContextKeyExpr.or(
110
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('top'),
111
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));
112
export const WorkbenchListScrollAtBottomContextKey = ContextKeyExpr.or(
113
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('bottom'),
114
RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));
115
116
export const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);
117
export const WorkbenchTreeStickyScrollFocused = new RawContextKey<boolean>('treestickyScrollFocused', false);
118
export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey<boolean>('listSupportsMultiselect', true);
119
export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey), WorkbenchTreeStickyScrollFocused.negate());
120
export const WorkbenchListHasSelectionOrFocus = new RawContextKey<boolean>('listHasSelectionOrFocus', false);
121
export const WorkbenchListDoubleSelection = new RawContextKey<boolean>('listDoubleSelection', false);
122
export const WorkbenchListMultiSelection = new RawContextKey<boolean>('listMultiSelection', false);
123
export const WorkbenchListSelectionNavigation = new RawContextKey<boolean>('listSelectionNavigation', false);
124
export const WorkbenchListSupportsFind = new RawContextKey<boolean>('listSupportsFind', true);
125
export const WorkbenchTreeElementCanCollapse = new RawContextKey<boolean>('treeElementCanCollapse', false);
126
export const WorkbenchTreeElementHasParent = new RawContextKey<boolean>('treeElementHasParent', false);
127
export const WorkbenchTreeElementCanExpand = new RawContextKey<boolean>('treeElementCanExpand', false);
128
export const WorkbenchTreeElementHasChild = new RawContextKey<boolean>('treeElementHasChild', false);
129
export const WorkbenchTreeFindOpen = new RawContextKey<boolean>('treeFindOpen', false);
130
const WorkbenchListTypeNavigationModeKey = 'listTypeNavigationMode';
131
132
/**
133
* @deprecated in favor of WorkbenchListTypeNavigationModeKey
134
*/
135
const WorkbenchListAutomaticKeyboardNavigationLegacyKey = 'listAutomaticKeyboardNavigation';
136
137
function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IScopedContextKeyService {
138
const result = contextKeyService.createScoped(widget.getHTMLElement());
139
RawWorkbenchListFocusContextKey.bindTo(result);
140
return result;
141
}
142
143
// Note: We must declare IScrollObservarable as the arithmetic of concrete classes,
144
// instead of object type like { onDidScroll: Event<any>; ... }. The latter will not mark
145
// those properties as referenced during tree-shaking, causing them to be shaked away.
146
type IScrollObservarable = Exclude<WorkbenchListWidget, WorkbenchPagedList<any>> | List<any>;
147
148
function createScrollObserver(contextKeyService: IContextKeyService, widget: IScrollObservarable): IDisposable {
149
const listScrollAt = RawWorkbenchListScrollAtBoundaryContextKey.bindTo(contextKeyService);
150
const update = () => {
151
const atTop = widget.scrollTop === 0;
152
153
// We need a threshold `1` since scrollHeight is rounded.
154
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled
155
const atBottom = widget.scrollHeight - widget.renderHeight - widget.scrollTop < 1;
156
if (atTop && atBottom) {
157
listScrollAt.set('both');
158
} else if (atTop) {
159
listScrollAt.set('top');
160
} else if (atBottom) {
161
listScrollAt.set('bottom');
162
} else {
163
listScrollAt.set('none');
164
}
165
};
166
update();
167
return widget.onDidScroll(update);
168
}
169
170
const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
171
const openModeSettingKey = 'workbench.list.openMode';
172
const horizontalScrollingKey = 'workbench.list.horizontalScrolling';
173
const defaultFindModeSettingKey = 'workbench.list.defaultFindMode';
174
const typeNavigationModeSettingKey = 'workbench.list.typeNavigationMode';
175
/** @deprecated in favor of `workbench.list.defaultFindMode` and `workbench.list.typeNavigationMode` */
176
const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';
177
const scrollByPageKey = 'workbench.list.scrollByPage';
178
const defaultFindMatchTypeSettingKey = 'workbench.list.defaultFindMatchType';
179
const treeIndentKey = 'workbench.tree.indent';
180
const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides';
181
const listSmoothScrolling = 'workbench.list.smoothScrolling';
182
const mouseWheelScrollSensitivityKey = 'workbench.list.mouseWheelScrollSensitivity';
183
const fastScrollSensitivityKey = 'workbench.list.fastScrollSensitivity';
184
const treeExpandMode = 'workbench.tree.expandMode';
185
const treeStickyScroll = 'workbench.tree.enableStickyScroll';
186
const treeStickyScrollMaxElements = 'workbench.tree.stickyScrollMaxItemCount';
187
188
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
189
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
190
}
191
192
class MultipleSelectionController<T> extends Disposable implements IMultipleSelectionController<T> {
193
private useAltAsMultipleSelectionModifier: boolean;
194
195
constructor(private configurationService: IConfigurationService) {
196
super();
197
198
this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
199
200
this.registerListeners();
201
}
202
203
private registerListeners(): void {
204
this._register(this.configurationService.onDidChangeConfiguration(e => {
205
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
206
this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
207
}
208
}));
209
}
210
211
isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {
212
if (this.useAltAsMultipleSelectionModifier) {
213
return event.browserEvent.altKey;
214
}
215
216
return isSelectionSingleChangeEvent(event);
217
}
218
219
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {
220
return isSelectionRangeChangeEvent(event);
221
}
222
}
223
224
function toWorkbenchListOptions<T>(
225
accessor: ServicesAccessor,
226
options: IListOptions<T>,
227
): [IListOptions<T>, IDisposable] {
228
const configurationService = accessor.get(IConfigurationService);
229
const keybindingService = accessor.get(IKeybindingService);
230
231
const disposables = new DisposableStore();
232
const result: IListOptions<T> = {
233
...options,
234
keyboardNavigationDelegate: { mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); } },
235
smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),
236
mouseWheelScrollSensitivity: configurationService.getValue<number>(mouseWheelScrollSensitivityKey),
237
fastScrollSensitivity: configurationService.getValue<number>(fastScrollSensitivityKey),
238
multipleSelectionController: options.multipleSelectionController ?? disposables.add(new MultipleSelectionController(configurationService)),
239
keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(keybindingService),
240
scrollByPage: Boolean(configurationService.getValue(scrollByPageKey))
241
};
242
243
return [result, disposables];
244
}
245
246
export interface IWorkbenchListOptionsUpdate extends IListOptionsUpdate {
247
readonly overrideStyles?: IStyleOverride<IListStyles>;
248
}
249
250
export interface IWorkbenchListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IListOptions<T> {
251
readonly selectionNavigation?: boolean;
252
}
253
254
export class WorkbenchList<T> extends List<T> {
255
256
readonly contextKeyService: IScopedContextKeyService;
257
private listSupportsMultiSelect: IContextKey<boolean>;
258
private listHasSelectionOrFocus: IContextKey<boolean>;
259
private listDoubleSelection: IContextKey<boolean>;
260
private listMultiSelection: IContextKey<boolean>;
261
private horizontalScrolling: boolean | undefined;
262
private _useAltAsMultipleSelectionModifier: boolean;
263
private navigator: ListResourceNavigator<T>;
264
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
265
266
constructor(
267
user: string,
268
container: HTMLElement,
269
delegate: IListVirtualDelegate<T>,
270
renderers: IListRenderer<T, any>[],
271
options: IWorkbenchListOptions<T>,
272
@IContextKeyService contextKeyService: IContextKeyService,
273
@IListService listService: IListService,
274
@IConfigurationService configurationService: IConfigurationService,
275
@IInstantiationService instantiationService: IInstantiationService
276
) {
277
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
278
const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
279
280
super(user, container, delegate, renderers,
281
{
282
keyboardSupport: false,
283
...workbenchListOptions,
284
horizontalScrolling,
285
}
286
);
287
288
this.disposables.add(workbenchListOptionsDisposable);
289
290
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
291
292
this.disposables.add(createScrollObserver(this.contextKeyService, this));
293
294
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
295
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
296
297
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
298
listSelectionNavigation.set(Boolean(options.selectionNavigation));
299
300
this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
301
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
302
this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
303
this.horizontalScrolling = options.horizontalScrolling;
304
305
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
306
307
this.disposables.add(this.contextKeyService);
308
this.disposables.add((listService as ListService).register(this));
309
310
this.updateStyles(options.overrideStyles);
311
312
this.disposables.add(this.onDidChangeSelection(() => {
313
const selection = this.getSelection();
314
const focus = this.getFocus();
315
316
this.contextKeyService.bufferChangeEvents(() => {
317
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
318
this.listMultiSelection.set(selection.length > 1);
319
this.listDoubleSelection.set(selection.length === 2);
320
});
321
}));
322
this.disposables.add(this.onDidChangeFocus(() => {
323
const selection = this.getSelection();
324
const focus = this.getFocus();
325
326
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
327
}));
328
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
329
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
330
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
331
}
332
333
let options: IListOptionsUpdate = {};
334
335
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
336
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
337
options = { ...options, horizontalScrolling };
338
}
339
if (e.affectsConfiguration(scrollByPageKey)) {
340
const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));
341
options = { ...options, scrollByPage };
342
}
343
if (e.affectsConfiguration(listSmoothScrolling)) {
344
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
345
options = { ...options, smoothScrolling };
346
}
347
if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {
348
const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);
349
options = { ...options, mouseWheelScrollSensitivity };
350
}
351
if (e.affectsConfiguration(fastScrollSensitivityKey)) {
352
const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);
353
options = { ...options, fastScrollSensitivity };
354
}
355
if (Object.keys(options).length > 0) {
356
this.updateOptions(options);
357
}
358
}));
359
360
this.navigator = new ListResourceNavigator(this, { configurationService, ...options });
361
this.disposables.add(this.navigator);
362
}
363
364
override updateOptions(options: IWorkbenchListOptionsUpdate): void {
365
super.updateOptions(options);
366
367
if (options.overrideStyles !== undefined) {
368
this.updateStyles(options.overrideStyles);
369
}
370
371
if (options.multipleSelectionSupport !== undefined) {
372
this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
373
}
374
}
375
376
private updateStyles(styles: IStyleOverride<IListStyles> | undefined): void {
377
this.style(styles ? getListStyles(styles) : defaultListStyles);
378
}
379
380
get useAltAsMultipleSelectionModifier(): boolean {
381
return this._useAltAsMultipleSelectionModifier;
382
}
383
}
384
385
export interface IWorkbenchPagedListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IPagedListOptions<T> {
386
readonly selectionNavigation?: boolean;
387
}
388
389
export class WorkbenchPagedList<T> extends PagedList<T> {
390
391
readonly contextKeyService: IScopedContextKeyService;
392
private readonly disposables: DisposableStore;
393
private listSupportsMultiSelect: IContextKey<boolean>;
394
private _useAltAsMultipleSelectionModifier: boolean;
395
private horizontalScrolling: boolean | undefined;
396
private navigator: ListResourceNavigator<T>;
397
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
398
399
constructor(
400
user: string,
401
container: HTMLElement,
402
delegate: IListVirtualDelegate<number>,
403
renderers: IPagedRenderer<T, any>[],
404
options: IWorkbenchPagedListOptions<T>,
405
@IContextKeyService contextKeyService: IContextKeyService,
406
@IListService listService: IListService,
407
@IConfigurationService configurationService: IConfigurationService,
408
@IInstantiationService instantiationService: IInstantiationService
409
) {
410
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
411
const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
412
super(user, container, delegate, renderers,
413
{
414
keyboardSupport: false,
415
...workbenchListOptions,
416
horizontalScrolling,
417
}
418
);
419
420
this.disposables = new DisposableStore();
421
this.disposables.add(workbenchListOptionsDisposable);
422
423
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
424
425
this.disposables.add(createScrollObserver(this.contextKeyService, this.widget));
426
427
this.horizontalScrolling = options.horizontalScrolling;
428
429
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
430
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
431
432
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
433
listSelectionNavigation.set(Boolean(options.selectionNavigation));
434
435
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
436
437
this.disposables.add(this.contextKeyService);
438
this.disposables.add((listService as ListService).register(this));
439
440
this.updateStyles(options.overrideStyles);
441
442
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
443
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
444
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
445
}
446
447
let options: IListOptionsUpdate = {};
448
449
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
450
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
451
options = { ...options, horizontalScrolling };
452
}
453
if (e.affectsConfiguration(scrollByPageKey)) {
454
const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));
455
options = { ...options, scrollByPage };
456
}
457
if (e.affectsConfiguration(listSmoothScrolling)) {
458
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
459
options = { ...options, smoothScrolling };
460
}
461
if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {
462
const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);
463
options = { ...options, mouseWheelScrollSensitivity };
464
}
465
if (e.affectsConfiguration(fastScrollSensitivityKey)) {
466
const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);
467
options = { ...options, fastScrollSensitivity };
468
}
469
if (Object.keys(options).length > 0) {
470
this.updateOptions(options);
471
}
472
}));
473
474
this.navigator = new ListResourceNavigator(this, { configurationService, ...options });
475
this.disposables.add(this.navigator);
476
}
477
478
override updateOptions(options: IWorkbenchListOptionsUpdate): void {
479
super.updateOptions(options);
480
481
if (options.overrideStyles !== undefined) {
482
this.updateStyles(options.overrideStyles);
483
}
484
485
if (options.multipleSelectionSupport !== undefined) {
486
this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
487
}
488
}
489
490
private updateStyles(styles: IStyleOverride<IListStyles> | undefined): void {
491
this.style(styles ? getListStyles(styles) : defaultListStyles);
492
}
493
494
get useAltAsMultipleSelectionModifier(): boolean {
495
return this._useAltAsMultipleSelectionModifier;
496
}
497
498
override dispose(): void {
499
this.disposables.dispose();
500
super.dispose();
501
}
502
}
503
504
export interface IWorkbenchTableOptionsUpdate extends ITableOptionsUpdate {
505
readonly overrideStyles?: IStyleOverride<IListStyles>;
506
}
507
508
export interface IWorkbenchTableOptions<T> extends IWorkbenchTableOptionsUpdate, IResourceNavigatorOptions, ITableOptions<T> {
509
readonly selectionNavigation?: boolean;
510
}
511
512
export class WorkbenchTable<TRow> extends Table<TRow> {
513
514
readonly contextKeyService: IScopedContextKeyService;
515
private listSupportsMultiSelect: IContextKey<boolean>;
516
private listHasSelectionOrFocus: IContextKey<boolean>;
517
private listDoubleSelection: IContextKey<boolean>;
518
private listMultiSelection: IContextKey<boolean>;
519
private horizontalScrolling: boolean | undefined;
520
private _useAltAsMultipleSelectionModifier: boolean;
521
private navigator: TableResourceNavigator<TRow>;
522
get onDidOpen(): Event<IOpenEvent<TRow | undefined>> { return this.navigator.onDidOpen; }
523
524
constructor(
525
user: string,
526
container: HTMLElement,
527
delegate: ITableVirtualDelegate<TRow>,
528
columns: ITableColumn<TRow, any>[],
529
renderers: ITableRenderer<TRow, any>[],
530
options: IWorkbenchTableOptions<TRow>,
531
@IContextKeyService contextKeyService: IContextKeyService,
532
@IListService listService: IListService,
533
@IConfigurationService configurationService: IConfigurationService,
534
@IInstantiationService instantiationService: IInstantiationService
535
) {
536
const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
537
const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
538
539
super(user, container, delegate, columns, renderers,
540
{
541
keyboardSupport: false,
542
...workbenchListOptions,
543
horizontalScrolling,
544
}
545
);
546
547
this.disposables.add(workbenchListOptionsDisposable);
548
549
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
550
551
this.disposables.add(createScrollObserver(this.contextKeyService, this));
552
553
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
554
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
555
556
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
557
listSelectionNavigation.set(Boolean(options.selectionNavigation));
558
559
this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
560
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
561
this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
562
this.horizontalScrolling = options.horizontalScrolling;
563
564
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
565
566
this.disposables.add(this.contextKeyService);
567
this.disposables.add((listService as ListService).register(this));
568
569
this.updateStyles(options.overrideStyles);
570
571
this.disposables.add(this.onDidChangeSelection(() => {
572
const selection = this.getSelection();
573
const focus = this.getFocus();
574
575
this.contextKeyService.bufferChangeEvents(() => {
576
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
577
this.listMultiSelection.set(selection.length > 1);
578
this.listDoubleSelection.set(selection.length === 2);
579
});
580
}));
581
this.disposables.add(this.onDidChangeFocus(() => {
582
const selection = this.getSelection();
583
const focus = this.getFocus();
584
585
this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
586
}));
587
this.disposables.add(configurationService.onDidChangeConfiguration(e => {
588
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
589
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
590
}
591
592
let options: IListOptionsUpdate = {};
593
594
if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {
595
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
596
options = { ...options, horizontalScrolling };
597
}
598
if (e.affectsConfiguration(scrollByPageKey)) {
599
const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));
600
options = { ...options, scrollByPage };
601
}
602
if (e.affectsConfiguration(listSmoothScrolling)) {
603
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
604
options = { ...options, smoothScrolling };
605
}
606
if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {
607
const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);
608
options = { ...options, mouseWheelScrollSensitivity };
609
}
610
if (e.affectsConfiguration(fastScrollSensitivityKey)) {
611
const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);
612
options = { ...options, fastScrollSensitivity };
613
}
614
if (Object.keys(options).length > 0) {
615
this.updateOptions(options);
616
}
617
}));
618
619
this.navigator = new TableResourceNavigator(this, { configurationService, ...options });
620
this.disposables.add(this.navigator);
621
}
622
623
override updateOptions(options: IWorkbenchTableOptionsUpdate): void {
624
super.updateOptions(options);
625
626
if (options.overrideStyles !== undefined) {
627
this.updateStyles(options.overrideStyles);
628
}
629
630
if (options.multipleSelectionSupport !== undefined) {
631
this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
632
}
633
}
634
635
private updateStyles(styles: IStyleOverride<ITableStyles> | undefined): void {
636
this.style(styles ? getListStyles(styles) : defaultListStyles);
637
}
638
639
get useAltAsMultipleSelectionModifier(): boolean {
640
return this._useAltAsMultipleSelectionModifier;
641
}
642
643
override dispose(): void {
644
this.disposables.dispose();
645
super.dispose();
646
}
647
}
648
649
export interface IOpenEvent<T> {
650
editorOptions: IEditorOptions;
651
sideBySide: boolean;
652
element: T;
653
browserEvent?: UIEvent;
654
}
655
656
export interface IResourceNavigatorOptions {
657
readonly configurationService?: IConfigurationService;
658
readonly openOnSingleClick?: boolean;
659
}
660
661
export interface SelectionKeyboardEvent extends KeyboardEvent {
662
preserveFocus?: boolean;
663
pinned?: boolean;
664
__forceEvent?: boolean;
665
}
666
667
export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean, pinned?: boolean): SelectionKeyboardEvent {
668
const e = new KeyboardEvent(typeArg);
669
(<SelectionKeyboardEvent>e).preserveFocus = preserveFocus;
670
(<SelectionKeyboardEvent>e).pinned = pinned;
671
(<SelectionKeyboardEvent>e).__forceEvent = true;
672
673
return e;
674
}
675
676
abstract class ResourceNavigator<T> extends Disposable {
677
678
private openOnSingleClick: boolean;
679
680
private readonly _onDidOpen = this._register(new Emitter<IOpenEvent<T | undefined>>());
681
readonly onDidOpen: Event<IOpenEvent<T | undefined>> = this._onDidOpen.event;
682
683
constructor(
684
protected readonly widget: ListWidget,
685
options?: IResourceNavigatorOptions
686
) {
687
super();
688
689
this._register(Event.filter(this.widget.onDidChangeSelection, e => isKeyboardEvent(e.browserEvent))(e => this.onSelectionFromKeyboard(e)));
690
this._register(this.widget.onPointer((e: { browserEvent: MouseEvent; element: T | undefined }) => this.onPointer(e.element, e.browserEvent)));
691
this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent; element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent)));
692
693
if (typeof options?.openOnSingleClick !== 'boolean' && options?.configurationService) {
694
this.openOnSingleClick = options?.configurationService.getValue(openModeSettingKey) !== 'doubleClick';
695
this._register(options?.configurationService.onDidChangeConfiguration(e => {
696
if (e.affectsConfiguration(openModeSettingKey)) {
697
this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick';
698
}
699
}));
700
} else {
701
this.openOnSingleClick = options?.openOnSingleClick ?? true;
702
}
703
}
704
705
private onSelectionFromKeyboard(event: ITreeEvent<any>): void {
706
if (event.elements.length !== 1) {
707
return;
708
}
709
710
const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent;
711
const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus : true;
712
const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned : !preserveFocus;
713
const sideBySide = false;
714
715
this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent);
716
}
717
718
private onPointer(element: T | undefined, browserEvent: MouseEvent): void {
719
if (!this.openOnSingleClick) {
720
return;
721
}
722
723
const isDoubleClick = browserEvent.detail === 2;
724
725
if (isDoubleClick) {
726
return;
727
}
728
729
const isMiddleClick = browserEvent.button === 1;
730
const preserveFocus = true;
731
const pinned = isMiddleClick;
732
const sideBySide = browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey;
733
734
this._open(element, preserveFocus, pinned, sideBySide, browserEvent);
735
}
736
737
private onMouseDblClick(element: T | undefined, browserEvent?: MouseEvent): void {
738
if (!browserEvent) {
739
return;
740
}
741
742
// copied from AbstractTree
743
const target = browserEvent.target as HTMLElement;
744
const onTwistie = target.classList.contains('monaco-tl-twistie')
745
|| (target.classList.contains('monaco-icon-label') && target.classList.contains('folder-icon') && browserEvent.offsetX < 16);
746
747
if (onTwistie) {
748
return;
749
}
750
751
const preserveFocus = false;
752
const pinned = true;
753
const sideBySide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);
754
755
this._open(element, preserveFocus, pinned, sideBySide, browserEvent);
756
}
757
758
private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void {
759
if (!element) {
760
return;
761
}
762
763
this._onDidOpen.fire({
764
editorOptions: {
765
preserveFocus,
766
pinned,
767
revealIfVisible: true
768
},
769
sideBySide,
770
element,
771
browserEvent
772
});
773
}
774
775
abstract getSelectedElement(): T | undefined;
776
}
777
778
class ListResourceNavigator<T> extends ResourceNavigator<T> {
779
780
protected override readonly widget: List<T> | PagedList<T>;
781
782
constructor(
783
widget: List<T> | PagedList<T>,
784
options: IResourceNavigatorOptions
785
) {
786
super(widget, options);
787
this.widget = widget;
788
}
789
790
getSelectedElement(): T | undefined {
791
return this.widget.getSelectedElements()[0];
792
}
793
}
794
795
class TableResourceNavigator<TRow> extends ResourceNavigator<TRow> {
796
797
protected declare readonly widget: Table<TRow>;
798
799
constructor(
800
widget: Table<TRow>,
801
options: IResourceNavigatorOptions
802
) {
803
super(widget, options);
804
}
805
806
getSelectedElement(): TRow | undefined {
807
return this.widget.getSelectedElements()[0];
808
}
809
}
810
811
class TreeResourceNavigator<T, TFilterData> extends ResourceNavigator<T> {
812
813
protected declare readonly widget: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>;
814
815
constructor(
816
widget: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>,
817
options: IResourceNavigatorOptions
818
) {
819
super(widget, options);
820
}
821
822
getSelectedElement(): T | undefined {
823
return this.widget.getSelection()[0] ?? undefined;
824
}
825
}
826
827
function createKeyboardNavigationEventFilter(keybindingService: IKeybindingService): IKeyboardNavigationEventFilter {
828
let inMultiChord = false;
829
830
return event => {
831
if (event.toKeyCodeChord().isModifierKey()) {
832
return false;
833
}
834
835
if (inMultiChord) {
836
inMultiChord = false;
837
return false;
838
}
839
840
const result = keybindingService.softDispatch(event, event.target);
841
842
if (result.kind === ResultKind.MoreChordsNeeded) {
843
inMultiChord = true;
844
return false;
845
}
846
847
inMultiChord = false;
848
return result.kind === ResultKind.NoMatchingKb;
849
};
850
}
851
852
export interface IWorkbenchObjectTreeOptions<T, TFilterData> extends IObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
853
readonly accessibilityProvider: IListAccessibilityProvider<T>;
854
readonly overrideStyles?: IStyleOverride<IListStyles>;
855
readonly selectionNavigation?: boolean;
856
readonly scrollToActiveElement?: boolean;
857
}
858
859
export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {
860
861
private internals: WorkbenchTreeInternals<any, T, TFilterData>;
862
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
863
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
864
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
865
866
constructor(
867
user: string,
868
container: HTMLElement,
869
delegate: IListVirtualDelegate<T>,
870
renderers: ITreeRenderer<T, TFilterData, any>[],
871
options: IWorkbenchObjectTreeOptions<T, TFilterData>,
872
@IInstantiationService instantiationService: IInstantiationService,
873
@IContextKeyService contextKeyService: IContextKeyService,
874
@IListService listService: IListService,
875
@IConfigurationService configurationService: IConfigurationService
876
) {
877
// eslint-disable-next-line local/code-no-any-casts
878
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
879
super(user, container, delegate, renderers, treeOptions);
880
this.disposables.add(disposable);
881
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
882
this.disposables.add(this.internals);
883
}
884
885
override updateOptions(options: IAbstractTreeOptionsUpdate<T | null>): void {
886
super.updateOptions(options);
887
this.internals.updateOptions(options);
888
}
889
}
890
891
export interface IWorkbenchCompressibleObjectTreeOptionsUpdate<T> extends ICompressibleObjectTreeOptionsUpdate<T> {
892
readonly overrideStyles?: IStyleOverride<IListStyles>;
893
}
894
895
export interface IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> extends IWorkbenchCompressibleObjectTreeOptionsUpdate<T>, ICompressibleObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
896
readonly accessibilityProvider: IListAccessibilityProvider<T>;
897
readonly selectionNavigation?: boolean;
898
}
899
900
export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends CompressibleObjectTree<T, TFilterData> {
901
902
private internals: WorkbenchTreeInternals<any, T, TFilterData>;
903
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
904
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
905
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
906
907
constructor(
908
user: string,
909
container: HTMLElement,
910
delegate: IListVirtualDelegate<T>,
911
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
912
options: IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>,
913
@IInstantiationService instantiationService: IInstantiationService,
914
@IContextKeyService contextKeyService: IContextKeyService,
915
@IListService listService: IListService,
916
@IConfigurationService configurationService: IConfigurationService
917
) {
918
// eslint-disable-next-line local/code-no-any-casts
919
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
920
super(user, container, delegate, renderers, treeOptions);
921
this.disposables.add(disposable);
922
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
923
this.disposables.add(this.internals);
924
}
925
926
override updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate<T | null> = {}): void {
927
super.updateOptions(options);
928
929
if (options.overrideStyles) {
930
this.internals.updateStyleOverrides(options.overrideStyles);
931
}
932
933
this.internals.updateOptions(options);
934
}
935
}
936
937
export interface IWorkbenchDataTreeOptionsUpdate<T> extends IAbstractTreeOptionsUpdate<T> {
938
readonly overrideStyles?: IStyleOverride<IListStyles>;
939
}
940
941
export interface IWorkbenchDataTreeOptions<T, TFilterData> extends IWorkbenchDataTreeOptionsUpdate<T>, IDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
942
readonly accessibilityProvider: IListAccessibilityProvider<T>;
943
readonly selectionNavigation?: boolean;
944
}
945
946
export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<TInput, T, TFilterData> {
947
948
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
949
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
950
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
951
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
952
953
constructor(
954
user: string,
955
container: HTMLElement,
956
delegate: IListVirtualDelegate<T>,
957
renderers: ITreeRenderer<T, TFilterData, any>[],
958
dataSource: IDataSource<TInput, T>,
959
options: IWorkbenchDataTreeOptions<T, TFilterData>,
960
@IInstantiationService instantiationService: IInstantiationService,
961
@IContextKeyService contextKeyService: IContextKeyService,
962
@IListService listService: IListService,
963
@IConfigurationService configurationService: IConfigurationService
964
) {
965
// eslint-disable-next-line local/code-no-any-casts
966
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
967
super(user, container, delegate, renderers, dataSource, treeOptions);
968
this.disposables.add(disposable);
969
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
970
this.disposables.add(this.internals);
971
}
972
973
override updateOptions(options: IWorkbenchDataTreeOptionsUpdate<T | null> = {}): void {
974
super.updateOptions(options);
975
976
if (options.overrideStyles !== undefined) {
977
this.internals.updateStyleOverrides(options.overrideStyles);
978
}
979
980
this.internals.updateOptions(options);
981
}
982
}
983
984
export interface IWorkbenchAsyncDataTreeOptionsUpdate<T> extends IAsyncDataTreeOptionsUpdate<T> {
985
readonly overrideStyles?: IStyleOverride<IListStyles>;
986
}
987
988
export interface IWorkbenchAsyncDataTreeOptions<T, TFilterData> extends IWorkbenchAsyncDataTreeOptionsUpdate<T>, IAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
989
readonly accessibilityProvider: IListAccessibilityProvider<T>;
990
readonly selectionNavigation?: boolean;
991
}
992
993
export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
994
995
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
996
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
997
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
998
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
999
1000
constructor(
1001
user: string,
1002
container: HTMLElement,
1003
delegate: IListVirtualDelegate<T>,
1004
renderers: ITreeRenderer<T, TFilterData, any>[],
1005
dataSource: IAsyncDataSource<TInput, T>,
1006
options: IWorkbenchAsyncDataTreeOptions<T, TFilterData>,
1007
@IInstantiationService instantiationService: IInstantiationService,
1008
@IContextKeyService contextKeyService: IContextKeyService,
1009
@IListService listService: IListService,
1010
@IConfigurationService configurationService: IConfigurationService
1011
) {
1012
// eslint-disable-next-line local/code-no-any-casts
1013
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
1014
super(user, container, delegate, renderers, dataSource, treeOptions);
1015
this.disposables.add(disposable);
1016
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
1017
this.disposables.add(this.internals);
1018
}
1019
1020
override updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate<IAsyncDataTreeNode<TInput, T> | null> = {}): void {
1021
super.updateOptions(options);
1022
1023
if (options.overrideStyles) {
1024
this.internals.updateStyleOverrides(options.overrideStyles);
1025
}
1026
1027
this.internals.updateOptions(options);
1028
}
1029
}
1030
1031
export interface IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData> extends ICompressibleAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
1032
readonly accessibilityProvider: IListAccessibilityProvider<T>;
1033
readonly overrideStyles?: IStyleOverride<IListStyles>;
1034
readonly selectionNavigation?: boolean;
1035
}
1036
1037
export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {
1038
1039
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
1040
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
1041
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
1042
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
1043
1044
constructor(
1045
user: string,
1046
container: HTMLElement,
1047
virtualDelegate: IListVirtualDelegate<T>,
1048
compressionDelegate: ITreeCompressionDelegate<T>,
1049
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
1050
dataSource: IAsyncDataSource<TInput, T>,
1051
options: IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
1052
@IInstantiationService instantiationService: IInstantiationService,
1053
@IContextKeyService contextKeyService: IContextKeyService,
1054
@IListService listService: IListService,
1055
@IConfigurationService configurationService: IConfigurationService
1056
) {
1057
// eslint-disable-next-line local/code-no-any-casts
1058
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
1059
super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);
1060
this.disposables.add(disposable);
1061
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
1062
this.disposables.add(this.internals);
1063
}
1064
1065
override updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate<IAsyncDataTreeNode<TInput, T> | null>): void {
1066
super.updateOptions(options);
1067
this.internals.updateOptions(options);
1068
}
1069
}
1070
1071
function getDefaultTreeFindMode(configurationService: IConfigurationService) {
1072
const value = configurationService.getValue<'highlight' | 'filter'>(defaultFindModeSettingKey);
1073
1074
if (value === 'highlight') {
1075
return TreeFindMode.Highlight;
1076
} else if (value === 'filter') {
1077
return TreeFindMode.Filter;
1078
}
1079
1080
const deprecatedValue = configurationService.getValue<'simple' | 'highlight' | 'filter'>(keyboardNavigationSettingKey);
1081
1082
if (deprecatedValue === 'simple' || deprecatedValue === 'highlight') {
1083
return TreeFindMode.Highlight;
1084
} else if (deprecatedValue === 'filter') {
1085
return TreeFindMode.Filter;
1086
}
1087
1088
return undefined;
1089
}
1090
1091
function getDefaultTreeFindMatchType(configurationService: IConfigurationService) {
1092
const value = configurationService.getValue<'fuzzy' | 'contiguous'>(defaultFindMatchTypeSettingKey);
1093
1094
if (value === 'fuzzy') {
1095
return TreeFindMatchType.Fuzzy;
1096
} else if (value === 'contiguous') {
1097
return TreeFindMatchType.Contiguous;
1098
}
1099
return undefined;
1100
}
1101
1102
function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(
1103
accessor: ServicesAccessor,
1104
options: TOptions,
1105
): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } {
1106
const configurationService = accessor.get(IConfigurationService);
1107
const contextViewService = accessor.get(IContextViewService);
1108
const contextKeyService = accessor.get(IContextKeyService);
1109
const instantiationService = accessor.get(IInstantiationService);
1110
1111
const getTypeNavigationMode = () => {
1112
// give priority to the context key value to specify a value
1113
const modeString = contextKeyService.getContextKeyValue<'automatic' | 'trigger'>(WorkbenchListTypeNavigationModeKey);
1114
1115
if (modeString === 'automatic') {
1116
return TypeNavigationMode.Automatic;
1117
} else if (modeString === 'trigger') {
1118
return TypeNavigationMode.Trigger;
1119
}
1120
1121
// also check the deprecated context key to set the mode to 'trigger'
1122
const modeBoolean = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
1123
1124
if (modeBoolean === false) {
1125
return TypeNavigationMode.Trigger;
1126
}
1127
1128
// finally, check the setting
1129
const configString = configurationService.getValue<'automatic' | 'trigger'>(typeNavigationModeSettingKey);
1130
1131
if (configString === 'automatic') {
1132
return TypeNavigationMode.Automatic;
1133
} else if (configString === 'trigger') {
1134
return TypeNavigationMode.Trigger;
1135
}
1136
1137
return undefined;
1138
};
1139
1140
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
1141
const [workbenchListOptions, disposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
1142
const paddingBottom = options.paddingBottom;
1143
const renderIndentGuides = options.renderIndentGuides !== undefined ? options.renderIndentGuides : configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);
1144
1145
return {
1146
getTypeNavigationMode,
1147
disposable,
1148
// eslint-disable-next-line local/code-no-dangerous-type-assertions
1149
options: {
1150
// ...options, // TODO@Joao why is this not splatted here?
1151
keyboardSupport: false,
1152
...workbenchListOptions,
1153
indent: typeof configurationService.getValue(treeIndentKey) === 'number' ? configurationService.getValue(treeIndentKey) : undefined,
1154
renderIndentGuides,
1155
smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),
1156
defaultFindMode: options.defaultFindMode ?? getDefaultTreeFindMode(configurationService),
1157
defaultFindMatchType: options.defaultFindMatchType ?? getDefaultTreeFindMatchType(configurationService),
1158
horizontalScrolling,
1159
scrollByPage: Boolean(configurationService.getValue(scrollByPageKey)),
1160
paddingBottom: paddingBottom,
1161
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
1162
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),
1163
contextViewProvider: contextViewService as IContextViewProvider,
1164
findWidgetStyles: defaultFindWidgetStyles,
1165
enableStickyScroll: Boolean(configurationService.getValue(treeStickyScroll)),
1166
stickyScrollMaxItemCount: Number(configurationService.getValue(treeStickyScrollMaxElements)),
1167
} as TOptions
1168
};
1169
}
1170
1171
interface IWorkbenchTreeInternalsOptionsUpdate {
1172
readonly multipleSelectionSupport?: boolean;
1173
}
1174
1175
class WorkbenchTreeInternals<TInput, T, TFilterData> {
1176
1177
readonly contextKeyService: IScopedContextKeyService;
1178
private listSupportsMultiSelect: IContextKey<boolean>;
1179
private listSupportFindWidget: IContextKey<boolean>;
1180
private hasSelectionOrFocus: IContextKey<boolean>;
1181
private hasDoubleSelection: IContextKey<boolean>;
1182
private hasMultiSelection: IContextKey<boolean>;
1183
private treeElementCanCollapse: IContextKey<boolean>;
1184
private treeElementHasParent: IContextKey<boolean>;
1185
private treeElementCanExpand: IContextKey<boolean>;
1186
private treeElementHasChild: IContextKey<boolean>;
1187
private treeFindOpen: IContextKey<boolean>;
1188
private treeStickyScrollFocused: IContextKey<boolean>;
1189
private _useAltAsMultipleSelectionModifier: boolean;
1190
private disposables: IDisposable[] = [];
1191
1192
private navigator: TreeResourceNavigator<T, TFilterData>;
1193
1194
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
1195
1196
constructor(
1197
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
1198
options: IWorkbenchObjectTreeOptions<T, TFilterData> | IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> | IWorkbenchDataTreeOptions<T, TFilterData> | IWorkbenchAsyncDataTreeOptions<T, TFilterData> | IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
1199
getTypeNavigationMode: () => TypeNavigationMode | undefined,
1200
overrideStyles: IStyleOverride<IListStyles> | undefined,
1201
@IContextKeyService contextKeyService: IContextKeyService,
1202
@IListService listService: IListService,
1203
@IConfigurationService configurationService: IConfigurationService
1204
) {
1205
this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);
1206
1207
this.disposables.push(createScrollObserver(this.contextKeyService, tree));
1208
1209
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
1210
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
1211
1212
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
1213
listSelectionNavigation.set(Boolean(options.selectionNavigation));
1214
1215
this.listSupportFindWidget = WorkbenchListSupportsFind.bindTo(this.contextKeyService);
1216
this.listSupportFindWidget.set(options.findWidgetEnabled ?? true);
1217
1218
this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
1219
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
1220
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
1221
1222
this.treeElementCanCollapse = WorkbenchTreeElementCanCollapse.bindTo(this.contextKeyService);
1223
this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService);
1224
this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService);
1225
this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService);
1226
1227
this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService);
1228
this.treeStickyScrollFocused = WorkbenchTreeStickyScrollFocused.bindTo(this.contextKeyService);
1229
1230
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
1231
1232
this.updateStyleOverrides(overrideStyles);
1233
1234
const updateCollapseContextKeys = () => {
1235
const focus = tree.getFocus()[0];
1236
1237
if (!focus) {
1238
return;
1239
}
1240
1241
const node = tree.getNode(focus);
1242
this.treeElementCanCollapse.set(node.collapsible && !node.collapsed);
1243
this.treeElementHasParent.set(!!tree.getParentElement(focus));
1244
this.treeElementCanExpand.set(node.collapsible && node.collapsed);
1245
this.treeElementHasChild.set(!!tree.getFirstElementChild(focus));
1246
};
1247
1248
const interestingContextKeys = new Set();
1249
interestingContextKeys.add(WorkbenchListTypeNavigationModeKey);
1250
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
1251
1252
this.disposables.push(
1253
this.contextKeyService,
1254
(listService as ListService).register(tree),
1255
tree.onDidChangeSelection(() => {
1256
const selection = tree.getSelection();
1257
const focus = tree.getFocus();
1258
1259
this.contextKeyService.bufferChangeEvents(() => {
1260
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
1261
this.hasMultiSelection.set(selection.length > 1);
1262
this.hasDoubleSelection.set(selection.length === 2);
1263
});
1264
}),
1265
tree.onDidChangeFocus(() => {
1266
const selection = tree.getSelection();
1267
const focus = tree.getFocus();
1268
1269
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
1270
updateCollapseContextKeys();
1271
}),
1272
tree.onDidChangeCollapseState(updateCollapseContextKeys),
1273
tree.onDidChangeModel(updateCollapseContextKeys),
1274
tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)),
1275
tree.onDidChangeStickyScrollFocused(focused => this.treeStickyScrollFocused.set(focused)),
1276
configurationService.onDidChangeConfiguration(e => {
1277
let newOptions: IAbstractTreeOptionsUpdate<unknown> = {};
1278
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
1279
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
1280
}
1281
if (e.affectsConfiguration(treeIndentKey)) {
1282
const indent = configurationService.getValue<number>(treeIndentKey);
1283
newOptions = { ...newOptions, indent };
1284
}
1285
if (e.affectsConfiguration(treeRenderIndentGuidesKey) && options.renderIndentGuides === undefined) {
1286
const renderIndentGuides = configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);
1287
newOptions = { ...newOptions, renderIndentGuides };
1288
}
1289
if (e.affectsConfiguration(listSmoothScrolling)) {
1290
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
1291
newOptions = { ...newOptions, smoothScrolling };
1292
}
1293
if (e.affectsConfiguration(defaultFindModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {
1294
const defaultFindMode = getDefaultTreeFindMode(configurationService);
1295
newOptions = { ...newOptions, defaultFindMode };
1296
}
1297
if (e.affectsConfiguration(typeNavigationModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {
1298
const typeNavigationMode = getTypeNavigationMode();
1299
newOptions = { ...newOptions, typeNavigationMode };
1300
}
1301
if (e.affectsConfiguration(defaultFindMatchTypeSettingKey)) {
1302
const defaultFindMatchType = getDefaultTreeFindMatchType(configurationService);
1303
newOptions = { ...newOptions, defaultFindMatchType };
1304
}
1305
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
1306
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
1307
newOptions = { ...newOptions, horizontalScrolling };
1308
}
1309
if (e.affectsConfiguration(scrollByPageKey)) {
1310
const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));
1311
newOptions = { ...newOptions, scrollByPage };
1312
}
1313
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {
1314
newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };
1315
}
1316
if (e.affectsConfiguration(treeStickyScroll)) {
1317
const enableStickyScroll = configurationService.getValue<boolean>(treeStickyScroll);
1318
newOptions = { ...newOptions, enableStickyScroll };
1319
}
1320
if (e.affectsConfiguration(treeStickyScrollMaxElements)) {
1321
const stickyScrollMaxItemCount = Math.max(1, configurationService.getValue<number>(treeStickyScrollMaxElements));
1322
newOptions = { ...newOptions, stickyScrollMaxItemCount };
1323
}
1324
if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {
1325
const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);
1326
newOptions = { ...newOptions, mouseWheelScrollSensitivity };
1327
}
1328
if (e.affectsConfiguration(fastScrollSensitivityKey)) {
1329
const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);
1330
newOptions = { ...newOptions, fastScrollSensitivity };
1331
}
1332
if (Object.keys(newOptions).length > 0) {
1333
tree.updateOptions(newOptions);
1334
}
1335
}),
1336
this.contextKeyService.onDidChangeContext(e => {
1337
if (e.affectsSome(interestingContextKeys)) {
1338
tree.updateOptions({ typeNavigationMode: getTypeNavigationMode() });
1339
}
1340
})
1341
);
1342
1343
this.navigator = new TreeResourceNavigator(tree, { configurationService, ...options });
1344
this.disposables.push(this.navigator);
1345
}
1346
1347
get useAltAsMultipleSelectionModifier(): boolean {
1348
return this._useAltAsMultipleSelectionModifier;
1349
}
1350
1351
updateOptions(options: IWorkbenchTreeInternalsOptionsUpdate): void {
1352
if (options.multipleSelectionSupport !== undefined) {
1353
this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
1354
}
1355
}
1356
1357
updateStyleOverrides(overrideStyles?: IStyleOverride<IListStyles>): void {
1358
this.tree.style(overrideStyles ? getListStyles(overrideStyles) : defaultListStyles);
1359
}
1360
1361
dispose(): void {
1362
this.disposables = dispose(this.disposables);
1363
}
1364
}
1365
1366
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
1367
1368
configurationRegistry.registerConfiguration({
1369
id: 'workbench',
1370
order: 7,
1371
title: localize('workbenchConfigurationTitle', "Workbench"),
1372
type: 'object',
1373
properties: {
1374
[multiSelectModifierSettingKey]: {
1375
type: 'string',
1376
enum: ['ctrlCmd', 'alt'],
1377
markdownEnumDescriptions: [
1378
localize('multiSelectModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."),
1379
localize('multiSelectModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.")
1380
],
1381
default: 'ctrlCmd',
1382
description: localize({
1383
key: 'multiSelectModifier',
1384
comment: [
1385
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
1386
'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'
1387
]
1388
}, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.")
1389
},
1390
[openModeSettingKey]: {
1391
type: 'string',
1392
enum: ['singleClick', 'doubleClick'],
1393
default: 'singleClick',
1394
description: localize({
1395
key: 'openModeModifier',
1396
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
1397
}, "Controls how to open items in trees and lists using the mouse (if supported). Note that some trees and lists might choose to ignore this setting if it is not applicable.")
1398
},
1399
[horizontalScrollingKey]: {
1400
type: 'boolean',
1401
default: false,
1402
description: localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")
1403
},
1404
[scrollByPageKey]: {
1405
type: 'boolean',
1406
default: false,
1407
description: localize('list.scrollByPage', "Controls whether clicks in the scrollbar scroll page by page.")
1408
},
1409
[treeIndentKey]: {
1410
type: 'number',
1411
default: 8,
1412
minimum: 4,
1413
maximum: 40,
1414
description: localize('tree indent setting', "Controls tree indentation in pixels.")
1415
},
1416
[treeRenderIndentGuidesKey]: {
1417
type: 'string',
1418
enum: ['none', 'onHover', 'always'],
1419
default: 'onHover',
1420
description: localize('render tree indent guides', "Controls whether the tree should render indent guides.")
1421
},
1422
[listSmoothScrolling]: {
1423
type: 'boolean',
1424
default: false,
1425
description: localize('list smoothScrolling setting', "Controls whether lists and trees have smooth scrolling."),
1426
},
1427
[mouseWheelScrollSensitivityKey]: {
1428
type: 'number',
1429
default: 1,
1430
markdownDescription: localize('Mouse Wheel Scroll Sensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.")
1431
},
1432
[fastScrollSensitivityKey]: {
1433
type: 'number',
1434
default: 5,
1435
markdownDescription: localize('Fast Scroll Sensitivity', "Scrolling speed multiplier when pressing `Alt`.")
1436
},
1437
[defaultFindModeSettingKey]: {
1438
type: 'string',
1439
enum: ['highlight', 'filter'],
1440
enumDescriptions: [
1441
localize('defaultFindModeSettingKey.highlight', "Highlight elements when searching. Further up and down navigation will traverse only the highlighted elements."),
1442
localize('defaultFindModeSettingKey.filter', "Filter elements when searching.")
1443
],
1444
default: 'highlight',
1445
description: localize('defaultFindModeSettingKey', "Controls the default find mode for lists and trees in the workbench.")
1446
},
1447
[keyboardNavigationSettingKey]: {
1448
type: 'string',
1449
enum: ['simple', 'highlight', 'filter'],
1450
enumDescriptions: [
1451
localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),
1452
localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),
1453
localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")
1454
],
1455
default: 'highlight',
1456
description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter."),
1457
deprecated: true,
1458
deprecationMessage: localize('keyboardNavigationSettingKeyDeprecated', "Please use 'workbench.list.defaultFindMode' and 'workbench.list.typeNavigationMode' instead.")
1459
},
1460
[defaultFindMatchTypeSettingKey]: {
1461
type: 'string',
1462
enum: ['fuzzy', 'contiguous'],
1463
enumDescriptions: [
1464
localize('defaultFindMatchTypeSettingKey.fuzzy', "Use fuzzy matching when searching."),
1465
localize('defaultFindMatchTypeSettingKey.contiguous', "Use contiguous matching when searching.")
1466
],
1467
default: 'fuzzy',
1468
description: localize('defaultFindMatchTypeSettingKey', "Controls the type of matching used when searching lists and trees in the workbench.")
1469
},
1470
[treeExpandMode]: {
1471
type: 'string',
1472
enum: ['singleClick', 'doubleClick'],
1473
default: 'singleClick',
1474
description: localize('expand mode', "Controls how tree folders are expanded when clicking the folder names. Note that some trees and lists might choose to ignore this setting if it is not applicable."),
1475
},
1476
[treeStickyScroll]: {
1477
type: 'boolean',
1478
default: true,
1479
description: localize('sticky scroll', "Controls whether sticky scrolling is enabled in trees."),
1480
},
1481
[treeStickyScrollMaxElements]: {
1482
type: 'number',
1483
minimum: 1,
1484
default: 7,
1485
markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when {0} is enabled.", '`#workbench.tree.enableStickyScroll#`'),
1486
},
1487
[typeNavigationModeSettingKey]: {
1488
type: 'string',
1489
enum: ['automatic', 'trigger'],
1490
default: 'automatic',
1491
markdownDescription: localize('typeNavigationMode2', "Controls how type navigation works in lists and trees in the workbench. When set to `trigger`, type navigation begins once the `list.triggerTypeNavigation` command is run."),
1492
}
1493
}
1494
});
1495
1496