Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -5,15 +5,21 @@
'use strict';
import Event from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
export const ID = 'urlService';
export const IURLService = createDecorator<IURLService>(ID);
export interface IURLHandler {
handleURL(uri: URI): TPromise<boolean>;
}
export interface IURLService {
_serviceBrand: any;
open(url: string): void;
onOpenURL: Event<URI>;
open(url: URI): TPromise<boolean>;
registerHandler(handler: IURLHandler): IDisposable;
}

View File

@@ -6,61 +6,65 @@
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { IChannel, eventToCall, eventFromCall, Serializer, Deserializer } from 'vs/base/parts/ipc/common/ipc';
import { IURLService } from './url';
import Event, { filterEvent } from 'vs/base/common/event';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
import { IURLHandler, IURLService } from './url';
import URI from 'vs/base/common/uri';
import { IDisposable } from 'vs/base/common/lifecycle';
const URISerializer: Serializer<URI, any> = uri => uri.toJSON();
const URIDeserializer: Deserializer<URI, any> = raw => URI.revive(raw);
export interface IURLChannel extends IChannel {
call(command: 'event:onOpenURL'): TPromise<void>;
export interface IURLServiceChannel extends IChannel {
call(command: 'open', url: string): TPromise<boolean>;
call(command: string, arg?: any): TPromise<any>;
}
export class URLChannel implements IURLChannel {
export class URLServiceChannel implements IURLServiceChannel {
private focusedWindowId: number;
constructor(
private service: IURLService,
@IWindowsService windowsService: IWindowsService
) {
windowsService.onWindowFocus(id => this.focusedWindowId = id);
}
constructor(private service: IURLService) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'event:onOpenURL': return eventToCall(filterEvent(this.service.onOpenURL, () => this.isWindowFocused(arg)), URISerializer);
case 'open': return this.service.open(URI.revive(arg));
}
return undefined;
}
/**
* 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 {
return this.focusedWindowId === windowID;
}
}
export class URLChannelClient implements IURLService {
export class URLServiceChannelClient implements IURLService {
_serviceBrand: any;
constructor(private channel: IChannel, private windowID: number) { }
constructor(private channel: IChannel) { }
private _onOpenURL = eventFromCall<URI>(this.channel, 'event:onOpenURL', this.windowID, URIDeserializer);
get onOpenURL(): Event<URI> { return this._onOpenURL; }
open(url: URI): TPromise<boolean, any> {
return this.channel.call('open', url.toJSON());
}
open(url: string): void {
return; // not implemented
registerHandler(handler: IURLHandler): IDisposable {
throw new Error('Not implemented.');
}
}
export interface IURLHandlerChannel extends IChannel {
call(command: 'handleURL', arg: any): TPromise<boolean>;
call(command: string, arg?: any): TPromise<any>;
}
export class URLHandlerChannel implements IURLHandlerChannel {
constructor(private handler: IURLHandler) { }
call(command: string, arg?: any): TPromise<any> {
switch (command) {
case 'handleURL': return this.handler.handleURL(URI.revive(arg));
}
return undefined;
}
}
export class URLHandlerChannelClient implements IURLHandler {
constructor(private channel: IChannel) { }
handleURL(uri: URI): TPromise<boolean> {
return this.channel.call('handleURL', uri.toJSON());
}
}

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import URI from 'vs/base/common/uri';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
declare module Array {
function from<T>(set: Set<T>): T[];
}
export class URLService implements IURLService {
_serviceBrand: any;
private handlers = new Set<IURLHandler>();
async open(uri: URI): TPromise<boolean> {
const handlers = Array.from(this.handlers);
for (const handler of handlers) {
if (await handler.handleURL(uri)) {
return true;
}
}
return false;
}
registerHandler(handler: IURLHandler): IDisposable {
this.handlers.add(handler);
return toDisposable(() => this.handlers.delete(handler));
}
}
export class RelayURLService extends URLService implements IURLHandler {
constructor(private urlService: IURLService) {
super();
}
async open(uri: URI): TPromise<boolean> {
return this.urlService.open(uri);
}
handleURL(uri: URI): TPromise<boolean> {
return super.open(uri);
}
}

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IURLService, IURLHandler } from 'vs/platform/url/common/url';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { localize } from 'vs/nls';
const FIVE_MINUTES = 5 * 60 * 1000;
const THIRTY_SECONDS = 30 * 1000;
function isExtensionId(value: string): boolean {
return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
}
export const IExtensionUrlHandler = createDecorator<IExtensionUrlHandler>('inactiveExtensionUrlHandler');
export interface IExtensionUrlHandler {
readonly _serviceBrand: any;
registerExtensionHandler(extensionId: string, handler: IURLHandler): void;
unregisterExtensionHandler(extensionId: string): void;
}
/**
* This class handles URLs which are directed towards inactive extensions.
* If a URL is directed towards an inactive extension, it buffers it,
* activates the extension and re-opens the URL once the extension registers
* a URL handler. If the extension never registers a URL handler, the urls
* will eventually be garbage collected.
*
* It also makes sure the user confirms opening URLs directed towards extensions.
*/
export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
readonly _serviceBrand: any;
private extensionHandlers = new Map<string, IURLHandler>();
private uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
private disposable: IDisposable;
constructor(
@IURLService urlService: IURLService,
@IExtensionService private extensionService: IExtensionService,
@IDialogService private dialogService: IDialogService
) {
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
this.disposable = combinedDisposable([
urlService.registerHandler(this),
toDisposable(() => clearInterval(interval))
]);
}
async handleURL(uri: URI): TPromise<boolean> {
if (!isExtensionId(uri.authority)) {
return false;
}
const extensionId = uri.authority;
const wasHandlerAvailable = this.extensionHandlers.has(extensionId);
const extensions = await this.extensionService.getExtensions();
const extension = extensions.filter(e => e.id === extensionId)[0];
if (!extension) {
return false;
}
const result = await this.dialogService.confirm({
message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId),
detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}`
});
if (!result.confirmed) {
return true;
}
const handler = this.extensionHandlers.get(extensionId);
if (handler) {
if (!wasHandlerAvailable) {
// forward it directly
return handler.handleURL(uri);
}
// let the ExtensionUrlHandler instance handle this
return TPromise.as(false);
}
// collect URI for eventual extension activation
const timestamp = new Date().getTime();
let uris = this.uriBuffer.get(extensionId);
if (!uris) {
uris = [];
this.uriBuffer.set(extensionId, uris);
}
uris.push({ timestamp, uri });
// activate the extension
await this.extensionService.activateByEvent(`onUri:${extensionId}`);
return true;
}
registerExtensionHandler(extensionId: string, handler: IURLHandler): void {
this.extensionHandlers.set(extensionId, handler);
const uris = this.uriBuffer.get(extensionId) || [];
for (const { uri } of uris) {
handler.handleURL(uri);
}
this.uriBuffer.delete(extensionId);
}
unregisterExtensionHandler(extensionId: string): void {
this.extensionHandlers.delete(extensionId);
}
// forget about all uris buffered more than 5 minutes ago
private garbageCollect(): void {
const now = new Date().getTime();
const uriBuffer = new Map<string, { timestamp: number, uri: URI }[]>();
this.uriBuffer.forEach((uris, extensionId) => {
uris = uris.filter(({ timestamp }) => now - timestamp < FIVE_MINUTES);
if (uris.length > 0) {
uriBuffer.set(extensionId, uris);
}
});
this.uriBuffer = uriBuffer;
}
dispose(): void {
this.disposable.dispose();
this.extensionHandlers.clear();
this.uriBuffer.clear();
}
}

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { mapEvent, fromNodeEventEmitter, filterEvent, once } from 'vs/base/common/event';
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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows';
import { ReadyState } from 'vs/platform/windows/common/windows';
function uriFromRawUrl(url: string): URI | null {
try {
return URI.parse(url);
} catch (e) {
return null;
}
}
export class ElectronURLListener {
private disposables: IDisposable[] = [];
constructor(
initial: string | string[],
@IURLService private urlService: IURLService,
@IWindowsMainService windowsService: IWindowsMainService
) {
const globalBuffer = ((<any>global).getOpenUrls() || []) as string[];
const rawBuffer = [
...(typeof initial === 'string' ? [initial] : initial),
...globalBuffer
];
const buffer = rawBuffer.map(uriFromRawUrl).filter(uri => !!uri);
const flush = () => buffer.forEach(uri => urlService.open(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.urlService.open, this.urlService, this.disposables);
const isWindowReady = windowsService.getWindows()
.filter(w => w.readyState === ReadyState.READY)
.length > 0;
if (isWindowReady) {
flush();
} else {
once(windowsService.onWindowReady)(flush);
}
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View File

@@ -1,61 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { mapEvent, chain, echo, Emitter, anyEvent, fromNodeEventEmitter } from 'vs/base/common/event';
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 '../../log/common/log';
export class URLService implements IURLService {
_serviceBrand: any;
private openUrlEmitter: Emitter<string> = new Emitter<string>();
onOpenURL: Event<URI>;
constructor(
initial: string | string[],
@ILogService private logService: ILogService
) {
const globalBuffer = (global.getOpenUrls() || []) as string[];
const initialBuffer = [
...(typeof initial === 'string' ? [initial] : initial),
...globalBuffer
];
app.setAsDefaultProtocolClient(product.urlProtocol, process.execPath, ['--open-url', '--']);
const rawOnOpenUrl = fromNodeEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
// always prevent default and return the url as string
const preventedOnOpenUrl = mapEvent(rawOnOpenUrl, ({ event, url }) => {
event.preventDefault();
return url;
});
// echo all `onOpenUrl` events to each listener
const bufferedOnOpenUrl = echo(preventedOnOpenUrl, true, initialBuffer);
this.onOpenURL = chain(anyEvent(bufferedOnOpenUrl, this.openUrlEmitter.event))
.map(url => {
try {
return URI.parse(url);
} catch (e) {
return null;
}
})
.filter(uri => !!uri)
.event;
}
open(url: string): void {
this.logService.trace('urlService#open', url);
this.openUrlEmitter.fire(url);
}
}