Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8 (#14883)

* Merge from vscode bead496a613e475819f89f08e9e882b841bc1fe8

* Bump distro

* Upgrade GCC to 4.9 due to yarn install errors

* Update build image

* Fix bootstrap base url

* Bump distro

* Fix build errors

* Update source map file

* Disable checkbox for blocking migration issues (#15131)

* disable checkbox for blocking issues

* wip

* disable checkbox fixes

* fix strings

* Remove duplicate tsec command

* Default to off for tab color if settings not present

* re-skip failing tests

* Fix mocha error

* Bump sqlite version & fix notebooks search view

* Turn off esbuild warnings

* Update esbuild log level

* Fix overflowactionbar tests

* Fix ts-ignore in dropdown tests

* cleanup/fixes

* Fix hygiene

* Bundle in entire zone.js module

* Remove extra constructor param

* bump distro for web compile break

* bump distro for web compile break v2

* Undo log level change

* New distro

* Fix integration test scripts

* remove the "no yarn.lock changes" workflow

* fix scripts v2

* Update unit test scripts

* Ensure ads-kerberos2 updates in .vscodeignore

* Try fix unit tests

* Upload crash reports

* remove nogpu

* always upload crashes

* Use bash script

* Consolidate data/ext dir names

* Create in tmp directory

Co-authored-by: chlafreniere <hichise@gmail.com>
Co-authored-by: Christopher Suh <chsuh@microsoft.com>
Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
Karl Burtram
2021-04-27 14:01:59 -07:00
committed by GitHub
parent 7e1c0076ba
commit 867a963882
1817 changed files with 81812 additions and 50843 deletions

View File

@@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { LogLevel } from 'vs/platform/log/common/log';
import { ExportData } from 'vs/base/common/performance';
import { PerformanceMark } from 'vs/base/common/performance';
export const WindowMinimumSize = {
WIDTH: 400,
@@ -18,38 +18,37 @@ export const WindowMinimumSize = {
};
export interface IBaseOpenWindowsOptions {
forceReuseWindow?: boolean;
readonly forceReuseWindow?: boolean;
}
export interface IOpenWindowOptions extends IBaseOpenWindowsOptions {
forceNewWindow?: boolean;
preferNewWindow?: boolean;
readonly forceNewWindow?: boolean;
readonly preferNewWindow?: boolean;
noRecentEntry?: boolean;
readonly noRecentEntry?: boolean;
addMode?: boolean;
readonly addMode?: boolean;
diffMode?: boolean;
gotoLineMode?: boolean;
readonly diffMode?: boolean;
readonly gotoLineMode?: boolean;
waitMarkerFileURI?: URI;
readonly waitMarkerFileURI?: URI;
}
export interface IAddFoldersRequest {
foldersToAdd: UriComponents[];
readonly foldersToAdd: UriComponents[];
}
export interface IOpenedWindow {
id: number;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
title: string;
filename?: string;
dirty: boolean;
readonly id: number;
readonly workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
readonly title: string;
readonly filename?: string;
readonly dirty: boolean;
}
export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions {
remoteAuthority?: string;
readonly remoteAuthority?: string;
}
export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen;
@@ -59,15 +58,15 @@ export interface IBaseWindowOpenable {
}
export interface IWorkspaceToOpen extends IBaseWindowOpenable {
workspaceUri: URI;
readonly workspaceUri: URI;
}
export interface IFolderToOpen extends IBaseWindowOpenable {
folderUri: URI;
readonly folderUri: URI;
}
export interface IFileToOpen extends IBaseWindowOpenable {
fileUri: URI;
readonly fileUri: URI;
}
export function isWorkspaceToOpen(uriToOpen: IWindowOpenable): uriToOpen is IWorkspaceToOpen {
@@ -82,40 +81,39 @@ export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOp
return !!(uriToOpen as IFileToOpen).fileUri;
}
export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact';
export type MenuBarVisibility = 'classic' | 'visible' | 'toggle' | 'hidden' | 'compact';
export function getMenuBarVisibility(configurationService: IConfigurationService): MenuBarVisibility {
const titleBarStyle = getTitleBarStyle(configurationService);
const menuBarVisibility = configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
const menuBarVisibility = configurationService.getValue<MenuBarVisibility | 'default'>('window.menuBarVisibility');
if (titleBarStyle === 'native' && menuBarVisibility === 'compact') {
return 'default';
if (menuBarVisibility === 'default' || (titleBarStyle === 'native' && menuBarVisibility === 'compact')) {
return 'classic';
} else {
return menuBarVisibility;
}
}
export interface IWindowsConfiguration {
window: IWindowSettings;
readonly window: IWindowSettings;
}
export interface IWindowSettings {
openFilesInNewWindow: 'on' | 'off' | 'default';
openFoldersInNewWindow: 'on' | 'off' | 'default';
openWithoutArgumentsInNewWindow: 'on' | 'off';
restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none';
restoreFullscreen: boolean;
zoomLevel: number;
titleBarStyle: 'native' | 'custom';
autoDetectHighContrast: boolean;
menuBarVisibility: MenuBarVisibility;
newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
nativeTabs: boolean;
nativeFullScreen: boolean;
enableMenuBarMnemonics: boolean;
closeWhenEmpty: boolean;
clickThroughInactive: boolean;
enableExperimentalProxyLoginDialog: boolean;
readonly openFilesInNewWindow: 'on' | 'off' | 'default';
readonly openFoldersInNewWindow: 'on' | 'off' | 'default';
readonly openWithoutArgumentsInNewWindow: 'on' | 'off';
readonly restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none';
readonly restoreFullscreen: boolean;
readonly zoomLevel: number;
readonly titleBarStyle: 'native' | 'custom';
readonly autoDetectHighContrast: boolean;
readonly menuBarVisibility: MenuBarVisibility;
readonly newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
readonly nativeTabs: boolean;
readonly nativeFullScreen: boolean;
readonly enableMenuBarMnemonics: boolean;
readonly closeWhenEmpty: boolean;
readonly clickThroughInactive: boolean;
}
export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' {
@@ -154,24 +152,24 @@ export interface IPath extends IPathData {
export interface IPathData {
// the file path to open within the instance
fileUri?: UriComponents;
readonly fileUri?: UriComponents;
// the line number in the file path to open
lineNumber?: number;
readonly lineNumber?: number;
// the column number in the file path to open
columnNumber?: number;
readonly columnNumber?: number;
// a hint that the file exists. if true, the
// file exists, if false it does not. with
// undefined the state is unknown.
exists?: boolean;
readonly exists?: boolean;
// Specifies if the file should be only be opened if it exists
openOnlyIfExists?: boolean;
readonly openOnlyIfExists?: boolean;
// Specifies an optional id to override the editor used to edit the resource, e.g. custom editor.
overrideId?: string;
readonly overrideId?: string;
}
export interface IPathsToWaitFor extends IPathsToWaitForData {
@@ -180,36 +178,36 @@ export interface IPathsToWaitFor extends IPathsToWaitForData {
}
interface IPathsToWaitForData {
paths: IPathData[];
waitMarkerFileUri: UriComponents;
readonly paths: IPathData[];
readonly waitMarkerFileUri: UriComponents;
}
export interface IOpenFileRequest {
filesToOpenOrCreate?: IPathData[];
filesToDiff?: IPathData[];
readonly filesToOpenOrCreate?: IPathData[];
readonly filesToDiff?: IPathData[];
}
/**
* Additional context for the request on native only.
*/
export interface INativeOpenFileRequest extends IOpenFileRequest {
termProgram?: string;
filesToWait?: IPathsToWaitForData;
readonly termProgram?: string;
readonly filesToWait?: IPathsToWaitForData;
}
export interface INativeRunActionInWindowRequest {
id: string;
from: 'menu' | 'touchbar' | 'mouse';
args?: any[];
readonly id: string;
readonly from: 'menu' | 'touchbar' | 'mouse';
readonly args?: any[];
}
export interface INativeRunKeybindingInWindowRequest {
userSettingsLabel: string;
readonly userSettingsLabel: string;
}
export interface IColorScheme {
dark: boolean;
highContrast: boolean;
readonly dark: boolean;
readonly highContrast: boolean;
}
export interface IWindowConfiguration {
@@ -225,7 +223,7 @@ export interface IWindowConfiguration {
}
export interface IOSConfiguration {
release: string;
readonly release: string;
}
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs {
@@ -241,8 +239,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
nodeCachedDataDir?: string;
partsSplashPath: string;
workspace?: IWorkspaceIdentifier;
folderUri?: ISingleFolderWorkspaceIdentifier;
workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
isInitialStartup?: boolean;
logLevel: LogLevel;
@@ -250,7 +247,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
fullscreen?: boolean;
maximized?: boolean;
accessibilitySupport?: boolean;
perfEntries: ExportData;
perfMarks: PerformanceMark[];
userEnv: IProcessEnvironment;
filesToWait?: IPathsToWaitFor;

View File

@@ -4,18 +4,38 @@
*--------------------------------------------------------------------------------------------*/
import { IWindowOpenable, IOpenEmptyWindowOptions, INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
import { OpenContext } from 'vs/platform/windows/node/window';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { ISerializableCommandAction } from 'vs/platform/actions/common/actions';
import { URI } from 'vs/base/common/uri';
import { Rectangle, BrowserWindow, WebContents } from 'electron';
import { IDisposable } from 'vs/base/common/lifecycle';
import { CancellationToken } from 'vs/base/common/cancellation';
export const enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
export interface IWindowState {
width?: number;
height?: number;
@@ -45,8 +65,8 @@ export interface ICodeWindow extends IDisposable {
readonly win: BrowserWindow;
readonly config: INativeWindowConfiguration | undefined;
readonly openedFolderUri?: URI;
readonly openedWorkspace?: IWorkspaceIdentifier;
readonly openedWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier;
readonly backupPath?: string;
readonly remoteAuthority?: string;
@@ -64,7 +84,7 @@ export interface ICodeWindow extends IDisposable {
addTabbedWindow(window: ICodeWindow): void;
load(config: INativeWindowConfiguration, isReload?: boolean): void;
load(config: INativeWindowConfiguration, options?: { isReload?: boolean }): void;
reload(cli?: NativeParsedArgs): void;
focus(options?: { force: boolean }): void;
@@ -104,9 +124,11 @@ export interface IWindowsMainService {
readonly _serviceBrand: undefined;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
readonly onWindowOpened: Event<ICodeWindow>;
readonly onWindowReady: Event<ICodeWindow>;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
readonly onWindowDestroyed: Event<ICodeWindow>;
open(openConfig: IOpenConfiguration): ICodeWindow[];
openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[];
@@ -115,13 +137,14 @@ export interface IWindowsMainService {
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
getWindows(): ICodeWindow[];
getWindowCount(): number;
getFocusedWindow(): ICodeWindow | undefined;
getLastActiveWindow(): ICodeWindow | undefined;
getWindowById(windowId: number): ICodeWindow | undefined;
getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined;
getWindows(): ICodeWindow[];
getWindowCount(): number;
}
export interface IBaseOpenConfiguration {

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { IWorkspaceIdentifier, IResolvedWorkspace, isWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { ICodeWindow } from 'vs/platform/windows/electron-main/windows';
export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): ICodeWindow | undefined {
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (isWorkspaceIdentifier(workspace)) {
const resolvedWorkspace = localWorkspaceResolver(workspace);
// resolved workspace: folders are known and can be compared with
if (resolvedWorkspace) {
if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) {
return window;
}
}
// unresolved: can only compare with workspace location
else {
if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) {
return window;
}
}
}
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedWorkspace.uri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((windowA, windowB) => -((windowA.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length - (windowB.openedWorkspace as ISingleFolderWorkspaceIdentifier).uri.path.length))[0];
}
return undefined;
}
export function findWindowOnWorkspaceOrFolder(windows: ICodeWindow[], folderOrWorkspaceConfigUri: URI): ICodeWindow | undefined {
for (const window of windows) {
// check for workspace config path
if (isWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, folderOrWorkspaceConfigUri)) {
return window;
}
// check for folder path
if (isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.uri, folderOrWorkspaceConfigUri)) {
return window;
}
}
return undefined;
}
export function findWindowOnExtensionDevelopmentPath(windows: ICodeWindow[], extensionDevelopmentPaths: string[]): ICodeWindow | undefined {
const matches = (uriString: string): boolean => {
return extensionDevelopmentPaths.some(path => extUriBiasedIgnorePathCase.isEqual(URI.file(path), URI.file(uriString)));
};
for (const window of windows) {
// match on extension development path. the path can be one or more paths
// so we check if any of the paths match on any of the provided ones
if (window.config?.extensionDevelopmentPath?.some(path => matches(path))) {
return window;
}
}
return undefined;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,450 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { app, Display, screen } from 'electron';
import { Disposable } from 'vs/base/common/lifecycle';
import { isMacintosh } from 'vs/base/common/platform';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { defaultWindowState } from 'vs/code/electron-main/window';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { ILogService } from 'vs/platform/log/common/log';
import { IStateService } from 'vs/platform/state/node/state';
import { INativeWindowConfiguration, IWindowSettings } from 'vs/platform/windows/common/windows';
import { ICodeWindow, IWindowsMainService, IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
export interface IWindowState {
workspace?: IWorkspaceIdentifier;
folderUri?: URI;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
}
export interface IWindowsState {
lastActiveWindow?: IWindowState;
lastPluginDevelopmentHostWindow?: IWindowState;
openedWindows: IWindowState[];
}
interface INewWindowState extends IWindowUIState {
hasDefaultState?: boolean;
}
interface ISerializedWindowsState {
readonly lastActiveWindow?: ISerializedWindowState;
readonly lastPluginDevelopmentHostWindow?: ISerializedWindowState;
readonly openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
readonly workspaceIdentifier?: { id: string; configURIPath: string };
readonly folder?: string;
readonly backupPath?: string;
readonly remoteAuthority?: string;
readonly uiState: IWindowUIState;
}
export class WindowsStateHandler extends Disposable {
private static readonly windowsStateStorageKey = 'windowsState';
get state() { return this._state; }
private readonly _state = restoreWindowsState(this.stateService.getItem<ISerializedWindowsState>(WindowsStateHandler.windowsStateStorageKey));
private lastClosedState: IWindowState | undefined = undefined;
private shuttingDown = false;
constructor(
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@IStateService private readonly stateService: IStateService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@ILogService private readonly logService: ILogService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.registerListeners();
}
private registerListeners(): void {
// When a window looses focus, save all windows state. This allows to
// prevent loss of window-state data when OS is restarted without properly
// shutting down the application (https://github.com/microsoft/vscode/issues/87171)
app.on('browser-window-blur', () => {
if (!this.shuttingDown) {
this.saveWindowsState();
}
});
// Handle various lifecycle events around windows
this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window));
this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown());
this.windowsMainService.onWindowsCountChanged(e => {
if (e.newCount - e.oldCount > 0) {
// clear last closed window state when a new window opens. this helps on macOS where
// otherwise closing the last window, opening a new window and then quitting would
// use the state of the previously closed window when restarting.
this.lastClosedState = undefined;
}
});
// try to save state before destroy because close will not fire
this.windowsMainService.onWindowDestroyed(window => this.onBeforeWindowClose(window));
}
// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
// - macOS: since the app will not quit when closing the last window, you will always first get
// the onBeforeShutdown() event followed by N onBeforeWindowClose() events for each window
// - other: on other OS, closing the last window will quit the app so the order depends on the
// user interaction: closing the last window will first trigger onBeforeWindowClose()
// and then onBeforeShutdown(). Using the quit action however will first issue onBeforeShutdown()
// and then onBeforeWindowClose().
//
// Here is the behavior on different OS depending on action taken (Electron 1.7.x):
//
// Legend
// - quit(N): quit application with N windows opened
// - close(1): close one window via the window close button
// - closeAll: close all windows via the taskbar command
// - onBeforeShutdown(N): number of windows reported in this event handler
// - onBeforeWindowClose(N, M): number of windows reported and quitRequested boolean in this event handler
//
// macOS
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - quit(0): onBeforeShutdown(0)
// - close(1): onBeforeWindowClose(1, false)
//
// Windows
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
// Linux
// - quit(1): onBeforeShutdown(1), onBeforeWindowClose(1, true)
// - quit(2): onBeforeShutdown(2), onBeforeWindowClose(2, true), onBeforeWindowClose(2, true)
// - close(1): onBeforeWindowClose(2, false)[not last window]
// - close(1): onBeforeWindowClose(1, false), onBeforeShutdown(0)[last window]
// - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeShutdown(0)
//
private onBeforeShutdown(): void {
this.shuttingDown = true;
this.saveWindowsState();
}
private saveWindowsState(): void {
const currentWindowsState: IWindowsState = {
openedWindows: [],
lastPluginDevelopmentHostWindow: this._state.lastPluginDevelopmentHostWindow,
lastActiveWindow: this.lastClosedState
};
// 1.) Find a last active window (pick any other first window otherwise)
if (!currentWindowsState.lastActiveWindow) {
let activeWindow = this.windowsMainService.getLastActiveWindow();
if (!activeWindow || activeWindow.isExtensionDevelopmentHost) {
activeWindow = this.windowsMainService.getWindows().find(window => !window.isExtensionDevelopmentHost);
}
if (activeWindow) {
currentWindowsState.lastActiveWindow = this.toWindowState(activeWindow);
}
}
// 2.) Find extension host window
const extensionHostWindow = this.windowsMainService.getWindows().find(window => window.isExtensionDevelopmentHost && !window.isExtensionTestHost);
if (extensionHostWindow) {
currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
}
// 3.) All windows (except extension host) for N >= 2 to support `restoreWindows: all` or for auto update
//
// Careful here: asking a window for its window state after it has been closed returns bogus values (width: 0, height: 0)
// so if we ever want to persist the UI state of the last closed window (window count === 1), it has
// to come from the stored lastClosedWindowState on Win/Linux at least
if (this.windowsMainService.getWindowCount() > 1) {
currentWindowsState.openedWindows = this.windowsMainService.getWindows().filter(window => !window.isExtensionDevelopmentHost).map(window => this.toWindowState(window));
}
// Persist
const state = getWindowsStateStoreData(currentWindowsState);
this.stateService.setItem(WindowsStateHandler.windowsStateStorageKey, state);
if (this.shuttingDown) {
this.logService.trace('[WindowsStateHandler] onBeforeShutdown', state);
}
}
// See note on #onBeforeShutdown() for details how these events are flowing
private onBeforeWindowClose(window: ICodeWindow): void {
if (this.lifecycleMainService.quitRequested) {
return; // during quit, many windows close in parallel so let it be handled in the before-quit handler
}
// On Window close, update our stored UI state of this window
const state: IWindowState = this.toWindowState(window);
if (window.isExtensionDevelopmentHost && !window.isExtensionTestHost) {
this._state.lastPluginDevelopmentHostWindow = state; // do not let test run window state overwrite our extension development state
}
// Any non extension host window with same workspace or folder
else if (!window.isExtensionDevelopmentHost && window.openedWorkspace) {
this._state.openedWindows.forEach(openedWindow => {
const sameWorkspace = isWorkspaceIdentifier(window.openedWorkspace) && openedWindow.workspace?.id === window.openedWorkspace.id;
const sameFolder = isSingleFolderWorkspaceIdentifier(window.openedWorkspace) && openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, window.openedWorkspace.uri);
if (sameWorkspace || sameFolder) {
openedWindow.uiState = state.uiState;
}
});
}
// On Windows and Linux closing the last window will trigger quit. Since we are storing all UI state
// before quitting, we need to remember the UI state of this window to be able to persist it.
// On macOS we keep the last closed window state ready in case the user wants to quit right after or
// wants to open another window, in which case we use this state over the persisted one.
if (this.windowsMainService.getWindowCount() === 1) {
this.lastClosedState = state;
}
}
private toWindowState(window: ICodeWindow): IWindowState {
return {
workspace: isWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace : undefined,
folderUri: isSingleFolderWorkspaceIdentifier(window.openedWorkspace) ? window.openedWorkspace.uri : undefined,
backupPath: window.backupPath,
remoteAuthority: window.remoteAuthority,
uiState: window.serializeWindowState()
};
}
getNewWindowState(configuration: INativeWindowConfiguration): INewWindowState {
const state = this.doGetNewWindowState(configuration);
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
// Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen
let allowFullscreen: boolean;
if (state.hasDefaultState) {
allowFullscreen = !!(windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0);
}
// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
else {
allowFullscreen = !!(this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen);
if (allowFullscreen && isMacintosh && this.windowsMainService.getWindows().some(window => window.isFullScreen)) {
// macOS: Electron does not allow to restore multiple windows in
// fullscreen. As such, if we already restored a window in that
// state, we cannot allow more fullscreen windows. See
// https://github.com/microsoft/vscode/issues/41691 and
// https://github.com/electron/electron/issues/13077
allowFullscreen = false;
}
}
if (state.mode === WindowMode.Fullscreen && !allowFullscreen) {
state.mode = WindowMode.Normal;
}
return state;
}
private doGetNewWindowState(configuration: INativeWindowConfiguration): INewWindowState {
const lastActive = this.windowsMainService.getLastActiveWindow();
// Restore state unless we are running extension tests
if (!configuration.extensionTestsPath) {
// extension development host Window - load from stored settings if any
if (!!configuration.extensionDevelopmentPath && this.state.lastPluginDevelopmentHostWindow) {
return this.state.lastPluginDevelopmentHostWindow.uiState;
}
// Known Workspace - load from stored settings
const workspace = configuration.workspace;
if (isWorkspaceIdentifier(workspace)) {
const stateForWorkspace = this.state.openedWindows.filter(openedWindow => openedWindow.workspace && openedWindow.workspace.id === workspace.id).map(openedWindow => openedWindow.uiState);
if (stateForWorkspace.length) {
return stateForWorkspace[0];
}
}
// Known Folder - load from stored settings
if (isSingleFolderWorkspaceIdentifier(workspace)) {
const stateForFolder = this.state.openedWindows.filter(openedWindow => openedWindow.folderUri && extUriBiasedIgnorePathCase.isEqual(openedWindow.folderUri, workspace.uri)).map(openedWindow => openedWindow.uiState);
if (stateForFolder.length) {
return stateForFolder[0];
}
}
// Empty windows with backups
else if (configuration.backupPath) {
const stateForEmptyWindow = this.state.openedWindows.filter(openedWindow => openedWindow.backupPath === configuration.backupPath).map(openedWindow => openedWindow.uiState);
if (stateForEmptyWindow.length) {
return stateForEmptyWindow[0];
}
}
// First Window
const lastActiveState = this.lastClosedState || this.state.lastActiveWindow;
if (!lastActive && lastActiveState) {
return lastActiveState.uiState;
}
}
//
// In any other case, we do not have any stored settings for the window state, so we come up with something smart
//
// We want the new window to open on the same display that the last active one is in
let displayToUse: Display | undefined;
const displays = screen.getAllDisplays();
// Single Display
if (displays.length === 1) {
displayToUse = displays[0];
}
// Multi Display
else {
// on mac there is 1 menu per window so we need to use the monitor where the cursor currently is
if (isMacintosh) {
const cursorPoint = screen.getCursorScreenPoint();
displayToUse = screen.getDisplayNearestPoint(cursorPoint);
}
// if we have a last active window, use that display for the new window
if (!displayToUse && lastActive) {
displayToUse = screen.getDisplayMatching(lastActive.getBounds());
}
// fallback to primary display or first display
if (!displayToUse) {
displayToUse = screen.getPrimaryDisplay() || displays[0];
}
}
// Compute x/y based on display bounds
// Note: important to use Math.round() because Electron does not seem to be too happy about
// display coordinates that are not absolute numbers.
let state = defaultWindowState();
state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2));
state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2));
// Check for newWindowDimensions setting and adjust accordingly
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
let ensureNoOverlap = true;
if (windowConfig?.newWindowDimensions) {
if (windowConfig.newWindowDimensions === 'maximized') {
state.mode = WindowMode.Maximized;
ensureNoOverlap = false;
} else if (windowConfig.newWindowDimensions === 'fullscreen') {
state.mode = WindowMode.Fullscreen;
ensureNoOverlap = false;
} else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) {
const lastActiveState = lastActive.serializeWindowState();
if (lastActiveState.mode === WindowMode.Fullscreen) {
state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331)
} else {
state = lastActiveState;
}
ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset';
}
}
if (ensureNoOverlap) {
state = this.ensureNoOverlap(state);
}
(state as INewWindowState).hasDefaultState = true; // flag as default state
return state;
}
private ensureNoOverlap(state: IWindowUIState): IWindowUIState {
if (this.windowsMainService.getWindows().length === 0) {
return state;
}
state.x = typeof state.x === 'number' ? state.x : 0;
state.y = typeof state.y === 'number' ? state.y : 0;
const existingWindowBounds = this.windowsMainService.getWindows().map(window => window.getBounds());
while (existingWindowBounds.some(bounds => bounds.x === state.x || bounds.y === state.y)) {
state.x += 30;
state.y += 30;
}
return state;
}
}
export function restoreWindowsState(data: ISerializedWindowsState | undefined): IWindowsState {
const result: IWindowsState = { openedWindows: [] };
const windowsState = data || { openedWindows: [] };
if (windowsState.lastActiveWindow) {
result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow);
}
if (windowsState.lastPluginDevelopmentHostWindow) {
result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow);
}
if (Array.isArray(windowsState.openedWindows)) {
result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState));
}
return result;
}
function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
const result: IWindowState = { uiState: windowState.uiState };
if (windowState.backupPath) {
result.backupPath = windowState.backupPath;
}
if (windowState.remoteAuthority) {
result.remoteAuthority = windowState.remoteAuthority;
}
if (windowState.folder) {
result.folderUri = URI.parse(windowState.folder);
}
if (windowState.workspaceIdentifier) {
result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) };
}
return result;
}
export function getWindowsStateStoreData(windowsState: IWindowsState): IWindowsState {
return {
lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow),
lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow),
openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws))
};
}
function serializeWindowState(windowState: IWindowState): ISerializedWindowState {
return {
workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() },
folder: windowState.folderUri && windowState.folderUri.toString(),
backupPath: windowState.backupPath,
remoteAuthority: windowState.remoteAuthority,
uiState: windowState.uiState
};
}

