Skip to content

Commit

Permalink
Initial support for syntax highlighting md code blocks in notebooks
Browse files Browse the repository at this point in the history
  • Loading branch information
mjbvz committed Nov 8, 2021
1 parent d1f2d0a commit 7648234
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 5 deletions.
10 changes: 8 additions & 2 deletions extensions/markdown-language-features/notebook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

const MarkdownIt: typeof import('markdown-it') = require('markdown-it');
import * as DOMPurify from 'dompurify';
import MarkdownIt from 'markdown-it';
import type * as MarkdownItToken from 'markdown-it/lib/token';
import type { ActivationFunction } from 'vscode-notebook-renderer';

Expand All @@ -13,9 +13,15 @@ const sanitizerOptions: DOMPurify.Config = {
};

export const activate: ActivationFunction<void> = (ctx) => {
const markdownIt = new MarkdownIt({
const markdownIt: MarkdownIt = new MarkdownIt({
html: true,
linkify: true,
highlight: (str: string, lang?: string) => {
if (lang) {
return `<code class="vscode-code-block" data-vscode-code-block-lang="${markdownIt.utils.escapeHtml(lang)}">${markdownIt.utils.escapeHtml(str)}</code>`;
}
return `<code>${markdownIt.utils.escapeHtml(str)}</code>`;
}
});
markdownIt.linkify.set({ fuzzyLink: false });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"outDir": "./dist/",
"jsx": "react",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true,
"module": "es2020",
"lib": [
"es2018",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import { isMacintosh, isWeb } from 'vs/base/common/platform';
import { dirname, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { TokenizationRegistry } from 'vs/editor/common/modes';
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization';
import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { IModeService } from 'vs/editor/common/services/modeService';
import * as nls from 'vs/nls';
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
Expand All @@ -36,7 +40,7 @@ import { INotebookRendererInfo, RendererMessagingSpec } from 'vs/workbench/contr
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IWebviewService, WebviewContentPurpose, IWebviewElement } from 'vs/workbench/contrib/webview/browser/webview';
import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, IContentWidgetTopRequest, IControllerPreload, ICreationRequestMessage, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';

Expand Down Expand Up @@ -131,6 +135,7 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
@ITelemetryService private readonly telemetryService: ITelemetryService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IModeService private readonly modeService: IModeService,
) {
super();

Expand Down Expand Up @@ -163,6 +168,13 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
isTrusted: e,
});
}));

this._register(TokenizationRegistry.onDidChange(() => {
this._sendMessageToWebview({
type: 'tokenizedStylesChanged',
css: getTokenizationCss(),
});
}));
}

