titlebarPart.ts 22.5 KB
Newer Older
B
Benjamin Pasero 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/titlebarpart';
B
Benjamin Pasero 已提交
7
import * as paths from 'vs/base/common/paths';
B
Benjamin Pasero 已提交
8
import { Part } from 'vs/workbench/browser/part';
9
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
B
Benjamin Pasero 已提交
10
import { getZoomFactor } from 'vs/base/browser/browser';
11
import { IWindowService, IWindowsService, MenuBarVisibility } from 'vs/platform/windows/common/windows';
B
Benjamin Pasero 已提交
12 13 14 15
import * as errors from 'vs/base/common/errors';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAction, Action } from 'vs/base/common/actions';
16
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
17
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
18
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
19
import * as nls from 'vs/nls';
B
Benjamin Pasero 已提交
20
import { EditorInput, toResource, Verbosity } from 'vs/workbench/common/editor';
21
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
22
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
S
SteVen Batten 已提交
23
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
24
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme';
B
Benjamin Pasero 已提交
25
import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform';
26
import { URI } from 'vs/base/common/uri';
R
Ryan Adolf 已提交
27
import { Color } from 'vs/base/common/color';
B
Benjamin Pasero 已提交
28
import { trim } from 'vs/base/common/strings';
S
SteVen Batten 已提交
29
import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, getComputedStyle } from 'vs/base/browser/dom';
30
import { MenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
S
SteVen Batten 已提交
31
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
I
isidor 已提交
32
import { template, getBaseLabel } from 'vs/base/common/labels';
I
isidor 已提交
33
import { ILabelService } from 'vs/platform/label/common/label';
34
import { Event } from 'vs/base/common/event';
B
Benjamin Pasero 已提交
35
import { IStorageService } from 'vs/platform/storage/common/storage';
B
Benjamin Pasero 已提交
36 37 38

export class TitlebarPart extends Part implements ITitleService {

B
Benjamin Pasero 已提交
39
	_serviceBrand: any;
B
Benjamin Pasero 已提交
40

41
	private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
42
	private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
43 44 45
	private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
	private static readonly TITLE_DIRTY = '\u25cf ';
	private static readonly TITLE_SEPARATOR = isMacintosh ? '' : ' - '; // macOS uses special - separator
46

47 48 49 50 51 52
	private titleContainer: HTMLElement;
	private title: HTMLElement;
	private dragRegion: HTMLElement;
	private windowControls: HTMLElement;
	private maxRestoreControl: HTMLElement;
	private appIcon: HTMLElement;
53
	private menubarPart: MenubarControl;
54 55
	private menubar: HTMLElement;
	private resizer: HTMLElement;
B
Benjamin Pasero 已提交
56

B
Benjamin Pasero 已提交
57
	private pendingTitle: string;
B
Benjamin Pasero 已提交
58
	private representedFileName: string;
59 60 61 62 63

	private initialSizing: {
		titleFontSize?: number;
		titlebarHeight?: number;
		controlsWidth?: number;
64
		appIconSize?: number;
65
		appIconWidth?: number;
B
Benjamin Pasero 已提交
66
	} = Object.create(null);
B
Benjamin Pasero 已提交
67

B
Benjamin Pasero 已提交
68 69
	private isInactive: boolean;

70
	private properties: ITitleProperties;
71 72
	private activeEditorListeners: IDisposable[];

73 74
	constructor(
		id: string,
B
Benjamin Pasero 已提交
75 76
		@IContextMenuService private contextMenuService: IContextMenuService,
		@IWindowService private windowService: IWindowService,
77 78
		@IConfigurationService private configurationService: IConfigurationService,
		@IWindowsService private windowsService: IWindowsService,
B
Benjamin Pasero 已提交
79
		@IEditorService private editorService: IEditorService,
80
		@IEnvironmentService private environmentService: IEnvironmentService,
B
Benjamin Pasero 已提交
81
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
S
SteVen Batten 已提交
82
		@IInstantiationService private instantiationService: IInstantiationService,
I
isidor 已提交
83
		@IThemeService themeService: IThemeService,
B
Benjamin Pasero 已提交
84
		@ILabelService private labelService: ILabelService,
85
		@IStorageService storageService: IStorageService
86
	) {
87
		super(id, { hasTitle: false }, themeService, storageService);
88

89
		this.properties = { isPure: true, isAdmin: false };
90 91
		this.activeEditorListeners = [];

92 93 94 95
		this.registerListeners();
	}

	private registerListeners(): void {
96
		this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
B
Benjamin Pasero 已提交
97 98
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
		this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
B
Benjamin Pasero 已提交
99 100 101 102
		this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.doUpdateTitle()));
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.doUpdateTitle()));
		this._register(this.contextService.onDidChangeWorkspaceName(() => this.doUpdateTitle()));
		this._register(this.labelService.onDidRegisterFormatter(() => this.doUpdateTitle()));
