diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 081100f852820..d917d2f9a3827 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -74,7 +74,6 @@ export class MenuId { static readonly ExplorerContextShare = new MenuId('ExplorerContextShare'); static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); - static readonly TitleBarGlobalControlMenu = new MenuId('TitleBarGlobalControlMenu'); static readonly CommandCenter = new MenuId('CommandCenter'); static readonly CommandCenterCenter = new MenuId('CommandCenterCenter'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index b172981b69daf..0c35bafff5918 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -51,6 +51,7 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { splitOnDragAndDrop: true, centeredLayoutFixedWidth: false, doubleClickTabToToggleEditorGroupSizes: 'expand', + showEditorActionsInTitleBar: 'noTabs', wrapTabs: false, enablePreviewFromQuickOpen: false, scrollToSwitchTabs: false, @@ -149,6 +150,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): IEditorPartOpti 'splitInGroupLayout': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitInGroupLayout'], ['vertical', 'horizontal']), 'splitSizing': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitSizing'], ['distribute', 'split', 'auto']), 'doubleClickTabToToggleEditorGroupSizes': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['doubleClickTabToToggleEditorGroupSizes'], ['maximize', 'expand', 'off']), + 'showEditorActionsInTitleBar': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['showEditorActionsInTitleBar'], ['noTabs', 'never']), 'autoLockGroups': new SetVerifier(DEFAULT_EDITOR_PART_OPTIONS['autoLockGroups']), 'limit': new ObjectVerifier(DEFAULT_EDITOR_PART_OPTIONS['limit'], { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index d305ac4482b29..0da1794a33962 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -5,11 +5,11 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroupModel, IEditorOpenOptions, IGroupModelChangeEvent, ISerializedEditorGroupModel, isGroupEditorCloseEvent, isGroupEditorOpenEvent, isSerializedEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; -import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions } from 'vs/workbench/common/editor'; -import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, EditorPinnedAndUnpinnedTabsContext } from 'vs/workbench/common/contextkeys'; +import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, EditorResourceAccessor, EditorInputCapabilities, IUntypedEditorInput, DEFAULT_EDITOR_ASSOCIATION, SideBySideEditor, EditorCloseContext, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, GroupModelChangeKind, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions } from 'vs/workbench/common/editor'; +import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, EditorPinnedAndUnpinnedTabsContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; -import { Emitter, Relay } from 'vs/base/common/event'; +import { Emitter, Relay, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement, focusWindow } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -18,20 +18,20 @@ import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_HEADER_BORDER } from 'vs/workbench/common/theme'; -import { ICloseEditorsFilter, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ICloseEditorsFilter, GroupsOrder, ICloseEditorOptions, ICloseAllEditorsOptions, IEditorReplacement, IActiveEditorActions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPanes } from 'vs/workbench/browser/parts/editor/editorPanes'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { localize } from 'vs/nls'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; -import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DeferredPromise, Promises, RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { IEditorGroupsView, IEditorGroupView, fillActiveEditorViewState, EditorServiceImpl, IEditorGroupTitleHeight, IInternalEditorOpenOptions, IInternalMoveCopyOptions, IInternalEditorCloseOptions, IInternalEditorTitleControlOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, SubmenuAction } from 'vs/base/common/actions'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -53,6 +53,8 @@ import { defaultProgressBarStyles } from 'vs/platform/theme/browser/defaultStyle import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { EditorGroupWatermark } from 'vs/workbench/browser/parts/editor/editorGroupWatermark'; import { EditorTitleControl } from 'vs/workbench/browser/parts/editor/editorTitleControl'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -115,6 +117,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private readonly scopedInstantiationService: IInstantiationService; + private readonly resourceContext: ResourceContextKey; + private readonly titleContainer: HTMLElement; private readonly titleControl: EditorTitleControl; @@ -149,7 +153,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IEditorService private readonly editorService: EditorServiceImpl, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IEditorResolverService private readonly editorResolverService: IEditorResolverService ) { super(themeService); @@ -192,6 +197,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { )); // Context keys + this.resourceContext = this._register(this.scopedInstantiationService.createInstance(ResourceContextKey)); this.handleGroupContextKeys(); // Title container @@ -245,20 +251,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const groupLockedContext = ActiveEditorGroupLockedContext.bindTo(this.scopedContextKeyService); const groupHasPinnedAndUnpinnedContext = EditorPinnedAndUnpinnedTabsContext.bindTo(this.scopedContextKeyService); + const groupActiveEditorAvailableEditorIds = ActiveEditorAvailableEditorIdsContext.bindTo(this.scopedContextKeyService); + const groupActiveEditorCanSplitInGroupContext = ActiveEditorCanSplitInGroupContext.bindTo(this.scopedContextKeyService); + const sideBySideEditorContext = SideBySideEditorActiveContext.bindTo(this.scopedContextKeyService); + const activeEditorListener = this._register(new MutableDisposable()); const observeActiveEditor = () => { activeEditorListener.clear(); - const activeEditor = this.model.activeEditor; - if (activeEditor) { - groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); - activeEditorListener.value = activeEditor.onDidChangeDirty(() => { + this.scopedContextKeyService.bufferChangeEvents(() => { + const activeEditor = this.activeEditor; + + this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); + + applyAvailableEditorIds(groupActiveEditorAvailableEditorIds, activeEditor, this.editorResolverService); + + groupActiveEditorCanSplitInGroupContext.set(activeEditor ? activeEditor.hasCapability(EditorInputCapabilities.CanSplitInGroup) : false); + sideBySideEditorContext.set(activeEditor?.typeId === SideBySideEditorInput.ID); + + if (activeEditor) { groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); - }); - } else { - groupActiveEditorDirtyContext.set(false); - } + activeEditorListener.value = activeEditor.onDidChangeDirty(() => { + groupActiveEditorDirtyContext.set(activeEditor.isDirty() && !activeEditor.isSaving()); + }); + } else { + groupActiveEditorDirtyContext.set(false); + } + }); }; // Update group contexts based on group changes @@ -350,7 +370,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Toolbar actions const containerToolbarMenu = this._register(this.menuService.createMenu(MenuId.EmptyEditorGroup, this.scopedContextKeyService)); const updateContainerToolbar = () => { - const actions: { primary: IAction[]; secondary: IAction[] } = { primary: [], secondary: [] }; + const actions: IToolbarActions = { primary: [], secondary: [] }; // Clear old actions this.containerToolBarMenuDisposable.value = toDisposable(() => containerToolbar.clear()); @@ -1889,6 +1909,37 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#endregion + //#region Editor Actions + + createEditorActions(disposables: DisposableStore): IActiveEditorActions { + const primary: IAction[] = []; + const secondary: IAction[] = []; + + let onDidChange = Event.None; + + // Editor actions require the editor control to be there, so we retrieve it via service + const activeEditorPane = this.activeEditorPane; + if (activeEditorPane instanceof EditorPane) { + const editorScopedContextKeyService = activeEditorPane.scopedContextKeyService ?? this.scopedContextKeyService; + const editorTitleMenu = disposables.add(this.menuService.createMenu(MenuId.EditorTitle, editorScopedContextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: 0 })); + onDidChange = editorTitleMenu.onDidChange; + + const shouldInlineGroup = (action: SubmenuAction, group: string) => group === 'navigation' && action.actions.length <= 1; + + createAndFillInActionBarActions( + editorTitleMenu, + { arg: this.resourceContext.get(), shouldForwardArgs: true }, + { primary, secondary }, + 'navigation', + shouldInlineGroup + ); + } + + return { actions: { primary, secondary }, onDidChange }; + } + + //#endregion + //#region Themable override updateStyles(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 9bcff2aec4338..362707e380c7a 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -9,11 +9,11 @@ import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType, isMouseEvent } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IAction, SubmenuAction, ActionRunner } from 'vs/base/common/actions'; +import { IAction, ActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; -import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -25,9 +25,9 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds } from 'vs/workbench/common/contextkeys'; +import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { assertIsDefined } from 'vs/base/common/types'; import { isFirefox } from 'vs/base/browser/browser'; @@ -41,11 +41,6 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { EDITOR_CORE_NAVIGATION_COMMANDS } from 'vs/workbench/browser/parts/editor/editorCommands'; -export interface IToolbarActions { - readonly primary: IAction[]; - readonly secondary: IAction[]; -} - export class EditorCommandsContextActionRunner extends ActionRunner { constructor( @@ -102,6 +97,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC }; private editorActionsToolbar: WorkbenchToolBar | undefined; + private editorActionsDisposables = this._register(new DisposableStore()); private resourceContext: ResourceContextKey; @@ -116,8 +112,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC private groupLockedContext: IContextKey; - private readonly editorToolBarMenuDisposables = this._register(new DisposableStore()); - private renderDropdownAsChildElement: boolean; constructor( @@ -131,7 +125,6 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IContextKeyService protected readonly contextKeyService: IContextKeyService, @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, - @IMenuService private readonly menuService: IMenuService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService @@ -209,63 +202,17 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } protected updateEditorActionsToolbar(): void { - const { primary, secondary } = this.prepareEditorActions(this.getEditorActions()); + this.editorActionsDisposables.clear(); + + const editorActions = this.groupView.createEditorActions(this.editorActionsDisposables); + this.editorActionsDisposables.add(editorActions.onDidChange(() => this.updateEditorActionsToolbar())); const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + const { primary, secondary } = this.prepareEditorActions(editorActions.actions); editorActionsToolbar.setActions(prepareActions(primary), prepareActions(secondary)); } protected abstract prepareEditorActions(editorActions: IToolbarActions): IToolbarActions; - - private getEditorActions(): IToolbarActions { - const primary: IAction[] = []; - const secondary: IAction[] = []; - - // Dispose previous listeners - this.editorToolBarMenuDisposables.clear(); - - // Update contexts - this.contextKeyService.bufferChangeEvents(() => { - const activeEditor = this.groupView.activeEditor; - - this.resourceContext.set(EditorResourceAccessor.getOriginalUri(activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY } ?? null)); - - this.editorPinnedContext.set(activeEditor ? this.groupView.isPinned(activeEditor) : false); - this.editorIsFirstContext.set(activeEditor ? this.groupView.isFirst(activeEditor) : false); - this.editorIsLastContext.set(activeEditor ? this.groupView.isLast(activeEditor) : false); - this.editorStickyContext.set(activeEditor ? this.groupView.isSticky(activeEditor) : false); - applyAvailableEditorIds(this.editorAvailableEditorIds, activeEditor, this.editorResolverService); - - this.editorCanSplitInGroupContext.set(activeEditor ? activeEditor.hasCapability(EditorInputCapabilities.CanSplitInGroup) : false); - this.sideBySideEditorContext.set(activeEditor?.typeId === SideBySideEditorInput.ID); - - this.groupLockedContext.set(this.groupView.isLocked); - }); - - // Editor actions require the editor control to be there, so we retrieve it via service - const activeEditorPane = this.groupView.activeEditorPane; - if (activeEditorPane instanceof EditorPane) { - const scopedContextKeyService = this.getEditorPaneAwareContextKeyService(); - const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService, { emitEventsForSubmenuChanges: true, eventDebounceDelay: 0 }); - this.editorToolBarMenuDisposables.add(titleBarMenu); - this.editorToolBarMenuDisposables.add(titleBarMenu.onDidChange(() => { - this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change - })); - - const shouldInlineGroup = (action: SubmenuAction, group: string) => group === 'navigation' && action.actions.length <= 1; - - createAndFillInActionBarActions( - titleBarMenu, - { arg: this.resourceContext.get(), shouldForwardArgs: true }, - { primary, secondary }, - 'navigation', - shouldInlineGroup - ); - } - - return { primary, secondary }; - } - private getEditorPaneAwareContextKeyService(): IContextKeyService { return this.groupView.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService; } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 5256a6f9460a4..9ed758b674ada 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/multieditortabscontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -18,8 +18,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { EditorCommandsContextActionRunner, IToolbarActions, EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { EditorCommandsContextActionRunner, EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -144,7 +144,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @INotificationService notificationService: INotificationService, - @IMenuService menuService: IMenuService, @IQuickInputService quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IEditorService private readonly editorService: EditorServiceImpl, @@ -154,7 +153,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IEditorResolverService editorResolverService: IEditorResolverService, @ILifecycleService private readonly lifecycleService: ILifecycleService ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, menuService, quickInputService, themeService, editorResolverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the diff --git a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts index 384365bd5c4a7..baf08bb450405 100644 --- a/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/noEditorTabsControl.ts @@ -5,9 +5,10 @@ import 'vs/css!./media/singleeditortabscontrol'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorTabsControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { Dimension } from 'vs/base/browser/dom'; import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; +import { IToolbarActions } from 'vs/workbench/common/editor'; export class NoEditorTabsControl extends EditorTabsControl { private activeEditor: EditorInput | null = null; diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 0ea58b717d2c7..03c24053b3e55 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/singleeditortabscontrol'; -import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, preventEditorClose, EditorCloseMethod } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, preventEditorClose, EditorCloseMethod, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorTabsControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 691b373b90adc..e7b73ab68be95 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -15,7 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_DRAG_AND_DROP_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { LayoutPriority } from 'vs/base/browser/ui/grid/grid'; @@ -27,13 +27,12 @@ import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IPaneCompositeBarOptions } from 'vs/workbench/browser/parts/paneCompositeBar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Action2, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; -import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { Action2, IMenuService, registerAction2 } from 'vs/platform/actions/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Separator } from 'vs/base/common/actions'; import { ToggleActivityBarVisibilityActionId } from 'vs/workbench/browser/actions/layoutActions'; +import { localize } from 'vs/nls'; export class SidebarPart extends AbstractPaneCompositePart { @@ -114,7 +113,6 @@ export class SidebarPart extends AbstractPaneCompositePart { })); this.registerActions(); - this.registerGlobalActions(); lifecycleService.when(LifecyclePhase.Eventually).then(() => { telemetryService.publicLog2<{ location: string }, { @@ -283,43 +281,6 @@ export class SidebarPart extends AbstractPaneCompositePart { })); } - private registerGlobalActions() { - this._register(registerAction2( - class extends Action2 { - constructor() { - super({ - id: GLOBAL_ACTIVITY_ID, - title: { value: localize('manage', "Manage"), original: 'Manage' }, - menu: [{ - id: MenuId.TitleBarGlobalControlMenu, - when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), - order: 2 - }] - }); - } - - async run(): Promise { - } - })); - this._register(registerAction2( - class extends Action2 { - constructor() { - super({ - id: ACCOUNTS_ACTIVITY_ID, - title: { value: localize('accounts', "Accounts"), original: 'Accounts' }, - menu: [{ - id: MenuId.TitleBarGlobalControlMenu, - when: ContextKeyExpr.equals(`config.${LayoutSettings.ACTIVITY_BAR_LOCATION}`, ActivityBarPosition.TOP), - order: 2 - }] - }); - } - - async run(): Promise { - } - })); - } - toJSON(): object { return { type: Parts.SIDEBAR_PART diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index d98f25eec3097..e71fb9d7eb557 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -361,9 +361,8 @@ color: white; } -/* Layout Controls */ -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container, -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { +/* Action Tool Bar Controls */ +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { display: none; padding-right: 2px; flex-grow: 0; @@ -376,41 +375,38 @@ min-width: 28px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { margin-left: auto; } -.monaco-workbench.mac:not(.web) .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container, -.monaco-workbench.mac:not(.web) .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container { +.monaco-workbench.mac:not(.web) .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container { right: 8px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container:not(.has-no-actions), -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container.show-layout-control { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container:not(.has-no-actions) { display: flex; justify-content: center; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .codicon, -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .layout-controls-container .codicon { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .codicon { color: inherit; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .action-item { display: flex; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .badge { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .badge { margin-left: 8px; display: flex; align-items: center; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .action-item.icon .badge { margin-left: 0px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .badge .badge-content { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .badge .badge-content { padding: 3px 5px; border-radius: 11px; font-size: 9px; @@ -424,7 +420,7 @@ position: relative; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .action-item.icon .badge.compact { position: absolute; top: 0; bottom: 0; @@ -436,13 +432,13 @@ z-index: 2; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact .badge-content::before { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .action-item.icon .badge.compact .badge-content::before { mask-size: 12px; -webkit-mask-size: 12px; top: 2px; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .global-actions-container .monaco-action-bar .action-item.icon .badge.compact .badge-content { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-right > .action-toolbar-container .monaco-action-bar .action-item.icon .badge.compact .badge-content { position: absolute; top: 10px; right: 0px; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 07533e0e03955..2e34ebb58efc0 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -24,10 +24,10 @@ import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menuba import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Parts, IWorkbenchLayoutService, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; -import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Codicon } from 'vs/base/common/codicons'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; @@ -36,10 +36,21 @@ import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/comman import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { SimpleAccountActivityActionViewItem, SimpleGlobalActivityActionViewItem } from 'vs/workbench/browser/parts/globalCompositeBar'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { EDITOR_CORE_NAVIGATION_COMMANDS } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { EditorCommandsContextActionRunner } from 'vs/workbench/browser/parts/editor/editorTabsControl'; +import { IEditorCommandsContext, IToolbarActions } from 'vs/workbench/common/editor'; export class TitlebarPart extends Part implements ITitleService { @@ -77,9 +88,17 @@ export class TitlebarPart extends Part implements ITitleService { protected appIcon: HTMLElement | undefined; private appIconBadge: HTMLElement | undefined; protected menubar?: HTMLElement; - protected layoutControls: HTMLElement | undefined; protected lastLayoutDimensions: Dimension | undefined; + private actionToolBar!: WorkbenchToolBar; + private actionToolBarDisposable = this._register(new DisposableStore()); + private editorActionsChangeDisposable = this._register(new DisposableStore()); + private actionToolBarElement!: HTMLElement; + + private layoutToolbarMenu: IMenu | undefined; + private readonly editorToolbarMenuDisposables = this._register(new DisposableStore()); + private readonly layoutToolbarMenuDisposables = this._register(new DisposableStore()); + private hoverDelegate: IHoverDelegate; private readonly titleDisposables = this._register(new DisposableStore()); @@ -99,7 +118,11 @@ export class TitlebarPart extends Part implements ITitleService { @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, - @IHoverService hoverService: IHoverService + @IHoverService hoverService: IHoverService, + @IEditorGroupsService private editorGroupService: IEditorGroupsService, + @IEditorService private editorService: IEditorService, + @IMenuService private readonly menuService: IMenuService, + @IKeybindingService private readonly keybindingService: IKeybindingService, ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, window, 'main')); @@ -162,9 +185,17 @@ export class TitlebarPart extends Part implements ITitleService { } } - if (this.titleBarStyle !== 'native' && this.layoutControls && event.affectsConfiguration('workbench.layoutControl.enabled')) { - this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled); - this._onDidChange.fire(undefined); + if (this.titleBarStyle !== 'native' && this.actionToolBar) { + const affectsEditorActions = event.affectsConfiguration('workbench.editor.showEditorActionsInTitleBar') || event.affectsConfiguration('workbench.editor.showTabs'); + const affectsLayoutControl = event.affectsConfiguration('workbench.layoutControl.enabled'); + const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); + if (affectsEditorActions) { + this.createActionToolBar(); + } + if (affectsEditorActions || affectsLayoutControl || affectsActivityControl) { + this.createActionToolBarMenus({ editorActions: affectsEditorActions, layoutActions: affectsLayoutControl, activityActions: affectsActivityControl }); + this._onDidChange.fire(undefined); + } } if (event.affectsConfiguration(LayoutSettings.COMMAND_CENTER)) { @@ -274,33 +305,10 @@ export class TitlebarPart extends Part implements ITitleService { this.updateTitle(); if (this.titleBarStyle !== 'native') { - this.layoutControls = append(this.rightContent, $('div.layout-controls-container')); - this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled); - - this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, this.layoutControls, MenuId.LayoutControlMenu, { - contextMenu: MenuId.TitleBarContext, - toolbarOptions: { primaryGroup: () => true }, - actionViewItemProvider: action => { - return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); - } - })); - - const globalActionControls = append(this.rightContent, $('div.global-actions-container.show-control')); - - this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, globalActionControls, MenuId.TitleBarGlobalControlMenu, { - contextMenu: MenuId.TitleBarContext, - toolbarOptions: { primaryGroup: () => true }, - hiddenItemStrategy: HiddenItemStrategy.NoHide, - actionViewItemProvider: action => { - if (action.id === GLOBAL_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }); - } - if (action.id === ACCOUNTS_ACTIVITY_ID) { - return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }); - } - return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate }); - } - })); + // Create Toolbar Actions + this.actionToolBarElement = append(this.rightContent, $('div.action-toolbar-container')); + this.createActionToolBar(); + this.createActionToolBarMenus(); } let primaryControlLocation = isMacintosh ? 'left' : 'right'; @@ -352,6 +360,138 @@ export class TitlebarPart extends Part implements ITitleService { return this.element; } + private actionViewItemProvider(action: IAction): IActionViewItem | undefined { + // --- Activity Actions + if (action.id === GLOBAL_ACTIVITY_ID) { + return this.instantiationService.createInstance(SimpleGlobalActivityActionViewItem, { position: () => HoverPosition.BELOW }); + } + if (action.id === ACCOUNTS_ACTIVITY_ID) { + return this.instantiationService.createInstance(SimpleAccountActivityActionViewItem, { position: () => HoverPosition.BELOW }); + } + + // --- Editor Actions + const activeEditorPane = this.editorGroupService.activeGroup?.activeEditorPane; + if (activeEditorPane && activeEditorPane instanceof EditorPane) { + const result = activeEditorPane.getActionViewItem(action); + + if (result) { + return result; + } + } + + // Check extensions + return createActionViewItem(this.instantiationService, action, { hoverDelegate: this.hoverDelegate, menuAsChild: false }); + } + + protected getKeybinding(action: IAction): ResolvedKeybinding | undefined { + const editorPaneAwareContextKeyService = this.editorGroupService.activeGroup?.activeEditorPane?.scopedContextKeyService ?? this.contextKeyService; + return this.keybindingService.lookupKeybinding(action.id, editorPaneAwareContextKeyService); + } + + private createActionToolBar() { + // Creates the action tool bar. Depends on the configuration of the title bar menus + // Requires to be recreated whenever editor actions enablement changes + + this.actionToolBarDisposable.clear(); + + this.actionToolBar = this.instantiationService.createInstance(WorkbenchToolBar, this.actionToolBarElement, { + contextMenu: MenuId.TitleBarContext, + orientation: ActionsOrientation.HORIZONTAL, + ariaLabel: localize('ariaLabelTitleActions', "Title actions"), + getKeyBinding: action => this.getKeybinding(action), + overflowBehavior: { maxItems: 9, exempted: [ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID, ...EDITOR_CORE_NAVIGATION_COMMANDS] }, + anchorAlignmentProvider: () => AnchorAlignment.RIGHT, + telemetrySource: 'titlePart', + highlightToggledItems: this.editorActionsEnabled, // Only show toggled state for editor actions (Layout actions are not shown as toggled) + actionViewItemProvider: action => this.actionViewItemProvider(action) + }); + + this.actionToolBarDisposable.add(this.actionToolBar); + + if (this.editorActionsEnabled) { + this.actionToolBarDisposable.add(this.editorGroupService.onDidChangeActiveGroup(() => this.createActionToolBarMenus({ editorActions: true }))); + } + } + + private createActionToolBarMenus(update: true | { editorActions?: boolean; layoutActions?: boolean; activityActions?: boolean } = true) { + if (update === true) { + update = { editorActions: true, layoutActions: true, activityActions: true }; + } + + const updateToolBarActions = () => { + const actions: IToolbarActions = { primary: [], secondary: [] }; + + // --- Editor Actions + if (this.editorActionsEnabled) { + this.editorActionsChangeDisposable.clear(); + + const activeGroup = this.editorGroupService.activeGroup; + if (activeGroup) { // Can be undefined on startup + const editorActions = activeGroup.createEditorActions(this.editorActionsChangeDisposable); + + actions.primary.push(...editorActions.actions.primary); + actions.secondary.push(...editorActions.actions.secondary); + + this.editorActionsChangeDisposable.add(editorActions.onDidChange(() => updateToolBarActions())); + } + } + + // --- Layout Actions + if (this.layoutToolbarMenu) { + createAndFillInActionBarActions( + this.layoutToolbarMenu, + {}, + actions, + () => !this.editorActionsEnabled // Layout Actions in overflow menu when editor actions enabled in title bar + ); + } + + // --- Activity Actions + if (this.activityActionsEnabled) { + actions.primary.push(ACCOUNTS_ACTIVITY_TILE_ACTION); + actions.primary.push(GLOBAL_ACTIVITY_TITLE_ACTION); + } + + this.actionToolBar.setActions(prepareActions(actions.primary), prepareActions(actions.secondary)); + }; + + // Create/Update the menus which should be in the title tool bar + + if (update.editorActions) { + this.editorToolbarMenuDisposables.clear(); + + // The editor toolbar menu is handled by the editor group so we do not need to manage it here. + // However, depending on the active editor, we need to update the context and action runner of the toolbar menu. + if (this.editorActionsEnabled && this.editorService.activeEditor !== undefined) { + const context: IEditorCommandsContext = { groupId: this.editorGroupService.activeGroup.id }; + + this.actionToolBar.actionRunner = new EditorCommandsContextActionRunner(context); + this.actionToolBar.context = context; + this.editorToolbarMenuDisposables.add(this.actionToolBar.actionRunner); + } else { + this.actionToolBar.actionRunner = new ActionRunner(); + this.actionToolBar.context = {}; + + this.editorToolbarMenuDisposables.add(this.actionToolBar.actionRunner); + } + } + + if (update.layoutActions) { + this.layoutToolbarMenuDisposables.clear(); + + if (this.layoutControlEnabled) { + this.layoutToolbarMenu = this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService); + + this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu); + this.layoutToolbarMenuDisposables.add(this.layoutToolbarMenu.onDidChange(() => updateToolBarActions())); + } else { + this.layoutToolbarMenu = undefined; + } + } + + updateToolBarActions(); + } + override updateStyles(): void { super.updateStyles(); @@ -411,6 +551,14 @@ export class TitlebarPart extends Part implements ITitleService { return this.configurationService.getValue('workbench.layoutControl.enabled'); } + private get editorActionsEnabled(): boolean { + return this.editorGroupService.partOptions.showEditorActionsInTitleBar !== 'never' && this.editorGroupService.partOptions.showTabs === 'none'; + } + + private get activityActionsEnabled(): boolean { + return this.configurationService.getValue(LayoutSettings.ACTIVITY_BAR_LOCATION) === ActivityBarPosition.TOP; + } + protected get useCounterZoom(): boolean { // Prevent zooming behavior if any of the following conditions are met: // 1. Shrinking below the window control size (zoom < 1) @@ -455,12 +603,12 @@ export class TitlebarPart extends Part implements ITitleService { class ToogleConfigAction extends Action2 { - constructor(private readonly section: string, title: string, order: number) { + constructor(private readonly section: string, title: string, order: number, when?: ContextKeyExpression) { super({ id: `toggle.${section}`, title, toggled: ContextKeyExpr.equals(`config.${section}`, true), - menu: { id: MenuId.TitleBarContext, order } + menu: { id: MenuId.TitleBarContext, order, when } }); } @@ -482,3 +630,27 @@ registerAction2(class ToogleLayoutControl extends ToogleConfigAction { super('workbench.layoutControl.enabled', localize('toggle.layout', 'Layout Controls'), 2); } }); + +registerAction2(class ToogleEditorActionsControl extends ToogleConfigAction { + constructor() { + super('workbench.editor.showEditorActionsInTitleBar', localize('toggle.editorActions', 'Editor Actions'), 2, ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none')); + } +}); + +const ACCOUNTS_ACTIVITY_TILE_ACTION: IAction = { + id: ACCOUNTS_ACTIVITY_ID, + label: localize('accounts', "Accounts"), + tooltip: localize('accounts', "Accounts"), + class: undefined, + enabled: true, + run: function (): void { } +}; + +const GLOBAL_ACTIVITY_TITLE_ACTION: IAction = { + id: GLOBAL_ACTIVITY_ID, + label: localize('manage', "Manage"), + tooltip: localize('manage', "Manage"), + class: undefined, + enabled: true, + run: function (): void { } +}; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 55f359fea7a77..1c16044a04302 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -49,6 +49,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('showEditorTabs', "Controls whether opened editors should show as individual tabs, one single large tab or if the title area should not be shown."), 'default': 'multiple' }, + 'workbench.editor.showEditorActionsInTitleBar': { + 'type': 'string', + 'enum': ['noTabs', 'never'], + 'enumDescriptions': [ + localize('workbench.editor.showEditorActionsInTitleBar.noTabs', "Show editor actions in the editor title bar only when `#workbench.editor.showTabs#` is set to `none`. Otherwise, editor actions show up in the tab bar."), + localize('workbench.editor.showEditorActionsInTitleBar.never', "Do not show editor actions in the editor title bar"), + ], + 'markdownDescription': localize('showEditorActionsInTitleBar', "Controls whether editor actions are shown in the title bar. This value only applies when `#window.titleBarStyle#` is set to `custom`."), + 'default': 'noTabs' + }, 'workbench.editor.wrapTabs': { 'type': 'boolean', 'markdownDescription': localize('wrapTabs', "Controls whether tabs should be wrapped over multiple lines when exceeding available space or whether a scrollbar should appear instead. This value is ignored when `#workbench.editor.showTabs#` is not set to `multiple`."), diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 86ee1f00770ba..e9957f64ede7a 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1147,6 +1147,7 @@ interface IEditorPartConfiguration { splitOnDragAndDrop?: boolean; centeredLayoutFixedWidth?: boolean; doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off'; + showEditorActionsInTitleBar?: 'noTabs' | 'never'; limit?: IEditorPartLimitConfiguration; decorations?: IEditorPartDecorationsConfiguration; } @@ -1587,3 +1588,8 @@ export function createEditorOpenError(messageOrError: string | Error, actions: I return error; } + +export interface IToolbarActions { + readonly primary: IAction[]; + readonly secondary: IAction[]; +} diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index a254b1d90c0ee..bed1ad9c9b199 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -11,7 +11,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { isMacintosh, isWindows, isLinux, isNative } from 'vs/base/common/platform'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { TitlebarPart as BrowserTitleBarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -23,6 +23,9 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; import { NativeMenubarControl } from 'vs/workbench/electron-sandbox/parts/titlebar/menubarControl'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export class TitlebarPart extends BrowserTitleBarPart { private maxRestoreControl: HTMLElement | undefined; @@ -65,9 +68,13 @@ export class TitlebarPart extends BrowserTitleBarPart { @IContextKeyService contextKeyService: IContextKeyService, @IHostService hostService: IHostService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IHoverService hoverService: IHoverService + @IHoverService hoverService: IHoverService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IEditorService editorService: IEditorService, + @IMenuService menuService: IMenuService, + @IKeybindingService keybindingService: IKeybindingService ) { - super(contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService); + super(contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, hoverService, editorGroupService, editorService, menuService, keybindingService); this.environmentService = environmentService; } diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index bb84ae7f4e272..1c003b02a5876 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,16 +5,17 @@ import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, IActiveEditorChangeEvent, IFindEditorOptions } from 'vs/workbench/common/editor'; +import { IEditorPane, GroupIdentifier, EditorInputWithOptions, CloseDirection, IEditorPartOptions, IEditorPartOptionsChangeEvent, EditorsOrder, IVisibleEditorPane, IEditorCloseEvent, IUntypedEditorInput, isEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IMatchEditorOptions, IActiveEditorChangeEvent, IFindEditorOptions, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDimension } from 'vs/editor/common/core/dimension'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; import { IGroupModelChangeEvent } from 'vs/workbench/common/editor/editorGroupModel'; import { IRectangle } from 'vs/platform/window/common/window'; +import { IMenuChangeEvent } from 'vs/platform/actions/common/actions'; import { DeepPartial } from 'vs/base/common/types'; export const IEditorGroupsService = createDecorator('editorGroupsService'); @@ -511,6 +512,11 @@ export const enum OpenEditorContext { COPY_EDITOR = 3 } +export interface IActiveEditorActions { + readonly actions: IToolbarActions; + readonly onDidChange: Event; +} + export interface IEditorGroup { /** @@ -811,6 +817,11 @@ export interface IEditorGroup { * Move keyboard focus into the group. */ focus(): void; + + /** + * Create the editor actions for the current active editor. + */ + createEditorActions(disposables: DisposableStore): IActiveEditorActions; } export function isEditorGroup(obj: unknown): obj is IEditorGroup { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 65905fc8f4696..546e3a6e36075 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorsOrder, IFileEditorInput, IEditorFactoryRegistry, IEditorSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, EditorExtensions as Extensions, EditorInputCapabilities, IUntypedEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IActiveEditorChangeEvent, EditorPaneSelectionChangeReason, IEditorPaneSelection } from 'vs/workbench/common/editor'; +import { EditorInputWithOptions, IEditorIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, IEditorPane, IEditorCloseEvent, IEditorPartOptions, IRevertOptions, GroupIdentifier, EditorsOrder, IFileEditorInput, IEditorFactoryRegistry, IEditorSerializer, EditorExtensions, ISaveOptions, IMoveResult, ITextDiffEditorPane, IVisibleEditorPane, IEditorOpenContext, EditorExtensions as Extensions, EditorInputCapabilities, IUntypedEditorInput, IEditorWillMoveEvent, IEditorWillOpenEvent, IActiveEditorChangeEvent, EditorPaneSelectionChangeReason, IEditorPaneSelection, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorServiceImpl, IEditorGroupView, IEditorGroupsView, IEditorGroupTitleHeight, DEFAULT_EDITOR_PART_OPTIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import { IResolvedWorkingCopyBackup, IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; @@ -40,7 +40,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; -import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, IMenuChangeEvent } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, ITextSnapshot } from 'vs/editor/common/model'; @@ -953,6 +953,7 @@ export class TestEditorGroupView implements IEditorGroupView { toJSON(): object { return Object.create(null); } layout(_width: number, _height: number): void { } relayout() { } + createEditorActions(_menuDisposable: IDisposable): { actions: IToolbarActions; onDidChange: Event } { throw new Error('not implemented'); } } export class TestEditorGroupAccessor implements IEditorGroupsView {