/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import * as arrays from './util/arrays'; import { Disposable } from './util/dispose'; function resolveExtensionResource(extension: vscode.Extension, resourcePath: string): vscode.Uri { return vscode.Uri.joinPath(extension.extensionUri, resourcePath); } function* resolveExtensionResources(extension: vscode.Extension, resourcePaths: unknown): Iterable { if (Array.isArray(resourcePaths)) { for (const resource of resourcePaths) { try { yield resolveExtensionResource(extension, resource); } catch { // noop } } } } export interface MarkdownContributions { readonly previewScripts: readonly vscode.Uri[]; readonly previewStyles: readonly vscode.Uri[]; readonly previewResourceRoots: readonly vscode.Uri[]; readonly markdownItPlugins: ReadonlyMap any>>; } export namespace MarkdownContributions { export const Empty: MarkdownContributions = { previewScripts: [], previewStyles: [], previewResourceRoots: [], markdownItPlugins: new Map() }; export function merge(a: MarkdownContributions, b: MarkdownContributions): MarkdownContributions { return { previewScripts: [...a.previewScripts, ...b.previewScripts], previewStyles: [...a.previewStyles, ...b.previewStyles], previewResourceRoots: [...a.previewResourceRoots, ...b.previewResourceRoots], markdownItPlugins: new Map([...a.markdownItPlugins.entries(), ...b.markdownItPlugins.entries()]), }; } function uriEqual(a: vscode.Uri, b: vscode.Uri): boolean { return a.toString() === b.toString(); } export function equal(a: MarkdownContributions, b: MarkdownContributions): boolean { return arrays.equals(a.previewScripts, b.previewScripts, uriEqual) && arrays.equals(a.previewStyles, b.previewStyles, uriEqual) && arrays.equals(a.previewResourceRoots, b.previewResourceRoots, uriEqual) && arrays.equals(Array.from(a.markdownItPlugins.keys()), Array.from(b.markdownItPlugins.keys())); } export function fromExtension(extension: vscode.Extension): MarkdownContributions { const contributions = extension.packageJSON?.contributes; if (!contributions) { return MarkdownContributions.Empty; } const previewStyles = Array.from(getContributedStyles(contributions, extension)); const previewScripts = Array.from(getContributedScripts(contributions, extension)); const previewResourceRoots = previewStyles.length || previewScripts.length ? [extension.extensionUri] : []; const markdownItPlugins = getContributedMarkdownItPlugins(contributions, extension); return { previewScripts, previewStyles, previewResourceRoots, markdownItPlugins }; } function getContributedMarkdownItPlugins( contributes: any, extension: vscode.Extension ): Map any>> { const map = new Map any>>(); if (contributes['markdown.markdownItPlugins']) { map.set(extension.id, extension.activate().then(() => { if (extension.exports && extension.exports.extendMarkdownIt) { return (md: any) => extension.exports.extendMarkdownIt(md); } return (md: any) => md; })); } return map; } function getContributedScripts( contributes: any, extension: vscode.Extension ) { return resolveExtensionResources(extension, contributes['markdown.previewScripts']); } function getContributedStyles( contributes: any, extension: vscode.Extension ) { return resolveExtensionResources(extension, contributes['markdown.previewStyles']); } } export interface MarkdownContributionProvider { readonly extensionUri: vscode.Uri; readonly contributions: MarkdownContributions; readonly onContributionsChanged: vscode.Event; dispose(): void; } class VSCodeExtensionMarkdownContributionProvider extends Disposable implements MarkdownContributionProvider { private _contributions?: MarkdownContributions; public constructor( private readonly _extensionContext: vscode.ExtensionContext, ) { super(); this._register(vscode.extensions.onDidChange(() => { const currentContributions = this._getCurrentContributions(); const existingContributions = this._contributions || MarkdownContributions.Empty; if (!MarkdownContributions.equal(existingContributions, currentContributions)) { this._contributions = currentContributions; this._onContributionsChanged.fire(this); } })); } public get extensionUri() { return this._extensionContext.extensionUri; } private readonly _onContributionsChanged = this._register(new vscode.EventEmitter()); public readonly onContributionsChanged = this._onContributionsChanged.event; public get contributions(): MarkdownContributions { this._contributions ??= this._getCurrentContributions(); return this._contributions; } private _getCurrentContributions(): MarkdownContributions { return vscode.extensions.all .map(MarkdownContributions.fromExtension) .reduce(MarkdownContributions.merge, MarkdownContributions.Empty); } } export function getMarkdownExtensionContributions(context: vscode.ExtensionContext): MarkdownContributionProvider { return new VSCodeExtensionMarkdownContributionProvider(context); }