103 104
	}

B
Benjamin Pasero 已提交
105 106 107 108 109 110 111 112 113 114
	private onBlur(): void {
		this.isInactive = true;
		this.updateStyles();
	}

	private onFocus(): void {
		this.isInactive = false;
		this.updateStyles();
	}

115 116
	private onConfigurationChanged(event: IConfigurationChangeEvent): void {
		if (event.affectsConfiguration('window.title')) {
B
Benjamin Pasero 已提交
117
			this.doUpdateTitle();
118 119 120
		}
	}

S
SteVen Batten 已提交
121 122 123 124 125
	private onMenubarVisibilityChanged(visible: boolean) {
		if (isWindows || isLinux) {
			// Hide title when toggling menu bar
			if (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'toggle' && visible) {
				// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
126 127
				hide(this.dragRegion);
				setTimeout(() => show(this.dragRegion), 50);
S
SteVen Batten 已提交
128
			}
S
SteVen Batten 已提交
129 130

			this.adjustTitleMarginToCenter();
S
SteVen Batten 已提交
131
		}
132 133
	}

S
SteVen Batten 已提交
134 135 136 137 138 139 140 141 142 143
	private onMenubarFocusChanged(focused: boolean) {
		if (isWindows || isLinux) {
			if (focused) {
				hide(this.dragRegion);
			} else {
				show(this.dragRegion);
			}
		}
	}

144 145 146 147
	onMenubarVisibilityChange(): Event<boolean> {
		return this.menubarPart.onVisibilityChange;
	}

B
Benjamin Pasero 已提交
148
	private onActiveEditorChange(): void {
149 150 151 152 153 154

		// Dispose old listeners
		dispose(this.activeEditorListeners);
		this.activeEditorListeners = [];

		// Calculate New Window Title
B
Benjamin Pasero 已提交
155
		this.doUpdateTitle();
156 157

		// Apply listener for dirty and label changes
B
Benjamin Pasero 已提交
158 159
		const activeEditor = this.editorService.activeEditor;
		if (activeEditor instanceof EditorInput) {
B
Benjamin Pasero 已提交
160 161
			this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => this.doUpdateTitle()));
			this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => this.doUpdateTitle()));
162
		}
B
Benjamin Pasero 已提交
163 164 165 166 167 168 169 170 171 172 173 174 175 176

		// Represented File Name
		this.updateRepresentedFilename();
	}

	private updateRepresentedFilename(): void {
		const file = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: 'file' });
		const path = file ? file.fsPath : '';

		// Apply to window
		this.windowService.setRepresentedFilename(path);

		// Keep for context menu
		this.representedFileName = path;
177 178
	}

