Skip to content

Commit fb7991a

Browse files
author
Benjamin Pasero
committed
debt - bring back macOS custom title menu
1 parent afde791 commit fb7991a

4 files changed

Lines changed: 73 additions & 63 deletions

File tree

src/vs/platform/actions/common/actions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export const enum MenuId {
9393
SearchContext,
9494
StatusBarWindowIndicatorMenu,
9595
TouchBarContext,
96+
TitleBarContext,
9697
ViewItemContext,
9798
ViewTitle,
9899
CommentThreadTitle,

src/vs/workbench/browser/parts/titlebar/titlebarPart.ts

Lines changed: 22 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,17 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import 'vs/css!./media/titlebarpart';
7-
import { dirname, posix } from 'vs/base/common/path';
87
import * as resources from 'vs/base/common/resources';
98
import { Part } from 'vs/workbench/browser/part';
109
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
1110
import { getZoomFactor } from 'vs/base/browser/browser';
12-
import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
11+
import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
1312
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
1413
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
15-
import { IAction, Action } from 'vs/base/common/actions';
14+
import { IAction } from 'vs/base/common/actions';
1615
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
1716
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
18-
import { DisposableStore } from 'vs/base/common/lifecycle';
17+
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
1918
import * as nls from 'vs/nls';
2019
import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor';
2120
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -29,14 +28,17 @@ import { trim } from 'vs/base/common/strings';
2928
import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
3029
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
3130
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
32-
import { template, getBaseLabel } from 'vs/base/common/labels';
31+
import { template } from 'vs/base/common/labels';
3332
import { ILabelService } from 'vs/platform/label/common/label';
3433
import { Event, Emitter } from 'vs/base/common/event';
3534
import { IStorageService } from 'vs/platform/storage/common/storage';
3635
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
3736
import { RunOnceScheduler } from 'vs/base/common/async';
3837
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
3938
import { Schemas } from 'vs/base/common/network';
39+
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
40+
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
41+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
4042

4143
export class TitlebarPart extends Part implements ITitleService {
4244

@@ -71,7 +73,6 @@ export class TitlebarPart extends Part implements ITitleService {
7173
private lastLayoutDimensions: Dimension;
7274

7375
private pendingTitle: string;
74-
private representedFileName: string;
7576

7677
private isInactive: boolean;
7778

@@ -80,22 +81,27 @@ export class TitlebarPart extends Part implements ITitleService {
8081

8182
private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0));
8283

84+
private contextMenu: IMenu;
85+
8386
constructor(
8487
@IContextMenuService private readonly contextMenuService: IContextMenuService,
8588
@IWindowService private readonly windowService: IWindowService,
8689
@IConfigurationService private readonly configurationService: IConfigurationService,
87-
@IWindowsService private readonly windowsService: IWindowsService,
8890
@IEditorService private readonly editorService: IEditorService,
8991
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
9092
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
9193
@IInstantiationService private readonly instantiationService: IInstantiationService,
9294
@IThemeService themeService: IThemeService,
9395
@ILabelService private readonly labelService: ILabelService,
9496
@IStorageService storageService: IStorageService,
95-
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
97+
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
98+
@IMenuService menuService: IMenuService,
99+
@IContextKeyService contextKeyService: IContextKeyService
96100
) {
97101
super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
98102

103+
this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));
104+
99105
this.registerListeners();
100106
}
101107

@@ -181,9 +187,6 @@ export class TitlebarPart extends Part implements ITitleService {
181187

182188
// Apply to window
183189
this.windowService.setRepresentedFilename(path);
184-
185-
// Keep for context menu
186-
this.representedFileName = path;
187190
}
188191

189192
private doUpdateTitle(): void {
@@ -502,44 +505,16 @@ export class TitlebarPart extends Part implements ITitleService {
502505
const event = new StandardMouseEvent(e);
503506
const anchor = { x: event.posx, y: event.posy };
504507

505-
// Show menu
506-
const actions = this.getContextMenuActions();
507-
if (actions.length) {
508-
this.contextMenuService.showContextMenu({
509-
getAnchor: () => anchor,
510-
getActions: () => actions,
511-
onHide: () => actions.forEach(a => a.dispose())
512-
});
513-
}
514-
}
515-
516-
private getContextMenuActions(): IAction[] {
508+
// Fill in contributed actions
517509
const actions: IAction[] = [];
510+
const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService);
518511

519-
if (this.representedFileName) {
520-
const segments = this.representedFileName.split(posix.sep);
521-
for (let i = segments.length; i > 0; i--) {
522-
const isFile = (i === segments.length);
523-
524-
let pathOffset = i;
525-
if (!isFile) {
526-
pathOffset++; // for segments which are not the file name we want to open the folder
527-
}
528-
529-
const path = segments.slice(0, pathOffset).join(posix.sep);
530-
531-
let label: string;
532-
if (!isFile) {
533-
label = getBaseLabel(dirname(path));
534-
} else {
535-
label = getBaseLabel(path);
536-
}
537-
538-
actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService));
539-
}
540-
}
541-
542-
return actions;
512+
// Show it
513+
this.contextMenuService.showContextMenu({
514+
getAnchor: () => anchor,
515+
getActions: () => actions,
516+
onHide: () => dispose(actionsDisposable)
517+
});
543518
}
544519

