Path: blob/main/extensions/copilot/src/util/vs/base/common/htmlContent.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { illegalArgument } from './errors';8import { escapeIcons } from './iconLabels';9import { Schemas } from './network';10import { isEqual } from './resources';11import { escapeRegExpCharacters } from './strings';12import { URI, UriComponents } from './uri';1314export interface MarkdownStringTrustedOptions {15readonly enabledCommands: readonly string[];16}1718export interface IMarkdownString {19readonly value: string;20readonly isTrusted?: boolean | MarkdownStringTrustedOptions;21readonly supportThemeIcons?: boolean;22readonly supportHtml?: boolean;23/** @internal */24readonly supportAlertSyntax?: boolean;25readonly baseUri?: UriComponents;26uris?: { [href: string]: UriComponents };27}2829export const enum MarkdownStringTextNewlineStyle {30Paragraph = 0,31Break = 1,32}3334export class MarkdownString implements IMarkdownString {3536public value: string;37public isTrusted?: boolean | MarkdownStringTrustedOptions;38public supportThemeIcons?: boolean;39public supportHtml?: boolean;40public supportAlertSyntax?: boolean;41public baseUri?: URI;42public uris?: { [href: string]: UriComponents } | undefined;4344public static lift(dto: IMarkdownString): MarkdownString {45const markdownString = new MarkdownString(dto.value, dto);46markdownString.uris = dto.uris;47markdownString.baseUri = dto.baseUri ? URI.revive(dto.baseUri) : undefined;48return markdownString;49}5051constructor(52value: string = '',53isTrustedOrOptions: boolean | { isTrusted?: boolean | MarkdownStringTrustedOptions; supportThemeIcons?: boolean; supportHtml?: boolean; supportAlertSyntax?: boolean } = false,54) {55this.value = value;56if (typeof this.value !== 'string') {57throw illegalArgument('value');58}5960if (typeof isTrustedOrOptions === 'boolean') {61this.isTrusted = isTrustedOrOptions;62this.supportThemeIcons = false;63this.supportHtml = false;64this.supportAlertSyntax = false;65}66else {67this.isTrusted = isTrustedOrOptions.isTrusted ?? undefined;68this.supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;69this.supportHtml = isTrustedOrOptions.supportHtml ?? false;70this.supportAlertSyntax = isTrustedOrOptions.supportAlertSyntax ?? false;71}72}7374appendText(value: string, newlineStyle: MarkdownStringTextNewlineStyle = MarkdownStringTextNewlineStyle.Paragraph): MarkdownString {75this.value += escapeMarkdownSyntaxTokens(this.supportThemeIcons ? escapeIcons(value) : value) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.76.replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length)) // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.77.replace(/\>/gm, '\\>') // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.78.replace(/\n/g, newlineStyle === MarkdownStringTextNewlineStyle.Break ? '\\\n' : '\n\n'); // CodeQL [SM02383] The Markdown is fully sanitized after being rendered.7980return this;81}8283appendMarkdown(value: string): MarkdownString {84this.value += value;85return this;86}8788appendCodeblock(langId: string, code: string): MarkdownString {89this.value += `\n${appendEscapedMarkdownCodeBlockFence(code, langId)}\n`;90return this;91}9293appendLink(target: URI | string, label: string, title?: string): MarkdownString {94this.value += '[';95this.value += this._escape(label, ']');96this.value += '](';97this.value += this._escape(String(target), ')');98if (title) {99this.value += ` "${this._escape(this._escape(title, '"'), ')')}"`;100}101this.value += ')';102return this;103}104105private _escape(value: string, ch: string): string {106const r = new RegExp(escapeRegExpCharacters(ch), 'g');107return value.replace(r, (match, offset) => {108if (value.charAt(offset - 1) !== '\\') {109return `\\${match}`;110} else {111return match;112}113});114}115}116117export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean {118if (isMarkdownString(oneOrMany)) {119return !oneOrMany.value;120} else if (Array.isArray(oneOrMany)) {121return oneOrMany.every(isEmptyMarkdownString);122} else {123return true;124}125}126127export function isMarkdownString(thing: unknown): thing is IMarkdownString {128if (thing instanceof MarkdownString) {129return true;130} else if (thing && typeof thing === 'object') {131return typeof (<IMarkdownString>thing).value === 'string'132&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || typeof (<IMarkdownString>thing).isTrusted === 'object' || (<IMarkdownString>thing).isTrusted === undefined)133&& (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined)134&& (typeof (<IMarkdownString>thing).supportAlertSyntax === 'boolean' || (<IMarkdownString>thing).supportAlertSyntax === undefined);135}136return false;137}138139export function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {140if (a === b) {141return true;142} else if (!a || !b) {143return false;144} else {145return a.value === b.value146&& a.isTrusted === b.isTrusted147&& a.supportThemeIcons === b.supportThemeIcons148&& a.supportHtml === b.supportHtml149&& a.supportAlertSyntax === b.supportAlertSyntax150&& (a.baseUri === b.baseUri || !!a.baseUri && !!b.baseUri && isEqual(URI.from(a.baseUri), URI.from(b.baseUri)));151}152}153154export function escapeMarkdownSyntaxTokens(text: string): string {155// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash156return text.replace(/[\\`*_{}[\]()#+\-!~]/g, '\\$&'); // CodeQL [SM02383] Backslash is escaped in the character class157}158159/**160* @see https://github.com/microsoft/vscode/issues/193746161*/162export function appendEscapedMarkdownCodeBlockFence(code: string, langId: string) {163const longestFenceLength =164code.match(/^`+/gm)?.reduce((a, b) => (a.length > b.length ? a : b)).length ??1650;166const desiredFenceLength =167longestFenceLength >= 3 ? longestFenceLength + 1 : 3;168169// the markdown result170return [171`${'`'.repeat(desiredFenceLength)}${langId}`,172code,173`${'`'.repeat(desiredFenceLength)}`,174].join('\n');175}176177export function escapeDoubleQuotes(input: string) {178return input.replace(/"/g, '"');179}180181export function removeMarkdownEscapes(text: string): string {182if (!text) {183return text;184}185return text.replace(/\\([\\`*_{}[\]()#+\-.!~])/g, '$1');186}187188export function parseHrefAndDimensions(href: string): { href: string; dimensions: string[] } {189const dimensions: string[] = [];190const splitted = href.split('|').map(s => s.trim());191href = splitted[0];192const parameters = splitted[1];193if (parameters) {194const heightFromParams = /height=(\d+)/.exec(parameters);195const widthFromParams = /width=(\d+)/.exec(parameters);196const height = heightFromParams ? heightFromParams[1] : '';197const width = widthFromParams ? widthFromParams[1] : '';198const widthIsFinite = isFinite(parseInt(width));199const heightIsFinite = isFinite(parseInt(height));200if (widthIsFinite) {201dimensions.push(`width="${width}"`);202}203if (heightIsFinite) {204dimensions.push(`height="${height}"`);205}206}207return { href, dimensions };208}209210export function createMarkdownLink(text: string, href: string, title?: string, escapeTokens = true): string {211return `[${escapeTokens ? escapeMarkdownSyntaxTokens(text) : text}](${href}${title ? ` "${escapeMarkdownSyntaxTokens(title)}"` : ''})`;212}213214export function createMarkdownCommandLink(command: { title: string; id: string; arguments?: unknown[]; tooltip?: string }, escapeTokens = true): string {215const uri = createCommandUri(command.id, ...(command.arguments || [])).toString();216return createMarkdownLink(command.title, uri, command.tooltip, escapeTokens);217}218219export function createCommandUri(commandId: string, ...commandArgs: unknown[]): URI {220return URI.from({221scheme: Schemas.command,222path: commandId,223query: commandArgs.length ? encodeURIComponent(JSON.stringify(commandArgs)) : undefined,224});225}226227228