diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 5ee3e79c644..71d108f44c6 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -6,7 +6,7 @@ import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; -import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -69,13 +69,13 @@ export class CodeWindow extends Disposable implements ICodeWindow { private static readonly MAX_URL_LENGTH = 2 * 1024 * 1024; // https://cs.chromium.org/chromium/src/url/url_constants.cc?l=32 private readonly _onClose = this._register(new Emitter()); - readonly onClose: CommonEvent = this._onClose.event; + readonly onClose = this._onClose.event; private readonly _onDestroy = this._register(new Emitter()); - readonly onDestroy: CommonEvent = this._onDestroy.event; + readonly onDestroy = this._onDestroy.event; private readonly _onLoad = this._register(new Emitter()); - readonly onLoad: CommonEvent = this._onLoad.event; + readonly onLoad = this._onLoad.event; private hiddenTitleBarStyle: boolean | undefined; private showTimeoutHandle: NodeJS.Timeout | undefined; @@ -83,7 +83,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { private _readyState: ReadyState; private windowState: IWindowState; private currentMenuBarVisibility: MenuBarVisibility | undefined; + private representedFilename: string | undefined; + private documentEdited: boolean | undefined; private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; @@ -271,6 +273,22 @@ export class CodeWindow extends Disposable implements ICodeWindow { return this.representedFilename; } + setDocumentEdited(edited: boolean): void { + if (isMacintosh) { + this._win.setDocumentEdited(edited); + } + + this.documentEdited = edited; + } + + isDocumentEdited(): boolean { + if (isMacintosh) { + return this._win.isDocumentEdited(); + } + + return !!this.documentEdited; + } + focus(): void { if (!this._win) { return; @@ -586,9 +604,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Clear Document Edited if needed - if (isMacintosh && this._win.isDocumentEdited()) { + if (this.isDocumentEdited()) { if (!isReload || !this.backupMainService.isHotExitEnabled()) { - this._win.setDocumentEdited(false); + this.setDocumentEdited(false); } } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 254f08577ad..4d593ec7495 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -65,7 +65,8 @@ export class ElectronMainService implements IElectronMainService { workspace: window.openedWorkspace, folderUri: window.openedFolderUri, title: window.win.getTitle(), - filename: window.getRepresentedFilename() + filename: window.getRepresentedFilename(), + dirty: window.isDocumentEdited() })); } @@ -271,7 +272,7 @@ export class ElectronMainService implements IElectronMainService { async setDocumentEdited(windowId: number | undefined, edited: boolean): Promise { const window = this.windowById(windowId); if (window) { - window.win.setDocumentEdited(edited); + window.setDocumentEdited(edited); } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 348d06948f6..7d809ddc737 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -15,6 +15,7 @@ export interface IOpenedWindow { folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; + dirty: boolean; } export interface IBaseOpenWindowsOptions { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 3e05c84fcd9..b88164b804f 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -80,6 +80,9 @@ export interface ICodeWindow extends IDisposable { setRepresentedFilename(name: string): void; getRepresentedFilename(): string | undefined; + setDocumentEdited(edited: boolean): void; + isDocumentEdited(): boolean; + handleTitleDoubleClick(): void; updateTouchBar(items: ISerializableCommandAction[][]): void; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 3ace4854d26..4841e9c65aa 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/actions'; - import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/electron-browser/actions/media/actions.css similarity index 60% rename from src/vs/workbench/browser/actions/media/actions.css rename to src/vs/workbench/electron-browser/actions/media/actions.css index a4a092d8349..ba0563a557e 100644 --- a/src/vs/workbench/browser/actions/media/actions.css +++ b/src/vs/workbench/electron-browser/actions/media/actions.css @@ -2,3 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + +.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-window::before { + content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */ +} diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 44de240a80d..82e4071e5eb 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/actions'; + import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; @@ -157,6 +159,12 @@ export abstract class BaseSwitchWindow extends Action { tooltip: nls.localize('close', "Close Window") }; + private readonly closeDirtyWindowAction: IQuickInputButton = { + iconClass: 'dirty-window codicon-circle-filled', + tooltip: nls.localize('close', "Close Window"), + alwaysVisible: true + }; + constructor( id: string, label: string, @@ -185,7 +193,7 @@ export abstract class BaseSwitchWindow extends Action { label: win.title, iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind), description: (currentWindowId === win.id) ? nls.localize('current', "Current Window") : undefined, - buttons: (!this.isQuickNavigate() && currentWindowId !== win.id) ? [this.closeWindowAction] : undefined + buttons: currentWindowId !== win.id ? win.dirty ? [this.closeDirtyWindowAction] : [this.closeWindowAction] : undefined }; }); const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.payload === currentWindowId)[0]) + 1) % picks.length; diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 71d4b4dc916..69b36a77e20 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -269,19 +269,17 @@ export class NativeWindow extends Disposable { })); } - // Document edited (macOS only): indicate for dirty working copies - if (isMacintosh) { - this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { - const gotDirty = workingCopy.isDirty(); - if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { - return; // do not indicate dirty of working copies that are auto saved after short delay - } + // Document edited: indicate for dirty working copies + this._register(this.workingCopyService.onDidChangeDirty(workingCopy => { + const gotDirty = workingCopy.isDirty(); + if (gotDirty && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { + return; // do not indicate dirty of working copies that are auto saved after short delay + } - this.updateDocumentEdited(gotDirty); - })); + this.updateDocumentEdited(gotDirty); + })); - this.updateDocumentEdited(); - } + this.updateDocumentEdited(); // Detect minimize / maximize this._register(Event.any(