Merge VS Code 1.31.1 (#4283)

This commit is contained in:
Matt Irvine
2019-03-15 13:09:45 -07:00
committed by GitHub
parent 7d31575149
commit 86bac90001
1716 changed files with 53308 additions and 48375 deletions

View File

@@ -3,8 +3,8 @@
* 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 } from 'electron';
import * as platform from 'vs/base/common/platform';
import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor } from 'electron';
import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform';
import { WindowsManager } from 'vs/code/electron-main/windows';
import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows';
import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc';
@@ -38,7 +38,6 @@ import pkg from 'vs/platform/node/package';
import { ProxyAuthHandler } from 'vs/code/electron-main/auth';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { isUndefinedOrNull } from 'vs/base/common/types';
@@ -61,11 +60,10 @@ import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/p
import { IMenubarService } from 'vs/platform/menubar/common/menubar';
import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService';
import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc';
import { ILabelService, RegisterFormatterEvent } from 'vs/platform/label/common/label';
import { hasArgs } from 'vs/platform/environment/node/argv';
import { RunOnceScheduler } from 'vs/base/common/async';
import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu';
import { THEME_STORAGE_KEY, THEME_BG_STORAGE_KEY } from 'vs/code/electron-main/theme';
import { storeBackgroundColor } from 'vs/code/electron-main/theme';
import { nativeSep, join } from 'vs/base/common/paths';
import { homedir } from 'os';
import { localize } from 'vs/nls';
@@ -76,9 +74,12 @@ import { SnapUpdateService } from 'vs/platform/update/electron-main/updateServic
import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService';
import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc';
import { generateUuid } from 'vs/base/common/uuid';
// {{SQL CARBON EDIT}}
import { CodeMenu } from 'sql/workbench/electron-browser/menus';
// {{SQL CARBON EDIT}} - End
import { startsWith } from 'vs/base/common/strings';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
import { URLService } from 'vs/platform/url/common/urlService';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
export class CodeApplication extends Disposable {
@@ -89,19 +90,17 @@ export class CodeApplication extends Disposable {
private electronIpcServer: ElectronIPCServer;
private sharedProcess: SharedProcess;
private sharedProcessClient: TPromise<Client>;
private sharedProcessClient: Promise<Client>;
constructor(
private mainIpcServer: Server,
private userEnv: platform.IProcessEnvironment,
@IInstantiationService private instantiationService: IInstantiationService,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IConfigurationService private configurationService: ConfigurationService,
@IStateService private stateService: IStateService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@ILabelService private labelService: ILabelService
private userEnv: IProcessEnvironment,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IConfigurationService private readonly configurationService: ConfigurationService,
@IStateService private readonly stateService: IStateService
) {
super();
@@ -144,12 +143,27 @@ export class CodeApplication extends Disposable {
app.on('web-contents-created', (event: any, contents) => {
contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => {
const isValidWebviewSource = (source: string): boolean => {
if (!source) {
return false;
}
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
return true;
}
const srcUri = URI.parse(source).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
return startsWith(srcUri, rootUri + nativeSep);
};
// Ensure defaults
delete webPreferences.preload;
webPreferences.nodeIntegration = false;
// Verify URLs being loaded
if (this.isValidWebviewSource(params.src) && this.isValidWebviewSource(webPreferences.preloadURL)) {
if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) {
return;
}
@@ -174,85 +188,6 @@ export class CodeApplication extends Disposable {
});
});
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
class ActiveConnection {
private _authority: string;
private _client: TPromise<Client<RemoteAgentConnectionContext>>;
private _disposeRunner: RunOnceScheduler;
constructor(authority: string, host: string, port: number) {
this._authority = authority;
this._client = connectRemoteAgentManagement(authority, host, port, `main`);
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
}
private _dispose(): void {
this._disposeRunner.dispose();
connectionPool.delete(this._authority);
this._client.then((connection) => {
connection.dispose();
});
}
public getClient(): TPromise<Client<RemoteAgentConnectionContext>> {
this._disposeRunner.schedule();
return this._client;
}
}
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
resolvedAuthorities.set(data.authority, data);
});
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
if (authority.indexOf('+') >= 0) {
if (resolvedAuthorities.has(authority)) {
return resolvedAuthorities.get(authority);
}
return null;
} else {
const [host, strPort] = authority.split(':');
const port = parseInt(strPort, 10);
return { authority, host, port, syncExtensions: false };
}
};
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
if (request.method !== 'GET') {
return callback(null);
}
const uri = URI.parse(request.url);
let activeConnection: ActiveConnection = null;
if (connectionPool.has(uri.authority)) {
activeConnection = connectionPool.get(uri.authority);
} else {
let resolvedAuthority = resolveAuthority(uri.authority);
if (!resolvedAuthority) {
callback(null);
return;
}
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
connectionPool.set(uri.authority, activeConnection);
}
try {
const rawClient = await activeConnection.getClient();
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
callback(Buffer.from(fileContents));
} else {
callback(null);
}
} catch (err) {
errors.onUnexpectedError(err);
callback(null);
}
});
let macOpenFileURIs: URI[] = [];
let runningTimeout: any = null;
app.on('open-file', (event: Event, path: string) => {
@@ -321,44 +256,18 @@ export class CodeApplication extends Disposable {
}
});
ipc.on('vscode:labelRegisterFormatter', (event: any, data: RegisterFormatterEvent) => {
this.labelService.registerFormatter(data.selector, data.formatter);
});
ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools());
ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools());
ipc.on('vscode:toggleDevTools', (event: Event) => {
event.sender.toggleDevTools();
});
ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload());
ipc.on('vscode:openDevTools', (event: Event) => {
event.sender.openDevTools();
});
ipc.on('vscode:reloadWindow', (event: Event) => {
event.sender.reload();
});
// Keyboard layout changes
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
powerMonitor.on('resume', () => { // After waking up from sleep
if (this.windowsMainService) {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
this.windowsMainService.sendToAll('vscode:osResume', undefined);
}
});
}
private isValidWebviewSource(source: string): boolean {
if (!source) {
return false;
}
if (source === 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E') {
return true;
}
const srcUri: any = URI.parse(source).fsPath.toLowerCase();
const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase();
return srcUri.startsWith(rootUri + nativeSep);
}
private onUnexpectedError(err: Error): void {
if (err) {
@@ -384,14 +293,11 @@ export class CodeApplication extends Disposable {
// Theme changes
if (event === 'vscode:changeColorTheme' && typeof payload === 'string') {
let data = JSON.parse(payload);
this.stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
storeBackgroundColor(this.stateService, JSON.parse(payload));
}
}
startup(): TPromise<void> {
startup(): Promise<void> {
this.logService.debug('Starting VS Code');
this.logService.debug(`from: ${this.environmentService.appRoot}`);
this.logService.debug('args:', this.environmentService.args);
@@ -400,7 +306,7 @@ export class CodeApplication extends Disposable {
// This will help Windows to associate the running program with
// any shortcut that is pinned to the taskbar and prevent showing
// two icons in the taskbar for the same app.
if (platform.isWindows && product.win32AppUserModelId) {
if (isWindows && product.win32AppUserModelId) {
app.setAppUserModelId(product.win32AppUserModelId);
}
@@ -411,7 +317,7 @@ export class CodeApplication extends Disposable {
// Explicitly opt out of the patch here before creating any windows.
// See: https://github.com/Microsoft/vscode/issues/35361#issuecomment-399794085
try {
if (platform.isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
if (isMacintosh && this.configurationService.getValue<boolean>('window.nativeTabs') === true && !systemPreferences.getUserDefault('NSUseImprovedLayoutPass', 'boolean')) {
systemPreferences.setUserDefault('NSUseImprovedLayoutPass', 'boolean', true as any);
}
} catch (error) {
@@ -421,9 +327,7 @@ export class CodeApplication extends Disposable {
// Create Electron IPC Server
this.electronIpcServer = new ElectronIPCServer();
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
return this.resolveMachineId().then(machineId => {
const startupWithMachineId = (machineId: string) => {
this.logService.trace(`Resolved machine identifier: ${machineId}`);
// Spawn shared process
@@ -456,6 +360,28 @@ export class CodeApplication extends Disposable {
this.stopTracingEventually(windows);
}
});
};
// Resolve unique machine ID
this.logService.trace('Resolving machine identifier...');
const resolvedMachineId = this.resolveMachineId();
if (typeof resolvedMachineId === 'string') {
return startupWithMachineId(resolvedMachineId);
} else {
return resolvedMachineId.then(machineId => startupWithMachineId(machineId));
}
}
private resolveMachineId(): string | Promise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return machineId;
}
return getMachineId().then(machineId => {
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
return machineId;
});
}
@@ -488,35 +414,20 @@ export class CodeApplication extends Disposable {
const timeoutHandle = setTimeout(() => stopRecording(true), 30000);
// Wait for all windows to get ready and stop tracing then
TPromise.join(windows.map(window => window.ready())).then(() => {
Promise.all(windows.map(window => window.ready())).then(() => {
clearTimeout(timeoutHandle);
stopRecording(false);
});
}
private resolveMachineId(): TPromise<string> {
const machineId = this.stateService.getItem<string>(CodeApplication.MACHINE_ID_KEY);
if (machineId) {
return TPromise.wrap(machineId);
}
return getMachineId().then(machineId => {
// Remember in global storage
this.stateService.setItem(CodeApplication.MACHINE_ID_KEY, machineId);
return machineId;
});
}
private initServices(machineId: string): Thenable<IInstantiationService> {
private initServices(machineId: string): Promise<IInstantiationService> {
const services = new ServiceCollection();
if (process.platform === 'win32') {
services.set(IUpdateService, new SyncDescriptor(Win32UpdateService));
} else if (process.platform === 'linux') {
if (process.env.SNAP && process.env.SNAP_REVISION) {
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService));
services.set(IUpdateService, new SyncDescriptor(SnapUpdateService, [process.env.SNAP, process.env.SNAP_REVISION]));
} else {
services.set(IUpdateService, new SyncDescriptor(LinuxUpdateService));
}
@@ -530,6 +441,10 @@ export class CodeApplication extends Disposable {
services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv]));
services.set(IMenubarService, new SyncDescriptor(MenubarService));
services.set(IStorageMainService, new SyncDescriptor(StorageMainService));
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
services.set(IURLService, new SyncDescriptor(URLService));
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
// Telemetry
if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) {
@@ -546,17 +461,20 @@ export class CodeApplication extends Disposable {
const appInstantiationService = this.instantiationService.createChild(services);
return appInstantiationService.invokeFunction(accessor => this.initStorageService(accessor)).then(() => appInstantiationService);
return appInstantiationService.invokeFunction(accessor => Promise.all([
this.initStorageService(accessor),
this.initBackupService(accessor)
])).then(() => appInstantiationService);
}
private initStorageService(accessor: ServicesAccessor): Thenable<void> {
private initStorageService(accessor: ServicesAccessor): Promise<void> {
const storageMainService = accessor.get(IStorageMainService) as StorageMainService;
// Ensure to close storage on shutdown
this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close()));
// Initialize storage service
return storageMainService.initialize().then(void 0, error => {
return storageMainService.initialize().then(undefined, error => {
errors.onUnexpectedError(error);
this.logService.error(error);
}).then(() => {
@@ -586,6 +504,12 @@ export class CodeApplication extends Disposable {
});
}
private initBackupService(accessor: ServicesAccessor): Promise<void> {
const backupMainService = accessor.get(IBackupMainService) as BackupMainService;
return backupMainService.initialize();
}
private openFirstWindow(accessor: ServicesAccessor): ICodeWindow[] {
const appInstantiationService = accessor.get(IInstantiationService);
@@ -635,8 +559,6 @@ export class CodeApplication extends Disposable {
// Propagate to clients
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
const args = this.environmentService.args;
// Create a URL handler which forwards to the last active window
const activeWindowManager = new ActiveWindowManager(windowsService);
const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id));
@@ -645,11 +567,11 @@ export class CodeApplication extends Disposable {
// On Mac, Code can be running without any open windows, so we must create a window to handle urls,
// if there is none
if (platform.isMacintosh) {
if (isMacintosh) {
const environmentService = accessor.get(IEnvironmentService);
urlService.registerHandler({
handleURL(uri: URI): TPromise<boolean> {
handleURL(uri: URI): Promise<boolean> {
if (windowsMainService.getWindowCount() === 0) {
const cli = { ...environmentService.args, goto: true };
const [window] = windowsMainService.open({ context: OpenContext.API, cli, forceEmpty: true });
@@ -657,7 +579,7 @@ export class CodeApplication extends Disposable {
return window.ready().then(() => urlService.open(uri));
}
return TPromise.as(false);
return Promise.resolve(false);
}
});
}
@@ -666,6 +588,7 @@ export class CodeApplication extends Disposable {
urlService.registerHandler(multiplexURLHandler);
// Watch Electron URLs and forward them to the UrlService
const args = this.environmentService.args;
const urls = args['open-url'] ? args._urls : [];
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
this._register(urlListener);
@@ -692,9 +615,10 @@ export class CodeApplication extends Disposable {
private afterWindowOpen(accessor: ServicesAccessor): void {
const windowsMainService = accessor.get(IWindowsMainService);
const historyMainService = accessor.get(IHistoryMainService);
let windowsMutex: Mutex | null = null;
if (platform.isWindows) {
if (isWindows) {
// Setup Windows mutex
try {
@@ -716,7 +640,7 @@ export class CodeApplication extends Disposable {
// Ensure Windows foreground love module
try {
// tslint:disable-next-line:no-unused-expression
<any>require.__$__nodeRequire('windows-foreground-love');
require.__$__nodeRequire('windows-foreground-love');
} catch (e) {
if (!this.environmentService.isBuilt) {
windowsMainService.showMessageBox({
@@ -730,21 +654,105 @@ export class CodeApplication extends Disposable {
}
}
// {{SQL CARBON EDIT}} - Use static menu for now
// Install Menu
const instantiationService = accessor.get(IInstantiationService);
const configurationService = accessor.get(IConfigurationService);
if (platform.isMacintosh || configurationService.getValue<string>('window.titleBarStyle') !== 'custom') {
instantiationService.createInstance(CodeMenu);
}
// {{SQL CARBON EDIT}} - End
// Remote Authorities
this.handleRemoteAuthorities();
// Keyboard layout changes
KeyboardLayoutMonitor.INSTANCE.onDidChangeKeyboardLayout(() => {
this.windowsMainService.sendToAll('vscode:keyboardLayoutChanged', false);
});
// Jump List
this.historyMainService.updateWindowsJumpList();
this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList());
historyMainService.updateWindowsJumpList();
historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList());
// Start shared process after a while
const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000));
sharedProcessSpawn.schedule();
}
private handleRemoteAuthorities(): void {
const connectionPool: Map<string, ActiveConnection> = new Map<string, ActiveConnection>();
const isBuilt = this.environmentService.isBuilt;
class ActiveConnection {
private _authority: string;
private _client: Promise<Client<RemoteAgentConnectionContext>>;
private _disposeRunner: RunOnceScheduler;
constructor(authority: string, host: string, port: number) {
this._authority = authority;
this._client = connectRemoteAgentManagement(authority, host, port, `main`, isBuilt);
this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000);
}
private _dispose(): void {
this._disposeRunner.dispose();
connectionPool.delete(this._authority);
this._client.then((connection) => {
connection.dispose();
});
}
public getClient(): Promise<Client<RemoteAgentConnectionContext>> {
this._disposeRunner.schedule();
return this._client;
}
}
const resolvedAuthorities = new Map<string, ResolvedAuthority>();
ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => {
resolvedAuthorities.set(data.authority, data);
});
const resolveAuthority = (authority: string): ResolvedAuthority | null => {
if (authority.indexOf('+') >= 0) {
if (resolvedAuthorities.has(authority)) {
return resolvedAuthorities.get(authority);
}
return null;
} else {
const [host, strPort] = authority.split(':');
const port = parseInt(strPort, 10);
return { authority, host, port, syncExtensions: false };
}
};
protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => {
if (request.method !== 'GET') {
return callback(null);
}
const uri = URI.parse(request.url);
let activeConnection: ActiveConnection = null;
if (connectionPool.has(uri.authority)) {
activeConnection = connectionPool.get(uri.authority);
} else {
let resolvedAuthority = resolveAuthority(uri.authority);
if (!resolvedAuthority) {
callback(null);
return;
}
activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port);
connectionPool.set(uri.authority, activeConnection);
}
try {
const rawClient = await activeConnection.getClient();
if (connectionPool.has(uri.authority)) { // not disposed in the meantime
const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME);
// TODO@alex don't use call directly, wrap it around a `RemoteExtensionsFileSystemProvider`
const fileContents = await channel.call<Uint8Array>('readFile', [uri]);
callback(Buffer.from(fileContents));
} else {
callback(null);
}
} catch (err) {
errors.onUnexpectedError(err);
callback(null);
}
});
}
}

View File

@@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { fromNodeEventEmitter } from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { BrowserWindow, app } from 'electron';
type LoginEvent = {
@@ -30,9 +30,9 @@ export class ProxyAuthHandler {
private disposables: IDisposable[] = [];
constructor(
@IWindowsMainService private windowsMainService: IWindowsMainService
@IWindowsMainService private readonly windowsMainService: IWindowsMainService
) {
const onLogin = fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
const onLogin = Event.fromNodeEventEmitter<LoginEvent>(app, 'login', (event, webContents, req, authInfo, cb) => ({ event, webContents, req, authInfo, cb }));
onLogin(this.onLogin, this, this.disposables);
}

View File

@@ -8,9 +8,8 @@ import { app, dialog } from 'electron';
import { assign } from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import product from 'vs/platform/node/product';
import * as path from 'path';
import { parseMainProcessArgv } from 'vs/platform/environment/node/argv';
import { mkdirp, readdir, rimraf } from 'vs/base/node/pfs';
import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper';
import { mkdirp } from 'vs/base/node/pfs';
import { validatePaths } from 'vs/code/node/paths';
import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
@@ -22,22 +21,14 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log';
import { StateService } from 'vs/platform/state/node/stateService';
import { IStateService } from 'vs/platform/state/common/state';
import { IBackupMainService } from 'vs/platform/backup/common/backup';
import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService';
import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment';
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationService } from 'vs/platform/configuration/node/configurationService';
import { IRequestService } from 'vs/platform/request/node/request';
import { RequestService } from 'vs/platform/request/electron-main/requestService';
import { IURLService } from 'vs/platform/url/common/url';
import { URLService } from 'vs/platform/url/common/urlService';
import * as fs from 'fs';
import { CodeApplication } from 'vs/code/electron-main/app';
import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService';
import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces';
import { localize } from 'vs/nls';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
@@ -45,75 +36,19 @@ import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics
import { BufferLogService } from 'vs/platform/log/common/bufferLog';
import { uploadLogs } from 'vs/code/electron-main/logUploader';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { CommandLineDialogService } from 'vs/platform/dialogs/node/dialogService';
import { ILabelService, LabelService } from 'vs/platform/label/common/label';
import { createWaitMarkerFile } from 'vs/code/node/wait';
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
setTimeout(() => cleanupOlderLogs(environmentService).then(null, err => console.error(err)), 10000);
services.set(IEnvironmentService, environmentService);
services.set(ILabelService, new LabelService(environmentService, void 0, void 0));
services.set(ILogService, logService);
services.set(IWorkspacesMainService, new SyncDescriptor(WorkspacesMainService));
services.set(IHistoryMainService, new SyncDescriptor(HistoryMainService));
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IURLService, new SyncDescriptor(URLService));
services.set(IBackupMainService, new SyncDescriptor(BackupMainService));
services.set(IDialogService, new SyncDescriptor(CommandLineDialogService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
return new InstantiationService(services, true);
}
/**
* Cleans up older logs, while keeping the 10 most recent ones.
*/
async function cleanupOlderLogs(environmentService: EnvironmentService): Promise<void> {
const currentLog = path.basename(environmentService.logsPath);
const logsRoot = path.dirname(environmentService.logsPath);
const children = await readdir(logsRoot);
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
await Promise.all(toDelete.map(name => rimraf(path.join(logsRoot, name))));
}
function createPaths(environmentService: IEnvironmentService): Thenable<any> {
const paths = [
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome
];
return Promise.all(paths.map(path => path && mkdirp(path)));
}
class ExpectedError extends Error {
public readonly isExpected = true;
readonly isExpected = true;
}
function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
function setupIPC(accessor: ServicesAccessor): Promise<Server> {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(IEnvironmentService);
const requestService = accessor.get(IRequestService);
const diagnosticsService = accessor.get(IDiagnosticsService);
const instantiationService = accessor.get(IInstantiationService);
function allowSetForegroundWindow(service: LaunchChannelClient): Thenable<void> {
let promise: Thenable<void> = Promise.resolve();
function allowSetForegroundWindow(service: LaunchChannelClient): Promise<void> {
let promise: Promise<void> = Promise.resolve();
if (platform.isWindows) {
promise = service.getMainProcessId()
.then(processId => {
@@ -131,7 +66,7 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
return promise;
}
function setup(retry: boolean): Thenable<Server> {
function setup(retry: boolean): Promise<Server> {
return serve(environmentService.mainIPCHandle).then(server => {
// Print --status usage info
@@ -157,7 +92,15 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
return server;
}, err => {
// Handle unexpected errors (the only expected error is EADDRINUSE that
// indicates a second instance of Code is running)
if (err.code !== 'EADDRINUSE') {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, err);
// Any other runtime error is just printed to the console
return Promise.reject<Server>(err);
}
@@ -198,14 +141,18 @@ function setupIPC(accessor: ServicesAccessor): Thenable<Server> {
// Process Info
if (environmentService.args.status) {
return service.getMainProcessInfo().then(info => {
return diagnosticsService.printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
return instantiationService.invokeFunction(accessor => {
return accessor.get(IDiagnosticsService).printDiagnostics(info).then(() => Promise.reject(new ExpectedError()));
});
});
}
// Log uploader
if (typeof environmentService.args['upload-logs'] !== 'undefined') {
return uploadLogs(service, requestService, environmentService)
.then(() => Promise.reject(new ExpectedError()));
return instantiationService.invokeFunction(accessor => {
return uploadLogs(service, accessor.get(IRequestService), environmentService)
.then(() => Promise.reject(new ExpectedError()));
});
}
logService.trace('Sending env to running instance...');
@@ -265,6 +212,15 @@ function showStartupWarningDialog(message: string, detail: string): void {
});
}
function handleStartupDataDirError(environmentService: IEnvironmentService, error): void {
if (error.code === 'EACCES' || error.code === 'EPERM') {
showStartupWarningDialog(
localize('startupDataDirError', "Unable to write program user data."),
localize('startupDataDirErrorDetail', "Please make sure the directory {0} is writeable.", environmentService.userDataPath)
);
}
}
function quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void {
const logService = accessor.get(ILogService);
const lifecycleService = accessor.get(ILifecycleService);
@@ -316,14 +272,20 @@ function startup(args: ParsedArgs): void {
const instantiationService = createServices(args, bufferLogService);
instantiationService.invokeFunction(accessor => {
const environmentService = accessor.get(IEnvironmentService);
const stateService = accessor.get(IStateService);
// Patch `process.env` with the instance's environment
const instanceEnvironment = patchEnvironment(environmentService);
// Startup
return instantiationService
.invokeFunction(a => createPaths(a.get(IEnvironmentService)))
.then(() => instantiationService.invokeFunction(setupIPC))
return initServices(environmentService, stateService as StateService)
.then(() => instantiationService.invokeFunction(setupIPC), error => {
// Show a dialog for errors that can be resolved by the user
handleStartupDataDirError(environmentService, error);
return Promise.reject(error);
})
.then(mainIpcServer => {
bufferLogService.logger = createSpdLogService('main', bufferLogService.getLevel(), environmentService.logsPath);
@@ -332,6 +294,43 @@ function startup(args: ParsedArgs): void {
}).then(null, err => instantiationService.invokeFunction(quit, err));
}
function createServices(args: ParsedArgs, bufferLogService: BufferLogService): IInstantiationService {
const services = new ServiceCollection();
const environmentService = new EnvironmentService(args, process.execPath);
const logService = new MultiplexLogService([new ConsoleLogMainService(getLogLevel(environmentService)), bufferLogService]);
process.once('exit', () => logService.dispose());
services.set(IEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(ILifecycleService, new SyncDescriptor(LifecycleService));
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IConfigurationService, new SyncDescriptor(ConfigurationService));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
return new InstantiationService(services, true);
}
function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise<any> {
// Ensure paths for environment service exist
const environmentServiceInitialization = Promise.all([
environmentService.extensionsPath,
environmentService.nodeCachedDataDir,
environmentService.logsPath,
environmentService.globalStorageHome,
environmentService.workspaceStorageHome,
environmentService.backupHome
].map(path => path && mkdirp(path)));
// State service
const stateServiceInitialization = stateService.init();
return Promise.all([environmentServiceInitialization, stateServiceInitialization]);
}
function main(): void {
// Set the error handler early enough so that we are not getting the
@@ -347,7 +346,7 @@ function main(): void {
console.error(err.message);
app.exit(1);
return void 0;
return undefined;
}
// If we are started with --wait create a random temporary file

View File

@@ -105,7 +105,7 @@ export class SharedProcess implements ISharedProcess {
});
disposables.push(toDisposable(() => sender.send('handshake:goodbye')));
ipcMain.once('handshake:im ready', () => c(void 0));
ipcMain.once('handshake:im ready', () => c(undefined));
});
});
}

View File

@@ -7,12 +7,17 @@ import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { systemPreferences } from 'electron';
import { IStateService } from 'vs/platform/state/common/state';
export const DEFAULT_BG_LIGHT = '#FFFFFF';
export const DEFAULT_BG_DARK = '#1E1E1E';
export const DEFAULT_BG_HC_BLACK = '#000000';
const DEFAULT_BG_LIGHT = '#FFFFFF';
const DEFAULT_BG_DARK = '#1E1E1E';
const DEFAULT_BG_HC_BLACK = '#000000';
export const THEME_STORAGE_KEY = 'theme';
export const THEME_BG_STORAGE_KEY = 'themeBackground';
const THEME_STORAGE_KEY = 'theme';
const THEME_BG_STORAGE_KEY = 'themeBackground';
export function storeBackgroundColor(stateService: IStateService, data: { baseTheme: string, background: string }): void {
stateService.setItem(THEME_STORAGE_KEY, data.baseTheme);
stateService.setItem(THEME_BG_STORAGE_KEY, data.background);
}
export function getBackgroundColor(stateService: IStateService): string {
if (isWindows && systemPreferences.isInvertedColorScheme()) {

View File

@@ -25,6 +25,7 @@ import * as perf from 'vs/base/common/performance';
import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { getBackgroundColor } from 'vs/code/electron-main/theme';
import { IStorageMainService } from 'vs/platform/storage/node/storageMainService';
import { RunOnceScheduler } from 'vs/base/common/async';
export interface IWindowCreationOptions {
state: IWindowState;
@@ -74,19 +75,19 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private currentConfig: IWindowConfiguration;
private pendingLoadConfig: IWindowConfiguration;
private marketplaceHeadersPromise: Thenable<object>;
private marketplaceHeadersPromise: Promise<object>;
private touchBarGroups: Electron.TouchBarSegmentedControl[];
constructor(
config: IWindowCreationOptions,
@ILogService private logService: ILogService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IConfigurationService private configurationService: IConfigurationService,
@IStateService private stateService: IStateService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IBackupMainService private backupMainService: IBackupMainService,
@IStorageMainService private storageMainService: IStorageMainService
@ILogService private readonly logService: ILogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IStateService private readonly stateService: IStateService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
@IStorageMainService private readonly storageMainService: IStorageMainService
) {
super();
@@ -256,19 +257,19 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
get backupPath(): string {
return this.currentConfig ? this.currentConfig.backupPath : void 0;
return this.currentConfig ? this.currentConfig.backupPath : undefined;
}
get openedWorkspace(): IWorkspaceIdentifier {
return this.currentConfig ? this.currentConfig.workspace : void 0;
return this.currentConfig ? this.currentConfig.workspace : undefined;
}
get openedFolderUri(): URI {
return this.currentConfig ? this.currentConfig.folderUri : void 0;
return this.currentConfig ? this.currentConfig.folderUri : undefined;
}
get remoteAuthority(): string {
return this.currentConfig ? this.currentConfig.remoteAuthority : void 0;
return this.currentConfig ? this.currentConfig.remoteAuthority : undefined;
}
setReady(): void {
@@ -276,11 +277,11 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// inform all waiting promises that we are ready now
while (this.whenReadyCallbacks.length) {
this.whenReadyCallbacks.pop()(this);
this.whenReadyCallbacks.pop()!(this);
}
}
ready(): Thenable<ICodeWindow> {
ready(): Promise<ICodeWindow> {
return new Promise<ICodeWindow>(resolve => {
if (this.isReady) {
return resolve(this);
@@ -363,17 +364,31 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this._lastFocusTime = Date.now();
});
// Simple fullscreen doesn't resize automatically when the resolution changes
// Simple fullscreen doesn't resize automatically when the resolution changes so as a workaround
// we need to detect when display metrics change or displays are added/removed and toggle the
// fullscreen manually.
if (isMacintosh) {
const displayMetricsChangedListener = () => {
if (this.isFullScreen() && !this.useNativeFullScreen()) {
const simpleFullScreenScheduler = this._register(new RunOnceScheduler(() => {
if (!this._win) {
return; // disposed
}
if (!this.useNativeFullScreen() && this.isFullScreen()) {
this.setFullScreen(false);
this.setFullScreen(true);
}
};
}, 100));
screen.addListener('display-metrics-changed', displayMetricsChangedListener);
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayMetricsChangedListener)));
const displayChangedListener = () => simpleFullScreenScheduler.schedule();
screen.on('display-metrics-changed', displayChangedListener);
this._register(toDisposable(() => screen.removeListener('display-metrics-changed', displayChangedListener)));
screen.on('display-added', displayChangedListener);
this._register(toDisposable(() => screen.removeListener('display-added', displayChangedListener)));
screen.on('display-removed', displayChangedListener);
this._register(toDisposable(() => screen.removeListener('display-removed', displayChangedListener)));
}
// Window (Un)Maximize
@@ -412,34 +427,6 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Handle Workspace events
this._register(this.workspacesMainService.onUntitledWorkspaceDeleted(e => this.onUntitledWorkspaceDeleted(e)));
// TODO@Ben workaround for https://github.com/Microsoft/vscode/issues/13612
// It looks like smooth scrolling disappears as soon as the window is minimized
// and maximized again. Touching some window properties "fixes" it, like toggling
// the visibility of the menu.
if (isWindows) {
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
if (windowConfig && windowConfig.smoothScrollingWorkaround === true) {
let minimized = false;
const restoreSmoothScrolling = () => {
if (minimized) {
const visibility = this.getMenuBarVisibility();
const temporaryVisibility: MenuBarVisibility = (visibility === 'hidden' || visibility === 'toggle') ? 'default' : 'hidden';
setTimeout(() => {
this.doSetMenuBarVisibility(temporaryVisibility);
this.doSetMenuBarVisibility(visibility);
}, 0);
}
minimized = false;
};
this._win.on('minimize', () => minimized = true);
this._win.on('restore', () => restoreSmoothScrolling());
this._win.on('maximize', () => restoreSmoothScrolling());
}
}
}
private onUntitledWorkspaceDeleted(workspace: IWorkspaceIdentifier): void {
@@ -447,7 +434,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Make sure to update our workspace config if we detect that it
// was deleted
if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
this.currentConfig.workspace = void 0;
this.currentConfig.workspace = undefined;
}
}
@@ -546,12 +533,10 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
}
reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void {
reload(configurationIn?: IWindowConfiguration, cli?: ParsedArgs): void {
// If config is not provided, copy our current one
if (!configuration) {
configuration = objects.mixin({}, this.currentConfig);
}
const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig);
// Delete some properties we do not want during reload
delete configuration.filesToOpen;
@@ -608,13 +593,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
windowConfiguration.perfEntries = perf.exportEntries();
// Parts splash
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', void 0);
windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', undefined);
// Config (combination of process.argv and window configuration)
const environment = parseArgs(process.argv);
const config = objects.assign(environment, windowConfiguration);
for (let key in config) {
if (config[key] === void 0 || config[key] === null || config[key] === '' || config[key] === false) {
if (config[key] === undefined || config[key] === null || config[key] === '' || config[key] === false) {
delete config[key]; // only send over properties that have a true value
}
}
@@ -654,7 +639,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
const res = {
mode: WindowMode.Fullscreen,
display: display ? display.id : void 0,
display: display ? display.id : undefined,
// Still carry over window dimensions from previous sessions
// if we can compute it in fullscreen state.
@@ -716,7 +701,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return state;
}
private validateWindowState(state: IWindowState): IWindowState {
private validateWindowState(state: IWindowState): IWindowState | null {
if (!state) {
return null;
}
@@ -1035,17 +1020,17 @@ export class CodeWindow extends Disposable implements ICodeWindow {
private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] {
const segments: ITouchBarSegment[] = items.map(item => {
let icon: Electron.NativeImage;
let icon: Electron.NativeImage | undefined;
if (item.iconLocation && item.iconLocation.dark.scheme === 'file') {
icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath);
if (icon.isEmpty()) {
icon = void 0;
icon = undefined;
}
}
return {
id: item.id,
label: !icon ? item.title as string : void 0,
label: !icon ? item.title as string : undefined,
icon
};
});

View File

@@ -13,7 +13,7 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/
import { IStateService } from 'vs/platform/state/common/state';
import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window';
import { hasArgs, asArray } from 'vs/platform/environment/node/argv';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, app } from 'electron';
import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron';
import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths';
import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -26,8 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows';
import { IHistoryMainService } from 'vs/platform/history/common/history';
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } 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';
@@ -35,7 +34,7 @@ import { normalizeNFC } from 'vs/base/common/normalization';
import { URI } from 'vs/base/common/uri';
import { Queue, timeout } from 'vs/base/common/async';
import { exists } from 'vs/base/node/pfs';
import { getComparisonKey, isEqual, normalizePath } from 'vs/base/common/resources';
import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename } from 'vs/base/common/resources';
import { endsWith } from 'vs/base/common/strings';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
@@ -146,27 +145,21 @@ export class WindowsManager implements IWindowsMainService {
private _onWindowLoad = new Emitter<number>();
onWindowLoad: CommonEvent<number> = this._onWindowLoad.event;
private _onActiveWindowChanged = new Emitter<ICodeWindow>();
onActiveWindowChanged: CommonEvent<ICodeWindow> = this._onActiveWindowChanged.event;
private _onWindowReload = new Emitter<number>();
onWindowReload: CommonEvent<number> = this._onWindowReload.event;
private _onWindowsCountChanged = new Emitter<IWindowsCountChangedEvent>();
onWindowsCountChanged: CommonEvent<IWindowsCountChangedEvent> = this._onWindowsCountChanged.event;
constructor(
private readonly machineId: string,
@ILogService private logService: ILogService,
@IStateService private stateService: IStateService,
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService,
@IBackupMainService private backupMainService: IBackupMainService,
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService,
@IHistoryMainService private historyMainService: IHistoryMainService,
@IWorkspacesMainService private workspacesMainService: IWorkspacesMainService,
@IInstantiationService private instantiationService: IInstantiationService
@ILogService private readonly logService: ILogService,
@IStateService private readonly stateService: IStateService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IBackupMainService private readonly backupMainService: IBackupMainService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IHistoryMainService private readonly historyMainService: IHistoryMainService,
@IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService,
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
this.windowsState = this.getWindowsState();
if (!Array.isArray(this.windowsState.openedWindows)) {
@@ -174,7 +167,7 @@ export class WindowsManager implements IWindowsMainService {
}
this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this);
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this);
this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, historyMainService, this);
}
private getWindowsState(): IWindowsState {
@@ -209,13 +202,6 @@ export class WindowsManager implements IWindowsMainService {
private registerListeners(): void {
// React to windows focus changes
app.on('browser-window-focus', () => {
setTimeout(() => {
this._onActiveWindowChanged.fire(this.getLastActiveWindow());
});
});
// React to workbench ready events from windows
ipc.on('vscode:workbenchReady', (event: any, windowId: number) => {
this.logService.trace('IPC#vscode-workbenchReady');
@@ -249,7 +235,7 @@ export class WindowsManager implements IWindowsMainService {
// 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.lastClosedWindowState = void 0;
this.lastClosedWindowState = undefined;
}
});
}
@@ -387,7 +373,7 @@ export class WindowsManager implements IWindowsMainService {
}
// collect all file inputs
let fileInputs: IFileInputs = void 0;
let fileInputs: IFileInputs = undefined;
for (const path of pathsToOpen) {
if (path.fileUri) {
if (!fileInputs) {
@@ -492,8 +478,8 @@ export class WindowsManager implements IWindowsMainService {
// Remember in recent document list (unless this opens for extension development)
// Also do not add paths when files are opened for diffing, only if opened individually
if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.cli.diff) {
const recentlyOpenedWorkspaces: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier)[] = [];
if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) {
const recentlyOpenedWorkspaces: Array<IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier> = [];
const recentlyOpenedFiles: URI[] = [];
pathsToOpen.forEach(win => {
@@ -513,7 +499,7 @@ export class WindowsManager implements IWindowsMainService {
// used for the edit operation is closed or loaded to a different folder so that the waiting
// process can continue. We do this by deleting the waitMarkerFilePath.
if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) {
this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => void 0));
this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => undefined));
}
return usedWindows;
@@ -552,9 +538,6 @@ export class WindowsManager implements IWindowsMainService {
if (lastActiveWindow) {
usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd));
}
// Reset because we handled them
foldersToAdd = [];
}
// Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit
@@ -595,7 +578,7 @@ export class WindowsManager implements IWindowsMainService {
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, bestWindowOrFolder, fileInputs));
// Reset these because we handled them
fileInputs = void 0;
fileInputs = undefined;
}
}
@@ -612,7 +595,7 @@ export class WindowsManager implements IWindowsMainService {
}));
// Reset these because we handled them
fileInputs = void 0;
fileInputs = undefined;
}
}
@@ -624,14 +607,14 @@ export class WindowsManager implements IWindowsMainService {
const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen)));
if (windowsOnWorkspace.length > 0) {
const windowOnWorkspace = windowsOnWorkspace[0];
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : void 0;
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined;
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnWorkspace, fileInputsForWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = void 0;
fileInputs = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -643,14 +626,14 @@ export class WindowsManager implements IWindowsMainService {
return; // ignore folders that are already open
}
const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : void 0;
const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : undefined;
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = void 0;
fileInputs = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -666,14 +649,14 @@ export class WindowsManager implements IWindowsMainService {
const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen)));
if (windowsOnFolderPath.length > 0) {
const windowOnFolderPath = windowsOnFolderPath[0];
const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : void 0;
const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined;
// Do open files
usedWindows.push(this.doOpenFilesInExistingWindow(openConfig, windowOnFolderPath, fileInputsForWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = void 0;
fileInputs = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -687,14 +670,14 @@ export class WindowsManager implements IWindowsMainService {
}
const remoteAuthority = getRemoteAuthority(folderToOpen);
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : void 0;
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
// Do open folder
usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderUri: folderToOpen, remoteAuthority }, openFolderInNewWindow, fileInputsForWindow));
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = void 0;
fileInputs = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -705,7 +688,7 @@ export class WindowsManager implements IWindowsMainService {
if (emptyToRestore.length > 0) {
emptyToRestore.forEach(emptyWindowBackupInfo => {
const remoteAuthority = emptyWindowBackupInfo.remoteAuthority;
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : void 0;
const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined;
usedWindows.push(this.openInBrowserWindow({
userEnv: openConfig.userEnv,
@@ -720,7 +703,7 @@ export class WindowsManager implements IWindowsMainService {
// Reset these because we handled them
if (fileInputsForWindow) {
fileInputs = void 0;
fileInputs = undefined;
}
openFolderInNewWindow = true; // any other folders to open must open in new window then
@@ -732,7 +715,7 @@ export class WindowsManager implements IWindowsMainService {
if (fileInputs && !emptyToOpen) {
emptyToOpen++;
}
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || void 0);
const remoteAuthority = fileInputs ? fileInputs.remoteAuthority : (openConfig.cli && openConfig.cli.remote || undefined);
for (let i = 0; i < emptyToOpen; i++) {
usedWindows.push(this.openInBrowserWindow({
userEnv: openConfig.userEnv,
@@ -745,7 +728,7 @@ export class WindowsManager implements IWindowsMainService {
}));
// Reset these because we handled them
fileInputs = void 0;
fileInputs = undefined;
openFolderInNewWindow = true; // any other window to open must open in new window then
}
}
@@ -834,8 +817,8 @@ export class WindowsManager implements IWindowsMainService {
// folders should be added to the existing window.
if (!openConfig.addMode && isCommandLineOrAPICall) {
const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri);
if (foldersToOpen.length > 1) {
const workspace = this.workspacesMainService.createWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
if (foldersToOpen.length > 1 && foldersToOpen.every(f => f.folderUri.scheme === Schemas.file)) {
const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri })));
// Add workspace and remove folders thereby
windowsToOpen.push({ workspace, remoteAuthority: foldersToOpen[0].remoteAuthority });
@@ -886,7 +869,7 @@ export class WindowsManager implements IWindowsMainService {
private doExtractPathsFromCLI(cli: ParsedArgs): IPath[] {
const pathsToOpen: IPathToOpen[] = [];
const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || void 0 };
const parseOptions: IPathParseOptions = { ignoreFileNotFound: true, gotoLineMode: cli.goto, remoteAuthority: cli.remote || undefined };
// folder uris
const folderUris = asArray(cli['folder-uri']);
@@ -1017,8 +1000,9 @@ export class WindowsManager implements IWindowsMainService {
// normalize URI
uri = normalizePath(uri);
if (endsWith(uri.path, '/')) {
uri = uri.with({ path: uri.path.substr(0, uri.path.length - 1) });
const uriPath = uri.path;
if (uriPath.length > 2 && endsWith(uriPath, '/')) {
uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) });
}
if (isFile) {
if (options && options.gotoLineMode) {
@@ -1074,8 +1058,8 @@ export class WindowsManager implements IWindowsMainService {
// File
return {
fileUri: URI.file(candidate),
lineNumber: gotoLineMode ? parsedPath.line : void 0,
columnNumber: gotoLineMode ? parsedPath.column : void 0,
lineNumber: gotoLineMode ? parsedPath.line : undefined,
columnNumber: gotoLineMode ? parsedPath.column : undefined,
remoteAuthority
};
}
@@ -1473,10 +1457,7 @@ export class WindowsManager implements IWindowsMainService {
// Only reload when the window has not vetoed this
this.lifecycleService.unload(win, UnloadReason.RELOAD).then(veto => {
if (!veto) {
win.reload(void 0, cli);
// Emit
this._onWindowReload.fire(win.id);
win.reload(undefined, cli);
}
});
}
@@ -1489,18 +1470,10 @@ export class WindowsManager implements IWindowsMainService {
});
}
saveAndEnterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
return this.workspacesManager.saveAndEnterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
}
enterWorkspace(win: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
enterWorkspace(win: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult> {
return this.workspacesManager.enterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result));
}
createAndEnterWorkspace(win: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
return this.workspacesManager.createAndEnterWorkspace(win, folders, path).then(result => this.doEnterWorkspace(win, result));
}
private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult {
// Mark as recently opened
@@ -1575,7 +1548,7 @@ export class WindowsManager implements IWindowsMainService {
openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] {
let cli = this.environmentService.args;
let remote = options && options.remoteAuthority || void 0;
let remote = options && options.remoteAuthority || undefined;
if (cli && (cli.remote !== remote)) {
cli = { ...cli, remote };
}
@@ -1586,7 +1559,7 @@ export class WindowsManager implements IWindowsMainService {
return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true });
}
waitForWindowCloseOrLoad(windowId: number): Thenable<void> {
waitForWindowCloseOrLoad(windowId: number): Promise<void> {
return new Promise<void>(resolve => {
function handler(id: number) {
if (id === windowId) {
@@ -1658,6 +1631,17 @@ export class WindowsManager implements IWindowsMainService {
// Unresponsive
if (error === WindowError.UNRESPONSIVE) {
if (window.isExtensionDevelopmentHost || window.isExtensionTestHost || (window.win && window.win.webContents && window.win.webContents.isDevToolsOpened())) {
// TODO@Ben Workaround for https://github.com/Microsoft/vscode/issues/56994
// In certain cases the window can report unresponsiveness because a breakpoint was hit
// and the process is stopped executing. The most typical cases are:
// - devtools are opened and debugging happens
// - window is an extensions development host that is being debugged
// - window is an extension test development host that is being debugged
return;
}
// Show Dialog
this.dialogs.showMessageBox({
title: product.nameLong,
type: 'warning',
@@ -1763,15 +1747,15 @@ export class WindowsManager implements IWindowsMainService {
this.dialogs.pickAndOpen(internalOptions);
}
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Thenable<IMessageBoxResult> {
showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise<IMessageBoxResult> {
return this.dialogs.showMessageBox(options, win);
}
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Thenable<string> {
showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise<string> {
return this.dialogs.showSaveDialog(options, win);
}
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Thenable<string[]> {
showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise<string[]> {
return this.dialogs.showOpenDialog(options, win);
}
@@ -1833,6 +1817,7 @@ class Dialogs {
if (numberOfPaths) {
this.windowsMainService.open({
context: OpenContext.DIALOG,
contextWindowId: options.windowId,
cli: this.environmentService.args,
urisToOpen: paths,
forceNewWindow: options.forceNewWindow,
@@ -1842,7 +1827,7 @@ class Dialogs {
});
}
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): TPromise<URI[]> {
private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise<URI[]> {
// Ensure dialog options
if (!options.dialogOptions) {
@@ -1856,7 +1841,7 @@ class Dialogs {
// Ensure properties
if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') {
options.dialogOptions.properties = void 0; // let it override based on the booleans
options.dialogOptions.properties = undefined; // let it override based on the booleans
if (options.pickFiles && options.pickFolders) {
options.dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory'];
@@ -1883,7 +1868,7 @@ class Dialogs {
return paths.map(path => URI.file(path));
}
return void 0;
return undefined;
});
}
@@ -1901,17 +1886,17 @@ class Dialogs {
return windowDialogQueue;
}
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Thenable<IMessageBoxResult> {
showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise<IMessageBoxResult> {
return this.getDialogQueue(window).queue(() => {
return new Promise(resolve => {
dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => {
dialog.showMessageBox(window ? window.win : undefined, options, (response: number, checkboxChecked: boolean) => {
resolve({ button: response, checkboxChecked });
});
});
});
}
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Thenable<string> {
showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise<string> {
function normalizePath(path: string): string {
if (path && isMacintosh) {
@@ -1923,14 +1908,14 @@ class Dialogs {
return this.getDialogQueue(window).queue(() => {
return new Promise(resolve => {
dialog.showSaveDialog(window ? window.win : void 0, options, path => {
dialog.showSaveDialog(window ? window.win : undefined, options, path => {
resolve(normalizePath(path));
});
});
});
}
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Thenable<string[]> {
showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise<string[]> {
function normalizePaths(paths: string[]): string[] {
if (paths && paths.length > 0 && isMacintosh) {
@@ -1948,14 +1933,14 @@ class Dialogs {
if (options.defaultPath) {
validatePathPromise = exists(options.defaultPath).then(exists => {
if (!exists) {
options.defaultPath = void 0;
options.defaultPath = undefined;
}
});
}
// Show dialog and wrap as promise
validatePathPromise.then(() => {
dialog.showOpenDialog(window ? window.win : void 0, options, paths => {
dialog.showOpenDialog(window ? window.win : undefined, options, paths => {
resolve(normalizePaths(paths));
});
});
@@ -1970,68 +1955,42 @@ class WorkspacesManager {
private workspacesMainService: IWorkspacesMainService,
private backupMainService: IBackupMainService,
private environmentService: IEnvironmentService,
private windowsMainService: IWindowsMainService
private historyMainService: IHistoryMainService,
private windowsMainService: IWindowsMainService,
) {
}
saveAndEnterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
if (!window || !window.win || !window.isReady || !window.openedWorkspace || !path || !this.isValidTargetWorkspacePath(window, path)) {
return TPromise.as(null); // return early if the window is not ready or disposed or does not have a workspace
}
return this.doSaveAndOpenWorkspace(window, window.openedWorkspace, path);
}
enterWorkspace(window: ICodeWindow, path: string): TPromise<IEnterWorkspaceResult> {
enterWorkspace(window: ICodeWindow, path: URI): Promise<IEnterWorkspaceResult | null> {
if (!window || !window.win || !window.isReady) {
return TPromise.as(null); // return early if the window is not ready or disposed
return Promise.resolve(null); // return early if the window is not ready or disposed
}
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
if (!isValid) {
return TPromise.as<IEnterWorkspaceResult>(null); // return early if the workspace is not valid
return null; // return early if the workspace is not valid
}
return this.workspacesMainService.resolveWorkspace(path).then(workspace => {
return this.doOpenWorkspace(window, workspace);
});
const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path);
return this.doOpenWorkspace(window, workspaceIdentifier);
});
}
createAndEnterWorkspace(window: ICodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise<IEnterWorkspaceResult> {
if (!window || !window.win || !window.isReady) {
return TPromise.as(null); // return early if the window is not ready or disposed
}
return this.isValidTargetWorkspacePath(window, path).then(isValid => {
if (!isValid) {
return TPromise.as(null); // return early if the workspace is not valid
}
return this.workspacesMainService.createWorkspace(folders).then(workspace => {
return this.doSaveAndOpenWorkspace(window, workspace, path);
});
});
}
private isValidTargetWorkspacePath(window: ICodeWindow, path?: string): TPromise<boolean> {
private isValidTargetWorkspacePath(window: ICodeWindow, path?: URI): Promise<boolean> {
if (!path) {
return TPromise.wrap(true);
return Promise.resolve(true);
}
if (window.openedWorkspace && window.openedWorkspace.configPath === path) {
return TPromise.wrap(false); // window is already opened on a workspace with that path
if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), path)) {
return Promise.resolve(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(), { id: this.workspacesMainService.getWorkspaceId(path), configPath: path })) {
if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) {
const options: Electron.MessageBoxOptions = {
title: product.nameLong,
type: 'info',
buttons: [localize('ok', "OK")],
message: localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)),
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
};
@@ -2039,18 +1998,7 @@ class WorkspacesManager {
return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false);
}
return TPromise.wrap(true); // OK
}
private doSaveAndOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise<IEnterWorkspaceResult> {
let savePromise: TPromise<IWorkspaceIdentifier>;
if (path) {
savePromise = this.workspacesMainService.saveWorkspace(workspace, path);
} else {
savePromise = TPromise.as(workspace);
}
return savePromise.then(workspace => this.doOpenWorkspace(window, workspace));
return Promise.resolve(true); // OK
}
private doOpenWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult {
@@ -2062,8 +2010,13 @@ class WorkspacesManager {
backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, 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 = void 0;
window.config.folderUri = undefined;
window.config.workspace = workspace;
window.config.backupPath = backupPath;
@@ -2074,7 +2027,7 @@ class WorkspacesManager {
const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow();
this.windowsMainService.pickFileAndOpen({
windowId: window ? window.id : void 0,
windowId: window ? window.id : undefined,
dialogOptions: {
buttonLabel: mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")),
title: localize('openWorkspaceTitle', "Open Workspace"),
@@ -2088,7 +2041,7 @@ class WorkspacesManager {
});
}
promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise<boolean> {
promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): Promise<boolean> {
enum ConfirmResult {
SAVE,
DONT_SAVE,
@@ -2143,7 +2096,11 @@ class WorkspacesManager {
defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace)
}, window).then(target => {
if (target) {
return this.workspacesMainService.saveWorkspace(workspace, target).then(() => false, () => false);
return this.workspacesMainService.saveWorkspaceAs(workspace, target).then(savedWorkspace => {
this.historyMainService.addRecentlyOpened([savedWorkspace], []);
this.workspacesMainService.deleteUntitledWorkspaceSync(workspace);
return false;
}, () => false);
}
return true; // keep veto if no target was provided
@@ -2156,7 +2113,7 @@ class WorkspacesManager {
private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string {
if (workspace) {
if (isSingleFolderWorkspaceIdentifier(workspace)) {
return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : void 0;
return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : undefined;
}
const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath);
@@ -2169,6 +2126,6 @@ class WorkspacesManager {
}
}
return void 0;
return undefined;
}
}