Path: blob/main/extensions/markdown-language-features/src/markdownExtensions.ts
3292 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 * as vscode from 'vscode';6import * as arrays from './util/arrays';7import { Disposable } from './util/dispose';89function resolveExtensionResource(extension: vscode.Extension<any>, resourcePath: string): vscode.Uri {10return vscode.Uri.joinPath(extension.extensionUri, resourcePath);11}1213function* resolveExtensionResources(extension: vscode.Extension<any>, resourcePaths: unknown): Iterable<vscode.Uri> {14if (Array.isArray(resourcePaths)) {15for (const resource of resourcePaths) {16try {17yield resolveExtensionResource(extension, resource);18} catch {19// noop20}21}22}23}2425export interface MarkdownContributions {26readonly previewScripts: readonly vscode.Uri[];27readonly previewStyles: readonly vscode.Uri[];28readonly previewResourceRoots: readonly vscode.Uri[];29readonly markdownItPlugins: ReadonlyMap<string, Thenable<(md: any) => any>>;30}3132export namespace MarkdownContributions {33export const Empty: MarkdownContributions = {34previewScripts: [],35previewStyles: [],36previewResourceRoots: [],37markdownItPlugins: new Map()38};3940export function merge(a: MarkdownContributions, b: MarkdownContributions): MarkdownContributions {41return {42previewScripts: [...a.previewScripts, ...b.previewScripts],43previewStyles: [...a.previewStyles, ...b.previewStyles],44previewResourceRoots: [...a.previewResourceRoots, ...b.previewResourceRoots],45markdownItPlugins: new Map([...a.markdownItPlugins.entries(), ...b.markdownItPlugins.entries()]),46};47}4849function uriEqual(a: vscode.Uri, b: vscode.Uri): boolean {50return a.toString() === b.toString();51}5253export function equal(a: MarkdownContributions, b: MarkdownContributions): boolean {54return arrays.equals(a.previewScripts, b.previewScripts, uriEqual)55&& arrays.equals(a.previewStyles, b.previewStyles, uriEqual)56&& arrays.equals(a.previewResourceRoots, b.previewResourceRoots, uriEqual)57&& arrays.equals(Array.from(a.markdownItPlugins.keys()), Array.from(b.markdownItPlugins.keys()));58}5960export function fromExtension(extension: vscode.Extension<any>): MarkdownContributions {61const contributions = extension.packageJSON?.contributes;62if (!contributions) {63return MarkdownContributions.Empty;64}6566const previewStyles = Array.from(getContributedStyles(contributions, extension));67const previewScripts = Array.from(getContributedScripts(contributions, extension));68const previewResourceRoots = previewStyles.length || previewScripts.length ? [extension.extensionUri] : [];69const markdownItPlugins = getContributedMarkdownItPlugins(contributions, extension);7071return {72previewScripts,73previewStyles,74previewResourceRoots,75markdownItPlugins76};77}7879function getContributedMarkdownItPlugins(80contributes: any,81extension: vscode.Extension<any>82): Map<string, Thenable<(md: any) => any>> {83const map = new Map<string, Thenable<(md: any) => any>>();84if (contributes['markdown.markdownItPlugins']) {85map.set(extension.id, extension.activate().then(() => {86if (extension.exports?.extendMarkdownIt) {87return (md: any) => extension.exports.extendMarkdownIt(md);88}89return (md: any) => md;90}));91}92return map;93}9495function getContributedScripts(96contributes: any,97extension: vscode.Extension<any>98) {99return resolveExtensionResources(extension, contributes['markdown.previewScripts']);100}101102function getContributedStyles(103contributes: any,104extension: vscode.Extension<any>105) {106return resolveExtensionResources(extension, contributes['markdown.previewStyles']);107}108}109110export interface MarkdownContributionProvider {111readonly extensionUri: vscode.Uri;112113readonly contributions: MarkdownContributions;114readonly onContributionsChanged: vscode.Event<this>;115116dispose(): void;117}118119class VSCodeExtensionMarkdownContributionProvider extends Disposable implements MarkdownContributionProvider {120121private _contributions?: MarkdownContributions;122123public constructor(124private readonly _extensionContext: vscode.ExtensionContext,125) {126super();127128this._register(vscode.extensions.onDidChange(() => {129const currentContributions = this._getCurrentContributions();130const existingContributions = this._contributions || MarkdownContributions.Empty;131if (!MarkdownContributions.equal(existingContributions, currentContributions)) {132this._contributions = currentContributions;133this._onContributionsChanged.fire(this);134}135}));136}137138public get extensionUri() {139return this._extensionContext.extensionUri;140}141142private readonly _onContributionsChanged = this._register(new vscode.EventEmitter<this>());143public readonly onContributionsChanged = this._onContributionsChanged.event;144145public get contributions(): MarkdownContributions {146this._contributions ??= this._getCurrentContributions();147return this._contributions;148}149150private _getCurrentContributions(): MarkdownContributions {151return vscode.extensions.all152.map(MarkdownContributions.fromExtension)153.reduce(MarkdownContributions.merge, MarkdownContributions.Empty);154}155}156157export function getMarkdownExtensionContributions(context: vscode.ExtensionContext): MarkdownContributionProvider {158return new VSCodeExtensionMarkdownContributionProvider(context);159}160161162