Path: blob/main/src/vs/workbench/browser/actions/widgetNavigationCommands.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 { KeyMod, KeyCode } from '../../../base/common/keyCodes.js';6import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../platform/contextkey/common/contextkey.js';7import { KeybindingWeight, KeybindingsRegistry } from '../../../platform/keybinding/common/keybindingsRegistry.js';8import { WorkbenchListFocusContextKey, WorkbenchListScrollAtBottomContextKey, WorkbenchListScrollAtTopContextKey } from '../../../platform/list/browser/listService.js';9import { Event } from '../../../base/common/event.js';10import { combinedDisposable, toDisposable, IDisposable, Disposable } from '../../../base/common/lifecycle.js';11import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../common/contributions.js';12import { ILogService } from '../../../platform/log/common/log.js';13import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';1415/** INavigableContainer represents a logical container composed of widgets that can16be navigated back and forth with key shortcuts */17interface INavigableContainer {18/**19* The container may coomposed of multiple parts that share no DOM ancestor20* (e.g., the main body and filter box of MarkersView may be separated).21* To track the focus of container we must pass in focus/blur events of all parts22* as `focusNotifiers`.23*24* Each element of `focusNotifiers` notifies the focus/blur event for a part of25* the container. The container is considered focused if at least one part being26* focused, and blurred if all parts being blurred.27*/28readonly focusNotifiers: readonly IFocusNotifier[];29readonly name?: string; // for debugging30focusPreviousWidget(): void;31focusNextWidget(): void;32}3334interface IFocusNotifier {35readonly onDidFocus: Event<any>;36readonly onDidBlur: Event<any>;37}3839function handleFocusEventsGroup(group: readonly IFocusNotifier[], handler: (isFocus: boolean) => void, onPartFocusChange?: (index: number, state: string) => void): IDisposable {40const focusedIndices = new Set<number>();41return combinedDisposable(...group.map((events, index) => combinedDisposable(42events.onDidFocus(() => {43onPartFocusChange?.(index, 'focus');44if (!focusedIndices.size) {45handler(true);46}47focusedIndices.add(index);48}),49events.onDidBlur(() => {50onPartFocusChange?.(index, 'blur');51focusedIndices.delete(index);52if (!focusedIndices.size) {53handler(false);54}55}),56)));57}5859const NavigableContainerFocusedContextKey = new RawContextKey<boolean>('navigableContainerFocused', false);6061class NavigableContainerManager implements IDisposable {6263static readonly ID = 'workbench.contrib.navigableContainerManager';6465private static INSTANCE: NavigableContainerManager | undefined;6667private readonly containers = new Set<INavigableContainer>();68private lastContainer: INavigableContainer | undefined;69private focused: IContextKey<boolean>;707172constructor(73@IContextKeyService contextKeyService: IContextKeyService,74@ILogService private logService: ILogService,75@IConfigurationService private configurationService: IConfigurationService) {76this.focused = NavigableContainerFocusedContextKey.bindTo(contextKeyService);77NavigableContainerManager.INSTANCE = this;78}7980dispose(): void {81this.containers.clear();82this.focused.reset();83NavigableContainerManager.INSTANCE = undefined;84}8586private get debugEnabled(): boolean {87return this.configurationService.getValue('workbench.navigibleContainer.enableDebug');88}8990private log(msg: string, ...args: any[]): void {91if (this.debugEnabled) {92this.logService.debug(msg, ...args);93}94}9596static register(container: INavigableContainer): IDisposable {97const instance = this.INSTANCE;98if (!instance) {99return Disposable.None;100}101instance.containers.add(container);102instance.log('NavigableContainerManager.register', container.name);103104return combinedDisposable(105handleFocusEventsGroup(container.focusNotifiers, (isFocus) => {106if (isFocus) {107instance.log('NavigableContainerManager.focus', container.name);108instance.focused.set(true);109instance.lastContainer = container;110} else {111instance.log('NavigableContainerManager.blur', container.name, instance.lastContainer?.name);112if (instance.lastContainer === container) {113instance.focused.set(false);114instance.lastContainer = undefined;115}116}117}, (index: number, event: string) => {118instance.log('NavigableContainerManager.partFocusChange', container.name, index, event);119}),120toDisposable(() => {121instance.containers.delete(container);122instance.log('NavigableContainerManager.unregister', container.name, instance.lastContainer?.name);123if (instance.lastContainer === container) {124instance.focused.set(false);125instance.lastContainer = undefined;126}127})128);129}130131static getActive(): INavigableContainer | undefined {132return this.INSTANCE?.lastContainer;133}134}135136export function registerNavigableContainer(container: INavigableContainer): IDisposable {137return NavigableContainerManager.register(container);138}139140registerWorkbenchContribution2(NavigableContainerManager.ID, NavigableContainerManager, WorkbenchPhase.BlockStartup);141142KeybindingsRegistry.registerCommandAndKeybindingRule({143id: 'widgetNavigation.focusPrevious',144weight: KeybindingWeight.WorkbenchContrib,145when: ContextKeyExpr.and(146NavigableContainerFocusedContextKey,147ContextKeyExpr.or(148WorkbenchListFocusContextKey?.negate(),149WorkbenchListScrollAtTopContextKey,150)151),152primary: KeyMod.CtrlCmd | KeyCode.UpArrow,153handler: () => {154const activeContainer = NavigableContainerManager.getActive();155activeContainer?.focusPreviousWidget();156}157});158159KeybindingsRegistry.registerCommandAndKeybindingRule({160id: 'widgetNavigation.focusNext',161weight: KeybindingWeight.WorkbenchContrib,162when: ContextKeyExpr.and(163NavigableContainerFocusedContextKey,164ContextKeyExpr.or(165WorkbenchListFocusContextKey?.negate(),166WorkbenchListScrollAtBottomContextKey,167)168),169primary: KeyMod.CtrlCmd | KeyCode.DownArrow,170handler: () => {171const activeContainer = NavigableContainerManager.getActive();172activeContainer?.focusNextWidget();173}174});175176177