B
Benjamin Pasero 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	private doUpdateTitle(): void {
		const title = this.getWindowTitle();

		// Always set the native window title to identify us properly to the OS
		let nativeTitle = title;
		if (!trim(nativeTitle)) {
			nativeTitle = this.environmentService.appNameLong;
		}
		window.document.title = nativeTitle;

		// Apply custom title if we can
		if (this.title) {
			this.title.innerText = title;
		} else {
			this.pendingTitle = title;
		}
S
SteVen Batten 已提交
195

196 197 198
		if (isWindows || isLinux) {
			this.adjustTitleMarginToCenter();
		}
B
Benjamin Pasero 已提交
199 200
	}

201 202 203
	private getWindowTitle(): string {
		let title = this.doGetWindowTitle();

204
		if (this.properties.isAdmin) {
B
Benjamin Pasero 已提交
205
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
206 207 208
		}

		if (!this.properties.isPure) {
B
Benjamin Pasero 已提交
209
			title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
210 211 212
		}

		if (this.environmentService.isExtensionDevelopment) {
B
Benjamin Pasero 已提交
213
			title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
214 215 216 217 218
		}

		return title;
	}

B
Benjamin Pasero 已提交
219
	updateProperties(properties: ITitleProperties): void {
220 221 222 223 224 225 226
		const isAdmin = typeof properties.isAdmin === 'boolean' ? properties.isAdmin : this.properties.isAdmin;
		const isPure = typeof properties.isPure === 'boolean' ? properties.isPure : this.properties.isPure;

		if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure) {
			this.properties.isAdmin = isAdmin;
			this.properties.isPure = isPure;

B
Benjamin Pasero 已提交
227
			this.doUpdateTitle();
228 229 230
		}
	}

231 232 233
	/**
	 * Possible template values:
	 *
B
Benjamin Pasero 已提交
234 235 236
	 * {activeEditorLong}: e.g. /Users/Development/myProject/myFolder/myFile.txt
	 * {activeEditorMedium}: e.g. myFolder/myFile.txt
	 * {activeEditorShort}: e.g. myFile.txt
237
	 * {rootName}: e.g. myFolder1, myFolder2, myFolder3
238
	 * {rootPath}: e.g. /Users/Development/myProject
239 240
	 * {folderName}: e.g. myFolder
	 * {folderPath}: e.g. /Users/Development/myFolder
241 242 243 244 245
	 * {appName}: e.g. VS Code
	 * {dirty}: indiactor
	 * {separator}: conditional separator
	 */
	private doGetWindowTitle(): string {
B
Benjamin Pasero 已提交
246
		const editor = this.editorService.activeEditor;
B
Benjamin Pasero 已提交
247
		const workspace = this.contextService.getWorkspace();
248

249
		let root: URI;
250
		if (workspace.configuration) {
251
			root = workspace.configuration;
252 253
		} else if (workspace.folders.length) {
			root = workspace.folders[0].uri;
254 255 256 257
		}

		// Compute folder resource
		// Single Root Workspace: always the root single workspace in this case
258
		// Otherwise: root folder of the currently active file if any
B
Benjamin Pasero 已提交
259
		let folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: true }));
260

261
		// Variables
B
Benjamin Pasero 已提交
262 263 264
		const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : '';
		const activeEditorMedium = editor ? editor.getTitle(Verbosity.MEDIUM) : activeEditorShort;
		const activeEditorLong = editor ? editor.getTitle(Verbosity.LONG) : activeEditorMedium;
I
isidor 已提交
265
		const rootName = this.labelService.getWorkspaceLabel(workspace);
I
isidor 已提交
266
		const rootPath = root ? this.labelService.getUriLabel(root) : '';
B
Benjamin Pasero 已提交
267
		const folderName = folder ? folder.name : '';
I
isidor 已提交
268
		const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
B
Benjamin Pasero 已提交
269
		const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
270 271
		const appName = this.environmentService.appNameLong;
		const separator = TitlebarPart.TITLE_SEPARATOR;
272
		const titleTemplate = this.configurationService.getValue<string>('window.title');
273