View File

@@ -1,93 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, UriComponents } from 'vs/base/common/uri';
import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows';
import { IWindowState, IWindowsState } from 'vs/platform/windows/electron-main/windowsMainService';
export type WindowsStateStorageData = object;
interface ISerializedWindowsState {
lastActiveWindow?: ISerializedWindowState;
lastPluginDevelopmentHostWindow?: ISerializedWindowState;
openedWindows: ISerializedWindowState[];
}
interface ISerializedWindowState {
workspaceIdentifier?: { id: string; configURIPath: string };
folder?: string;
backupPath?: string;
remoteAuthority?: string;
uiState: IWindowUIState;
// deprecated
folderUri?: UriComponents;
folderPath?: string;
workspace?: { id: string; configPath: string };
}
export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState {
const result: IWindowsState = { openedWindows: [] };
const windowsState = data as ISerializedWindowsState || { openedWindows: [] };
if (windowsState.lastActiveWindow) {
result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow);
}
if (windowsState.lastPluginDevelopmentHostWindow) {
result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow);
}
if (Array.isArray(windowsState.openedWindows)) {
result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState));
}
return result;
}
function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
const result: IWindowState = { uiState: windowState.uiState };
if (windowState.backupPath) {
result.backupPath = windowState.backupPath;
}
if (windowState.remoteAuthority) {
result.remoteAuthority = windowState.remoteAuthority;
}
if (windowState.folder) {
result.folderUri = URI.parse(windowState.folder);
} else if (windowState.folderUri) {
result.folderUri = URI.revive(windowState.folderUri);
} else if (windowState.folderPath) {
result.folderUri = URI.file(windowState.folderPath);
}
if (windowState.workspaceIdentifier) {
result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) };
} else if (windowState.workspace) {
result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) };
}
return result;
}
export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData {
return {
lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow),
lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow),
openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws))
};
}
function serializeWindowState(windowState: IWindowState): ISerializedWindowState {
return {
workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() },
folder: windowState.folderUri && windowState.folderUri.toString(),
backupPath: windowState.backupPath,
remoteAuthority: windowState.remoteAuthority,
uiState: windowState.uiState
};
}