updateOptions(options: {
Expand Down Expand Up @@ -353,8 +365,8 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
tbody th {
font-weight: normal;
}
</style>
<style id="vscode-tokenization-styles" nonce="${this.nonce}">${getTokenizationCss()}</style>
</head>
<body style="overflow: hidden;">
<script>
Expand Down Expand Up @@ -719,6 +731,27 @@ var requirejs = (function() {
if (cell instanceof MarkupCellViewModel) {
cell.renderedHtml = data.html;
}

for (const { id, value, lang } of data.codeBlocks) {
// The language id may be a language aliases (e.g.js instead of javascript)
const languageId = this.modeService.getModeIdForLanguageName(lang);
if (!languageId) {
continue;
}

this.modeService.triggerMode(languageId);
TokenizationRegistry.getPromise(languageId)?.then(tokenization => {
if (this._disposed) {
return;
}
const html = tokenizeToString(value, this.modeService.languageIdCodec, tokenization);
this._sendMessageToWebview({
type: 'tokenizedCodeBlock',
html,
codeBlockId: id
});
});
}
break;
}
case 'telemetryFoundRenderedMarkdownMath':
Expand Down Expand Up @@ -1282,3 +1315,10 @@ var requirejs = (function() {
super.dispose();
}
}

function getTokenizationCss() {
const colorMap = TokenizationRegistry.getColorMap();
const tokenizationCss = colorMap ? generateTokensCSSForColorMap(colorMap) : '';
return tokenizationCss;
}

Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export interface IRenderedMarkupMessage extends BaseToWebviewMessage {
readonly type: 'renderedMarkup';
readonly cellId: string;
readonly html: string;
readonly codeBlocks: ReadonlyArray<{
readonly id: string;
readonly value: string;
readonly lang: string;
}>;
}

export interface ITelemetryFoundRenderedMarkdownMath extends BaseToWebviewMessage {
Expand Down Expand Up @@ -338,6 +343,16 @@ export interface INotebookUpdateWorkspaceTrust {
readonly type: 'updateWorkspaceTrust';
readonly isTrusted: boolean;
}
export interface ITokenizedCodeBlockMessage {
readonly type: 'tokenizedCodeBlock';
readonly codeBlockId: string;
readonly html: string;
}

export interface ITokenizedStylesChangedMessage {
readonly type: 'tokenizedStylesChanged';
readonly css: string;
}

export type FromWebviewMessage = WebviewIntialized |
IDimensionMessage |
Expand Down Expand Up @@ -388,6 +403,8 @@ export type ToWebviewMessage = IClearMessage |
IInitializeMarkupCells |
INotebookStylesMessage |
INotebookOptionsMessage |
INotebookUpdateWorkspaceTrust;
INotebookUpdateWorkspaceTrust |
ITokenizedCodeBlockMessage |
ITokenizedStylesChangedMessage;

export type AnyMessage = FromWebviewMessage | ToWebviewMessage;
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ async function webviewPreloads(ctx: PreloadContext) {
const vscode = acquireVsCodeApi();
delete (globalThis as any).acquireVsCodeApi;

const tokenizationStyleElement = document.querySelector('style#vscode-tokenization-styles');

const handleInnerClick = (event: MouseEvent) => {
if (!event || !event.view || !event.view.document) {
return;
Expand Down Expand Up @@ -675,6 +677,17 @@ async function webviewPreloads(ctx: PreloadContext) {
viewModel.rerender();
break;
}
case 'tokenizedCodeBlock': {
const { codeBlockId, html } = event.data;
MarkupCell.highlightCodeBlock(codeBlockId, html);
break;
}
case 'tokenizedStylesChanged': {
if (tokenizationStyleElement) {
tokenizationStyleElement.textContent = event.data.css;
}
break;
}
}
});

Expand Down Expand Up @@ -1068,6 +1081,20 @@ async function webviewPreloads(ctx: PreloadContext) {

class MarkupCell implements IOutputItem {

private static pendingCodeBlocksToHighlight = new Map<string, HTMLElement>();

public static highlightCodeBlock(id: string, html: string) {
const el = MarkupCell.pendingCodeBlocksToHighlight.get(id);
if (!el) {
return;
}
const trustedHtml = ttPolicy?.createHTML(html) ?? html;
el.innerHTML = trustedHtml as string;
if (tokenizationStyleElement) {
el.insertAdjacentElement('beforebegin', tokenizationStyleElement.cloneNode(true) as HTMLElement);
}
}

public readonly ready: Promise<void>;

public readonly element: HTMLElement;
Expand Down Expand Up @@ -1199,9 +1226,21 @@ async function webviewPreloads(ctx: PreloadContext) {
}
}

const codeBlocks: Array<{ value: string, lang: string, id: string }> = [];
let i = 0;
for (const el of root.querySelectorAll('.vscode-code-block')) {
const lang = el.getAttribute('data-vscode-code-block-lang');
if (el.textContent && lang) {
const id = `${Date.now()}-${i++}`;
codeBlocks.push({ value: el.textContent, lang: lang, id });
MarkupCell.pendingCodeBlocksToHighlight.set(id, el as HTMLElement);
}
}

postNotebookMessage<webviewMessages.IRenderedMarkupMessage>('renderedMarkup', {
cellId: this.id,
html: html.join(''),
codeBlocks
});

dimensionUpdater.updateHeight(this.id, this.element.offsetHeight, {
Expand Down

0 comments on commit 7648234

Please sign in to comment.