I
isidor 已提交
274
		return template(titleTemplate, {
B
Benjamin Pasero 已提交
275 276 277
			activeEditorShort,
			activeEditorLong,
			activeEditorMedium,
278 279
			rootName,
			rootPath,
280 281
			folderName,
			folderPath,
282 283 284 285 286 287
			dirty,
			appName,
			separator: { label: separator }
		});
	}

B
Benjamin Pasero 已提交
288
	createContentArea(parent: HTMLElement): HTMLElement {
289
		this.titleContainer = parent;
B
Benjamin Pasero 已提交
290

291
		// Draggable region that we can manipulate for #52522
292
		this.dragRegion = append(this.titleContainer, $('div.titlebar-drag-region'));
293

B
Benjamin Pasero 已提交
294
		// App Icon (Windows/Linux)
R
Ryan Adolf 已提交
295
		if (!isMacintosh) {
296
			this.appIcon = append(this.titleContainer, $('div.window-appicon'));
297
		}
S
SteVen Batten 已提交
298

S
SteVen Batten 已提交
299
		// Menubar: the menubar part which is responsible for populating both the custom and native menubars
300 301 302
		this.menubarPart = this.instantiationService.createInstance(MenubarControl);
		this.menubar = append(this.titleContainer, $('div.menubar'));
		this.menubar.setAttribute('role', 'menubar');
S
SteVen Batten 已提交
303

304
		this.menubarPart.create(this.menubar);
S
SteVen Batten 已提交
305 306 307

		if (!isMacintosh) {
			this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
S
SteVen Batten 已提交
308
			this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
S
SteVen Batten 已提交
309 310
		}

B
Benjamin Pasero 已提交
311
		// Title
312
		this.title = append(this.titleContainer, $('div.window-title'));
B
Benjamin Pasero 已提交
313
		if (this.pendingTitle) {
314
			this.title.innerText = this.pendingTitle;
B
Benjamin Pasero 已提交
315
		} else {
B
Benjamin Pasero 已提交
316
			this.doUpdateTitle();
B
Benjamin Pasero 已提交
317 318
		}

319
		// Maximize/Restore on doubleclick
S
SteVen Batten 已提交
320
		if (isMacintosh) {
321
			this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, e => {
S
SteVen Batten 已提交
322
				EventHelper.stop(e);
323

S
SteVen Batten 已提交
324
				this.onTitleDoubleclick();
325
			}));
S
SteVen Batten 已提交
326
		}
327

B
Benjamin Pasero 已提交
328
		// Context menu on title
329 330 331 332
		[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
			this._register(addDisposableListener(this.title, event, e => {
				if (e.type === EventType.CONTEXT_MENU || e.metaKey) {
					EventHelper.stop(e);
B
Benjamin Pasero 已提交
333

334 335 336
					this.onContextMenu(e);
				}
			}));
B
Benjamin Pasero 已提交
337 338
		});

B
Benjamin Pasero 已提交
339
		// Window Controls (Windows/Linux)
R
Ryan Adolf 已提交
340
		if (!isMacintosh) {
341 342
			this.windowControls = append(this.titleContainer, $('div.window-controls-container'));

S
SteVen Batten 已提交
343

B
Benjamin Pasero 已提交
344
			// Minimize
345 346 347 348 349 350
			const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			const minimizeIcon = append(minimizeIconContainer, $('div.window-icon'));
			addClass(minimizeIcon, 'window-minimize');
			this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => {
				this.windowService.minimizeWindow();
			}));
R
Ryan Adolf 已提交
351

B
Benjamin Pasero 已提交
352
			// Restore
353 354 355 356
			const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
			addClass(this.maxRestoreControl, 'window-max-restore');
			this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, e => {
S
SteVen Batten 已提交
357 358 359 360
				this.windowService.isMaximized().then((maximized) => {
					if (maximized) {
						return this.windowService.unmaximizeWindow();
					}
B
Benjamin Pasero 已提交
361 362

					return this.windowService.maximizeWindow();
363 364
				});
			}));
R
Ryan Adolf 已提交
365

B
Benjamin Pasero 已提交
366
			// Close