View File

@@ -1,150 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import * as extpath from 'vs/base/common/extpath';
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
export const enum OpenContext {
// opening when running from the command line
CLI,
// macOS only: opening from the dock (also when opening files to a running instance from desktop)
DOCK,
// opening from the main application window
MENU,
// opening from a file or folder dialog
DIALOG,
// opening from the OS's UI
DESKTOP,
// opening through the API
API
}
export interface IWindowContext {
openedWorkspace?: IWorkspaceIdentifier;
openedFolderUri?: URI;
extensionDevelopmentPath?: string[];
lastFocusTime: number;
}
export interface IBestWindowOrFolderOptions<W extends IWindowContext> {
windows: W[];
newWindow: boolean;
context: OpenContext;
fileUri?: URI;
codeSettingsFolder?: string;
localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null;
}
export function findBestWindowOrFolderForFile<W extends IWindowContext>({ windows, newWindow, context, fileUri, localWorkspaceResolver: workspaceResolver }: IBestWindowOrFolderOptions<W>): W | undefined {
if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) {
const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver);
if (windowOnFilePath) {
return windowOnFilePath;
}
}
return !newWindow ? getLastActiveWindow(windows) : undefined;
}
function findWindowOnFilePath<W extends IWindowContext>(windows: W[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null {
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (workspace) {
const resolvedWorkspace = localWorkspaceResolver(workspace);
if (resolvedWorkspace) {
// workspace could be resolved: It's in the local file system
if (resolvedWorkspace.folders.some(folder => extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, folder.uri))) {
return window;
}
} else {
// use the config path instead
if (extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, workspace.configPath)) {
return window;
}
}
}
}
// Then go with single folder windows that are parent of the provided file path
const singleFolderWindowsOnFilePath = windows.filter(window => window.openedFolderUri && extUriBiasedIgnorePathCase.isEqualOrParent(fileUri, window.openedFolderUri));
if (singleFolderWindowsOnFilePath.length) {
return singleFolderWindowsOnFilePath.sort((a, b) => -(a.openedFolderUri!.path.length - b.openedFolderUri!.path.length))[0];
}
return null;
}
export function getLastActiveWindow<W extends IWindowContext>(windows: W[]): W | undefined {
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
return windows.find(window => window.lastFocusTime === lastFocusedDate);
}
export function findWindowOnWorkspace<W extends IWindowContext>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on folder
if (isSingleFolderWorkspaceIdentifier(workspace)) {
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, workspace)) {
return window;
}
}
}
} else if (isWorkspaceIdentifier(workspace)) {
for (const window of windows) {
// match on workspace
if (window.openedWorkspace && window.openedWorkspace.id === workspace.id) {
return window;
}
}
}
return null;
}
export function findWindowOnExtensionDevelopmentPath<W extends IWindowContext>(windows: W[], extensionDevelopmentPaths: string[]): W | null {
const matches = (uriString: string): boolean => {
return extensionDevelopmentPaths.some(p => extpath.isEqual(p, uriString, !platform.isLinux /* ignorecase */));
};
for (const window of windows) {
// match on extension development path. The path can be one or more paths or uri strings, using paths.isEqual is not 100% correct but good enough
const currPaths = window.extensionDevelopmentPath;
if (currPaths?.some(p => matches(p))) {
return window;
}
}
return null;
}
export function findWindowOnWorkspaceOrFolderUri<W extends IWindowContext>(windows: W[], uri: URI | undefined): W | null {
if (!uri) {
return null;
}
for (const window of windows) {
// check for workspace config path
if (window.openedWorkspace && extUriBiasedIgnorePathCase.isEqual(window.openedWorkspace.configPath, uri)) {
return window;
}
// check for folder path
if (window.openedFolderUri && extUriBiasedIgnorePathCase.isEqual(window.openedFolderUri, uri)) {
return window;
}
}
return null;
}

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { join } from 'vs/base/common/path';
import { findWindowOnFile } from 'vs/platform/windows/electron-main/windowsFinder';
import { ICodeWindow, IWindowState } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier, toWorkspaceFolders } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { extUriBiasedIgnorePathCase } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { UriDto } from 'vs/base/common/types';
import { ICommandAction } from 'vs/platform/actions/common/actions';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { INativeWindowConfiguration } from 'vs/platform/windows/common/windows';
suite('WindowsFinder', () => {
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
const testWorkspace: IWorkspaceIdentifier = {
id: Date.now().toString(),
configPath: URI.file(join(fixturesFolder, 'workspaces.json'))
};
const testWorkspaceFolders = toWorkspaceFolders([{ path: join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath, extUriBiasedIgnorePathCase);
const localWorkspaceResolver = (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; };
function createTestCodeWindow(options: { lastFocusTime: number, openedFolderUri?: URI, openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow {
return new class implements ICodeWindow {
onLoad: Event<void> = Event.None;
onReady: Event<void> = Event.None;
onClose: Event<void> = Event.None;
onDestroy: Event<void> = Event.None;
whenClosedOrLoaded: Promise<void> = Promise.resolve();
id: number = -1;
win: Electron.BrowserWindow = undefined!;
config: INativeWindowConfiguration | undefined;
openedWorkspace = options.openedFolderUri ? { id: '', uri: options.openedFolderUri } : options.openedWorkspace;
backupPath?: string | undefined;
remoteAuthority?: string | undefined;
isExtensionDevelopmentHost = false;
isExtensionTestHost = false;
lastFocusTime = options.lastFocusTime;
isFullScreen = false;
isReady = true;
hasHiddenTitleBarStyle = false;
ready(): Promise<ICodeWindow> { throw new Error('Method not implemented.'); }
setReady(): void { throw new Error('Method not implemented.'); }
addTabbedWindow(window: ICodeWindow): void { throw new Error('Method not implemented.'); }
load(config: INativeWindowConfiguration, options: { isReload?: boolean }): void { throw new Error('Method not implemented.'); }
reload(cli?: NativeParsedArgs): void { throw new Error('Method not implemented.'); }
focus(options?: { force: boolean; }): void { throw new Error('Method not implemented.'); }
close(): void { throw new Error('Method not implemented.'); }
getBounds(): Electron.Rectangle { throw new Error('Method not implemented.'); }
send(channel: string, ...args: any[]): void { throw new Error('Method not implemented.'); }
sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void { throw new Error('Method not implemented.'); }
toggleFullScreen(): void { throw new Error('Method not implemented.'); }
isMinimized(): boolean { throw new Error('Method not implemented.'); }
setRepresentedFilename(name: string): void { throw new Error('Method not implemented.'); }
getRepresentedFilename(): string | undefined { throw new Error('Method not implemented.'); }
setDocumentEdited(edited: boolean): void { throw new Error('Method not implemented.'); }
isDocumentEdited(): boolean { throw new Error('Method not implemented.'); }
handleTitleDoubleClick(): void { throw new Error('Method not implemented.'); }
updateTouchBar(items: UriDto<ICommandAction>[][]): void { throw new Error('Method not implemented.'); }
serializeWindowState(): IWindowState { throw new Error('Method not implemented'); }
dispose(): void { }
};
}
const vscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(join(fixturesFolder, 'vscode_folder')) });
const lastActiveWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 3, openedFolderUri: undefined });
const noVscodeFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(join(fixturesFolder, 'no_vscode_folder')) });
const windows: ICodeWindow[] = [
vscodeFolderWindow,
lastActiveWindow,
noVscodeFolderWindow,
];
test('New window without folder when no windows exist', () => {
assert.strictEqual(findWindowOnFile([], URI.file('nonexisting'), localWorkspaceResolver), undefined);
assert.strictEqual(findWindowOnFile([], URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), undefined);
});
test('Existing window with folder', () => {
assert.strictEqual(findWindowOnFile(windows, URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), noVscodeFolderWindow);
assert.strictEqual(findWindowOnFile(windows, URI.file(join(fixturesFolder, 'vscode_folder', 'file.txt')), localWorkspaceResolver), vscodeFolderWindow);
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(join(fixturesFolder, 'vscode_folder', 'nested_folder')) });
assert.strictEqual(findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
test('More specific existing window wins', () => {
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(join(fixturesFolder, 'no_vscode_folder')) });
const nestedFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) });
assert.strictEqual(findWindowOnFile([window, nestedFolderWindow], URI.file(join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), nestedFolderWindow);
});
test('Workspace folder wins', () => {
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedWorkspace: testWorkspace });
assert.strictEqual(findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
});

