mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-23 01:25:38 -05:00
Merge from vscode 313ede61cbad8f9dc748907b3384e059ddddb79a (#7436)
* Merge from vscode 313ede61cbad8f9dc748907b3384e059ddddb79a * fix strict null checks
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent } from 'electron';
|
||||
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, Event as IpcMainEvent, BrowserWindow } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
|
||||
import { WindowsManager } from 'vs/code/electron-main/windows';
|
||||
import { OpenContext, IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
@@ -78,6 +78,8 @@ import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService';
|
||||
import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
|
||||
@@ -85,6 +87,7 @@ export class CodeApplication extends Disposable {
|
||||
private static readonly TRUE_MACHINE_ID_KEY = 'telemetry.trueMachineId';
|
||||
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
private dialogMainService: IDialogMainService | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly mainIpcServer: Server,
|
||||
@@ -381,8 +384,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
// Setup Auth Handler
|
||||
const authHandler = appInstantiationService.createInstance(ProxyAuthHandler);
|
||||
this._register(authHandler);
|
||||
this._register(new ProxyAuthHandler());
|
||||
|
||||
// Open Windows
|
||||
const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));
|
||||
@@ -449,6 +451,7 @@ export class CodeApplication extends Disposable {
|
||||
}
|
||||
|
||||
services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv]));
|
||||
services.set(IDialogMainService, new SyncDescriptor(DialogMainService));
|
||||
services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess]));
|
||||
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService));
|
||||
|
||||
@@ -503,13 +506,13 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => {
|
||||
if (!timeout) {
|
||||
if (this.windowsMainService) {
|
||||
this.windowsMainService.showMessageBox({
|
||||
if (this.dialogMainService) {
|
||||
this.dialogMainService.showMessageBox({
|
||||
type: 'info',
|
||||
message: localize('trace.message', "Successfully created trace."),
|
||||
detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path),
|
||||
buttons: [localize('trace.ok', "Ok")]
|
||||
}, this.windowsMainService.getLastActiveWindow());
|
||||
}, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
}
|
||||
} else {
|
||||
this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`);
|
||||
@@ -580,6 +583,7 @@ export class CodeApplication extends Disposable {
|
||||
|
||||
// Propagate to clients
|
||||
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
|
||||
this.dialogMainService = accessor.get(IDialogMainService);
|
||||
|
||||
// Create a URL handler to open file URIs in the active window
|
||||
const environmentService = accessor.get(IEnvironmentService);
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { BrowserWindow, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron';
|
||||
|
||||
@@ -22,18 +21,21 @@ type Credentials = {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export class ProxyAuthHandler {
|
||||
export class ProxyAuthHandler extends Disposable {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private retryCount = 0;
|
||||
private disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
@IWindowsMainService private readonly windowsMainService: IWindowsMainService
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
|
||||
onLogin(this.onLogin, this, this.disposables);
|
||||
this._register(onLogin(this.onLogin, this));
|
||||
}
|
||||
|
||||
private onLogin({ event, authInfo, cb }: LoginEvent): void {
|
||||
@@ -61,10 +63,9 @@ export class ProxyAuthHandler {
|
||||
}
|
||||
};
|
||||
|
||||
const focusedWindow = this.windowsMainService.getFocusedWindow();
|
||||
|
||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
opts.parent = focusedWindow.win;
|
||||
opts.parent = focusedWindow;
|
||||
opts.modal = true;
|
||||
}
|
||||
|
||||
@@ -89,8 +90,4 @@ export class ProxyAuthHandler {
|
||||
win.close();
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { assign } from 'vs/base/common/objects';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { ISharedProcess } from 'vs/platform/windows/electron-main/windows';
|
||||
import { ISharedProcess } from 'vs/platform/ipc/electron-main/sharedProcessMainService';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService';
|
||||
|
||||
@@ -561,7 +561,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
autoDetectHighContrast = false;
|
||||
}
|
||||
windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme();
|
||||
windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled;
|
||||
windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled();
|
||||
|
||||
// Title style related
|
||||
windowConfiguration.maximized = this._win.isMaximized();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import { basename, normalize, join, dirname } from 'vs/base/common/path';
|
||||
import { basename, normalize, join, } from 'vs/base/common/path';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { assign, mixin } from 'vs/base/common/objects';
|
||||
@@ -13,34 +13,36 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup';
|
||||
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter, shell, MessageBoxReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Display } from 'electron';
|
||||
import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display } from 'electron';
|
||||
import { parseLineAndColumnAware } from 'vs/code/node/paths';
|
||||
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';
|
||||
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows';
|
||||
import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, IPathsToWaitFor, isFileToOpen, isWorkspaceToOpen, isFolderToOpen, IWindowOpenable, IOpenEmptyWindowOptions, IAddFoldersRequest } from 'vs/platform/windows/common/windows';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder';
|
||||
import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/platform/windows/node/window';
|
||||
import { Event as CommonEvent, Emitter } from 'vs/base/common/event';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
|
||||
import { IWorkspacesHistoryMainService } from 'vs/platform/workspaces/electron-main/workspacesHistoryMainService';
|
||||
import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import { IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IEnterWorkspaceResult, IRecent } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension, IRecent } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { normalizeNFC } from 'vs/base/common/normalization';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { exists, dirExists } from 'vs/base/node/pfs';
|
||||
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
|
||||
import { dirExists } from 'vs/base/node/pfs';
|
||||
import { getComparisonKey, isEqual, normalizePath, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources';
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage';
|
||||
import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogs';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { isWindowsDriveLetter, toSlashes } from 'vs/base/common/extpath';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
const enum WindowError {
|
||||
UNRESPONSIVE = 1,
|
||||
@@ -167,9 +169,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
private readonly windowsState: IWindowsState;
|
||||
private lastClosedWindowState?: IWindowState;
|
||||
|
||||
private readonly dialogs: Dialogs;
|
||||
private readonly workspacesManager: WorkspacesManager;
|
||||
|
||||
private readonly _onWindowReady = this._register(new Emitter<ICodeWindow>());
|
||||
readonly onWindowReady: CommonEvent<ICodeWindow> = this._onWindowReady.event;
|
||||
|
||||
@@ -194,7 +193,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IWorkspacesHistoryMainService private readonly workspacesHistoryMainService: IWorkspacesHistoryMainService,
|
||||
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IDialogMainService private readonly dialogMainService: IDialogMainService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -203,9 +203,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
this.windowsState.openedWindows = [];
|
||||
}
|
||||
|
||||
this.dialogs = new Dialogs(stateService, this);
|
||||
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this);
|
||||
|
||||
this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners());
|
||||
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex());
|
||||
}
|
||||
@@ -260,6 +257,11 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
this.lastClosedWindowState = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Signal a window is ready after having entered a workspace
|
||||
this._register(this.workspacesMainService.onWorkspaceEntered(event => {
|
||||
this._onWindowReady.fire(event.window);
|
||||
}));
|
||||
}
|
||||
|
||||
// Note that onBeforeShutdown() and onBeforeWindowClose() are fired in different order depending on the OS:
|
||||
@@ -380,12 +382,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
};
|
||||
}
|
||||
|
||||
async openExternal(url: string): Promise<boolean> {
|
||||
shell.openExternal(url);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
open(openConfig: IOpenConfiguration): ICodeWindow[] {
|
||||
this.logService.trace('windowsManager#open');
|
||||
openConfig = this.validateOpenConfig(openConfig);
|
||||
@@ -776,7 +772,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
private doAddFoldersToExistingWindow(window: ICodeWindow, foldersToAdd: URI[]): ICodeWindow {
|
||||
window.focus(); // make sure window has focus
|
||||
|
||||
window.sendWhenReady('vscode:addFolders', { foldersToAdd });
|
||||
const request: IAddFoldersRequest = { foldersToAdd };
|
||||
|
||||
window.sendWhenReady('vscode:addFolders', request);
|
||||
|
||||
return window;
|
||||
}
|
||||
@@ -862,7 +860,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
path.label = pathToOpen.label;
|
||||
pathsToOpen.push(path);
|
||||
} else {
|
||||
const uri = resourceFromURIToOpen(pathToOpen);
|
||||
const uri = this.resourceFromURIToOpen(pathToOpen);
|
||||
|
||||
// Warn about the invalid URI or path
|
||||
let message, detail;
|
||||
if (uri.scheme === Schemas.file) {
|
||||
@@ -881,7 +880,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
noLink: true
|
||||
};
|
||||
|
||||
this.dialogs.showMessageBox(options, this.getFocusedWindow());
|
||||
this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
|
||||
}
|
||||
}
|
||||
return pathsToOpen;
|
||||
@@ -1011,10 +1010,12 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
this.logService.error(`Invalid URI input string, scheme missing: ${arg}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return uri;
|
||||
} catch (e) {
|
||||
this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1023,7 +1024,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let uri = resourceFromURIToOpen(toOpen);
|
||||
let uri = this.resourceFromURIToOpen(toOpen);
|
||||
if (uri.scheme === Schemas.file) {
|
||||
return this.parsePath(uri.fsPath, options, isFileToOpen(toOpen));
|
||||
}
|
||||
@@ -1050,6 +1051,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
remoteAuthority
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
fileUri: uri,
|
||||
remoteAuthority
|
||||
@@ -1071,6 +1073,18 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
};
|
||||
}
|
||||
|
||||
private resourceFromURIToOpen(openable: IWindowOpenable): URI {
|
||||
if (isWorkspaceToOpen(openable)) {
|
||||
return openable.workspaceUri;
|
||||
}
|
||||
|
||||
if (isFolderToOpen(openable)) {
|
||||
return openable.folderUri;
|
||||
}
|
||||
|
||||
return openable.fileUri;
|
||||
}
|
||||
|
||||
private parsePath(anyPath: string, options: IPathParseOptions, forceOpenWorkspaceAsFile?: boolean): IPathToOpen | undefined {
|
||||
if (!anyPath) {
|
||||
return undefined;
|
||||
@@ -1086,11 +1100,36 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
anyPath = parsedPath.path;
|
||||
}
|
||||
|
||||
// open remote if either specified in the cli even if it is a local file. TODO@aeschli: Future idea: resolve in remote host context.
|
||||
// open remote if either specified in the cli even if it is a local file.
|
||||
const remoteAuthority = options.remoteAuthority;
|
||||
|
||||
const candidate = normalize(anyPath);
|
||||
if (remoteAuthority) {
|
||||
// assume it's a folder or workspace file
|
||||
|
||||
const first = anyPath.charCodeAt(0);
|
||||
// make absolute
|
||||
if (first !== CharCode.Slash) {
|
||||
if (isWindowsDriveLetter(first) && anyPath.charCodeAt(anyPath.charCodeAt(1)) === CharCode.Colon) {
|
||||
anyPath = toSlashes(anyPath);
|
||||
}
|
||||
anyPath = '/' + anyPath;
|
||||
}
|
||||
|
||||
const uri = URI.from({ scheme: Schemas.vscodeRemote, authority: remoteAuthority, path: anyPath });
|
||||
|
||||
if (hasWorkspaceFileExtension(anyPath)) {
|
||||
if (forceOpenWorkspaceAsFile) {
|
||||
return { fileUri: uri, remoteAuthority };
|
||||
}
|
||||
return { workspace: getWorkspaceIdentifier(uri), remoteAuthority };
|
||||
}
|
||||
return { folderUri: uri, remoteAuthority };
|
||||
}
|
||||
|
||||
let candidate = normalize(anyPath);
|
||||
|
||||
try {
|
||||
|
||||
const candidateStat = fs.statSync(candidate);
|
||||
if (candidateStat.isFile()) {
|
||||
|
||||
@@ -1185,7 +1224,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
|
||||
}
|
||||
|
||||
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): void {
|
||||
openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[] {
|
||||
|
||||
// Reload an existing extension development host window on the same path
|
||||
// We currently do not allow more than one extension development window
|
||||
@@ -1195,7 +1234,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
this.reload(existingWindow, openConfig.cli);
|
||||
existingWindow.focus(); // make sure it gets focus and is restored
|
||||
|
||||
return;
|
||||
return [existingWindow];
|
||||
}
|
||||
let folderUris = openConfig.cli['folder-uri'] || [];
|
||||
let fileUris = openConfig.cli['file-uri'] || [];
|
||||
@@ -1286,7 +1325,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
noRecentEntry: true,
|
||||
waitMarkerFileURI: openConfig.waitMarkerFileURI
|
||||
};
|
||||
this.open(openArgs);
|
||||
|
||||
return this.open(openArgs);
|
||||
}
|
||||
|
||||
private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow {
|
||||
@@ -1580,23 +1620,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
async enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | undefined> {
|
||||
const result = await this.workspacesManager.enterWorkspace(win, path);
|
||||
|
||||
return result ? this.doEnterWorkspace(win, result) : undefined;
|
||||
}
|
||||
|
||||
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
|
||||
|
||||
// Mark as recently opened
|
||||
this.workspacesHistoryMainService.addRecentlyOpened([{ workspace: result.workspace }]);
|
||||
|
||||
// Trigger Eevent to indicate load of workspace into window
|
||||
this._onWindowReady.fire(win);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow {
|
||||
const lastActive = this.getLastActiveWindow();
|
||||
if (lastActive) {
|
||||
@@ -1613,7 +1636,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
return getLastActiveWindow(WindowsManager.WINDOWS);
|
||||
}
|
||||
|
||||
getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
|
||||
private getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined {
|
||||
return getLastActiveWindow(WindowsManager.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority));
|
||||
}
|
||||
|
||||
@@ -1623,13 +1646,11 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
if (cli && (cli.remote !== remote)) {
|
||||
cli = { ...cli, remote };
|
||||
}
|
||||
const forceReuseWindow = options && options.reuse;
|
||||
const forceNewWindow = !forceReuseWindow;
|
||||
return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow });
|
||||
}
|
||||
|
||||
openNewTabbedWindow(context: OpenContext): ICodeWindow[] {
|
||||
return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
|
||||
const forceReuseWindow = options && options.forceReuseWindow;
|
||||
const forceNewWindow = !forceReuseWindow;
|
||||
|
||||
return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow });
|
||||
}
|
||||
|
||||
waitForWindowCloseOrLoad(windowId: number): Promise<void> {
|
||||
@@ -1666,7 +1687,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
|
||||
getFocusedWindow(): ICodeWindow | undefined {
|
||||
private getFocusedWindow(): ICodeWindow | undefined {
|
||||
const win = BrowserWindow.getFocusedWindow();
|
||||
if (win) {
|
||||
return this.getWindowById(win.id);
|
||||
@@ -1710,14 +1731,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
// Show Dialog
|
||||
this.dialogs.showMessageBox({
|
||||
this.dialogMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message: localize('appStalled', "The window is no longer responding"),
|
||||
detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."),
|
||||
noLink: true
|
||||
}, window).then(result => {
|
||||
}, window.win).then(result => {
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
@@ -1733,14 +1754,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
|
||||
// Crashed
|
||||
else {
|
||||
this.dialogs.showMessageBox({
|
||||
this.dialogMainService.showMessageBox({
|
||||
title: product.nameLong,
|
||||
type: 'warning',
|
||||
buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))],
|
||||
message: localize('appCrashed', "The window has crashed"),
|
||||
detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."),
|
||||
noLink: true
|
||||
}, window).then(result => {
|
||||
}, window.win).then(result => {
|
||||
if (!window.win) {
|
||||
return; // Return early if the window has been going down already
|
||||
}
|
||||
@@ -1770,8 +1791,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
async pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void> {
|
||||
const title = localize('open', "Open");
|
||||
const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title });
|
||||
const paths = await this.dialogMainService.pickFileFolder(options);
|
||||
if (paths) {
|
||||
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFileFolder', options.telemetryExtraData);
|
||||
const urisToOpen = await Promise.all(paths.map(async path => {
|
||||
@@ -1790,8 +1810,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
async pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void> {
|
||||
const title = localize('openFolder', "Open Folder");
|
||||
const paths = await this.dialogs.pick({ ...options, pickFolders: true, title });
|
||||
const paths = await this.dialogMainService.pickFolder(options);
|
||||
if (paths) {
|
||||
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData);
|
||||
this.open({
|
||||
@@ -1805,8 +1824,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
async pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void> {
|
||||
const title = localize('openFile', "Open File");
|
||||
const paths = await this.dialogs.pick({ ...options, pickFiles: true, title });
|
||||
const paths = await this.dialogMainService.pickFile(options);
|
||||
if (paths) {
|
||||
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData);
|
||||
this.open({
|
||||
@@ -1820,10 +1838,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
async pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise<void> {
|
||||
const title = localize('openWorkspaceTitle', "Open Workspace");
|
||||
const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open"));
|
||||
const filters = WORKSPACE_FILTER;
|
||||
const paths = await this.dialogs.pick({ ...options, pickFiles: true, title, filters, buttonLabel });
|
||||
const paths = await this.dialogMainService.pickWorkspace(options);
|
||||
if (paths) {
|
||||
this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData);
|
||||
this.open({
|
||||
@@ -1838,7 +1853,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
|
||||
private sendPickerTelemetry(paths: string[], telemetryEventName: string, telemetryExtraData?: ITelemetryData) {
|
||||
|
||||
const numberOfPaths = paths ? paths.length : 0;
|
||||
|
||||
// Telemetry
|
||||
@@ -1850,18 +1864,6 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
});
|
||||
}
|
||||
|
||||
showMessageBox(options: MessageBoxOptions, win?: ICodeWindow): Promise<MessageBoxReturnValue> {
|
||||
return this.dialogs.showMessageBox(options, win);
|
||||
}
|
||||
|
||||
showSaveDialog(options: SaveDialogOptions, win?: ICodeWindow): Promise<SaveDialogReturnValue> {
|
||||
return this.dialogs.showSaveDialog(options, win);
|
||||
}
|
||||
|
||||
showOpenDialog(options: OpenDialogOptions, win?: ICodeWindow): Promise<OpenDialogReturnValue> {
|
||||
return this.dialogs.showOpenDialog(options, win);
|
||||
}
|
||||
|
||||
quit(): void {
|
||||
|
||||
// If the user selected to exit from an extension development host window, do not quit, but just
|
||||
@@ -1879,240 +1881,3 @@ export class WindowsManager extends Disposable implements IWindowsMainService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions {
|
||||
|
||||
pickFolders?: boolean;
|
||||
pickFiles?: boolean;
|
||||
|
||||
title: string;
|
||||
buttonLabel?: string;
|
||||
filters?: FileFilter[];
|
||||
}
|
||||
|
||||
class Dialogs {
|
||||
|
||||
private static readonly workingDirPickerStorageKey = 'pickerWorkingDir';
|
||||
|
||||
private readonly mapWindowToDialogQueue: Map<number, Queue<void>>;
|
||||
private readonly noWindowDialogQueue: Queue<void>;
|
||||
|
||||
constructor(
|
||||
private readonly stateService: IStateService,
|
||||
private readonly windowsMainService: IWindowsMainService
|
||||
) {
|
||||
this.mapWindowToDialogQueue = new Map<number, Queue<void>>();
|
||||
this.noWindowDialogQueue = new Queue<void>();
|
||||
}
|
||||
|
||||
async pick(options: IInternalNativeOpenDialogOptions, win?: ICodeWindow): Promise<string[] | undefined> {
|
||||
|
||||
// Ensure dialog options
|
||||
const dialogOptions: OpenDialogOptions = {
|
||||
title: options.title,
|
||||
buttonLabel: options.buttonLabel,
|
||||
filters: options.filters
|
||||
};
|
||||
|
||||
// Ensure defaultPath
|
||||
dialogOptions.defaultPath = options.defaultPath || this.stateService.getItem<string>(Dialogs.workingDirPickerStorageKey);
|
||||
|
||||
|
||||
// Ensure properties
|
||||
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
|
||||
dialogOptions.properties = undefined; // let it override based on the booleans
|
||||
|
||||
if (options.pickFiles && options.pickFolders) {
|
||||
dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!dialogOptions.properties) {
|
||||
dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory'];
|
||||
}
|
||||
|
||||
if (isMacintosh) {
|
||||
dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files
|
||||
}
|
||||
|
||||
// Show Dialog
|
||||
const windowToUse = win || this.windowsMainService.getFocusedWindow();
|
||||
|
||||
const result = await this.showOpenDialog(dialogOptions, windowToUse);
|
||||
if (result && result.filePaths && result.filePaths.length > 0) {
|
||||
|
||||
// Remember path in storage for next time
|
||||
this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(result.filePaths[0]));
|
||||
|
||||
return result.filePaths;
|
||||
}
|
||||
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
|
||||
private getDialogQueue(window?: ICodeWindow): Queue<any> {
|
||||
if (!window) {
|
||||
return this.noWindowDialogQueue;
|
||||
}
|
||||
|
||||
let windowDialogQueue = this.mapWindowToDialogQueue.get(window.id);
|
||||
if (!windowDialogQueue) {
|
||||
windowDialogQueue = new Queue<any>();
|
||||
this.mapWindowToDialogQueue.set(window.id, windowDialogQueue);
|
||||
}
|
||||
|
||||
return windowDialogQueue;
|
||||
}
|
||||
|
||||
showMessageBox(options: MessageBoxOptions, window?: ICodeWindow): Promise<MessageBoxReturnValue> {
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
if (window) {
|
||||
return dialog.showMessageBox(window.win, options);
|
||||
}
|
||||
|
||||
return dialog.showMessageBox(options);
|
||||
});
|
||||
}
|
||||
|
||||
showSaveDialog(options: SaveDialogOptions, window?: ICodeWindow): Promise<SaveDialogReturnValue> {
|
||||
|
||||
function normalizePath(path: string | undefined): string | undefined {
|
||||
if (path && isMacintosh) {
|
||||
path = normalizeNFC(path); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
let result: SaveDialogReturnValue;
|
||||
if (window) {
|
||||
result = await dialog.showSaveDialog(window.win, options);
|
||||
} else {
|
||||
result = await dialog.showSaveDialog(options);
|
||||
}
|
||||
|
||||
result.filePath = normalizePath(result.filePath);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
showOpenDialog(options: OpenDialogOptions, window?: ICodeWindow): Promise<OpenDialogReturnValue> {
|
||||
|
||||
function normalizePaths(paths: string[] | undefined): string[] | undefined {
|
||||
if (paths && paths.length > 0 && isMacintosh) {
|
||||
paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return this.getDialogQueue(window).queue(async () => {
|
||||
|
||||
// Ensure the path exists (if provided)
|
||||
if (options.defaultPath) {
|
||||
const pathExists = await exists(options.defaultPath);
|
||||
if (!pathExists) {
|
||||
options.defaultPath = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Show dialog
|
||||
let result: OpenDialogReturnValue;
|
||||
if (window) {
|
||||
result = await dialog.showOpenDialog(window.win, options);
|
||||
} else {
|
||||
result = await dialog.showOpenDialog(options);
|
||||
}
|
||||
|
||||
result.filePaths = normalizePaths(result.filePaths);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspacesManager {
|
||||
|
||||
constructor(
|
||||
private readonly workspacesMainService: IWorkspacesMainService,
|
||||
private readonly backupMainService: IBackupMainService,
|
||||
private readonly windowsMainService: IWindowsMainService,
|
||||
) { }
|
||||
|
||||
async enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
|
||||
if (!window || !window.win || !window.isReady) {
|
||||
return null; // return early if the window is not ready or disposed
|
||||
}
|
||||
|
||||
const isValid = await this.isValidTargetWorkspacePath(window, path);
|
||||
if (!isValid) {
|
||||
return null; // return early if the workspace is not valid
|
||||
}
|
||||
|
||||
return this.doOpenWorkspace(window, getWorkspaceIdentifier(path));
|
||||
}
|
||||
|
||||
private async isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
|
||||
if (!path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) {
|
||||
return false; // window is already opened on a workspace with that path
|
||||
}
|
||||
|
||||
// Prevent overwriting a workspace that is currently opened in another window
|
||||
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), getWorkspaceIdentifier(path))) {
|
||||
const options: MessageBoxOptions = {
|
||||
title: product.nameLong,
|
||||
type: 'info',
|
||||
buttons: [localize('ok', "OK")],
|
||||
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", resourcesBasename(path)),
|
||||
detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."),
|
||||
noLink: true
|
||||
};
|
||||
|
||||
await this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true; // OK
|
||||
}
|
||||
|
||||
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
|
||||
window.focus();
|
||||
|
||||
// Register window for backups and migrate current backups over
|
||||
let backupPath: string | undefined;
|
||||
if (!window.config.extensionDevelopmentPath) {
|
||||
backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
|
||||
}
|
||||
|
||||
// if the window was opened on an untitled workspace, delete it.
|
||||
if (window.openedWorkspace && this.workspacesMainService.isUntitledWorkspace(window.openedWorkspace)) {
|
||||
this.workspacesMainService.deleteUntitledWorkspaceSync(window.openedWorkspace);
|
||||
}
|
||||
|
||||
// Update window configuration properly based on transition to workspace
|
||||
window.config.folderUri = undefined;
|
||||
window.config.workspace = workspace;
|
||||
window.config.backupPath = backupPath;
|
||||
|
||||
return { workspace, backupPath };
|
||||
}
|
||||
}
|
||||
|
||||
function resourceFromURIToOpen(openable: IWindowOpenable): URI {
|
||||
if (isWorkspaceToOpen(openable)) {
|
||||
return openable.workspaceUri;
|
||||
}
|
||||
|
||||
if (isFolderToOpen(openable)) {
|
||||
return openable.folderUri;
|
||||
}
|
||||
|
||||
return openable.fileUri;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,15 @@ export function restoreWindowsState(data: WindowsStateStorageData | undefined):
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -49,9 +52,11 @@ function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
|
||||
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) {
|
||||
@@ -59,11 +64,13 @@ function restoreWindowState(windowState: ISerializedWindowState): IWindowState {
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { spawn, ChildProcess, SpawnOptions } from 'child_process';
|
||||
import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
|
||||
import { ParsedArgs } from 'vs/platform/environment/common/environment';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { whenDeleted, writeFileSync } from 'vs/base/node/pfs';
|
||||
import { findFreePort, randomPort } from 'vs/base/node/ports';
|
||||
import { resolveTerminalEncoding } from 'vs/base/node/encoding';
|
||||
import * as iconv from 'iconv-lite';
|
||||
import { isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { ProfilingSession, Target } from 'v8-inspect-profiler';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
|
||||
@@ -179,7 +178,8 @@ export async function main(argv: string[]): Promise<any> {
|
||||
if (!stdinFileError) {
|
||||
|
||||
// Pipe into tmp file using terminals encoding
|
||||
resolveTerminalEncoding(verbose).then(encoding => {
|
||||
resolveTerminalEncoding(verbose).then(async encoding => {
|
||||
const iconv = await import('iconv-lite');
|
||||
const converterStream = iconv.decodeStream(encoding);
|
||||
process.stdin.pipe(converterStream).pipe(stdinFileStream);
|
||||
});
|
||||
@@ -360,10 +360,6 @@ export async function main(argv: string[]): Promise<any> {
|
||||
options['stdio'] = 'ignore';
|
||||
}
|
||||
|
||||
if (isLinux) {
|
||||
addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox
|
||||
}
|
||||
|
||||
const child = spawn(process.execPath, argv.slice(2), options);
|
||||
|
||||
if (args.wait && waitMarkerFilePath) {
|
||||
|
||||
@@ -1,131 +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 platform from 'vs/base/common/platform';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isEqual, isEqualOrParent } from 'vs/base/common/resources';
|
||||
|
||||
export interface ISimpleWindow {
|
||||
openedWorkspace?: IWorkspaceIdentifier;
|
||||
openedFolderUri?: URI;
|
||||
|
||||
extensionDevelopmentPath?: string[];
|
||||
lastFocusTime: number;
|
||||
}
|
||||
|
||||
export interface IBestWindowOrFolderOptions<W extends ISimpleWindow> {
|
||||
windows: W[];
|
||||
newWindow: boolean;
|
||||
context: OpenContext;
|
||||
fileUri?: URI;
|
||||
userHome?: string;
|
||||
codeSettingsFolder?: string;
|
||||
localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null;
|
||||
}
|
||||
|
||||
export function findBestWindowOrFolderForFile<W extends ISimpleWindow>({ 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 ISimpleWindow>(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 => isEqualOrParent(fileUri, folder.uri))) {
|
||||
return window;
|
||||
}
|
||||
} else {
|
||||
// use the config path instead
|
||||
if (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 && 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 ISimpleWindow>(windows: W[]): W | undefined {
|
||||
const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime));
|
||||
|
||||
return windows.filter(window => window.lastFocusTime === lastFocusedDate)[0];
|
||||
}
|
||||
|
||||
export function findWindowOnWorkspace<W extends ISimpleWindow>(windows: W[], workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)): W | null {
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
for (const window of windows) {
|
||||
// match on folder
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace)) {
|
||||
if (window.openedFolderUri && 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 ISimpleWindow>(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 && currPaths.some(p => matches(p))) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findWindowOnWorkspaceOrFolderUri<W extends ISimpleWindow>(windows: W[], uri: URI | undefined): W | null {
|
||||
if (!uri) {
|
||||
return null;
|
||||
}
|
||||
for (const window of windows) {
|
||||
// check for workspace config path
|
||||
if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, uri)) {
|
||||
return window;
|
||||
}
|
||||
|
||||
// check for folder path
|
||||
if (window.openedFolderUri && isEqual(window.openedFolderUri, uri)) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,128 +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 { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder';
|
||||
import { OpenContext } from 'vs/platform/windows/common/windows';
|
||||
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<ISimpleWindow>>): IBestWindowOrFolderOptions<ISimpleWindow> {
|
||||
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: ISimpleWindow = { lastFocusTime: 1, openedFolderUri: URI.file(path.join(fixturesFolder, 'vscode_folder')) };
|
||||
const lastActiveWindow: ISimpleWindow = { lastFocusTime: 3, openedFolderUri: undefined };
|
||||
const noVscodeFolderWindow: ISimpleWindow = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const windows: ISimpleWindow[] = [
|
||||
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: ISimpleWindow = { 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: ISimpleWindow = { lastFocusTime: 2, openedFolderUri: URI.file(path.join(fixturesFolder, 'no_vscode_folder')) };
|
||||
const nestedFolderWindow: ISimpleWindow = { 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: ISimpleWindow = { 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