Skip to content

Backport clickable links in markdown jupyter cells #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: leapide-code-1.58.2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions extensions/markdown-language-features/notebook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
*--------------------------------------------------------------------------------------------*/

const MarkdownIt = require('markdown-it');
import type * as markdownIt from 'markdown-it';

export function activate() {
let markdownIt = new MarkdownIt({
html: true
});
addNamedHeaderRendering(markdownIt);

const style = document.createElement('style');
style.textContent = `
Expand Down Expand Up @@ -181,3 +183,49 @@ export function activate() {
}
};
}


function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
const slugCounter = new Map<string, number>();

const originalHeaderOpen = md.renderer.rules.heading_open;
md.renderer.rules.heading_open = (tokens: markdownIt.Token[], idx: number, options: any, env: any, self: any) => {
const title = tokens[idx + 1].children.reduce((acc: string, t: any) => acc + t.content, '');
let slug = slugFromHeading(title);

if (slugCounter.has(slug)) {
const count = slugCounter.get(slug)!;
slugCounter.set(slug, count + 1);
slug = slugFromHeading(slug + '-' + (count + 1));
} else {
slugCounter.set(slug, 0);
}

tokens[idx].attrs = tokens[idx].attrs || [];
tokens[idx].attrs.push(['id', slug]);

if (originalHeaderOpen) {
return originalHeaderOpen(tokens, idx, options, env, self);
} else {
return self.renderToken(tokens, idx, options, env, self);
}
};

const originalRender = md.render;
md.render = function () {
slugCounter.clear();
return originalRender.apply(this, arguments as any);
};
}

function slugFromHeading(heading: string): string {
const slugifiedHeading = encodeURI(
heading.trim()
.toLowerCase()
.replace(/\s+/g, '-') // Replace whitespace with -
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators
.replace(/^\-+/, '') // Remove leading -
.replace(/\-+$/, '') // Remove trailing -
);
return slugifiedHeading;
}
2 changes: 1 addition & 1 deletion src/vs/base/browser/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1392,7 +1392,7 @@ const _ttpSafeInnerHtml = window.trustedTypes?.createPolicy('safeInnerHtml', {
export function safeInnerHtml(node: HTMLElement, value: string): void {

const options = _extInsaneOptions({
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
allowedAttributes: {
'a': ['href', 'x-dispatch'],
'button': ['data-href', 'x-dispatch'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
return new Promise<void>(resolve => { r = resolve; });
}

setScrollTop(scrollTop: number): void {
this._list.scrollTop = scrollTop;
}

triggerScroll(event: IMouseWheelEvent) {
this._list.triggerScrollFromMouseWheelEvent(event);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd
import { isCompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl';
import { CellKind, CellToolbarLocation, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBar, CompactView, FocusIndicator, InsertToolbarLocation, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, CellToolbarLocation, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBar, CompactView, FocusIndicator, InsertToolbarLocation, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, NotebookCellEditorOptionsCustomizations, ConsolidatedRunButton } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
Expand Down Expand Up @@ -651,11 +651,6 @@ configurationRegistry.registerConfiguration({
type: 'boolean',
default: true
},
[ExperimentalUseMarkdownRenderer]: {
description: nls.localize('notebook.experimental.useMarkdownRenderer.description', "Enable/disable using the new extensible markdown renderer."),
type: 'boolean',
default: true
},
[CellToolbarVisibility]: {
markdownDescription: nls.localize('notebook.cellToolbarVisibility.description', "Whether the cell toolbar should appear on hover or click."),
type: 'string',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export interface IFocusNotebookCellOptions {
export interface ICommonNotebookEditor {
readonly creationOptions: INotebookEditorCreationOptions;
getCellOutputLayoutInfo(cell: IGenericCellViewModel): INotebookCellOutputLayoutInfo;
setScrollTop(scrollTop: number): void;
triggerScroll(event: IMouseWheelEvent): void;
getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel;
getCellById(cellId: string): IGenericCellViewModel | undefined;
Expand Down Expand Up @@ -749,7 +750,6 @@ export interface MarkdownCellRenderTemplate extends BaseCellRenderTemplate {
foldingIndicator: HTMLElement;
focusIndicatorBottom: HTMLElement;
currentEditor?: ICodeEditor;
readonly useRenderer: boolean;
}

export interface CodeCellRenderTemplate extends BaseCellRenderTemplate {
Expand Down
74 changes: 13 additions & 61 deletions src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { contrastBorder, diffInserted, diffRemoved, editorBackground, errorForeground, focusBorder, foreground, listInactiveSelectionBackground, registerColor, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground, textPreformatForeground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorMemento } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorMemento } from 'vs/workbench/common/editor';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors';
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, INotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
Expand All @@ -58,12 +54,10 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, ExperimentalUseMarkdownRenderer, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { readFontInfo } from 'vs/editor/browser/config/configuration';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
Expand All @@ -83,6 +77,10 @@ export class ListViewInfoAccessor extends Disposable {
super();
}

setScrollTop(scrollTop: number) {
this.list.scrollTop = scrollTop;
}

revealCellRangeInView(range: ICellRange) {
return this.list.revealElementsInView(range);
}
Expand Down Expand Up @@ -209,7 +207,6 @@ export function getDefaultNotebookCreationOptions() {
}

export class NotebookEditorWidget extends Disposable implements INotebookEditor {
private static readonly EDITOR_MEMENTOS = new Map<string, EditorMemento<unknown>>();
private _overlayContainer!: HTMLElement;
private _notebookTopToolbarContainer!: HTMLElement;
private _notebookTopToolbar!: NotebookEditorToolbar;
Expand Down Expand Up @@ -240,7 +237,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
private _outputRenderer: OutputRenderer;
protected readonly _contributions = new Map<string, INotebookEditorContribution>();
private _scrollBeyondLastLine: boolean;
private readonly _memento: Memento;
private readonly _onDidFocusEmitter = this._register(new Emitter<void>());
public readonly onDidFocus = this._onDidFocusEmitter.event;
private readonly _onDidBlurEmitter = this._register(new Emitter<void>());
Expand All @@ -254,8 +250,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor

private _isDisposed: boolean = false;

private useRenderer = false;

get isDisposed() {
return this._isDisposed;
}
Expand Down Expand Up @@ -328,8 +322,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
constructor(
readonly creationOptions: INotebookEditorCreationOptions,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@INotebookRendererMessagingService private readonly notebookRendererMessaging: INotebookRendererMessagingService,
@INotebookEditorService private readonly notebookEditorService: INotebookEditorService,
@INotebookKernelService private readonly notebookKernelService: INotebookKernelService,
Expand All @@ -346,7 +338,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.isEmbedded = creationOptions.isEmbedded ?? false;
this._readOnly = creationOptions.isReadOnly ?? false;

this.useRenderer = !!this.configurationService.getValue<boolean>(ExperimentalUseMarkdownRenderer) && !accessibilityService.isScreenReaderOptimized();
this._notebookOptions = new NotebookOptions(this.configurationService);
this._register(this._notebookOptions);
this._viewContext = new ViewContext(this._notebookOptions, new NotebookEventDispatcher());
Expand All @@ -364,8 +355,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}
}));

this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService);

this._outputRenderer = this._register(new OutputRenderer(this, this.instantiationService));
this._scrollBeyondLastLine = this.configurationService.getValue<boolean>('editor.scrollBeyondLastLine');

Expand Down Expand Up @@ -526,23 +515,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

//#region Editor Core

protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
const mementoKey = `${NOTEBOOK_EDITOR_ID}${key}`;

let editorMemento = NotebookEditorWidget.EDITOR_MEMENTOS.get(mementoKey);
if (!editorMemento) {
editorMemento = new EditorMemento(NOTEBOOK_EDITOR_ID, key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
NotebookEditorWidget.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
}

return editorMemento as IEditorMemento<T>;
}

protected getMemento(scope: StorageScope): MementoObject {
return this._memento.getMemento(scope, StorageTarget.MACHINE);
}

private _updateForNotebookConfiguration() {
if (!this._overlayContainer) {
return;
Expand Down Expand Up @@ -822,7 +794,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const getScopedContextKeyService = (container: HTMLElement) => this._list.contextKeyService.createScoped(container);
const renderers = [
this.instantiationService.createInstance(CodeCellRenderer, this, this._renderedEditors, this._dndController, getScopedContextKeyService),
this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService, { useRenderer: this.useRenderer }),
this.instantiationService.createInstance(MarkupCellRenderer, this, this._dndController, this._renderedEditors, getScopedContextKeyService),
];

renderers.forEach(renderer => {
Expand Down Expand Up @@ -1307,11 +1279,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}));

// init rendering
if (this.useRenderer) {
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);
} else {
this._list.attachViewModel(this.viewModel);
}
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);

mark(textModel.uri, 'customMarkdownLoaded');

Expand Down Expand Up @@ -1788,6 +1756,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return this._listViewInfoAccessor.getVisibleRangesPlusViewportAboveBelow();
}

setScrollTop(scrollTop: number) {
this._listViewInfoAccessor.setScrollTop(scrollTop);
}

triggerScroll(event: IMouseWheelEvent) {
this._listViewInfoAccessor.triggerScroll(event);
}
Expand Down Expand Up @@ -2300,11 +2272,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

async createMarkupPreview(cell: MarkupCellViewModel) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}

if (!this._webview) {
return;
}
Expand All @@ -2329,11 +2296,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

async unhideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}

if (!this._webview) {
return;
}
Expand All @@ -2346,11 +2308,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

async hideMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}

if (!this._webview || !cells.length) {
return;
}
Expand All @@ -2363,11 +2320,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

async deleteMarkupPreviews(cells: readonly MarkupCellViewModel[]) {
if (!this.useRenderer) {
// TODO: handle case where custom renderer is disabled?
return;
}

if (!this._webview) {
return;
}
Expand All @@ -2380,7 +2332,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
}

private async updateSelectedMarkdownPreviews(): Promise<void> {
if (!this.useRenderer || !this._webview) {
if (!this._webview) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ var requirejs = (function() {
// console.log('ack top ', top, ' version: ', data.version, ' - ', date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds());
break;
}
case 'scroll-to-reveal':
{
this.notebookEditor.setScrollTop(data.scrollTop);
break;
}
case 'did-scroll-wheel':
{
this.notebookEditor.triggerScroll({
Expand Down Expand Up @@ -644,7 +649,14 @@ var requirejs = (function() {
this.notebookEditor.didEndDragMarkupCell(data.cellId);
break;
}

case 'renderedMarkup':
{
const cell = this.notebookEditor.getCellById(data.cellId);
if (cell instanceof MarkupCellViewModel) {
cell.renderedHtml = data.html;
}
break;
}
case 'telemetryFoundRenderedMarkdownMath':
{
this.telemetryService.publicLog2<{}, {}>('notebook/markdown/renderedLatex', {});
Expand Down
Loading