/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. 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'; const resolveExtensionResource = (extension: vscode.Extension, resourcePath: string): vscode.Uri => { return vscode.Uri.joinPath(extension.extensionUri, resourcePath); }; const resolveExtensionResources = (extension: vscode.Extension, resourcePaths: unknown): vscode.Uri[] => { const result: vscode.Uri[] = []; if (Array.isArray(resourcePaths)) { for (const resource of resourcePaths) { try { result.push(resolveExtensionResource(extension, resource)); } catch (e) { // noop } } } return result; }; export interface MarkdownContributions { readonly previewScripts: ReadonlyArray; readonly previewStyles: ReadonlyArray; readonly previewResourceRoots: ReadonlyArray; readonly markdownItPlugins: Map 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 && extension.packageJSON.contributes; if (!contributions) { return MarkdownContributions.Empty; } const previewStyles = getContributedStyles(contributions, extension); const previewScripts = 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(); 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); } }, undefined, this._disposables); } 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 { if (!this._contributions) { 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); }