367 368 369 370 371 372 373
			const closeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
			addClass(closeIconContainer, 'window-close-bg');
			const closeIcon = append(closeIconContainer, $('div.window-icon'));
			addClass(closeIcon, 'window-close');
			this._register(addDisposableListener(closeIcon, EventType.CLICK, e => {
				this.windowService.closeWindow();
			}));
374

S
SteVen Batten 已提交
375
			// Resizer
376
			this.resizer = append(this.titleContainer, $('div.resizer'));
S
SteVen Batten 已提交
377

B
Benjamin Pasero 已提交
378
			const isMaximized = this.windowService.getConfiguration().maximized ? true : false;
379
			this.onDidChangeMaximized(isMaximized);
380
			this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this);
381
		}
R
Ryan Adolf 已提交
382

383 384
		// Since the title area is used to drag the window, we do not want to steal focus from the
		// currently active element. So we restore focus after a timeout back to where it was.
385 386
		this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_DOWN, e => {
			if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) {
S
SteVen Batten 已提交
387 388 389
				return;
			}

390 391 392 393 394 395
			const active = document.activeElement;
			setTimeout(() => {
				if (active instanceof HTMLElement) {
					active.focus();
				}
			}, 0 /* need a timeout because we are in capture phase */);
396
		}, true /* use capture to know the currently active element properly */));
397

398
		return this.titleContainer;
B
Benjamin Pasero 已提交
399 400
	}

401
	private onDidChangeMaximized(maximized: boolean) {
S
SteVen Batten 已提交
402 403
		if (this.maxRestoreControl) {
			if (maximized) {
404 405
				removeClass(this.maxRestoreControl, 'window-maximize');
				addClass(this.maxRestoreControl, 'window-unmaximize');
S
SteVen Batten 已提交
406
			} else {
407 408
				removeClass(this.maxRestoreControl, 'window-unmaximize');
				addClass(this.maxRestoreControl, 'window-maximize');
S
SteVen Batten 已提交
409
			}
S
SteVen Batten 已提交
410
		}
S
SteVen Batten 已提交
411

S
SteVen Batten 已提交
412 413
		if (this.resizer) {
			if (maximized) {
414
				hide(this.resizer);
S
SteVen Batten 已提交
415
			} else {
416
				show(this.resizer);
S
SteVen Batten 已提交
417
			}
S
SteVen Batten 已提交
418
		}
S
SteVen Batten 已提交
419 420

		this.adjustTitleMarginToCenter();
421 422
	}

B
Benjamin Pasero 已提交
423 424 425 426
	protected updateStyles(): void {
		super.updateStyles();

		// Part container
427
		if (this.titleContainer) {
S
SteVen Batten 已提交
428
			if (this.isInactive) {
429
				addClass(this.titleContainer, 'inactive');
S
SteVen Batten 已提交
430
			} else {
431
				removeClass(this.titleContainer, 'inactive');
S
SteVen Batten 已提交
432 433
			}

B
Benjamin Pasero 已提交
434
			const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
435
			this.titleContainer.style.backgroundColor = titleBackground;
B
Benjamin Pasero 已提交
436
			if (Color.fromHex(titleBackground).isLighter()) {
437
				addClass(this.titleContainer, 'light');
S
SteVen Batten 已提交
438
			} else {
439
				removeClass(this.titleContainer, 'light');
S
SteVen Batten 已提交
440
			}
441

B
Benjamin Pasero 已提交
442
			const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
443
			this.titleContainer.style.color = titleForeground;
B
Benjamin Pasero 已提交
444

445
			const titleBorder = this.getColor(TITLE_BAR_BORDER);
446
			this.titleContainer.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
447
		}
B
Benjamin Pasero 已提交
448 449
	}

450
	private onTitleDoubleclick(): void {
451
		this.windowService.onWindowTitleDoubleClick().then(null, errors.onUnexpectedError);
452 453
	}

