mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)
* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 * Fix breaks * Extension management fixes * Fix breaks in windows bundling * Fix/skip failing tests * Update distro * Add clear to nuget.config * Add hygiene task * Bump distro * Fix hygiene issue * Add build to hygiene exclusion * Update distro * Update hygiene * Hygiene exclusions * Update tsconfig * Bump distro for server breaks * Update build config * Update darwin path * Add done calls to notebook tests * Skip failing tests * Disable smoke tests
This commit is contained in:
@@ -3,35 +3,46 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as DOM from 'vs/base/browser/dom'; // {{SQL CARBON EDIT}} added missing import to fix build break
|
||||
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IMarkdownString, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { insane } from 'vs/base/common/insane/insane';
|
||||
import { insane, InsaneOptions } from 'vs/base/common/insane/insane';
|
||||
import { parse } from 'vs/base/common/marshalling';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
|
||||
import { resolvePath } from 'vs/base/common/resources';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { domEvent } from 'vs/base/browser/event';
|
||||
|
||||
export interface MarkedOptions extends marked.MarkedOptions {
|
||||
baseUrl?: never;
|
||||
}
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
codeBlockRenderCallback?: () => void;
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<HTMLElement>;
|
||||
asyncRenderCallback?: () => void;
|
||||
baseUrl?: URI;
|
||||
}
|
||||
|
||||
const _ttpInsane = window.trustedTypes?.createPolicy('insane', {
|
||||
createHTML(value, options: InsaneOptions): string {
|
||||
return insane(value, options);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Create html nodes for the given content element.
|
||||
* Low-level way create a html element from a markdown string.
|
||||
*
|
||||
* **Note** that for most cases you should be using [`MarkdownRenderer`](./src/vs/editor/browser/core/markdownRenderer.ts)
|
||||
* which comes with support for pretty code block rendering and which uses the default way of handling links.
|
||||
*/
|
||||
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: MarkedOptions = {}): HTMLElement {
|
||||
const element = createElement(options);
|
||||
@@ -70,7 +81,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// and because of that special rewriting needs to be done
|
||||
// so that the URI uses a protocol that's understood by
|
||||
// browsers (like http or https)
|
||||
return DOM.asDomUri(uri).toString(true);
|
||||
return FileAccess.asBrowserUri(uri).toString(true);
|
||||
}
|
||||
if (uri.query) {
|
||||
uri = uri.with({ query: _uriMassage(uri.query) });
|
||||
@@ -161,9 +172,9 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// {{SQL CARBON EDIT}} - Promise.all not returning the strValue properly in original code? @todo anthonydresser 4/12/19 investigate a better way to do this.
|
||||
const promise = value.then(strValue => {
|
||||
withInnerHTML.then(e => {
|
||||
const span = element.querySelector(`div[data-code="${id}"]`);
|
||||
const span = <HTMLDivElement>element.querySelector(`div[data-code="${id}"]`);
|
||||
if (span) {
|
||||
span.innerHTML = strValue;
|
||||
span.innerHTML = strValue.innerHTML;
|
||||
}
|
||||
}).catch(err => {
|
||||
// ignore
|
||||
@@ -181,83 +192,115 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
// // ignore
|
||||
// });
|
||||
|
||||
if (options.codeBlockRenderCallback) {
|
||||
promise.then(options.codeBlockRenderCallback);
|
||||
if (options.asyncRenderCallback) {
|
||||
promise.then(options.asyncRenderCallback);
|
||||
}
|
||||
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
};
|
||||
}
|
||||
|
||||
const actionHandler = options.actionHandler;
|
||||
if (actionHandler) {
|
||||
[DOM.EventType.CLICK, DOM.EventType.AUXCLICK].forEach(event => {
|
||||
actionHandler.disposeables.add(DOM.addDisposableListener(element, event, (e: MouseEvent) => {
|
||||
const mouseEvent = new StandardMouseEvent(e);
|
||||
if (!mouseEvent.leftButton && !mouseEvent.middleButton) {
|
||||
if (options.actionHandler) {
|
||||
options.actionHandler.disposeables.add(Event.any<MouseEvent>(domEvent(element, 'click'), domEvent(element, 'auxclick'))(e => {
|
||||
const mouseEvent = new StandardMouseEvent(e);
|
||||
if (!mouseEvent.leftButton && !mouseEvent.middleButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target: HTMLElement | null = mouseEvent.target;
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
if (!target || target.tagName !== 'A') {
|
||||
return;
|
||||
}
|
||||
|
||||
let target: HTMLElement | null = mouseEvent.target;
|
||||
if (target.tagName !== 'A') {
|
||||
target = target.parentElement;
|
||||
if (!target || target.tagName !== 'A') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const href = target.dataset['href'];
|
||||
if (href) {
|
||||
options.actionHandler!.callback(href, mouseEvent);
|
||||
}
|
||||
try {
|
||||
const href = target.dataset['href'];
|
||||
if (href) {
|
||||
actionHandler.callback(href, mouseEvent);
|
||||
}
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
} finally {
|
||||
mouseEvent.preventDefault();
|
||||
}
|
||||
}));
|
||||
});
|
||||
} catch (err) {
|
||||
onUnexpectedError(err);
|
||||
} finally {
|
||||
mouseEvent.preventDefault();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Use our own sanitizer so that we can let through only spans.
|
||||
// Otherwise, we'd be letting all html be rendered.
|
||||
// If we want to allow markdown permitted tags, then we can delete sanitizer and sanitize.
|
||||
// We always pass the output through insane after this so that we don't rely on
|
||||
// marked for sanitization.
|
||||
markedOptions.sanitizer = (html: string): string => {
|
||||
const match = markdown.isTrusted ? html.match(/^(<span[^<]+>)|(<\/\s*span>)$/) : undefined;
|
||||
return match ? html : '';
|
||||
};
|
||||
markedOptions.sanitize = true;
|
||||
markedOptions.renderer = renderer;
|
||||
markedOptions.silent = true;
|
||||
|
||||
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
|
||||
if (markdown.isTrusted) {
|
||||
allowedSchemes.push(Schemas.command);
|
||||
}
|
||||
markedOptions.renderer = renderer;
|
||||
|
||||
// values that are too long will freeze the UI
|
||||
let value = markdown.value ?? '';
|
||||
if (value.length > 100_000) {
|
||||
value = `${value.substr(0, 100_000)}…`;
|
||||
}
|
||||
const renderedMarkdown = marked.parse(
|
||||
markdown.supportThemeIcons ? markdownEscapeEscapedCodicons(value) : value,
|
||||
markedOptions
|
||||
);
|
||||
|
||||
function filter(token: { tag: string, attrs: { readonly [key: string]: string } }): boolean {
|
||||
if (token.tag === 'span' && markdown.isTrusted && (Object.keys(token.attrs).length === 1)) {
|
||||
if (token.attrs['style']) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// escape theme icons
|
||||
if (markdown.supportThemeIcons) {
|
||||
value = markdownEscapeEscapedCodicons(value);
|
||||
}
|
||||
|
||||
element.innerHTML = insane(renderedMarkdown, {
|
||||
const renderedMarkdown = marked.parse(value, markedOptions);
|
||||
|
||||
// sanitize with insane
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, renderedMarkdown);
|
||||
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
|
||||
// signal size changes for image tags
|
||||
if (options.asyncRenderCallback) {
|
||||
for (const img of element.getElementsByTagName('img')) {
|
||||
const listener = DOM.addDisposableListener(img, 'load', () => {
|
||||
listener.dispose();
|
||||
options.asyncRenderCallback!();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
function sanitizeRenderedMarkdown(
|
||||
options: { isTrusted?: boolean },
|
||||
renderedMarkdown: string,
|
||||
): string {
|
||||
const insaneOptions = getInsaneOptions(options);
|
||||
if (_ttpInsane) {
|
||||
return _ttpInsane.createHTML(renderedMarkdown, insaneOptions) as unknown as string;
|
||||
} else {
|
||||
return insane(renderedMarkdown, insaneOptions);
|
||||
}
|
||||
}
|
||||
|
||||
function getInsaneOptions(options: { readonly isTrusted?: boolean }): InsaneOptions {
|
||||
const allowedSchemes = [
|
||||
Schemas.http,
|
||||
Schemas.https,
|
||||
Schemas.mailto,
|
||||
Schemas.data,
|
||||
Schemas.file,
|
||||
Schemas.vscodeRemote,
|
||||
Schemas.vscodeRemoteResource,
|
||||
];
|
||||
|
||||
if (options.isTrusted) {
|
||||
allowedSchemes.push(Schemas.command);
|
||||
}
|
||||
|
||||
return {
|
||||
allowedSchemes,
|
||||
// allowedTags should included everything that markdown renders to.
|
||||
// Since we have our own sanitize function for marked, it's possible we missed some tag so let insane make sure.
|
||||
@@ -273,10 +316,91 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
'th': ['align'],
|
||||
'td': ['align']
|
||||
},
|
||||
filter
|
||||
});
|
||||
|
||||
signalInnerHTML!();
|
||||
|
||||
return element;
|
||||
filter(token: { tag: string; attrs: { readonly [key: string]: string; }; }): boolean {
|
||||
if (token.tag === 'span' && options.isTrusted && (Object.keys(token.attrs).length === 1)) {
|
||||
if (token.attrs['style']) {
|
||||
return !!token.attrs['style'].match(/^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/);
|
||||
} else if (token.attrs['class']) {
|
||||
// The class should match codicon rendering in src\vs\base\common\codicons.ts
|
||||
return !!token.attrs['class'].match(/^codicon codicon-[a-z\-]+( codicon-animation-[a-z\-]+)?$/);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips all markdown from `markdown`. For example `# Header` would be output as `Header`.
|
||||
*/
|
||||
export function renderMarkdownAsPlaintext(markdown: IMarkdownString) {
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.code = (code: string): string => {
|
||||
return code;
|
||||
};
|
||||
renderer.blockquote = (quote: string): string => {
|
||||
return quote;
|
||||
};
|
||||
renderer.html = (_html: string): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.heading = (text: string, _level: 1 | 2 | 3 | 4 | 5 | 6, _raw: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.hr = (): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.list = (body: string, _ordered: boolean): string => {
|
||||
return body;
|
||||
};
|
||||
renderer.listitem = (text: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.paragraph = (text: string): string => {
|
||||
return text + '\n';
|
||||
};
|
||||
renderer.table = (header: string, body: string): string => {
|
||||
return header + body + '\n';
|
||||
};
|
||||
renderer.tablerow = (content: string): string => {
|
||||
return content;
|
||||
};
|
||||
renderer.tablecell = (content: string, _flags: {
|
||||
header: boolean;
|
||||
align: 'center' | 'left' | 'right' | null;
|
||||
}): string => {
|
||||
return content + ' ';
|
||||
};
|
||||
renderer.strong = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.em = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.codespan = (code: string): string => {
|
||||
return code;
|
||||
};
|
||||
renderer.br = (): string => {
|
||||
return '\n';
|
||||
};
|
||||
renderer.del = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.image = (_href: string, _title: string, _text: string): string => {
|
||||
return '';
|
||||
};
|
||||
renderer.text = (text: string): string => {
|
||||
return text;
|
||||
};
|
||||
renderer.link = (_href: string, _title: string, text: string): string => {
|
||||
return text;
|
||||
};
|
||||
// values that are too long will freeze the UI
|
||||
let value = markdown.value ?? '';
|
||||
if (value.length > 100_000) {
|
||||
value = `${value.substr(0, 100_000)}…`;
|
||||
}
|
||||
return sanitizeRenderedMarkdown({ isTrusted: false }, marked.parse(value, { renderer })).toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user