mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 01:25:37 -05:00
Merge from vscode e3c4990c67c40213af168300d1cfeb71d680f877 (#16569)
This commit is contained in:
@@ -35,4 +35,4 @@ export class CommandManager {
|
||||
|
||||
this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export { OpenDocumentLinkCommand } from './openDocumentLink';
|
||||
export { ShowPreviewCommand, ShowPreviewToSideCommand, ShowLockedPreviewToSideCommand } from './showPreview';
|
||||
export { ShowSourceCommand } from './showSource';
|
||||
export { RefreshPreviewCommand } from './refreshPreview';
|
||||
export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
|
||||
export { MoveCursorToPositionCommand } from './moveCursorToPosition';
|
||||
export { ToggleLockCommand } from './toggleLock';
|
||||
export { OpenDocumentLinkCommand } from './openDocumentLink';
|
||||
export { RefreshPreviewCommand } from './refreshPreview';
|
||||
export { ReloadPlugins } from './reloadPlugins';
|
||||
export { RenderDocument } from './renderDocument';
|
||||
export { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview';
|
||||
export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
|
||||
export { ShowSourceCommand } from './showSource';
|
||||
export { ToggleLockCommand } from './toggleLock';
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ export class RefreshPreviewCommand implements Command {
|
||||
this.engine.cleanCache();
|
||||
this.webviewManager.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Command } from '../commandManager';
|
||||
import { MarkdownPreviewManager } from '../features/previewManager';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
|
||||
export class ReloadPlugins implements Command {
|
||||
public readonly id = 'markdown.api.reloadPlugins';
|
||||
|
||||
public constructor(
|
||||
private readonly webviewManager: MarkdownPreviewManager,
|
||||
private readonly engine: MarkdownEngine,
|
||||
) { }
|
||||
|
||||
public execute(): void {
|
||||
this.engine.reloadPlugins();
|
||||
this.engine.cleanCache();
|
||||
this.webviewManager.refresh();
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,4 @@ export class ShowPreviewSecuritySelectorCommand implements Command {
|
||||
this.previewSecuritySelector.showSecuritySelectorForResource(vscode.window.activeTextEditor.document.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,4 @@ export class ShowSourceCommand implements Command {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@ export class ToggleLockCommand implements Command {
|
||||
public execute() {
|
||||
this.previewManager.toggleLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,12 +52,7 @@ function registerMarkdownLanguageFeatures(
|
||||
): vscode.Disposable {
|
||||
const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
|
||||
|
||||
const charPattern = '(\\p{Alphabetic}|\\p{Number}|\\p{Nonspacing_Mark})';
|
||||
|
||||
return vscode.Disposable.from(
|
||||
vscode.languages.setLanguageConfiguration('markdown', {
|
||||
wordPattern: new RegExp(`${charPattern}((${charPattern}|[_])?${charPattern})*`, 'ug'),
|
||||
}),
|
||||
vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider),
|
||||
vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()),
|
||||
vscode.languages.registerFoldingRangeProvider(selector, new MarkdownFoldingProvider(engine)),
|
||||
@@ -85,5 +80,6 @@ function registerMarkdownCommands(
|
||||
commandManager.register(new commands.OpenDocumentLinkCommand(engine));
|
||||
commandManager.register(new commands.ToggleLockCommand(previewManager));
|
||||
commandManager.register(new commands.RenderDocument(engine));
|
||||
commandManager.register(new commands.ReloadPlugins(previewManager, engine));
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
@@ -72,4 +72,4 @@ export default class MDDocumentSymbolProvider implements vscode.DocumentSymbolPr
|
||||
private getSymbolName(entry: TocEntry): string {
|
||||
return '#'.repeat(entry.level) + ' ' + entry.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import { Logger } from '../logger';
|
||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { isMarkdownFile } from '../util/file';
|
||||
import { normalizeResource, WebviewResourceProvider } from '../util/resources';
|
||||
import { getVisibleLine, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||
import { WebviewResourceProvider } from '../util/resources';
|
||||
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||
import { MarkdownContentProvider, MarkdownContentProviderOutput } from './previewContentProvider';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
@@ -120,6 +120,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
private imageInfo: { readonly id: string, readonly width: number, readonly height: number; }[] = [];
|
||||
|
||||
private readonly _fileWatchersBySrc = new Map</* src: */ string, vscode.FileSystemWatcher>();
|
||||
private readonly _onScrollEmitter = this._register(new vscode.EventEmitter<LastScrollLocation>());
|
||||
public readonly onScroll = this._onScrollEmitter.event;
|
||||
|
||||
constructor(
|
||||
webview: vscode.WebviewPanel,
|
||||
@@ -324,7 +326,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
|
||||
private onDidScrollPreview(line: number) {
|
||||
this.line = line;
|
||||
|
||||
this._onScrollEmitter.fire({ line: this.line, uri: this._resource });
|
||||
const config = this._previewConfigurations.loadAndCacheConfiguration(this._resource);
|
||||
if (!config.scrollEditorWithPreview) {
|
||||
return;
|
||||
@@ -336,13 +338,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
|
||||
this.isScrolling = true;
|
||||
const sourceLine = Math.floor(line);
|
||||
const fraction = line - sourceLine;
|
||||
const text = editor.document.lineAt(sourceLine).text;
|
||||
const start = Math.floor(fraction * text.length);
|
||||
editor.revealRange(
|
||||
new vscode.Range(sourceLine, start, sourceLine + 1, 0),
|
||||
vscode.TextEditorRevealType.AtTop);
|
||||
scrollEditorToLine(line, editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,12 +423,12 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
baseRoots.push(vscode.Uri.file(path.dirname(this._resource.fsPath)));
|
||||
}
|
||||
|
||||
return baseRoots.map(root => normalizeResource(this._resource, root));
|
||||
return baseRoots;
|
||||
}
|
||||
|
||||
|
||||
private async onDidClickPreviewLink(href: string) {
|
||||
let [hrefPath, fragment] = decodeURIComponent(href).split('#');
|
||||
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
|
||||
|
||||
if (hrefPath[0] !== '/') {
|
||||
// We perviously already resolve absolute paths.
|
||||
@@ -460,7 +456,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
//#region WebviewResourceProvider
|
||||
|
||||
asWebviewUri(resource: vscode.Uri) {
|
||||
return this._webviewPanel.webview.asWebviewUri(normalizeResource(this._resource, resource));
|
||||
return this._webviewPanel.webview.asWebviewUri(resource);
|
||||
}
|
||||
|
||||
get cspSource() {
|
||||
@@ -497,11 +493,13 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
|
||||
webview: vscode.WebviewPanel,
|
||||
contentProvider: MarkdownContentProvider,
|
||||
previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
topmostLineMonitor: TopmostLineMonitor,
|
||||
logger: Logger,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
engine: MarkdownEngine,
|
||||
scrollLine?: number,
|
||||
): StaticMarkdownPreview {
|
||||
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, logger, contributionProvider, engine);
|
||||
return new StaticMarkdownPreview(webview, resource, contentProvider, previewConfigurations, topmostLineMonitor, logger, contributionProvider, engine, scrollLine);
|
||||
}
|
||||
|
||||
private readonly preview: MarkdownPreview;
|
||||
@@ -511,13 +509,15 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
|
||||
resource: vscode.Uri,
|
||||
contentProvider: MarkdownContentProvider,
|
||||
private readonly _previewConfigurations: MarkdownPreviewConfigurationManager,
|
||||
topmostLineMonitor: TopmostLineMonitor,
|
||||
logger: Logger,
|
||||
contributionProvider: MarkdownContributionProvider,
|
||||
engine: MarkdownEngine,
|
||||
scrollLine?: number,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, undefined, {
|
||||
const topScrollLocation = scrollLine ? new StartingScrollLine(scrollLine) : undefined;
|
||||
this.preview = this._register(new MarkdownPreview(this._webviewPanel, resource, topScrollLocation, {
|
||||
getAdditionalState: () => { return {}; },
|
||||
openPreviewLinkToMarkdownFile: () => { /* todo */ }
|
||||
}, engine, contentProvider, _previewConfigurations, logger, contributionProvider));
|
||||
@@ -529,6 +529,16 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
|
||||
this._register(this._webviewPanel.onDidChangeViewState(e => {
|
||||
this._onDidChangeViewState.fire(e);
|
||||
}));
|
||||
|
||||
this._register(this.preview.onScroll((scrollInfo) => {
|
||||
topmostLineMonitor.setPreviousEditorLine(scrollInfo);
|
||||
}));
|
||||
|
||||
this._register(topmostLineMonitor.onDidChanged(event => {
|
||||
if (this.preview.isPreviewOf(event.resource)) {
|
||||
this.preview.scrollTo(event.line);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private readonly _onDispose = this._register(new vscode.EventEmitter<void>());
|
||||
@@ -789,3 +799,18 @@ export class DynamicMarkdownPreview extends Disposable implements ManagedMarkdow
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the top-most visible line of `editor` to be at `line`
|
||||
*/
|
||||
export function scrollEditorToLine(
|
||||
line: number,
|
||||
editor: vscode.TextEditor
|
||||
) {
|
||||
const sourceLine = Math.floor(line);
|
||||
const fraction = line - sourceLine;
|
||||
const text = editor.document.lineAt(sourceLine).text;
|
||||
const start = Math.floor(fraction * text.length);
|
||||
editor.revealRange(
|
||||
new vscode.Range(sourceLine, start, sourceLine + 1, 0),
|
||||
vscode.TextEditorRevealType.AtTop);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { equals } from '../util/arrays';
|
||||
|
||||
export class MarkdownPreviewConfiguration {
|
||||
public static getForResource(resource: vscode.Uri) {
|
||||
@@ -21,7 +22,7 @@ export class MarkdownPreviewConfiguration {
|
||||
public readonly lineHeight: number;
|
||||
public readonly fontSize: number;
|
||||
public readonly fontFamily: string | undefined;
|
||||
public readonly styles: string[];
|
||||
public readonly styles: readonly string[];
|
||||
|
||||
private constructor(resource: vscode.Uri) {
|
||||
const editorConfig = vscode.workspace.getConfiguration('editor', resource);
|
||||
@@ -49,7 +50,7 @@ export class MarkdownPreviewConfiguration {
|
||||
}
|
||||
|
||||
public isEqualTo(otherConfig: MarkdownPreviewConfiguration) {
|
||||
for (let key in this) {
|
||||
for (const key in this) {
|
||||
if (this.hasOwnProperty(key) && key !== 'styles') {
|
||||
if (this[key] !== otherConfig[key]) {
|
||||
return false;
|
||||
@@ -57,17 +58,7 @@ export class MarkdownPreviewConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
// Check styles
|
||||
if (this.styles.length !== otherConfig.styles.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < this.styles.length; ++i) {
|
||||
if (this.styles[i] !== otherConfig.styles[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return equals(this.styles, otherConfig.styles);
|
||||
}
|
||||
|
||||
[key: string]: any;
|
||||
|
||||
@@ -81,7 +81,7 @@ export class MarkdownContentProvider {
|
||||
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
|
||||
const csp = this.getCsp(resourceProvider, sourceUri, nonce);
|
||||
|
||||
const body = await this.engine.render(markdownDocument);
|
||||
const body = await this.engine.render(markdownDocument, resourceProvider);
|
||||
const html = `<!DOCTYPE html>
|
||||
<html style="${escapeAttribute(this.getSettingsOverrideStyles(config))}">
|
||||
<head>
|
||||
|
||||
@@ -9,9 +9,10 @@ import { MarkdownEngine } from '../markdownEngine';
|
||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||
import { Disposable, disposeAll } from '../util/dispose';
|
||||
import { TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||
import { DynamicMarkdownPreview, ManagedMarkdownPreview, StartingScrollFragment, StaticMarkdownPreview } from './preview';
|
||||
import { DynamicMarkdownPreview, ManagedMarkdownPreview, StartingScrollFragment, StaticMarkdownPreview, scrollEditorToLine } from './preview';
|
||||
import { MarkdownPreviewConfigurationManager } from './previewConfig';
|
||||
import { MarkdownContentProvider } from './previewContentProvider';
|
||||
import { isMarkdownFile } from '../util/file';
|
||||
|
||||
export interface DynamicPreviewSettings {
|
||||
readonly resourceColumn: vscode.ViewColumn;
|
||||
@@ -75,6 +76,17 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
super();
|
||||
this._register(vscode.window.registerWebviewPanelSerializer(DynamicMarkdownPreview.viewType, this));
|
||||
this._register(vscode.window.registerCustomEditorProvider(this.customEditorViewType, this));
|
||||
|
||||
this._register(vscode.window.onDidChangeActiveTextEditor(textEditor => {
|
||||
|
||||
// When at a markdown file, apply existing scroll settings
|
||||
if (textEditor && textEditor.document && isMarkdownFile(textEditor.document)) {
|
||||
const line = this._topmostLineMonitor.getPreviousEditorLineByUri(textEditor.document.uri);
|
||||
if (line) {
|
||||
scrollEditorToLine(line, textEditor);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public refresh() {
|
||||
@@ -160,14 +172,18 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
document: vscode.TextDocument,
|
||||
webview: vscode.WebviewPanel
|
||||
): Promise<void> {
|
||||
const lineNumber = this._topmostLineMonitor.getPreviousEditorLineByUri(document.uri);
|
||||
const preview = StaticMarkdownPreview.revive(
|
||||
document.uri,
|
||||
webview,
|
||||
this._contentProvider,
|
||||
this._previewConfigurations,
|
||||
this._topmostLineMonitor,
|
||||
this._logger,
|
||||
this._contributions,
|
||||
this._engine);
|
||||
this._engine,
|
||||
lineNumber
|
||||
);
|
||||
this.registerStaticPreview(preview);
|
||||
}
|
||||
|
||||
@@ -175,11 +191,14 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
resource: vscode.Uri,
|
||||
previewSettings: DynamicPreviewSettings
|
||||
): DynamicMarkdownPreview {
|
||||
const activeTextEditorURI = vscode.window.activeTextEditor?.document.uri;
|
||||
const scrollLine = (activeTextEditorURI?.toString() === resource.toString()) ? vscode.window.activeTextEditor?.visibleRanges[0].start.line : undefined;
|
||||
const preview = DynamicMarkdownPreview.create(
|
||||
{
|
||||
resource,
|
||||
resourceColumn: previewSettings.resourceColumn,
|
||||
locked: previewSettings.locked,
|
||||
line: scrollLine,
|
||||
},
|
||||
previewSettings.previewColumn,
|
||||
this._contentProvider,
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkdownIt, Token } from 'markdown-it';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
|
||||
import { Slugifier } from './slugify';
|
||||
import { SkinnyTextDocument } from './tableOfContentsProvider';
|
||||
import { hash } from './util/hash';
|
||||
import { isOfScheme, MarkdownFileExtensions, Schemes } from './util/links';
|
||||
import { isOfScheme, Schemes } from './util/links';
|
||||
import { WebviewResourceProvider } from './util/resources';
|
||||
|
||||
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
|
||||
|
||||
@@ -62,12 +62,14 @@ export interface RenderOutput {
|
||||
|
||||
interface RenderEnv {
|
||||
containingImages: { src: string }[];
|
||||
currentDocument: vscode.Uri | undefined;
|
||||
resourceProvider: WebviewResourceProvider | undefined;
|
||||
}
|
||||
|
||||
export class MarkdownEngine {
|
||||
|
||||
private md?: Promise<MarkdownIt>;
|
||||
|
||||
private currentDocument?: vscode.Uri;
|
||||
private _slugCount = new Map<string, number>();
|
||||
private _tokenCache = new TokenCache();
|
||||
|
||||
@@ -113,7 +115,7 @@ export class MarkdownEngine {
|
||||
this.addLineNumberRenderer(md, renderName);
|
||||
}
|
||||
|
||||
this.addImageStabilizer(md);
|
||||
this.addImageRenderer(md);
|
||||
this.addFencedRenderer(md);
|
||||
this.addLinkNormalizer(md);
|
||||
this.addLinkValidator(md);
|
||||
@@ -128,6 +130,10 @@ export class MarkdownEngine {
|
||||
return md;
|
||||
}
|
||||
|
||||
public reloadPlugins() {
|
||||
this.md = undefined;
|
||||
}
|
||||
|
||||
private tokenizeDocument(
|
||||
document: SkinnyTextDocument,
|
||||
config: MarkdownItConfig,
|
||||
@@ -138,27 +144,18 @@ export class MarkdownEngine {
|
||||
return cached;
|
||||
}
|
||||
|
||||
this.currentDocument = document.uri;
|
||||
|
||||
const tokens = this.tokenizeString(document.getText(), engine);
|
||||
this._tokenCache.update(document, config, tokens);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public async renderText(document: vscode.Uri, text: string): Promise<string> { // {{SQL CARBON EDIT}} - Add renderText method
|
||||
const config = this.getConfig(document);
|
||||
const engine = await this.getEngine(config);
|
||||
this.currentDocument = document;
|
||||
return engine.render(text, config);
|
||||
}
|
||||
|
||||
private tokenizeString(text: string, engine: MarkdownIt) {
|
||||
this._slugCount = new Map<string, number>();
|
||||
|
||||
return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {});
|
||||
}
|
||||
|
||||
public async render(input: SkinnyTextDocument | string): Promise<RenderOutput> {
|
||||
public async render(input: SkinnyTextDocument | string, resourceProvider?: WebviewResourceProvider): Promise<RenderOutput> {
|
||||
const config = this.getConfig(typeof input === 'string' ? undefined : input.uri);
|
||||
const engine = await this.getEngine(config);
|
||||
|
||||
@@ -167,7 +164,9 @@ export class MarkdownEngine {
|
||||
: this.tokenizeDocument(input, config, engine);
|
||||
|
||||
const env: RenderEnv = {
|
||||
containingImages: []
|
||||
containingImages: [],
|
||||
currentDocument: typeof input === 'string' ? undefined : input.uri,
|
||||
resourceProvider,
|
||||
};
|
||||
|
||||
const html = engine.renderer.render(tokens, {
|
||||
@@ -200,12 +199,12 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addLineNumberRenderer(md: any, ruleName: string): void {
|
||||
private addLineNumberRenderer(md: MarkdownIt, ruleName: string): void {
|
||||
const original = md.renderer.rules[ruleName];
|
||||
md.renderer.rules[ruleName] = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules[ruleName] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
|
||||
const token = tokens[idx];
|
||||
if (token.map && token.map.length) {
|
||||
token.attrSet('data-line', token.map[0]);
|
||||
token.attrSet('data-line', token.map[0] + '');
|
||||
token.attrJoin('class', 'code-line');
|
||||
}
|
||||
|
||||
@@ -217,9 +216,9 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addImageStabilizer(md: any): void {
|
||||
private addImageRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: any, idx: number, options: any, env: RenderEnv, self: any) => {
|
||||
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: RenderEnv, self: any) => {
|
||||
const token = tokens[idx];
|
||||
token.attrJoin('class', 'loading');
|
||||
|
||||
@@ -228,6 +227,11 @@ export class MarkdownEngine {
|
||||
env.containingImages?.push({ src });
|
||||
const imgHash = hash(src);
|
||||
token.attrSet('id', `image-hash-${imgHash}`);
|
||||
|
||||
if (!token.attrGet('data-src')) {
|
||||
token.attrSet('src', this.toResourceUri(src, env.currentDocument, env.resourceProvider));
|
||||
token.attrSet('data-src', src);
|
||||
}
|
||||
}
|
||||
|
||||
if (original) {
|
||||
@@ -238,9 +242,9 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addFencedRenderer(md: any): void {
|
||||
private addFencedRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules['fenced'];
|
||||
md.renderer.rules['fenced'] = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
|
||||
const token = tokens[idx];
|
||||
if (token.map && token.map.length) {
|
||||
token.attrJoin('class', 'hljs');
|
||||
@@ -250,7 +254,7 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addLinkNormalizer(md: any): void {
|
||||
private addLinkNormalizer(md: MarkdownIt): void {
|
||||
const normalizeLink = md.normalizeLink;
|
||||
md.normalizeLink = (link: string) => {
|
||||
try {
|
||||
@@ -259,43 +263,6 @@ export class MarkdownEngine {
|
||||
return normalizeLink(vscode.Uri.parse(link).with({ scheme: vscode.env.uriScheme }).toString());
|
||||
}
|
||||
|
||||
// Support file:// links
|
||||
if (isOfScheme(Schemes.file, link)) {
|
||||
// Ensure link is relative by prepending `/` so that it uses the <base> element URI
|
||||
// when resolving the absolute URL
|
||||
return normalizeLink('/' + link.replace(/^file:/, 'file'));
|
||||
}
|
||||
|
||||
// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
|
||||
if (!/^[a-z\-]+:/i.test(link)) {
|
||||
// Use a fake scheme for parsing
|
||||
let uri = vscode.Uri.parse('markdown-link:' + link);
|
||||
|
||||
// Relative paths should be resolved correctly inside the preview but we need to
|
||||
// handle absolute paths specially (for images) to resolve them relative to the workspace root
|
||||
if (uri.path[0] === '/') {
|
||||
const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!);
|
||||
if (root) {
|
||||
const fileUri = vscode.Uri.joinPath(root.uri, uri.fsPath).with({
|
||||
fragment: uri.fragment,
|
||||
query: uri.query,
|
||||
});
|
||||
|
||||
// Ensure fileUri is relative by prepending `/` so that it uses the <base> element URI
|
||||
// when resolving the absolute URL
|
||||
uri = vscode.Uri.parse('markdown-link:' + '/' + fileUri.toString(true).replace(/^\S+?:/, fileUri.scheme));
|
||||
}
|
||||
}
|
||||
|
||||
const extname = path.extname(uri.fsPath);
|
||||
|
||||
if (uri.fragment && (extname === '' || MarkdownFileExtensions.includes(extname))) {
|
||||
uri = uri.with({
|
||||
fragment: this.slugifier.fromHeading(uri.fragment).value
|
||||
});
|
||||
}
|
||||
return normalizeLink(uri.toString(true).replace(/^markdown-link:/, ''));
|
||||
}
|
||||
} catch (e) {
|
||||
// noop
|
||||
}
|
||||
@@ -303,7 +270,7 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addLinkValidator(md: any): void {
|
||||
private addLinkValidator(md: MarkdownIt): void {
|
||||
const validateLink = md.validateLink;
|
||||
md.validateLink = (link: string) => {
|
||||
return validateLink(link)
|
||||
@@ -313,9 +280,9 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addNamedHeaders(md: any): void {
|
||||
private addNamedHeaders(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.heading_open;
|
||||
md.renderer.rules.heading_open = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules.heading_open = (tokens: 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 = this.slugifier.fromHeading(title);
|
||||
|
||||
@@ -338,12 +305,12 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addLinkRenderer(md: any): void {
|
||||
const old_render = md.renderer.rules.link_open || ((tokens: any, idx: number, options: any, _env: any, self: any) => {
|
||||
private addLinkRenderer(md: MarkdownIt): void {
|
||||
const old_render = md.renderer.rules.link_open || ((tokens: Token[], idx: number, options: any, _env: any, self: any) => {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
});
|
||||
|
||||
md.renderer.rules.link_open = (tokens: any, idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules.link_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
|
||||
const token = tokens[idx];
|
||||
const hrefIndex = token.attrIndex('href');
|
||||
if (hrefIndex >= 0) {
|
||||
@@ -353,6 +320,50 @@ export class MarkdownEngine {
|
||||
return old_render(tokens, idx, options, env, self);
|
||||
};
|
||||
}
|
||||
|
||||
private toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
|
||||
try {
|
||||
// Support file:// links
|
||||
if (isOfScheme(Schemes.file, href)) {
|
||||
const uri = vscode.Uri.parse(href);
|
||||
if (resourceProvider) {
|
||||
return resourceProvider.asWebviewUri(uri).toString(true);
|
||||
}
|
||||
// Not sure how to resolve this
|
||||
return href;
|
||||
}
|
||||
|
||||
// If original link doesn't look like a url with a scheme, assume it must be a link to a file in workspace
|
||||
if (!/^[a-z\-]+:/i.test(href)) {
|
||||
// Use a fake scheme for parsing
|
||||
let uri = vscode.Uri.parse('markdown-link:' + href);
|
||||
|
||||
// Relative paths should be resolved correctly inside the preview but we need to
|
||||
// handle absolute paths specially to resolve them relative to the workspace root
|
||||
if (uri.path[0] === '/' && currentDocument) {
|
||||
const root = vscode.workspace.getWorkspaceFolder(currentDocument);
|
||||
if (root) {
|
||||
uri = vscode.Uri.joinPath(root.uri, uri.fsPath).with({
|
||||
fragment: uri.fragment,
|
||||
query: uri.query,
|
||||
});
|
||||
|
||||
if (resourceProvider) {
|
||||
return resourceProvider.asWebviewUri(uri).toString(true);
|
||||
} else {
|
||||
uri = uri.with({ scheme: 'markdown-link' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uri.toString(true).replace(/^markdown-link:/, '');
|
||||
}
|
||||
|
||||
return href;
|
||||
} catch {
|
||||
return href;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getMarkdownOptions(md: () => MarkdownIt) {
|
||||
|
||||
@@ -37,11 +37,11 @@ suite('markdown.engine', () => {
|
||||
const engine = createNewMarkdownEngine();
|
||||
assert.deepStrictEqual((await engine.render(input)), {
|
||||
html: '<p data-line="0" class="code-line">'
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435"> '
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435" data-src="img.png"> '
|
||||
+ '<a href="no-img.png" data-href="no-img.png"></a> '
|
||||
+ '<img src="http://example.org/img.png" alt="" class="loading" id="image-hash--1903814170"> '
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435"> '
|
||||
+ '<img src="./img2.png" alt="" class="loading" id="image-hash-265238964">'
|
||||
+ '<img src="http://example.org/img.png" alt="" class="loading" id="image-hash--1903814170" data-src="http://example.org/img.png"> '
|
||||
+ '<img src="img.png" alt="" class="loading" id="image-hash--754511435" data-src="img.png"> '
|
||||
+ '<img src="./img2.png" alt="" class="loading" id="image-hash-265238964" data-src="./img2.png">'
|
||||
+ '</p>\n'
|
||||
,
|
||||
containingImages: [{ src: 'img.png' }, { src: 'http://example.org/img.png' }, { src: 'img.png' }, { src: './img2.png' }],
|
||||
|
||||
@@ -18,7 +18,7 @@ suite('markdown.WorkspaceSymbolProvider', () => {
|
||||
test('Should not return anything for empty workspace', async () => {
|
||||
const provider = new MarkdownWorkspaceSymbolProvider(symbolProvider, new InMemoryWorkspaceMarkdownDocumentProvider([]));
|
||||
|
||||
assert.deepEqual(await provider.provideWorkspaceSymbols(''), []);
|
||||
assert.deepStrictEqual(await provider.provideWorkspaceSymbols(''), []);
|
||||
});
|
||||
|
||||
test('Should return symbols from workspace with one markdown file', async () => {
|
||||
|
||||
@@ -15,4 +15,4 @@ export function equals<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, itemEq
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ export abstract class Disposable {
|
||||
protected get isDisposed() {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,4 @@ import * as vscode from 'vscode';
|
||||
|
||||
export function isMarkdownFile(document: vscode.TextDocument) {
|
||||
return document.languageId === 'markdown';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,4 +36,4 @@ class LazyValue<T> implements Lazy<T> {
|
||||
|
||||
export function lazy<T>(getValue: () => T): Lazy<T> {
|
||||
return new LazyValue<T>(getValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ export const Schemes = {
|
||||
data: 'data:',
|
||||
vscode: 'vscode:',
|
||||
'vscode-insiders': 'vscode-insiders:',
|
||||
'vscode-resource': 'vscode-resource:',
|
||||
};
|
||||
|
||||
const knownSchemes = [
|
||||
|
||||
@@ -11,23 +11,3 @@ export interface WebviewResourceProvider {
|
||||
readonly cspSource: string;
|
||||
}
|
||||
|
||||
export function normalizeResource(
|
||||
base: vscode.Uri,
|
||||
resource: vscode.Uri
|
||||
): vscode.Uri {
|
||||
// If we have a windows path and are loading a workspace with an authority,
|
||||
// make sure we use a unc path with an explicit localhost authority.
|
||||
//
|
||||
// Otherwise, the `<base>` rule will insert the authority into the resolved resource
|
||||
// URI incorrectly.
|
||||
if (base.authority && !resource.authority) {
|
||||
const driveMatch = resource.path.match(/^\/(\w):\//);
|
||||
if (driveMatch) {
|
||||
return vscode.Uri.file(`\\\\localhost\\${driveMatch[1]}$\\${resource.fsPath.replace(/^\w:\\/, '')}`).with({
|
||||
fragment: resource.fragment,
|
||||
query: resource.query
|
||||
});
|
||||
}
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
|
||||
@@ -7,18 +7,32 @@ import * as vscode from 'vscode';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { isMarkdownFile } from './file';
|
||||
|
||||
export interface LastScrollLocation {
|
||||
readonly line: number;
|
||||
readonly uri: vscode.Uri;
|
||||
}
|
||||
|
||||
export class TopmostLineMonitor extends Disposable {
|
||||
|
||||
private readonly pendingUpdates = new Map<string, number>();
|
||||
private readonly throttle = 50;
|
||||
private previousEditorInfo = new Map<string, LastScrollLocation>();
|
||||
public isPrevEditorCustom = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const line = getVisibleLine(vscode.window.activeTextEditor);
|
||||
this.setPreviousEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 });
|
||||
}
|
||||
|
||||
this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
|
||||
if (isMarkdownFile(event.textEditor.document)) {
|
||||
const line = getVisibleLine(event.textEditor);
|
||||
if (typeof line === 'number') {
|
||||
this.updateLine(event.textEditor.document.uri, line);
|
||||
this.setPreviousEditorLine({ uri: event.textEditor.document.uri, line: line });
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -27,7 +41,16 @@ export class TopmostLineMonitor extends Disposable {
|
||||
private readonly _onChanged = this._register(new vscode.EventEmitter<{ readonly resource: vscode.Uri, readonly line: number }>());
|
||||
public readonly onDidChanged = this._onChanged.event;
|
||||
|
||||
private updateLine(
|
||||
public setPreviousEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this.previousEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
|
||||
}
|
||||
|
||||
public getPreviousEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this.previousEditorInfo.get(resource.toString());
|
||||
return scrollLoc?.line;
|
||||
}
|
||||
|
||||
public updateLine(
|
||||
resource: vscode.Uri,
|
||||
line: number
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user