Path: blob/main/src/vs/workbench/browser/actions/navigationActions.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 { localize2 } from '../../../nls.js';6import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from '../../services/editor/common/editorGroupsService.js';7import { IWorkbenchLayoutService, Parts } from '../../services/layout/browser/layoutService.js';8import { Action2, IAction2Options, registerAction2 } from '../../../platform/actions/common/actions.js';9import { Categories } from '../../../platform/action/common/actionCommonCategories.js';10import { Direction } from '../../../base/browser/ui/grid/grid.js';11import { KeyCode, KeyMod } from '../../../base/common/keyCodes.js';12import { IEditorService } from '../../services/editor/common/editorService.js';13import { IPaneComposite } from '../../common/panecomposite.js';14import { IComposite } from '../../common/composite.js';15import { IPaneCompositePartService } from '../../services/panecomposite/browser/panecomposite.js';16import { ViewContainerLocation } from '../../common/views.js';17import { KeybindingWeight } from '../../../platform/keybinding/common/keybindingsRegistry.js';18import { ServicesAccessor } from '../../../platform/instantiation/common/instantiation.js';19import { getActiveWindow } from '../../../base/browser/dom.js';20import { isAuxiliaryWindow } from '../../../base/browser/window.js';2122abstract class BaseNavigationAction extends Action2 {2324constructor(25options: IAction2Options,26protected direction: Direction27) {28super(options);29}3031run(accessor: ServicesAccessor): void {32const layoutService = accessor.get(IWorkbenchLayoutService);33const editorGroupService = accessor.get(IEditorGroupsService);34const paneCompositeService = accessor.get(IPaneCompositePartService);3536const isEditorFocus = layoutService.hasFocus(Parts.EDITOR_PART);37const isPanelFocus = layoutService.hasFocus(Parts.PANEL_PART);38const isSidebarFocus = layoutService.hasFocus(Parts.SIDEBAR_PART);39const isAuxiliaryBarFocus = layoutService.hasFocus(Parts.AUXILIARYBAR_PART);4041let neighborPart: Parts | undefined;42if (isEditorFocus) {43const didNavigate = this.navigateAcrossEditorGroup(this.toGroupDirection(this.direction), editorGroupService);44if (didNavigate) {45return;46}4748neighborPart = layoutService.getVisibleNeighborPart(Parts.EDITOR_PART, this.direction);49}5051if (isPanelFocus) {52neighborPart = layoutService.getVisibleNeighborPart(Parts.PANEL_PART, this.direction);53}5455if (isSidebarFocus) {56neighborPart = layoutService.getVisibleNeighborPart(Parts.SIDEBAR_PART, this.direction);57}5859if (isAuxiliaryBarFocus) {60neighborPart = neighborPart = layoutService.getVisibleNeighborPart(Parts.AUXILIARYBAR_PART, this.direction);61}6263if (neighborPart === Parts.EDITOR_PART) {64if (!this.navigateBackToEditorGroup(this.toGroupDirection(this.direction), editorGroupService)) {65this.navigateToEditorGroup(this.direction === Direction.Right ? GroupLocation.FIRST : GroupLocation.LAST, editorGroupService);66}67} else if (neighborPart === Parts.SIDEBAR_PART) {68this.navigateToSidebar(layoutService, paneCompositeService);69} else if (neighborPart === Parts.PANEL_PART) {70this.navigateToPanel(layoutService, paneCompositeService);71} else if (neighborPart === Parts.AUXILIARYBAR_PART) {72this.navigateToAuxiliaryBar(layoutService, paneCompositeService);73}74}7576private async navigateToPanel(layoutService: IWorkbenchLayoutService, paneCompositeService: IPaneCompositePartService): Promise<IComposite | boolean> {77if (!layoutService.isVisible(Parts.PANEL_PART)) {78return false;79}8081const activePanel = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel);82if (!activePanel) {83return false;84}8586const activePanelId = activePanel.getId();8788const res = await paneCompositeService.openPaneComposite(activePanelId, ViewContainerLocation.Panel, true);89if (!res) {90return false;91}9293return res;94}9596private async navigateToSidebar(layoutService: IWorkbenchLayoutService, paneCompositeService: IPaneCompositePartService): Promise<IPaneComposite | boolean> {97if (!layoutService.isVisible(Parts.SIDEBAR_PART)) {98return false;99}100101const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);102if (!activeViewlet) {103return false;104}105const activeViewletId = activeViewlet.getId();106107const viewlet = await paneCompositeService.openPaneComposite(activeViewletId, ViewContainerLocation.Sidebar, true);108return !!viewlet;109}110111private async navigateToAuxiliaryBar(layoutService: IWorkbenchLayoutService, paneCompositeService: IPaneCompositePartService): Promise<IComposite | boolean> {112if (!layoutService.isVisible(Parts.AUXILIARYBAR_PART)) {113return false;114}115116const activePanel = paneCompositeService.getActivePaneComposite(ViewContainerLocation.AuxiliaryBar);117if (!activePanel) {118return false;119}120121const activePanelId = activePanel.getId();122123const res = await paneCompositeService.openPaneComposite(activePanelId, ViewContainerLocation.AuxiliaryBar, true);124if (!res) {125return false;126}127128return res;129}130131private navigateAcrossEditorGroup(direction: GroupDirection, editorGroupService: IEditorGroupsService): boolean {132return this.doNavigateToEditorGroup({ direction }, editorGroupService);133}134135private navigateToEditorGroup(location: GroupLocation, editorGroupService: IEditorGroupsService): boolean {136return this.doNavigateToEditorGroup({ location }, editorGroupService);137}138139private navigateBackToEditorGroup(direction: GroupDirection, editorGroupService: IEditorGroupsService): boolean {140if (!editorGroupService.activeGroup) {141return false;142}143144const oppositeDirection = this.toOppositeDirection(direction);145146// Check to see if there is a group in between the last147// active group and the direction of movement148149const groupInBetween = editorGroupService.findGroup({ direction: oppositeDirection }, editorGroupService.activeGroup);150if (!groupInBetween) {151152// No group in between means we can return153// focus to the last active editor group154155editorGroupService.activeGroup.focus();156return true;157}158159return false;160}161162private toGroupDirection(direction: Direction): GroupDirection {163switch (direction) {164case Direction.Down: return GroupDirection.DOWN;165case Direction.Left: return GroupDirection.LEFT;166case Direction.Right: return GroupDirection.RIGHT;167case Direction.Up: return GroupDirection.UP;168}169}170171private toOppositeDirection(direction: GroupDirection): GroupDirection {172switch (direction) {173case GroupDirection.UP: return GroupDirection.DOWN;174case GroupDirection.RIGHT: return GroupDirection.LEFT;175case GroupDirection.LEFT: return GroupDirection.RIGHT;176case GroupDirection.DOWN: return GroupDirection.UP;177}178}179180private doNavigateToEditorGroup(scope: IFindGroupScope, editorGroupService: IEditorGroupsService): boolean {181const targetGroup = editorGroupService.findGroup(scope, editorGroupService.activeGroup);182if (targetGroup) {183targetGroup.focus();184185return true;186}187188return false;189}190}191192registerAction2(class extends BaseNavigationAction {193194constructor() {195super({196id: 'workbench.action.navigateLeft',197title: localize2('navigateLeft', 'Navigate to the View on the Left'),198category: Categories.View,199f1: true200}, Direction.Left);201}202});203204registerAction2(class extends BaseNavigationAction {205206constructor() {207super({208id: 'workbench.action.navigateRight',209title: localize2('navigateRight', 'Navigate to the View on the Right'),210category: Categories.View,211f1: true212}, Direction.Right);213}214});215216registerAction2(class extends BaseNavigationAction {217218constructor() {219super({220id: 'workbench.action.navigateUp',221title: localize2('navigateUp', 'Navigate to the View Above'),222category: Categories.View,223f1: true224}, Direction.Up);225}226});227228registerAction2(class extends BaseNavigationAction {229230constructor() {231super({232id: 'workbench.action.navigateDown',233title: localize2('navigateDown', 'Navigate to the View Below'),234category: Categories.View,235f1: true236}, Direction.Down);237}238});239240abstract class BaseFocusAction extends Action2 {241242constructor(243options: IAction2Options,244private readonly focusNext: boolean245) {246super(options);247}248249run(accessor: ServicesAccessor): void {250const layoutService = accessor.get(IWorkbenchLayoutService);251const editorService = accessor.get(IEditorService);252253this.focusNextOrPreviousPart(layoutService, editorService, this.focusNext);254}255256private findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Parts, next: boolean): Parts {257const activeWindow = getActiveWindow();258const windowIsAuxiliary = isAuxiliaryWindow(activeWindow);259260let neighbour: Parts;261if (windowIsAuxiliary) {262switch (part) {263case Parts.EDITOR_PART:264neighbour = Parts.STATUSBAR_PART;265break;266default:267neighbour = Parts.EDITOR_PART;268}269} else {270switch (part) {271case Parts.EDITOR_PART:272neighbour = next ? Parts.PANEL_PART : Parts.SIDEBAR_PART;273break;274case Parts.PANEL_PART:275neighbour = next ? Parts.AUXILIARYBAR_PART : Parts.EDITOR_PART;276break;277case Parts.AUXILIARYBAR_PART:278neighbour = next ? Parts.STATUSBAR_PART : Parts.PANEL_PART;279break;280case Parts.STATUSBAR_PART:281neighbour = next ? Parts.ACTIVITYBAR_PART : Parts.AUXILIARYBAR_PART;282break;283case Parts.ACTIVITYBAR_PART:284neighbour = next ? Parts.SIDEBAR_PART : Parts.STATUSBAR_PART;285break;286case Parts.SIDEBAR_PART:287neighbour = next ? Parts.EDITOR_PART : Parts.ACTIVITYBAR_PART;288break;289default:290neighbour = Parts.EDITOR_PART;291}292}293294if (layoutService.isVisible(neighbour, activeWindow) || neighbour === Parts.EDITOR_PART) {295return neighbour;296}297298return this.findVisibleNeighbour(layoutService, neighbour, next);299}300301private focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {302let currentlyFocusedPart: Parts | undefined;303if (editorService.activeEditorPane?.hasFocus() || layoutService.hasFocus(Parts.EDITOR_PART)) {304currentlyFocusedPart = Parts.EDITOR_PART;305} else if (layoutService.hasFocus(Parts.ACTIVITYBAR_PART)) {306currentlyFocusedPart = Parts.ACTIVITYBAR_PART;307} else if (layoutService.hasFocus(Parts.STATUSBAR_PART)) {308currentlyFocusedPart = Parts.STATUSBAR_PART;309} else if (layoutService.hasFocus(Parts.SIDEBAR_PART)) {310currentlyFocusedPart = Parts.SIDEBAR_PART;311} else if (layoutService.hasFocus(Parts.AUXILIARYBAR_PART)) {312currentlyFocusedPart = Parts.AUXILIARYBAR_PART;313} else if (layoutService.hasFocus(Parts.PANEL_PART)) {314currentlyFocusedPart = Parts.PANEL_PART;315}316317layoutService.focusPart(currentlyFocusedPart ? this.findVisibleNeighbour(layoutService, currentlyFocusedPart, next) : Parts.EDITOR_PART, getActiveWindow());318}319}320321registerAction2(class extends BaseFocusAction {322323constructor() {324super({325id: 'workbench.action.focusNextPart',326title: localize2('focusNextPart', 'Focus Next Part'),327category: Categories.View,328f1: true,329keybinding: {330primary: KeyCode.F6,331weight: KeybindingWeight.WorkbenchContrib332}333}, true);334}335});336337registerAction2(class extends BaseFocusAction {338339constructor() {340super({341id: 'workbench.action.focusPreviousPart',342title: localize2('focusPreviousPart', 'Focus Previous Part'),343category: Categories.View,344f1: true,345keybinding: {346primary: KeyMod.Shift | KeyCode.F6,347weight: KeybindingWeight.WorkbenchContrib348}349}, false);350}351});352353354