Skip to content

Commit bd81089

Browse files
committed
Merge branch 'urlservice'
2 parents 4335cfb + 7ab8a18 commit bd81089

11 files changed

Lines changed: 295 additions & 21 deletions

File tree

build/gulpfile.vscode.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ const config = {
108108
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"],
109109
iconFile: 'resources/darwin/code_file.icns'
110110
}],
111+
darwinBundleURLTypes: [{
112+
role: 'Viewer',
113+
name: product.nameLong,
114+
urlSchemes: [product.urlProtocol]
115+
}],
111116
darwinCredits: darwinCreditsTemplate ? new Buffer(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : void 0,
112117
linuxExecutableName: product.applicationName,
113118
winIcon: 'resources/win32/code.ico',

src/vs/base/node/event.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import Event, { Emitter } from 'vs/base/common/event';
9+
import { EventEmitter } from 'events';
10+
11+
export function fromEventEmitter<T>(emitter: EventEmitter, eventName: string, map: (...args: any[]) => T = ([arg]) => arg): Event<T> {
12+
const fn = (...args) => result.fire(map(...args));
13+
const onFirstListenerAdd = () => emitter.on(eventName, fn);
14+
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
15+
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
16+
17+
return result.event;
18+
};
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
7+
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
8+
import Event, { mapEvent, filterEvent } from 'vs/base/common/event';
9+
import { fromEventEmitter } from 'vs/base/node/event';
10+
import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc';
11+
12+
const Hello = 'ipc:hello';
13+
const Goodbye = 'ipc:goodbye';
14+
const Message = 'ipc:message';
15+
16+
export interface Sender {
17+
send(channel: string, ...args: any[]): void;
18+
}
19+
20+
export interface IPC extends Sender, NodeJS.EventEmitter {}
21+
22+
class Protocol implements IMessagePassingProtocol {
23+
24+
private listener: IDisposable;
25+
26+
constructor(private sender: Sender, private onMessageEvent: Event<any>) {}
27+
28+
send(message: any): void {
29+
this.sender.send(Message, message);
30+
}
31+
32+
onMessage(callback: (message: any) => void): void {
33+
this.listener = this.onMessageEvent(callback);
34+
}
35+
36+
dispose(): void {
37+
this.listener = dispose(this.listener);
38+
}
39+
}
40+
41+
interface IIPCEvent {
42+
event: any;
43+
message: string;
44+
}
45+
46+
export class Server implements IServer, IDisposable {
47+
48+
private channels: { [name: string]: IChannel } = Object.create(null);
49+
50+
constructor(private ipc: NodeJS.EventEmitter) {
51+
ipc.on(Hello, ({ sender }) => this.onHello(sender));
52+
}
53+
54+
registerChannel(channelName: string, channel: IChannel): void {
55+
this.channels[channelName] = channel;
56+
}
57+
58+
private onHello(sender: any): void {
59+
const senderId = sender.getId();
60+
const onMessage = this.createScopedEvent(Message, senderId);
61+
const protocol = new Protocol(sender, onMessage);
62+
const ipcServer = new IPCServer(protocol);
63+
64+
Object.keys(this.channels)
65+
.forEach(name => ipcServer.registerChannel(name, this.channels[name]));
66+
67+
const onGoodbye = this.createScopedEvent(Goodbye, senderId);
68+
const listener = onGoodbye(() => {
69+
listener.dispose();
70+
ipcServer.dispose();
71+
protocol.dispose();
72+
});
73+
}
74+
75+
private createScopedEvent(eventName: string, senderId: string) {
76+
const onRawMessageEvent = fromEventEmitter<IIPCEvent>(this.ipc, eventName, (event, message) => ({ event, message }));
77+
const onScopedRawMessageEvent = filterEvent<IIPCEvent>(onRawMessageEvent, ({ event }) => event.sender.getId() === senderId);
78+
return mapEvent<IIPCEvent,string>(onScopedRawMessageEvent, ({ message }) => message);
79+
}
80+
81+
dispose(): void {
82+
this.channels = null;
83+
}
84+
}
85+
86+
export class Client implements IClient, IDisposable {
87+
88+
private protocol: Protocol;
89+
private ipcClient: IPCClient;
90+
91+
constructor(private ipc: IPC) {
92+
ipc.send(Hello);
93+
94+
const receiverEvent = fromEventEmitter<string>(ipc, Message, (_, message) => message);
95+
this.protocol = new Protocol(ipc, receiverEvent);
96+
this.ipcClient = new IPCClient(this.protocol);
97+
}
98+
99+
getChannel<T extends IChannel>(channelName: string): T {
100+
return this.ipcClient.getChannel(channelName) as T;
101+
}
102+
103+
dispose(): void {
104+
this.ipc.send(Goodbye);
105+
this.ipcClient = dispose(this.ipcClient);
106+
this.protocol = dispose(this.protocol);
107+
}
108+
}

src/vs/base/parts/ipc/common/ipc.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,17 @@ export class Server {
148148
}
149149
}
150150

151-
export class Client implements IClient {
151+
export class Client implements IClient, IDisposable {
152152

153153
private state: State;
154+
private activeRequests: Promise[];
154155
private bufferedRequests: IRequest[];
155156
private handlers: { [id: number]: IHandler; };
156157
private lastRequestId: number;
157158

158159
constructor(private protocol: IMessagePassingProtocol) {
159160
this.state = State.Uninitialized;
161+
this.activeRequests = [];
160162
this.bufferedRequests = [];
161163
this.handlers = Object.create(null);
162164
this.lastRequestId = 0;
@@ -179,11 +181,17 @@ export class Client implements IClient {
179181
}
180182
};
181183

182-
if (this.state === State.Uninitialized) {
183-
return this.bufferRequest(request);
184-
}
184+
const activeRequest = this.state === State.Uninitialized
185+
? this.bufferRequest(request)
186+
: this.doRequest(request);
187+
188+
this.activeRequests.push(activeRequest);
189+
190+
activeRequest
191+
.then(null, _ => null)
192+
.done(() => this.activeRequests = this.activeRequests.filter(i => i !== activeRequest));
185193

186-
return this.doRequest(request);
194+
return activeRequest;
187195
}
188196

189197
private doRequest(request: IRequest): Promise {
@@ -274,6 +282,11 @@ export class Client implements IClient {
274282
// noop
275283
}
276284
}
285+
286+
dispose(): void {
287+
this.activeRequests.forEach(r => r.cancel());
288+
this.activeRequests = [];
289+
}
277290
}
278291

279292
export function getDelayedChannel<T extends IChannel>(promise: TPromise<IChannel>): T {
@@ -306,12 +319,12 @@ export function eventToCall(event: Event<any>): TPromise<any> {
306319
);
307320
}
308321

