mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
79
src/vs/platform/windows/electron-main/windowsFinder.ts
Normal file
79
src/vs/platform/windows/electron-main/windowsFinder.ts
Normal 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
450
src/vs/platform/windows/electron-main/windowsStateHandler.ts
Normal file
450
src/vs/platform/windows/electron-main/windowsStateHandler.ts
Normal 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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
109
src/vs/platform/windows/test/electron-main/window.test.ts
Normal file
109
src/vs/platform/windows/test/electron-main/window.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user