B
Benjamin Pasero 已提交
454 455 456 457 458 459 460 461 462 463 464
	private onContextMenu(e: MouseEvent): void {

		// Find target anchor
		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,
465
				getActions: () => Promise.resolve(actions),
B
Benjamin Pasero 已提交
466 467 468 469 470 471 472 473 474 475 476
				onHide: () => actions.forEach(a => a.dispose())
			});
		}
	}

	private getContextMenuActions(): IAction[] {
		const actions: IAction[] = [];

		if (this.representedFileName) {
			const segments = this.representedFileName.split(paths.sep);
			for (let i = segments.length; i > 0; i--) {
477 478 479 480 481 482 483 484 485
				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(paths.sep);

B
Benjamin Pasero 已提交
486
				let label: string;
487
				if (!isFile) {
I
isidor 已提交
488
					label = getBaseLabel(paths.dirname(path));
B
Benjamin Pasero 已提交
489
				} else {
I
isidor 已提交
490
					label = getBaseLabel(path);
491 492 493
				}

				actions.push(new ShowItemInFolderAction(path, label || paths.sep, this.windowsService));
B
Benjamin Pasero 已提交
494 495 496 497 498 499
			}
		}

		return actions;
	}

S
SteVen Batten 已提交
500 501 502
	private adjustTitleMarginToCenter(): void {
		setTimeout(() => {
			// Center the title in the window
M
Matt Bierner 已提交
503
			const currentAppIconWidth = this.appIcon ? parseInt(getComputedStyle(this.appIcon).width, 10) : 0;
S
SteVen Batten 已提交
504 505 506 507
			let currentMenubarWidth = parseInt(getComputedStyle(this.menubar).width, 10);
			currentMenubarWidth = isNaN(currentMenubarWidth) ? 0 : currentMenubarWidth;
			const currentTotalWidth = parseInt(getComputedStyle(document.body).width, 10);
			const currentTitleWidth = parseInt(getComputedStyle(this.title).width, 10);
M
Matt Bierner 已提交
508
			const currentWindowControlsWidth = this.windowControls ? parseInt(getComputedStyle(this.windowControls).width, 10) : 0;
S
SteVen Batten 已提交
509 510 511 512 513 514 515 516 517 518 519 520 521

			let leftMargin = (currentTotalWidth / 2) - (currentTitleWidth / 2) - (currentMenubarWidth + currentAppIconWidth);
			let rightMargin = currentTotalWidth - (currentAppIconWidth + currentMenubarWidth + leftMargin + currentTitleWidth + currentWindowControlsWidth);

			// Center if we can, leaving some space on both sides
			if (leftMargin >= 20 && rightMargin >= 20) {
				this.title.style.marginLeft = `${leftMargin}px`;
			} else {
				this.title.style.marginLeft = null;
			}
		}, 0); // delay so that we can get accurate information about the widths
	}

