mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-07 17:23:56 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
54
src/vs/platform/url/common/urlService.ts
Normal file
54
src/vs/platform/url/common/urlService.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
70
src/vs/platform/url/electron-main/electronUrlListener.ts
Normal file
70
src/vs/platform/url/electron-main/electronUrlListener.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user