/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigBasedExtensionTip as IRawConfigBasedExtensionTip } from 'vs/base/common/product'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { getDomainsOfRemotes } from 'vs/platform/extensionManagement/common/configRemotes'; import { IConfigBasedExtensionTip, IExecutableBasedExtensionTip, IExtensionTipsService, IWorkspaceTips } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { asJson, IRequestService } from 'vs/platform/request/common/request'; export class ExtensionTipsService extends Disposable implements IExtensionTipsService { _serviceBrand: any; private readonly allConfigBasedTips: Map = new Map(); constructor( @IFileService protected readonly fileService: IFileService, @IProductService private readonly productService: IProductService, @IRequestService private readonly requestService: IRequestService, @ILogService private readonly logService: ILogService, ) { super(); if (this.productService.configBasedExtensionTips) { Object.entries(this.productService.configBasedExtensionTips).forEach(([, value]) => this.allConfigBasedTips.set(value.configPath, value)); } } getConfigBasedTips(folder: URI): Promise { return this.getValidConfigBasedTips(folder); } getAllWorkspacesTips(): Promise { return this.fetchWorkspacesTips(); } async getImportantExecutableBasedTips(): Promise { return []; } async getOtherExecutableBasedTips(): Promise { return []; } private async getValidConfigBasedTips(folder: URI): Promise { const result: IConfigBasedExtensionTip[] = []; for (const [configPath, tip] of this.allConfigBasedTips) { if (tip.configScheme && tip.configScheme !== folder.scheme) { continue; } try { const content = await this.fileService.readFile(joinPath(folder, configPath)); const recommendationByRemote: Map = new Map(); Object.entries(tip.recommendations).forEach(([key, value]) => { if (isNonEmptyArray(value.remotes)) { for (const remote of value.remotes) { recommendationByRemote.set(remote, { extensionId: key, extensionName: value.name, configName: tip.configName, important: !!value.important, isExtensionPack: !!value.isExtensionPack, whenNotInstalled: value.whenNotInstalled }); } } else { result.push({ extensionId: key, extensionName: value.name, configName: tip.configName, important: !!value.important, isExtensionPack: !!value.isExtensionPack, whenNotInstalled: value.whenNotInstalled }); } }); const domains = getDomainsOfRemotes(content.value.toString(), [...recommendationByRemote.keys()]); for (const domain of domains) { const remote = recommendationByRemote.get(domain); if (remote) { result.push(remote); } } } catch (error) { /* Ignore */ } } return result; } private async fetchWorkspacesTips(): Promise { if (!this.productService.extensionsGallery?.recommendationsUrl) { return []; } try { const context = await this.requestService.request({ type: 'GET', url: this.productService.extensionsGallery?.recommendationsUrl }, CancellationToken.None); if (context.res.statusCode !== 200) { return []; } const result = await asJson<{ workspaceRecommendations?: IWorkspaceTips[] }>(context); if (!result) { return []; } return result.workspaceRecommendations || []; } catch (error) { this.logService.error(error); return []; } } }