Merge branch 'urlservice'

This commit is contained in:
Joao Moreno 2016-08-09 15:48:51 +02:00
commit bd81089174
11 changed files with 295 additions and 21 deletions

View file

@ -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: [product.urlProtocol]
}],
darwinCredits: darwinCreditsTemplate ? new Buffer(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : void 0,
linuxExecutableName: product.applicationName,
winIcon: 'resources/win32/code.ico',

18
src/vs/base/node/event.ts Normal file
View file

@ -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<T>(emitter: EventEmitter, eventName: string, map: (...args: any[]) => T = ([arg]) => arg): Event<T> {
const fn = (...args) => result.fire(map(...args));
const onFirstListenerAdd = () => emitter.on(eventName, fn);
const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove });
return result.event;
};

View file

@ -0,0 +1,108 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';
const Goodbye = 'ipc:goodbye';
const Message = 'ipc:message';
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 sender: Sender, private onMessageEvent: Event<any>) {}
send(message: any): void {
this.sender.send(Message, message);
}
onMessage(callback: (message: any) => void): void {
this.listener = this.onMessageEvent(callback);
}
dispose(): void {
this.listener = dispose(this.listener);
}
}
interface IIPCEvent {
event: any;
message: string;
}
export class Server implements IServer, IDisposable {
private channels: { [name: string]: IChannel } = Object.create(null);
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<IIPCEvent>(this.ipc, eventName, (event, message) => ({ event, message }));
const onScopedRawMessageEvent = filterEvent<IIPCEvent>(onRawMessageEvent, ({ event }) => event.sender.getId() === senderId);
return mapEvent<IIPCEvent,string>(onScopedRawMessageEvent, ({ message }) => message);
}
dispose(): void {
this.channels = null;
}
}
export class Client implements IClient, IDisposable {
private protocol: Protocol;
private ipcClient: IPCClient;
constructor(private ipc: IPC) {
ipc.send(Hello);
const receiverEvent = fromEventEmitter<string>(ipc, Message, (_, message) => message);
this.protocol = new Protocol(ipc, receiverEvent);
this.ipcClient = new IPCClient(this.protocol);
}
getChannel<T extends IChannel>(channelName: string): T {
return this.ipcClient.getChannel(channelName) as T;
}
dispose(): void {
this.ipc.send(Goodbye);
this.ipcClient = dispose(this.ipcClient);
this.protocol = dispose(this.protocol);
}
}

View file

@ -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<T extends IChannel>(promise: TPromise<IChannel>): T {
@ -306,12 +319,12 @@ export function eventToCall(event: Event<any>): TPromise<any> {
);
}
export function eventFromCall<T>(channel: IChannel, name: string): Event<T> {
export function eventFromCall<T>(channel: IChannel, name: string, arg: any = null): Event<T> {
let promise: Promise;
const emitter = new Emitter<any>({
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();

View file

@ -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 = instantiationService.createInstance(URLChannel, urlService);
electronIpcServer.registerChannel('url', urlChannel);
// Spawn shared process
const sharedProcess = spawnSharedProcess({
@ -120,9 +131,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();
@ -372,6 +383,6 @@ getEnvironment().then(env => {
return instantiationService.invokeFunction(a => a.get(IEnvironmentService).createPaths())
.then(() => instantiationService.invokeFunction(setupIPC))
.then(ipcServer => instantiationService.invokeFunction(main, ipcServer, env));
.then(mainIpcServer => instantiationService.invokeFunction(main, mainIpcServer, env));
})
.done(null, err => instantiationService.invokeFunction(quit, err));
.done(null, err => instantiationService.invokeFunction(quit, err));

View file

@ -13,6 +13,7 @@ export interface IProductConfiguration {
win32AppUserModelId: string;
win32MutexName: string;
darwinBundleIdentifier: string;
urlProtocol: string;
dataFolderName: string;
downloadUrl: string;
updateUrl?: string;

View file

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* 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<IURLService>(ID);
export interface IURLService {
_serviceBrand: any;
onOpenURL: Event<string>;
}

View file

@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* 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 { 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<void>;
call(command: string, arg: any): TPromise<any>;
}
export class URLChannel implements IURLChannel {
constructor(
private service: IURLService,
@IWindowsService private windowsService: IWindowsService
) { }
call(command: string, arg: any): TPromise<any> {
switch (command) {
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 windowID: number) { }
private _onOpenURL = eventFromCall<string>(this.channel, 'event:onOpenURL', this.windowID);
get onOpenURL(): Event<string> { return this._onOpenURL; }
}

View file

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* 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, {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 {
_serviceBrand: any;
onOpenURL: Event<string>;
constructor() {
const rawOnOpenUrl = fromEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
this.onOpenURL = mapEvent(rawOnOpenUrl, ({ event, url }) => {
event.preventDefault();
return url;
});
app.setAsDefaultProtocolClient(product.urlProtocol);
}
}

View file

@ -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,9 @@ export class WorkbenchShell {
}
private initServiceCollection(): [InstantiationService, ServiceCollection] {
const sharedProcess = connect(process.env['VSCODE_SHARED_IPC_HOOK']);
const disposables = new Disposables();
const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']);
sharedProcess.done(service => {
service.onClose(() => {
this.messageService.show(Severity.Error, {
@ -212,13 +218,15 @@ export class WorkbenchShell {
});
}, errors.onUnexpectedError);
const mainProcessClient = new ElectronIPCClient(ipcRenderer);
disposables.add(mainProcessClient);
const serviceCollection = new ServiceCollection();
serviceCollection.set(IEventService, this.eventService);
serviceCollection.set(IWorkspaceContextService, this.contextService);
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);
@ -311,9 +319,13 @@ export class WorkbenchShell {
serviceCollection.set(ICodeEditorService, codeEditorService);
const extensionManagementChannel = getDelayedChannel<IExtensionManagementChannel>(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 = new URLChannelClient(urlChannel, this.windowService.getWindowId());
serviceCollection.set(IURLService, urlChannelClient);
return [instantiationService, serviceCollection];
}

View file

@ -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);
}