View File

@@ -1,292 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as os from 'os';
import * as path from 'vs/base/common/path';
import { restoreWindowsState, getWindowsStateStoreData } from 'vs/platform/windows/electron-main/windowsStateStorage';
import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { URI } from 'vs/base/common/uri';
import { IWindowsState, IWindowState } from 'vs/platform/windows/electron-main/windowsMainService';
function getUIState(): IWindowUIState {
return {
x: 0,
y: 10,
width: 100,
height: 200,
mode: 0
};
}
function toWorkspace(uri: URI): IWorkspaceIdentifier {
return {
id: '1234',
configPath: uri
};
}
function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void {
assert.equal(u1 && u1.toString(), u2 && u2.toString(), message);
}
function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void {
if (!w1 || !w2) {
assert.equal(w1, w2, message);
return;
}
assert.equal(w1.id, w2.id, message);
assertEqualURI(w1.configPath, w2.configPath, message);
}
function assertEqualWindowState(expected: IWindowState | undefined, actual: IWindowState | undefined, message?: string) {
if (!expected || !actual) {
assert.deepEqual(expected, actual, message);
return;
}
assert.equal(expected.backupPath, actual.backupPath, message);
assertEqualURI(expected.folderUri, actual.folderUri, message);
assert.equal(expected.remoteAuthority, actual.remoteAuthority, message);
assertEqualWorkspace(expected.workspace, actual.workspace, message);
assert.deepEqual(expected.uiState, actual.uiState, message);
}
function assertEqualWindowsState(expected: IWindowsState, actual: IWindowsState, message?: string) {
assertEqualWindowState(expected.lastPluginDevelopmentHostWindow, actual.lastPluginDevelopmentHostWindow, message);
assertEqualWindowState(expected.lastActiveWindow, actual.lastActiveWindow, message);
assert.equal(expected.openedWindows.length, actual.openedWindows.length, message);
for (let i = 0; i < expected.openedWindows.length; i++) {
assertEqualWindowState(expected.openedWindows[i], actual.openedWindows[i], message);
}
}
function assertRestoring(state: IWindowsState, message?: string) {
const stored = getWindowsStateStoreData(state);
const restored = restoreWindowsState(stored);
assertEqualWindowsState(state, restored, message);
}
const testBackupPath1 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder1');
const testBackupPath2 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder2');
const testWSPath = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'test.code-workspace'));
const testFolderURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFolder'));
const testRemoteFolderURI = URI.parse('foo://bar/c/d');
suite('Windows State Storing', () => {
test('storing and restoring', () => {
let windowState: IWindowsState;
windowState = {
openedWindows: []
};
assertRestoring(windowState, 'no windows');
windowState = {
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState() }]
};
assertRestoring(windowState, 'empty workspace');
windowState = {
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), workspace: toWorkspace(testWSPath) }]
};
assertRestoring(windowState, 'workspace');
windowState = {
openedWindows: [{ backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }]
};
assertRestoring(windowState, 'folder');
windowState = {
openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), folderUri: testFolderURI }, { backupPath: testBackupPath1, uiState: getUIState(), folderUri: testRemoteFolderURI, remoteAuthority: 'bar' }]
};
assertRestoring(windowState, 'multiple windows');
windowState = {
lastActiveWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI },
openedWindows: []
};
assertRestoring(windowState, 'lastActiveWindow');
windowState = {
lastPluginDevelopmentHostWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI },
openedWindows: []
};
assertRestoring(windowState, 'lastPluginDevelopmentHostWindow');
});
test('open 1_31', () => {
const v1_31_workspace = `{
"openedWindows": [],
"lastActiveWindow": {
"workspace": {
"id": "a41787288b5e9cc1a61ba2dd84cd0d80",
"configPath": "/home/user/workspaces/code-and-docs.code-workspace"
},
"backupPath": "/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80",
"uiState": {
"mode": 0,
"x": 0,
"y": 27,
"width": 2560,
"height": 1364
}
}
}`;
let windowsState = restoreWindowsState(JSON.parse(v1_31_workspace));
let expected: IWindowsState = {
openedWindows: [],
lastActiveWindow: {
backupPath: '/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80',
uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 },
workspace: { id: 'a41787288b5e9cc1a61ba2dd84cd0d80', configPath: URI.file('/home/user/workspaces/code-and-docs.code-workspace') }
}
};
assertEqualWindowsState(expected, windowsState, 'v1_31_workspace');
const v1_31_folder = `{
"openedWindows": [],
"lastPluginDevelopmentHostWindow": {
"folderUri": {
"$mid": 1,
"fsPath": "/home/user/workspaces/testing/customdata",
"external": "file:///home/user/workspaces/testing/customdata",
"path": "/home/user/workspaces/testing/customdata",
"scheme": "file"
},
"uiState": {
"mode": 1,
"x": 593,
"y": 617,
"width": 1625,
"height": 595
}
}
}`;
windowsState = restoreWindowsState(JSON.parse(v1_31_folder));
expected = {
openedWindows: [],
lastPluginDevelopmentHostWindow: {
uiState: { mode: WindowMode.Normal, x: 593, y: 617, width: 1625, height: 595 },
folderUri: URI.parse('file:///home/user/workspaces/testing/customdata')
}
};
assertEqualWindowsState(expected, windowsState, 'v1_31_folder');
const v1_31_empty_window = ` {
"openedWindows": [
],
"lastActiveWindow": {
"backupPath": "C:\\\\Users\\\\Mike\\\\AppData\\\\Roaming\\\\Code\\\\Backups\\\\1549538599815",
"uiState": {
"mode": 0,
"x": -8,
"y": -8,
"width": 2576,
"height": 1344
}
}
}`;
windowsState = restoreWindowsState(JSON.parse(v1_31_empty_window));
expected = {
openedWindows: [],
lastActiveWindow: {
backupPath: 'C:\\Users\\Mike\\AppData\\Roaming\\Code\\Backups\\1549538599815',
uiState: { mode: WindowMode.Maximized, x: -8, y: -8, width: 2576, height: 1344 }
}
};
assertEqualWindowsState(expected, windowsState, 'v1_31_empty_window');
});
test('open 1_32', () => {
const v1_32_workspace = `{
"openedWindows": [],
"lastActiveWindow": {
"workspaceIdentifier": {
"id": "53b714b46ef1a2d4346568b4f591028c",
"configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace"
},
"backupPath": "/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c",
"uiState": {
"mode": 0,
"x": 0,
"y": 27,
"width": 2560,
"height": 1364
}
}
}`;
let windowsState = restoreWindowsState(JSON.parse(v1_32_workspace));
let expected: IWindowsState = {
openedWindows: [],
lastActiveWindow: {
backupPath: '/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c',
uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 },
workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') }
}
};
assertEqualWindowsState(expected, windowsState, 'v1_32_workspace');
const v1_32_folder = `{
"openedWindows": [],
"lastActiveWindow": {
"folder": "file:///home/user/workspaces/testing/folding",
"backupPath": "/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5",
"uiState": {
"mode": 1,
"x": 625,
"y": 263,
"width": 1718,
"height": 953
}
}
}`;
windowsState = restoreWindowsState(JSON.parse(v1_32_folder));
expected = {
openedWindows: [],
lastActiveWindow: {
backupPath: '/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5',
uiState: { mode: WindowMode.Normal, x: 625, y: 263, width: 1718, height: 953 },
folderUri: URI.parse('file:///home/user/workspaces/testing/folding')
}
};
assertEqualWindowsState(expected, windowsState, 'v1_32_folder');
const v1_32_empty_window = ` {
"openedWindows": [
],
"lastActiveWindow": {
"backupPath": "/home/user/.config/code-oss-dev/Backups/1549539668998",
"uiState": {
"mode": 1,
"x": 768,
"y": 336,
"width": 1024,
"height": 768
}
}
}`;
windowsState = restoreWindowsState(JSON.parse(v1_32_empty_window));
expected = {
openedWindows: [],
lastActiveWindow: {
backupPath: '/home/user/.config/code-oss-dev/Backups/1549539668998',
uiState: { mode: WindowMode.Normal, x: 768, y: 336, width: 1024, height: 768 }
}
};
assertEqualWindowsState(expected, windowsState, 'v1_32_empty_window');
});
});