309-
export function eventFromCall<T>(channel: IChannel, name: string): Event<T> {
322+
export function eventFromCall<T>(channel: IChannel, name: string, arg: any = null): Event<T> {
310323
let promise: Promise;
311324

312325
const emitter = new Emitter<any>({
313326
onFirstListenerAdd: () => {
314-
promise = channel.call(name, null).then(null, err => null, e => emitter.fire(e));
327+
promise = channel.call(name, arg).then(null, err => null, e => emitter.fire(e));
315328
},
316329
onLastListenerRemove: () => {
317330
promise.cancel();

src/vs/code/electron-main/main.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { ILifecycleService, LifecycleService } from 'vs/code/electron-main/lifec
1616
import { VSCodeMenu } from 'vs/code/electron-main/menus';
1717
import { ISettingsService, SettingsManager } from 'vs/code/electron-main/settings';
1818
import { IUpdateService, UpdateManager } from 'vs/code/electron-main/update-manager';
19+
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/common/ipc.electron';
1920
import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net';
2021
import { TPromise } from 'vs/base/common/winjs.base';
2122
import { AskpassChannel } from 'vs/workbench/parts/git/common/gitIpc';
@@ -31,6 +32,8 @@ import { ILogService, MainLogService } from 'vs/code/electron-main/log';
3132
import { IStorageService, StorageService } from 'vs/code/electron-main/storage';
3233
import * as cp from 'child_process';
3334
import { generateUuid } from 'vs/base/common/uuid';
35+
import { URLChannel } from 'vs/platform/url/common/urlIpc';
36+
import { URLService } from 'vs/platform/url/electron-main/urlService';
3437

3538
function quit(accessor: ServicesAccessor, error?: Error);
3639
function quit(accessor: ServicesAccessor, message?: string);
@@ -52,7 +55,7 @@ function quit(accessor: ServicesAccessor, arg?: any) {
5255
process.exit(exitCode); // in main, process.exit === app.exit
5356
}
5457

55-
function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEnvironment): void {
58+
function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProcessEnvironment): void {
5659
const instantiationService = accessor.get(IInstantiationService);
5760
const logService = accessor.get(ILogService);
5861
const envService = accessor.get(IEnvironmentService);
@@ -93,14 +96,22 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn
9396
// noop
9497
}
9598

96-
// Register IPC services
99+
// Register Main IPC services
97100
const launchService = instantiationService.createInstance(LaunchService);
98101
const launchChannel = new LaunchChannel(launchService);
99-
ipcServer.registerChannel('launch', launchChannel);
102+
mainIpcServer.registerChannel('launch', launchChannel);
100103

101104
const askpassService = new GitAskpassService();
102105
const askpassChannel = new AskpassChannel(askpassService);
103-
ipcServer.registerChannel('askpass', askpassChannel);
106+
mainIpcServer.registerChannel('askpass', askpassChannel);
107+
108+
// Create Electron IPC Server
109+
const electronIpcServer = new ElectronIPCServer(ipc);
110+
111+
// Register Electron IPC services
112+
const urlService = instantiationService.createInstance(URLService);
113+
const urlChannel = instantiationService.createInstance(URLChannel, urlService);
114+
electronIpcServer.registerChannel('url', urlChannel);
104115

105116
// Spawn shared process
106117
const sharedProcess = spawnSharedProcess({
@@ -120,9 +131,9 @@ function main(accessor: ServicesAccessor, ipcServer: Server, userEnv: IProcessEn
120131
global.programStart = envService.cliArgs.programStart;
121132

122133
function dispose() {
123-
if (ipcServer) {
124-
ipcServer.dispose();
125-
ipcServer = null;
134+
if (mainIpcServer) {
135+
mainIpcServer.dispose();
136+
mainIpcServer = null;
126137
}
127138

128139
sharedProcess.dispose();
@@ -372,6 +383,6 @@ getEnvironment().then(env => {
372383

373384
return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths())
374385
.then(() => instantiationService.invokeFunction(setupIPC))
375-
.then(ipcServer => instantiationService.invokeFunction(main, ipcServer, env));
386+
.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, env));
376387
})
377-
.done(null, err => instantiationService.invokeFunction(quit, err));
388+
.done(null, err => instantiationService.invokeFunction(quit, err));

src/vs/platform/product.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IProductConfiguration {
1313
win32AppUserModelId: string;
1414
win32MutexName: string;
1515
darwinBundleIdentifier: string;
16+
urlProtocol: string;
1617
dataFolderName: string;
1718
downloadUrl: string;
1819
updateUrl?: string;

src/vs/platform/url/common/url.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import Event from 'vs/base/common/event';
9+
import {createDecorator} from 'vs/platform/instantiation/common/instantiation';
10+
11+
export const ID = 'urlService';
12+
export const IURLService = createDecorator<IURLService>(ID);
13+
14+
export interface IURLService {
15+
_serviceBrand: any;
16+
onOpenURL: Event<string>;
17+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import { TPromise } from 'vs/base/common/winjs.base';
9+
import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc';
10+
import { IURLService } from './url';
11+
import Event, { filterEvent } from 'vs/base/common/event';
12+
import { IWindowsService } from 'vs/code/electron-main/windows';
13+
14+
export interface IURLChannel extends IChannel {
15+
call(command: 'event:onOpenURL'): TPromise<void>;
16+
call(command: string, arg: any): TPromise<any>;
17+
}
18+
19+
export class URLChannel implements IURLChannel {
20+
21+
constructor(
22+
private service: IURLService,
23+
@IWindowsService private windowsService: IWindowsService
24+
) { }
25+
26+
call(command: string, arg: any): TPromise<any> {
27+
switch (command) {
28+
case 'event:onOpenURL': return eventToCall(filterEvent(this.service.onOpenURL, () => this.isWindowFocused(arg)));
29+
}
30+
}
31+
32+
/**
33+
* We only want the focused window to get pinged with the onOpenUrl event.
34+
* The idea here is to filter the onOpenUrl event with the knowledge of which
35+
* was the last window to be focused. When first listening to the event,
36+
* each client sends its window ID via the arguments to `call(...)`.
37+
* When the event fires, the server has enough knowledge to filter the event
38+
* and fire it only to the focused window.
39+
*/
40+
private isWindowFocused(windowID: number): boolean {
41+
const window = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow();
42+
return window ? window.id === windowID : false;
43+
}
44+
}
45+
46+
export class URLChannelClient implements IURLService {
47+
48+
_serviceBrand: any;
49+
50+
constructor(private channel: IChannel, private windowID: number) { }
51+
52+
private _onOpenURL = eventFromCall<string>(this.channel, 'event:onOpenURL', this.windowID);
53+
get onOpenURL(): Event<string> { return this._onOpenURL; }
54+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
'use strict';
7+
8+
import Event, {mapEvent} from 'vs/base/common/event';
9+
import {fromEventEmitter} from 'vs/base/node/event';
10+
import {IURLService} from 'vs/platform/url/common/url';
11+
import product from 'vs/platform/product';
12+
import {app} from 'electron';
13+
14+
export class URLService implements IURLService {
15+
16+
_serviceBrand: any;
17+
18+
onOpenURL: Event<string>;
19+
20+
constructor() {
21+
const rawOnOpenUrl = fromEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
22+
23+
this.onOpenURL = mapEvent(rawOnOpenUrl, ({ event, url }) => {
24+
event.preventDefault();
25+
return url;
26+
});
27+
28+
app.setAsDefaultProtocolClient(product.urlProtocol);
29+
}
30+
}

0 commit comments

Comments
 (0)