545520
private adjustTitleMarginToCenter(): void {
@@ -604,19 +579,6 @@ export class TitlebarPart extends Part implements ITitleService {
604579
}
605580
}
606581

607-
class ShowItemInFolderAction extends Action {
608-
609-
constructor(private path: string, label: string, private windowsService: IWindowsService) {
610-
super('showItemInFolder.action.id', label);
611-
}
612-
613-
run(): Promise<void> {
614-
if (this.path && this.windowsService) { }
615-
return Promise.resolve();
616-
// return this.windowsService.showItemInFolder(URI.file(this.path));
617-
}
618-
}
619-
620582
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
621583
const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
622584
if (titlebarActiveFg) {

src/vs/workbench/electron-browser/window.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
1919
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
2020
import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService';
2121
import * as browser from 'vs/base/browser/browser';
22-
import { ICommandService } from 'vs/platform/commands/common/commands';
22+
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
2323
import { IResourceInput } from 'vs/platform/editor/common/editor';
2424
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService';
2525
import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron';
2626
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
27-
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
27+
import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
2828
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2929
import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
3030
import { RunOnceScheduler } from 'vs/base/common/async';
@@ -56,6 +56,8 @@ import { withNullAsUndefined } from 'vs/base/common/types';
5656
import { IOpenerService } from 'vs/platform/opener/common/opener';
5757
import { Schemas } from 'vs/base/common/network';
5858
import { IElectronService } from 'vs/platform/electron/node/electron';
59+
import { posix, dirname } from 'vs/base/common/path';
60+
import { getBaseLabel } from 'vs/base/common/labels';
5961

6062
const TextInputActions: IAction[] = [
6163
new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))),
@@ -74,6 +76,8 @@ export class ElectronWindow extends Disposable {
7476
private readonly touchBarDisposables = this._register(new DisposableStore());
7577
private lastInstalledTouchedBar: ICommandAction[][] | undefined;
7678

79+
private customTitleContextMenuDisposable = this._register(new DisposableStore());
80+
7781
private previousConfiguredZoomLevel: number | undefined;
7882

7983
private addFoldersScheduler: RunOnceScheduler;
@@ -245,6 +249,11 @@ export class ElectronWindow extends Disposable {
245249

246250
this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor));
247251
}
252+
253+
// macOS custom title menu
254+
if (isMacintosh) {
255+
this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu()));
256+
}
248257
}
249258

250259
private onDidVisibleEditorsChange(): void {
@@ -308,6 +317,43 @@ export class ElectronWindow extends Disposable {
308317
}
309318
}
310319

320+
private provideCustomTitleContextMenu(): void {
321+
322+
// Clear old menu
323+
this.customTitleContextMenuDisposable.clear();
324+
325+
// Provide new menu if a file is opened and we are on a custom title
326+
const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file });
327+
if (!fileResource || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') {
328+
return;
329+
}
330+
331+
// Split up filepath into segments
332+
const filePath = fileResource.fsPath;
333+
const segments = filePath.split(posix.sep);
334+
for (let i = segments.length; i > 0; i--) {
335+
const isFile = (i === segments.length);
336+
337+
let pathOffset = i;
338+
if (!isFile) {
339+
pathOffset++; // for segments which are not the file name we want to open the folder
340+
}
341+
342+
const path = segments.slice(0, pathOffset).join(posix.sep);
343+
344+
let label: string;
345+
if (!isFile) {
346+
label = getBaseLabel(dirname(path));
347+
} else {
348+
label = getBaseLabel(path);
349+
}
350+
351+
const commandId = `workbench.action.revealPathInFinder${i}`;
352+
this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path)));
353+
this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i }));
354+
}
355+
}
356+
311357
private create(): void {
312358

313359
// Native menu controller

src/vs/workbench/services/title/common/titleService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ITitleProperties {
1414
}
1515

1616
export interface ITitleService {
17+
1718
_serviceBrand: undefined;
1819

1920
/**
@@ -25,4 +26,4 @@ export interface ITitleService {
2526
* Update some environmental title properties.
2627
*/
2728
updateProperties(properties: ITitleProperties): void;
28-
}
29+
}

0 commit comments

Comments
 (0)