mirror of
https://github.com/Microsoft/vscode
synced 2024-09-18 01:58:27 +00:00
Fix #57636
This commit is contained in:
parent
49837d16a1
commit
b8c4254ae4
|
@ -11,9 +11,16 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||||
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
|
import { IExtensionManagementService, IExtensionIdentifier, IExtensionEnablementService, EnablementState, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||||
|
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||||
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||||
|
import { Action } from 'vs/base/common/actions';
|
||||||
|
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||||
|
|
||||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||||
const THIRTY_SECONDS = 30 * 1000;
|
const THIRTY_SECONDS = 30 * 1000;
|
||||||
|
const URL_TO_HANDLE = 'extensionUrlHandler.urlToHandle';
|
||||||
|
|
||||||
function isExtensionId(value: string): boolean {
|
function isExtensionId(value: string): boolean {
|
||||||
return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
|
return /^[a-z0-9][a-z0-9\-]*\.[a-z0-9][a-z0-9\-]*$/i.test(value);
|
||||||
|
@ -47,9 +54,20 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||||
constructor(
|
constructor(
|
||||||
@IURLService urlService: IURLService,
|
@IURLService urlService: IURLService,
|
||||||
@IExtensionService private extensionService: IExtensionService,
|
@IExtensionService private extensionService: IExtensionService,
|
||||||
@IDialogService private dialogService: IDialogService
|
@IDialogService private dialogService: IDialogService,
|
||||||
|
@INotificationService private notificationService: INotificationService,
|
||||||
|
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
|
||||||
|
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
|
||||||
|
@IWindowService private windowService: IWindowService,
|
||||||
|
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
|
||||||
|
@IStorageService private storageService: IStorageService
|
||||||
) {
|
) {
|
||||||
const interval = setInterval(() => this.garbageCollect(), THIRTY_SECONDS);
|
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([
|
this.disposable = combinedDisposable([
|
||||||
urlService.registerHandler(this),
|
urlService.registerHandler(this),
|
||||||
|
@ -57,7 +75,7 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleURL(uri: URI): TPromise<boolean> {
|
handleURL(uri: URI, confirmed?: boolean): TPromise<boolean> {
|
||||||
if (!isExtensionId(uri.authority)) {
|
if (!isExtensionId(uri.authority)) {
|
||||||
return TPromise.as(false);
|
return TPromise.as(false);
|
||||||
}
|
}
|
||||||
|
@ -69,18 +87,10 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||||
const extension = extensions.filter(e => e.id === extensionId)[0];
|
const extension = extensions.filter(e => e.id === extensionId)[0];
|
||||||
|
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
return TPromise.as(false);
|
return this.handleUnhandledURL(uri, { id: extensionId }).then(() => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.dialogService.confirm({
|
const handleURL = () => {
|
||||||
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()}`
|
|
||||||
}).then(result => {
|
|
||||||
|
|
||||||
if (!result.confirmed) {
|
|
||||||
return TPromise.as(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handler = this.extensionHandlers.get(extensionId);
|
const handler = this.extensionHandlers.get(extensionId);
|
||||||
if (handler) {
|
if (handler) {
|
||||||
if (!wasHandlerAvailable) {
|
if (!wasHandlerAvailable) {
|
||||||
|
@ -106,6 +116,22 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||||
// activate the extension
|
// activate the extension
|
||||||
return this.extensionService.activateByEvent(`onUri:${extensionId}`)
|
return this.extensionService.activateByEvent(`onUri:${extensionId}`)
|
||||||
.then(() => true);
|
.then(() => true);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
return handleURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 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()}`
|
||||||
|
}).then(result => {
|
||||||
|
|
||||||
|
if (!result.confirmed) {
|
||||||
|
return TPromise.as(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleURL();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -126,6 +152,93 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||||
this.extensionHandlers.delete(extensionId);
|
this.extensionHandlers.delete(extensionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async handleUnhandledURL(uri: URI, extensionIdentifier: IExtensionIdentifier): Promise<void> {
|
||||||
|
const installedExtensions = await this.extensionManagementService.getInstalled();
|
||||||
|
const extension = installedExtensions.filter(e => areSameExtensions(e.galleryIdentifier, extensionIdentifier))[0];
|
||||||
|
|
||||||
|
// Extension is installed
|
||||||
|
if (extension) {
|
||||||
|
const enabled = this.extensionEnablementService.isEnabled(extension);
|
||||||
|
|
||||||
|
// Extension is not running. Reload the window to handle.
|
||||||
|
if (enabled) {
|
||||||
|
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()}`
|
||||||
|
}).then(result => {
|
||||||
|
if (result.confirmed) {
|
||||||
|
return this.reloadAndHandle(uri);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension is disabled. Enable the extension and reload the window to handle.
|
||||||
|
else {
|
||||||
|
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()}`
|
||||||
|
}).then(result => {
|
||||||
|
if (result.confirmed) {
|
||||||
|
return this.extensionEnablementService.setEnablement(extension, EnablementState.Enabled)
|
||||||
|
.then(() => this.reloadAndHandle(uri));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extnesion is not installed
|
||||||
|
else {
|
||||||
|
const galleryExtension = await this.galleryService.getExtension(extensionIdentifier);
|
||||||
|
if (galleryExtension) {
|
||||||
|
// Install the Extension and reload the window to handle.
|
||||||
|
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()}`
|
||||||
|
}).then(async result => {
|
||||||
|
if (result.confirmed) {
|
||||||
|
let notificationHandle = this.notificationService.notify({ severity: Severity.Info, message: localize('Installing', "Installing {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 URL");
|
||||||
|
if (notificationHandle) {
|
||||||
|
notificationHandle.progress.done();
|
||||||
|
notificationHandle.updateMessage(reloadMessage);
|
||||||
|
notificationHandle.updateActions({
|
||||||
|
primary: [new Action('reloadWindow', reloadActionLabel, null, true, () => this.reloadAndHandle(uri))]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.notificationService.prompt(Severity.Info, reloadMessage,
|
||||||
|
[{
|
||||||
|
label: reloadActionLabel,
|
||||||
|
run: () => this.reloadAndHandle(uri)
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (notificationHandle) {
|
||||||
|
notificationHandle.progress.done();
|
||||||
|
notificationHandle.updateSeverity(Severity.Error);
|
||||||
|
notificationHandle.updateMessage(e);
|
||||||
|
} else {
|
||||||
|
this.notificationService.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private reloadAndHandle(url: URI): TPromise<void> {
|
||||||
|
this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE);
|
||||||
|
return this.windowService.reloadWindow();
|
||||||
|
}
|
||||||
|
|
||||||
// forget about all uris buffered more than 5 minutes ago
|
// forget about all uris buffered more than 5 minutes ago
|
||||||
private garbageCollect(): void {
|
private garbageCollect(): void {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
|
|
Loading…
Reference in a new issue