Path: blob/main/src/vs/platform/list/browser/listService.ts
5222 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, IAsyncDataTreeNode, 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) {876// eslint-disable-next-line local/code-no-any-casts877const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);878super(user, container, delegate, renderers, treeOptions);879this.disposables.add(disposable);880this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);881this.disposables.add(this.internals);882}883884override updateOptions(options: IAbstractTreeOptionsUpdate<T | null>): void {885super.updateOptions(options);886this.internals.updateOptions(options);887}888}889890export interface IWorkbenchCompressibleObjectTreeOptionsUpdate<T> extends ICompressibleObjectTreeOptionsUpdate<T> {891readonly overrideStyles?: IStyleOverride<IListStyles>;892}893894export interface IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> extends IWorkbenchCompressibleObjectTreeOptionsUpdate<T>, ICompressibleObjectTreeOptions<T, TFilterData>, IResourceNavigatorOptions {895readonly accessibilityProvider: IListAccessibilityProvider<T>;896readonly selectionNavigation?: boolean;897}898899export class WorkbenchCompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends CompressibleObjectTree<T, TFilterData> {900901private internals: WorkbenchTreeInternals<any, T, TFilterData>;902get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }903get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }904get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }905906constructor(907user: string,908container: HTMLElement,909delegate: IListVirtualDelegate<T>,910renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],911options: IWorkbenchCompressibleObjectTreeOptions<T, TFilterData>,912@IInstantiationService instantiationService: IInstantiationService,913@IContextKeyService contextKeyService: IContextKeyService,914@IListService listService: IListService,915@IConfigurationService configurationService: IConfigurationService916) {917// eslint-disable-next-line local/code-no-any-casts918const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);919super(user, container, delegate, renderers, treeOptions);920this.disposables.add(disposable);921this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);922this.disposables.add(this.internals);923}924925override updateOptions(options: IWorkbenchCompressibleObjectTreeOptionsUpdate<T | null> = {}): void {926super.updateOptions(options);927928if (options.overrideStyles) {929this.internals.updateStyleOverrides(options.overrideStyles);930}931932this.internals.updateOptions(options);933}934}935936export interface IWorkbenchDataTreeOptionsUpdate<T> extends IAbstractTreeOptionsUpdate<T> {937readonly overrideStyles?: IStyleOverride<IListStyles>;938}939940export interface IWorkbenchDataTreeOptions<T, TFilterData> extends IWorkbenchDataTreeOptionsUpdate<T>, IDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {941readonly accessibilityProvider: IListAccessibilityProvider<T>;942readonly selectionNavigation?: boolean;943}944945export class WorkbenchDataTree<TInput, T, TFilterData = void> extends DataTree<TInput, T, TFilterData> {946947private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;948get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }949get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }950get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }951952constructor(953user: string,954container: HTMLElement,955delegate: IListVirtualDelegate<T>,956renderers: ITreeRenderer<T, TFilterData, any>[],957dataSource: IDataSource<TInput, T>,958options: IWorkbenchDataTreeOptions<T, TFilterData>,959@IInstantiationService instantiationService: IInstantiationService,960@IContextKeyService contextKeyService: IContextKeyService,961@IListService listService: IListService,962@IConfigurationService configurationService: IConfigurationService963) {964// eslint-disable-next-line local/code-no-any-casts965const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);966super(user, container, delegate, renderers, dataSource, treeOptions);967this.disposables.add(disposable);968this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);969this.disposables.add(this.internals);970}971972override updateOptions(options: IWorkbenchDataTreeOptionsUpdate<T | null> = {}): void {973super.updateOptions(options);974975if (options.overrideStyles !== undefined) {976this.internals.updateStyleOverrides(options.overrideStyles);977}978979this.internals.updateOptions(options);980}981}982983export interface IWorkbenchAsyncDataTreeOptionsUpdate<T> extends IAsyncDataTreeOptionsUpdate<T> {984readonly overrideStyles?: IStyleOverride<IListStyles>;985}986987export interface IWorkbenchAsyncDataTreeOptions<T, TFilterData> extends IWorkbenchAsyncDataTreeOptionsUpdate<T>, IAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {988readonly accessibilityProvider: IListAccessibilityProvider<T>;989readonly selectionNavigation?: boolean;990}991992export class WorkbenchAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> {993994private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;995get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }996get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }997get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }998999constructor(1000user: string,1001container: HTMLElement,1002delegate: IListVirtualDelegate<T>,1003renderers: ITreeRenderer<T, TFilterData, any>[],1004dataSource: IAsyncDataSource<TInput, T>,1005options: IWorkbenchAsyncDataTreeOptions<T, TFilterData>,1006@IInstantiationService instantiationService: IInstantiationService,1007@IContextKeyService contextKeyService: IContextKeyService,1008@IListService listService: IListService,1009@IConfigurationService configurationService: IConfigurationService1010) {1011// eslint-disable-next-line local/code-no-any-casts1012const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);1013super(user, container, delegate, renderers, dataSource, treeOptions);1014this.disposables.add(disposable);1015this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);1016this.disposables.add(this.internals);1017}10181019override updateOptions(options: IWorkbenchAsyncDataTreeOptionsUpdate<IAsyncDataTreeNode<TInput, T> | null> = {}): void {1020super.updateOptions(options);10211022if (options.overrideStyles) {1023this.internals.updateStyleOverrides(options.overrideStyles);1024}10251026this.internals.updateOptions(options);1027}1028}10291030export interface IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData> extends ICompressibleAsyncDataTreeOptions<T, TFilterData>, IResourceNavigatorOptions {1031readonly accessibilityProvider: IListAccessibilityProvider<T>;1032readonly overrideStyles?: IStyleOverride<IListStyles>;1033readonly selectionNavigation?: boolean;1034}10351036export class WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData = void> extends CompressibleAsyncDataTree<TInput, T, TFilterData> {10371038private internals: WorkbenchTreeInternals<TInput, T, TFilterData>;1039get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; }1040get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; }1041get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.internals.onDidOpen; }10421043constructor(1044user: string,1045container: HTMLElement,1046virtualDelegate: IListVirtualDelegate<T>,1047compressionDelegate: ITreeCompressionDelegate<T>,1048renderers: ICompressibleTreeRenderer<T, TFilterData, any>[],1049dataSource: IAsyncDataSource<TInput, T>,1050options: IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,1051@IInstantiationService instantiationService: IInstantiationService,1052@IContextKeyService contextKeyService: IContextKeyService,1053@IListService listService: IListService,1054@IConfigurationService configurationService: IConfigurationService1055) {1056// eslint-disable-next-line local/code-no-any-casts1057const { options: treeOptions, getTypeNavigationMode, disposable } = instantiationService.invokeFunction(workbenchTreeDataPreamble, options as any);1058super(user, container, virtualDelegate, compressionDelegate, renderers, dataSource, treeOptions);1059this.disposables.add(disposable);1060this.internals = new WorkbenchTreeInternals(this, options, getTypeNavigationMode, options.overrideStyles, contextKeyService, listService, configurationService);1061this.disposables.add(this.internals);1062}10631064override updateOptions(options: ICompressibleAsyncDataTreeOptionsUpdate<IAsyncDataTreeNode<TInput, T> | null>): void {1065super.updateOptions(options);1066this.internals.updateOptions(options);1067}1068}10691070function getDefaultTreeFindMode(configurationService: IConfigurationService) {1071const value = configurationService.getValue<'highlight' | 'filter'>(defaultFindModeSettingKey);10721073if (value === 'highlight') {1074return TreeFindMode.Highlight;1075} else if (value === 'filter') {1076return TreeFindMode.Filter;1077}10781079const deprecatedValue = configurationService.getValue<'simple' | 'highlight' | 'filter'>(keyboardNavigationSettingKey);10801081if (deprecatedValue === 'simple' || deprecatedValue === 'highlight') {1082return TreeFindMode.Highlight;1083} else if (deprecatedValue === 'filter') {1084return TreeFindMode.Filter;1085}10861087return undefined;1088}10891090function getDefaultTreeFindMatchType(configurationService: IConfigurationService) {1091const value = configurationService.getValue<'fuzzy' | 'contiguous'>(defaultFindMatchTypeSettingKey);10921093if (value === 'fuzzy') {1094return TreeFindMatchType.Fuzzy;1095} else if (value === 'contiguous') {1096return TreeFindMatchType.Contiguous;1097}1098return undefined;1099}11001101function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTreeOptions<T, TFilterData> | IAsyncDataTreeOptions<T, TFilterData>>(1102accessor: ServicesAccessor,1103options: TOptions,1104): { options: TOptions; getTypeNavigationMode: () => TypeNavigationMode | undefined; disposable: IDisposable } {1105const configurationService = accessor.get(IConfigurationService);1106const contextViewService = accessor.get(IContextViewService);1107const contextKeyService = accessor.get(IContextKeyService);1108const instantiationService = accessor.get(IInstantiationService);11091110const getTypeNavigationMode = () => {1111// give priority to the context key value to specify a value1112const modeString = contextKeyService.getContextKeyValue<'automatic' | 'trigger'>(WorkbenchListTypeNavigationModeKey);11131114if (modeString === 'automatic') {1115return TypeNavigationMode.Automatic;1116} else if (modeString === 'trigger') {1117return TypeNavigationMode.Trigger;1118}11191120// also check the deprecated context key to set the mode to 'trigger'1121const modeBoolean = contextKeyService.getContextKeyValue<boolean>(WorkbenchListAutomaticKeyboardNavigationLegacyKey);11221123if (modeBoolean === false) {1124return TypeNavigationMode.Trigger;1125}11261127// finally, check the setting1128const configString = configurationService.getValue<'automatic' | 'trigger'>(typeNavigationModeSettingKey);11291130if (configString === 'automatic') {1131return TypeNavigationMode.Automatic;1132} else if (configString === 'trigger') {1133return TypeNavigationMode.Trigger;1134}11351136return undefined;1137};11381139const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : Boolean(configurationService.getValue(horizontalScrollingKey));1140const [workbenchListOptions, disposable] = instantiationService.invokeFunction(toWorkbenchListOptions, options);1141const paddingBottom = options.paddingBottom;1142const renderIndentGuides = options.renderIndentGuides !== undefined ? options.renderIndentGuides : configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);11431144return {1145getTypeNavigationMode,1146disposable,1147// eslint-disable-next-line local/code-no-dangerous-type-assertions1148options: {1149// ...options, // TODO@Joao why is this not splatted here?1150keyboardSupport: false,1151...workbenchListOptions,1152indent: typeof configurationService.getValue(treeIndentKey) === 'number' ? configurationService.getValue(treeIndentKey) : undefined,1153renderIndentGuides,1154smoothScrolling: Boolean(configurationService.getValue(listSmoothScrolling)),1155defaultFindMode: options.defaultFindMode ?? getDefaultTreeFindMode(configurationService),1156defaultFindMatchType: options.defaultFindMatchType ?? getDefaultTreeFindMatchType(configurationService),1157horizontalScrolling,1158scrollByPage: Boolean(configurationService.getValue(scrollByPageKey)),1159paddingBottom: paddingBottom,1160hideTwistiesOfChildlessElements: options.hideTwistiesOfChildlessElements,1161expandOnlyOnTwistieClick: options.expandOnlyOnTwistieClick ?? (configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick'),1162contextViewProvider: contextViewService as IContextViewProvider,1163findWidgetStyles: defaultFindWidgetStyles,1164enableStickyScroll: Boolean(configurationService.getValue(treeStickyScroll)),1165stickyScrollMaxItemCount: Number(configurationService.getValue(treeStickyScrollMaxElements)),1166} as TOptions1167};1168}11691170interface IWorkbenchTreeInternalsOptionsUpdate {1171readonly multipleSelectionSupport?: boolean;1172}11731174class WorkbenchTreeInternals<TInput, T, TFilterData> {11751176readonly contextKeyService: IScopedContextKeyService;1177private listSupportsMultiSelect: IContextKey<boolean>;1178private listSupportFindWidget: IContextKey<boolean>;1179private hasSelectionOrFocus: IContextKey<boolean>;1180private hasDoubleSelection: IContextKey<boolean>;1181private hasMultiSelection: IContextKey<boolean>;1182private treeElementCanCollapse: IContextKey<boolean>;1183private treeElementHasParent: IContextKey<boolean>;1184private treeElementCanExpand: IContextKey<boolean>;1185private treeElementHasChild: IContextKey<boolean>;1186private treeFindOpen: IContextKey<boolean>;1187private treeStickyScrollFocused: IContextKey<boolean>;1188private _useAltAsMultipleSelectionModifier: boolean;1189private disposables: IDisposable[] = [];11901191private navigator: TreeResourceNavigator<T, TFilterData>;11921193get onDidOpen(): Event<IOpenEvent<T | undefined>> { return this.navigator.onDidOpen; }11941195constructor(1196private tree: WorkbenchObjectTree<T, TFilterData> | WorkbenchCompressibleObjectTree<T, TFilterData> | WorkbenchDataTree<TInput, T, TFilterData> | WorkbenchAsyncDataTree<TInput, T, TFilterData> | WorkbenchCompressibleAsyncDataTree<TInput, T, TFilterData>,1197options: IWorkbenchObjectTreeOptions<T, TFilterData> | IWorkbenchCompressibleObjectTreeOptions<T, TFilterData> | IWorkbenchDataTreeOptions<T, TFilterData> | IWorkbenchAsyncDataTreeOptions<T, TFilterData> | IWorkbenchCompressibleAsyncDataTreeOptions<T, TFilterData>,1198getTypeNavigationMode: () => TypeNavigationMode | undefined,1199overrideStyles: IStyleOverride<IListStyles> | undefined,1200@IContextKeyService contextKeyService: IContextKeyService,1201@IListService listService: IListService,1202@IConfigurationService configurationService: IConfigurationService1203) {1204this.contextKeyService = createScopedContextKeyService(contextKeyService, tree);12051206this.disposables.push(createScrollObserver(this.contextKeyService, tree));12071208this.listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService);1209this.listSupportsMultiSelect.set(options.multipleSelectionSupport !== false);12101211const listSelectionNavigation = WorkbenchListSelectionNavigation.bindTo(this.contextKeyService);1212listSelectionNavigation.set(Boolean(options.selectionNavigation));12131214this.listSupportFindWidget = WorkbenchListSupportsFind.bindTo(this.contextKeyService);1215this.listSupportFindWidget.set(options.findWidgetEnabled ?? true);12161217this.hasSelectionOrFocus = WorkbenchListHasSelectionOrFocus.bindTo(this.contextKeyService);1218this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);1219this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);12201221this.treeElementCanCollapse = WorkbenchTreeElementCanCollapse.bindTo(this.contextKeyService);1222this.treeElementHasParent = WorkbenchTreeElementHasParent.bindTo(this.contextKeyService);1223this.treeElementCanExpand = WorkbenchTreeElementCanExpand.bindTo(this.contextKeyService);1224this.treeElementHasChild = WorkbenchTreeElementHasChild.bindTo(this.contextKeyService);12251226this.treeFindOpen = WorkbenchTreeFindOpen.bindTo(this.contextKeyService);1227this.treeStickyScrollFocused = WorkbenchTreeStickyScrollFocused.bindTo(this.contextKeyService);12281229this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);12301231this.updateStyleOverrides(overrideStyles);12321233const updateCollapseContextKeys = () => {1234const focus = tree.getFocus()[0];12351236if (!focus) {1237return;1238}12391240const node = tree.getNode(focus);1241this.treeElementCanCollapse.set(node.collapsible && !node.collapsed);1242this.treeElementHasParent.set(!!tree.getParentElement(focus));1243this.treeElementCanExpand.set(node.collapsible && node.collapsed);1244this.treeElementHasChild.set(!!tree.getFirstElementChild(focus));1245};12461247const interestingContextKeys = new Set();1248interestingContextKeys.add(WorkbenchListTypeNavigationModeKey);1249interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationLegacyKey);12501251this.disposables.push(1252this.contextKeyService,1253(listService as ListService).register(tree),1254tree.onDidChangeSelection(() => {1255const selection = tree.getSelection();1256const focus = tree.getFocus();12571258this.contextKeyService.bufferChangeEvents(() => {1259this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);1260this.hasMultiSelection.set(selection.length > 1);1261this.hasDoubleSelection.set(selection.length === 2);1262});1263}),1264tree.onDidChangeFocus(() => {1265const selection = tree.getSelection();1266const focus = tree.getFocus();12671268this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0);1269updateCollapseContextKeys();1270}),1271tree.onDidChangeCollapseState(updateCollapseContextKeys),1272tree.onDidChangeModel(updateCollapseContextKeys),1273tree.onDidChangeFindOpenState(enabled => this.treeFindOpen.set(enabled)),1274tree.onDidChangeStickyScrollFocused(focused => this.treeStickyScrollFocused.set(focused)),1275configurationService.onDidChangeConfiguration(e => {1276let newOptions: IAbstractTreeOptionsUpdate<unknown> = {};1277if (e.affectsConfiguration(multiSelectModifierSettingKey)) {1278this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);1279}1280if (e.affectsConfiguration(treeIndentKey)) {1281const indent = configurationService.getValue<number>(treeIndentKey);1282newOptions = { ...newOptions, indent };1283}1284if (e.affectsConfiguration(treeRenderIndentGuidesKey) && options.renderIndentGuides === undefined) {1285const renderIndentGuides = configurationService.getValue<RenderIndentGuides>(treeRenderIndentGuidesKey);1286newOptions = { ...newOptions, renderIndentGuides };1287}1288if (e.affectsConfiguration(listSmoothScrolling)) {1289const smoothScrolling = Boolean(configurationService.getValue(listSmoothScrolling));1290newOptions = { ...newOptions, smoothScrolling };1291}1292if (e.affectsConfiguration(defaultFindModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {1293const defaultFindMode = getDefaultTreeFindMode(configurationService);1294newOptions = { ...newOptions, defaultFindMode };1295}1296if (e.affectsConfiguration(typeNavigationModeSettingKey) || e.affectsConfiguration(keyboardNavigationSettingKey)) {1297const typeNavigationMode = getTypeNavigationMode();1298newOptions = { ...newOptions, typeNavigationMode };1299}1300if (e.affectsConfiguration(defaultFindMatchTypeSettingKey)) {1301const defaultFindMatchType = getDefaultTreeFindMatchType(configurationService);1302newOptions = { ...newOptions, defaultFindMatchType };1303}1304if (e.affectsConfiguration(horizontalScrollingKey) && options.horizontalScrolling === undefined) {1305const horizontalScrolling = Boolean(configurationService.getValue(horizontalScrollingKey));1306newOptions = { ...newOptions, horizontalScrolling };1307}1308if (e.affectsConfiguration(scrollByPageKey)) {1309const scrollByPage = Boolean(configurationService.getValue(scrollByPageKey));1310newOptions = { ...newOptions, scrollByPage };1311}1312if (e.affectsConfiguration(treeExpandMode) && options.expandOnlyOnTwistieClick === undefined) {1313newOptions = { ...newOptions, expandOnlyOnTwistieClick: configurationService.getValue<'singleClick' | 'doubleClick'>(treeExpandMode) === 'doubleClick' };1314}1315if (e.affectsConfiguration(treeStickyScroll)) {1316const enableStickyScroll = configurationService.getValue<boolean>(treeStickyScroll);1317newOptions = { ...newOptions, enableStickyScroll };1318}1319if (e.affectsConfiguration(treeStickyScrollMaxElements)) {1320const stickyScrollMaxItemCount = Math.max(1, configurationService.getValue<number>(treeStickyScrollMaxElements));1321newOptions = { ...newOptions, stickyScrollMaxItemCount };1322}1323if (e.affectsConfiguration(mouseWheelScrollSensitivityKey)) {1324const mouseWheelScrollSensitivity = configurationService.getValue<number>(mouseWheelScrollSensitivityKey);1325newOptions = { ...newOptions, mouseWheelScrollSensitivity };1326}1327if (e.affectsConfiguration(fastScrollSensitivityKey)) {1328const fastScrollSensitivity = configurationService.getValue<number>(fastScrollSensitivityKey);1329newOptions = { ...newOptions, fastScrollSensitivity };1330}1331if (Object.keys(newOptions).length > 0) {1332tree.updateOptions(newOptions);1333}1334}),1335this.contextKeyService.onDidChangeContext(e => {1336if (e.affectsSome(interestingContextKeys)) {1337tree.updateOptions({ typeNavigationMode: getTypeNavigationMode() });1338}1339})1340);13411342this.navigator = new TreeResourceNavigator(tree, { configurationService, ...options });1343this.disposables.push(this.navigator);1344}13451346get useAltAsMultipleSelectionModifier(): boolean {1347return this._useAltAsMultipleSelectionModifier;1348}13491350updateOptions(options: IWorkbenchTreeInternalsOptionsUpdate): void {1351if (options.multipleSelectionSupport !== undefined) {1352this.listSupportsMultiSelect.set(!!options.multipleSelectionSupport);1353}1354}13551356updateStyleOverrides(overrideStyles?: IStyleOverride<IListStyles>): void {1357this.tree.style(overrideStyles ? getListStyles(overrideStyles) : defaultListStyles);1358}13591360dispose(): void {1361this.disposables = dispose(this.disposables);1362}1363}13641365const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);13661367configurationRegistry.registerConfiguration({1368id: 'workbench',1369order: 7,1370title: localize('workbenchConfigurationTitle', "Workbench"),1371type: 'object',1372properties: {1373[multiSelectModifierSettingKey]: {1374type: 'string',1375enum: ['ctrlCmd', 'alt'],1376markdownEnumDescriptions: [1377localize('multiSelectModifier.ctrlCmd', "Maps to `Control` on Windows and Linux and to `Command` on macOS."),1378localize('multiSelectModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.")1379],1380default: 'ctrlCmd',1381description: localize({1382key: 'multiSelectModifier',1383comment: [1384'- `ctrlCmd` refers to a value the setting can take and should not be localized.',1385'- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.'1386]1387}, "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.")1388},1389[openModeSettingKey]: {1390type: 'string',1391enum: ['singleClick', 'doubleClick'],1392default: 'singleClick',1393description: localize({1394key: 'openModeModifier',1395comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']1396}, "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.")1397},1398[horizontalScrollingKey]: {1399type: 'boolean',1400default: false,1401description: localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench. Warning: turning on this setting has a performance implication.")1402},1403[scrollByPageKey]: {1404type: 'boolean',1405default: false,1406description: localize('list.scrollByPage', "Controls whether clicks in the scrollbar scroll page by page.")1407},1408[treeIndentKey]: {1409type: 'number',1410default: 8,1411minimum: 4,1412maximum: 40,1413description: localize('tree indent setting', "Controls tree indentation in pixels.")1414},1415[treeRenderIndentGuidesKey]: {1416type: 'string',1417enum: ['none', 'onHover', 'always'],1418default: 'onHover',1419description: localize('render tree indent guides', "Controls whether the tree should render indent guides.")1420},1421[listSmoothScrolling]: {1422type: 'boolean',1423default: false,1424description: localize('list smoothScrolling setting', "Controls whether lists and trees have smooth scrolling."),1425},1426[mouseWheelScrollSensitivityKey]: {1427type: 'number',1428default: 1,1429markdownDescription: localize('Mouse Wheel Scroll Sensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.")1430},1431[fastScrollSensitivityKey]: {1432type: 'number',1433default: 5,1434markdownDescription: localize('Fast Scroll Sensitivity', "Scrolling speed multiplier when pressing `Alt`.")1435},1436[defaultFindModeSettingKey]: {1437type: 'string',1438enum: ['highlight', 'filter'],1439enumDescriptions: [1440localize('defaultFindModeSettingKey.highlight', "Highlight elements when searching. Further up and down navigation will traverse only the highlighted elements."),1441localize('defaultFindModeSettingKey.filter', "Filter elements when searching.")1442],1443default: 'highlight',1444description: localize('defaultFindModeSettingKey', "Controls the default find mode for lists and trees in the workbench.")1445},1446[keyboardNavigationSettingKey]: {1447type: 'string',1448enum: ['simple', 'highlight', 'filter'],1449enumDescriptions: [1450localize('keyboardNavigationSettingKey.simple', "Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes."),1451localize('keyboardNavigationSettingKey.highlight', "Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements."),1452localize('keyboardNavigationSettingKey.filter', "Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.")1453],1454default: 'highlight',1455description: localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter."),1456deprecated: true,1457deprecationMessage: localize('keyboardNavigationSettingKeyDeprecated', "Please use 'workbench.list.defaultFindMode' and 'workbench.list.typeNavigationMode' instead.")1458},1459[defaultFindMatchTypeSettingKey]: {1460type: 'string',1461enum: ['fuzzy', 'contiguous'],1462enumDescriptions: [1463localize('defaultFindMatchTypeSettingKey.fuzzy', "Use fuzzy matching when searching."),1464localize('defaultFindMatchTypeSettingKey.contiguous', "Use contiguous matching when searching.")1465],1466default: 'fuzzy',1467description: localize('defaultFindMatchTypeSettingKey', "Controls the type of matching used when searching lists and trees in the workbench.")1468},1469[treeExpandMode]: {1470type: 'string',1471enum: ['singleClick', 'doubleClick'],1472default: 'singleClick',1473description: 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."),1474},1475[treeStickyScroll]: {1476type: 'boolean',1477default: true,1478description: localize('sticky scroll', "Controls whether sticky scrolling is enabled in trees."),1479},1480[treeStickyScrollMaxElements]: {1481type: 'number',1482minimum: 1,1483default: 7,1484markdownDescription: localize('sticky scroll maximum items', "Controls the number of sticky elements displayed in the tree when {0} is enabled.", '`#workbench.tree.enableStickyScroll#`'),1485},1486[typeNavigationModeSettingKey]: {1487type: 'string',1488enum: ['automatic', 'trigger'],1489default: 'automatic',1490markdownDescription: 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."),1491}1492}1493});149414951496