diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index ca4288010f2..65aa20b3f67 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -57,24 +57,22 @@ const vscodeResources = [ 'out-build/bootstrap.js', 'out-build/bootstrap-amd.js', 'out-build/paths.js', - 'out-build/vs/**/*.{svg,png,cur}', + 'out-build/vs/**/*.{svg,png,cur,html}', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/electron-browser/bootstrap/**', 'out-build/vs/workbench/parts/debug/**/*.json', 'out-build/vs/workbench/parts/execution/**/*.scpt', - 'out-build/vs/workbench/parts/git/**/*.html', 'out-build/vs/workbench/parts/git/**/*.sh', - 'out-build/vs/workbench/parts/html/browser/webview.html', 'out-build/vs/workbench/parts/html/browser/webview-pre.js', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/parts/tasks/**/*.json', 'out-build/vs/workbench/parts/terminal/electron-browser/terminalProcess.js', 'out-build/vs/workbench/parts/welcome/walkThrough/**/*.md', - 'out-build/vs/workbench/parts/welcome/page/**/*.html', 'out-build/vs/workbench/services/files/**/*.exe', 'out-build/vs/workbench/services/files/**/*.md', + 'out-build/vs/code/electron-browser/sharedProcess.js', '!**/test/**' ]; diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index f08ff7fd48c..ab5d7d9b830 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -3370,15 +3370,15 @@ declare namespace Electron { /** * The URL associated with the PAC file. */ - pacScript: string; + pacScript?: string; /** * Rules indicating which proxies to use. */ - proxyRules: string; + proxyRules?: string; /** * Rules indicating which URLs should bypass the proxy settings. */ - proxyBypassRules: string; + proxyBypassRules?: string; } interface NetworkEmulationOptions { diff --git a/src/vs/base/node/request.ts b/src/vs/base/node/request.ts index cc9e228b4a2..924dbacaea4 100644 --- a/src/vs/base/node/request.ts +++ b/src/vs/base/node/request.ts @@ -17,6 +17,10 @@ import { createGunzip } from 'zlib'; export type Agent = any; +export interface IRawRequestFunction { + (options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; +} + export interface IRequestOptions { type?: string; url?: string; @@ -28,6 +32,7 @@ export interface IRequestOptions { agent?: Agent; followRedirects?: number; strictSSL?: boolean; + getRawRequest?(options: IRequestOptions): IRawRequestFunction; } export interface IRequestContext { @@ -44,15 +49,22 @@ export interface IRequestFunction { (options: IRequestOptions): TPromise; } +function getNodeRequest(options: IRequestOptions): IRawRequestFunction { + const endpoint = parseUrl(options.url); + return endpoint.protocol === 'https:' ? https.request : http.request; +} + export function request(options: IRequestOptions): TPromise { let req: http.ClientRequest; return new TPromise((c, e) => { const endpoint = parseUrl(options.url); - const rawRequest = endpoint.protocol === 'https:' ? https.request : http.request; + const getRawRequest = options.getRawRequest || getNodeRequest; + const rawRequest = getRawRequest(options); const opts: https.RequestOptions = { hostname: endpoint.hostname, port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80), + protocol: endpoint.protocol, path: endpoint.path, method: options.type || 'GET', headers: options.headers, diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 960acabd281..2781667fc3a 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -20,6 +20,6 @@ exports.collectModules= function() { createModuleDescription('vs/code/electron-main/main', []), createModuleDescription('vs/code/node/cli', []), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), - createModuleDescription('vs/code/node/sharedProcessMain', []) + createModuleDescription('vs/code/electron-browser/sharedProcessMain', []) ]; }; \ No newline at end of file diff --git a/src/vs/code/electron-browser/sharedProcess.html b/src/vs/code/electron-browser/sharedProcess.html new file mode 100644 index 00000000000..1c3da8dcb91 --- /dev/null +++ b/src/vs/code/electron-browser/sharedProcess.html @@ -0,0 +1,16 @@ + + + + + + + + + + Shared Process + + + + + + \ No newline at end of file diff --git a/src/vs/code/electron-browser/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess.js new file mode 100644 index 00000000000..835eeee1330 --- /dev/null +++ b/src/vs/code/electron-browser/sharedProcess.js @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Warning: Do not use the `let` declarator in this file, it breaks our minification + +'use strict'; + +/*global window,document,define*/ + +const path = require('path'); +const electron = require('electron'); +const remote = electron.remote; +const ipc = electron.ipcRenderer; + +function assign(destination, source) { + return Object.keys(source) + .reduce(function (r, key) { r[key] = source[key]; return r; }, destination); +} + +function parseURLQueryArgs() { + const search = window.location.search || ''; + + return search.split(/[?&]/) + .filter(function (param) { return !!param; }) + .map(function (param) { return param.split('='); }) + .filter(function (param) { return param.length === 2; }) + .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); +} + +function createScript(src, onload) { + const script = document.createElement('script'); + script.src = src; + script.addEventListener('load', onload); + + const head = document.getElementsByTagName('head')[0]; + head.insertBefore(script, head.lastChild); +} + +function uriFromPath(_path) { + var pathName = path.resolve(_path).replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = '/' + pathName; + } + + return encodeURI('file://' + pathName); +} + +function main() { + const args = parseURLQueryArgs(); + const configuration = JSON.parse(args['config'] || '{}') || {}; + + // Correctly inherit the parent's environment + assign(process.env, configuration.userEnv); + + // Get the nls configuration into the process.env as early as possible. + var nlsConfig = { availableLanguages: {} }; + const config = process.env['VSCODE_NLS_CONFIG']; + if (config) { + process.env['VSCODE_NLS_CONFIG'] = config; + try { + nlsConfig = JSON.parse(config); + } catch (e) { /*noop*/ } + } + + var locale = nlsConfig.availableLanguages['*'] || 'en'; + if (locale === 'zh-tw') { + locale = 'zh-Hant'; + } else if (locale === 'zh-cn') { + locale = 'zh-Hans'; + } + + window.document.documentElement.setAttribute('lang', locale); + + // Load the loader and start loading the workbench + const rootUrl = uriFromPath(configuration.appRoot) + '/out'; + + // In the bundled version the nls plugin is packaged with the loader so the NLS Plugins + // loads as soon as the loader loads. To be able to have pseudo translation + createScript(rootUrl + '/vs/loader.js', function () { + define('fs', ['original-fs'], function (originalFS) { return originalFS; }); // replace the patched electron fs with the original node fs for all AMD code + + window.MonacoEnvironment = {}; + + const nodeCachedDataErrors = window.MonacoEnvironment.nodeCachedDataErrors = []; + require.config({ + baseUrl: rootUrl, + 'vs/nls': nlsConfig, + nodeCachedDataDir: configuration.nodeCachedDataDir, + onNodeCachedDataError: function (err) { nodeCachedDataErrors.push(err) }, + nodeModules: [/*BUILD->INSERT_NODE_MODULES*/] + }); + + if (nlsConfig.pseudo) { + require(['vs/nls'], function (nlsPlugin) { + nlsPlugin.setPseudoTranslation(nlsConfig.pseudo); + }); + } + + require(['vs/code/electron-browser/sharedProcessMain'], function () { }); + }); +} + +main(); diff --git a/src/vs/code/node/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcessMain.ts similarity index 87% rename from src/vs/code/node/sharedProcessMain.ts rename to src/vs/code/electron-browser/sharedProcessMain.ts index d6ca3fa783c..e30c31a2f4b 100644 --- a/src/vs/code/node/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcessMain.ts @@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -21,39 +21,23 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/ex 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/node/requestService'; +import { RequestService } from 'vs/platform/request/electron-browser/requestService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { ISharedProcessInitData } from './sharedProcess'; import { IChoiceService } from 'vs/platform/message/common/message'; import { ChoiceChannelClient } from 'vs/platform/message/common/messageIpc'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc'; import { ActiveWindowManager } from 'vs/code/common/windows'; +import { ipcRenderer } from 'electron'; -function quit(err?: Error) { - if (err) { - console.error(err.stack || err); - } - - process.exit(err ? 1 : 0); -} - -/** - * Plan B is to kill oneself if one's parent dies. Much drama. - */ -function setupPlanB(parentPid: number): void { - setInterval(function () { - try { - process.kill(parentPid, 0); // throws an exception if the main process doesn't exist anymore. - } catch (e) { - process.exit(); - } - }, 5000); +interface ISharedProcessInitData { + sharedIPCHandle: string; + args: ParsedArgs; } const eventPrefix = 'monacoworkbench'; @@ -159,16 +143,17 @@ function setupIPC(hook: string): TPromise { return setup(true); } -function handshake(): TPromise { +function startHandshake(): TPromise { return new TPromise((c, e) => { - process.once('message', c); - process.once('error', e); - process.send('hello'); + ipcRenderer.once('handshake:hey there', (_, r) => c(r)); + ipcRenderer.send('handshake:hello'); }); } -setupIPC(process.env['VSCODE_SHARED_IPC_HOOK']) - .then(server => handshake() - .then(data => main(server, data)) - .then(() => setupPlanB(process.env['VSCODE_PID'])) - .done(null, quit)); \ No newline at end of file +function handshake(): TPromise { + return startHandshake() + .then((data) => setupIPC(data.sharedIPCHandle).then(server => main(server, data))) + .then(() => ipcRenderer.send('handshake:im ready')); +} + +handshake(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index ba8f2436591..4c9ca188f1b 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -27,9 +27,8 @@ import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { TPromise } from 'vs/base/common/winjs.base'; import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc'; import { GitAskpassService } from 'vs/workbench/parts/git/electron-main/askpassService'; -import { spawnSharedProcess } from 'vs/code/node/sharedProcess'; +import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; import { Mutex } from 'windows-mutex'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { LaunchService, ILaunchChannel, LaunchChannel, LaunchChannelClient, ILaunchService } from './launch'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; @@ -45,7 +44,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ 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/node/requestService'; +import { RequestService } from 'vs/platform/request/electron-main/requestService'; import { IURLService } from 'vs/platform/url/common/url'; import { URLChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/electron-main/urlService'; @@ -143,18 +142,9 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo const electronIpcServer = new ElectronIPCServer(); // Spawn shared process - const initData = { args: environmentService.args }; - const options = { - allowOutput: !environmentService.isBuilt || environmentService.verbose, - debugPort: environmentService.isBuilt ? null : 5871 - }; - - let sharedProcessDisposable: IDisposable; - - const sharedProcess = spawnSharedProcess(initData, options).then(disposable => { - sharedProcessDisposable = disposable; - return connect(environmentService.sharedIPCHandle, 'main'); - }); + const sharedProcess = new SharedProcess(environmentService, userEnv); + const sharedProcessClient = sharedProcess.onReady + .then(() => connect(environmentService.sharedIPCHandle, 'main')); // Create a new service collection, because the telemetry service // requires a connection to shared process, which was only established @@ -163,11 +153,11 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo services.set(IUpdateService, new SyncDescriptor(UpdateService)); services.set(IWindowsMainService, new SyncDescriptor(WindowsManager)); - services.set(IWindowsService, new SyncDescriptor(WindowsService)); + services.set(IWindowsService, new SyncDescriptor(WindowsService, sharedProcess)); services.set(ILaunchService, new SyncDescriptor(LaunchService)); if (environmentService.isBuilt && !environmentService.isExtensionDevelopment && !!product.enableTelemetry) { - const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); + const channel = getDelayedChannel(sharedProcessClient.then(c => c.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); const commonProperties = resolveCommonProperties(product.commit, pkg.version); const piiPaths = [environmentService.appRoot, environmentService.extensionsPath]; @@ -183,6 +173,13 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo // TODO@Joao: unfold this windowsMainService = accessor.get(IWindowsMainService); + // TODO@Joao: so ugly... + windowsMainService.onWindowClose(() => { + if (!platform.isMacintosh && windowsMainService.getWindowCount() === 0) { + sharedProcess.dispose(); + } + }); + // Register more Main IPC services const launchService = accessor.get(ILaunchService); const launchChannel = new LaunchChannel(launchService); @@ -204,7 +201,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo const windowsService = accessor.get(IWindowsService); const windowsChannel = new WindowsChannel(windowsService); electronIpcServer.registerChannel('windows', windowsChannel); - sharedProcess.done(client => client.registerChannel('windows', windowsChannel)); + sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel)); // Make sure we associate the program with the app user model id // This will help Windows to associate the running program with @@ -220,15 +217,12 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: platfo mainIpcServer = null; } - if (sharedProcessDisposable) { - sharedProcessDisposable.dispose(); - } - if (windowsMutex) { windowsMutex.release(); } configurationService.dispose(); + sharedProcess.dispose(); } // Dispose on app quit @@ -399,7 +393,6 @@ function start(): void { const instanceEnv: typeof process.env = { VSCODE_PID: String(process.pid), VSCODE_IPC_HOOK: environmentService.mainIPCHandle, - VSCODE_SHARED_IPC_HOOK: environmentService.sharedIPCHandle, VSCODE_NLS_CONFIG: process.env['VSCODE_NLS_CONFIG'] }; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts new file mode 100644 index 00000000000..4111cdc6526 --- /dev/null +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assign } from 'vs/base/common/objects'; +import { memoize } from 'vs/base/common/decorators'; +import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { BrowserWindow, ipcMain } from 'electron'; + +export class SharedProcess { + + private window: Electron.BrowserWindow; + private disposables: IDisposable[] = []; + + @memoize + get onReady(): TPromise { + this.window = new BrowserWindow({ show: false }); + const config = assign({ + appRoot: this.environmentService.appRoot, + nodeCachedDataDir: this.environmentService.nodeCachedDataDir, + userEnv: this.userEnv + }); + + const url = `${require.toUrl('vs/code/electron-browser/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; + this.window.loadURL(url); + + // Prevent the window from dying + const onClose = e => { + if (this.window.isVisible()) { + e.preventDefault(); + this.window.hide(); + } + }; + + this.window.on('close', onClose); + this.disposables.push(toDisposable(() => this.window.removeListener('close', onClose))); + + this.disposables.push(toDisposable(() => { + // Electron seems to crash on Windows without this setTimeout :| + setTimeout(() => { + try { + this.window.close(); + } catch (err) { + // ignore, as electron is already shutting down + } + + this.window = null; + }, 0); + })); + + return new TPromise((c, e) => { + ipcMain.once('handshake:hello', ({ sender }) => { + sender.send('handshake:hey there', { + sharedIPCHandle: this.environmentService.sharedIPCHandle, + args: this.environmentService.args + }); + + sender.once('handshake:im ready', () => c(null)); + }); + }); + } + + constructor( + private environmentService: IEnvironmentService, + private userEnv: IProcessEnvironment + ) { } + + toggle(): void { + if (this.window.isVisible()) { + this.hide(); + } else { + this.show(); + } + } + + show(): void { + this.window.show(); + this.window.webContents.openDevTools(); + } + + hide(): void { + this.window.webContents.closeDevTools(); + this.window.hide(); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/src/vs/code/node/sharedProcess.ts b/src/vs/code/node/sharedProcess.ts deleted file mode 100644 index 46f0f1ea9b5..00000000000 --- a/src/vs/code/node/sharedProcess.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as cp from 'child_process'; -import URI from 'vs/base/common/uri'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { TPromise } from 'vs/base/common/winjs.base'; - -export interface ISharedProcessInitData { - args: ParsedArgs; -} - -export interface ISharedProcessOptions { - allowOutput?: boolean; - debugPort?: number; -} - -const boostrapPath = URI.parse(require.toUrl('bootstrap')).fsPath; - -function _spawnSharedProcess(initData: ISharedProcessInitData, options: ISharedProcessOptions): cp.ChildProcess { - const execArgv: string[] = []; - const env = assign({}, process.env, { - AMD_ENTRYPOINT: 'vs/code/node/sharedProcessMain', - ELECTRON_NO_ASAR: '1' - }); - - if (options.allowOutput) { - env['VSCODE_ALLOW_IO'] = 'true'; - } - - if (options.debugPort) { - execArgv.push(`--debug=${options.debugPort}`); - } - - const result = cp.fork(boostrapPath, ['--type=SharedProcess'], { env, execArgv }); - - return result; -} - -export function spawnSharedProcess(initData: ISharedProcessInitData, options: ISharedProcessOptions = {}): TPromise { - let spawnCount = 0; - let child: cp.ChildProcess; - - let promise: TPromise; - - const spawn = () => { - if (++spawnCount > 10) { - return; - } - - child = _spawnSharedProcess(initData, options); - promise = new TPromise((c, e) => { - // handshake - child.once('message', () => { - child.send(initData); - c({ - dispose: () => { - if (child) { - child.removeListener('exit', spawn); - child.kill(); - child = null; - } - } - }); - }); - }); - child.on('exit', spawn); - }; - - spawn(); - - return promise; -} \ No newline at end of file diff --git a/src/vs/platform/request/electron-main/requestService.ts b/src/vs/platform/request/electron-main/requestService.ts new file mode 100644 index 00000000000..2d583abe0cd --- /dev/null +++ b/src/vs/platform/request/electron-main/requestService.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IRequestOptions, IRequestContext, request, IRawRequestFunction } from 'vs/base/node/request'; +import { RequestService as NodeRequestService } from 'vs/platform/request/node/requestService'; +import { assign } from 'vs/base/common/objects'; +import { net } from 'electron'; + +function getRawRequest(options: IRequestOptions): IRawRequestFunction { + return net.request as any as IRawRequestFunction; +} + +export class RequestService extends NodeRequestService { + + request(options: IRequestOptions): TPromise { + return super.request(options, options => request(assign({}, options || {}, { getRawRequest }))); + } +} \ No newline at end of file diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index df64227750e..58588327549 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -39,6 +39,9 @@ export interface IWindowsService { quit(): TPromise; relaunch(options: { addArgs?: string[], removeArgs?: string[] }): TPromise; + // Shared process + toggleSharedProcess(): TPromise; + // Global methods openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise; openNewWindow(): TPromise; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 45665b734f2..2176e6672cf 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -36,6 +36,8 @@ export interface IWindowsChannel extends IChannel { call(command: 'showWindow', arg: number): TPromise; call(command: 'getWindows'): TPromise<{ id: number; path: string; title: string; }[]>; call(command: 'getWindowCount'): TPromise; + call(command: 'relaunch', arg: { addArgs?: string[], removeArgs?: string[] }): TPromise; + call(command: 'toggleSharedProcess'): TPromise; call(command: 'log', arg: [string, string[]]): TPromise; call(command: 'closeExtensionHostWindow', arg: string): TPromise; call(command: 'showItemInFolder', arg: string): TPromise; @@ -81,6 +83,7 @@ export class WindowsChannel implements IWindowsChannel { case 'getWindows': return this.service.getWindows(); case 'getWindowCount': return this.service.getWindowCount(); case 'relaunch': return this.service.relaunch(arg[0]); + case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); case 'log': return this.service.log(arg[0], arg[1]); case 'closeExtensionHostWindow': return this.service.closeExtensionHostWindow(arg); @@ -180,6 +183,10 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('relaunch', [options]); } + toggleSharedProcess(): TPromise { + return this.channel.call('toggleSharedProcess'); + } + openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise { return this.channel.call('openWindow', [paths, options]); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 3727fd66516..37d48abfe25 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -20,6 +20,10 @@ import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { OpenContext } from 'vs/code/common/windows'; import { IWindowsMainService } from 'vs/code/electron-main/windows'; +export interface ISharedProcess { + toggle(): void; +} + export class WindowsService implements IWindowsService, IDisposable { _serviceBrand: any; @@ -30,6 +34,7 @@ export class WindowsService implements IWindowsService, IDisposable { onWindowFocus: Event = fromEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id); constructor( + private sharedProcess: ISharedProcess, @IWindowsMainService private windowsMainService: IWindowsMainService, @IEnvironmentService private environmentService: IEnvironmentService, @IURLService urlService: IURLService @@ -282,6 +287,11 @@ export class WindowsService implements IWindowsService, IDisposable { return TPromise.as(null); } + toggleSharedProcess(): TPromise { + this.sharedProcess.toggle(); + return TPromise.as(null); + } + private openFileForURI(filePath: string): TPromise { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const pathsToOpen = [filePath]; diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index cd4bdb789ac..86eac1ce0f1 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -905,3 +905,17 @@ export class OpenIntroductoryVideosUrlAction extends Action { return null; } } + +export class ToggleSharedProcessAction extends Action { + + static ID = 'workbench.action.toggleSharedProcess'; + static LABEL = nls.localize('toggleSharedProcess', "Toggle Shared Process"); + + constructor(id: string, label: string, @IWindowsService private windowsService: IWindowsService) { + super(id, label); + } + + run(): TPromise { + return this.windowsService.toggleSharedProcess(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index a498a4713e8..dee7e8c3174 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -87,7 +87,7 @@ import { URLChannelClient } from 'vs/platform/url/common/urlIpc'; import { IURLService } from 'vs/platform/url/common/url'; import { IBackupService } from 'vs/platform/backup/common/backup'; import { BackupChannelClient } from 'vs/platform/backup/common/backupIpc'; -import { ReloadWindowAction, ReportPerformanceIssueAction } from 'vs/workbench/electron-browser/actions'; +import { ReportPerformanceIssueAction } from 'vs/workbench/electron-browser/actions'; import { ExtensionHostProcessWorker } from 'vs/workbench/electron-browser/extensionHost'; import { ITimerService } from 'vs/workbench/services/timer/common/timerService'; import { remote } from 'electron'; @@ -296,16 +296,8 @@ export class WorkbenchShell { const sharedProcess = connectNet(this.environmentService.sharedIPCHandle, `window:${this.windowIPCService.getWindowId()}`); sharedProcess.done(client => { - // Choice channel client.registerChannel('choice', instantiationService.createInstance(ChoiceChannel)); - - client.onClose(() => { - this.messageService.show(Severity.Error, { - message: nls.localize('sharedProcessCrashed', "The shared process terminated unexpectedly. Please reload the window to recover."), - actions: [instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL)] - }); - }); }, errors.onUnexpectedError); // Storage Sevice diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 701447f2503..aff1ba12667 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -40,7 +40,7 @@ import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/action import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IThemeService, VS_HC_THEME, VS_DARK_THEME } from 'vs/workbench/services/themes/common/themeService'; import * as browser from 'vs/base/browser/browser'; -import { ReloadWindowAction, ToggleDevToolsAction, ShowStartupPerformance, OpenRecentAction } from 'vs/workbench/electron-browser/actions'; +import { ReloadWindowAction, ToggleDevToolsAction, ShowStartupPerformance, OpenRecentAction, ToggleSharedProcessAction } from 'vs/workbench/electron-browser/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -335,6 +335,7 @@ export class ElectronWindow { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_R } : void 0), 'Reload Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } } : void 0), 'Developer: Toggle Developer Tools', developerCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowStartupPerformance, ShowStartupPerformance.ID, ShowStartupPerformance.LABEL), 'Developer: Startup Performance', developerCategory); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); // Action registered here to prevent a keybinding conflict with reload window const fileCategory = nls.localize('file', "File"); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 517b7eec0b8..224fb1f38a6 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -917,11 +917,12 @@ export class TestWindowsService implements IWindowsService { quit(): TPromise { return TPromise.as(void 0); } - relaunch(options: { addArgs?: string[], removeArgs?: string[] }): TPromise { return TPromise.as(void 0); } - + toggleSharedProcess(): TPromise { + return TPromise.as(void 0); + } // Global methods openWindow(paths: string[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean }): TPromise { return TPromise.as(void 0);