mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 10:38:31 -05:00
Merge from vscode fc10e26ea50f82cdd84e9141491357922e6f5fba (#4639)
This commit is contained in:
@@ -12,16 +12,15 @@ import { timeout } from 'vs/base/common/async';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
|
||||
import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console';
|
||||
import { findFreePort, randomPort } from 'vs/base/node/ports';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { PersistentProtocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService';
|
||||
import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
@@ -36,6 +35,7 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { parseExtensionDevOptions } from '../common/extensionDevOptions';
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onCrashed: Event<[number, string | null]>;
|
||||
@@ -44,28 +44,6 @@ export interface IExtensionHostStarter {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IExtensionDevOptions {
|
||||
readonly isExtensionDevHost: boolean;
|
||||
readonly isExtensionDevDebug: boolean;
|
||||
readonly isExtensionDevDebugBrk: boolean;
|
||||
readonly isExtensionDevTestFromCli: boolean;
|
||||
}
|
||||
export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions {
|
||||
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
|
||||
let isExtensionDevHost = environmentService.isExtensionDevelopment;
|
||||
const extDevLoc = environmentService.extensionDevelopmentLocationURI;
|
||||
const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file;
|
||||
let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number';
|
||||
let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break;
|
||||
let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break;
|
||||
return {
|
||||
isExtensionDevHost,
|
||||
isExtensionDevDebug,
|
||||
isExtensionDevDebugBrk,
|
||||
isExtensionDevTestFromCli,
|
||||
};
|
||||
}
|
||||
|
||||
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
|
||||
@@ -87,7 +65,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
private _inspectPort: number;
|
||||
private _extensionHostProcess: ChildProcess | null;
|
||||
private _extensionHostConnection: Socket | null;
|
||||
private _messageProtocol: Promise<IMessagePassingProtocol> | null;
|
||||
private _messageProtocol: Promise<PersistentProtocol> | null;
|
||||
|
||||
constructor(
|
||||
private readonly _autoStart: boolean,
|
||||
@@ -341,9 +319,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
});
|
||||
}
|
||||
|
||||
private _tryExtHostHandshake(): Promise<IMessagePassingProtocol> {
|
||||
private _tryExtHostHandshake(): Promise<PersistentProtocol> {
|
||||
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
return new Promise<PersistentProtocol>((resolve, reject) => {
|
||||
|
||||
// Wait for the extension host to connect to our named pipe
|
||||
// and wrap the socket in the message passing protocol
|
||||
@@ -373,7 +351,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
// 1) wait for the incoming `ready` event and send the initialization data.
|
||||
// 2) wait for the incoming `initialized` event.
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
return new Promise<PersistentProtocol>((resolve, reject) => {
|
||||
|
||||
let timeoutHandle: NodeJS.Timer;
|
||||
const installTimeoutCheck = () => {
|
||||
@@ -549,6 +527,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
// (graceful termination)
|
||||
protocol.send(createMessageOfType(MessageType.Terminate));
|
||||
|
||||
protocol.dispose();
|
||||
|
||||
// Give the extension host 10s, after which we will
|
||||
// try to kill the process and release any resources
|
||||
setTimeout(() => this._cleanResources(), 10 * 1000);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier, runWhenIdle } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -840,6 +841,10 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionService, ExtensionService);
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { EnablementState, IExtensionEnablementService, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IURLHandler, IURLService } from 'vs/platform/url/common/url';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||
const THIRTY_SECONDS = 30 * 1000;
|
||||
const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle';
|
||||
|
||||
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: ExtensionIdentifier, handler: IURLHandler): void;
|
||||
unregisterExtensionHandler(extensionId: ExtensionIdentifier): 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 readonly extensionService: IExtensionService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
|
||||
@IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) {
|
||||
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
|
||||
const urlToHandleValue = this.storageService.get(URL_TO_HANDLE, StorageScope.WORKSPACE);
|
||||
if (urlToHandleValue) {
|
||||
this.storageService.remove(URL_TO_HANDLE, StorageScope.WORKSPACE);
|
||||
this.handleURL(URI.revive(JSON.parse(urlToHandleValue)), true);
|
||||
}
|
||||
|
||||
this.disposable = combinedDisposable([
|
||||
urlService.registerHandler(this),
|
||||
toDisposable(() => clearInterval(interval))
|
||||
]);
|
||||
}
|
||||
|
||||
async handleURL(uri: URI, confirmed?: boolean): Promise<boolean> {
|
||||
if (!isExtensionId(uri.authority)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionId = uri.authority;
|
||||
const wasHandlerAvailable = this.extensionHandlers.has(ExtensionIdentifier.toKey(extensionId));
|
||||
const extension = await this.extensionService.getExtension(extensionId);
|
||||
|
||||
if (!extension) {
|
||||
await this.handleUnhandledURL(uri, { id: extensionId });
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!confirmed) {
|
||||
let uriString = uri.toString();
|
||||
|
||||
if (uriString.length > 40) {
|
||||
uriString = `${uriString.substring(0, 30)}...${uriString.substring(uriString.length - 5)}`;
|
||||
}
|
||||
|
||||
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${uriString}`,
|
||||
primaryButton: localize('open', "&&Open"),
|
||||
type: 'question'
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const handler = this.extensionHandlers.get(ExtensionIdentifier.toKey(extensionId));
|
||||
|
||||
if (handler) {
|
||||
if (!wasHandlerAvailable) {
|
||||
// forward it directly
|
||||
return await handler.handleURL(uri);
|
||||
}
|
||||
|
||||
// let the ExtensionUrlHandler instance handle this
|
||||
return false;
|
||||
}
|
||||
|
||||
// collect URI for eventual extension activation
|
||||
const timestamp = new Date().getTime();
|
||||
let uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId));
|
||||
|
||||
if (!uris) {
|
||||
uris = [];
|
||||
this.uriBuffer.set(ExtensionIdentifier.toKey(extensionId), uris);
|
||||
}
|
||||
|
||||
uris.push({ timestamp, uri });
|
||||
|
||||
// activate the extension
|
||||
await this.extensionService.activateByEvent(`onUri:${ExtensionIdentifier.toKey(extensionId)}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void {
|
||||
this.extensionHandlers.set(ExtensionIdentifier.toKey(extensionId), handler);
|
||||
|
||||
const uris = this.uriBuffer.get(ExtensionIdentifier.toKey(extensionId)) || [];
|
||||
|
||||
for (const { uri } of uris) {
|
||||
handler.handleURL(uri);
|
||||
}
|
||||
|
||||
this.uriBuffer.delete(ExtensionIdentifier.toKey(extensionId));
|
||||
}
|
||||
|
||||
unregisterExtensionHandler(extensionId: ExtensionIdentifier): void {
|
||||
this.extensionHandlers.delete(ExtensionIdentifier.toKey(extensionId));
|
||||
}
|
||||
|
||||
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
|
||||
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||
const extension = installedExtensions.filter(e => areSameExtensions(e.identifier, extensionIdentifier))[0];
|
||||
|
||||
// Extension is installed
|
||||
if (extension) {
|
||||
const enabled = this.extensionEnablementService.isEnabled(extension);
|
||||
|
||||
// Extension is not running. Reload the window to handle.
|
||||
if (enabled) {
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('reloadAndHandle', "Extension '{0}' is not loaded. Would you like to reload the window to load the extension and open the URL?", extension.manifest.displayName || extension.manifest.name),
|
||||
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
|
||||
primaryButton: localize('reloadAndOpen', "&&Reload Window and Open"),
|
||||
type: 'question'
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.reloadAndHandle(uri);
|
||||
}
|
||||
|
||||
// Extension is disabled. Enable the extension and reload the window to handle.
|
||||
else {
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('enableAndHandle', "Extension '{0}' is disabled. Would you like to enable the extension and reload the window to open the URL?", extension.manifest.displayName || extension.manifest.name),
|
||||
detail: `${extension.manifest.displayName || extension.manifest.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
|
||||
primaryButton: localize('enableAndReload', "&&Enable and Open"),
|
||||
type: 'question'
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.extensionEnablementService.setEnablement([extension], EnablementState.Enabled);
|
||||
await this.reloadAndHandle(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Extension is not installed
|
||||
else {
|
||||
const galleryExtension = await this.galleryService.getCompatibleExtension(extensionIdentifier);
|
||||
|
||||
if (!galleryExtension) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Install the Extension and reload the window to handle.
|
||||
const result = await this.dialogService.confirm({
|
||||
message: localize('installAndHandle', "Extension '{0}' is not installed. Would you like to install the extension and reload the window to open this URL?", galleryExtension.displayName || galleryExtension.name),
|
||||
detail: `${galleryExtension.displayName || galleryExtension.name} (${extensionIdentifier.id}) wants to open a URL:\n\n${uri.toString()}`,
|
||||
primaryButton: localize('install', "&&Install"),
|
||||
type: 'question'
|
||||
});
|
||||
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationHandle: INotificationHandle | null = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing Extension '{0}'...", galleryExtension.displayName || galleryExtension.name) });
|
||||
notificationHandle.progress.infinite();
|
||||
notificationHandle.onDidClose(() => notificationHandle = null);
|
||||
|
||||
try {
|
||||
await this.extensionManagementService.installFromGallery(galleryExtension);
|
||||
const reloadMessage = localize('reload', "Would you like to reload the window and open the URL '{0}'?", uri.toString());
|
||||
const reloadActionLabel = localize('Reload', "Reload Window and Open");
|
||||
|
||||
if (notificationHandle) {
|
||||
notificationHandle.progress.done();
|
||||
notificationHandle.updateMessage(reloadMessage);
|
||||
notificationHandle.updateActions({
|
||||
primary: [new Action('reloadWindow', reloadActionLabel, undefined, true, () => this.reloadAndHandle(uri))]
|
||||
});
|
||||
} else {
|
||||
this.notificationService.prompt(Severity.Info, reloadMessage, [{ label: reloadActionLabel, run: () => this.reloadAndHandle(uri) }], { sticky: true });
|
||||
}
|
||||
} catch (e) {
|
||||
if (notificationHandle) {
|
||||
notificationHandle.progress.done();
|
||||
notificationHandle.updateSeverity(Severity.Error);
|
||||
notificationHandle.updateMessage(e);
|
||||
} else {
|
||||
this.notificationService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async reloadAndHandle(url: URI): Promise<void> {
|
||||
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
|
||||
await this.windowService.reloadWindow();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler);
|
||||
Reference in New Issue
Block a user