#15756 - Pre-release support for extensions
This commit is contained in:
parent
e7b11f34e1
commit
bea3784159
|
@ -22,6 +22,8 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export type Metadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean; isPreReleaseVersion: boolean, hadPreReleaseVersion: boolean, installedTimestamp: number }>;
|
||||
|
||||
export interface IInstallExtensionTask {
|
||||
readonly identifier: IExtensionIdentifier;
|
||||
readonly source: IGalleryExtension | URI;
|
||||
|
@ -85,8 +87,13 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
|
||||
async installFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
try {
|
||||
return await this.doInstallFromGallery(extension, options);
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
|
||||
}
|
||||
const compatible = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion, !!options.installPreReleaseVersion);
|
||||
return await this.installExtension(compatible.manifest, compatible.extension, options);
|
||||
} catch (error) {
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
this.logService.error(`Failed to install extension.`, extension.identifier.id);
|
||||
this.logService.error(error);
|
||||
throw toExtensionManagementError(error);
|
||||
|
@ -128,41 +135,6 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
this.participants.push(participant);
|
||||
}
|
||||
|
||||
private async doInstallFromGallery(extension: IGalleryExtension, options: InstallOptions = {}): Promise<ILocalExtension> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
throw new ExtensionManagementError(nls.localize('MarketPlaceDisabled', "Marketplace is not enabled"), ExtensionManagementErrorCode.Internal);
|
||||
}
|
||||
|
||||
if (!await this.canInstall(extension)) {
|
||||
const targetPlatform = await this.getTargetPlatform();
|
||||
const error = new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.Incompatible);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
extension = await this.checkAndGetCompatibleVersion(extension, !options.installGivenVersion);
|
||||
} catch (error) {
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const manifest = await this.galleryService.getManifest(extension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
const error = new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (manifest.version !== extension.version) {
|
||||
const error = new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
|
||||
reportTelemetry(this.telemetryService, 'extensionGallery:install', getGalleryExtensionTelemetryData(extension), undefined, error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return this.installExtension(manifest, extension, options);
|
||||
}
|
||||
|
||||
protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions & InstallVSIXOptions): Promise<ILocalExtension> {
|
||||
// only cache gallery extensions tasks
|
||||
if (!URI.isUri(extension)) {
|
||||
|
@ -190,7 +162,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id);
|
||||
} else {
|
||||
try {
|
||||
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack);
|
||||
const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion);
|
||||
for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) {
|
||||
installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier));
|
||||
if (this.installingExtensions.has(new ExtensionIdentifierWithVersion(gallery.identifier, gallery.version).key())) {
|
||||
|
@ -322,7 +294,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
return results;
|
||||
}
|
||||
|
||||
private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> {
|
||||
private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean): Promise<{ gallery: IGalleryExtension, manifest: IExtensionManifest }[]> {
|
||||
if (!this.galleryService.isEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
@ -357,17 +329,19 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
continue;
|
||||
}
|
||||
const isDependency = dependecies.some(id => areSameExtensions({ id }, galleryExtension.identifier));
|
||||
if (!isDependency && !await this.canInstall(galleryExtension)) {
|
||||
this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id);
|
||||
continue;
|
||||
let compatible;
|
||||
try {
|
||||
compatible = await this.checkAndGetCompatibleVersion(galleryExtension, true, installPreRelease);
|
||||
} catch (error) {
|
||||
if (error instanceof ExtensionManagementError && error.code === ExtensionManagementErrorCode.IncompatibleTargetPlatform && !isDependency) {
|
||||
this.logService.info('Skipping the packed extension as it cannot be installed', galleryExtension.identifier.id);
|
||||
continue;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const compatibleExtension = await this.checkAndGetCompatibleVersion(galleryExtension, true);
|
||||
const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
|
||||
}
|
||||
allDependenciesAndPacks.push({ gallery: compatibleExtension, manifest });
|
||||
await collectDependenciesAndPackExtensionsToInstall(compatibleExtension.identifier, manifest);
|
||||
allDependenciesAndPacks.push({ gallery: compatible.extension, manifest: compatible.manifest });
|
||||
await collectDependenciesAndPackExtensionsToInstall(compatible.extension.identifier, manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,28 +352,51 @@ export abstract class AbstractExtensionManagementService extends Disposable impl
|
|||
return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier)));
|
||||
}
|
||||
|
||||
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise<IGalleryExtension> {
|
||||
private async checkAndGetCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean, includePreRelease: boolean): Promise<{ extension: IGalleryExtension, manifest: IExtensionManifest }> {
|
||||
if (await this.isMalicious(extension)) {
|
||||
throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious);
|
||||
}
|
||||
|
||||
const compatibleExtension = await this.getCompatibleVersion(extension, fetchCompatibleVersion);
|
||||
if (!compatibleExtension) {
|
||||
if (!await this.canInstall(extension)) {
|
||||
const targetPlatform = await this.getTargetPlatform();
|
||||
throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform);
|
||||
}
|
||||
|
||||
const compatibleExtension = await this.getCompatibleVersion(extension, fetchCompatibleVersion, includePreRelease);
|
||||
if (compatibleExtension) {
|
||||
if (includePreRelease && !compatibleExtension.properties.isPreReleaseVersion && extension.hasPreReleaseVersion) {
|
||||
throw new ExtensionManagementError(nls.localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
} else {
|
||||
throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
|
||||
return compatibleExtension;
|
||||
const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None);
|
||||
if (manifest === null) {
|
||||
throw new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid);
|
||||
}
|
||||
|
||||
if (manifest.version !== compatibleExtension.version) {
|
||||
throw new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid);
|
||||
}
|
||||
|
||||
return { extension: compatibleExtension, manifest };
|
||||
}
|
||||
|
||||
protected async getCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise<IGalleryExtension | null> {
|
||||
protected async getCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> {
|
||||
const targetPlatform = await this.getTargetPlatform();
|
||||
let compatibleExtension: IGalleryExtension | null = null;
|
||||
if (await this.galleryService.isExtensionCompatible(extension, targetPlatform)) {
|
||||
|
||||
if (fetchCompatibleVersion && extension.hasPreReleaseVersion && extension.properties.isPreReleaseVersion !== includePreRelease) {
|
||||
compatibleExtension = await this.galleryService.getCompatibleExtension(extension.identifier, includePreRelease, targetPlatform);
|
||||
}
|
||||
|
||||
if (!compatibleExtension && await this.galleryService.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
|
||||
compatibleExtension = extension;
|
||||
}
|
||||
|
||||
if (!compatibleExtension && fetchCompatibleVersion) {
|
||||
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, targetPlatform);
|
||||
compatibleExtension = await this.galleryService.getCompatibleExtension(extension, includePreRelease, targetPlatform);
|
||||
}
|
||||
|
||||
return compatibleExtension;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { getOrDefault } from 'vs/base/common/objects';
|
|||
import { IPager } from 'vs/base/common/paging';
|
||||
import { isWeb, platform } from 'vs/base/common/platform';
|
||||
import { arch } from 'vs/base/common/process';
|
||||
import { isBoolean } from 'vs/base/common/types';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IHeaders, IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -135,6 +136,7 @@ const PropertyType = {
|
|||
Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',
|
||||
ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',
|
||||
Engine: 'Microsoft.VisualStudio.Code.Engine',
|
||||
PreRelease: 'Microsoft.VisualStudio.Code.PreRelease',
|
||||
LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages',
|
||||
WebExtension: 'Microsoft.VisualStudio.Code.WebExtension'
|
||||
};
|
||||
|
@ -314,6 +316,11 @@ function getEngine(version: IRawGalleryExtensionVersion): string {
|
|||
return (values.length > 0 && values[0].value) || '';
|
||||
}
|
||||
|
||||
function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {
|
||||
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];
|
||||
return values.length > 0 && values[0].value === 'true';
|
||||
}
|
||||
|
||||
function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {
|
||||
const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];
|
||||
const value = (values.length > 0 && values[0].value) || '';
|
||||
|
@ -376,14 +383,8 @@ export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], p
|
|||
return versions;
|
||||
}
|
||||
|
||||
function toExtensionWithLatestVersion(galleryExtension: IRawGalleryExtension, index: number, query: Query, querySource: string | undefined, targetPlatform: TargetPlatform): IGalleryExtension {
|
||||
const allTargetPlatforms = getAllTargetPlatforms(galleryExtension);
|
||||
let latestVersion = galleryExtension.versions[0];
|
||||
latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(version), allTargetPlatforms, targetPlatform)) || latestVersion;
|
||||
return toExtension(galleryExtension, latestVersion, allTargetPlatforms, index, query, querySource);
|
||||
}
|
||||
|
||||
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], index: number, query: Query, querySource?: string): IGalleryExtension {
|
||||
function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], telemetryData?: any): IGalleryExtension {
|
||||
const latestVersion = galleryExtension.versions[0];
|
||||
const assets = <IGalleryExtensionAssets>{
|
||||
manifest: getVersionAsset(version, AssetType.Manifest),
|
||||
readme: getVersionAsset(version, AssetType.Details),
|
||||
|
@ -423,7 +424,9 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
|||
engine: getEngine(version),
|
||||
localizedLanguages: getLocalizedLanguages(version),
|
||||
targetPlatform: getTargetPlatformForExtensionVersion(version),
|
||||
isPreReleaseVersion: isPreReleaseVersion(version)
|
||||
},
|
||||
hasPreReleaseVersion: isPreReleaseVersion(latestVersion),
|
||||
preview: getIsPreview(galleryExtension.flags),
|
||||
/* __GDPR__FRAGMENT__
|
||||
"GalleryExtensionTelemetryData2" : {
|
||||
|
@ -431,10 +434,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller
|
|||
"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
telemetryData: {
|
||||
index: ((query.pageNumber - 1) * query.pageSize) + index,
|
||||
querySource
|
||||
},
|
||||
telemetryData,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -476,8 +476,11 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
return !!this.extensionsGalleryUrl;
|
||||
}
|
||||
|
||||
async getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const result: IGalleryExtension[] = [];
|
||||
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]>
|
||||
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, includePreRelease: boolean, token: CancellationToken): Promise<IGalleryExtension[]>
|
||||
async getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, arg1: any, arg2?: any): Promise<IGalleryExtension[]> {
|
||||
const includePreRelease = isBoolean(arg1) ? arg1 : false;
|
||||
const token: CancellationToken = isBoolean(arg1) ? arg2 : arg1;
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, identifiers.length)
|
||||
|
@ -489,33 +492,20 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
}
|
||||
|
||||
const { galleryExtensions } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, CancellationToken.None);
|
||||
for (let index = 0; index < galleryExtensions.length; index++) {
|
||||
const galleryExtension = galleryExtensions[index];
|
||||
if (!galleryExtension.versions.length) {
|
||||
continue;
|
||||
}
|
||||
const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName);
|
||||
const version = (<IExtensionIdentifierWithVersion | undefined>identifiers.find(identifier => areSameExtensions(identifier, { id })))?.version;
|
||||
if (version) {
|
||||
const versionAsset = galleryExtension.versions.find(v => v.version === version);
|
||||
if (versionAsset) {
|
||||
result.push(toExtension(galleryExtension, versionAsset, getAllTargetPlatforms(galleryExtension), index, query));
|
||||
}
|
||||
} else {
|
||||
result.push(toExtensionWithLatestVersion(galleryExtension, index, query, undefined, CURRENT_TARGET_PLATFORM));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
const galleryExtensionsByVersion = galleryExtensions.map(rawGalleryExtension => {
|
||||
const id = getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName);
|
||||
return { rawGalleryExtension, version: (<IExtensionIdentifierWithVersion | undefined>identifiers.find(identifier => areSameExtensions(identifier, { id })))?.version };
|
||||
});
|
||||
return this.converToGalleryExtensions(galleryExtensionsByVersion, includePreRelease, CURRENT_TARGET_PLATFORM, () => undefined, token);
|
||||
}
|
||||
|
||||
async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null> {
|
||||
async getCompatibleExtension(arg1: IExtensionIdentifier | IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null> {
|
||||
const extension: IGalleryExtension | null = isIExtensionIdentifier(arg1) ? null : arg1;
|
||||
if (extension) {
|
||||
if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {
|
||||
return null;
|
||||
}
|
||||
if (await this.isExtensionCompatible(extension, targetPlatform)) {
|
||||
if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {
|
||||
return extension;
|
||||
}
|
||||
}
|
||||
|
@ -551,19 +541,23 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
properties: [...(rawVersion.properties || []), { key: PropertyType.Engine, value: engine }]
|
||||
};
|
||||
}
|
||||
if (await this.isRawExtensionVersionCompatible(rawVersion, allTargetPlatforms, targetPlatform)) {
|
||||
return toExtension(rawExtension, rawVersion, allTargetPlatforms, 0, query);
|
||||
if (await this.isRawExtensionVersionCompatible(rawVersion, includePreRelease, allTargetPlatforms, targetPlatform)) {
|
||||
return toExtension(rawExtension, rawVersion, allTargetPlatforms);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
if (!isTargetPlatformCompatible(extension.properties.targetPlatform, extension.allTargetPlatforms, targetPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!includePreRelease && extension.properties.isPreReleaseVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let engine = extension.properties.engine;
|
||||
if (!engine) {
|
||||
const manifest = await this.getManifest(extension, CancellationToken.None);
|
||||
|
@ -575,11 +569,15 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
return isEngineValid(engine, this.productService.version, this.productService.date);
|
||||
}
|
||||
|
||||
private async isRawExtensionVersionCompatible(rawExtensionVersion: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
private async isRawExtensionVersionCompatible(rawExtensionVersion: IRawGalleryExtensionVersion, includePreRelease: boolean, allTargetPlatforms: TargetPlatform[], targetPlatform: TargetPlatform): Promise<boolean> {
|
||||
if (!isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(rawExtensionVersion), allTargetPlatforms, targetPlatform)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!includePreRelease && isPreReleaseVersion(rawExtensionVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const engine = await this.getEngine(rawExtensionVersion);
|
||||
return isEngineValid(engine, this.productService.version, this.productService.date);
|
||||
}
|
||||
|
@ -641,19 +639,69 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
}
|
||||
|
||||
const { galleryExtensions, total } = await this.queryGallery(query, CURRENT_TARGET_PLATFORM, token);
|
||||
const extensions = galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, query, options.source, CURRENT_TARGET_PLATFORM));
|
||||
const telemetryData = (index: number) => ({ index: ((query.pageNumber - 1) * query.pageSize) + index, querySource: options.source });
|
||||
const extensions = await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension })), !!options.includePreRelease, CURRENT_TARGET_PLATFORM, telemetryData, token);
|
||||
const getPage = async (pageIndex: number, ct: CancellationToken) => {
|
||||
if (ct.isCancellationRequested) {
|
||||
throw canceled();
|
||||
}
|
||||
const nextPageQuery = query.withPage(pageIndex + 1);
|
||||
const { galleryExtensions } = await this.queryGallery(nextPageQuery, CURRENT_TARGET_PLATFORM, ct);
|
||||
return galleryExtensions.map((e, index) => toExtensionWithLatestVersion(e, index, nextPageQuery, options.source, CURRENT_TARGET_PLATFORM));
|
||||
return await this.converToGalleryExtensions(galleryExtensions.map(rawGalleryExtension => ({ rawGalleryExtension })), !!options.includePreRelease, CURRENT_TARGET_PLATFORM, telemetryData, token);
|
||||
};
|
||||
|
||||
return { firstPage: extensions, total, pageSize: query.pageSize, getPage } as IPager<IGalleryExtension>;
|
||||
}
|
||||
|
||||
private async converToGalleryExtensions(rawGalleryExtensions: { rawGalleryExtension: IRawGalleryExtension, version?: string }[], includePreRelease: boolean, targetPlatform: TargetPlatform, telemetryData: (index: number) => any, token: CancellationToken): Promise<IGalleryExtension[]> {
|
||||
const toExtensionWithLatestVersion = (galleryExtension: IRawGalleryExtension, index: number): IGalleryExtension => {
|
||||
const allTargetPlatforms = getAllTargetPlatforms(galleryExtension);
|
||||
let latestVersion = galleryExtension.versions[0];
|
||||
latestVersion = galleryExtension.versions.find(version => version.version === latestVersion.version && isTargetPlatformCompatible(getTargetPlatformForExtensionVersion(version), allTargetPlatforms, targetPlatform)) || latestVersion;
|
||||
if (!includePreRelease && isPreReleaseVersion(latestVersion)) {
|
||||
latestVersion = galleryExtension.versions.find(version => version.version !== latestVersion.version && !isPreReleaseVersion(version)) || latestVersion;
|
||||
}
|
||||
return toExtension(galleryExtension, latestVersion, allTargetPlatforms, telemetryData(index));
|
||||
};
|
||||
const result: [number, IGalleryExtension][] = [];
|
||||
const preReleaseVersions = new Map<string, number>();
|
||||
for (let index = 0; index < rawGalleryExtensions.length; index++) {
|
||||
const { rawGalleryExtension, version } = rawGalleryExtensions[index];
|
||||
if (version) {
|
||||
const versionAsset = rawGalleryExtension.versions.find(v => v.version === version);
|
||||
if (versionAsset) {
|
||||
result.push([index, toExtension(rawGalleryExtension, versionAsset, getAllTargetPlatforms(rawGalleryExtension))]);
|
||||
}
|
||||
} else {
|
||||
const extension = toExtensionWithLatestVersion(rawGalleryExtension, index);
|
||||
if (extension.properties.isPreReleaseVersion && !includePreRelease) {
|
||||
preReleaseVersions.set(extension.identifier.uuid, index);
|
||||
} else {
|
||||
result.push([index, extension]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preReleaseVersions.size) {
|
||||
const query = new Query()
|
||||
.withFlags(Flags.IncludeVersions, Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, preReleaseVersions.size)
|
||||
.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code')
|
||||
.withFilter(FilterType.ExtensionId, ...preReleaseVersions.keys());
|
||||
const { galleryExtensions } = await this.queryGallery(query, targetPlatform, token);
|
||||
if (galleryExtensions.length !== preReleaseVersions.size) {
|
||||
throw new Error('Not all extensions with latest versions are returned');
|
||||
}
|
||||
for (const rawGalleryExtension of galleryExtensions) {
|
||||
const index = preReleaseVersions.get(rawGalleryExtension.extensionId)!;
|
||||
const extension = toExtensionWithLatestVersion(rawGalleryExtension, index);
|
||||
result.push([index, extension]);
|
||||
}
|
||||
}
|
||||
|
||||
return result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension);
|
||||
}
|
||||
|
||||
private async queryGallery(query: Query, targetPlatform: TargetPlatform, token: CancellationToken): Promise<{ galleryExtensions: IRawGalleryExtension[], total: number; }> {
|
||||
if (!this.isEnabled()) {
|
||||
throw new Error('No extension gallery service configured.');
|
||||
|
@ -811,7 +859,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
return '';
|
||||
}
|
||||
|
||||
async getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {
|
||||
async getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {
|
||||
let query = new Query()
|
||||
.withFlags(Flags.IncludeVersions, Flags.IncludeCategoryAndTags, Flags.IncludeFiles, Flags.IncludeVersionProperties)
|
||||
.withPage(1, 1)
|
||||
|
@ -836,7 +884,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi
|
|||
const result: IGalleryExtensionVersion[] = [];
|
||||
for (const version of galleryExtensions[0].versions) {
|
||||
try {
|
||||
if (result[result.length - 1]?.version !== version.version && await this.isRawExtensionVersionCompatible(version, allTargetPlatforms, targetPlatform)) {
|
||||
if (result[result.length - 1]?.version !== version.version && await this.isRawExtensionVersionCompatible(version, includePreRelease, allTargetPlatforms, targetPlatform)) {
|
||||
result.push({ version: version.version, date: version.lastUpdated });
|
||||
}
|
||||
} catch (error) { /* Ignore error and skip version */ }
|
||||
|
|
|
@ -184,6 +184,7 @@ export interface IGalleryExtensionProperties {
|
|||
engine?: string;
|
||||
localizedLanguages?: string[];
|
||||
targetPlatform: TargetPlatform;
|
||||
isPreReleaseVersion: boolean;
|
||||
}
|
||||
|
||||
export interface IGalleryExtensionAsset {
|
||||
|
@ -253,10 +254,11 @@ export interface IGalleryExtension {
|
|||
releaseDate: number;
|
||||
lastUpdated: number;
|
||||
preview: boolean;
|
||||
hasPreReleaseVersion: boolean;
|
||||
allTargetPlatforms: TargetPlatform[];
|
||||
assets: IGalleryExtensionAssets;
|
||||
properties: IGalleryExtensionProperties;
|
||||
telemetryData: any;
|
||||
telemetryData?: any;
|
||||
}
|
||||
|
||||
export interface IGalleryMetadata {
|
||||
|
@ -270,6 +272,8 @@ export interface ILocalExtension extends IExtension {
|
|||
publisherId: string | null;
|
||||
publisherDisplayName: string | null;
|
||||
installedTimestamp?: number;
|
||||
isPreReleaseVersion: boolean;
|
||||
hadPreReleaseVersion: boolean;
|
||||
}
|
||||
|
||||
export const enum SortBy {
|
||||
|
@ -297,6 +301,7 @@ export interface IQueryOptions {
|
|||
sortBy?: SortBy;
|
||||
sortOrder?: SortOrder;
|
||||
source?: string;
|
||||
includePreRelease?: boolean;
|
||||
}
|
||||
|
||||
export const enum StatisticType {
|
||||
|
@ -325,6 +330,7 @@ export interface IExtensionGalleryService {
|
|||
isEnabled(): boolean;
|
||||
query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>>;
|
||||
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, token: CancellationToken): Promise<IGalleryExtension[]>;
|
||||
getExtensions(identifiers: ReadonlyArray<IExtensionIdentifier | IExtensionIdentifierWithVersion>, includePreRelease: boolean, token: CancellationToken): Promise<IGalleryExtension[]>;
|
||||
download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void>;
|
||||
reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void>;
|
||||
getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
|
@ -332,10 +338,10 @@ export interface IExtensionGalleryService {
|
|||
getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string>;
|
||||
getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
isExtensionCompatible(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<boolean>;
|
||||
getCompatibleExtension(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getCompatibleExtension(id: IExtensionIdentifier, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getAllCompatibleVersions(extension: IGalleryExtension, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
|
||||
isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<boolean>;
|
||||
getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getCompatibleExtension(id: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtension | null>;
|
||||
getAllCompatibleVersions(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]>;
|
||||
}
|
||||
|
||||
export interface InstallExtensionEvent {
|
||||
|
@ -359,6 +365,7 @@ export enum ExtensionManagementErrorCode {
|
|||
Unsupported = 'Unsupported',
|
||||
Malicious = 'Malicious',
|
||||
Incompatible = 'Incompatible',
|
||||
IncompatibleTargetPlatform = 'IncompatibleTargetPlatform',
|
||||
Invalid = 'Invalid',
|
||||
Download = 'Download',
|
||||
Extract = 'Extract',
|
||||
|
@ -376,7 +383,7 @@ export class ExtensionManagementError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean };
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean, installGivenVersion?: boolean, installPreReleaseVersion?: boolean };
|
||||
export type InstallVSIXOptions = Omit<InstallOptions, 'installGivenVersion'> & { installOnlyNewlyAddedFromExtensionPack?: boolean };
|
||||
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import { IFile, zip } from 'vs/base/node/zip';
|
|||
import * as nls from 'vs/nls';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, joinErrors, Metadata, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import {
|
||||
ExtensionManagementError, ExtensionManagementErrorCode, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGalleryExtension, IGalleryMetadata, ILocalExtension, InstallOperation, InstallOptions,
|
||||
InstallVSIXOptions, TargetPlatform
|
||||
|
@ -29,7 +29,7 @@ import { ExtensionsDownloader } from 'vs/platform/extensionManagement/node/exten
|
|||
import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extensionLifecycle';
|
||||
import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
|
||||
import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache';
|
||||
import { ExtensionsScanner, ILocalExtensionManifest, IMetadata } from 'vs/platform/extensionManagement/node/extensionsScanner';
|
||||
import { ExtensionsScanner, ILocalExtensionManifest } from 'vs/platform/extensionManagement/node/extensionsScanner';
|
||||
import { ExtensionsWatcher } from 'vs/platform/extensionManagement/node/extensionsWatcher';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator';
|
||||
|
@ -43,7 +43,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'
|
|||
interface InstallableExtension {
|
||||
zipPath: string;
|
||||
identifierWithVersion: ExtensionIdentifierWithVersion;
|
||||
metadata?: IMetadata;
|
||||
metadata?: Metadata;
|
||||
}
|
||||
|
||||
export class ExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService {
|
||||
|
@ -226,7 +226,7 @@ abstract class AbstractInstallExtensionTask extends AbstractExtensionTask<ILocal
|
|||
try {
|
||||
const local = await this.unsetUninstalledAndGetLocal(installableExtension.identifierWithVersion);
|
||||
if (local) {
|
||||
return installableExtension.metadata ? this.extensionsScanner.saveMetadataForLocalExtension(local, installableExtension.metadata) : local;
|
||||
return installableExtension.metadata ? this.extensionsScanner.saveMetadataForLocalExtension(local, { ...((<ILocalExtensionManifest>local.manifest).__metadata || {}), ...installableExtension.metadata }) : local;
|
||||
}
|
||||
} catch (e) {
|
||||
if (isMacintosh) {
|
||||
|
@ -287,6 +287,8 @@ class InstallGalleryExtensionTask extends AbstractInstallExtensionTask {
|
|||
const installableExtension = await this.downloadInstallableExtension(this.gallery, this._operation);
|
||||
installableExtension.metadata.isMachineScoped = this.options.isMachineScoped || existingExtension?.isMachineScoped;
|
||||
installableExtension.metadata.isBuiltin = this.options.isBuiltin || existingExtension?.isBuiltin;
|
||||
installableExtension.metadata.isPreReleaseVersion = this.gallery.properties.isPreReleaseVersion;
|
||||
installableExtension.metadata.hadPreReleaseVersion = this.gallery.properties.isPreReleaseVersion || existingExtension?.hadPreReleaseVersion;
|
||||
|
||||
try {
|
||||
const local = await this.installExtension(installableExtension, token);
|
||||
|
@ -383,7 +385,7 @@ class InstallVSIXTask extends AbstractInstallExtensionTask {
|
|||
return this.installExtension({ zipPath: path.resolve(this.location.fsPath), identifierWithVersion, metadata }, token);
|
||||
}
|
||||
|
||||
private async getMetadata(name: string, token: CancellationToken): Promise<IMetadata> {
|
||||
private async getMetadata(name: string, token: CancellationToken): Promise<Metadata> {
|
||||
try {
|
||||
const galleryExtension = (await this.galleryService.query({ names: [name], pageSize: 1 }, token)).firstPage[0];
|
||||
if (galleryExtension) {
|
||||
|
|
|
@ -19,7 +19,8 @@ import * as pfs from 'vs/base/node/pfs';
|
|||
import { extract, ExtractError } from 'vs/base/node/zip';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ExtensionManagementError, ExtensionManagementErrorCode, IGalleryMetadata, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Metadata } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import { ExtensionManagementError, ExtensionManagementErrorCode, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, ExtensionIdentifierWithVersion, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
|
||||
import { ExtensionType, IExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
|
@ -28,9 +29,7 @@ import { ILogService } from 'vs/platform/log/common/log';
|
|||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { CancellationToken } from 'vscode';
|
||||
|
||||
export type IMetadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; isBuiltin: boolean; }>;
|
||||
type IStoredMetadata = IMetadata & { installedTimestamp: number | undefined };
|
||||
export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: IMetadata };
|
||||
export type ILocalExtensionManifest = IExtensionManifest & { __metadata?: Metadata };
|
||||
type IRelaxedLocalExtension = Omit<ILocalExtension, 'isBuiltin'> & { isBuiltin: boolean };
|
||||
|
||||
export class ExtensionsScanner extends Disposable {
|
||||
|
@ -94,7 +93,7 @@ export class ExtensionsScanner extends Disposable {
|
|||
return this.scanExtensionsInDir(this.extensionsPath, ExtensionType.User);
|
||||
}
|
||||
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: IMetadata | undefined, token: CancellationToken): Promise<ILocalExtension> {
|
||||
async extractUserExtension(identifierWithVersion: ExtensionIdentifierWithVersion, zipPath: string, metadata: Metadata | undefined, token: CancellationToken): Promise<ILocalExtension> {
|
||||
const folderName = identifierWithVersion.key();
|
||||
const tempPath = path.join(this.extensionsPath, `.${generateUuid()}`);
|
||||
const extensionPath = path.join(this.extensionsPath, folderName);
|
||||
|
@ -140,21 +139,21 @@ export class ExtensionsScanner extends Disposable {
|
|||
throw new Error(localize('cannot read', "Cannot read the extension from {0}", this.extensionsPath));
|
||||
}
|
||||
|
||||
async saveMetadataForLocalExtension(local: ILocalExtension, metadata: IMetadata): Promise<ILocalExtension> {
|
||||
async saveMetadataForLocalExtension(local: ILocalExtension, metadata: Metadata): Promise<ILocalExtension> {
|
||||
this.setMetadata(local, metadata);
|
||||
await this.storeMetadata(local, { ...metadata, installedTimestamp: local.installedTimestamp });
|
||||
return local;
|
||||
}
|
||||
|
||||
private async storeMetadata(local: ILocalExtension, storedMetadata: IStoredMetadata): Promise<ILocalExtension> {
|
||||
private async storeMetadata(local: ILocalExtension, metaData: Metadata): Promise<ILocalExtension> {
|
||||
// unset if false
|
||||
storedMetadata.isMachineScoped = storedMetadata.isMachineScoped || undefined;
|
||||
storedMetadata.isBuiltin = storedMetadata.isBuiltin || undefined;
|
||||
storedMetadata.installedTimestamp = storedMetadata.installedTimestamp || undefined;
|
||||
metaData.isMachineScoped = metaData.isMachineScoped || undefined;
|
||||
metaData.isBuiltin = metaData.isBuiltin || undefined;
|
||||
metaData.installedTimestamp = metaData.installedTimestamp || undefined;
|
||||
const manifestPath = path.join(local.location.fsPath, 'package.json');
|
||||
const raw = await pfs.Promises.readFile(manifestPath, 'utf8');
|
||||
const { manifest } = await this.parseManifest(raw);
|
||||
(manifest as ILocalExtensionManifest).__metadata = storedMetadata;
|
||||
(manifest as ILocalExtensionManifest).__metadata = metaData;
|
||||
await pfs.Promises.writeFile(manifestPath, JSON.stringify(manifest, null, '\t'));
|
||||
return local;
|
||||
}
|
||||
|
@ -302,7 +301,6 @@ export class ExtensionsScanner extends Disposable {
|
|||
const local = <ILocalExtension>{ type, identifier, manifest, location: extensionLocation, readmeUrl, changelogUrl, publisherDisplayName: null, publisherId: null, isMachineScoped: false, isBuiltin: type === ExtensionType.System };
|
||||
if (metadata) {
|
||||
this.setMetadata(local, metadata);
|
||||
local.installedTimestamp = metadata.installedTimestamp;
|
||||
}
|
||||
return local;
|
||||
}
|
||||
|
@ -331,12 +329,15 @@ export class ExtensionsScanner extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
private setMetadata(local: IRelaxedLocalExtension, metadata: IMetadata): void {
|
||||
private setMetadata(local: IRelaxedLocalExtension, metadata: Metadata): void {
|
||||
local.publisherDisplayName = metadata.publisherDisplayName || null;
|
||||
local.publisherId = metadata.publisherId || null;
|
||||
local.identifier.uuid = metadata.id;
|
||||
local.isMachineScoped = !!metadata.isMachineScoped;
|
||||
local.isPreReleaseVersion = !!metadata.isPreReleaseVersion;
|
||||
local.hadPreReleaseVersion = !!metadata.hadPreReleaseVersion;
|
||||
local.isBuiltin = local.type === ExtensionType.System || !!metadata.isBuiltin;
|
||||
local.installedTimestamp = metadata.installedTimestamp;
|
||||
}
|
||||
|
||||
private async removeUninstalledExtensions(): Promise<void> {
|
||||
|
@ -392,7 +393,7 @@ export class ExtensionsScanner extends Disposable {
|
|||
return this._devSystemExtensionsPath;
|
||||
}
|
||||
|
||||
private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: IStoredMetadata | null; }> {
|
||||
private async readManifest(extensionPath: string): Promise<{ manifest: IExtensionManifest; metadata: Metadata | null; }> {
|
||||
const promises = [
|
||||
pfs.Promises.readFile(path.join(extensionPath, 'package.json'), 'utf8')
|
||||
.then(raw => this.parseManifest(raw)),
|
||||
|
@ -408,7 +409,7 @@ export class ExtensionsScanner extends Disposable {
|
|||
};
|
||||
}
|
||||
|
||||
private parseManifest(raw: string): Promise<{ manifest: IExtensionManifest; metadata: IMetadata | null; }> {
|
||||
private parseManifest(raw: string): Promise<{ manifest: IExtensionManifest; metadata: Metadata | null; }> {
|
||||
return new Promise((c, e) => {
|
||||
try {
|
||||
const manifest = JSON.parse(raw);
|
||||
|
|
|
@ -419,7 +419,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
|
|||
}
|
||||
} catch (error) {
|
||||
addToSkipped.push(e);
|
||||
if (error instanceof ExtensionManagementError && error.code === ExtensionManagementErrorCode.Incompatible) {
|
||||
if (error instanceof ExtensionManagementError && (error.code === ExtensionManagementErrorCode.Incompatible || error.code === ExtensionManagementErrorCode.IncompatibleTargetPlatform)) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Skipped synchronizing extension because the compatible extension is not found.`, extension.displayName || extension.identifier.id);
|
||||
} else {
|
||||
this.logService.error(error);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import { Cache, CacheResult } from 'vs/base/common/cache';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { getErrorMessage, isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { dispose, toDisposable, Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dispose, toDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { append, $, finalHandler, join, addDisposableListener, EventType, setParentFlowTo, reset, Dimension } from 'vs/base/browser/dom';
|
||||
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
@ -21,23 +21,23 @@ import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsServi
|
|||
import { IExtensionManifest, IKeyBinding, IView, IViewContainer } from 'vs/platform/extensions/common/extensions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keybindings';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, IExtension, ExtensionContainers, ExtensionEditorTab, ExtensionState } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget, PreReleaseIndicatorWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import {
|
||||
UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction,
|
||||
RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction,
|
||||
ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction,
|
||||
InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction
|
||||
InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, UsePreReleaseVersionAction, StopUsingPreReleaseVersionAction
|
||||
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
@ -67,7 +67,6 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
|||
import { Delegate } from 'vs/workbench/contrib/extensions/browser/extensionsList';
|
||||
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
||||
import { attachKeybindingLabelStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { errorIcon, infoIcon, starEmptyIcon, verifiedPublisherIcon as verifiedPublisherThemeIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
@ -141,6 +140,7 @@ interface IExtensionEditorTemplate {
|
|||
preview: HTMLElement;
|
||||
builtin: HTMLElement;
|
||||
version: HTMLElement;
|
||||
preRelease: HTMLElement;
|
||||
publisher: HTMLElement;
|
||||
publisherDisplayName: HTMLElement;
|
||||
verifiedPublisherIcon: HTMLElement;
|
||||
|
@ -161,10 +161,13 @@ const enum WebviewIndex {
|
|||
Changelog
|
||||
}
|
||||
|
||||
const CONTEXT_SHOW_PRE_RELEASE_VERSION = new RawContextKey<boolean>('showPreReleaseVersion', false);
|
||||
|
||||
export class ExtensionEditor extends EditorPane {
|
||||
|
||||
static readonly ID: string = 'workbench.editor.extension';
|
||||
|
||||
private readonly _scopedContextKeyService = this._register(new MutableDisposable<IContextKeyService>());
|
||||
private template: IExtensionEditorTemplate | undefined;
|
||||
|
||||
private extensionReadme: Cache<string> | null;
|
||||
|
@ -184,6 +187,8 @@ export class ExtensionEditor extends EditorPane {
|
|||
private editorLoadComplete: boolean = false;
|
||||
private dimension: Dimension | undefined;
|
||||
|
||||
private showPreReleaseVersionContextKey: IContextKey<boolean> | undefined;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
|
@ -201,6 +206,7 @@ export class ExtensionEditor extends EditorPane {
|
|||
@IWebviewService private readonly webviewService: IWebviewService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super(ExtensionEditor.ID, telemetryService, themeService, storageService);
|
||||
this.extensionReadme = null;
|
||||
|
@ -208,8 +214,16 @@ export class ExtensionEditor extends EditorPane {
|
|||
this.extensionManifest = null;
|
||||
}
|
||||
|
||||
override get scopedContextKeyService(): IContextKeyService | undefined {
|
||||
return this._scopedContextKeyService.value;
|
||||
}
|
||||
|
||||
createEditor(parent: HTMLElement): void {
|
||||
const root = append(parent, $('.extension-editor'));
|
||||
this._scopedContextKeyService.value = this.contextKeyService.createScoped(root);
|
||||
this._scopedContextKeyService.value.createKey('inExtensionEditor', true);
|
||||
this.showPreReleaseVersionContextKey = CONTEXT_SHOW_PRE_RELEASE_VERSION.bindTo(this._scopedContextKeyService.value);
|
||||
|
||||
root.tabIndex = 0; // this is required for the focus tracker on the editor
|
||||
root.style.outline = 'none';
|
||||
root.setAttribute('role', 'document');
|
||||
|
@ -228,6 +242,7 @@ export class ExtensionEditor extends EditorPane {
|
|||
|
||||
const builtin = append(title, $('span.builtin'));
|
||||
builtin.textContent = localize('builtin', "Built-in");
|
||||
const preRelease = append(title, $('span.pre-release'));
|
||||
|
||||
const subtitle = append(details, $('.subtitle'));
|
||||
const publisher = append(append(subtitle, $('.subtitle-entry')), $('.publisher.clickable', { title: localize('publisher', "Publisher"), tabIndex: 0 }));
|
||||
|
@ -277,6 +292,7 @@ export class ExtensionEditor extends EditorPane {
|
|||
icon,
|
||||
iconContainer,
|
||||
version,
|
||||
preRelease,
|
||||
installCount,
|
||||
name,
|
||||
navbar,
|
||||
|
@ -306,13 +322,31 @@ export class ExtensionEditor extends EditorPane {
|
|||
return disposables;
|
||||
}
|
||||
|
||||
override async setInput(input: ExtensionsInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
override async setInput(input: ExtensionsInput, options: IExtensionEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
|
||||
await super.setInput(input, options, context, token);
|
||||
this.updatePreReleaseVersionContext();
|
||||
if (this.template) {
|
||||
await this.updateTemplate(input, this.template, !!options?.preserveFocus);
|
||||
await this.updateTemplate(input.extension, this.template, !!options?.preserveFocus);
|
||||
}
|
||||
}
|
||||
|
||||
override setOptions(options: IExtensionEditorOptions | undefined): void {
|
||||
const currentOptions: IExtensionEditorOptions | undefined = this.options;
|
||||
super.setOptions(options);
|
||||
this.updatePreReleaseVersionContext();
|
||||
if (currentOptions?.showPreReleaseVersion !== options?.showPreReleaseVersion) {
|
||||
this.openTab(ExtensionEditorTab.Readme);
|
||||
}
|
||||
}
|
||||
|
||||
private updatePreReleaseVersionContext(): void {
|
||||
let showPreReleaseVersion = (<IExtensionEditorOptions | undefined>this.options)?.showPreReleaseVersion;
|
||||
if (isUndefined(showPreReleaseVersion)) {
|
||||
showPreReleaseVersion = !!(<ExtensionsInput>this.input).extension.gallery?.properties.isPreReleaseVersion;
|
||||
}
|
||||
this.showPreReleaseVersionContextKey?.set(showPreReleaseVersion);
|
||||
}
|
||||
|
||||
async openTab(tab: ExtensionEditorTab): Promise<void> {
|
||||
if (!this.input || !this.template) {
|
||||
return;
|
||||
|
@ -326,10 +360,9 @@ export class ExtensionEditor extends EditorPane {
|
|||
}
|
||||
}
|
||||
|
||||
private async updateTemplate(input: ExtensionsInput, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise<void> {
|
||||
private async updateTemplate(extension: IExtension, template: IExtensionEditorTemplate, preserveFocus: boolean): Promise<void> {
|
||||
this.activeElement = null;
|
||||
this.editorLoadComplete = false;
|
||||
const extension = input.extension;
|
||||
|
||||
if (this.currentIdentifier !== extension.identifier.id) {
|
||||
this.initialScrollProgress.clear();
|
||||
|
@ -338,9 +371,9 @@ export class ExtensionEditor extends EditorPane {
|
|||
|
||||
this.transientDisposables.clear();
|
||||
|
||||
this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token)));
|
||||
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token)));
|
||||
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token)));
|
||||
this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(!!this.showPreReleaseVersionContextKey?.get(), token)));
|
||||
this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(!!this.showPreReleaseVersionContextKey?.get(), token)));
|
||||
this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(!!this.showPreReleaseVersionContextKey?.get(), token)));
|
||||
|
||||
const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, template.iconContainer, true);
|
||||
this.transientDisposables.add(addDisposableListener(template.icon, 'error', () => template.icon.src = extension.iconUrlFallback, { once: true }));
|
||||
|
@ -393,6 +426,7 @@ export class ExtensionEditor extends EditorPane {
|
|||
}
|
||||
|
||||
const widgets = [
|
||||
this.instantiationService.createInstance(PreReleaseIndicatorWidget, template.preRelease),
|
||||
remoteBadge,
|
||||
this.instantiationService.createInstance(InstallCountWidget, template.installCount, false),
|
||||
this.instantiationService.createInstance(RatingsWidget, template.rating, false)
|
||||
|
@ -415,11 +449,15 @@ export class ExtensionEditor extends EditorPane {
|
|||
combinedInstallAction,
|
||||
this.instantiationService.createInstance(InstallingLabelAction),
|
||||
this.instantiationService.createInstance(ActionWithDropDownAction, 'extensions.uninstall', UninstallAction.UninstallLabel, [
|
||||
this.instantiationService.createInstance(UninstallAction),
|
||||
this.instantiationService.createInstance(InstallAnotherVersionAction),
|
||||
[
|
||||
this.instantiationService.createInstance(UninstallAction),
|
||||
this.instantiationService.createInstance(InstallAnotherVersionAction),
|
||||
]
|
||||
]),
|
||||
this.instantiationService.createInstance(UsePreReleaseVersionAction),
|
||||
this.instantiationService.createInstance(StopUsingPreReleaseVersionAction),
|
||||
this.instantiationService.createInstance(ToggleSyncExtensionAction),
|
||||
this.instantiationService.createInstance(ExtensionEditorManageExtensionAction),
|
||||
new ExtensionEditorManageExtensionAction(this.scopedContextKeyService || this.contextKeyService, this.instantiationService),
|
||||
];
|
||||
const extensionStatus = this.instantiationService.createInstance(ExtensionStatusAction);
|
||||
const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, extensionStatus]);
|
||||
|
|
|
@ -15,7 +15,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo
|
|||
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, UsePreReleaseVersionAction, StopUsingPreReleaseVersionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
|
||||
import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
|
||||
|
@ -1156,6 +1156,75 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
|
|||
|
||||
// Extension Context Menu
|
||||
private registerContextMenuActions(): void {
|
||||
this.registerExtensionAction({
|
||||
id: 'workbench.extensions.action.showPreReleaseVersion',
|
||||
title: { value: localize('show pre-release version', "Show Pre-release Version"), original: 'Show Pre-release Version' },
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '0_install',
|
||||
order: 0,
|
||||
when: ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('galleryExtensionIsPreReleaseVersion')),
|
||||
ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('showPreReleaseVersion')))
|
||||
},
|
||||
run: async (accessor: ServicesAccessor, extensionId: string) => {
|
||||
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
|
||||
const extension = (await extensionWorkbenchService.queryGallery({ names: [extensionId] }, CancellationToken.None)).firstPage[0];
|
||||
extensionWorkbenchService.open(extension, { showPreReleaseVersion: true });
|
||||
}
|
||||
});
|
||||
this.registerExtensionAction({
|
||||
id: 'workbench.extensions.action.showReleasedVersion',
|
||||
title: { value: localize('show released version', "Show Released Version"), original: 'Show Released Version' },
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '0_install',
|
||||
order: 1,
|
||||
when: ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('galleryExtensionIsPreReleaseVersion'), ContextKeyExpr.equals('extensionStatus', 'installed')),
|
||||
ContextKeyExpr.and(ContextKeyExpr.has('inExtensionEditor'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.has('showPreReleaseVersion')))
|
||||
},
|
||||
run: async (accessor: ServicesAccessor, extensionId: string) => {
|
||||
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
|
||||
const extension = (await extensionWorkbenchService.queryGallery({ names: [extensionId] }, CancellationToken.None)).firstPage[0];
|
||||
extensionWorkbenchService.open(extension, { showPreReleaseVersion: false });
|
||||
}
|
||||
});
|
||||
this.registerExtensionAction({
|
||||
id: UsePreReleaseVersionAction.ID,
|
||||
title: UsePreReleaseVersionAction.TITLE,
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '0_install',
|
||||
order: 2,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'))
|
||||
},
|
||||
run: async (accessor: ServicesAccessor, id: string) => {
|
||||
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
|
||||
const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id }));
|
||||
if (extension) {
|
||||
await extensionWorkbenchService.install(extension, { installPreReleaseVersion: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
this.registerExtensionAction({
|
||||
id: StopUsingPreReleaseVersionAction.ID,
|
||||
title: StopUsingPreReleaseVersionAction.TITLE,
|
||||
menu: {
|
||||
id: MenuId.ExtensionContext,
|
||||
group: '0_install',
|
||||
order: 3,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.has('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'))
|
||||
},
|
||||
run: async (accessor: ServicesAccessor, id: string) => {
|
||||
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
|
||||
const extension = extensionWorkbenchService.local.find(e => areSameExtensions(e.identifier, { id }));
|
||||
if (extension) {
|
||||
await extensionWorkbenchService.install(extension, { installPreReleaseVersion: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.registerExtensionAction({
|
||||
id: 'workbench.extensions.action.copyExtension',
|
||||
title: { value: localize('workbench.extensions.action.copyExtension', "Copy"), original: 'Copy' },
|
||||
|
|
|
@ -33,7 +33,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/
|
|||
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { MenuId, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
|
||||
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
@ -63,6 +63,7 @@ import { isVirtualWorkspace } from 'vs/platform/remote/common/remoteHosts';
|
|||
import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
||||
import { ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { flatten } from 'vs/base/common/arrays';
|
||||
|
||||
function getRelativeDateLabel(date: Date): string {
|
||||
const delta = new Date().getTime() - date.getTime();
|
||||
|
@ -133,7 +134,7 @@ export class PromptExtensionInstallFailureAction extends Action {
|
|||
return;
|
||||
}
|
||||
|
||||
if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.Malicious].includes(<ExtensionManagementErrorCode>this.error.name)) {
|
||||
if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious].includes(<ExtensionManagementErrorCode>this.error.name)) {
|
||||
await this.dialogService.show(Severity.Info, getErrorMessage(this.error));
|
||||
return;
|
||||
}
|
||||
|
@ -185,28 +186,40 @@ export class ActionWithDropDownAction extends ExtensionAction {
|
|||
}
|
||||
|
||||
override set extension(extension: IExtension | null) {
|
||||
this.actions.forEach(a => a.extension = extension);
|
||||
this.extensionActions.forEach(a => a.extension = extension);
|
||||
super.extension = extension;
|
||||
}
|
||||
|
||||
protected readonly extensionActions: ExtensionAction[];
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
protected readonly actions: ExtensionAction[],
|
||||
private readonly actionsGroups: ExtensionAction[][],
|
||||
) {
|
||||
super(id, label);
|
||||
this.extensionActions = flatten(actionsGroups);
|
||||
this.update();
|
||||
this._register(Event.any(...actions.map(a => a.onDidChange))(() => this.update(true)));
|
||||
actions.forEach(a => this._register(a));
|
||||
this._register(Event.any(...this.extensionActions.map(a => a.onDidChange))(() => this.update(true)));
|
||||
this.extensionActions.forEach(a => this._register(a));
|
||||
}
|
||||
|
||||
update(donotUpdateActions?: boolean): void {
|
||||
if (!donotUpdateActions) {
|
||||
this.actions.forEach(a => a.update());
|
||||
this.extensionActions.forEach(a => a.update());
|
||||
}
|
||||
|
||||
const enabledActions = this.actions.filter(a => a.enabled);
|
||||
this.action = enabledActions[0];
|
||||
this._menuActions = enabledActions.slice(1);
|
||||
const enabledActionsGroups = this.actionsGroups.map(actionsGroup => actionsGroup.filter(a => a.enabled));
|
||||
|
||||
let actions: IAction[] = [];
|
||||
for (const enabledActions of enabledActionsGroups) {
|
||||
if (enabledActions.length) {
|
||||
actions = [...actions, ...enabledActions, new Separator()];
|
||||
}
|
||||
}
|
||||
actions = actions.length ? actions.slice(0, actions.length - 1) : actions;
|
||||
|
||||
this.action = actions[0];
|
||||
this._menuActions = actions.slice(1);
|
||||
|
||||
this.enabled = !!this.action;
|
||||
if (this.action) {
|
||||
|
@ -214,7 +227,7 @@ export class ActionWithDropDownAction extends ExtensionAction {
|
|||
this.tooltip = this.action.tooltip;
|
||||
}
|
||||
|
||||
let clazz = (this.action || this.actions[0])?.class || '';
|
||||
let clazz = (this.action || this.extensionActions[0])?.class || '';
|
||||
clazz = clazz ? `${clazz} action-dropdown` : 'action-dropdown';
|
||||
if (this._menuActions.length === 0) {
|
||||
clazz += ' action-dropdown';
|
||||
|
@ -223,7 +236,7 @@ export class ActionWithDropDownAction extends ExtensionAction {
|
|||
}
|
||||
|
||||
override run(): Promise<void> {
|
||||
const enabledActions = this.actions.filter(a => a.enabled);
|
||||
const enabledActions = this.extensionActions.filter(a => a.enabled);
|
||||
return enabledActions[0].run();
|
||||
}
|
||||
}
|
||||
|
@ -241,14 +254,14 @@ export abstract class AbstractInstallAction extends ExtensionAction {
|
|||
private readonly updateThrottler = new Throttler();
|
||||
|
||||
constructor(
|
||||
id: string, label: string, cssClass: string,
|
||||
id: string, private readonly installPreReleaseVersion: boolean, cssClass: string,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IExtensionService private readonly runtimeExtensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
) {
|
||||
super(id, label, cssClass, false);
|
||||
super(id, localize('install', "Install"), cssClass, false);
|
||||
this.update();
|
||||
this._register(this.labelService.onDidChangeFormatters(() => this.updateLabel(), this));
|
||||
}
|
||||
|
@ -261,7 +274,7 @@ export abstract class AbstractInstallAction extends ExtensionAction {
|
|||
this.enabled = false;
|
||||
if (this.extension && !this.extension.isBuiltin) {
|
||||
if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
|
||||
this.enabled = true;
|
||||
this.enabled = !this.installPreReleaseVersion || this.extension.hasPreReleaseVersion;
|
||||
this.updateLabel();
|
||||
}
|
||||
}
|
||||
|
@ -271,7 +284,7 @@ export abstract class AbstractInstallAction extends ExtensionAction {
|
|||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
this.extensionsWorkbenchService.open(this.extension);
|
||||
this.extensionsWorkbenchService.open(this.extension, { showPreReleaseVersion: this.installPreReleaseVersion });
|
||||
|
||||
alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
|
||||
|
||||
|
@ -324,13 +337,23 @@ export abstract class AbstractInstallAction extends ExtensionAction {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected abstract updateLabel(): void;
|
||||
protected abstract getInstallOptions(): InstallOptions;
|
||||
protected updateLabel(): void {
|
||||
this.label = this.getLabel();
|
||||
}
|
||||
|
||||
protected getLabel(): string {
|
||||
return this.installPreReleaseVersion && this.extension?.hasPreReleaseVersion ? localize('install pre-release', "Install Pre-release Version") : localize('install', "Install");
|
||||
}
|
||||
|
||||
protected getInstallOptions(): InstallOptions {
|
||||
return { installPreReleaseVersion: this.installPreReleaseVersion };
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallAction extends AbstractInstallAction {
|
||||
|
||||
constructor(
|
||||
installPreReleaseVersion: boolean,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IExtensionService runtimeExtensionService: IExtensionService,
|
||||
|
@ -341,7 +364,7 @@ export class InstallAction extends AbstractInstallAction {
|
|||
@IUserDataAutoSyncEnablementService protected readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
|
||||
@IUserDataSyncResourceEnablementService protected readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
|
||||
) {
|
||||
super(`extensions.installAndSync`, localize('install', "Install"), InstallAction.Class,
|
||||
super(`extensions.install`, installPreReleaseVersion, InstallAction.Class,
|
||||
extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService);
|
||||
this.updateLabel();
|
||||
this._register(labelService.onDidChangeFormatters(() => this.updateLabel(), this));
|
||||
|
@ -349,13 +372,11 @@ export class InstallAction extends AbstractInstallAction {
|
|||
Event.filter(userDataSyncResourceEnablementService.onDidChangeResourceEnablement, e => e[0] === SyncResource.Extensions))(() => this.update()));
|
||||
}
|
||||
|
||||
protected updateLabel(): void {
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
protected override getLabel(): string {
|
||||
const baseLabel = super.getLabel();
|
||||
|
||||
const donotSyncLabel = localize('do no sync', "Do not sync");
|
||||
const isMachineScoped = this.getInstallOptions().isMachineScoped;
|
||||
this.label = isMachineScoped ? localize('install and do no sync', "Install (Do not sync)") : localize('install', "Install");
|
||||
|
||||
// When remote connection exists
|
||||
if (this._manifest && this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
|
@ -364,19 +385,33 @@ export class InstallAction extends AbstractInstallAction {
|
|||
|
||||
if (server === this.extensionManagementServerService.remoteExtensionManagementServer) {
|
||||
const host = this.extensionManagementServerService.remoteExtensionManagementServer.label;
|
||||
this.label = isMachineScoped
|
||||
? localize({ key: 'install in remote and do not sync', comment: ['This is the name of the action to install an extension in remote server and do not sync it. Placeholder is for the name of remote server.'] }, "Install in {0} (Do not sync)", host)
|
||||
: localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", host);
|
||||
return;
|
||||
return isMachineScoped
|
||||
? localize({
|
||||
key: 'install extension in remote and do not sync',
|
||||
comment: [
|
||||
'First placeholder is install action label.',
|
||||
'Second placeholder is the name of the action to install an extension in remote server and do not sync it. Placeholder is for the name of remote server.',
|
||||
'Third placeholder is do not sync label.',
|
||||
]
|
||||
}, "{0} in {1} ({2})", baseLabel, host, donotSyncLabel)
|
||||
: localize({
|
||||
key: 'install extension in remote',
|
||||
comment: [
|
||||
'First placeholder is install action label.',
|
||||
'Second placeholder is the name of the action to install an extension in remote server and do not sync it. Placeholder is for the name of remote server.',
|
||||
]
|
||||
}, "{0} in {1}", baseLabel, host);
|
||||
}
|
||||
|
||||
this.label = isMachineScoped ? localize('install locally and do not sync', "Install Locally (Do not sync)") : localize('install locally', "Install Locally");
|
||||
return;
|
||||
return isMachineScoped ?
|
||||
localize('install extension locally and do not sync', "{0} Locally ({1})", baseLabel, donotSyncLabel) : localize('install extension locally', "{0} Locally", baseLabel);
|
||||
}
|
||||
|
||||
return isMachineScoped ? `${baseLabel} (${donotSyncLabel})` : baseLabel;
|
||||
}
|
||||
|
||||
protected getInstallOptions(): InstallOptions {
|
||||
return { isMachineScoped: this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions) };
|
||||
protected override getInstallOptions(): InstallOptions {
|
||||
return { ...super.getInstallOptions(), isMachineScoped: this.userDataAutoSyncEnablementService.isEnabled() && this.userDataSyncResourceEnablementService.isResourceEnabled(SyncResource.Extensions) };
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -384,6 +419,7 @@ export class InstallAction extends AbstractInstallAction {
|
|||
export class InstallAndSyncAction extends AbstractInstallAction {
|
||||
|
||||
constructor(
|
||||
installPreReleaseVersion: boolean,
|
||||
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IExtensionService runtimeExtensionService: IExtensionService,
|
||||
|
@ -393,7 +429,7 @@ export class InstallAndSyncAction extends AbstractInstallAction {
|
|||
@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
|
||||
@IUserDataSyncResourceEnablementService private readonly userDataSyncResourceEnablementService: IUserDataSyncResourceEnablementService,
|
||||
) {
|
||||
super(`extensions.installAndSync`, localize('install', "Install"), InstallAndSyncAction.Class,
|
||||
super('extensions.installAndSync', installPreReleaseVersion, AbstractInstallAction.Class,
|
||||
extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService);
|
||||
this.tooltip = localize({ key: 'install everywhere tooltip', comment: ['Placeholder is the name of the product. Eg: Visual Studio Code or Visual Studio Code - Insiders'] }, "Install this extension in all your synced {0} instances", productService.nameLong);
|
||||
this._register(Event.any(userDataAutoSyncEnablementService.onDidChangeEnablement,
|
||||
|
@ -407,18 +443,16 @@ export class InstallAndSyncAction extends AbstractInstallAction {
|
|||
}
|
||||
}
|
||||
|
||||
protected updateLabel(): void { }
|
||||
|
||||
protected getInstallOptions(): InstallOptions {
|
||||
return { isMachineScoped: false };
|
||||
protected override getInstallOptions(): InstallOptions {
|
||||
return { ...super.getInstallOptions(), isMachineScoped: false };
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallDropdownAction extends ActionWithDropDownAction {
|
||||
|
||||
set manifest(manifest: IExtensionManifest) {
|
||||
this.actions.forEach(a => (<AbstractInstallAction>a).manifest = manifest);
|
||||
this.actions.forEach(a => a.update());
|
||||
this.extensionActions.forEach(a => (<AbstractInstallAction>a).manifest = manifest);
|
||||
this.extensionActions.forEach(a => a.update());
|
||||
this.update();
|
||||
}
|
||||
|
||||
|
@ -426,8 +460,14 @@ export class InstallDropdownAction extends ActionWithDropDownAction {
|
|||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super(`extensions.installActions`, '', [
|
||||
instantiationService.createInstance(InstallAndSyncAction),
|
||||
instantiationService.createInstance(InstallAction),
|
||||
[
|
||||
instantiationService.createInstance(InstallAndSyncAction, false),
|
||||
instantiationService.createInstance(InstallAndSyncAction, true),
|
||||
],
|
||||
[
|
||||
instantiationService.createInstance(InstallAction, false),
|
||||
instantiationService.createInstance(InstallAction, true),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -812,7 +852,7 @@ export class DropDownMenuActionViewItem extends ActionViewItem {
|
|||
}
|
||||
}
|
||||
|
||||
export function getContextMenuActions(extension: IExtension | undefined | null, inExtensionEditor: boolean, instantiationService: IInstantiationService): IAction[][] {
|
||||
function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): [string, Array<MenuItemAction | SubmenuItemAction>][] {
|
||||
return instantiationService.invokeFunction(accessor => {
|
||||
const menuService = accessor.get(IMenuService);
|
||||
const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService);
|
||||
|
@ -826,25 +866,38 @@ export function getContextMenuActions(extension: IExtension | undefined | null,
|
|||
cksOverlay.push(['isExtensionRecommended', !!extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]]);
|
||||
cksOverlay.push(['isExtensionWorkspaceRecommended', extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]?.reasonId === ExtensionRecommendationReason.Workspace]);
|
||||
cksOverlay.push(['isUserIgnoredRecommendation', extensionIgnoredRecommendationsService.globalIgnoredRecommendations.some(e => e === extension.identifier.id.toLowerCase())]);
|
||||
cksOverlay.push(['inExtensionEditor', inExtensionEditor]);
|
||||
if (extension.state === ExtensionState.Installed) {
|
||||
cksOverlay.push(['extensionStatus', 'installed']);
|
||||
}
|
||||
cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]);
|
||||
cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]);
|
||||
cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]);
|
||||
}
|
||||
|
||||
const contextKeyService = accessor.get(IContextKeyService).createOverlay(cksOverlay);
|
||||
const groups: IAction[][] = [];
|
||||
const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService);
|
||||
menu.getActions({ shouldForwardArgs: true }).forEach(([, actions]) => groups.push(actions.map(action => {
|
||||
const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay));
|
||||
const actionsGroups = menu.getActions({ shouldForwardArgs: true });
|
||||
menu.dispose();
|
||||
return actionsGroups;
|
||||
});
|
||||
}
|
||||
|
||||
function toActions(actionsGroups: [string, Array<MenuItemAction | SubmenuItemAction>][], instantiationService: IInstantiationService): IAction[][] {
|
||||
const result: IAction[][] = [];
|
||||
for (const [, actions] of actionsGroups) {
|
||||
result.push(actions.map(action => {
|
||||
if (action instanceof SubmenuAction) {
|
||||
return action;
|
||||
}
|
||||
return instantiationService.createInstance(MenuItemExtensionAction, action);
|
||||
})));
|
||||
menu.dispose();
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return groups;
|
||||
});
|
||||
|
||||
export function getContextMenuActions(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): IAction[][] {
|
||||
const actionsGroups = getContextMenuActionsGroups(extension, contextKeyService, instantiationService);
|
||||
return toActions(actionsGroups, instantiationService);
|
||||
}
|
||||
|
||||
export class ManageExtensionAction extends ExtensionDropDownAction {
|
||||
|
@ -858,6 +911,7 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
|||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
|
||||
super(ManageExtensionAction.ID, '', '', true, instantiationService);
|
||||
|
@ -886,6 +940,11 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
|||
groups.push(themesGroup);
|
||||
}
|
||||
}
|
||||
|
||||
const contextMenuActionsGroups = getContextMenuActionsGroups(this.extension, this.contextKeyService, this.instantiationService);
|
||||
const installActions = toActions(contextMenuActionsGroups.filter(([group]) => group === '0_install'), this.instantiationService);
|
||||
const otherActions = toActions(contextMenuActionsGroups.filter(([group]) => group !== '0_install'), this.instantiationService);
|
||||
|
||||
groups.push([
|
||||
this.instantiationService.createInstance(EnableGloballyAction),
|
||||
this.instantiationService.createInstance(EnableForWorkspaceAction)
|
||||
|
@ -895,11 +954,12 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
|||
this.instantiationService.createInstance(DisableForWorkspaceAction, runningExtensions)
|
||||
]);
|
||||
groups.push([
|
||||
...(installActions[0] || []),
|
||||
this.instantiationService.createInstance(InstallAnotherVersionAction),
|
||||
this.instantiationService.createInstance(UninstallAction),
|
||||
this.instantiationService.createInstance(InstallAnotherVersionAction)
|
||||
]);
|
||||
|
||||
getContextMenuActions(this.extension, false, this.instantiationService).forEach(actions => groups.push(actions));
|
||||
otherActions.forEach(actions => groups.push(actions));
|
||||
|
||||
groups.forEach(group => group.forEach(extensionAction => {
|
||||
if (extensionAction instanceof ExtensionAction) {
|
||||
|
@ -930,7 +990,8 @@ export class ManageExtensionAction extends ExtensionDropDownAction {
|
|||
export class ExtensionEditorManageExtensionAction extends ExtensionDropDownAction {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
private readonly contextKeyService: IContextKeyService,
|
||||
instantiationService: IInstantiationService
|
||||
) {
|
||||
super('extensionEditor.manageExtension', '', `${ExtensionAction.ICON_ACTION_CLASS} manage ${ThemeIcon.asClassName(manageExtensionIcon)}`, true, instantiationService);
|
||||
this.tooltip = localize('manage', "Manage");
|
||||
|
@ -940,7 +1001,7 @@ export class ExtensionEditorManageExtensionAction extends ExtensionDropDownActio
|
|||
|
||||
override run(): Promise<any> {
|
||||
const actionGroups: IAction[][] = [];
|
||||
getContextMenuActions(this.extension, true, this.instantiationService).forEach(actions => actionGroups.push(actions));
|
||||
getContextMenuActions(this.extension, this.contextKeyService, this.instantiationService).forEach(actions => actionGroups.push(actions));
|
||||
actionGroups.forEach(group => group.forEach(extensionAction => {
|
||||
if (extensionAction instanceof ExtensionAction) {
|
||||
extensionAction.extension = this.extension;
|
||||
|
@ -976,6 +1037,58 @@ export class MenuItemExtensionAction extends ExtensionAction {
|
|||
}
|
||||
}
|
||||
|
||||
export class UsePreReleaseVersionAction extends ExtensionAction {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.usePreReleaseVersion';
|
||||
static readonly TITLE = { value: localize('use pre-release version', "Use Pre-release Version"), original: 'Use Pre-release Version' };
|
||||
|
||||
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`;
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super(UsePreReleaseVersionAction.ID, UsePreReleaseVersionAction.TITLE.value, UsePreReleaseVersionAction.Class);
|
||||
this.update();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this.enabled = !!this.extension && !this.extension.local?.isPreReleaseVersion && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed;
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
return this.commandService.executeCommand(UsePreReleaseVersionAction.ID, this.extension?.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class StopUsingPreReleaseVersionAction extends ExtensionAction {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.stopUsingPreReleaseVersion';
|
||||
static readonly TITLE = { value: localize('stop using pre-release version', "Stop Using Pre-release Version"), original: 'Stop Using Pre-release Version' };
|
||||
|
||||
private static readonly Class = `${ExtensionAction.LABEL_ACTION_CLASS} hide-when-disabled`;
|
||||
|
||||
constructor(
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
) {
|
||||
super(StopUsingPreReleaseVersionAction.ID, StopUsingPreReleaseVersionAction.TITLE.value, StopUsingPreReleaseVersionAction.Class);
|
||||
this.update();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this.enabled = !!this.extension && this.extension.state === ExtensionState.Installed && !!this.extension.local?.isPreReleaseVersion;
|
||||
}
|
||||
|
||||
override async run(): Promise<any> {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
return this.commandService.executeCommand(StopUsingPreReleaseVersionAction.ID, this.extension?.identifier.id);
|
||||
}
|
||||
}
|
||||
|
||||
export class InstallAnotherVersionAction extends ExtensionAction {
|
||||
|
||||
static readonly ID = 'workbench.extensions.action.install.anotherVersion';
|
||||
|
@ -1019,7 +1132,7 @@ export class InstallAnotherVersionAction extends ExtensionAction {
|
|||
|
||||
private async getVersionEntries(): Promise<(IQuickPickItem & { latest: boolean, id: string })[]> {
|
||||
const targetPlatform = await this.extension!.server!.extensionManagementService.getTargetPlatform();
|
||||
const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension!.gallery!, targetPlatform);
|
||||
const allVersions = await this.extensionGalleryService.getAllCompatibleVersions(this.extension!.gallery!, true, targetPlatform);
|
||||
return allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }));
|
||||
}
|
||||
}
|
||||
|
@ -1166,8 +1279,10 @@ export class EnableDropDownAction extends ActionWithDropDownAction {
|
|||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super('extensions.enable', localize('enableAction', "Enable"), [
|
||||
instantiationService.createInstance(EnableGloballyAction),
|
||||
instantiationService.createInstance(EnableForWorkspaceAction)
|
||||
[
|
||||
instantiationService.createInstance(EnableGloballyAction),
|
||||
instantiationService.createInstance(EnableForWorkspaceAction)
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1182,7 +1297,7 @@ export class DisableDropDownAction extends ActionWithDropDownAction {
|
|||
instantiationService.createInstance(DisableGloballyAction, []),
|
||||
instantiationService.createInstance(DisableForWorkspaceAction, [])
|
||||
];
|
||||
super('extensions.disable', localize('disableAction', "Disable"), actions);
|
||||
super('extensions.disable', localize('disableAction', "Disable"), [actions]);
|
||||
|
||||
const updateRunningExtensions = async () => {
|
||||
const runningExtensions = await extensionService.getExtensions();
|
||||
|
@ -2475,7 +2590,7 @@ export class InstallLocalExtensionsInRemoteAction extends AbstractInstallExtensi
|
|||
const targetPlatform = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform();
|
||||
await Promises.settled(localExtensionsToInstall.map(async extension => {
|
||||
if (this.extensionGalleryService.isEnabled()) {
|
||||
const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, targetPlatform);
|
||||
const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, !!extension.local?.isPreReleaseVersion, targetPlatform);
|
||||
if (gallery) {
|
||||
galleryExtensions.push(gallery);
|
||||
return;
|
||||
|
@ -2524,7 +2639,7 @@ export class InstallRemoteExtensionsInLocalAction extends AbstractInstallExtensi
|
|||
const targetPlatform = await this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform();
|
||||
await Promises.settled(extensions.map(async extension => {
|
||||
if (this.extensionGalleryService.isEnabled()) {
|
||||
const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, targetPlatform);
|
||||
const gallery = await this.extensionGalleryService.getCompatibleExtension(extension.identifier, !!extension.local?.isPreReleaseVersion, targetPlatform);
|
||||
if (gallery) {
|
||||
galleryExtensions.push(gallery);
|
||||
return;
|
||||
|
|
|
@ -25,6 +25,7 @@ export const remoteIcon = registerIcon('extensions-remote', Codicon.remote, loca
|
|||
export const installCountIcon = registerIcon('extensions-install-count', Codicon.cloudDownload, localize('installCountIcon', 'Icon shown along with the install count in the extensions view and editor.'));
|
||||
export const ratingIcon = registerIcon('extensions-rating', Codicon.star, localize('ratingIcon', 'Icon shown along with the rating in the extensions view and editor.'));
|
||||
export const verifiedPublisherIcon = registerIcon('extensions-verified-publisher', Codicon.verifiedFilled, localize('verifiedPublisher', 'Icon used for the verified extension publisher in the extensions view and editor.'));
|
||||
export const preReleaseIcon = registerIcon('extensions-pre-release', Codicon.circleFilled, localize('preReleaseIcon', 'Icon shown for extensions having pre-release versions in extensions view and editor.'));
|
||||
|
||||
export const starFullIcon = registerIcon('extensions-star-full', Codicon.starFull, localize('starFullIcon', 'Full star icon used for the rating in the extensions editor.'));
|
||||
export const starHalfIcon = registerIcon('extensions-star-half', Codicon.starHalf, localize('starHalfIcon', 'Half star icon used for the rating in the extensions editor.'));
|
||||
|
|
|
@ -15,7 +15,7 @@ import { Event } from 'vs/base/common/event';
|
|||
import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, PreReleaseIndicatorWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
|
||||
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
@ -78,6 +78,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
|||
|
||||
renderTemplate(root: HTMLElement): ITemplateData {
|
||||
const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, append(root, $('.extension-bookmark-container')));
|
||||
const preReleaseWidget = this.instantiationService.createInstance(PreReleaseBookmarkWidget, append(root, $('.extension-bookmark-container')));
|
||||
const element = append(root, $('.extension-list-item'));
|
||||
const iconContainer = append(element, $('.icon-container'));
|
||||
const icon = append(iconContainer, $<HTMLImageElement>('img.icon'));
|
||||
|
@ -89,6 +90,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
|||
const name = append(header, $('span.name'));
|
||||
const installCount = append(header, $('span.install-count'));
|
||||
const ratings = append(header, $('span.ratings'));
|
||||
const preRelease = append(header, $('span.pre-release'));
|
||||
const syncIgnore = append(header, $('span.sync-ignored'));
|
||||
const activationStatus = append(header, $('span.activation-status'));
|
||||
const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false);
|
||||
|
@ -131,10 +133,12 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
|
|||
|
||||
const widgets = [
|
||||
recommendationWidget,
|
||||
preReleaseWidget,
|
||||
iconRemoteBadgeWidget,
|
||||
extensionPackBadgeWidget,
|
||||
headerRemoteBadgeWidget,
|
||||
extensionHoverWidget,
|
||||
this.instantiationService.createInstance(PreReleaseIndicatorWidget, preRelease),
|
||||
this.instantiationService.createInstance(SyncIgnoredWidget, syncIgnore),
|
||||
this.instantiationService.createInstance(ExtensionActivationStatusWidget, activationStatus, true),
|
||||
this.instantiationService.createInstance(InstallCountWidget, installCount, true),
|
||||
|
|
|
@ -287,7 +287,7 @@ export class ExtensionsListView extends ViewPane {
|
|||
groups = await manageExtensionAction.getActionGroups(runningExtensions);
|
||||
|
||||
} else if (e.element) {
|
||||
groups = getContextMenuActions(e.element, false, this.instantiationService);
|
||||
groups = getContextMenuActions(e.element, this.contextKeyService, this.instantiationService);
|
||||
groups.forEach(group => group.forEach(extensionAction => {
|
||||
if (extensionAction instanceof ExtensionAction) {
|
||||
extensionAction.extension = e.element!;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { activationTimeIcon, errorIcon, infoIcon, installCountIcon, ratingIcon, remoteIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon, verifiedPublisherIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
import { activationTimeIcon, errorIcon, infoIcon, installCountIcon, preReleaseIcon, ratingIcon, remoteIcon, starEmptyIcon, starFullIcon, starHalfIcon, syncIgnoredIcon, verifiedPublisherIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
|
||||
import { registerColor, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
|
||||
|
@ -158,6 +158,29 @@ export class RatingsWidget extends ExtensionWidget {
|
|||
}
|
||||
}
|
||||
|
||||
export class PreReleaseIndicatorWidget extends ExtensionWidget {
|
||||
|
||||
constructor(private container: HTMLElement) {
|
||||
super();
|
||||
container.classList.add('extension-pre-release');
|
||||
this.render();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.container.innerText = '';
|
||||
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.extension.local?.isPreReleaseVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
append(this.container, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon)));
|
||||
}
|
||||
}
|
||||
|
||||
export class RecommendationWidget extends ExtensionWidget {
|
||||
|
||||
private element?: HTMLElement;
|
||||
|
@ -183,7 +206,7 @@ export class RecommendationWidget extends ExtensionWidget {
|
|||
|
||||
render(): void {
|
||||
this.clear();
|
||||
if (!this.extension) {
|
||||
if (!this.extension || this.extension.state === ExtensionState.Installed) {
|
||||
return;
|
||||
}
|
||||
const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason();
|
||||
|
@ -196,6 +219,41 @@ export class RecommendationWidget extends ExtensionWidget {
|
|||
|
||||
}
|
||||
|
||||
export class PreReleaseBookmarkWidget extends ExtensionWidget {
|
||||
|
||||
private element?: HTMLElement;
|
||||
private readonly disposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
) {
|
||||
super();
|
||||
this.render();
|
||||
this._register(toDisposable(() => this.clear()));
|
||||
}
|
||||
|
||||
private clear(): void {
|
||||
if (this.element) {
|
||||
this.parent.removeChild(this.element);
|
||||
}
|
||||
this.element = undefined;
|
||||
this.disposables.clear();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.clear();
|
||||
if (!this.extension) {
|
||||
return;
|
||||
}
|
||||
if (this.extension.hasPreReleaseVersion) {
|
||||
this.element = append(this.parent, $('div.extension-bookmark'));
|
||||
const preRelease = append(this.element, $('.pre-release'));
|
||||
append(preRelease, $('span' + ThemeIcon.asCSSSelector(preReleaseIcon)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class RemoteBadgeWidget extends ExtensionWidget {
|
||||
|
||||
private readonly remoteBadge = this._register(new MutableDisposable<RemoteBadge>());
|
||||
|
@ -304,26 +362,26 @@ export class ExtensionPackCountWidget extends ExtensionWidget {
|
|||
|
||||
export class SyncIgnoredWidget extends ExtensionWidget {
|
||||
|
||||
private element: HTMLElement;
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private readonly container: HTMLElement,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
|
||||
@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
|
||||
) {
|
||||
super();
|
||||
this.element = append(container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon)));
|
||||
this.element.title = localize('syncingore.label', "This extension is ignored during sync.");
|
||||
this.element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon));
|
||||
this.element.classList.add('hide');
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectedKeys.includes('settingsSync.ignoredExtensions'))(() => this.render()));
|
||||
this._register(userDataAutoSyncEnablementService.onDidChangeEnablement(() => this.update()));
|
||||
this.render();
|
||||
}
|
||||
|
||||
render(): void {
|
||||
this.element.classList.toggle('hide', !(this.extension && this.extension.state === ExtensionState.Installed && this.userDataAutoSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)));
|
||||
this.container.innerText = '';
|
||||
|
||||
if (this.extension && this.extension.state === ExtensionState.Installed && this.userDataAutoSyncEnablementService.isEnabled() && this.extensionsWorkbenchService.isExtensionIgnoredToSync(this.extension)) {
|
||||
const element = append(this.container, $('span.extension-sync-ignored' + ThemeIcon.asCSSSelector(syncIgnoredIcon)));
|
||||
element.title = localize('syncingore.label', "This extension is ignored during sync.");
|
||||
element.classList.add(...ThemeIcon.asClassNameArray(syncIgnoredIcon));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -416,6 +474,10 @@ export class ExtensionHoverWidget extends ExtensionWidget {
|
|||
const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true });
|
||||
|
||||
markdown.appendMarkdown(`**${this.extension.displayName}** _v${this.extension.version}_`);
|
||||
if (this.extension.state === ExtensionState.Installed && this.extension.local?.isPreReleaseVersion) {
|
||||
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
|
||||
markdown.appendMarkdown(` <span style="color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">$(${preReleaseIcon.id})</span>`);
|
||||
}
|
||||
markdown.appendText(`\n`);
|
||||
|
||||
if (this.extension.description) {
|
||||
|
@ -430,6 +492,12 @@ export class ExtensionHoverWidget extends ExtensionWidget {
|
|||
markdown.appendText(`\n`);
|
||||
}
|
||||
|
||||
const preReleaseMessage = this.getPreReleaseMessage(this.extension);
|
||||
if (preReleaseMessage) {
|
||||
markdown.appendMarkdown(preReleaseMessage);
|
||||
markdown.appendText(`\n`);
|
||||
}
|
||||
|
||||
const extensionRuntimeStatus = this.extensionsWorkbenchService.getExtensionStatus(this.extension);
|
||||
const extensionStatus = this.extensionStatusAction.status;
|
||||
const reloadRequiredMessage = this.reloadAction.enabled ? this.reloadAction.tooltip : '';
|
||||
|
@ -488,12 +556,28 @@ export class ExtensionHoverWidget extends ExtensionWidget {
|
|||
}
|
||||
|
||||
private getRecommendationMessage(extension: IExtension): string | undefined {
|
||||
const recommendation = this.extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()];
|
||||
if (recommendation?.reasonText) {
|
||||
const bgColor = this.themeService.getColorTheme().getColor(extensionButtonProminentBackground);
|
||||
return `<span style="color:${bgColor ? Color.Format.CSS.formatHex(bgColor) : '#ffffff'};">$(${starEmptyIcon.id})</span> ${recommendation.reasonText}`;
|
||||
if (extension.state === ExtensionState.Installed) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
const recommendation = this.extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()];
|
||||
if (!recommendation?.reasonText) {
|
||||
return undefined;
|
||||
}
|
||||
const bgColor = this.themeService.getColorTheme().getColor(extensionButtonProminentBackground);
|
||||
return `<span style="color:${bgColor ? Color.Format.CSS.formatHex(bgColor) : '#ffffff'};">$(${starEmptyIcon.id})</span> ${recommendation.reasonText}`;
|
||||
}
|
||||
|
||||
private getPreReleaseMessage(extension: IExtension): string | undefined {
|
||||
if (!extension.hasPreReleaseVersion) {
|
||||
return undefined;
|
||||
}
|
||||
if (extension.state === ExtensionState.Installed && extension.local?.isPreReleaseVersion) {
|
||||
return undefined;
|
||||
}
|
||||
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
|
||||
const preReleaseVersionLink = `[${localize('Show prerelease version', "Pre-release version")}](${URI.parse(`command:workbench.extensions.action.showPreReleaseVersion?${encodeURIComponent(JSON.stringify([extension.identifier.id]))}`)})`;
|
||||
const message = localize('has prerelease', "There is a new {0} of this extension available in the Marketplace.", preReleaseVersionLink);
|
||||
return `<span style="color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">$(${preReleaseIcon.id})</span> ${message}.`;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -501,6 +585,7 @@ export class ExtensionHoverWidget extends ExtensionWidget {
|
|||
// Rating icon
|
||||
export const extensionRatingIconColor = registerColor('extensionIcon.starForeground', { light: '#DF6100', dark: '#FF8E00', hc: '#FF8E00' }, localize('extensionIconStarForeground', "The icon color for extension ratings."), true);
|
||||
export const extensionVerifiedPublisherIconColor = registerColor('extensionIcon.verifiedForeground', { dark: textLinkForeground, light: textLinkForeground, hc: textLinkForeground }, localize('extensionIconVerifiedForeground', "The icon color for extension verified publisher."), true);
|
||||
export const extensionPreReleaseIconColor = registerColor('extensionIcon.preReleaseForeground', { dark: '#1d9271', light: '#1d9271', hc: '#1d9271' }, localize('extensionPreReleaseForeground', "The icon color for pre-release extension."), true);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const extensionRatingIcon = theme.getColor(extensionRatingIconColor);
|
||||
|
@ -523,4 +608,11 @@ registerThemingParticipant((theme, collector) => {
|
|||
if (extensionVerifiedPublisherIcon) {
|
||||
collector.addRule(`${ThemeIcon.asCSSSelector(verifiedPublisherIcon)} { color: ${extensionVerifiedPublisherIcon}; }`);
|
||||
}
|
||||
|
||||
const extensionPreReleaseIcon = theme.getColor(extensionPreReleaseIconColor);
|
||||
if (extensionPreReleaseIcon) {
|
||||
collector.addRule(`.extension-bookmark .pre-release { border-top-color: ${extensionPreReleaseIcon}; }`);
|
||||
collector.addRule(`.extension-bookmark .pre-release ${ThemeIcon.asCSSSelector(preReleaseIcon)} { color: #ffffff; }`);
|
||||
collector.addRule(`${ThemeIcon.asCSSSelector(preReleaseIcon)} { color: ${extensionPreReleaseIcon}; }`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
|
|||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import {
|
||||
IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
|
||||
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult
|
||||
InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, DefaultIconPath, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, isIExtensionIdentifier
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet, groupByExtension, ExtensionIdentifierWithVersion, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
|
@ -22,10 +22,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtension, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, HasOutdatedExtensionsContext } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url';
|
||||
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ExtensionsInput, IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
|
@ -200,7 +200,7 @@ class Extension implements IExtension {
|
|||
}
|
||||
|
||||
get outdated(): boolean {
|
||||
return !!this.gallery && this.type === ExtensionType.User && semver.gt(this.latestVersion, this.version);
|
||||
return !!this.gallery && this.type === ExtensionType.User && semver.gt(this.latestVersion, this.version) && this.local?.isPreReleaseVersion === this.gallery?.properties.isPreReleaseVersion;
|
||||
}
|
||||
|
||||
get telemetryData(): any {
|
||||
|
@ -217,20 +217,40 @@ class Extension implements IExtension {
|
|||
return this.gallery ? this.gallery.preview : false;
|
||||
}
|
||||
|
||||
getManifest(token: CancellationToken): Promise<IExtensionManifest | null> {
|
||||
if (this.local && !this.outdated) {
|
||||
return Promise.resolve(this.local.manifest);
|
||||
get hasPreReleaseVersion(): boolean {
|
||||
return !!this.gallery?.hasPreReleaseVersion;
|
||||
}
|
||||
|
||||
private getLocal(preRelease: boolean): ILocalExtension | undefined {
|
||||
return this.local && !this.outdated && this.local.isPreReleaseVersion === preRelease ? this.local : undefined;
|
||||
}
|
||||
|
||||
private async getGallery(preRelease: boolean, token: CancellationToken): Promise<IGalleryExtension | undefined> {
|
||||
if (this.gallery) {
|
||||
if (preRelease === this.gallery.properties.isPreReleaseVersion) {
|
||||
return this.gallery;
|
||||
}
|
||||
return (await this.galleryService.getExtensions([this.gallery.identifier], preRelease, token))[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async getManifest(preRelease: boolean, token: CancellationToken): Promise<IExtensionManifest | null> {
|
||||
const local = this.getLocal(preRelease);
|
||||
if (local) {
|
||||
return local.manifest;
|
||||
}
|
||||
|
||||
if (this.gallery) {
|
||||
if (this.gallery.assets.manifest) {
|
||||
return this.galleryService.getManifest(this.gallery, token);
|
||||
const gallery = await this.getGallery(preRelease, token);
|
||||
if (gallery) {
|
||||
if (gallery.assets.manifest) {
|
||||
return this.galleryService.getManifest(gallery, token);
|
||||
}
|
||||
this.logService.error(nls.localize('Manifest is not found', "Manifest is not found"), this.identifier.id);
|
||||
return Promise.resolve(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
hasReadme(): boolean {
|
||||
|
@ -245,14 +265,17 @@ class Extension implements IExtension {
|
|||
return this.type === ExtensionType.System;
|
||||
}
|
||||
|
||||
getReadme(token: CancellationToken): Promise<string> {
|
||||
if (this.local && this.local.readmeUrl && !this.outdated) {
|
||||
return this.fileService.readFile(this.local.readmeUrl).then(content => content.value.toString());
|
||||
async getReadme(preRelease: boolean, token: CancellationToken): Promise<string> {
|
||||
const local = this.getLocal(preRelease);
|
||||
if (local?.readmeUrl) {
|
||||
const content = await this.fileService.readFile(local.readmeUrl);
|
||||
return content.value.toString();
|
||||
}
|
||||
|
||||
if (this.gallery) {
|
||||
if (this.gallery.assets.readme) {
|
||||
return this.galleryService.getReadme(this.gallery, token);
|
||||
const gallery = await this.getGallery(preRelease, token);
|
||||
if (gallery) {
|
||||
if (gallery.assets.readme) {
|
||||
return this.galleryService.getReadme(gallery, token);
|
||||
}
|
||||
this.telemetryService.publicLog('extensions:NotFoundReadMe', this.telemetryData);
|
||||
}
|
||||
|
@ -280,14 +303,16 @@ ${this.description}
|
|||
return this.type === ExtensionType.System;
|
||||
}
|
||||
|
||||
getChangelog(token: CancellationToken): Promise<string> {
|
||||
|
||||
if (this.local && this.local.changelogUrl && !this.outdated) {
|
||||
return this.fileService.readFile(this.local.changelogUrl).then(content => content.value.toString());
|
||||
async getChangelog(preRelease: boolean, token: CancellationToken): Promise<string> {
|
||||
const local = this.getLocal(preRelease);
|
||||
if (local?.changelogUrl) {
|
||||
const content = await this.fileService.readFile(local.changelogUrl);
|
||||
return content.value.toString();
|
||||
}
|
||||
|
||||
if (this.gallery && this.gallery.assets.changelog) {
|
||||
return this.galleryService.getChangelog(this.gallery, token);
|
||||
const gallery = await this.getGallery(preRelease, token);
|
||||
if (gallery?.assets.changelog) {
|
||||
return this.galleryService.getChangelog(gallery, token);
|
||||
}
|
||||
|
||||
if (this.type === ExtensionType.System) {
|
||||
|
@ -401,9 +426,8 @@ class Extensions extends Disposable {
|
|||
if (maliciousExtensionSet.has(extension.identifier.id)) {
|
||||
extension.isMalicious = true;
|
||||
}
|
||||
// Loading the compatible version only there is an engine property
|
||||
// Otherwise falling back to old way so that we will not make many roundtrips
|
||||
const compatible = gallery.properties.engine ? await this.galleryService.getCompatibleExtension(gallery, await this.server.extensionManagementService.getTargetPlatform()) : gallery;
|
||||
|
||||
const compatible = await this.getCompatibleExtension(gallery, !!extension.local?.isPreReleaseVersion);
|
||||
if (!compatible) {
|
||||
return false;
|
||||
}
|
||||
|
@ -418,6 +442,17 @@ class Extensions extends Disposable {
|
|||
return false;
|
||||
}
|
||||
|
||||
private async getCompatibleExtension(extensionOrIdentifier: IGalleryExtension | IExtensionIdentifier, includePreRelease: boolean): Promise<IGalleryExtension | null> {
|
||||
if (isIExtensionIdentifier(extensionOrIdentifier)) {
|
||||
return this.galleryService.getCompatibleExtension(extensionOrIdentifier, includePreRelease, await this.server.extensionManagementService.getTargetPlatform());
|
||||
}
|
||||
const extension = extensionOrIdentifier;
|
||||
if (includePreRelease && extension.hasPreReleaseVersion && !extension.properties.isPreReleaseVersion) {
|
||||
return this.getCompatibleExtension(extension.identifier, includePreRelease);
|
||||
}
|
||||
return this.galleryService.getCompatibleExtension(extension, includePreRelease, await this.server.extensionManagementService.getTargetPlatform());
|
||||
}
|
||||
|
||||
canInstall(galleryExtension: IGalleryExtension): Promise<boolean> {
|
||||
return this.server.extensionManagementService.canInstall(galleryExtension);
|
||||
}
|
||||
|
@ -426,7 +461,7 @@ class Extensions extends Disposable {
|
|||
if (!this.galleryService.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
const compatible = await this.galleryService.getCompatibleExtension(extension.identifier, await this.server.extensionManagementService.getTargetPlatform());
|
||||
const compatible = await this.getCompatibleExtension(extension.identifier, !!extension.local?.isPreReleaseVersion);
|
||||
if (compatible) {
|
||||
extension.gallery = compatible;
|
||||
this._onChange.fire({ extension });
|
||||
|
@ -706,7 +741,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
const options: IQueryOptions = CancellationToken.isCancellationToken(arg1) ? {} : arg1;
|
||||
const token: CancellationToken = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2;
|
||||
options.text = options.text ? this.resolveQueryText(options.text) : options.text;
|
||||
|
||||
|
||||
const report = await this.extensionManagementService.getExtensionsReport();
|
||||
const maliciousSet = getMaliciousExtensionsSet(report);
|
||||
try {
|
||||
|
@ -743,8 +778,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
return text.substr(0, 350);
|
||||
}
|
||||
|
||||
async open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean, tab?: ExtensionEditorTab }): Promise<void> {
|
||||
const editor = await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), { preserveFocus: options?.preserveFocus, pinned: options?.pinned }, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
async open(extension: IExtension, options?: IExtensionEditorOptions): Promise<void> {
|
||||
const editor = await this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension), options, options?.sideByside ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
if (options?.tab && editor instanceof ExtensionEditor) {
|
||||
await editor.openTab(options.tab);
|
||||
}
|
||||
|
@ -977,7 +1012,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
|
|||
(this.getAutoUpdateValue() === true || (e.local && this.extensionEnablementService.isEnabled(e.local)))
|
||||
);
|
||||
|
||||
return Promises.settled(toUpdate.map(e => this.install(e)));
|
||||
return Promises.settled(toUpdate.map(e => this.install(e, e.local?.hadPreReleaseVersion ? { installPreReleaseVersion: true } : undefined)));
|
||||
}
|
||||
|
||||
async canInstall(extension: IExtension): Promise<boolean> {
|
||||
|
|
|
@ -89,15 +89,23 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header > .activation-status:not(:empty),
|
||||
.extension-list-item > .details > .header-container > .header > .install-count:not(:empty) {
|
||||
font-size: 80%;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header > .activation-status:not(:empty) {
|
||||
font-size: 80%;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header > .activation-status:not(:empty) .codicon {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .codicon {
|
||||
font-size: 120%;
|
||||
margin-right: 2px;
|
||||
margin-right: 3px;
|
||||
-webkit-mask: inherit;
|
||||
}
|
||||
|
||||
|
@ -130,9 +138,12 @@
|
|||
color: currentColor;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .pre-release {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .sync-ignored {
|
||||
display: flex;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.extension-list-item > .details > .header-container > .header .sync-ignored > .codicon {
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
.monaco-action-bar .action-item.disabled .action-label.extension-action.undo-ignore,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing),
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling),
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.hide-when-disabled,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.theme,
|
||||
.monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync,
|
||||
|
|
|
@ -82,6 +82,11 @@
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .title > .pre-release {
|
||||
display: flex;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.extension-editor > .header > .details > .title > .builtin {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
|
|
|
@ -37,14 +37,16 @@
|
|||
width: 20px;
|
||||
}
|
||||
|
||||
.extension-bookmark > .recommendation {
|
||||
.extension-bookmark > .recommendation,
|
||||
.extension-bookmark > .pre-release {
|
||||
border-right: 20px solid transparent;
|
||||
border-top: 20px solid;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.extension-bookmark > .recommendation > .codicon {
|
||||
.extension-bookmark > .recommendation > .codicon,
|
||||
.extension-bookmark > .pre-release > .codicon {
|
||||
position: absolute;
|
||||
bottom: 9px;
|
||||
left: 1px;
|
||||
|
|
|
@ -16,6 +16,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { IView, IViewPaneContainer } from 'vs/workbench/common/views';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.extensions';
|
||||
|
||||
|
@ -48,6 +49,7 @@ export interface IExtension {
|
|||
readonly publisherDomain?: { link: string, verified: boolean };
|
||||
readonly version: string;
|
||||
readonly latestVersion: string;
|
||||
readonly hasPreReleaseVersion: boolean;
|
||||
readonly description: string;
|
||||
readonly url?: string;
|
||||
readonly repository?: string;
|
||||
|
@ -65,11 +67,11 @@ export interface IExtension {
|
|||
readonly extensionPack: string[];
|
||||
readonly telemetryData: any;
|
||||
readonly preview: boolean;
|
||||
getManifest(token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
getReadme(token: CancellationToken): Promise<string>;
|
||||
getManifest(preRelease: boolean, token: CancellationToken): Promise<IExtensionManifest | null>;
|
||||
hasReadme(): boolean;
|
||||
getChangelog(token: CancellationToken): Promise<string>;
|
||||
getReadme(preRelease: boolean, token: CancellationToken): Promise<string>;
|
||||
hasChangelog(): boolean;
|
||||
getChangelog(preRelease: boolean, token: CancellationToken): Promise<string>;
|
||||
readonly server?: IExtensionManagementServer;
|
||||
readonly local?: ILocalExtension;
|
||||
gallery?: IGalleryExtension;
|
||||
|
@ -96,7 +98,7 @@ export interface IExtensionsWorkbenchService {
|
|||
installVersion(extension: IExtension, version: string): Promise<IExtension>;
|
||||
reinstall(extension: IExtension): Promise<IExtension>;
|
||||
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
|
||||
open(extension: IExtension, options?: { sideByside?: boolean, preserveFocus?: boolean, pinned?: boolean, tab?: string }): Promise<void>;
|
||||
open(extension: IExtension, options?: IExtensionEditorOptions): Promise<void>;
|
||||
checkForUpdates(): Promise<void>;
|
||||
getExtensionStatus(extension: IExtension): IExtensionsStatus | undefined;
|
||||
|
||||
|
|
|
@ -8,9 +8,16 @@ import { URI } from 'vs/base/common/uri';
|
|||
import { localize } from 'vs/nls';
|
||||
import { EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor';
|
||||
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
|
||||
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { ExtensionEditorTab, IExtension, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
|
||||
export interface IExtensionEditorOptions extends IEditorOptions {
|
||||
showPreReleaseVersion?: boolean;
|
||||
tab?: ExtensionEditorTab;
|
||||
sideByside?: boolean;
|
||||
}
|
||||
|
||||
export class ExtensionsInput extends EditorInput {
|
||||
|
||||
|
|
|
@ -219,6 +219,7 @@ suite('ExtensionRecommendationsService Test', () => {
|
|||
async getInstalled() { return []; },
|
||||
async canInstall() { return true; },
|
||||
async getExtensionsReport() { return []; },
|
||||
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
|
||||
});
|
||||
instantiationService.stub(IExtensionService, <Partial<IExtensionService>>{
|
||||
async whenInstalledExtensionsRegistered() { return true; }
|
||||
|
|
|
@ -108,7 +108,8 @@ async function setupTest() {
|
|||
local.publisherId = metadata.publisherId;
|
||||
return local;
|
||||
},
|
||||
async canInstall() { return true; }
|
||||
async canInstall() { return true; },
|
||||
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
|
||||
});
|
||||
|
||||
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
|
||||
|
@ -153,14 +154,14 @@ suite('ExtensionsActions', () => {
|
|||
teardown(() => disposables.dispose());
|
||||
|
||||
test('Install action is disabled when there is no extension', () => {
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, false);
|
||||
|
||||
assert.ok(!testObject.enabled);
|
||||
});
|
||||
|
||||
test('Test Install action when state is installed', () => {
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, false);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
@ -196,7 +197,7 @@ suite('ExtensionsActions', () => {
|
|||
|
||||
test('Test Install action when state is uninstalled', async () => {
|
||||
const workbenchService = instantiationService.get(IExtensionsWorkbenchService);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, false);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const gallery = aGalleryExtension('a');
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
|
@ -209,7 +210,7 @@ suite('ExtensionsActions', () => {
|
|||
});
|
||||
|
||||
test('Test Install action when extension is system action', () => {
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, false);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a', {}, { type: ExtensionType.System });
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
@ -224,7 +225,7 @@ suite('ExtensionsActions', () => {
|
|||
});
|
||||
|
||||
test('Test Install action when extension doesnot has gallery', () => {
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction);
|
||||
const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.InstallAction, false);
|
||||
instantiationService.createInstance(ExtensionContainers, [testObject]);
|
||||
const local = aLocalExtension('a');
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
|
||||
|
@ -386,7 +387,9 @@ suite('ExtensionsActions', () => {
|
|||
return workbenchService.queryLocal()
|
||||
.then(async extensions => {
|
||||
testObject.extension = extensions[0];
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' })));
|
||||
const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' });
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery);
|
||||
assert.ok(!testObject.enabled);
|
||||
return new Promise<void>(c => {
|
||||
testObject.onDidChange(() => {
|
||||
|
@ -409,6 +412,7 @@ suite('ExtensionsActions', () => {
|
|||
testObject.extension = extensions[0];
|
||||
const gallery = aGalleryExtension('a', { identifier: local.identifier, version: '1.0.1' });
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery);
|
||||
await instantiationService.get(IExtensionsWorkbenchService).queryGallery(CancellationToken.None);
|
||||
const promise = Event.toPromise(testObject.onDidChange);
|
||||
installEvent.fire({ identifier: local.identifier, source: gallery });
|
||||
|
@ -2408,7 +2412,8 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IE
|
|||
local.publisherDisplayName = metadata.publisherDisplayName;
|
||||
local.publisherId = metadata.publisherId;
|
||||
return local;
|
||||
}
|
||||
},
|
||||
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ suite('ExtensionsListView Tests', () => {
|
|||
async getInstalled() { return []; },
|
||||
async canInstall() { return true; },
|
||||
async getExtensionsReport() { return []; },
|
||||
async getTargetPlatform() { return getTargetPlatform(platform, arch); }
|
||||
});
|
||||
instantiationService.stub(IRemoteAgentService, RemoteAgentService);
|
||||
instantiationService.stub(IContextKeyService, new MockContextKeyService());
|
||||
|
|
|
@ -101,7 +101,8 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
|||
local.publisherId = metadata.publisherId;
|
||||
return local;
|
||||
},
|
||||
async canInstall() { return true; }
|
||||
async canInstall() { return true; },
|
||||
getTargetPlatform: async () => getTargetPlatform(platform, arch)
|
||||
});
|
||||
|
||||
instantiationService.stub(IExtensionManagementServerService, anExtensionManagementServerService({
|
||||
|
@ -306,6 +307,7 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
|||
});
|
||||
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local1, local2]);
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery1));
|
||||
instantiationService.stubPromise(IExtensionGalleryService, 'getCompatibleExtension', gallery1);
|
||||
testObject = await aWorkbenchService();
|
||||
await testObject.queryLocal();
|
||||
|
||||
|
@ -1511,7 +1513,8 @@ suite('ExtensionsWorkbenchServiceTest', () => {
|
|||
local.publisherDisplayName = metadata.publisherDisplayName;
|
||||
local.publisherId = metadata.publisherId;
|
||||
return local;
|
||||
}
|
||||
},
|
||||
getTargetPlatform: async () => getTargetPlatform(platform, arch)
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
|
@ -10,13 +10,11 @@ import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionM
|
|||
import { IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import { AbstractExtensionManagementService, AbstractExtensionTask, IInstallExtensionTask, Metadata, IUninstallExtensionTask, UninstallExtensionTaskOptions } from 'vs/platform/extensionManagement/common/abstractExtensionManagementService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
type Metadata = Partial<IGalleryMetadata & { isMachineScoped: boolean; }>;
|
||||
|
||||
export class WebExtensionManagementService extends AbstractExtensionManagementService implements IExtensionManagementService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
@ -68,8 +66,8 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe
|
|||
return this.installExtension(manifest, location, options);
|
||||
}
|
||||
|
||||
protected override async getCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean): Promise<IGalleryExtension | null> {
|
||||
const compatibleExtension = await super.getCompatibleVersion(extension, fetchCompatibleVersion);
|
||||
protected override async getCompatibleVersion(extension: IGalleryExtension, fetchCompatibleVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> {
|
||||
const compatibleExtension = await super.getCompatibleVersion(extension, fetchCompatibleVersion, includePreRelease);
|
||||
if (compatibleExtension) {
|
||||
return compatibleExtension;
|
||||
}
|
||||
|
@ -110,6 +108,9 @@ function toLocalExtension(extension: IScannedExtension): ILocalExtension {
|
|||
isMachineScoped: !!metadata.isMachineScoped,
|
||||
publisherId: metadata.publisherId || null,
|
||||
publisherDisplayName: metadata.publisherDisplayName || null,
|
||||
installedTimestamp: metadata.installedTimestamp,
|
||||
isPreReleaseVersion: !!metadata.isPreReleaseVersion,
|
||||
hadPreReleaseVersion: !!metadata.hadPreReleaseVersion
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -149,6 +150,9 @@ class InstallExtensionTask extends AbstractExtensionTask<ILocalExtension> implem
|
|||
metadata.id = this.extension.identifier.uuid;
|
||||
metadata.publisherDisplayName = this.extension.publisherDisplayName;
|
||||
metadata.publisherId = this.extension.publisherId;
|
||||
metadata.installedTimestamp = Date.now();
|
||||
metadata.isPreReleaseVersion = this.extension.properties.isPreReleaseVersion;
|
||||
metadata.hadPreReleaseVersion = this.extension.properties.isPreReleaseVersion || metadata.hadPreReleaseVersion;
|
||||
}
|
||||
|
||||
const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata)
|
||||
|
|
|
@ -76,7 +76,7 @@ export class NativeRemoteExtensionManagementService extends ExtensionManagementC
|
|||
|
||||
private async downloadAndInstall(extension: IGalleryExtension, installOptions: InstallOptions): Promise<ILocalExtension> {
|
||||
this.logService.info(`Downloading the '${extension.identifier.id}' extension locally and install`);
|
||||
const compatible = await this.checkAndGetCompatible(extension);
|
||||
const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);
|
||||
installOptions = { ...installOptions, donotIncludePackAndDependencies: true };
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
const workspaceExtensions = await this.getAllWorkspaceDependenciesAndPackedExtensions(compatible, CancellationToken.None);
|
||||
|
@ -90,7 +90,7 @@ export class NativeRemoteExtensionManagementService extends ExtensionManagementC
|
|||
}
|
||||
|
||||
private async downloadCompatibleAndInstall(extension: IGalleryExtension, installed: ILocalExtension[], installOptions: InstallOptions): Promise<ILocalExtension> {
|
||||
const compatible = await this.checkAndGetCompatible(extension);
|
||||
const compatible = await this.checkAndGetCompatible(extension, !!installOptions.installPreReleaseVersion);
|
||||
const location = joinPath(this.environmentService.tmpDir, generateUuid());
|
||||
this.logService.info('Downloaded extension:', compatible.identifier.id, location.path);
|
||||
await this.galleryService.download(compatible, location, installed.filter(i => areSameExtensions(i.identifier, compatible.identifier))[0] ? InstallOperation.Update : InstallOperation.Install);
|
||||
|
@ -99,11 +99,16 @@ export class NativeRemoteExtensionManagementService extends ExtensionManagementC
|
|||
return local;
|
||||
}
|
||||
|
||||
private async checkAndGetCompatible(extension: IGalleryExtension): Promise<IGalleryExtension> {
|
||||
const compatible = await this.galleryService.getCompatibleExtension(extension, await this.getTargetPlatform());
|
||||
if (!compatible) {
|
||||
throw new ExtensionManagementError(localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
private async checkAndGetCompatible(extension: IGalleryExtension, includePreRelease: boolean): Promise<IGalleryExtension> {
|
||||
const compatible = await this.galleryService.getCompatibleExtension(extension, includePreRelease, await this.getTargetPlatform());
|
||||
if (compatible) {
|
||||
if (includePreRelease && !compatible.properties.isPreReleaseVersion && extension.hasPreReleaseVersion) {
|
||||
throw new ExtensionManagementError(localize('notFoundCompatiblePrereleaseDependency', "Can't install pre-release version of '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
} else {
|
||||
throw new ExtensionManagementError(localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible);
|
||||
}
|
||||
|
||||
return compatible;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue