Path: blob/main/src/vs/platform/extensions/common/extensions.ts
5240 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import Severity from '../../../base/common/severity.js';6import * as strings from '../../../base/common/strings.js';7import { URI } from '../../../base/common/uri.js';8import { ILocalizedString } from '../../action/common/action.js';9import { ExtensionKind } from '../../environment/common/environment.js';10import { createDecorator } from '../../instantiation/common/instantiation.js';11import { getRemoteName } from '../../remote/common/remoteHosts.js';1213export const USER_MANIFEST_CACHE_FILE = 'extensions.user.cache';14export const BUILTIN_MANIFEST_CACHE_FILE = 'extensions.builtin.cache';15export const UNDEFINED_PUBLISHER = 'undefined_publisher';1617export interface ICommand {18command: string;19title: string | ILocalizedString;20category?: string | ILocalizedString;21}2223export interface IDebugger {24label?: string;25type: string;26runtime?: string;27}2829export interface IGrammar {30language?: string;31}3233export interface IJSONValidation {34fileMatch: string | string[];35url: string;36}3738export interface IKeyBinding {39command: string;40key: string;41when?: string;42mac?: string;43linux?: string;44win?: string;45}4647export interface ILanguage {48id: string;49extensions: string[];50aliases: string[];51}5253export interface IMenu {54command: string;55alt?: string;56when?: string;57group?: string;58}5960export interface ISnippet {61language: string;62}6364export interface ITheme {65label: string;66}6768export interface IViewContainer {69id: string;70title: string;71}7273export interface IView {74id: string;75name: string;76}7778export interface IColor {79id: string;80description: string;81defaults: { light: string; dark: string; highContrast: string };82}8384interface IWebviewEditor {85readonly viewType: string;86readonly priority: string;87readonly selector: readonly {88readonly filenamePattern?: string;89}[];90}9192export interface ICodeActionContributionAction {93readonly kind: string;94readonly title: string;95readonly description?: string;96}9798export interface ICodeActionContribution {99readonly languages: readonly string[];100readonly actions: readonly ICodeActionContributionAction[];101}102103export interface IAuthenticationContribution {104readonly id: string;105readonly label: string;106readonly authorizationServerGlobs?: string[];107}108109export interface IWalkthroughStep {110readonly id: string;111readonly title: string;112readonly description: string | undefined;113readonly media:114| { image: string | { dark: string; light: string; hc: string }; altText: string; markdown?: never; svg?: never; video?: never }115| { markdown: string; image?: never; svg?: never; video?: never }116| { svg: string; altText: string; markdown?: never; image?: never; video?: never }117| { video: string | { dark: string; light: string; hc: string }; poster: string | { dark: string; light: string; hc: string }; altText: string; markdown?: never; image?: never; svg?: never };118readonly completionEvents?: string[];119/** @deprecated use `completionEvents: 'onCommand:...'` */120readonly doneOn?: { command: string };121readonly when?: string;122}123124export interface IWalkthrough {125readonly id: string;126readonly title: string;127readonly icon?: string;128readonly description: string;129readonly steps: IWalkthroughStep[];130readonly featuredFor: string[] | undefined;131readonly when?: string;132}133134export interface IStartEntry {135readonly title: string;136readonly description: string;137readonly command: string;138readonly when?: string;139readonly category: 'file' | 'folder' | 'notebook';140}141142export interface INotebookEntry {143readonly type: string;144readonly displayName: string;145}146147export interface INotebookRendererContribution {148readonly id: string;149readonly displayName: string;150readonly mimeTypes: string[];151}152153export interface IDebugVisualizationContribution {154readonly id: string;155readonly when: string;156}157158export interface ITranslation {159id: string;160path: string;161}162163export interface ILocalizationContribution {164languageId: string;165languageName?: string;166localizedLanguageName?: string;167translations: ITranslation[];168minimalTranslations?: { [key: string]: string };169}170171export interface IChatParticipantContribution {172id: string;173name: string;174fullName: string;175description?: string;176isDefault?: boolean;177commands?: { name: string }[];178}179180export interface IToolContribution {181name: string;182displayName: string;183modelDescription: string;184userDescription?: string;185}186187export interface IToolSetContribution {188name: string;189referenceName: string;190description: string;191icon?: string;192tools: string[];193}194195export interface IMcpCollectionContribution {196readonly id: string;197readonly label: string;198readonly when?: string;199}200201export interface IChatFileContribution {202readonly path: string;203readonly name?: string;204readonly description?: string;205}206207export interface IExtensionContributions {208commands?: ICommand[];209configuration?: any;210configurationDefaults?: any;211debuggers?: IDebugger[];212grammars?: IGrammar[];213jsonValidation?: IJSONValidation[];214keybindings?: IKeyBinding[];215languages?: ILanguage[];216menus?: { [context: string]: IMenu[] };217snippets?: ISnippet[];218themes?: ITheme[];219iconThemes?: ITheme[];220productIconThemes?: ITheme[];221viewsContainers?: { [location: string]: IViewContainer[] };222views?: { [location: string]: IView[] };223colors?: IColor[];224localizations?: ILocalizationContribution[];225readonly customEditors?: readonly IWebviewEditor[];226readonly codeActions?: readonly ICodeActionContribution[];227authentication?: IAuthenticationContribution[];228walkthroughs?: IWalkthrough[];229startEntries?: IStartEntry[];230readonly notebooks?: INotebookEntry[];231readonly notebookRenderer?: INotebookRendererContribution[];232readonly debugVisualizers?: IDebugVisualizationContribution[];233readonly chatParticipants?: ReadonlyArray<IChatParticipantContribution>;234readonly chatPromptFiles?: ReadonlyArray<IChatFileContribution>;235readonly chatInstructions?: ReadonlyArray<IChatFileContribution>;236readonly chatAgents?: ReadonlyArray<IChatFileContribution>;237readonly chatSkills?: ReadonlyArray<IChatFileContribution>;238readonly languageModelTools?: ReadonlyArray<IToolContribution>;239readonly languageModelToolSets?: ReadonlyArray<IToolSetContribution>;240readonly mcpServerDefinitionProviders?: ReadonlyArray<IMcpCollectionContribution>;241}242243export interface IExtensionCapabilities {244readonly virtualWorkspaces?: ExtensionVirtualWorkspaceSupport;245readonly untrustedWorkspaces?: ExtensionUntrustedWorkspaceSupport;246}247248249export const ALL_EXTENSION_KINDS: readonly ExtensionKind[] = ['ui', 'workspace', 'web'];250251export type LimitedWorkspaceSupportType = 'limited';252export type ExtensionUntrustedWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;253export type ExtensionUntrustedWorkspaceSupport = { supported: true } | { supported: false; description: string } | { supported: LimitedWorkspaceSupportType; description: string; restrictedConfigurations?: string[] };254255export type ExtensionVirtualWorkspaceSupportType = boolean | LimitedWorkspaceSupportType;256export type ExtensionVirtualWorkspaceSupport = boolean | { supported: true } | { supported: false | LimitedWorkspaceSupportType; description: string };257258export function getWorkspaceSupportTypeMessage(supportType: ExtensionUntrustedWorkspaceSupport | ExtensionVirtualWorkspaceSupport | undefined): string | undefined {259if (typeof supportType === 'object' && supportType !== null) {260if (supportType.supported !== true) {261return supportType.description;262}263}264return undefined;265}266267268export interface IExtensionIdentifier {269id: string;270uuid?: string;271}272273export const EXTENSION_CATEGORIES = [274'AI',275'Azure',276'Chat',277'Data Science',278'Debuggers',279'Extension Packs',280'Education',281'Formatters',282'Keymaps',283'Language Packs',284'Linters',285'Machine Learning',286'Notebooks',287'Programming Languages',288'SCM Providers',289'Snippets',290'Testing',291'Themes',292'Visualization',293'Other',294];295296export interface IRelaxedExtensionManifest {297name: string;298displayName?: string;299publisher: string;300version: string;301engines: { readonly vscode: string };302description?: string;303main?: string;304type?: string;305browser?: string;306preview?: boolean;307// For now this only supports pointing to l10n bundle files308// but it will be used for package.l10n.json files in the future309l10n?: string;310icon?: string;311categories?: string[];312keywords?: string[];313activationEvents?: readonly string[];314extensionDependencies?: string[];315extensionAffinity?: string[];316extensionPack?: string[];317extensionKind?: ExtensionKind | ExtensionKind[];318contributes?: IExtensionContributions;319repository?: { url: string };320bugs?: { url: string };321originalEnabledApiProposals?: readonly string[];322enabledApiProposals?: readonly string[];323api?: string;324scripts?: { [key: string]: string };325capabilities?: IExtensionCapabilities;326}327328export type IExtensionManifest = Readonly<IRelaxedExtensionManifest>;329330export const enum ExtensionType {331System,332User333}334335export const enum TargetPlatform {336WIN32_X64 = 'win32-x64',337WIN32_ARM64 = 'win32-arm64',338339LINUX_X64 = 'linux-x64',340LINUX_ARM64 = 'linux-arm64',341LINUX_ARMHF = 'linux-armhf',342343ALPINE_X64 = 'alpine-x64',344ALPINE_ARM64 = 'alpine-arm64',345346DARWIN_X64 = 'darwin-x64',347DARWIN_ARM64 = 'darwin-arm64',348349WEB = 'web',350351UNIVERSAL = 'universal',352UNKNOWN = 'unknown',353UNDEFINED = 'undefined',354}355356export interface IExtension {357readonly type: ExtensionType;358readonly isBuiltin: boolean;359readonly identifier: IExtensionIdentifier;360readonly manifest: IExtensionManifest;361readonly location: URI;362readonly targetPlatform: TargetPlatform;363readonly publisherDisplayName?: string;364readonly readmeUrl?: URI;365readonly changelogUrl?: URI;366readonly isValid: boolean;367readonly validations: readonly [Severity, string][];368readonly preRelease: boolean;369}370371/**372* **!Do not construct directly!**373*374* **!Only static methods because it gets serialized!**375*376* This represents the "canonical" version for an extension identifier. Extension ids377* have to be case-insensitive (due to the marketplace), but we must ensure case378* preservation because the extension API is already public at this time.379*380* For example, given an extension with the publisher `"Hello"` and the name `"World"`,381* its canonical extension identifier is `"Hello.World"`. This extension could be382* referenced in some other extension's dependencies using the string `"hello.world"`.383*384* To make matters more complicated, an extension can optionally have an UUID. When two385* extensions have the same UUID, they are considered equal even if their identifier is different.386*/387export class ExtensionIdentifier {388public readonly value: string;389390/**391* Do not use directly. This is public to avoid mangling and thus392* allow compatibility between running from source and a built version.393*/394readonly _lower: string;395396constructor(value: string) {397this.value = value;398this._lower = value.toLowerCase();399}400401public static equals(a: ExtensionIdentifier | string | null | undefined, b: ExtensionIdentifier | string | null | undefined) {402if (typeof a === 'undefined' || a === null) {403return (typeof b === 'undefined' || b === null);404}405if (typeof b === 'undefined' || b === null) {406return false;407}408if (typeof a === 'string' || typeof b === 'string') {409// At least one of the arguments is an extension id in string form,410// so we have to use the string comparison which ignores case.411const aValue = (typeof a === 'string' ? a : a.value);412const bValue = (typeof b === 'string' ? b : b.value);413return strings.equalsIgnoreCase(aValue, bValue);414}415416// Now we know both arguments are ExtensionIdentifier417return (a._lower === b._lower);418}419420/**421* Gives the value by which to index (for equality).422*/423public static toKey(id: ExtensionIdentifier | string): string {424if (typeof id === 'string') {425return id.toLowerCase();426}427return id._lower;428}429}430431export class ExtensionIdentifierSet {432433private readonly _set = new Set<string>();434435public get size(): number {436return this._set.size;437}438439constructor(iterable?: Iterable<ExtensionIdentifier | string>) {440if (iterable) {441for (const value of iterable) {442this.add(value);443}444}445}446447public add(id: ExtensionIdentifier | string): void {448this._set.add(ExtensionIdentifier.toKey(id));449}450451public delete(extensionId: ExtensionIdentifier): boolean {452return this._set.delete(ExtensionIdentifier.toKey(extensionId));453}454455public has(id: ExtensionIdentifier | string): boolean {456return this._set.has(ExtensionIdentifier.toKey(id));457}458}459460export class ExtensionIdentifierMap<T> {461462private readonly _map = new Map<string, T>();463464public clear(): void {465this._map.clear();466}467468public delete(id: ExtensionIdentifier | string): void {469this._map.delete(ExtensionIdentifier.toKey(id));470}471472public get(id: ExtensionIdentifier | string): T | undefined {473return this._map.get(ExtensionIdentifier.toKey(id));474}475476public has(id: ExtensionIdentifier | string): boolean {477return this._map.has(ExtensionIdentifier.toKey(id));478}479480public set(id: ExtensionIdentifier | string, value: T): void {481this._map.set(ExtensionIdentifier.toKey(id), value);482}483484public values(): IterableIterator<T> {485return this._map.values();486}487488forEach(callbackfn: (value: T, key: string, map: Map<string, T>) => void): void {489this._map.forEach(callbackfn);490}491492[Symbol.iterator](): IterableIterator<[string, T]> {493return this._map[Symbol.iterator]();494}495}496497/**498* An error that is clearly from an extension, identified by the `ExtensionIdentifier`499*/500export class ExtensionError extends Error {501502readonly extension: ExtensionIdentifier;503504constructor(extensionIdentifier: ExtensionIdentifier, cause: Error, message?: string) {505super(`Error in extension ${ExtensionIdentifier.toKey(extensionIdentifier)}: ${message ?? cause.message}`, { cause });506this.name = 'ExtensionError';507this.extension = extensionIdentifier;508}509}510511export interface IRelaxedExtensionDescription extends IRelaxedExtensionManifest {512id?: string;513identifier: ExtensionIdentifier;514uuid?: string;515publisherDisplayName?: string;516targetPlatform: TargetPlatform;517isBuiltin: boolean;518isUserBuiltin: boolean;519isUnderDevelopment: boolean;520extensionLocation: URI;521preRelease: boolean;522}523524export type IExtensionDescription = Readonly<IRelaxedExtensionDescription>;525526export function isApplicationScopedExtension(manifest: IExtensionManifest): boolean {527return isLanguagePackExtension(manifest);528}529530export function isLanguagePackExtension(manifest: IExtensionManifest): boolean {531return manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations.length > 0 : false;532}533534export function isAuthenticationProviderExtension(manifest: IExtensionManifest): boolean {535return manifest.contributes && manifest.contributes.authentication ? manifest.contributes.authentication.length > 0 : false;536}537538export function isResolverExtension(manifest: IExtensionManifest, remoteAuthority: string | undefined): boolean {539if (remoteAuthority) {540const activationEvent = `onResolveRemoteAuthority:${getRemoteName(remoteAuthority)}`;541return !!manifest.activationEvents?.includes(activationEvent);542}543return false;544}545546export function parseApiProposals(enabledApiProposals: string[]): { proposalName: string; version?: number }[] {547return enabledApiProposals.map(proposal => {548const [proposalName, version] = proposal.split('@');549return { proposalName, version: version ? parseInt(version) : undefined };550});551}552553export function parseEnabledApiProposalNames(enabledApiProposals: string[]): string[] {554return enabledApiProposals.map(proposal => proposal.split('@')[0]);555}556557export const IBuiltinExtensionsScannerService = createDecorator<IBuiltinExtensionsScannerService>('IBuiltinExtensionsScannerService');558export interface IBuiltinExtensionsScannerService {559readonly _serviceBrand: undefined;560scanBuiltinExtensions(): Promise<IExtension[]>;561}562563564