Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/list/browser/listService.ts
3296 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, 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
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
878
super(user, container, delegate, renderers, treeOptions);
879
this.disposables.add(disposable);
880
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
881
this.disposables.add(this.internals);
882
}
883
884
override updateOptions(options: IAbstractTreeOptionsUpdate): void {
885
super.updateOptions(options);
886
this.internals.updateOptions(options);
887
}
888
}
889
890
export interface IWorkbenchCompressibleObjectTreeOptionsUpdate extends ICompressibleObjectTreeOptionsUpdate {
891
readonly overrideStyles?: IStyleOverride<IListStyles>;
892
}
893
894
export interface IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> extends IWorkbenchCompressibleObjectTreeOptionsUpdate, ICompressibleObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
895
readonly accessibilityProvider: IListAccessibilityProvider<T>;
896
readonly selectionNavigation?: boolean;
897
}
898
899
export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends CompressibleObjectTree<T, TFilterData> {
900
901
private internals: WorkbenchTreeInternals<any, T, TFilterData>;
902
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
903
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
904
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
905
906
constructor(
907
user: string,
908
container: HTMLElement,
909
delegate: IListVirtualDelegate<T>,
910
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
911
options: IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>,
912
@IInstantiationService instantiationService: IInstantiationService,
913
@IContextKeyService contextKeyService: IContextKeyService,
914
@IListService listService: IListService,
915
@IConfigurationService configurationService: IConfigurationService
916
) {
917
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
918
super(user, container, delegate, renderers, treeOptions);
919
this.disposables.add(disposable);
920
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
921
this.disposables.add(this.internals);
922
}
923
924
override updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate = {}): void {
925
super.updateOptions(options);
926
927
if (options.overrideStyles) {
928
this.internals.updateStyleOverrides(options.overrideStyles);
929
}
930
931
this.internals.updateOptions(options);
932
}
933
}
934
935
export interface IWorkbenchDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {
936
readonly overrideStyles?: IStyleOverride<IListStyles>;
937
}
938
939
export interface IWorkbenchDataTreeOptions<T, TFilterData> extends IWorkbenchDataTreeOptionsUpdate, IDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
940
readonly accessibilityProvider: IListAccessibilityProvider<T>;
941
readonly selectionNavigation?: boolean;
942
}
943
944
export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<TInput, T, TFilterData> {
945
946
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
947
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
948
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
949
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
950
951
constructor(
952
user: string,
953
container: HTMLElement,
954
delegate: IListVirtualDelegate<T>,
955
renderers: ITreeRenderer<T, TFilterData, any>[],
956
dataSource: IDataSource<TInput, T>,
957
options: IWorkbenchDataTreeOptions<T, TFilterData>,
958
@IInstantiationService instantiationService: IInstantiationService,
959
@IContextKeyService contextKeyService: IContextKeyService,
960
@IListService listService: IListService,
961
@IConfigurationService configurationService: IConfigurationService
962
) {
963
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
964
super(user, container, delegate, renderers, dataSource, treeOptions);
965
this.disposables.add(disposable);
966
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
967
this.disposables.add(this.internals);
968
}
969
970
override updateOptions(options: IWorkbenchDataTreeOptionsUpdate = {}): void {
971
super.updateOptions(options);
972
973
if (options.overrideStyles !== undefined) {
974
this.internals.updateStyleOverrides(options.overrideStyles);
975
}
976
977
this.internals.updateOptions(options);
978
}
979
}
980
981
export interface IWorkbenchAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {
982
readonly overrideStyles?: IStyleOverride<IListStyles>;
983
}
984
985
export interface IWorkbenchAsyncDataTreeOptions<T, TFilterData> extends IWorkbenchAsyncDataTreeOptionsUpdate, IAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
986
readonly accessibilityProvider: IListAccessibilityProvider<T>;
987
readonly selectionNavigation?: boolean;
988
}
989
990
export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {
991
992
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
993
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
994
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
995
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
996
997
constructor(
998
user: string,
999
container: HTMLElement,
1000
delegate: IListVirtualDelegate<T>,
1001
renderers: ITreeRenderer<T, TFilterData, any>[],
1002
dataSource: IAsyncDataSource<TInput, T>,
1003
options: IWorkbenchAsyncDataTreeOptions<T, TFilterData>,
1004
@IInstantiationService instantiationService: IInstantiationService,
1005
@IContextKeyService contextKeyService: IContextKeyService,
1006
@IListService listService: IListService,
1007
@IConfigurationService configurationService: IConfigurationService
1008
) {
1009
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
1010
super(user, container, delegate, renderers, dataSource, treeOptions);
1011
this.disposables.add(disposable);
1012
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
1013
this.disposables.add(this.internals);
1014
}
1015
1016
override updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate = {}): void {
1017
super.updateOptions(options);
1018
1019
if (options.overrideStyles) {
1020
this.internals.updateStyleOverrides(options.overrideStyles);
1021
}
1022
1023
this.internals.updateOptions(options);
1024
}
1025
}
1026
1027
export interface IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData> extends ICompressibleAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {
1028
readonly accessibilityProvider: IListAccessibilityProvider<T>;
1029
readonly overrideStyles?: IStyleOverride<IListStyles>;
1030
readonly selectionNavigation?: boolean;
1031
}
1032
1033
export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {
1034
1035
private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;
1036
get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }
1037
get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }
1038
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }
1039
1040
constructor(
1041
user: string,
1042
container: HTMLElement,
1043
virtualDelegate: IListVirtualDelegate<T>,
1044
compressionDelegate: ITreeCompressionDelegate<T>,
1045
renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],
1046
dataSource: IAsyncDataSource<TInput, T>,
1047
options: IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
1048
@IInstantiationService instantiationService: IInstantiationService,
1049
@IContextKeyService contextKeyService: IContextKeyService,
1050
@IListService listService: IListService,
1051
@IConfigurationService configurationService: IConfigurationService
1052
) {
1053
const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);
1054
super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);
1055
this.disposables.add(disposable);
1056
this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);
1057
this.disposables.add(this.internals);
1058
}
1059
1060
override updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate): void {
1061
super.updateOptions(options);
1062
this.internals.updateOptions(options);
1063
}
1064
}
1065
1066
function getDefaultTreeFindMode(configurationService: IConfigurationService) {
1067
const value = configurationService.getValue<'highlight' | 'filter'>(defaultFindModeSettingKey);
1068
1069
if (value === 'highlight') {
1070
return TreeFindMode.Highlight;
1071
} else if (value === 'filter') {
1072
return TreeFindMode.Filter;
1073
}
1074
1075
const deprecatedValue = configurationService.getValue<'simple' | 'highlight' | 'filter'>(keyboardNavigationSettingKey);
1076
1077
if (deprecatedValue === 'simple' || deprecatedValue === 'highlight') {
1078
return TreeFindMode.Highlight;
1079
} else if (deprecatedValue === 'filter') {
1080
return TreeFindMode.Filter;
1081
}
1082
1083
return undefined;
1084
}
1085
1086
function getDefaultTreeFindMatchType(configurationService: IConfigurationService) {
1087
const value = configurationService.getValue<'fuzzy' | 'contiguous'>(defaultFindMatchTypeSettingKey);
1088
1089
if (value === 'fuzzy') {
1090
return TreeFindMatchType.Fuzzy;
1091
} else if (value === 'contiguous') {
1092
return TreeFindMatchType.Contiguous;
1093
}
1094
return undefined;
1095
}
1096
1097
function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(
1098
accessor: ServicesAccessor,
1099
options: TOptions,
1100
): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } {
1101
const configurationService = accessor.get(IConfigurationService);
1102
const contextViewService = accessor.get(IContextViewService);
1103
const contextKeyService = accessor.get(IContextKeyService);
1104
const instantiationService = accessor.get(IInstantiationService);
1105
1106
const getTypeNavigationMode = () => {
1107
// give priority to the context key value to specify a value
1108
const modeString = contextKeyService.getContextKeyValue<'automatic' | 'trigger'>(WorkbenchListTypeNavigationModeKey);
1109
1110
if (modeString === 'automatic') {
1111
return TypeNavigationMode.Automatic;
1112
} else if (modeString === 'trigger') {
1113
return TypeNavigationMode.Trigger;
1114
}
1115
1116
// also check the deprecated context key to set the mode to 'trigger'
1117
const modeBoolean = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
1118
1119
if (modeBoolean === false) {
1120
return TypeNavigationMode.Trigger;
1121
}
1122
1123
// finally, check the setting
1124
const configString = configurationService.getValue<'automatic' | 'trigger'>(typeNavigationModeSettingKey);
1125
1126
if (configString === 'automatic') {
1127
return TypeNavigationMode.Automatic;
1128
} else if (configString === 'trigger') {
1129
return TypeNavigationMode.Trigger;
1130
}
1131
1132
return undefined;
1133
};
1134
1135
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));
1136
const [workbenchListOptions, disposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);
1137
const paddingBottom = options.paddingBottom;
1138
const renderIndentGuides = options.renderIndentGuides !== undefined ? options.renderIndentGuides : configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);
1139
1140
return {
1141
getTypeNavigationMode,
1142
disposable,
1143
// eslint-disable-next-line local/code-no-dangerous-type-assertions
1144
options: {
1145
// ...options, // TODO@Joao why is this not splatted here?
1146
keyboardSupport: false,
1147
...workbenchListOptions,
1148
indent: typeof configurationService.getValue(treeIndentKey) === 'number' ? configurationService.getValue(treeIndentKey) : undefined,
1149
renderIndentGuides,
1150
smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),
1151
defaultFindMode: getDefaultTreeFindMode(configurationService),
1152
defaultFindMatchType: getDefaultTreeFindMatchType(configurationService),
1153
horizontalScrolling,
1154
scrollByPage: Boolean(configurationService.getValue(scrollByPageKey)),
1155
paddingBottom: paddingBottom,
1156
hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,
1157
expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),
1158
contextViewProvider: contextViewService as IContextViewProvider,
1159
findWidgetStyles: defaultFindWidgetStyles,
1160
enableStickyScroll: Boolean(configurationService.getValue(treeStickyScroll)),
1161
stickyScrollMaxItemCount: Number(configurationService.getValue(treeStickyScrollMaxElements)),
1162
} as TOptions
1163
};
1164
}
1165
1166
interface IWorkbenchTreeInternalsOptionsUpdate {
1167
readonly multipleSelectionSupport?: boolean;
1168
}
1169
1170
class WorkbenchTreeInternals<TInput, T, TFilterData> {
1171
1172
readonly contextKeyService: IScopedContextKeyService;
1173
private listSupportsMultiSelect: IContextKey<boolean>;
1174
private listSupportFindWidget: IContextKey<boolean>;
1175
private hasSelectionOrFocus: IContextKey<boolean>;
1176
private hasDoubleSelection: IContextKey<boolean>;
1177
private hasMultiSelection: IContextKey<boolean>;
1178
private treeElementCanCollapse: IContextKey<boolean>;
1179
private treeElementHasParent: IContextKey<boolean>;
1180
private treeElementCanExpand: IContextKey<boolean>;
1181
private treeElementHasChild: IContextKey<boolean>;
1182
private treeFindOpen: IContextKey<boolean>;
1183
private treeStickyScrollFocused: IContextKey<boolean>;
1184
private _useAltAsMultipleSelectionModifier: boolean;
1185
private disposables: IDisposable[] = [];
1186
1187
private navigator: TreeResourceNavigator<T, TFilterData>;
1188
1189
get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }
1190
1191
constructor(
1192
private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,
1193
options: IWorkbenchObjectTreeOptions<T, TFilterData> | IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> | IWorkbenchDataTreeOptions<T, TFilterData> | IWorkbenchAsyncDataTreeOptions<T, TFilterData> | IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,
1194
getTypeNavigationMode: () => TypeNavigationMode | undefined,
1195
overrideStyles: IStyleOverride<IListStyles> | undefined,
1196
@IContextKeyService contextKeyService: IContextKeyService,
1197
@IListService listService: IListService,
1198
@IConfigurationService configurationService: IConfigurationService
1199
) {
1200
this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);
1201
1202
this.disposables.push(createScrollObserver(this.contextKeyService, tree));
1203
1204
this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);
1205
this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);
1206
1207
const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);
1208
listSelectionNavigation.set(Boolean(options.selectionNavigation));
1209
1210
this.listSupportFindWidget = WorkbenchListSupportsFind.bindTo(this.contextKeyService);
1211
this.listSupportFindWidget.set(options.findWidgetEnabled ?? true);
1212
1213
this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);
1214
this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
1215
this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
1216
1217
this.treeElementCanCollapse = WorkbenchTreeElementCanCollapse.bindTo(this.contextKeyService);
1218
this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService);
1219
this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService);
1220
this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService);
1221
1222
this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService);
1223
this.treeStickyScrollFocused = WorkbenchTreeStickyScrollFocused.bindTo(this.contextKeyService);
1224
1225
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
1226
1227
this.updateStyleOverrides(overrideStyles);
1228
1229
const updateCollapseContextKeys = () => {
1230
const focus = tree.getFocus()[0];
1231
1232
if (!focus) {
1233
return;
1234
}
1235
1236
const node = tree.getNode(focus);
1237
this.treeElementCanCollapse.set(node.collapsible && !node.collapsed);
1238
this.treeElementHasParent.set(!!tree.getParentElement(focus));
1239
this.treeElementCanExpand.set(node.collapsible && node.collapsed);
1240
this.treeElementHasChild.set(!!tree.getFirstElementChild(focus));
1241
};
1242
1243
const interestingContextKeys = new Set();
1244
interestingContextKeys.add(WorkbenchListTypeNavigationModeKey);
1245
interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationLegacyKey);
1246
1247
this.disposables.push(
1248
this.contextKeyService,
1249
(listService as ListService).register(tree),
1250
tree.onDidChangeSelection(() => {
1251
const selection = tree.getSelection();
1252
const focus = tree.getFocus();
1253
1254
this.contextKeyService.bufferChangeEvents(() => {
1255
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
1256
this.hasMultiSelection.set(selection.length > 1);
1257
this.hasDoubleSelection.set(selection.length === 2);
1258
});
1259
}),
1260
tree.onDidChangeFocus(() => {
1261
const selection = tree.getSelection();
1262
const focus = tree.getFocus();
1263
1264
this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);
1265
updateCollapseContextKeys();
1266
}),
1267
tree.onDidChangeCollapseState(updateCollapseContextKeys),
1268
tree.onDidChangeModel(updateCollapseContextKeys),
1269
tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)),
1270
tree.onDidChangeStickyScrollFocused(focused => this.treeStickyScrollFocused.set(focused)),
1271
configurationService.onDidChangeConfiguration(e => {
1272
let newOptions: IAbstractTreeOptionsUpdate = {};
1273
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
1274
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
1275
}
1276
if (e.affectsConfiguration(treeIndentKey)) {
1277
const indent = configurationService.getValue<number>(treeIndentKey);
1278
newOptions = { ...newOptions, indent };
1279
}
1280
if (e.affectsConfiguration(treeRenderIndentGuidesKey) && options.renderIndentGuides === undefined) {
1281
const renderIndentGuides = configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);
1282
newOptions = { ...newOptions, renderIndentGuides };
1283
}
1284
if (e.affectsConfiguration(listSmoothScrolling)) {
1285
const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));
1286
newOptions = { ...newOptions, smoothScrolling };
1287
}
1288
if (e.affectsConfiguration(defaultFindModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {
1289
const defaultFindMode = getDefaultTreeFindMode(configurationService);
1290
newOptions = { ...newOptions, defaultFindMode };
1291
}
1292
if (e.affectsConfiguration(typeNavigationModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {
1293
const typeNavigationMode = getTypeNavigationMode();
1294
newOptions = { ...newOptions, typeNavigationMode };
1295
}
1296
if (e.affectsConfiguration(defaultFindMatchTypeSettingKey)) {
1297
const defaultFindMatchType = getDefaultTreeFindMatchType(configurationService);
1298
newOptions = { ...newOptions, defaultFindMatchType };
1299
}
1300
if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {
1301
const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));
1302
newOptions = { ...newOptions, horizontalScrolling };
1303
}
1304
if (e.affectsConfiguration(scrollByPageKey)) {
1305
const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));
1306
newOptions = { ...newOptions, scrollByPage };
1307
}
1308
if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {
1309
newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };
1310
}
1311
if (e.affectsConfiguration(treeStickyScroll)) {
1312
const enableStickyScroll = configurationService.getValue<boolean>(treeStickyScroll);
1313
newOptions = { ...newOptions, enableStickyScroll };
1314
}
1315
if (e.affectsConfiguration(treeStickyScrollMaxElements)) {
1316
const stickyScrollMaxItemCount = Math.max(1, configurationService.getValue<number>(treeStickyScrollMaxElements));
1317
newOptions = { ...newOptions, stickyScrollMaxItemCount };
1318
}
1319
if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {
1320
const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);
1321
newOptions = { ...newOptions, mouseWheelScrollSensitivity };
1322
}
1323
if (e.affectsConfiguration(fastScrollSensitivityKey)) {
1324
const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);
1325
newOptions = { ...newOptions, fastScrollSensitivity };
1326
}
1327
if (Object.keys(newOptions).length > 0) {
1328
tree.updateOptions(newOptions);
1329
}
1330
}),
1331
this.contextKeyService.onDidChangeContext(e => {
1332
if (e.affectsSome(interestingContextKeys)) {
1333
tree.updateOptions({ typeNavigationMode: getTypeNavigationMode() });
1334
}
1335
})
1336
);
1337
1338
this.navigator = new TreeResourceNavigator(tree, { configurationService, ...options });
1339
this.disposables.push(this.navigator);
1340
}
1341
1342
get useAltAsMultipleSelectionModifier(): boolean {
1343
return this._useAltAsMultipleSelectionModifier;
1344
}
1345
1346
updateOptions(options: IWorkbenchTreeInternalsOptionsUpdate): void {
1347
if (options.multipleSelectionSupport !== undefined) {
1348
this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);
1349
}
1350
}
1351
1352
updateStyleOverrides(overrideStyles?: IStyleOverride<IListStyles>): void {
1353
this.tree.style(overrideStyles ? getListStyles(overrideStyles) : defaultListStyles);
1354
}
1355
1356
dispose(): void {
1357
this.disposables = dispose(this.disposables);
1358
}
1359
}
1360
1361
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
1362
1363
configurationRegistry.registerConfiguration({
1364
id: 'workbench',
1365
order: 7,
1366
title: localize('workbenchConfigurationTitle', "Workbench"),
1367
type: 'object',
1368
properties: {
1369
[multiSelectModifierSettingKey]: {
1370
type: 'string',
1371
enum: ['ctrlCmd', 'alt'],
1372
markdownEnumDescriptions: [
1373
localize('multiSelectModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."),
1374
localize('multiSelectModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.")
1375
],
1376
default: 'ctrlCmd',
1377
description: localize({
1378
key: 'multiSelectModifier',
1379
comment: [
1380
'- `ctrlCmd` refers to a value the setting can take and should not be localized.',
1381
'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'
1382
]
1383
}, "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.")
1384
},
1385
[openModeSettingKey]: {
1386
type: 'string',
1387
enum: ['singleClick', 'doubleClick'],
1388
default: 'singleClick',
1389
description: localize({
1390
key: 'openModeModifier',
1391
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
1392
}, "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.")
1393
},
1394
[horizontalScrollingKey]: {
1395
type: 'boolean',
1396
default: false,
1397
description: localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")
1398
},
1399
[scrollByPageKey]: {
1400
type: 'boolean',
1401
default: false,
1402
description: localize('list.scrollByPage', "Controls whether clicks in the scrollbar scroll page by page.")
1403
},
1404
[treeIndentKey]: {
1405
type: 'number',
1406
default: 8,
1407
minimum: 4,
1408
maximum: 40,
1409
description: localize('tree indent setting', "Controls tree indentation in pixels.")
1410
},
1411
[treeRenderIndentGuidesKey]: {
1412
type: 'string',
1413
enum: ['none', 'onHover', 'always'],
1414
default: 'onHover',
1415
description: localize('render tree indent guides', "Controls whether the tree should render indent guides.")
1416
},
1417
[listSmoothScrolling]: {
1418
type: 'boolean',
1419
default: false,
1420
description: localize('list smoothScrolling setting', "Controls whether lists and trees have smooth scrolling."),
1421
},
1422
[mouseWheelScrollSensitivityKey]: {
1423
type: 'number',
1424
default: 1,
1425
markdownDescription: localize('Mouse Wheel Scroll Sensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.")
1426
},
1427
[fastScrollSensitivityKey]: {
1428
type: 'number',
1429
default: 5,
1430
markdownDescription: localize('Fast Scroll Sensitivity', "Scrolling speed multiplier when pressing `Alt`.")
1431
},
1432
[defaultFindModeSettingKey]: {
1433
type: 'string',
1434
enum: ['highlight', 'filter'],
1435
enumDescriptions: [
1436
localize('defaultFindModeSettingKey.highlight', "Highlight elements when searching. Further up and down navigation will traverse only the highlighted elements."),
1437
localize('defaultFindModeSettingKey.filter', "Filter elements when searching.")
1438
],
1439
default: 'highlight',
1440
description: localize('defaultFindModeSettingKey', "Controls the default find mode for lists and trees in the workbench.")
1441
},
1442
[keyboardNavigationSettingKey]: {
1443
type: 'string',
1444
enum: ['simple', 'highlight', 'filter'],
1445
enumDescriptions: [
1446
localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),
1447
localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),
1448
localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")
1449
],
1450
default: 'highlight',
1451
description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter."),
1452
deprecated: true,
1453
deprecationMessage: localize('keyboardNavigationSettingKeyDeprecated', "Please use 'workbench.list.defaultFindMode' and 'workbench.list.typeNavigationMode' instead.")
1454
},
1455
[defaultFindMatchTypeSettingKey]: {
1456
type: 'string',
1457
enum: ['fuzzy', 'contiguous'],
1458
enumDescriptions: [
1459
localize('defaultFindMatchTypeSettingKey.fuzzy', "Use fuzzy matching when searching."),
1460
localize('defaultFindMatchTypeSettingKey.contiguous', "Use contiguous matching when searching.")
1461
],
1462
default: 'fuzzy',
1463
description: localize('defaultFindMatchTypeSettingKey', "Controls the type of matching used when searching lists and trees in the workbench.")
1464
},
1465
[treeExpandMode]: {
1466
type: 'string',
1467
enum: ['singleClick', 'doubleClick'],
1468
default: 'singleClick',
1469
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."),
1470
},
1471
[treeStickyScroll]: {
1472
type: 'boolean',
1473
default: true,
1474
description: localize('sticky scroll', "Controls whether sticky scrolling is enabled in trees."),
1475
},
1476
[treeStickyScrollMaxElements]: {
1477
type: 'number',
1478
minimum: 1,
1479
default: 7,
1480
markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when {0} is enabled.", '`#workbench.tree.enableStickyScroll#`'),
1481
},
1482
[typeNavigationModeSettingKey]: {
1483
type: 'string',
1484
enum: ['automatic', 'trigger'],
1485
default: 'automatic',
1486
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."),
1487
}
1488
}
1489
});
1490
1491