From fb7991a3fd5392e3f3abb5d2f5fd7e5a89ab4445 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 18 Sep 2019 15:03:24 +0200 Subject: [PATCH] debt - bring back macOS custom title menu --- src/vs/platform/actions/common/actions.ts | 1 + .../browser/parts/titlebar/titlebarPart.ts | 82 +++++-------------- src/vs/workbench/electron-browser/window.ts | 50 ++++++++++- .../services/title/common/titleService.ts | 3 +- 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 7a409b98a5e..c7bcd9b066c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -93,6 +93,7 @@ export const enum MenuId { SearchContext, StatusBarWindowIndicatorMenu, TouchBarContext, + TitleBarContext, ViewItemContext, ViewTitle, CommentThreadTitle, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f222733af9d..394f522bd67 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import { dirname, posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction } 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 } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,7 +28,7 @@ import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { template, getBaseLabel } from 'vs/base/common/labels'; +import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,6 +36,9 @@ import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/bro import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class TitlebarPart extends Part implements ITitleService { @@ -71,7 +73,6 @@ export class TitlebarPart extends Part implements ITitleService { private lastLayoutDimensions: Dimension; private pendingTitle: string; - private representedFileName: string; private isInactive: boolean; @@ -80,11 +81,12 @@ export class TitlebarPart extends Part implements ITitleService { private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private contextMenu: IMenu; + constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -92,10 +94,14 @@ export class TitlebarPart extends Part implements ITitleService { @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); + this.registerListeners(); } @@ -181,9 +187,6 @@ export class TitlebarPart extends Part implements ITitleService { // Apply to window this.windowService.setRepresentedFilename(path); - - // Keep for context menu - this.representedFileName = path; } private doUpdateTitle(): void { @@ -502,44 +505,16 @@ export class TitlebarPart extends Part implements ITitleService { const event = new StandardMouseEvent(e); const anchor = { x: event.posx, y: event.posy }; - // Show menu - const actions = this.getContextMenuActions(); - if (actions.length) { - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => actions, - onHide: () => actions.forEach(a => a.dispose()) - }); - } - } - - private getContextMenuActions(): IAction[] { + // Fill in contributed actions const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService); - if (this.representedFileName) { - const segments = this.representedFileName.split(posix.sep); - for (let i = segments.length; i > 0; i--) { - const isFile = (i === segments.length); - - let pathOffset = i; - if (!isFile) { - pathOffset++; // for segments which are not the file name we want to open the folder - } - - const path = segments.slice(0, pathOffset).join(posix.sep); - - let label: string; - if (!isFile) { - label = getBaseLabel(dirname(path)); - } else { - label = getBaseLabel(path); - } - - actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService)); - } - } - - return actions; + // Show it + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => actions, + onHide: () => dispose(actionsDisposable) + }); } private adjustTitleMarginToCenter(): void { @@ -604,19 +579,6 @@ export class TitlebarPart extends Part implements ITitleService { } } -class ShowItemInFolderAction extends Action { - - constructor(private path: string, label: string, private windowsService: IWindowsService) { - super('showItemInFolder.action.id', label); - } - - run(): Promise { - if (this.path && this.windowsService) { } - return Promise.resolve(); - // return this.windowsService.showItemInFolder(URI.file(this.path)); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 80eadd8163a..31cb64f2d5b 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -19,12 +19,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -56,6 +56,8 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { posix, dirname } from 'vs/base/common/path'; +import { getBaseLabel } from 'vs/base/common/labels'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -74,6 +76,8 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private previousConfiguredZoomLevel: number | undefined; private addFoldersScheduler: RunOnceScheduler; @@ -245,6 +249,11 @@ export class ElectronWindow extends Disposable { this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } + + // macOS custom title menu + if (isMacintosh) { + this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu())); + } } private onDidVisibleEditorsChange(): void { @@ -308,6 +317,43 @@ export class ElectronWindow extends Disposable { } } + private provideCustomTitleContextMenu(): void { + + // Clear old menu + this.customTitleContextMenuDisposable.clear(); + + // Provide new menu if a file is opened and we are on a custom title + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); + if (!fileResource || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + // Split up filepath into segments + const filePath = fileResource.fsPath; + const segments = filePath.split(posix.sep); + for (let i = segments.length; i > 0; i--) { + const isFile = (i === segments.length); + + let pathOffset = i; + if (!isFile) { + pathOffset++; // for segments which are not the file name we want to open the folder + } + + const path = segments.slice(0, pathOffset).join(posix.sep); + + let label: string; + if (!isFile) { + label = getBaseLabel(dirname(path)); + } else { + label = getBaseLabel(path); + } + + const commandId = `workbench.action.revealPathInFinder${i}`; + this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path))); + this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i })); + } + } + private create(): void { // Native menu controller diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index b3476f94deb..a827857a797 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -14,6 +14,7 @@ export interface ITitleProperties { } export interface ITitleService { + _serviceBrand: undefined; /** @@ -25,4 +26,4 @@ export interface ITitleService { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; -} \ No newline at end of file +} -- GitLab