Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)

* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79

* Fix breaks

* Extension management fixes

* Fix breaks in windows bundling

* Fix/skip failing tests

* Update distro

* Add clear to nuget.config

* Add hygiene task

* Bump distro

* Fix hygiene issue

* Add build to hygiene exclusion

* Update distro

* Update hygiene

* Hygiene exclusions

* Update tsconfig

* Bump distro for server breaks

* Update build config

* Update darwin path

* Add done calls to notebook tests

* Skip failing tests

* Disable smoke tests
This commit is contained in:
Karl Burtram
2021-02-09 16:15:05 -08:00
committed by GitHub
parent 6f192f9af5
commit ce612a3d96
1929 changed files with 68012 additions and 34564 deletions

View File

@@ -6,7 +6,6 @@
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService';
export class ActiveWindowManager extends Disposable {
@@ -15,19 +14,25 @@ export class ActiveWindowManager extends Disposable {
private activeWindowId: number | undefined;
constructor(@IElectronMainService electronService: IElectronMainService) {
constructor({ onDidOpenWindow, onDidFocusWindow, getActiveWindowId }: {
onDidOpenWindow: Event<number>,
onDidFocusWindow: Event<number>,
getActiveWindowId(): Promise<number | undefined>
}) {
super();
// remember last active window id upon events
const onActiveWindowChange = Event.latch(Event.any(electronService.onWindowOpen, electronService.onWindowFocus));
const onActiveWindowChange = Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow));
onActiveWindowChange(this.setActiveWindow, this, this.disposables);
// resolve current active window
this.firstActiveWindowIdPromise = createCancelablePromise(() => electronService.getActiveWindowId(-1));
this.firstActiveWindowIdPromise = createCancelablePromise(() => getActiveWindowId());
(async () => {
try {
const windowId = await this.firstActiveWindowIdPromise;
this.activeWindowId = (typeof this.activeWindowId === 'number') ? this.activeWindowId : windowId;
} catch (error) {
// ignore
} finally {
this.firstActiveWindowIdPromise = undefined;
}

View File

@@ -4,14 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import { isMacintosh, isLinux, isWeb, IProcessEnvironment } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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 { ColorScheme } from 'vs/platform/theme/common/theme';
export const WindowMinimumSize = {
WIDTH: 400,
WIDTH_WITH_VERTICAL_PANEL: 600,
HEIGHT: 270
};
export interface IBaseOpenWindowsOptions {
forceReuseWindow?: boolean;
@@ -80,8 +84,8 @@ export function isFileToOpen(uriToOpen: IWindowOpenable): uriToOpen is IFileToOp
export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden' | 'compact';
export function getMenuBarVisibility(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): MenuBarVisibility {
const titleBarStyle = getTitleBarStyle(configurationService, environment, isExtensionDevelopment);
export function getMenuBarVisibility(configurationService: IConfigurationService): MenuBarVisibility {
const titleBarStyle = getTitleBarStyle(configurationService);
const menuBarVisibility = configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
if (titleBarStyle === 'native' && menuBarVisibility === 'compact') {
@@ -99,7 +103,7 @@ export interface IWindowSettings {
openFilesInNewWindow: 'on' | 'off' | 'default';
openFoldersInNewWindow: 'on' | 'off' | 'default';
openWithoutArgumentsInNewWindow: 'on' | 'off';
restoreWindows: 'all' | 'folders' | 'one' | 'none';
restoreWindows: 'preserve' | 'all' | 'folders' | 'one' | 'none';
restoreFullscreen: boolean;
zoomLevel: number;
titleBarStyle: 'native' | 'custom';
@@ -111,19 +115,15 @@ export interface IWindowSettings {
enableMenuBarMnemonics: boolean;
closeWhenEmpty: boolean;
clickThroughInactive: boolean;
enableExperimentalProxyLoginDialog: boolean;
}
export function getTitleBarStyle(configurationService: IConfigurationService, environment: IEnvironmentService, isExtensionDevelopment = environment.isExtensionDevelopment): 'native' | 'custom' {
export function getTitleBarStyle(configurationService: IConfigurationService): 'native' | 'custom' {
if (isWeb) {
return 'custom';
}
const configuration = configurationService.getValue<IWindowSettings>('window');
const isDev = !environment.isBuilt || isExtensionDevelopment;
if (isMacintosh && isDev) {
return 'native'; // not enabled when developing due to https://github.com/electron/electron/issues/3647
}
const configuration = configurationService.getValue<IWindowSettings | undefined>('window');
if (configuration) {
const useNativeTabs = isMacintosh && configuration.nativeTabs === true;
@@ -133,7 +133,7 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en
const useSimpleFullScreen = isMacintosh && configuration.nativeFullScreen === false;
if (useSimpleFullScreen) {
return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/Microsoft/vscode/issues/63291)
return 'native'; // simple fullscreen does not work well with custom title style (https://github.com/microsoft/vscode/issues/63291)
}
const style = configuration.titleBarStyle;
@@ -207,18 +207,27 @@ export interface INativeRunKeybindingInWindowRequest {
userSettingsLabel: string;
}
export interface IColorScheme {
dark: boolean;
highContrast: boolean;
}
export interface IWindowConfiguration {
sessionId: string;
remoteAuthority?: string;
colorScheme: ColorScheme;
colorScheme: IColorScheme;
autoDetectHighContrast?: boolean;
filesToOpenOrCreate?: IPath[];
filesToDiff?: IPath[];
}
export interface IOSConfiguration {
release: string;
}
export interface INativeWindowConfiguration extends IWindowConfiguration, NativeParsedArgs {
mainPid: number;
@@ -245,6 +254,8 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native
userEnv: IProcessEnvironment;
filesToWait?: IPathsToWaitFor;
os: IOSConfiguration;
}
/**

View File

@@ -12,8 +12,9 @@ import { IProcessEnvironment } from 'vs/base/common/platform';
import { 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 } from 'electron';
import { Rectangle, BrowserWindow, WebContents } from 'electron';
import { IDisposable } from 'vs/base/common/lifecycle';
import { CancellationToken } from 'vs/base/common/cancellation';
export interface IWindowState {
width?: number;
@@ -33,6 +34,11 @@ export const enum WindowMode {
export interface ICodeWindow extends IDisposable {
readonly onLoad: Event<void>;
readonly onReady: Event<void>;
readonly onClose: Event<void>;
readonly onDestroy: Event<void>;
readonly whenClosedOrLoaded: Promise<void>;
readonly id: number;
@@ -59,7 +65,7 @@ export interface ICodeWindow extends IDisposable {
addTabbedWindow(window: ICodeWindow): void;
load(config: INativeWindowConfiguration, isReload?: boolean): void;
reload(configuration?: INativeWindowConfiguration, cli?: NativeParsedArgs): void;
reload(cli?: NativeParsedArgs): void;
focus(options?: { force: boolean }): void;
close(): void;
@@ -67,7 +73,7 @@ export interface ICodeWindow extends IDisposable {
getBounds(): Rectangle;
send(channel: string, ...args: any[]): void;
sendWhenReady(channel: string, ...args: any[]): void;
sendWhenReady(channel: string, token: CancellationToken, ...args: any[]): void;
readonly isFullScreen: boolean;
toggleFullScreen(): void;
@@ -98,6 +104,7 @@ export interface IWindowsMainService {
readonly _serviceBrand: undefined;
readonly onWindowOpened: Event<ICodeWindow>;
readonly onWindowReady: Event<ICodeWindow>;
readonly onWindowsCountChanged: Event<IWindowsCountChangedEvent>;
@@ -108,9 +115,11 @@ export interface IWindowsMainService {
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
getFocusedWindow(): ICodeWindow | undefined;
getLastActiveWindow(): ICodeWindow | undefined;
getWindowById(windowId: number): ICodeWindow | undefined;
getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined;
getWindows(): ICodeWindow[];
getWindowCount(): number;
}

View File

@@ -7,14 +7,13 @@ import * as fs from 'fs';
import { basename, normalize, join, posix } from 'vs/base/common/path';
import { localize } from 'vs/nls';
import * as arrays from 'vs/base/common/arrays';
import { mixin } from 'vs/base/common/objects';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IStateService } from 'vs/platform/state/node/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { screen, BrowserWindow, MessageBoxOptions, Display, app } from 'electron';
import { screen, BrowserWindow, MessageBoxOptions, Display, app, WebContents } from 'electron';
import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
@@ -40,6 +39,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { isWindowsDriveLetter, toSlashes, parseLineAndColumnAware } from 'vs/base/common/extpath';
import { CharCode } from 'vs/base/common/charCode';
import { getPathLabel } from 'vs/base/common/labels';
import { CancellationToken } from 'vs/base/common/cancellation';
export interface IWindowState {
workspace?: IWorkspaceIdentifier;
@@ -59,7 +59,7 @@ interface INewWindowState extends ISingleWindowState {
hasDefaultState?: boolean;
}
type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none';
type RestoreWindowsSetting = 'preserve' | 'all' | 'folders' | 'one' | 'none';
interface IOpenBrowserWindowOptions {
userEnv?: IProcessEnvironment;
@@ -72,7 +72,7 @@ interface IOpenBrowserWindowOptions {
initialStartup?: boolean;
fileInputs?: IFileInputs;
filesToOpen?: IFilesToOpen;
forceNewWindow?: boolean;
forceNewTabbedWindow?: boolean;
@@ -87,7 +87,7 @@ interface IPathParseOptions {
remoteAuthority?: string;
}
interface IFileInputs {
interface IFilesToOpen {
filesToOpenOrCreate: IPath[];
filesToDiff: IPath[];
filesToWait?: IPathsToWaitFor;
@@ -163,6 +163,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private shuttingDown = false;
private readonly _onWindowOpened = this._register(new Emitter<ICodeWindow>());
readonly onWindowOpened = this._onWindowOpened.event;
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
readonly onWindowReady = this._onWindowReady.event;
@@ -174,7 +177,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private readonly initialUserEnv: IProcessEnvironment,
@ILogService private readonly logService: ILogService,
@IStateService private readonly stateService: IStateService,
@IEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IEnvironmentMainService private readonly environmentService: IEnvironmentMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@@ -304,7 +307,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
currentWindowsState.lastPluginDevelopmentHostWindow = this.toWindowState(extensionHostWindow);
}
// 3.) All windows (except extension host) for N >= 2 to support restoreWindows: all or for auto update
// 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
@@ -382,14 +385,17 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
this.logService.trace('windowsManager#open');
openConfig = this.validateOpenConfig(openConfig);
const pathsToOpen = this.getPathsToOpen(openConfig);
const foldersToAdd: IFolderPathToOpen[] = [];
const foldersToOpen: IFolderPathToOpen[] = [];
const workspacesToOpen: IWorkspacePathToOpen[] = [];
const emptyToRestore: IEmptyWindowBackupInfo[] = []; // empty windows with backupPath
let emptyToOpen: number = 0;
let fileInputs: IFileInputs | undefined; // collect all file inputs
const workspacesToRestore: IWorkspacePathToOpen[] = [];
const emptyToRestore: IEmptyWindowBackupInfo[] = [];
let filesToOpen: IFilesToOpen | undefined;
let emptyToOpen = 0;
// Identify things to open from open config
const pathsToOpen = this.getPathsToOpen(openConfig);
this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen);
for (const path of pathsToOpen) {
if (isFolderPathToOpen(path)) {
if (openConfig.addMode) {
@@ -402,10 +408,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
} else if (isWorkspacePathToOpen(path)) {
workspacesToOpen.push(path);
} else if (path.fileUri) {
if (!fileInputs) {
fileInputs = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
if (!filesToOpen) {
filesToOpen = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority };
}
fileInputs.filesToOpenOrCreate.push(path);
filesToOpen.filesToOpenOrCreate.push(path);
} else if (path.backupPath) {
emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority });
} else {
@@ -415,24 +421,23 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// When run with --diff, take the files to open as files to diff
// if there are exactly two files provided.
if (fileInputs && openConfig.diffMode && fileInputs.filesToOpenOrCreate.length === 2) {
fileInputs.filesToDiff = fileInputs.filesToOpenOrCreate;
fileInputs.filesToOpenOrCreate = [];
if (openConfig.diffMode && filesToOpen?.filesToOpenOrCreate.length === 2) {
filesToOpen.filesToDiff = filesToOpen.filesToOpenOrCreate;
filesToOpen.filesToOpenOrCreate = [];
}
// When run with --wait, make sure we keep the paths to wait for
if (fileInputs && openConfig.waitMarkerFileURI) {
fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI };
if (filesToOpen && openConfig.waitMarkerFileURI) {
filesToOpen.filesToWait = { paths: [...filesToOpen.filesToDiff, ...filesToOpen.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI };
}
//
// These are windows to restore because of hot-exit or from previous session (only performed once on startup!)
//
let workspacesToRestore: IWorkspacePathToOpen[] = [];
if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) {
if (openConfig.initialStartup) {
// Untitled workspaces are always restored
workspacesToRestore = this.workspacesMainService.getUntitledWorkspacesSync();
workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync());
workspacesToOpen.push(...workspacesToRestore);
// Empty windows with backups are always restored
@@ -442,11 +447,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
// Open based on config
const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);
const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, filesToOpen, foldersToAdd);
this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyToRestore.length}, emptyToOpen: ${emptyToOpen})`);
// Make sure to pass focus to the most relevant of the windows if we open multiple
if (usedWindows.length > 1) {
const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !openConfig.cli._.length && !openConfig.cli['file-uri'] && !openConfig.cli['folder-uri'] && !(openConfig.urisToOpen && openConfig.urisToOpen.length);
let focusLastOpened = true;
let focusLastWindow = true;
@@ -485,7 +492,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Remember in recent document list (unless this opens for extension development)
// Also do not add paths when files are opened for diffing, only if opened individually
const isDiff = fileInputs && fileInputs.filesToDiff.length > 0;
const isDiff = filesToOpen && filesToOpen.filesToDiff.length > 0;
if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) {
const recents: IRecent[] = [];
for (let pathToOpen of pathsToOpen) {
@@ -497,6 +504,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri });
}
}
this.workspacesHistoryMainService.addRecentlyOpened(recents);
}
@@ -527,7 +535,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
foldersToOpen: IFolderPathToOpen[],
emptyToRestore: IEmptyWindowBackupInfo[],
emptyToOpen: number,
fileInputs: IFileInputs | undefined,
filesToOpen: IFilesToOpen | undefined,
foldersToAdd: IFolderPathToOpen[]
) {
const usedWindows: ICodeWindow[] = [];
@@ -546,13 +554,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit
const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length;
if (potentialWindowsCount === 0 && fileInputs) {
if (potentialWindowsCount === 0 && filesToOpen) {
// Find suitable window or folder path to open files in
const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0];
const fileToCheck = filesToOpen.filesToOpenOrCreate[0] || filesToOpen.filesToDiff[0];
// only look at the windows with correct authority
const windows = WindowsMainService.WINDOWS.filter(window => fileInputs && window.remoteAuthority === fileInputs.remoteAuthority);
const windows = WindowsMainService.WINDOWS.filter(window => filesToOpen && window.remoteAuthority === filesToOpen.remoteAuthority);
const bestWindowOrFolder = findBestWindowOrFolderForFile({
windows,
@@ -579,10 +587,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
else {
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, filesToOpen));
// Reset these because we handled them
fileInputs = undefined;
filesToOpen = undefined;
}
}
@@ -592,14 +600,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
userEnv: openConfig.userEnv,
cli: openConfig.cli,
initialStartup: openConfig.initialStartup,
fileInputs,
filesToOpen,
forceNewWindow: true,
remoteAuthority: fileInputs.remoteAuthority,
remoteAuthority: filesToOpen.remoteAuthority,
forceNewTabbedWindow: openConfig.forceNewTabbedWindow
}));
// Reset these because we handled them
fileInputs = undefined;
filesToOpen = undefined;
}
}
@@ -611,14 +619,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, workspaceToOpen.workspace)));
if (windowsOnWorkspace.length > 0) {
const windowOnWorkspace = windowsOnWorkspace[0];
const fileInputsForWindow = (fileInputs?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined;
const filesToOpenInWindow = (filesToOpen?.remoteAuthority === windowOnWorkspace.remoteAuthority) ? filesToOpen : undefined;
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, filesToOpenInWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = undefined;
if (filesToOpenInWindow) {
filesToOpen = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -631,14 +639,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
const remoteAuthority = workspaceToOpen.remoteAuthority;
const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined;
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow));
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, filesToOpenInWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = undefined;
if (filesToOpenInWindow) {
filesToOpen = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -653,14 +661,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsMainService.WINDOWS, folderToOpen.folderUri)));
if (windowsOnFolderPath.length > 0) {
const windowOnFolderPath = windowsOnFolderPath[0];
const fileInputsForWindow = fileInputs?.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined;
const filesToOpenInWindow = filesToOpen?.remoteAuthority === windowOnFolderPath.remoteAuthority ? filesToOpen : undefined;
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, filesToOpenInWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = undefined;
if (filesToOpenInWindow) {
filesToOpen = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -674,14 +682,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
const remoteAuthority = folderToOpen.remoteAuthority;
const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined;
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow));
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, filesToOpenInWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = undefined;
if (filesToOpenInWindow) {
filesToOpen = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -693,13 +701,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (allEmptyToRestore.length > 0) {
allEmptyToRestore.forEach(emptyWindowBackupInfo => {
const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
const fileInputsForWindow = (fileInputs?.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
const filesToOpenInWindow = (filesToOpen?.remoteAuthority === remoteAuthority) ? filesToOpen : undefined;
usedWindows.push(this.openInBrowserWindow({
userEnv: openConfig.userEnv,
cli: openConfig.cli,
initialStartup: openConfig.initialStartup,
fileInputs: fileInputsForWindow,
filesToOpen: filesToOpenInWindow,
remoteAuthority,
forceNewWindow: true,
forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
@@ -707,8 +715,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = undefined;
if (filesToOpenInWindow) {
filesToOpen = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -716,18 +724,18 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
// Handle empty to open (only if no other window opened)
if (usedWindows.length === 0 || fileInputs) {
if (fileInputs && !emptyToOpen) {
if (usedWindows.length === 0 || filesToOpen) {
if (filesToOpen && !emptyToOpen) {
emptyToOpen++;
}
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
const remoteAuthority = filesToOpen ? filesToOpen.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
for (let i = 0; i < emptyToOpen; i++) {
usedWindows.push(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, fileInputs));
usedWindows.push(this.doOpenEmpty(openConfig, openFolderInNewWindow, remoteAuthority, filesToOpen));
// Reset these because we handled them
fileInputs = undefined;
filesToOpen = undefined;
openFolderInNewWindow = true; // any other window to open must open in new window then
}
}
@@ -735,21 +743,23 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return arrays.distinct(usedWindows);
}
private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow {
private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, filesToOpen?: IFilesToOpen): ICodeWindow {
this.logService.trace('windowsManager#doOpenFilesInExistingWindow');
window.focus(); // make sure window has focus
const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {};
if (fileInputs) {
params.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate;
params.filesToDiff = fileInputs.filesToDiff;
params.filesToWait = fileInputs.filesToWait;
if (filesToOpen) {
params.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate;
params.filesToDiff = filesToOpen.filesToDiff;
params.filesToWait = filesToOpen.filesToWait;
}
if (configuration.userEnv) {
params.termProgram = configuration.userEnv['TERM_PROGRAM'];
}
window.sendWhenReady('vscode:openFiles', params);
window.sendWhenReady('vscode:openFiles', CancellationToken.None, params);
return window;
}
@@ -758,13 +768,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
window.focus(); // make sure window has focus
const request: IAddFoldersRequest = { foldersToAdd };
window.sendWhenReady('vscode:addFolders', request);
window.sendWhenReady('vscode:addFolders', CancellationToken.None, request);
return window;
}
private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow {
private doOpenEmpty(openConfig: IOpenConfiguration, forceNewWindow: boolean, remoteAuthority: string | undefined, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow {
if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/97172
}
@@ -776,14 +785,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
remoteAuthority,
forceNewWindow,
forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
fileInputs,
filesToOpen,
windowToUse
});
}
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow {
private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, filesToOpen: IFilesToOpen | undefined, windowToUse?: ICodeWindow): ICodeWindow {
if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') {
windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587
windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/microsoft/vscode/issues/49587
}
return this.openInBrowserWindow({
@@ -792,7 +801,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
initialStartup: openConfig.initialStartup,
workspace: folderOrWorkspace.workspace,
folderUri: folderOrWorkspace.folderUri,
fileInputs,
filesToOpen,
remoteAuthority: folderOrWorkspace.remoteAuthority,
forceNewWindow,
forceNewTabbedWindow: openConfig.forceNewTabbedWindow,
@@ -803,6 +812,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
let windowsToOpen: IPathToOpen[];
let isCommandLineOrAPICall = false;
let restoredWindows = false;
// Extract paths: from API
if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
@@ -824,11 +834,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Extract windows: from previous session
else {
windowsToOpen = this.doGetWindowsFromLastSession();
restoredWindows = true;
}
// Convert multiple folders into workspace (if opened via API or CLI)
// This will ensure to open these folders in one window instead of multiple
// If we are in addMode, we should not do this because in that case all
// If we are in `addMode`, we should not do this because in that case all
// folders should be added to the existing window.
if (!openConfig.addMode && isCommandLineOrAPICall) {
const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
@@ -844,6 +855,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
}
// Check for `window.startup` setting to include all windows
// from the previous session if this is the initial startup and we have
// not restored windows already otherwise.
// Use `unshift` to ensure any new window to open comes last
// for proper focus treatment.
if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue<IWindowSettings | undefined>('window')?.restoreWindows === 'preserve') {
windowsToOpen.unshift(...this.doGetWindowsFromLastSession().filter(window => window.workspace || window.folderUri || window.backupPath));
}
return windowsToOpen;
}
@@ -905,7 +925,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
}
// file uris
const fileUris = cli['file-uri'];
if (fileUris) {
@@ -938,9 +957,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
private doGetWindowsFromLastSession(): IPathToOpen[] {
const restoreWindows = this.getRestoreWindowsSetting();
const restoreWindowsSetting = this.getRestoreWindowsSetting();
switch (restoreWindows) {
switch (restoreWindowsSetting) {
// none: we always open an empty window
case 'none':
@@ -951,9 +970,12 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// folders: restore last opened folders only
case 'one':
case 'all':
case 'preserve':
case 'folders':
// Collect previously opened windows
const openedWindows: IWindowState[] = [];
if (restoreWindows !== 'one') {
if (restoreWindowsSetting !== 'one') {
openedWindows.push(...this.windowsState.openedWindows);
}
if (this.windowsState.lastActiveWindow) {
@@ -962,17 +984,25 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
const windowsToOpen: IPathToOpen[] = [];
for (const openedWindow of openedWindows) {
if (openedWindow.workspace) { // Workspaces
// Workspaces
if (openedWindow.workspace) {
const pathToOpen = this.parseUri({ workspaceUri: openedWindow.workspace.configPath }, { remoteAuthority: openedWindow.remoteAuthority });
if (pathToOpen?.workspace) {
windowsToOpen.push(pathToOpen);
}
} else if (openedWindow.folderUri) { // Folders
}
// Folders
else if (openedWindow.folderUri) {
const pathToOpen = this.parseUri({ folderUri: openedWindow.folderUri }, { remoteAuthority: openedWindow.remoteAuthority });
if (pathToOpen?.folderUri) {
windowsToOpen.push(pathToOpen);
}
} else if (restoreWindows !== 'folders' && openedWindow.backupPath && !openedWindow.remoteAuthority) { // Local windows that were empty. Empty windows with backups will always be restored in open()
}
// Empty window, potentially editors open to be restored
else if (restoreWindowsSetting !== 'folders' && openedWindow.backupPath) {
windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority });
}
}
@@ -993,10 +1023,10 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (this.lifecycleMainService.wasRestarted) {
restoreWindows = 'all'; // always reopen all windows when an update was applied
} else {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
restoreWindows = windowConfig?.restoreWindows || 'all'; // by default restore all windows
if (!['all', 'folders', 'one', 'none'].includes(restoreWindows)) {
if (!['preserve', 'all', 'folders', 'one', 'none'].includes(restoreWindows)) {
restoreWindows = 'all'; // by default restore all windows
}
}
@@ -1089,7 +1119,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return undefined;
}
let lineNumber, columnNumber: number | undefined;
let lineNumber: number | undefined;
let columnNumber: number | undefined;
if (options.gotoLineMode) {
const parsedPath = parseLineAndColumnAware(anyPath);
@@ -1101,7 +1132,6 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// open remote if either specified in the cli even if it is a local file.
const remoteAuthority = options.remoteAuthority;
if (remoteAuthority) {
const first = anyPath.charCodeAt(0);
@@ -1110,7 +1140,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (isWindowsDriveLetter(first) && anyPath.charCodeAt(anyPath.charCodeAt(1)) === CharCode.Colon) {
anyPath = toSlashes(anyPath);
}
anyPath = '/' + anyPath;
anyPath = `/${anyPath}`;
}
const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath });
@@ -1125,13 +1156,13 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return { fileUri: uri, remoteAuthority };
}
}
return { folderUri: uri, remoteAuthority };
}
let candidate = normalize(anyPath);
try {
const candidateStat = fs.statSync(candidate);
if (candidateStat.isFile()) {
@@ -1187,7 +1218,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private shouldOpenNewWindow(openConfig: IOpenConfiguration): { openFolderInNewWindow: boolean; openFilesInNewWindow: boolean; } {
// let the user settings override how folders are open in a new window or same window unless we are forced
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
const openFolderInNewWindowConfig = windowConfig?.openFoldersInNewWindow || 'default' /* default */;
const openFilesInNewWindowConfig = windowConfig?.openFilesInNewWindow || 'off' /* default */;
@@ -1238,6 +1269,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return [existingWindow];
}
let folderUris = openConfig.cli['folder-uri'] || [];
let fileUris = openConfig.cli['file-uri'] || [];
let cliArgs = openConfig.cli._;
@@ -1288,23 +1320,26 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, uri)) {
return false;
}
return uri.authority === authority;
});
folderUris = folderUris.filter(uri => {
const u = this.argToUri(uri);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) {
folderUris = folderUris.filter(folderUriStr => {
const folderUri = this.argToUri(folderUriStr);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, folderUri)) {
return false;
}
return u ? u.authority === authority : false;
return folderUri ? folderUri.authority === authority : false;
});
fileUris = fileUris.filter(uri => {
const u = this.argToUri(uri);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, u)) {
fileUris = fileUris.filter(fileUriStr => {
const fileUri = this.argToUri(fileUriStr);
if (!!findWindowOnWorkspaceOrFolderUri(WindowsMainService.WINDOWS, fileUri)) {
return false;
}
return u ? u.authority === authority : false;
return fileUri ? fileUri.authority === authority : false;
});
openConfig.cli._ = cliArgs;
@@ -1334,7 +1369,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
// Build INativeWindowConfiguration from config and options
const configuration: INativeWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI
const configuration = { ...options.cli } as INativeWindowConfiguration;
configuration.appRoot = this.environmentService.appRoot;
configuration.machineId = this.machineId;
configuration.nodeCachedDataDir = this.environmentService.nodeCachedDataDir;
@@ -1346,11 +1381,11 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
configuration.folderUri = options.folderUri;
configuration.remoteAuthority = options.remoteAuthority;
const fileInputs = options.fileInputs;
if (fileInputs) {
configuration.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate;
configuration.filesToDiff = fileInputs.filesToDiff;
configuration.filesToWait = fileInputs.filesToWait;
const filesToOpen = options.filesToOpen;
if (filesToOpen) {
configuration.filesToOpenOrCreate = filesToOpen.filesToOpenOrCreate;
configuration.filesToDiff = filesToOpen.filesToDiff;
configuration.filesToWait = filesToOpen.filesToWait;
}
// if we know the backup folder upfront (for empty windows to restore), we can set it
@@ -1371,18 +1406,18 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// New window
if (!window) {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
const state = this.getNewWindowState(configuration);
// 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);
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;
allowFullscreen = !!(this.lifecycleMainService.wasRestarted || windowConfig?.restoreFullscreen);
if (allowFullscreen && isMacintosh && WindowsMainService.WINDOWS.some(win => win.isFullScreen)) {
// macOS: Electron does not allow to restore multiple windows in
@@ -1416,6 +1451,9 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Add to our list of windows
WindowsMainService.WINDOWS.push(createdWindow);
// Indicate new window via event
this._onWindowOpened.fire(createdWindow);
// Indicate number change via event
this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length - 1, newCount: WindowsMainService.WINDOWS.length });
@@ -1564,7 +1602,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
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>('window');
const windowConfig = this.configurationService.getValue<IWindowSettings | undefined>('window');
let ensureNoOverlap = true;
if (windowConfig?.newWindowDimensions) {
if (windowConfig.newWindowDimensions === 'maximized') {
@@ -1576,7 +1614,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
} 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)
state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/microsoft/vscode/issues/19331)
} else {
state = lastActiveState;
}
@@ -1611,6 +1649,25 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return state;
}
private onWindowClosed(win: ICodeWindow): void {
// Remove from our list so that Electron can clean it up
const index = WindowsMainService.WINDOWS.indexOf(win);
WindowsMainService.WINDOWS.splice(index, 1);
// Emit
this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length });
}
getFocusedWindow(): ICodeWindow | undefined {
const win = BrowserWindow.getFocusedWindow();
if (win) {
return this.getWindowById(win.id);
}
return undefined;
}
getLastActiveWindow(): ICodeWindow | undefined {
return getLastActiveWindow(WindowsMainService.WINDOWS);
}
@@ -1623,7 +1680,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
const focusedWindow = this.getFocusedWindow() || this.getLastActiveWindow();
if (focusedWindow) {
focusedWindow.sendWhenReady(channel, ...args);
focusedWindow.sendWhenReady(channel, CancellationToken.None, ...args);
}
}
@@ -1633,25 +1690,25 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
continue; // do not send if we are instructed to ignore it
}
window.sendWhenReady(channel, payload);
window.sendWhenReady(channel, CancellationToken.None, payload);
}
}
private getFocusedWindow(): ICodeWindow | undefined {
const win = BrowserWindow.getFocusedWindow();
if (win) {
return this.getWindowById(win.id);
}
return undefined;
}
getWindowById(windowId: number): ICodeWindow | undefined {
const res = WindowsMainService.WINDOWS.filter(window => window.id === windowId);
return arrays.firstOrDefault(res);
}
getWindowByWebContents(webContents: WebContents): ICodeWindow | undefined {
const browserWindow = BrowserWindow.fromWebContents(webContents);
if (!browserWindow) {
return undefined;
}
return this.getWindowById(browserWindow.id);
}
getWindows(): ICodeWindow[] {
return WindowsMainService.WINDOWS;
}
@@ -1659,14 +1716,4 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
getWindowCount(): number {
return WindowsMainService.WINDOWS.length;
}
private onWindowClosed(win: ICodeWindow): void {
// Remove from our list so that Electron can clean it up
const index = WindowsMainService.WINDOWS.indexOf(win);
WindowsMainService.WINDOWS.splice(index, 1);
// Emit
this._onWindowsCountChanged.fire({ oldCount: WindowsMainService.WINDOWS.length + 1, newCount: WindowsMainService.WINDOWS.length });
}
}

View File

@@ -16,7 +16,7 @@ export function applyZoom(zoomLevel: number): void {
setZoomFactor(zoomLevelToZoomFactor(zoomLevel));
// Cannot be trusted because the webFrame might take some time
// until it really applies the new zoom level
// See https://github.com/Microsoft/vscode/issues/26151
// See https://github.com/microsoft/vscode/issues/26151
setZoomLevel(zoomLevel, false /* isTrusted */);
}

View File

@@ -0,0 +1,292 @@
/*---------------------------------------------------------------------------------------------
* 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');
});
});