From c063542c2ad993fc76fd4e6d742bab16d3c72b91 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Wed, 3 Aug 2016 18:03:53 +0200 Subject: [PATCH 1/9] wip: UrlService --- build/gulpfile.vscode.js | 5 +++++ package.json | 2 +- src/vs/code/electron-main/main.ts | 6 ++++++ src/vs/platform/url/common/url.ts | 16 ++++++++++++++++ src/vs/platform/url/common/urlIpc.ts | 6 ++++++ src/vs/platform/url/node/urlService.ts | 19 +++++++++++++++++++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 src/vs/platform/url/common/url.ts create mode 100644 src/vs/platform/url/common/urlIpc.ts create mode 100644 src/vs/platform/url/node/urlService.ts diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 757ec894355..ae2399c2d7b 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -108,6 +108,11 @@ const config = { extensions: ["ascx", "asp", "aspx", "bash", "bash_login", "bash_logout", "bash_profile", "bashrc", "bat", "bowerrc", "c", "cc", "clj", "cljs", "cljx", "clojure", "cmd", "coffee", "config", "cpp", "cs", "cshtml", "csproj", "css", "csx", "ctp", "cxx", "dockerfile", "dot", "dtd", "editorconfig", "edn", "eyaml", "eyml", "fs", "fsi", "fsscript", "fsx", "gemspec", "gitattributes", "gitconfig", "gitignore", "go", "h", "handlebars", "hbs", "hh", "hpp", "htm", "html", "hxx", "ini", "jade", "jav", "java", "js", "jscsrc", "jshintrc", "jshtm", "json", "jsp", "less", "lua", "m", "makefile", "markdown", "md", "mdoc", "mdown", "mdtext", "mdtxt", "mdwn", "mkd", "mkdn", "ml", "mli", "php", "phtml", "pl", "pl6", "pm", "pm6", "pod", "pp", "profile", "properties", "ps1", "psd1", "psgi", "psm1", "py", "r", "rb", "rhistory", "rprofile", "rs", "rt", "scss", "sh", "shtml", "sql", "svg", "svgz", "t", "ts", "txt", "vb", "wxi", "wxl", "wxs", "xaml", "xml", "yaml", "yml", "zlogin", "zlogout", "zprofile", "zsh", "zshenv", "zshrc"], iconFile: 'resources/darwin/code_file.icns' }], + darwinBundleURLTypes: [{ + role: 'Viewer', + name: product.nameLong, + urlSchemes: ['vscode'] + }], darwinCredits: darwinCreditsTemplate ? new Buffer(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : void 0, linuxExecutableName: product.applicationName, winIcon: 'resources/win32/code.ico', diff --git a/package.json b/package.json index 40743620192..871851b1e8d 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "ghooks": "1.0.3", "glob": "^5.0.13", "gulp": "^3.8.9", - "gulp-atom-electron": "^1.7.1", + "gulp-atom-electron": "^1.8.0", "gulp-azure-storage": "^0.6.0", "gulp-bom": "^1.0.0", "gulp-buffer": "0.0.2", diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index c3668abf925..e23a8478ba2 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -144,6 +144,12 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn dispose(); }); + app.setAsDefaultProtocolClient('vscode'); + + app.on('open-url', url => { + console.log(url); + }); + // Dispose on vscode:exit ipc.on('vscode:exit', (event, code: number) => { logService.log('IPC#vscode:exit', code); diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts new file mode 100644 index 00000000000..a3ef9cf051a --- /dev/null +++ b/src/vs/platform/url/common/url.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event from 'vs/base/common/event'; +import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; + +export const ID = 'urlService'; +export const IURLService = createDecorator(ID); + +export interface IURLService { + onOpenUrl: Event; +} diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts new file mode 100644 index 00000000000..2000711f430 --- /dev/null +++ b/src/vs/platform/url/common/urlIpc.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; \ No newline at end of file diff --git a/src/vs/platform/url/node/urlService.ts b/src/vs/platform/url/node/urlService.ts new file mode 100644 index 00000000000..ce3dce08007 --- /dev/null +++ b/src/vs/platform/url/node/urlService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event, {Emitter} from 'vs/base/common/event'; +import {IURLService} from 'vs/platform/url/common/url'; + +export class URLService implements IURLService { + + private _onOpenUrl = new Emitter(); + onOpenUrl: Event = this._onOpenUrl.event; + + constructor() { + + } +} \ No newline at end of file From dccf029ef66e3367bf452b40b763f1f5e5ac4c53 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 09:55:58 +0200 Subject: [PATCH 2/9] electron ipc --- src/vs/base/parts/ipc/common/ipc.electron.ts | 102 +++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/vs/base/parts/ipc/common/ipc.electron.ts diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts new file mode 100644 index 00000000000..5712b4082e6 --- /dev/null +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc'; + +const Hello = 'ipc:hello'; +const Goodbye = 'ipc:goodbye'; +const Message = 'ipc:message'; + +export interface IPC extends NodeJS.EventEmitter { + send(channel: string, ...args: any[]): void; +} + +class Protocol implements IMessagePassingProtocol { + + private listener: IDisposable; + + constructor(private ipc: IPC) {} + + send(message: any): void { + this.ipc.send(Message, message); + } + + onMessage(callback: (message: any) => void): void { + const cb = (_, m) => callback(m); + this.ipc.on(Message, cb); + this.listener = toDisposable(() => this.ipc.removeListener(Message, cb)); + } + + dispose(): void { + this.listener = dispose(this.listener); + } +} + +export class Server implements IServer, IDisposable { + + private channels: { [name: string]: IChannel }; + + constructor(ipc: IPC) { + this.channels = Object.create(null); + + ipc.on(Hello, ({ sender }) => { + const protocol = new Protocol(sender); + const ipcServer = new IPCServer(protocol); + + Object.keys(this.channels) + .forEach(name => ipcServer.registerChannel(name, this.channels[name])); + + sender.once(Goodbye, () => { + ipcServer.dispose(); + protocol.dispose(); + }); + + sender.send(Hello); + }); + } + + registerChannel(channelName: string, channel: IChannel): void { + this.channels[channelName] = channel; + } + + dispose(): void { + this.channels = null; + } +} + +export class Client implements IClient, IDisposable { + + private protocol: Protocol; + private ipcClient: IPCClient; + + constructor(private ipc: IPC) { + this.protocol = new Protocol(ipc); + this.ipcClient = new IPCClient(this.protocol); + } + + getChannel(channelName: string): T { + return this.ipcClient.getChannel(channelName) as T; + } + + dispose(): void { + this.ipc.send(Goodbye); + this.protocol = dispose(this.protocol); + } +} + +export function connect(ipc: IPC): TPromise { + return new TPromise((c, e) => { + ipc.once(Hello, () => { + ipc.removeListener('error', e); + c(new Client(ipc)); + }); + + ipc.once('error', e); + ipc.send(Hello); + }); +} \ No newline at end of file From fe33ef722aa8f1b0739e0a4e99f4ecdaa765d7fd Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 11:51:40 +0200 Subject: [PATCH 3/9] wip: url service --- src/vs/base/parts/ipc/common/ipc.electron.ts | 34 ++++++------------ src/vs/code/electron-main/main.ts | 33 +++++++++-------- src/vs/platform/url/common/url.ts | 3 +- src/vs/platform/url/common/urlIpc.ts | 33 ++++++++++++++++- .../platform/url/electron-main/urlService.ts | 36 +++++++++++++++++++ src/vs/platform/url/node/urlService.ts | 19 ---------- src/vs/workbench/electron-browser/shell.ts | 14 ++++++-- .../extensionsWorkbenchExtension.ts | 7 +++- 8 files changed, 118 insertions(+), 61 deletions(-) create mode 100644 src/vs/platform/url/electron-main/urlService.ts delete mode 100644 src/vs/platform/url/node/urlService.ts diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts index 5712b4082e6..4d27c87d1d3 100644 --- a/src/vs/base/parts/ipc/common/ipc.electron.ts +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc'; @@ -12,24 +11,26 @@ const Hello = 'ipc:hello'; const Goodbye = 'ipc:goodbye'; const Message = 'ipc:message'; -export interface IPC extends NodeJS.EventEmitter { +export interface Sender { send(channel: string, ...args: any[]): void; } +export interface IPC extends Sender, NodeJS.EventEmitter {} + class Protocol implements IMessagePassingProtocol { private listener: IDisposable; - constructor(private ipc: IPC) {} + constructor(private sender: Sender, private receiver: NodeJS.EventEmitter) {} send(message: any): void { - this.ipc.send(Message, message); + this.sender.send(Message, message); } onMessage(callback: (message: any) => void): void { const cb = (_, m) => callback(m); - this.ipc.on(Message, cb); - this.listener = toDisposable(() => this.ipc.removeListener(Message, cb)); + this.receiver.on(Message, cb); + this.listener = toDisposable(() => this.receiver.removeListener(Message, cb)); } dispose(): void { @@ -41,11 +42,11 @@ export class Server implements IServer, IDisposable { private channels: { [name: string]: IChannel }; - constructor(ipc: IPC) { + constructor(ipc: NodeJS.EventEmitter) { this.channels = Object.create(null); ipc.on(Hello, ({ sender }) => { - const protocol = new Protocol(sender); + const protocol = new Protocol(sender, ipc); const ipcServer = new IPCServer(protocol); Object.keys(this.channels) @@ -55,8 +56,6 @@ export class Server implements IServer, IDisposable { ipcServer.dispose(); protocol.dispose(); }); - - sender.send(Hello); }); } @@ -75,7 +74,8 @@ export class Client implements IClient, IDisposable { private ipcClient: IPCClient; constructor(private ipc: IPC) { - this.protocol = new Protocol(ipc); + ipc.send(Hello); + this.protocol = new Protocol(ipc, ipc); this.ipcClient = new IPCClient(this.protocol); } @@ -87,16 +87,4 @@ export class Client implements IClient, IDisposable { this.ipc.send(Goodbye); this.protocol = dispose(this.protocol); } -} - -export function connect(ipc: IPC): TPromise { - return new TPromise((c, e) => { - ipc.once(Hello, () => { - ipc.removeListener('error', e); - c(new Client(ipc)); - }); - - ipc.once('error', e); - ipc.send(Hello); - }); } \ No newline at end of file diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index e23a8478ba2..c837d853050 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -16,6 +16,7 @@ import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifec import { VSCodeMenu } from 'vs/code/electron-main/menus'; import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings'; import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager'; +import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron'; 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'; @@ -31,6 +32,8 @@ import { ILogService, MainLogService } from 'vs/code/electron-main/log'; import { IStorageService, StorageService } from 'vs/code/electron-main/storage'; import * as cp from 'child_process'; import { generateUuid } from 'vs/base/common/uuid'; +import { URLChannel } from 'vs/platform/url/common/urlIpc'; +import { URLService } from 'vs/platform/url/electron-main/urlService'; function quit(accessor: ServicesAccessor, error?: Error); function quit(accessor: ServicesAccessor, message?: string); @@ -52,7 +55,7 @@ function quit(accessor: ServicesAccessor, arg?: any) { process.exit(exitCode); // in main, process.exit === app.exit } -function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEnvironment): void { +function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProcessEnvironment): void { const instantiationService = accessor.get(IInstantiationService); const logService = accessor.get(ILogService); const envService = accessor.get(IEnvironmentService); @@ -93,14 +96,22 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn // noop } - // Register IPC services + // Register Main IPC services const launchService = instantiationService.createInstance(LaunchService); const launchChannel = new LaunchChannel(launchService); - ipcServer.registerChannel('launch', launchChannel); + mainIpcServer.registerChannel('launch', launchChannel); const askpassService = new GitAskpassService(); const askpassChannel = new AskpassChannel(askpassService); - ipcServer.registerChannel('askpass', askpassChannel); + mainIpcServer.registerChannel('askpass', askpassChannel); + + // Create Electron IPC Server + const electronIpcServer = new ElectronIPCServer(ipc); + + // Register Electron IPC services + const urlService = instantiationService.createInstance(URLService); + const urlChannel = new URLChannel(urlService); + electronIpcServer.registerChannel('url', urlChannel); // Used by sub processes to communicate back to the main instance process.env['VSCODE_PID'] = '' + process.pid; @@ -125,9 +136,9 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn global.programStart = envService.cliArgs.programStart; function dispose() { - if (ipcServer) { - ipcServer.dispose(); - ipcServer = null; + if (mainIpcServer) { + mainIpcServer.dispose(); + mainIpcServer = null; } sharedProcess.dispose(); @@ -144,12 +155,6 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn dispose(); }); - app.setAsDefaultProtocolClient('vscode'); - - app.on('open-url', url => { - console.log(url); - }); - // Dispose on vscode:exit ipc.on('vscode:exit', (event, code: number) => { logService.log('IPC#vscode:exit', code); @@ -356,6 +361,6 @@ getUserEnvironment() return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths()) .then(() => instantiationService.invokeFunction(setupIPC)) - .then(ipcServer => instantiationService.invokeFunction(main, ipcServer, userEnv)); + .then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, userEnv)); }) .done(null, err => instantiationService.invokeFunction(quit, err)); \ No newline at end of file diff --git a/src/vs/platform/url/common/url.ts b/src/vs/platform/url/common/url.ts index a3ef9cf051a..7caa5dbda95 100644 --- a/src/vs/platform/url/common/url.ts +++ b/src/vs/platform/url/common/url.ts @@ -12,5 +12,6 @@ export const ID = 'urlService'; export const IURLService = createDecorator(ID); export interface IURLService { - onOpenUrl: Event; + _serviceBrand: any; + onOpenURL: Event; } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 2000711f430..a00c7c1c5ec 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -3,4 +3,35 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; \ No newline at end of file +'use strict'; + +import { TPromise } from 'vs/base/common/winjs.base'; +import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; +import { IURLService } from './url'; +import Event from 'vs/base/common/event'; + +export interface IURLChannel extends IChannel { + call(command: 'event:onOpenURL'): TPromise; + call(command: string, arg: any): TPromise; +} + +export class URLChannel implements IURLChannel { + + constructor(private service: IURLService) { } + + call(command: string, arg: any): TPromise { + switch (command) { + case 'event:onOpenURL': return eventToCall(this.service.onOpenURL); + } + } +} + +export class URLChannelClient implements IURLService { + + _serviceBrand: any; + + constructor(private channel: IChannel) { } + + private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL'); + get onOpenURL(): Event { return this._onOpenURL; } +} \ No newline at end of file diff --git a/src/vs/platform/url/electron-main/urlService.ts b/src/vs/platform/url/electron-main/urlService.ts new file mode 100644 index 00000000000..6db88e185a1 --- /dev/null +++ b/src/vs/platform/url/electron-main/urlService.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event, {Emitter} from 'vs/base/common/event'; +import {IDisposable, dispose, toDisposable} from 'vs/base/common/lifecycle'; +import {IURLService} from 'vs/platform/url/common/url'; +import {app} from 'electron'; + +export class URLService implements IURLService, IDisposable { + + _serviceBrand: any; + + private _onOpenURL = new Emitter(); + onOpenURL: Event = this._onOpenURL.event; + private disposables: IDisposable[] = []; + + constructor() { + const handler = (e: Electron.Event, url: string) => { + e.preventDefault(); + this._onOpenURL.fire(url); + }; + + app.on('open-url', handler); + this.disposables.push(toDisposable(() => app.removeListener('open-url', handler))); + + // app.setAsDefaultProtocolClient('vscode'); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} \ No newline at end of file diff --git a/src/vs/platform/url/node/urlService.ts b/src/vs/platform/url/node/urlService.ts deleted file mode 100644 index ce3dce08007..00000000000 --- a/src/vs/platform/url/node/urlService.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 Event, {Emitter} from 'vs/base/common/event'; -import {IURLService} from 'vs/platform/url/common/url'; - -export class URLService implements IURLService { - - private _onOpenUrl = new Emitter(); - onOpenUrl: Event = this._onOpenUrl.event; - - constructor() { - - } -} \ 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 9897ba3accb..7601de4a0b9 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -70,9 +70,13 @@ import {CrashReporter} from 'vs/workbench/electron-browser/crashReporter'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; import {ThemeService} from 'vs/workbench/services/themes/electron-browser/themeService'; import {getDelayedChannel} from 'vs/base/parts/ipc/common/ipc'; -import {connect} from 'vs/base/parts/ipc/node/ipc.net'; +import {connect as connectNet} from 'vs/base/parts/ipc/node/ipc.net'; +import {Client as ElectronIPCClient} from 'vs/base/parts/ipc/common/ipc.electron'; +import {ipcRenderer} from 'electron'; import {IExtensionManagementChannel, ExtensionManagementChannelClient} from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import {IExtensionManagementService} from 'vs/platform/extensionManagement/common/extensionManagement'; +import {URLChannelClient} from 'vs/platform/url/common/urlIpc'; +import {IURLService} from 'vs/platform/url/common/url'; import {ReloadWindowAction} from 'vs/workbench/electron-browser/actions'; // self registering services @@ -202,7 +206,7 @@ export class WorkbenchShell { } private initServiceCollection(): [InstantiationService, ServiceCollection] { - const sharedProcess = connect(process.env['VSCODE_SHARED_IPC_HOOK']); + const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']); sharedProcess.done(service => { service.onClose(() => { this.messageService.show(Severity.Error, { @@ -212,6 +216,8 @@ export class WorkbenchShell { }); }, errors.onUnexpectedError); + const mainProcessClient = new ElectronIPCClient(ipcRenderer); + const serviceCollection = new ServiceCollection(); serviceCollection.set(IEventService, this.eventService); serviceCollection.set(IWorkspaceContextService, this.contextService); @@ -314,6 +320,10 @@ export class WorkbenchShell { const extensionManagementChannelClient = instantiationService.createInstance(ExtensionManagementChannelClient, extensionManagementChannel); serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient); + const urlChannel = mainProcessClient.getChannel('url'); + const urlChannelClient = instantiationService.createInstance(URLChannelClient, urlChannel); + serviceCollection.set(IURLService, urlChannelClient); + return [instantiationService, serviceCollection]; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts index 8233041d58e..1422643cd06 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension.ts @@ -17,6 +17,7 @@ import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activityService'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { ipcRenderer as ipc } from 'electron'; +import { IURLService } from 'vs/platform/url/common/url'; interface IInstallExtensionsRequest { extensionsToInstall: string[]; @@ -31,7 +32,8 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { @IMessageService private messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IExtensionTipsService extenstionTips: IExtensionTipsService, // this is to eagerly start the service - @IExtensionGalleryService galleryService: IExtensionGalleryService + @IExtensionGalleryService galleryService: IExtensionGalleryService, + @IURLService urlService: IURLService ) { this.registerListeners(); @@ -40,6 +42,9 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { if (options.extensionsToInstall && options.extensionsToInstall.length) { this.install(options.extensionsToInstall).done(null, onUnexpectedError); } + + urlService.onOpenURL(url => console.log(url)); + //actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallExtensionAction, InstallExtensionAction.ID, InstallExtensionAction.LABEL), 'Extensions: Install Extension', ExtensionsLabel); } From 94466a2da5076e480648552034c7ce6ccae54136 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 16:39:26 +0200 Subject: [PATCH 4/9] node: fromEventEmitter --- src/vs/base/node/event.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/vs/base/node/event.ts diff --git a/src/vs/base/node/event.ts b/src/vs/base/node/event.ts new file mode 100644 index 00000000000..5ba3b481692 --- /dev/null +++ b/src/vs/base/node/event.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * 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 Event, { Emitter } from 'vs/base/common/event'; +import { EventEmitter } from 'events'; + +export function fromEventEmitter(emitter: EventEmitter, eventName: string, map: (...args: any[]) => T = ([arg]) => arg): Event { + const fn = (...args) => result.fire(map(...args)); + const onFirstListenerAdd = () => emitter.on(eventName, fn); + const onLastListenerRemove = () => emitter.removeListener(eventName, fn); + const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove }); + + return result.event; +}; From 2f4aa9dee1a24674c6fea7ffe24b8cf8892da8f4 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 16:40:08 +0200 Subject: [PATCH 5/9] lifecycle is HARD --- src/vs/base/parts/ipc/common/ipc.electron.ts | 62 ++++++++++++------- src/vs/base/parts/ipc/common/ipc.ts | 23 +++++-- .../platform/url/electron-main/urlService.ts | 25 +++----- src/vs/workbench/electron-browser/shell.ts | 4 +- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts index 4d27c87d1d3..87fe8075e2c 100644 --- a/src/vs/base/parts/ipc/common/ipc.electron.ts +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import Event, { mapEvent, filterEvent } from 'vs/base/common/event'; +import { fromEventEmitter } from 'vs/base/node/event'; import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc'; const Hello = 'ipc:hello'; @@ -21,16 +23,14 @@ class Protocol implements IMessagePassingProtocol { private listener: IDisposable; - constructor(private sender: Sender, private receiver: NodeJS.EventEmitter) {} + constructor(private sender: Sender, private onMessageEvent: Event) {} send(message: any): void { this.sender.send(Message, message); } onMessage(callback: (message: any) => void): void { - const cb = (_, m) => callback(m); - this.receiver.on(Message, cb); - this.listener = toDisposable(() => this.receiver.removeListener(Message, cb)); + this.listener = this.onMessageEvent(callback); } dispose(): void { @@ -38,31 +38,46 @@ class Protocol implements IMessagePassingProtocol { } } +interface IIPCEvent { + event: any; + message: string; +} + export class Server implements IServer, IDisposable { - private channels: { [name: string]: IChannel }; + private channels: { [name: string]: IChannel } = Object.create(null); - constructor(ipc: NodeJS.EventEmitter) { - this.channels = Object.create(null); - - ipc.on(Hello, ({ sender }) => { - const protocol = new Protocol(sender, ipc); - const ipcServer = new IPCServer(protocol); - - Object.keys(this.channels) - .forEach(name => ipcServer.registerChannel(name, this.channels[name])); - - sender.once(Goodbye, () => { - ipcServer.dispose(); - protocol.dispose(); - }); - }); + constructor(private ipc: NodeJS.EventEmitter) { + ipc.on(Hello, ({ sender }) => this.onHello(sender)); } registerChannel(channelName: string, channel: IChannel): void { this.channels[channelName] = channel; } + private onHello(sender: any): void { + const senderId = sender.getId(); + const onMessage = this.createScopedEvent(Message, senderId); + const protocol = new Protocol(sender, onMessage); + const ipcServer = new IPCServer(protocol); + + Object.keys(this.channels) + .forEach(name => ipcServer.registerChannel(name, this.channels[name])); + + const onGoodbye = this.createScopedEvent(Goodbye, senderId); + const listener = onGoodbye(() => { + listener.dispose(); + ipcServer.dispose(); + protocol.dispose(); + }); + } + + private createScopedEvent(eventName: string, senderId: string) { + const onRawMessageEvent = fromEventEmitter(this.ipc, eventName, (event, message) => ({ event, message })); + const onScopedRawMessageEvent = filterEvent(onRawMessageEvent, ({ event }) => event.sender.getId() === senderId); + return mapEvent(onScopedRawMessageEvent, ({ message }) => message); + } + dispose(): void { this.channels = null; } @@ -75,7 +90,9 @@ export class Client implements IClient, IDisposable { constructor(private ipc: IPC) { ipc.send(Hello); - this.protocol = new Protocol(ipc, ipc); + + const receiverEvent = fromEventEmitter(ipc, Message, (_, message) => message); + this.protocol = new Protocol(ipc, receiverEvent); this.ipcClient = new IPCClient(this.protocol); } @@ -86,5 +103,6 @@ export class Client implements IClient, IDisposable { dispose(): void { this.ipc.send(Goodbye); this.protocol = dispose(this.protocol); + this.ipcClient = dispose(this.ipcClient); } } \ No newline at end of file diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 0562a627947..ecbb725eba5 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -148,15 +148,17 @@ export class Server { } } -export class Client implements IClient { +export class Client implements IClient, IDisposable { private state: State; + private activeRequests: Promise[]; private bufferedRequests: IRequest[]; private handlers: { [id: number]: IHandler; }; private lastRequestId: number; constructor(private protocol: IMessagePassingProtocol) { this.state = State.Uninitialized; + this.activeRequests = []; this.bufferedRequests = []; this.handlers = Object.create(null); this.lastRequestId = 0; @@ -179,11 +181,17 @@ export class Client implements IClient { } }; - if (this.state === State.Uninitialized) { - return this.bufferRequest(request); - } + const activeRequest = this.state === State.Uninitialized + ? this.bufferRequest(request) + : this.doRequest(request); - return this.doRequest(request); + this.activeRequests.push(activeRequest); + + activeRequest + .then(null, _ => null) + .done(() => this.activeRequests = this.activeRequests.filter(i => i !== activeRequest)); + + return activeRequest; } private doRequest(request: IRequest): Promise { @@ -274,6 +282,11 @@ export class Client implements IClient { // noop } } + + dispose(): void { + this.activeRequests.forEach(r => r.cancel()); + this.activeRequests = []; + } } export function getDelayedChannel(promise: TPromise): T { diff --git a/src/vs/platform/url/electron-main/urlService.ts b/src/vs/platform/url/electron-main/urlService.ts index 6db88e185a1..089843cc660 100644 --- a/src/vs/platform/url/electron-main/urlService.ts +++ b/src/vs/platform/url/electron-main/urlService.ts @@ -5,32 +5,25 @@ 'use strict'; -import Event, {Emitter} from 'vs/base/common/event'; -import {IDisposable, dispose, toDisposable} from 'vs/base/common/lifecycle'; +import Event, {mapEvent} from 'vs/base/common/event'; +import {fromEventEmitter} from 'vs/base/node/event'; import {IURLService} from 'vs/platform/url/common/url'; import {app} from 'electron'; -export class URLService implements IURLService, IDisposable { +export class URLService implements IURLService { _serviceBrand: any; - private _onOpenURL = new Emitter(); - onOpenURL: Event = this._onOpenURL.event; - private disposables: IDisposable[] = []; + onOpenURL: Event; constructor() { - const handler = (e: Electron.Event, url: string) => { - e.preventDefault(); - this._onOpenURL.fire(url); - }; + const rawOnOpenUrl = fromEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url })); - app.on('open-url', handler); - this.disposables.push(toDisposable(() => app.removeListener('open-url', handler))); + this.onOpenURL = mapEvent(rawOnOpenUrl, ({ event, url }) => { + event.preventDefault(); + return url; + }); // app.setAsDefaultProtocolClient('vscode'); } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } \ 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 7601de4a0b9..7533a6b82c9 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -206,6 +206,8 @@ export class WorkbenchShell { } private initServiceCollection(): [InstantiationService, ServiceCollection] { + const disposables = new Disposables(); + const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']); sharedProcess.done(service => { service.onClose(() => { @@ -217,6 +219,7 @@ export class WorkbenchShell { }, errors.onUnexpectedError); const mainProcessClient = new ElectronIPCClient(ipcRenderer); + disposables.add(mainProcessClient); const serviceCollection = new ServiceCollection(); serviceCollection.set(IEventService, this.eventService); @@ -224,7 +227,6 @@ export class WorkbenchShell { serviceCollection.set(IConfigurationService, this.configurationService); const instantiationService = new InstantiationService(serviceCollection, true); - const disposables = new Disposables(); this.windowService = instantiationService.createInstance(WindowService); serviceCollection.set(IWindowService, this.windowService); From 7803b68ca1c18e489ac8d9f80b85da6f63d36587 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 18:38:13 +0200 Subject: [PATCH 6/9] wip: send openUrl only to the active window --- src/vs/base/parts/ipc/common/ipc.ts | 4 ++-- src/vs/code/electron-main/main.ts | 5 ++++- src/vs/platform/url/common/urlIpc.ts | 10 +++++----- src/vs/workbench/electron-browser/shell.ts | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index ecbb725eba5..72ba4b14cde 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -319,12 +319,12 @@ export function eventToCall(event: Event): TPromise { ); } -export function eventFromCall(channel: IChannel, name: string): Event { +export function eventFromCall(channel: IChannel, name: string, arg: any = null): Event { let promise: Promise; const emitter = new Emitter({ onFirstListenerAdd: () => { - promise = channel.call(name, null).then(null, err => null, e => emitter.fire(e)); + promise = channel.call(name, arg).then(null, err => null, e => emitter.fire(e)); }, onLastListenerRemove: () => { promise.cancel(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index c837d853050..16062c55747 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -110,7 +110,10 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce // Register Electron IPC services const urlService = instantiationService.createInstance(URLService); - const urlChannel = new URLChannel(urlService); + const urlChannel = new URLChannel(urlService, id => { + const window = windowsService.getFocusedWindow() || windowsService.getLastActiveWindow(); + return window ? window.id === id : false; + }); electronIpcServer.registerChannel('url', urlChannel); // Used by sub processes to communicate back to the main instance diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index a00c7c1c5ec..65cef3f4b9c 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; import { IURLService } from './url'; -import Event from 'vs/base/common/event'; +import Event, { filterEvent } from 'vs/base/common/event'; export interface IURLChannel extends IChannel { call(command: 'event:onOpenURL'): TPromise; @@ -17,11 +17,11 @@ export interface IURLChannel extends IChannel { export class URLChannel implements IURLChannel { - constructor(private service: IURLService) { } + constructor(private service: IURLService, private eventScope: (id: number) => boolean) { } call(command: string, arg: any): TPromise { switch (command) { - case 'event:onOpenURL': return eventToCall(this.service.onOpenURL); + case 'event:onOpenURL': return eventToCall(filterEvent(this.service.onOpenURL, () => this.eventScope(arg as number))); } } } @@ -30,8 +30,8 @@ export class URLChannelClient implements IURLService { _serviceBrand: any; - constructor(private channel: IChannel) { } + constructor(private channel: IChannel, private id: number) { } - private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL'); + private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL', this.id); get onOpenURL(): Event { return this._onOpenURL; } } \ 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 7533a6b82c9..8cd50acf3b9 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -323,7 +323,7 @@ export class WorkbenchShell { serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient); const urlChannel = mainProcessClient.getChannel('url'); - const urlChannelClient = instantiationService.createInstance(URLChannelClient, urlChannel); + const urlChannelClient = instantiationService.createInstance(URLChannelClient, urlChannel, this.windowService.getWindowId()); serviceCollection.set(IURLService, urlChannelClient); return [instantiationService, serviceCollection]; From 51047443d38c890c7df598f5ea69d9b15a17a470 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 18:59:44 +0200 Subject: [PATCH 7/9] pimp up open-url --- src/vs/code/electron-main/main.ts | 5 +---- src/vs/platform/url/common/urlIpc.ts | 25 ++++++++++++++++++---- src/vs/workbench/electron-browser/shell.ts | 4 ++-- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 16062c55747..90dee27a17b 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -110,10 +110,7 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce // Register Electron IPC services const urlService = instantiationService.createInstance(URLService); - const urlChannel = new URLChannel(urlService, id => { - const window = windowsService.getFocusedWindow() || windowsService.getLastActiveWindow(); - return window ? window.id === id : false; - }); + const urlChannel = instantiationService.createInstance(URLChannel, urlService); electronIpcServer.registerChannel('url', urlChannel); // Used by sub processes to communicate back to the main instance diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 65cef3f4b9c..bb4f041bd8a 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -9,6 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; import { IURLService } from './url'; import Event, { filterEvent } from 'vs/base/common/event'; +import { IWindowsService } from 'vs/code/electron-main/windows'; export interface IURLChannel extends IChannel { call(command: 'event:onOpenURL'): TPromise; @@ -17,21 +18,37 @@ export interface IURLChannel extends IChannel { export class URLChannel implements IURLChannel { - constructor(private service: IURLService, private eventScope: (id: number) => boolean) { } + constructor( + private service: IURLService, + @IWindowsService private windowsService: IWindowsService + ) { } call(command: string, arg: any): TPromise { switch (command) { - case 'event:onOpenURL': return eventToCall(filterEvent(this.service.onOpenURL, () => this.eventScope(arg as number))); + case 'event:onOpenURL': return eventToCall(filterEvent(this.service.onOpenURL, () => this.isWindowFocused(arg))); } } + + /** + * We only want the focused window to get pinged with the onOpenUrl event. + * The idea here is to filter the onOpenUrl event with the knowledge of which + * was the last window to be focused. When first listening to the event, + * each client sends its window ID via the arguments to `call(...)`. + * When the event fires, the server has enough knowledge to filter the event + * and fire it only to the focused window. + */ + private isWindowFocused(windowID: number): boolean { + const window = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow(); + return window ? window.id === windowID : false; + } } export class URLChannelClient implements IURLService { _serviceBrand: any; - constructor(private channel: IChannel, private id: number) { } + constructor(private channel: IChannel, private windowID: number) { } - private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL', this.id); + private _onOpenURL = eventFromCall(this.channel, 'event:onOpenURL', this.windowID); get onOpenURL(): Event { return this._onOpenURL; } } \ 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 8cd50acf3b9..b9ae5e19149 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -319,11 +319,11 @@ export class WorkbenchShell { serviceCollection.set(ICodeEditorService, codeEditorService); const extensionManagementChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('extensions'))); - const extensionManagementChannelClient = instantiationService.createInstance(ExtensionManagementChannelClient, extensionManagementChannel); + const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel); serviceCollection.set(IExtensionManagementService, extensionManagementChannelClient); const urlChannel = mainProcessClient.getChannel('url'); - const urlChannelClient = instantiationService.createInstance(URLChannelClient, urlChannel, this.windowService.getWindowId()); + const urlChannelClient = new URLChannelClient(urlChannel, this.windowService.getWindowId()); serviceCollection.set(IURLService, urlChannelClient); return [instantiationService, serviceCollection]; From 36e96174e0c0c253ef65ce2daaa643c8a9aee239 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Thu, 4 Aug 2016 19:02:34 +0200 Subject: [PATCH 8/9] :lipstick: --- src/vs/base/parts/ipc/common/ipc.electron.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.electron.ts b/src/vs/base/parts/ipc/common/ipc.electron.ts index 87fe8075e2c..d11db74cce0 100644 --- a/src/vs/base/parts/ipc/common/ipc.electron.ts +++ b/src/vs/base/parts/ipc/common/ipc.electron.ts @@ -91,7 +91,7 @@ export class Client implements IClient, IDisposable { constructor(private ipc: IPC) { ipc.send(Hello); - const receiverEvent = fromEventEmitter(ipc, Message, (_, message) => message); + const receiverEvent = fromEventEmitter(ipc, Message, (_, message) => message); this.protocol = new Protocol(ipc, receiverEvent); this.ipcClient = new IPCClient(this.protocol); } @@ -102,7 +102,7 @@ export class Client implements IClient, IDisposable { dispose(): void { this.ipc.send(Goodbye); - this.protocol = dispose(this.protocol); this.ipcClient = dispose(this.ipcClient); + this.protocol = dispose(this.protocol); } } \ No newline at end of file From 7ab8a18a02e8948330cf9a103f90cffbcefbd783 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 5 Aug 2016 12:26:26 +0200 Subject: [PATCH 9/9] propagate product.urlProtocol --- build/gulpfile.vscode.js | 2 +- src/vs/platform/product.ts | 1 + src/vs/platform/url/electron-main/urlService.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index ae2399c2d7b..9c6d5668aaa 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -111,7 +111,7 @@ const config = { darwinBundleURLTypes: [{ role: 'Viewer', name: product.nameLong, - urlSchemes: ['vscode'] + urlSchemes: [product.urlProtocol] }], darwinCredits: darwinCreditsTemplate ? new Buffer(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : void 0, linuxExecutableName: product.applicationName, diff --git a/src/vs/platform/product.ts b/src/vs/platform/product.ts index 57a7e510e83..720faaaad87 100644 --- a/src/vs/platform/product.ts +++ b/src/vs/platform/product.ts @@ -13,6 +13,7 @@ export interface IProductConfiguration { win32AppUserModelId: string; win32MutexName: string; darwinBundleIdentifier: string; + urlProtocol: string; dataFolderName: string; downloadUrl: string; updateUrl?: string; diff --git a/src/vs/platform/url/electron-main/urlService.ts b/src/vs/platform/url/electron-main/urlService.ts index 089843cc660..710c7bc4e1d 100644 --- a/src/vs/platform/url/electron-main/urlService.ts +++ b/src/vs/platform/url/electron-main/urlService.ts @@ -8,6 +8,7 @@ import Event, {mapEvent} from 'vs/base/common/event'; import {fromEventEmitter} from 'vs/base/node/event'; import {IURLService} from 'vs/platform/url/common/url'; +import product from 'vs/platform/product'; import {app} from 'electron'; export class URLService implements IURLService { @@ -24,6 +25,6 @@ export class URLService implements IURLService { return url; }); - // app.setAsDefaultProtocolClient('vscode'); + app.setAsDefaultProtocolClient(product.urlProtocol); } } \ No newline at end of file