View File

@@ -1,127 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as path from 'vs/base/common/path';
import { IBestWindowOrFolderOptions, IWindowContext, findBestWindowOrFolderForFile, OpenContext } from 'vs/platform/windows/node/window';
import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { getPathFromAmdModule } from 'vs/base/common/amd';
const fixturesFolder = getPathFromAmdModule(require, './fixtures');
const testWorkspace: IWorkspaceIdentifier = {
id: Date.now().toString(),
configPath: URI.file(path.join(fixturesFolder, 'workspaces.json'))
};
const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath);
function options(custom?: Partial<IBestWindowOrFolderOptions<IWindowContext>>): IBestWindowOrFolderOptions<IWindowContext> {
return {
windows: [],
newWindow: false,
context: OpenContext.CLI,
codeSettingsFolder: '_vscode',
localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; },
...custom
};
}
const vscodeFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
const lastActiveWindow: IWindowContext = { lastFocusTime: 3, openedFolderUri: undefined };
const noVscodeFolderWindow: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
const windows: IWindowContext[] = [
vscodeFolderWindow,
lastActiveWindow,
noVscodeFolderWindow,
];
suite('WindowsFinder', () => {
test('New window without folder when no windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options()), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
newWindow: true
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
context: OpenContext.API
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), null);
assert.equal(findBestWindowOrFolderForFile(options({
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'new_folder', 'new_file.txt'))
})), null);
});
test('New window without folder when windows exist', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
newWindow: true
})), null);
});
test('Last active window', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder2', 'file.txt'))
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows: [lastActiveWindow, noVscodeFolderWindow],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')),
})), lastActiveWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt')),
context: OpenContext.API
})), lastActiveWindow);
});
test('Existing window with folder', () => {
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'file.txt'))
})), noVscodeFolderWindow);
assert.equal(findBestWindowOrFolderForFile(options({
windows,
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt'))
})), vscodeFolderWindow);
const window: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), window);
});
test('More specific existing window wins', () => {
const window: IWindowContext = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
const nestedFolderWindow: IWindowContext = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window, nestedFolderWindow],
fileUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt'))
})), nestedFolderWindow);
});
test('Workspace folder wins', () => {
const window: IWindowContext = { lastFocusTime: 1, openedWorkspace: testWorkspace };
assert.equal(findBestWindowOrFolderForFile(options({
windows: [window],
fileUri: URI.file(path.join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt'))
})), window);
});
});