From cb0d7ad9877f77942aff97ddefbe3076a2deb585 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 29 Apr 2022 16:42:40 +0200 Subject: [PATCH] Add a title menu which sits at the window title location, add special rendering for quick pick action --- src/vs/platform/actions/common/actions.ts | 1 + .../browser/actions/quickAccessActions.ts | 63 ++++++++++--------- .../parts/editor/editor.contribution.ts | 3 + .../parts/titlebar/media/titlebarpart.css | 16 +++++ .../browser/parts/titlebar/titlebarPart.ts | 62 +++++++++++++++++- .../browser/workbench.contribution.ts | 5 ++ 6 files changed, 118 insertions(+), 32 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index e0b47fb6ddc..e662dc4f73b 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -67,6 +67,7 @@ export class MenuId { static readonly ExplorerContext = new MenuId('ExplorerContext'); static readonly ExtensionContext = new MenuId('ExtensionContext'); static readonly GlobalActivity = new MenuId('GlobalActivity'); + static readonly TitleMenu = new MenuId('TitleMenu'); static readonly LayoutControlMenuSubmenu = new MenuId('LayoutControlMenuSubmenu'); static readonly LayoutControlMenu = new MenuId('LayoutControlMenu'); static readonly MenubarMainMenu = new MenuId('MenubarMainMenu'); diff --git a/src/vs/workbench/browser/actions/quickAccessActions.ts b/src/vs/workbench/browser/actions/quickAccessActions.ts index f587eb23bf8..fadc1947d1c 100644 --- a/src/vs/workbench/browser/actions/quickAccessActions.ts +++ b/src/vs/workbench/browser/actions/quickAccessActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { MenuRegistry, MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight, IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput'; @@ -22,21 +22,6 @@ const globalQuickAccessKeybinding = { mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyP, secondary: undefined } }; -const QUICKACCESS_ACTION_ID = 'workbench.action.quickOpen'; - -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { id: QUICKACCESS_ACTION_ID, title: { value: localize('quickOpen', "Go to File..."), original: 'Go to File...' } } -}); - -KeybindingsRegistry.registerKeybindingRule({ - id: QUICKACCESS_ACTION_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: globalQuickAccessKeybinding.primary, - secondary: globalQuickAccessKeybinding.secondary, - mac: globalQuickAccessKeybinding.mac -}); - KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.closeQuickOpen', weight: KeybindingWeight.WorkbenchContrib, @@ -131,21 +116,39 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -CommandsRegistry.registerCommand({ - id: QUICKACCESS_ACTION_ID, - handler: async function (accessor: ServicesAccessor, prefix: unknown) { - const quickInputService = accessor.get(IQuickInputService); - - quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined, { preserveValue: typeof prefix === 'string' /* preserve as is if provided */ }); - }, - description: { - description: `Quick access`, - args: [{ - name: 'prefix', - schema: { - 'type': 'string' +registerAction2(class QuickAccessAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.quickOpen', + title: { + value: localize('quickOpen', "Go to File..."), + original: 'Go to File...' + }, + description: { + description: `Quick access`, + args: [{ + name: 'prefix', + schema: { + 'type': 'string' + } + }] + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: globalQuickAccessKeybinding.primary, + secondary: globalQuickAccessKeybinding.secondary, + mac: globalQuickAccessKeybinding.mac + }, + f1: true, + menu: { + id: MenuId.TitleMenu } - }] + }); + } + + run(accessor: ServicesAccessor, prefix: undefined): void { + const quickInputService = accessor.get(IQuickInputService); + quickInputService.quickAccess.show(typeof prefix === 'string' ? prefix : undefined, { preserveValue: typeof prefix === 'string' /* preserve as is if provided */ }); } }); diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 318e7deb266..8e0c175392e 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -314,6 +314,9 @@ if (isMacintosh) { }); } +MenuRegistry.appendMenuItem(MenuId.TitleMenu, { command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, icon: Codicon.arrowLeft } }); +MenuRegistry.appendMenuItem(MenuId.TitleMenu, { command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, icon: Codicon.arrowRight } }); + // Empty Editor Group Toolbar MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: UNLOCK_GROUP_COMMAND_ID, title: localize('unlockGroupAction', "Unlock Group"), icon: Codicon.lock }, group: 'navigation', order: 10, when: ActiveEditorGroupLockedContext }); MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroup, { command: { id: CLOSE_EDITOR_GROUP_COMMAND_ID, title: localize('closeGroupAction', "Close Group"), icon: Codicon.close }, group: 'navigation', order: 20 }); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 2471bcee7f8..2ed9789a00a 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -33,6 +33,18 @@ -webkit-app-region: drag; } +.monaco-workbench .part.titlebar>.titlebar-container>.title-menu .quickopen .action-label { + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-dropdown-border); + display: inline-block; + text-align: center; + height: 14px; + width: 50vw; + max-width: calc(100vw - 200px); + margin: 2px 4px; + line-height: 14px; +} + .monaco-workbench .part.titlebar > .titlebar-container > .window-title { flex: 0 1 auto; font-size: 12px; @@ -44,6 +56,10 @@ zoom: 1; /* prevent zooming */ } +.monaco-workbench .part.titlebar > .titlebar-container.enable-title-menu > .window-title { + display: none; +} + /* Windows/Linux: Rules for custom title (icon, window controls) */ .monaco-workbench.web .part.titlebar > .titlebar-container, .monaco-workbench.windows .part.titlebar > .titlebar-container, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 52771febbf5..ed4c0687828 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -15,7 +15,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction, toAction } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { EditorResourceAccessor, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -35,7 +35,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, IMenu, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -45,6 +45,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; export class TitlebarPart extends Part implements ITitleService { @@ -77,6 +78,8 @@ export class TitlebarPart extends Part implements ITitleService { protected windowControls: HTMLElement | undefined; private layoutToolbar: ToolBar | undefined; protected lastLayoutDimensions: Dimension | undefined; + + private readonly titleMenuDisposables = this._register(new DisposableStore()); private titleBarStyle: 'native' | 'custom'; private pendingTitle: string | undefined; @@ -150,6 +153,10 @@ export class TitlebarPart extends Part implements ITitleService { } } + if (event.affectsConfiguration('window.titleMenu')) { + this.updateTitleMenu(); + } + if (this.titleBarStyle !== 'native' && this.windowControls && event.affectsConfiguration('workbench.layoutControl.enabled')) { this.windowControls.classList.toggle('show-layout-control', this.layoutControlEnabled); } @@ -364,6 +371,54 @@ export class TitlebarPart extends Part implements ITitleService { this._register(this.customMenubar.onVisibilityChange(e => this.onMenubarVisibilityChanged(e))); } + private updateTitleMenu(): void { + this.titleMenuDisposables.clear(); + const enableTitleMenu = this.configurationService.getValue('window.titleMenu'); + this.rootContainer.classList.toggle('enable-title-menu', enableTitleMenu); + if (enableTitleMenu) { + + const that = this; + const titleMenuContainer = append(this.rootContainer, $('div.title-menu')); + const titleToolbar = new ToolBar(titleMenuContainer, this.contextMenuService, { + actionViewItemProvider: (action) => { + if (action instanceof MenuItemAction && action.item.id === 'workbench.action.quickOpen') { + return new class extends ActionViewItem { + constructor() { + super(undefined, action); + } + + override render(container: HTMLElement): void { + super.render(container); + if (this.label) { + container.classList.add('quickopen'); + this.label.innerText = localize('quick-open', "Search: {0}", this.getWorkspaceName()); + this.label.title = that.getWindowTitle(); + } + } + + private getWorkspaceName(): string { + const workspace = that.contextService.getWorkspace(); + return that.labelService.getWorkspaceLabel(workspace); + } + }; + } + return undefined; + } + }); + const titleMenu = this.titleMenuDisposables.add(this.menuService.createMenu(MenuId.TitleMenu, this.contextKeyService)); + const titleMenuDisposables = this.titleMenuDisposables.add(new DisposableStore()); + const updateTitleMenu = () => { + titleMenuDisposables.clear(); + const actions: IAction[] = []; + titleMenuDisposables.add(createAndFillInContextMenuActions(titleMenu, undefined, actions)); + titleToolbar.setActions(actions); + }; + this.titleMenuDisposables.add(titleMenu.onDidChange(updateTitleMenu)); + this.titleMenuDisposables.add(toDisposable(() => titleMenuContainer.remove())); + updateTitleMenu(); + } + } + override createContentArea(parent: HTMLElement): HTMLElement { this.element = parent; this.rootContainer = append(parent, $('.titlebar-container')); @@ -395,6 +450,9 @@ export class TitlebarPart extends Part implements ITitleService { this.installMenubar(); } + // Title Menu + this.updateTitleMenu(); + // Title this.title = append(this.rootContainer, $('div.window-title')); if (this.pendingTitle) { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 903ca298a14..c64206ac405 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -533,6 +533,11 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': isMacintosh ? ' \u2014 ' : ' - ', 'markdownDescription': localize("window.titleSeparator", "Separator used by `window.title`.") }, + 'window.titleMenu': { + type: 'boolean', + default: false, + description: localize('window.titleMenu', "Show window title as menu") + }, 'window.menuBarVisibility': { 'type': 'string', 'enum': ['classic', 'visible', 'toggle', 'hidden', 'compact'],