提交 94125f91 编写于 作者: J Joao Moreno

url handlers

fixes #34404
上级 241f82af
......@@ -29,7 +29,7 @@ import { IStateService } from 'vs/platform/state/common/state';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IURLService } from 'vs/platform/url/common/url';
import { URLHandlerChannelClient } from 'vs/platform/url/common/urlIpc';
import { URLHandlerChannelClient, URLServiceChannel } from 'vs/platform/url/common/urlIpc';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { ITelemetryAppenderChannel, TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc';
......@@ -59,7 +59,7 @@ import { IssueChannel } from 'vs/platform/issue/common/issueIpc';
import { IssueService } from 'vs/platform/issue/electron-main/issueService';
import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { ElectronURLListener } from 'vs/platform/url/electron-main/urlService';
import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener';
export class CodeApplication {
......@@ -369,6 +369,10 @@ export class CodeApplication {
this.electronIpcServer.registerChannel('windows', windowsChannel);
this.sharedProcessClient.done(client => client.registerChannel('windows', windowsChannel));
const urlService = accessor.get(IURLService);
const urlChannel = new URLServiceChannel(urlService);
this.electronIpcServer.registerChannel('url', urlChannel);
// Log level management
const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService));
this.electronIpcServer.registerChannel('loglevel', logLevelChannel);
......@@ -381,13 +385,16 @@ export class CodeApplication {
this.windowsMainService = accessor.get(IWindowsMainService); // TODO@Joao: unfold this
const args = this.environmentService.args;
const urlService = accessor.get(IURLService);
// Create a URL handler which forwards to the last active window
const activeWindowManager = new ActiveWindowManager(windowsService);
const urlHandlerChannel = this.electronIpcServer.getChannel('urlHandler', { route: () => activeWindowManager.activeClientId });
const multiplexURLHandler = new URLHandlerChannelClient(urlHandlerChannel);
// Register the multiple URL handker
urlService.registerHandler(multiplexURLHandler);
// Watch Electron URLs and forward them to the UrlService
const urls = args['open-url'] ? args._urls : [];
const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService);
this.toDispose.push(urlListener);
......
......@@ -40,6 +40,24 @@ export interface IMainProcessInfo {
windows: IWindowInfo[];
}
function parseOpenUrl(args: ParsedArgs): URI[] {
if (args['open-url'] && args._urls && args._urls.length > 0) {
// --open-url must contain -- followed by the url(s)
// process.argv is used over args._ as args._ are resolved to file paths at this point
return args._urls
.map(url => {
try {
return URI.parse(url);
} catch (err) {
return null;
}
})
.filter(uri => !!uri);
}
return [];
}
export interface ILaunchService {
_serviceBrand: any;
start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void>;
......@@ -119,8 +137,25 @@ export class LaunchService implements ILaunchService {
public start(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
this.logService.trace('Received data from other instance: ', args, userEnv);
const urlsToOpen = parseOpenUrl(args);
// Check early for open-url which is handled in URL service
if (this.shouldOpenUrl(args)) {
if (urlsToOpen.length) {
let whenWindowReady = TPromise.as<any>(null);
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0];
whenWindowReady = window.ready();
}
// Make sure a window is open, ready to receive the url event
whenWindowReady.then(() => {
for (const url of urlsToOpen) {
this.urlService.open(url);
}
});
return TPromise.as(null);
}
......@@ -128,27 +163,6 @@ export class LaunchService implements ILaunchService {
return this.startOpenWindow(args, userEnv);
}
private shouldOpenUrl(args: ParsedArgs): boolean {
if (args['open-url'] && args._urls && args._urls.length > 0) {
// --open-url must contain -- followed by the url(s)
// process.argv is used over args._ as args._ are resolved to file paths at this point
args._urls
.map(url => {
try {
return URI.parse(url);
} catch (err) {
return null;
}
})
.filter(uri => !!uri)
.forEach(uri => this.urlService.open(uri));
return true;
}
return false;
}
private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise<void> {
const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP;
let usedWindows: ICodeWindow[];
......
......@@ -1374,8 +1374,8 @@ export class WindowsManager implements IWindowsMainService {
return getLastActiveWindow(WindowsManager.WINDOWS);
}
public openNewWindow(context: OpenContext): void {
this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
public openNewWindow(context: OpenContext): ICodeWindow[] {
return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true });
}
public waitForWindowCloseOrLoad(windowId: number): TPromise<void> {
......
......@@ -7,8 +7,41 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IURLHandler } from './url';
import { IURLHandler, IURLService } from './url';
import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
export interface IURLServiceChannel extends IChannel {
call(command: 'open', url: string): TPromise<boolean>;
call(command: string, arg?: any): TPromise<any>;
}
export class URLServiceChannel implements IURLServiceChannel {
constructor(private service: IURLService) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'open': return this.service.open(URI.revive(arg));
}
return undefined;
}
}
export class URLServiceChannelClient implements IURLService {
_serviceBrand: any;
constructor(private channel: IChannel) { }
open(url: URI): TPromise<boolean, any> {
return this.channel.call('open', url.toJSON());
}
registerHandler(handler: IURLHandler): IDisposable {
throw new Error('Not implemented.');
}
}
export interface IURLHandlerChannel extends IChannel {
call(command: 'handleURL', arg: any): TPromise<boolean>;
......
......@@ -38,11 +38,17 @@ export class URLService implements IURLService {
}
}
export class ForwardingURLHandler implements IURLHandler {
export class RelayURLService extends URLService implements IURLHandler {
constructor(@IURLService private urlService: IURLService) { }
constructor(private urlService: IURLService) {
super();
}
handleURL(uri: URI): TPromise<boolean> {
async open(uri: URI): TPromise<boolean> {
return this.urlService.open(uri);
}
handleURL(uri: URI): TPromise<boolean> {
return super.open(uri);
}
}
\ No newline at end of file
......@@ -6,14 +6,11 @@
'use strict';
import { mapEvent, fromNodeEventEmitter, filterEvent } from 'vs/base/common/event';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import { IURLService } from 'vs/platform/url/common/url';
import product from 'vs/platform/node/product';
import { app } from 'electron';
import URI from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { Limiter } from 'vs/base/common/async';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { ReadyState } from 'vs/platform/windows/common/windows';
......@@ -25,93 +22,6 @@ function uriFromRawUrl(url: string): URI | null {
}
}
export class URLService implements IURLService {
_serviceBrand: any;
private buffer: URI[];
private handlers: IURLHandler[] = [];
private handlerLimiter = new Limiter<void>(1);
private disposables: IDisposable[] = [];
constructor(
initial: string | string[],
@ILogService private logService: ILogService
) {
const globalBuffer = (global.getOpenUrls() || []) as string[];
const rawBuffer = [
...(typeof initial === 'string' ? [initial] : initial),
...globalBuffer
];
this.buffer = rawBuffer.map(uriFromRawUrl).filter(uri => !!uri);
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']);
const onOpenElectronUrl = mapEvent(
fromNodeEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url })),
({ event, url }) => {
// always prevent default and return the url as string
event.preventDefault();
return url;
});
const onOpenUrl = filterEvent(mapEvent(onOpenElectronUrl, uriFromRawUrl), uri => !!uri);
onOpenUrl(this.openURI, this, this.disposables);
}
open(uri: URI): TPromise<boolean> {
// const uri = uriFromRawUrl(url);
// if (!uri) {
// return ;
// }
return this.openURI(uri);
}
private async openURI(uri: URI, handlers = this.handlers): TPromise<boolean> {
this.logService.trace('urlService#handleURI', uri.toString());
for (const handler of handlers) {
if (await handler.handleURL(uri)) {
return true;
}
}
return false;
}
private async flushBuffer(handler: IURLHandler): TPromise<void> {
const buffer = [...this.buffer];
for (const uri of buffer) {
if (await handler.handleURL(uri)) {
this.buffer.splice(this.buffer.indexOf(uri, 1));
}
}
}
registerHandler(handler: IURLHandler): IDisposable {
this.handlers.push(handler);
this.handlerLimiter.queue(() => this.flushBuffer(handler));
return toDisposable(() => {
const index = this.handlers.indexOf(handler);
if (index === -1) {
return;
}
this.handlers.splice(index, 1);
});
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
export class ElectronURLListener {
private buffer: URI[] = [];
......
......@@ -107,7 +107,7 @@ export interface IWindowsMainService {
focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow;
getLastActiveWindow(): ICodeWindow;
waitForWindowCloseOrLoad(windowId: number): TPromise<void>;
openNewWindow(context: OpenContext): void;
openNewWindow(context: OpenContext): ICodeWindow[];
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void;
getFocusedWindow(): ICodeWindow;
......
......@@ -489,40 +489,18 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable
async handleURL(uri: URI): TPromise<boolean> {
// Catch file URLs
if (uri.authority === Schemas.file && !!uri.path) {
this.openFileForURI(URI.file(uri.fsPath));
return true;
}
// Catch extension URLs when there are no windows open
if (uri => /^extension/.test(uri.path) && this.windowsMainService.getWindowCount() === 0) {
this.openExtensionForURI(uri);
return true;
return this.openFileForURI(URI.file(uri.fsPath));
}
return false;
}
private openFileForURI(uri: URI): TPromise<void> {
private async openFileForURI(uri: URI): TPromise<boolean> {
const cli = assign(Object.create(null), this.environmentService.args, { goto: true });
const pathsToOpen = [uri.fsPath];
this.windowsMainService.open({ context: OpenContext.API, cli, pathsToOpen });
return TPromise.as(null);
}
/**
* This should only fire whenever an extension URL is open
* and there are no windows to handle it.
*/
private async openExtensionForURI(uri: URI): TPromise<void> {
const cli = assign(Object.create(null), this.environmentService.args);
const window = await this.windowsMainService.open({ context: OpenContext.API, cli })[0];
if (!window) {
return;
}
window.win.show();
return true;
}
dispose(): void {
......
......@@ -36,18 +36,17 @@ import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/
import { webFrame } from 'electron';
import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc';
import { IUpdateService } from 'vs/platform/update/common/update';
import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc';
import { URLHandlerChannel, URLServiceChannelClient } from 'vs/platform/url/common/urlIpc';
import { IURLService } from 'vs/platform/url/common/url';
import { WorkspacesChannelClient } from 'vs/platform/workspaces/common/workspacesIpc';
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { createSpdLogService } from 'vs/platform/log/node/spdlogService';
import * as fs from 'fs';
import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log';
import { IssueChannelClient } from 'vs/platform/issue/common/issueIpc';
import { IIssueService } from 'vs/platform/issue/common/issue';
import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
import { URLService, ForwardingURLHandler } from 'vs/platform/url/common/urlService';
import { RelayURLService } from 'vs/platform/url/common/urlService';
gracefulFs.gracefulify(fs); // enable gracefulFs
......@@ -217,11 +216,12 @@ function createMainProcessServices(mainProcessClient: ElectronIPCClient, configu
const updateChannel = mainProcessClient.getChannel('update');
serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, updateChannel));
const localUrlService = new URLService();
serviceCollection.set(IURLService, localUrlService);
const urlChannel = mainProcessClient.getChannel('url');
const mainUrlService = new URLServiceChannelClient(urlChannel);
const urlService = new RelayURLService(mainUrlService);
serviceCollection.set(IURLService, urlService);
const urlHandler = new ForwardingURLHandler(localUrlService);
const urlHandlerChannel = new URLHandlerChannel(urlHandler);
const urlHandlerChannel = new URLHandlerChannel(urlService);
mainProcessClient.registerChannel('urlHandler', urlHandlerChannel);
const issueChannel = mainProcessClient.getChannel('issue');
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册