Path: blob/main/src/vs/platform/list/browser/listService.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isActiveElement, isKeyboardEvent } from '../../../base/browser/dom.js';6import { IContextViewProvider } from '../../../base/browser/ui/contextview/contextview.js';7import { IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate } from '../../../base/browser/ui/list/list.js';8import { IPagedListOptions, IPagedRenderer, PagedList } from '../../../base/browser/ui/list/listPaging.js';9import { IKeyboardNavigationEventFilter, IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, IMultipleSelectionController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List, TypeNavigationMode } from '../../../base/browser/ui/list/listWidget.js';10import { ITableColumn, ITableRenderer, ITableVirtualDelegate } from '../../../base/browser/ui/table/table.js';11import { ITableOptions, ITableOptionsUpdate, ITableStyles, Table } from '../../../base/browser/ui/table/tableWidget.js';12import { IAbstractTreeOptions, IAbstractTreeOptionsUpdate, RenderIndentGuides, TreeFindMatchType, TreeFindMode } from '../../../base/browser/ui/tree/abstractTree.js';13import { AsyncDataTree, CompressibleAsyncDataTree, IAsyncDataTreeOptions, IAsyncDataTreeOptionsUpdate, ICompressibleAsyncDataTreeOptions, ICompressibleAsyncDataTreeOptionsUpdate, ITreeCompressionDelegate } from '../../../base/browser/ui/tree/asyncDataTree.js';14import { DataTree, IDataTreeOptions } from '../../../base/browser/ui/tree/dataTree.js';15import { CompressibleObjectTree, ICompressibleObjectTreeOptions, ICompressibleObjectTreeOptionsUpdate, ICompressibleTreeRenderer, IObjectTreeOptions, ObjectTree } from '../../../base/browser/ui/tree/objectTree.js';16import { IAsyncDataSource, IDataSource, ITreeEvent, ITreeRenderer } from '../../../base/browser/ui/tree/tree.js';17import { Emitter, Event } from '../../../base/common/event.js';18import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../base/common/lifecycle.js';19import { localize } from '../../../nls.js';20import { IConfigurationService } from '../../configuration/common/configuration.js';21import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../configuration/common/configurationRegistry.js';22import { ContextKeyExpr, IContextKey, IContextKeyService, IScopedContextKeyService, RawContextKey } from '../../contextkey/common/contextkey.js';23import { InputFocusedContextKey } from '../../contextkey/common/contextkeys.js';24import { IContextViewService } from '../../contextview/browser/contextView.js';25import { IEditorOptions } from '../../editor/common/editor.js';26import { createDecorator, IInstantiationService, ServicesAccessor } from '../../instantiation/common/instantiation.js';27import { IKeybindingService } from '../../keybinding/common/keybinding.js';28import { ResultKind } from '../../keybinding/common/keybindingResolver.js';29import { Registry } from '../../registry/common/platform.js';30import { defaultFindWidgetStyles, defaultListStyles, getListStyles, IStyleOverride } from '../../theme/browser/defaultStyles.js';3132export type ListWidget = List<any> | PagedList<any> | ObjectTree<any, any> | DataTree<any, any, any> | AsyncDataTree<any, any, any> | Table<any>;33export 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>;3435export const IListService = createDecorator<IListService>('listService');3637export interface IListService {3839readonly _serviceBrand: undefined;4041/**42* Returns the currently focused list widget if any.43*/44readonly lastFocusedList: WorkbenchListWidget | undefined;45}4647interface IRegisteredList {48widget: WorkbenchListWidget;49extraContextKeys?: (IContextKey<boolean>)[];50}5152export class ListService implements IListService {5354declare readonly _serviceBrand: undefined;5556private readonly disposables = new DisposableStore();57private lists: IRegisteredList[] = [];58private _lastFocusedWidget: WorkbenchListWidget | undefined = undefined;5960get lastFocusedList(): WorkbenchListWidget | undefined {61return this._lastFocusedWidget;62}6364constructor() { }6566private setLastFocusedList(widget: WorkbenchListWidget | undefined): void {67if (widget === this._lastFocusedWidget) {68return;69}7071this._lastFocusedWidget?.getHTMLElement().classList.remove('last-focused');72this._lastFocusedWidget = widget;73this._lastFocusedWidget?.getHTMLElement().classList.add('last-focused');74}7576register(widget: WorkbenchListWidget, extraContextKeys?: (IContextKey<boolean>)[]): IDisposable {77if (this.lists.some(l => l.widget === widget)) {78throw new Error('Cannot register the same widget multiple times');79}8081// Keep in our lists list82const registeredList: IRegisteredList = { widget, extraContextKeys };83this.lists.push(registeredList);8485// Check for currently being focused86if (isActiveElement(widget.getHTMLElement())) {87this.setLastFocusedList(widget);88}8990return combinedDisposable(91widget.onDidFocus(() => this.setLastFocusedList(widget)),92toDisposable(() => this.lists.splice(this.lists.indexOf(registeredList), 1)),93widget.onDidDispose(() => {94this.lists = this.lists.filter(l => l !== registeredList);95if (this._lastFocusedWidget === widget) {96this.setLastFocusedList(undefined);97}98})99);100}101102dispose(): void {103this.disposables.dispose();104}105}106107export const RawWorkbenchListScrollAtBoundaryContextKey = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('listScrollAtBoundary', 'none');108export const WorkbenchListScrollAtTopContextKey = ContextKeyExpr.or(109RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('top'),110RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));111export const WorkbenchListScrollAtBottomContextKey = ContextKeyExpr.or(112RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('bottom'),113RawWorkbenchListScrollAtBoundaryContextKey.isEqualTo('both'));114115export const RawWorkbenchListFocusContextKey = new RawContextKey<boolean>('listFocus', true);116export const WorkbenchTreeStickyScrollFocused = new RawContextKey<boolean>('treestickyScrollFocused', false);117export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey<boolean>('listSupportsMultiselect', true);118export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey), WorkbenchTreeStickyScrollFocused.negate());119export const WorkbenchListHasSelectionOrFocus = new RawContextKey<boolean>('listHasSelectionOrFocus', false);120export const WorkbenchListDoubleSelection = new RawContextKey<boolean>('listDoubleSelection', false);121export const WorkbenchListMultiSelection = new RawContextKey<boolean>('listMultiSelection', false);122export const WorkbenchListSelectionNavigation = new RawContextKey<boolean>('listSelectionNavigation', false);123export const WorkbenchListSupportsFind = new RawContextKey<boolean>('listSupportsFind', true);124export const WorkbenchTreeElementCanCollapse = new RawContextKey<boolean>('treeElementCanCollapse', false);125export const WorkbenchTreeElementHasParent = new RawContextKey<boolean>('treeElementHasParent', false);126export const WorkbenchTreeElementCanExpand = new RawContextKey<boolean>('treeElementCanExpand', false);127export const WorkbenchTreeElementHasChild = new RawContextKey<boolean>('treeElementHasChild', false);128export const WorkbenchTreeFindOpen = new RawContextKey<boolean>('treeFindOpen', false);129const WorkbenchListTypeNavigationModeKey = 'listTypeNavigationMode';130131/**132* @deprecated in favor of WorkbenchListTypeNavigationModeKey133*/134const WorkbenchListAutomaticKeyboardNavigationLegacyKey = 'listAutomaticKeyboardNavigation';135136function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IScopedContextKeyService {137const result = contextKeyService.createScoped(widget.getHTMLElement());138RawWorkbenchListFocusContextKey.bindTo(result);139return result;140}141142// Note: We must declare IScrollObservarable as the arithmetic of concrete classes,143// instead of object type like { onDidScroll: Event<any>; ... }. The latter will not mark144// those properties as referenced during tree-shaking, causing them to be shaked away.145type IScrollObservarable = Exclude<WorkbenchListWidget, WorkbenchPagedList<any>> | List<any>;146147function createScrollObserver(contextKeyService: IContextKeyService, widget: IScrollObservarable): IDisposable {148const listScrollAt = RawWorkbenchListScrollAtBoundaryContextKey.bindTo(contextKeyService);149const update = () => {150const atTop = widget.scrollTop === 0;151152// We need a threshold `1` since scrollHeight is rounded.153// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#determine_if_an_element_has_been_totally_scrolled154const atBottom = widget.scrollHeight - widget.renderHeight - widget.scrollTop < 1;155if (atTop && atBottom) {156listScrollAt.set('both');157} else if (atTop) {158listScrollAt.set('top');159} else if (atBottom) {160listScrollAt.set('bottom');161} else {162listScrollAt.set('none');163}164};165update();166return widget.onDidScroll(update);167}168169const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';170const openModeSettingKey = 'workbench.list.openMode';171const horizontalScrollingKey = 'workbench.list.horizontalScrolling';172const defaultFindModeSettingKey = 'workbench.list.defaultFindMode';173const typeNavigationModeSettingKey = 'workbench.list.typeNavigationMode';174/** @deprecated in favor of `workbench.list.defaultFindMode` and `workbench.list.typeNavigationMode` */175const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation';176const scrollByPageKey = 'workbench.list.scrollByPage';177const defaultFindMatchTypeSettingKey = 'workbench.list.defaultFindMatchType';178const treeIndentKey = 'workbench.tree.indent';179const treeRenderIndentGuidesKey = 'workbench.tree.renderIndentGuides';180const listSmoothScrolling = 'workbench.list.smoothScrolling';181const mouseWheelScrollSensitivityKey = 'workbench.list.mouseWheelScrollSensitivity';182const fastScrollSensitivityKey = 'workbench.list.fastScrollSensitivity';183const treeExpandMode = 'workbench.tree.expandMode';184const treeStickyScroll = 'workbench.tree.enableStickyScroll';185const treeStickyScrollMaxElements = 'workbench.tree.stickyScrollMaxItemCount';186187function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {188return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';189}190191class MultipleSelectionController<T> extends Disposable implements IMultipleSelectionController<T> {192private useAltAsMultipleSelectionModifier: boolean;193194constructor(private configurationService: IConfigurationService) {195super();196197this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);198199this.registerListeners();200}201202private registerListeners(): void {203this._register(this.configurationService.onDidChangeConfiguration(e => {204if (e.affectsConfiguration(multiSelectModifierSettingKey)) {205this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);206}207}));208}209210isSelectionSingleChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {211if (this.useAltAsMultipleSelectionModifier) {212return event.browserEvent.altKey;213}214215return isSelectionSingleChangeEvent(event);216}217218isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean {219return isSelectionRangeChangeEvent(event);220}221}222223function toWorkbenchListOptions<T>(224accessor: ServicesAccessor,225options: IListOptions<T>,226): [IListOptions<T>, IDisposable] {227const configurationService = accessor.get(IConfigurationService);228const keybindingService = accessor.get(IKeybindingService);229230const disposables = new DisposableStore();231const result: IListOptions<T> = {232...options,233keyboardNavigationDelegate: { mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); } },234smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),235mouseWheelScrollSensitivity: configurationService.getValue<number>(mouseWheelScrollSensitivityKey),236fastScrollSensitivity: configurationService.getValue<number>(fastScrollSensitivityKey),237multipleSelectionController: options.multipleSelectionController ?? disposables.add(new MultipleSelectionController(configurationService)),238keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(keybindingService),239scrollByPage: Boolean(configurationService.getValue(scrollByPageKey))240};241242return [result, disposables];243}244245export interface IWorkbenchListOptionsUpdate extends IListOptionsUpdate {246readonly overrideStyles?: IStyleOverride<IListStyles>;247}248249export interface IWorkbenchListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IListOptions<T> {250readonly selectionNavigation?: boolean;251}252253export class WorkbenchList<T> extends List<T> {254255readonly contextKeyService: IScopedContextKeyService;256private listSupportsMultiSelect: IContextKey<boolean>;257private listHasSelectionOrFocus: IContextKey<boolean>;258private listDoubleSelection: IContextKey<boolean>;259private listMultiSelection: IContextKey<boolean>;260private horizontalScrolling: boolean | undefined;261private _useAltAsMultipleSelectionModifier: boolean;262private navigator: ListResourceNavigator<T>;263get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }264265constructor(266user: string,267container: HTMLElement,268delegate: IListVirtualDelegate<T>,269renderers: IListRenderer<T, any>[],270options: IWorkbenchListOptions<T>,271@IContextKeyService contextKeyService: IContextKeyService,272@IListService listService: IListService,273@IConfigurationService configurationService: IConfigurationService,274@IInstantiationService instantiationService: IInstantiationService275) {276const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));277const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);278279super(user, container, delegate, renderers,280{281keyboardSupport: false,282...workbenchListOptions,283horizontalScrolling,284}285);286287this.disposables.add(workbenchListOptionsDisposable);288289this.contextKeyService = createScopedContextKeyService(contextKeyService, this);290291this.disposables.add(createScrollObserver(this.contextKeyService, this));292293this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);294this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);295296const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);297listSelectionNavigation.set(Boolean(options.selectionNavigation));298299this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);300this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);301this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);302this.horizontalScrolling = options.horizontalScrolling;303304this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);305306this.disposables.add(this.contextKeyService);307this.disposables.add((listService as ListService).register(this));308309this.updateStyles(options.overrideStyles);310311this.disposables.add(this.onDidChangeSelection(() => {312const selection = this.getSelection();313const focus = this.getFocus();314315this.contextKeyService.bufferChangeEvents(() => {316this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);317this.listMultiSelection.set(selection.length > 1);318this.listDoubleSelection.set(selection.length === 2);319});320}));321this.disposables.add(this.onDidChangeFocus(() => {322const selection = this.getSelection();323const focus = this.getFocus();324325this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);326}));327this.disposables.add(configurationService.onDidChangeConfiguration(e => {328if (e.affectsConfiguration(multiSelectModifierSettingKey)) {329this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);330}331332let options: IListOptionsUpdate = {};333334if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {335const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));336options = { ...options, horizontalScrolling };337}338if (e.affectsConfiguration(scrollByPageKey)) {339const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));340options = { ...options, scrollByPage };341}342if (e.affectsConfiguration(listSmoothScrolling)) {343const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));344options = { ...options, smoothScrolling };345}346if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {347const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);348options = { ...options, mouseWheelScrollSensitivity };349}350if (e.affectsConfiguration(fastScrollSensitivityKey)) {351const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);352options = { ...options, fastScrollSensitivity };353}354if (Object.keys(options).length > 0) {355this.updateOptions(options);356}357}));358359this.navigator = new ListResourceNavigator(this, { configurationService, ...options });360this.disposables.add(this.navigator);361}362363override updateOptions(options: IWorkbenchListOptionsUpdate): void {364super.updateOptions(options);365366if (options.overrideStyles !== undefined) {367this.updateStyles(options.overrideStyles);368}369370if (options.multipleSelectionSupport !== undefined) {371this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);372}373}374375private updateStyles(styles: IStyleOverride<IListStyles> | undefined): void {376this.style(styles ? getListStyles(styles) : defaultListStyles);377}378379get useAltAsMultipleSelectionModifier(): boolean {380return this._useAltAsMultipleSelectionModifier;381}382}383384export interface IWorkbenchPagedListOptions<T> extends IWorkbenchListOptionsUpdate, IResourceNavigatorOptions, IPagedListOptions<T> {385readonly selectionNavigation?: boolean;386}387388export class WorkbenchPagedList<T> extends PagedList<T> {389390readonly contextKeyService: IScopedContextKeyService;391private readonly disposables: DisposableStore;392private listSupportsMultiSelect: IContextKey<boolean>;393private _useAltAsMultipleSelectionModifier: boolean;394private horizontalScrolling: boolean | undefined;395private navigator: ListResourceNavigator<T>;396get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }397398constructor(399user: string,400container: HTMLElement,401delegate: IListVirtualDelegate<number>,402renderers: IPagedRenderer<T, any>[],403options: IWorkbenchPagedListOptions<T>,404@IContextKeyService contextKeyService: IContextKeyService,405@IListService listService: IListService,406@IConfigurationService configurationService: IConfigurationService,407@IInstantiationService instantiationService: IInstantiationService408) {409const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));410const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);411super(user, container, delegate, renderers,412{413keyboardSupport: false,414...workbenchListOptions,415horizontalScrolling,416}417);418419this.disposables = new DisposableStore();420this.disposables.add(workbenchListOptionsDisposable);421422this.contextKeyService = createScopedContextKeyService(contextKeyService, this);423424this.disposables.add(createScrollObserver(this.contextKeyService, this.widget));425426this.horizontalScrolling = options.horizontalScrolling;427428this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);429this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);430431const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);432listSelectionNavigation.set(Boolean(options.selectionNavigation));433434this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);435436this.disposables.add(this.contextKeyService);437this.disposables.add((listService as ListService).register(this));438439this.updateStyles(options.overrideStyles);440441this.disposables.add(configurationService.onDidChangeConfiguration(e => {442if (e.affectsConfiguration(multiSelectModifierSettingKey)) {443this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);444}445446let options: IListOptionsUpdate = {};447448if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {449const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));450options = { ...options, horizontalScrolling };451}452if (e.affectsConfiguration(scrollByPageKey)) {453const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));454options = { ...options, scrollByPage };455}456if (e.affectsConfiguration(listSmoothScrolling)) {457const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));458options = { ...options, smoothScrolling };459}460if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {461const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);462options = { ...options, mouseWheelScrollSensitivity };463}464if (e.affectsConfiguration(fastScrollSensitivityKey)) {465const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);466options = { ...options, fastScrollSensitivity };467}468if (Object.keys(options).length > 0) {469this.updateOptions(options);470}471}));472473this.navigator = new ListResourceNavigator(this, { configurationService, ...options });474this.disposables.add(this.navigator);475}476477override updateOptions(options: IWorkbenchListOptionsUpdate): void {478super.updateOptions(options);479480if (options.overrideStyles !== undefined) {481this.updateStyles(options.overrideStyles);482}483484if (options.multipleSelectionSupport !== undefined) {485this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);486}487}488489private updateStyles(styles: IStyleOverride<IListStyles> | undefined): void {490this.style(styles ? getListStyles(styles) : defaultListStyles);491}492493get useAltAsMultipleSelectionModifier(): boolean {494return this._useAltAsMultipleSelectionModifier;495}496497override dispose(): void {498this.disposables.dispose();499super.dispose();500}501}502503export interface IWorkbenchTableOptionsUpdate extends ITableOptionsUpdate {504readonly overrideStyles?: IStyleOverride<IListStyles>;505}506507export interface IWorkbenchTableOptions<T> extends IWorkbenchTableOptionsUpdate, IResourceNavigatorOptions, ITableOptions<T> {508readonly selectionNavigation?: boolean;509}510511export class WorkbenchTable<TRow> extends Table<TRow> {512513readonly contextKeyService: IScopedContextKeyService;514private listSupportsMultiSelect: IContextKey<boolean>;515private listHasSelectionOrFocus: IContextKey<boolean>;516private listDoubleSelection: IContextKey<boolean>;517private listMultiSelection: IContextKey<boolean>;518private horizontalScrolling: boolean | undefined;519private _useAltAsMultipleSelectionModifier: boolean;520private navigator: TableResourceNavigator<TRow>;521get onDidOpen(): Event<IOpenEvent<TRow | undefined>> { return this.navigator.onDidOpen; }522523constructor(524user: string,525container: HTMLElement,526delegate: ITableVirtualDelegate<TRow>,527columns: ITableColumn<TRow, any>[],528renderers: ITableRenderer<TRow, any>[],529options: IWorkbenchTableOptions<TRow>,530@IContextKeyService contextKeyService: IContextKeyService,531@IListService listService: IListService,532@IConfigurationService configurationService: IConfigurationService,533@IInstantiationService instantiationService: IInstantiationService534) {535const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));536const [workbenchListOptions, workbenchListOptionsDisposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);537538super(user, container, delegate, columns, renderers,539{540keyboardSupport: false,541...workbenchListOptions,542horizontalScrolling,543}544);545546this.disposables.add(workbenchListOptionsDisposable);547548this.contextKeyService = createScopedContextKeyService(contextKeyService, this);549550this.disposables.add(createScrollObserver(this.contextKeyService, this));551552this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);553this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);554555const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);556listSelectionNavigation.set(Boolean(options.selectionNavigation));557558this.listHasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);559this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);560this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);561this.horizontalScrolling = options.horizontalScrolling;562563this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);564565this.disposables.add(this.contextKeyService);566this.disposables.add((listService as ListService).register(this));567568this.updateStyles(options.overrideStyles);569570this.disposables.add(this.onDidChangeSelection(() => {571const selection = this.getSelection();572const focus = this.getFocus();573574this.contextKeyService.bufferChangeEvents(() => {575this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);576this.listMultiSelection.set(selection.length > 1);577this.listDoubleSelection.set(selection.length === 2);578});579}));580this.disposables.add(this.onDidChangeFocus(() => {581const selection = this.getSelection();582const focus = this.getFocus();583584this.listHasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);585}));586this.disposables.add(configurationService.onDidChangeConfiguration(e => {587if (e.affectsConfiguration(multiSelectModifierSettingKey)) {588this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);589}590591let options: IListOptionsUpdate = {};592593if (e.affectsConfiguration(horizontalScrollingKey) && this.horizontalScrolling === undefined) {594const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));595options = { ...options, horizontalScrolling };596}597if (e.affectsConfiguration(scrollByPageKey)) {598const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));599options = { ...options, scrollByPage };600}601if (e.affectsConfiguration(listSmoothScrolling)) {602const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));603options = { ...options, smoothScrolling };604}605if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {606const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);607options = { ...options, mouseWheelScrollSensitivity };608}609if (e.affectsConfiguration(fastScrollSensitivityKey)) {610const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);611options = { ...options, fastScrollSensitivity };612}613if (Object.keys(options).length > 0) {614this.updateOptions(options);615}616}));617618this.navigator = new TableResourceNavigator(this, { configurationService, ...options });619this.disposables.add(this.navigator);620}621622override updateOptions(options: IWorkbenchTableOptionsUpdate): void {623super.updateOptions(options);624625if (options.overrideStyles !== undefined) {626this.updateStyles(options.overrideStyles);627}628629if (options.multipleSelectionSupport !== undefined) {630this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);631}632}633634private updateStyles(styles: IStyleOverride<ITableStyles> | undefined): void {635this.style(styles ? getListStyles(styles) : defaultListStyles);636}637638get useAltAsMultipleSelectionModifier(): boolean {639return this._useAltAsMultipleSelectionModifier;640}641642override dispose(): void {643this.disposables.dispose();644super.dispose();645}646}647648export interface IOpenEvent<T> {649editorOptions: IEditorOptions;650sideBySide: boolean;651element: T;652browserEvent?: UIEvent;653}654655export interface IResourceNavigatorOptions {656readonly configurationService?: IConfigurationService;657readonly openOnSingleClick?: boolean;658}659660export interface SelectionKeyboardEvent extends KeyboardEvent {661preserveFocus?: boolean;662pinned?: boolean;663__forceEvent?: boolean;664}665666export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean, pinned?: boolean): SelectionKeyboardEvent {667const e = new KeyboardEvent(typeArg);668(<SelectionKeyboardEvent>e).preserveFocus = preserveFocus;669(<SelectionKeyboardEvent>e).pinned = pinned;670(<SelectionKeyboardEvent>e).__forceEvent = true;671672return e;673}674675abstract class ResourceNavigator<T> extends Disposable {676677private openOnSingleClick: boolean;678679private readonly _onDidOpen = this._register(new Emitter<IOpenEvent<T | undefined>>());680readonly onDidOpen: Event<IOpenEvent<T | undefined>> = this._onDidOpen.event;681682constructor(683protected readonly widget: ListWidget,684options?: IResourceNavigatorOptions685) {686super();687688this._register(Event.filter(this.widget.onDidChangeSelection, e => isKeyboardEvent(e.browserEvent))(e => this.onSelectionFromKeyboard(e)));689this._register(this.widget.onPointer((e: { browserEvent: MouseEvent; element: T | undefined }) => this.onPointer(e.element, e.browserEvent)));690this._register(this.widget.onMouseDblClick((e: { browserEvent: MouseEvent; element: T | undefined }) => this.onMouseDblClick(e.element, e.browserEvent)));691692if (typeof options?.openOnSingleClick !== 'boolean' && options?.configurationService) {693this.openOnSingleClick = options?.configurationService.getValue(openModeSettingKey) !== 'doubleClick';694this._register(options?.configurationService.onDidChangeConfiguration(e => {695if (e.affectsConfiguration(openModeSettingKey)) {696this.openOnSingleClick = options?.configurationService!.getValue(openModeSettingKey) !== 'doubleClick';697}698}));699} else {700this.openOnSingleClick = options?.openOnSingleClick ?? true;701}702}703704private onSelectionFromKeyboard(event: ITreeEvent<any>): void {705if (event.elements.length !== 1) {706return;707}708709const selectionKeyboardEvent = event.browserEvent as SelectionKeyboardEvent;710const preserveFocus = typeof selectionKeyboardEvent.preserveFocus === 'boolean' ? selectionKeyboardEvent.preserveFocus : true;711const pinned = typeof selectionKeyboardEvent.pinned === 'boolean' ? selectionKeyboardEvent.pinned : !preserveFocus;712const sideBySide = false;713714this._open(this.getSelectedElement(), preserveFocus, pinned, sideBySide, event.browserEvent);715}716717private onPointer(element: T | undefined, browserEvent: MouseEvent): void {718if (!this.openOnSingleClick) {719return;720}721722const isDoubleClick = browserEvent.detail === 2;723724if (isDoubleClick) {725return;726}727728const isMiddleClick = browserEvent.button === 1;729const preserveFocus = true;730const pinned = isMiddleClick;731const sideBySide = browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey;732733this._open(element, preserveFocus, pinned, sideBySide, browserEvent);734}735736private onMouseDblClick(element: T | undefined, browserEvent?: MouseEvent): void {737if (!browserEvent) {738return;739}740741// copied from AbstractTree742const target = browserEvent.target as HTMLElement;743const onTwistie = target.classList.contains('monaco-tl-twistie')744|| (target.classList.contains('monaco-icon-label') && target.classList.contains('folder-icon') && browserEvent.offsetX < 16);745746if (onTwistie) {747return;748}749750const preserveFocus = false;751const pinned = true;752const sideBySide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);753754this._open(element, preserveFocus, pinned, sideBySide, browserEvent);755}756757private _open(element: T | undefined, preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void {758if (!element) {759return;760}761762this._onDidOpen.fire({763editorOptions: {764preserveFocus,765pinned,766revealIfVisible: true767},768sideBySide,769element,770browserEvent771});772}773774abstract getSelectedElement(): T | undefined;775}776777class ListResourceNavigator<T> extends ResourceNavigator<T> {778779protected override readonly widget: List<T> | PagedList<T>;780781constructor(782widget: List<T> | PagedList<T>,783options: IResourceNavigatorOptions784) {785super(widget, options);786this.widget = widget;787}788789getSelectedElement(): T | undefined {790return this.widget.getSelectedElements()[0];791}792}793794class TableResourceNavigator<TRow> extends ResourceNavigator<TRow> {795796protected declare readonly widget: Table<TRow>;797798constructor(799widget: Table<TRow>,800options: IResourceNavigatorOptions801) {802super(widget, options);803}804805getSelectedElement(): TRow | undefined {806return this.widget.getSelectedElements()[0];807}808}809810class TreeResourceNavigator<T, TFilterData> extends ResourceNavigator<T> {811812protected declare readonly widget: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>;813814constructor(815widget: ObjectTree<T, TFilterData> | CompressibleObjectTree<T, TFilterData> | DataTree<any, T, TFilterData> | AsyncDataTree<any, T, TFilterData> | CompressibleAsyncDataTree<any, T, TFilterData>,816options: IResourceNavigatorOptions817) {818super(widget, options);819}820821getSelectedElement(): T | undefined {822return this.widget.getSelection()[0] ?? undefined;823}824}825826function createKeyboardNavigationEventFilter(keybindingService: IKeybindingService): IKeyboardNavigationEventFilter {827let inMultiChord = false;828829return event => {830if (event.toKeyCodeChord().isModifierKey()) {831return false;832}833834if (inMultiChord) {835inMultiChord = false;836return false;837}838839const result = keybindingService.softDispatch(event, event.target);840841if (result.kind === ResultKind.MoreChordsNeeded) {842inMultiChord = true;843return false;844}845846inMultiChord = false;847return result.kind === ResultKind.NoMatchingKb;848};849}850851export interface IWorkbenchObjectTreeOptions<T, TFilterData> extends IObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {852readonly accessibilityProvider: IListAccessibilityProvider<T>;853readonly overrideStyles?: IStyleOverride<IListStyles>;854readonly selectionNavigation?: boolean;855readonly scrollToActiveElement?: boolean;856}857858export class WorkbenchObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> {859860private internals: WorkbenchTreeInternals<any, T, TFilterData>;861get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }862get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }863get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }864865constructor(866user: string,867container: HTMLElement,868delegate: IListVirtualDelegate<T>,869renderers: ITreeRenderer<T, TFilterData, any>[],870options: IWorkbenchObjectTreeOptions<T, TFilterData>,871@IInstantiationService instantiationService: IInstantiationService,872@IContextKeyService contextKeyService: IContextKeyService,873@IListService listService: IListService,874@IConfigurationService configurationService: IConfigurationService875) {876const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);877super(user, container, delegate, renderers, treeOptions);878this.disposables.add(disposable);879this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);880this.disposables.add(this.internals);881}882883override updateOptions(options: IAbstractTreeOptionsUpdate): void {884super.updateOptions(options);885this.internals.updateOptions(options);886}887}888889export interface IWorkbenchCompressibleObjectTreeOptionsUpdate extends ICompressibleObjectTreeOptionsUpdate {890readonly overrideStyles?: IStyleOverride<IListStyles>;891}892893export interface IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> extends IWorkbenchCompressibleObjectTreeOptionsUpdate, ICompressibleObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {894readonly accessibilityProvider: IListAccessibilityProvider<T>;895readonly selectionNavigation?: boolean;896}897898export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends CompressibleObjectTree<T, TFilterData> {899900private internals: WorkbenchTreeInternals<any, T, TFilterData>;901get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }902get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }903get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }904905constructor(906user: string,907container: HTMLElement,908delegate: IListVirtualDelegate<T>,909renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],910options: IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>,911@IInstantiationService instantiationService: IInstantiationService,912@IContextKeyService contextKeyService: IContextKeyService,913@IListService listService: IListService,914@IConfigurationService configurationService: IConfigurationService915) {916const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);917super(user, container, delegate, renderers, treeOptions);918this.disposables.add(disposable);919this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);920this.disposables.add(this.internals);921}922923override updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate = {}): void {924super.updateOptions(options);925926if (options.overrideStyles) {927this.internals.updateStyleOverrides(options.overrideStyles);928}929930this.internals.updateOptions(options);931}932}933934export interface IWorkbenchDataTreeOptionsUpdate extends IAbstractTreeOptionsUpdate {935readonly overrideStyles?: IStyleOverride<IListStyles>;936}937938export interface IWorkbenchDataTreeOptions<T, TFilterData> extends IWorkbenchDataTreeOptionsUpdate, IDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {939readonly accessibilityProvider: IListAccessibilityProvider<T>;940readonly selectionNavigation?: boolean;941}942943export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<TInput, T, TFilterData> {944945private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;946get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }947get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }948get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }949950constructor(951user: string,952container: HTMLElement,953delegate: IListVirtualDelegate<T>,954renderers: ITreeRenderer<T, TFilterData, any>[],955dataSource: IDataSource<TInput, T>,956options: IWorkbenchDataTreeOptions<T, TFilterData>,957@IInstantiationService instantiationService: IInstantiationService,958@IContextKeyService contextKeyService: IContextKeyService,959@IListService listService: IListService,960@IConfigurationService configurationService: IConfigurationService961) {962const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);963super(user, container, delegate, renderers, dataSource, treeOptions);964this.disposables.add(disposable);965this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);966this.disposables.add(this.internals);967}968969override updateOptions(options: IWorkbenchDataTreeOptionsUpdate = {}): void {970super.updateOptions(options);971972if (options.overrideStyles !== undefined) {973this.internals.updateStyleOverrides(options.overrideStyles);974}975976this.internals.updateOptions(options);977}978}979980export interface IWorkbenchAsyncDataTreeOptionsUpdate extends IAsyncDataTreeOptionsUpdate {981readonly overrideStyles?: IStyleOverride<IListStyles>;982}983984export interface IWorkbenchAsyncDataTreeOptions<T, TFilterData> extends IWorkbenchAsyncDataTreeOptionsUpdate, IAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {985readonly accessibilityProvider: IListAccessibilityProvider<T>;986readonly selectionNavigation?: boolean;987}988989export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {990991private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;992get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }993get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }994get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }995996constructor(997user: string,998container: HTMLElement,999delegate: IListVirtualDelegate<T>,1000renderers: ITreeRenderer<T, TFilterData, any>[],1001dataSource: IAsyncDataSource<TInput, T>,1002options: IWorkbenchAsyncDataTreeOptions<T, TFilterData>,1003@IInstantiationService instantiationService: IInstantiationService,1004@IContextKeyService contextKeyService: IContextKeyService,1005@IListService listService: IListService,1006@IConfigurationService configurationService: IConfigurationService1007) {1008const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);1009super(user, container, delegate, renderers, dataSource, treeOptions);1010this.disposables.add(disposable);1011this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);1012this.disposables.add(this.internals);1013}10141015override updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate = {}): void {1016super.updateOptions(options);10171018if (options.overrideStyles) {1019this.internals.updateStyleOverrides(options.overrideStyles);1020}10211022this.internals.updateOptions(options);1023}1024}10251026export interface IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData> extends ICompressibleAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {1027readonly accessibilityProvider: IListAccessibilityProvider<T>;1028readonly overrideStyles?: IStyleOverride<IListStyles>;1029readonly selectionNavigation?: boolean;1030}10311032export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {10331034private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;1035get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }1036get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }1037get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }10381039constructor(1040user: string,1041container: HTMLElement,1042virtualDelegate: IListVirtualDelegate<T>,1043compressionDelegate: ITreeCompressionDelegate<T>,1044renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],1045dataSource: IAsyncDataSource<TInput, T>,1046options: IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,1047@IInstantiationService instantiationService: IInstantiationService,1048@IContextKeyService contextKeyService: IContextKeyService,1049@IListService listService: IListService,1050@IConfigurationService configurationService: IConfigurationService1051) {1052const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);1053super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);1054this.disposables.add(disposable);1055this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);1056this.disposables.add(this.internals);1057}10581059override updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate): void {1060super.updateOptions(options);1061this.internals.updateOptions(options);1062}1063}10641065function getDefaultTreeFindMode(configurationService: IConfigurationService) {1066const value = configurationService.getValue<'highlight' | 'filter'>(defaultFindModeSettingKey);10671068if (value === 'highlight') {1069return TreeFindMode.Highlight;1070} else if (value === 'filter') {1071return TreeFindMode.Filter;1072}10731074const deprecatedValue = configurationService.getValue<'simple' | 'highlight' | 'filter'>(keyboardNavigationSettingKey);10751076if (deprecatedValue === 'simple' || deprecatedValue === 'highlight') {1077return TreeFindMode.Highlight;1078} else if (deprecatedValue === 'filter') {1079return TreeFindMode.Filter;1080}10811082return undefined;1083}10841085function getDefaultTreeFindMatchType(configurationService: IConfigurationService) {1086const value = configurationService.getValue<'fuzzy' | 'contiguous'>(defaultFindMatchTypeSettingKey);10871088if (value === 'fuzzy') {1089return TreeFindMatchType.Fuzzy;1090} else if (value === 'contiguous') {1091return TreeFindMatchType.Contiguous;1092}1093return undefined;1094}10951096function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(1097accessor: ServicesAccessor,1098options: TOptions,1099): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } {1100const configurationService = accessor.get(IConfigurationService);1101const contextViewService = accessor.get(IContextViewService);1102const contextKeyService = accessor.get(IContextKeyService);1103const instantiationService = accessor.get(IInstantiationService);11041105const getTypeNavigationMode = () => {1106// give priority to the context key value to specify a value1107const modeString = contextKeyService.getContextKeyValue<'automatic' | 'trigger'>(WorkbenchListTypeNavigationModeKey);11081109if (modeString === 'automatic') {1110return TypeNavigationMode.Automatic;1111} else if (modeString === 'trigger') {1112return TypeNavigationMode.Trigger;1113}11141115// also check the deprecated context key to set the mode to 'trigger'1116const modeBoolean = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationLegacyKey);11171118if (modeBoolean === false) {1119return TypeNavigationMode.Trigger;1120}11211122// finally, check the setting1123const configString = configurationService.getValue<'automatic' | 'trigger'>(typeNavigationModeSettingKey);11241125if (configString === 'automatic') {1126return TypeNavigationMode.Automatic;1127} else if (configString === 'trigger') {1128return TypeNavigationMode.Trigger;1129}11301131return undefined;1132};11331134const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));1135const [workbenchListOptions, disposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);1136const paddingBottom = options.paddingBottom;1137const renderIndentGuides = options.renderIndentGuides !== undefined ? options.renderIndentGuides : configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);11381139return {1140getTypeNavigationMode,1141disposable,1142// eslint-disable-next-line local/code-no-dangerous-type-assertions1143options: {1144// ...options, // TODO@Joao why is this not splatted here?1145keyboardSupport: false,1146...workbenchListOptions,1147indent: typeof configurationService.getValue(treeIndentKey) === 'number' ? configurationService.getValue(treeIndentKey) : undefined,1148renderIndentGuides,1149smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),1150defaultFindMode: getDefaultTreeFindMode(configurationService),1151defaultFindMatchType: getDefaultTreeFindMatchType(configurationService),1152horizontalScrolling,1153scrollByPage: Boolean(configurationService.getValue(scrollByPageKey)),1154paddingBottom: paddingBottom,1155hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,1156expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),1157contextViewProvider: contextViewService as IContextViewProvider,1158findWidgetStyles: defaultFindWidgetStyles,1159enableStickyScroll: Boolean(configurationService.getValue(treeStickyScroll)),1160stickyScrollMaxItemCount: Number(configurationService.getValue(treeStickyScrollMaxElements)),1161} as TOptions1162};1163}11641165interface IWorkbenchTreeInternalsOptionsUpdate {1166readonly multipleSelectionSupport?: boolean;1167}11681169class WorkbenchTreeInternals<TInput, T, TFilterData> {11701171readonly contextKeyService: IScopedContextKeyService;1172private listSupportsMultiSelect: IContextKey<boolean>;1173private listSupportFindWidget: IContextKey<boolean>;1174private hasSelectionOrFocus: IContextKey<boolean>;1175private hasDoubleSelection: IContextKey<boolean>;1176private hasMultiSelection: IContextKey<boolean>;1177private treeElementCanCollapse: IContextKey<boolean>;1178private treeElementHasParent: IContextKey<boolean>;1179private treeElementCanExpand: IContextKey<boolean>;1180private treeElementHasChild: IContextKey<boolean>;1181private treeFindOpen: IContextKey<boolean>;1182private treeStickyScrollFocused: IContextKey<boolean>;1183private _useAltAsMultipleSelectionModifier: boolean;1184private disposables: IDisposable[] = [];11851186private navigator: TreeResourceNavigator<T, TFilterData>;11871188get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }11891190constructor(1191private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,1192options: IWorkbenchObjectTreeOptions<T, TFilterData> | IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> | IWorkbenchDataTreeOptions<T, TFilterData> | IWorkbenchAsyncDataTreeOptions<T, TFilterData> | IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,1193getTypeNavigationMode: () => TypeNavigationMode | undefined,1194overrideStyles: IStyleOverride<IListStyles> | undefined,1195@IContextKeyService contextKeyService: IContextKeyService,1196@IListService listService: IListService,1197@IConfigurationService configurationService: IConfigurationService1198) {1199this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);12001201this.disposables.push(createScrollObserver(this.contextKeyService, tree));12021203this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);1204this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);12051206const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);1207listSelectionNavigation.set(Boolean(options.selectionNavigation));12081209this.listSupportFindWidget = WorkbenchListSupportsFind.bindTo(this.contextKeyService);1210this.listSupportFindWidget.set(options.findWidgetEnabled ?? true);12111212this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);1213this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);1214this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);12151216this.treeElementCanCollapse = WorkbenchTreeElementCanCollapse.bindTo(this.contextKeyService);1217this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService);1218this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService);1219this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService);12201221this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService);1222this.treeStickyScrollFocused = WorkbenchTreeStickyScrollFocused.bindTo(this.contextKeyService);12231224this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);12251226this.updateStyleOverrides(overrideStyles);12271228const updateCollapseContextKeys = () => {1229const focus = tree.getFocus()[0];12301231if (!focus) {1232return;1233}12341235const node = tree.getNode(focus);1236this.treeElementCanCollapse.set(node.collapsible && !node.collapsed);1237this.treeElementHasParent.set(!!tree.getParentElement(focus));1238this.treeElementCanExpand.set(node.collapsible && node.collapsed);1239this.treeElementHasChild.set(!!tree.getFirstElementChild(focus));1240};12411242const interestingContextKeys = new Set();1243interestingContextKeys.add(WorkbenchListTypeNavigationModeKey);1244interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationLegacyKey);12451246this.disposables.push(1247this.contextKeyService,1248(listService as ListService).register(tree),1249tree.onDidChangeSelection(() => {1250const selection = tree.getSelection();1251const focus = tree.getFocus();12521253this.contextKeyService.bufferChangeEvents(() => {1254this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);1255this.hasMultiSelection.set(selection.length > 1);1256this.hasDoubleSelection.set(selection.length === 2);1257});1258}),1259tree.onDidChangeFocus(() => {1260const selection = tree.getSelection();1261const focus = tree.getFocus();12621263this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);1264updateCollapseContextKeys();1265}),1266tree.onDidChangeCollapseState(updateCollapseContextKeys),1267tree.onDidChangeModel(updateCollapseContextKeys),1268tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)),1269tree.onDidChangeStickyScrollFocused(focused => this.treeStickyScrollFocused.set(focused)),1270configurationService.onDidChangeConfiguration(e => {1271let newOptions: IAbstractTreeOptionsUpdate = {};1272if (e.affectsConfiguration(multiSelectModifierSettingKey)) {1273this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);1274}1275if (e.affectsConfiguration(treeIndentKey)) {1276const indent = configurationService.getValue<number>(treeIndentKey);1277newOptions = { ...newOptions, indent };1278}1279if (e.affectsConfiguration(treeRenderIndentGuidesKey) && options.renderIndentGuides === undefined) {1280const renderIndentGuides = configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);1281newOptions = { ...newOptions, renderIndentGuides };1282}1283if (e.affectsConfiguration(listSmoothScrolling)) {1284const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));1285newOptions = { ...newOptions, smoothScrolling };1286}1287if (e.affectsConfiguration(defaultFindModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {1288const defaultFindMode = getDefaultTreeFindMode(configurationService);1289newOptions = { ...newOptions, defaultFindMode };1290}1291if (e.affectsConfiguration(typeNavigationModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {1292const typeNavigationMode = getTypeNavigationMode();1293newOptions = { ...newOptions, typeNavigationMode };1294}1295if (e.affectsConfiguration(defaultFindMatchTypeSettingKey)) {1296const defaultFindMatchType = getDefaultTreeFindMatchType(configurationService);1297newOptions = { ...newOptions, defaultFindMatchType };1298}1299if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {1300const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));1301newOptions = { ...newOptions, horizontalScrolling };1302}1303if (e.affectsConfiguration(scrollByPageKey)) {1304const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));1305newOptions = { ...newOptions, scrollByPage };1306}1307if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {1308newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };1309}1310if (e.affectsConfiguration(treeStickyScroll)) {1311const enableStickyScroll = configurationService.getValue<boolean>(treeStickyScroll);1312newOptions = { ...newOptions, enableStickyScroll };1313}1314if (e.affectsConfiguration(treeStickyScrollMaxElements)) {1315const stickyScrollMaxItemCount = Math.max(1, configurationService.getValue<number>(treeStickyScrollMaxElements));1316newOptions = { ...newOptions, stickyScrollMaxItemCount };1317}1318if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {1319const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);1320newOptions = { ...newOptions, mouseWheelScrollSensitivity };1321}1322if (e.affectsConfiguration(fastScrollSensitivityKey)) {1323const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);1324newOptions = { ...newOptions, fastScrollSensitivity };1325}1326if (Object.keys(newOptions).length > 0) {1327tree.updateOptions(newOptions);1328}1329}),1330this.contextKeyService.onDidChangeContext(e => {1331if (e.affectsSome(interestingContextKeys)) {1332tree.updateOptions({ typeNavigationMode: getTypeNavigationMode() });1333}1334})1335);13361337this.navigator = new TreeResourceNavigator(tree, { configurationService, ...options });1338this.disposables.push(this.navigator);1339}13401341get useAltAsMultipleSelectionModifier(): boolean {1342return this._useAltAsMultipleSelectionModifier;1343}13441345updateOptions(options: IWorkbenchTreeInternalsOptionsUpdate): void {1346if (options.multipleSelectionSupport !== undefined) {1347this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);1348}1349}13501351updateStyleOverrides(overrideStyles?: IStyleOverride<IListStyles>): void {1352this.tree.style(overrideStyles ? getListStyles(overrideStyles) : defaultListStyles);1353}13541355dispose(): void {1356this.disposables = dispose(this.disposables);1357}1358}13591360const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);13611362configurationRegistry.registerConfiguration({1363id: 'workbench',1364order: 7,1365title: localize('workbenchConfigurationTitle', "Workbench"),1366type: 'object',1367properties: {1368[multiSelectModifierSettingKey]: {1369type: 'string',1370enum: ['ctrlCmd', 'alt'],1371markdownEnumDescriptions: [1372localize('multiSelectModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."),1373localize('multiSelectModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.")1374],1375default: 'ctrlCmd',1376description: localize({1377key: 'multiSelectModifier',1378comment: [1379'- `ctrlCmd` refers to a value the setting can take and should not be localized.',1380'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'1381]1382}, "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.")1383},1384[openModeSettingKey]: {1385type: 'string',1386enum: ['singleClick', 'doubleClick'],1387default: 'singleClick',1388description: localize({1389key: 'openModeModifier',1390comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']1391}, "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.")1392},1393[horizontalScrollingKey]: {1394type: 'boolean',1395default: false,1396description: localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")1397},1398[scrollByPageKey]: {1399type: 'boolean',1400default: false,1401description: localize('list.scrollByPage', "Controls whether clicks in the scrollbar scroll page by page.")1402},1403[treeIndentKey]: {1404type: 'number',1405default: 8,1406minimum: 4,1407maximum: 40,1408description: localize('tree indent setting', "Controls tree indentation in pixels.")1409},1410[treeRenderIndentGuidesKey]: {1411type: 'string',1412enum: ['none', 'onHover', 'always'],1413default: 'onHover',1414description: localize('render tree indent guides', "Controls whether the tree should render indent guides.")1415},1416[listSmoothScrolling]: {1417type: 'boolean',1418default: false,1419description: localize('list smoothScrolling setting', "Controls whether lists and trees have smooth scrolling."),1420},1421[mouseWheelScrollSensitivityKey]: {1422type: 'number',1423default: 1,1424markdownDescription: localize('Mouse Wheel Scroll Sensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.")1425},1426[fastScrollSensitivityKey]: {1427type: 'number',1428default: 5,1429markdownDescription: localize('Fast Scroll Sensitivity', "Scrolling speed multiplier when pressing `Alt`.")1430},1431[defaultFindModeSettingKey]: {1432type: 'string',1433enum: ['highlight', 'filter'],1434enumDescriptions: [1435localize('defaultFindModeSettingKey.highlight', "Highlight elements when searching. Further up and down navigation will traverse only the highlighted elements."),1436localize('defaultFindModeSettingKey.filter', "Filter elements when searching.")1437],1438default: 'highlight',1439description: localize('defaultFindModeSettingKey', "Controls the default find mode for lists and trees in the workbench.")1440},1441[keyboardNavigationSettingKey]: {1442type: 'string',1443enum: ['simple', 'highlight', 'filter'],1444enumDescriptions: [1445localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),1446localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),1447localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")1448],1449default: 'highlight',1450description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter."),1451deprecated: true,1452deprecationMessage: localize('keyboardNavigationSettingKeyDeprecated', "Please use 'workbench.list.defaultFindMode' and 'workbench.list.typeNavigationMode' instead.")1453},1454[defaultFindMatchTypeSettingKey]: {1455type: 'string',1456enum: ['fuzzy', 'contiguous'],1457enumDescriptions: [1458localize('defaultFindMatchTypeSettingKey.fuzzy', "Use fuzzy matching when searching."),1459localize('defaultFindMatchTypeSettingKey.contiguous', "Use contiguous matching when searching.")1460],1461default: 'fuzzy',1462description: localize('defaultFindMatchTypeSettingKey', "Controls the type of matching used when searching lists and trees in the workbench.")1463},1464[treeExpandMode]: {1465type: 'string',1466enum: ['singleClick', 'doubleClick'],1467default: 'singleClick',1468description: 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."),1469},1470[treeStickyScroll]: {1471type: 'boolean',1472default: true,1473description: localize('sticky scroll', "Controls whether sticky scrolling is enabled in trees."),1474},1475[treeStickyScrollMaxElements]: {1476type: 'number',1477minimum: 1,1478default: 7,1479markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when {0} is enabled.", '`#workbench.tree.enableStickyScroll#`'),1480},1481[typeNavigationModeSettingKey]: {1482type: 'string',1483enum: ['automatic', 'trigger'],1484default: 'automatic',1485markdownDescription: 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."),1486}1487}1488});148914901491