S
SteVen Batten 已提交
522 523
	private updateLayout(dimension: Dimension) {
		// Store initital title sizing if we need to prevent zooming
524
		if (typeof this.initialSizing.titleFontSize !== 'number') {
525
			this.initialSizing.titleFontSize = parseInt(getComputedStyle(this.title).fontSize, 10);
526 527 528
		}

		if (typeof this.initialSizing.titlebarHeight !== 'number') {
529
			this.initialSizing.titlebarHeight = parseInt(getComputedStyle(this.title).height, 10);
B
Benjamin Pasero 已提交
530
		}
531

S
SteVen Batten 已提交
532 533 534 535
		// Only prevent zooming behavior on macOS or when the menubar is not visible
		if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
			// To prevent zooming we need to adjust the font size with the zoom factor
			const newHeight = this.initialSizing.titlebarHeight / getZoomFactor();
536 537
			this.title.style.fontSize = `${this.initialSizing.titleFontSize / getZoomFactor()}px`;
			this.title.style.lineHeight = `${newHeight}px`;
538

S
SteVen Batten 已提交
539 540 541
			// Windows/Linux specific layout
			if (isWindows || isLinux) {
				if (typeof this.initialSizing.controlsWidth !== 'number') {
542
					this.initialSizing.controlsWidth = parseInt(getComputedStyle(this.windowControls).width, 10);
S
SteVen Batten 已提交
543
				}
544

545
				const appIconComputedStyles = getComputedStyle(this.appIcon);
S
SteVen Batten 已提交
546
				if (typeof this.initialSizing.appIconWidth !== 'number') {
547
					this.initialSizing.appIconWidth = parseInt(appIconComputedStyles.width, 10);
S
SteVen Batten 已提交
548
				}
549

S
SteVen Batten 已提交
550
				if (typeof this.initialSizing.appIconSize !== 'number') {
551
					this.initialSizing.appIconSize = parseInt(appIconComputedStyles.backgroundSize, 10);
S
SteVen Batten 已提交
552
				}
553

554
				const currentAppIconHeight = parseInt(appIconComputedStyles.height, 10);
S
SteVen Batten 已提交
555 556 557 558 559
				const newControlsWidth = this.initialSizing.controlsWidth / getZoomFactor();
				const newAppIconWidth = this.initialSizing.appIconWidth / getZoomFactor();
				const newAppIconSize = this.initialSizing.appIconSize / getZoomFactor();

				// Adjust app icon mimic menubar
560 561 562 563
				this.appIcon.style.width = `${newAppIconWidth}px`;
				this.appIcon.style.backgroundSize = `${newAppIconSize}px`;
				this.appIcon.style.paddingTop = `${(newHeight - currentAppIconHeight) / 2.0}px`;
				this.appIcon.style.paddingBottom = `${(newHeight - currentAppIconHeight) / 2.0}px`;
564

S
SteVen Batten 已提交
565
				// Adjust windows controls
566
				this.windowControls.style.width = `${newControlsWidth}px`;
567
			}
S
SteVen Batten 已提交
568 569
		} else {
			// We need to undo zoom prevention
570 571
			this.title.style.fontSize = null;
			this.title.style.lineHeight = null;
572

573 574 575 576
			this.appIcon.style.width = null;
			this.appIcon.style.backgroundSize = null;
			this.appIcon.style.paddingTop = null;
			this.appIcon.style.paddingBottom = null;
577

578
			this.windowControls.style.width = null;
S
SteVen Batten 已提交
579
		}
580

S
SteVen Batten 已提交
581 582 583
		if (this.menubarPart) {
			const menubarDimension = new Dimension(undefined, dimension.height);
			this.menubarPart.layout(menubarDimension);
584 585 586
		}
	}

B
Benjamin Pasero 已提交
587
	layout(dimension: Dimension): Dimension[] {
S
SteVen Batten 已提交
588
		this.updateLayout(dimension);
B
Benjamin Pasero 已提交
589 590 591

		return super.layout(dimension);
	}
B
Benjamin Pasero 已提交
592 593 594 595
}

class ShowItemInFolderAction extends Action {

596 597
	constructor(private path: string, label: string, private windowsService: IWindowsService) {
		super('showItemInFolder.action.id', label);
B
Benjamin Pasero 已提交
598 599
	}

B
Benjamin Pasero 已提交
600
	run(): Thenable<void> {
B
Benjamin Pasero 已提交
601 602
		return this.windowsService.showItemInFolder(this.path);
	}
R
Ryan Adolf 已提交
603
}
S
SteVen Batten 已提交
604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
	const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
	if (titlebarActiveFg) {
		collector.addRule(`
		.monaco-workbench > .part.titlebar > .window-controls-container .window-icon {
			background-color: ${titlebarActiveFg};
		}
		`);
	}

	const titlebarInactiveFg = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
	if (titlebarInactiveFg) {
		collector.addRule(`
		.monaco-workbench > .part.titlebar.inactive > .window-controls-container .window-icon {
				background-color: ${titlebarInactiveFg};
			}
		`);
	}
});