mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge vscode source through 1.62 release (#19981)
* Build breaks 1 * Build breaks * Build breaks * Build breaks * More build breaks * Build breaks (#2512) * Runtime breaks * Build breaks * Fix dialog location break * Update typescript * Fix ASAR break issue * Unit test breaks * Update distro * Fix breaks in ADO builds (#2513) * Bump to node 16 * Fix hygiene errors * Bump distro * Remove reference to node type * Delete vscode specific extension * Bump to node 16 in CI yaml * Skip integration tests in CI builds (while fixing) * yarn.lock update * Bump moment dependency in remote yarn * Fix drop-down chevron style * Bump to node 16 * Remove playwrite from ci.yaml * Skip building build scripts in hygine check
This commit is contained in:
@@ -55,8 +55,8 @@ body.showEditorSelection .code-line {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.showEditorSelection .code-active-line:before,
|
||||
body.showEditorSelection .code-line:hover:before {
|
||||
body.showEditorSelection :not(tr).code-active-line:before,
|
||||
body.showEditorSelection :not(tr).code-line:hover:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const MarkdownIt: typeof import('markdown-it') = require('markdown-it');
|
||||
import * as DOMPurify from 'dompurify';
|
||||
import type * as markdownIt from 'markdown-it';
|
||||
import type * as MarkdownItToken from 'markdown-it/lib/token';
|
||||
import type { ActivationFunction } from 'vscode-notebook-renderer';
|
||||
|
||||
const sanitizerOptions: DOMPurify.Config = {
|
||||
ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
};
|
||||
|
||||
export function activate(ctx: { workspace: { isTrusted: boolean } }) {
|
||||
export const activate: ActivationFunction<void> = (ctx) => {
|
||||
let markdownIt = new MarkdownIt({
|
||||
html: true
|
||||
});
|
||||
addNamedHeaderRendering(markdownIt);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.classList.add('markdown-style');
|
||||
style.textContent = `
|
||||
.emptyMarkdownCell::before {
|
||||
content: "${document.documentElement.style.getPropertyValue('--notebook-cell-markup-empty-content')}";
|
||||
@@ -54,16 +54,19 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
line-height: 31px;
|
||||
margin: 0;
|
||||
margin-bottom: 13px;
|
||||
font-size: 2.25em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 19px;
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.9em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h1,
|
||||
@@ -141,10 +144,13 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
const template = document.createElement('template');
|
||||
template.classList.add('markdown-style');
|
||||
template.content.appendChild(style);
|
||||
document.head.appendChild(template);
|
||||
|
||||
return {
|
||||
renderOutputItem: (outputInfo: { text(): string }, element: HTMLElement) => {
|
||||
renderOutputItem: (outputInfo, element) => {
|
||||
let previewNode: HTMLElement;
|
||||
if (!element.shadowRoot) {
|
||||
const previewRoot = element.attachShadow({ mode: 'open' });
|
||||
@@ -155,15 +161,19 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
|
||||
previewRoot.appendChild(defaultStyles.cloneNode(true));
|
||||
|
||||
// And then contributed styles
|
||||
for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) {
|
||||
previewRoot.appendChild(markdownStyleNode.cloneNode(true));
|
||||
for (const element of document.getElementsByClassName('markdown-style')) {
|
||||
if (element instanceof HTMLTemplateElement) {
|
||||
previewRoot.appendChild(element.content.cloneNode(true));
|
||||
} else {
|
||||
previewRoot.appendChild(element.cloneNode(true));
|
||||
}
|
||||
}
|
||||
|
||||
previewNode = document.createElement('div');
|
||||
previewNode.id = 'preview';
|
||||
previewRoot.appendChild(previewNode);
|
||||
} else {
|
||||
previewNode = element.shadowRoot.getElementById('preview')! as HTMLElement; // {{SQL CARBON EDIT}} Cast to fix compilation error
|
||||
previewNode = element.shadowRoot.getElementById('preview')!;
|
||||
}
|
||||
|
||||
const text = outputInfo.text();
|
||||
@@ -174,24 +184,24 @@ export function activate(ctx: { workspace: { isTrusted: boolean } }) {
|
||||
previewNode.classList.remove('emptyMarkdownCell');
|
||||
|
||||
const unsanitizedRenderedMarkdown = markdownIt.render(text);
|
||||
previewNode.innerHTML = ctx.workspace.isTrusted
|
||||
previewNode.innerHTML = <any>(ctx.workspace.isTrusted
|
||||
? unsanitizedRenderedMarkdown
|
||||
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions);
|
||||
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions));
|
||||
}
|
||||
},
|
||||
extendMarkdownIt: (f: (md: typeof markdownIt) => void) => {
|
||||
f(markdownIt);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
|
||||
function addNamedHeaderRendering(md: InstanceType<typeof 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, '');
|
||||
md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
|
||||
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
|
||||
let slug = slugFromHeading(title);
|
||||
|
||||
if (slugCounter.has(slug)) {
|
||||
@@ -202,13 +212,12 @@ function addNamedHeaderRendering(md: markdownIt.MarkdownIt): void {
|
||||
slugCounter.set(slug, 0);
|
||||
}
|
||||
|
||||
tokens[idx].attrs = tokens[idx].attrs || [];
|
||||
tokens[idx].attrs.push(['id', slug]);
|
||||
tokens[idx].attrSet('id', slug);
|
||||
|
||||
if (originalHeaderOpen) {
|
||||
return originalHeaderOpen(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options, env, self);
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"jsx": "react",
|
||||
"moduleResolution": "Node",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2018",
|
||||
|
||||
@@ -356,14 +356,14 @@
|
||||
"highlight.js": "^10.4.1",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-front-matter": "^0.2.1",
|
||||
"vscode-extension-telemetry": "0.2.8",
|
||||
"vscode-extension-telemetry": "0.4.2",
|
||||
"vscode-nls": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dompurify": "^2.2.3",
|
||||
"@types/highlight.js": "10.1.0",
|
||||
"@types/dompurify": "^2.3.1",
|
||||
"@types/lodash.throttle": "^4.1.3",
|
||||
"@types/markdown-it": "0.0.2",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
"@types/vscode-notebook-renderer": "^1.60.0",
|
||||
"@types/vscode-webview": "^1.57.0",
|
||||
"lodash.throttle": "^4.1.1"
|
||||
},
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { Command } from '../commandManager';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContentsProvider } from '../tableOfContentsProvider';
|
||||
import { isMarkdownFile } from '../util/file';
|
||||
import { extname } from '../util/path';
|
||||
|
||||
import { openDocumentLink } from '../util/openDocumentLink';
|
||||
|
||||
type UriComponents = {
|
||||
readonly scheme?: string;
|
||||
@@ -25,11 +22,6 @@ export interface OpenDocumentLinkArgs {
|
||||
readonly fromResource: UriComponents;
|
||||
}
|
||||
|
||||
enum OpenMarkdownLinks {
|
||||
beside = 'beside',
|
||||
currentGroup = 'currentGroup',
|
||||
}
|
||||
|
||||
export class OpenDocumentLinkCommand implements Command {
|
||||
private static readonly id = '_markdown.openDocumentLink';
|
||||
public readonly id = OpenDocumentLinkCommand.id;
|
||||
@@ -60,95 +52,9 @@ export class OpenDocumentLinkCommand implements Command {
|
||||
) { }
|
||||
|
||||
public async execute(args: OpenDocumentLinkArgs) {
|
||||
return OpenDocumentLinkCommand.execute(this.engine, args);
|
||||
}
|
||||
|
||||
public static async execute(engine: MarkdownEngine, args: OpenDocumentLinkArgs): Promise<void> {
|
||||
const fromResource = vscode.Uri.parse('').with(args.fromResource);
|
||||
|
||||
const targetResource = reviveUri(args.parts);
|
||||
|
||||
const column = this.getViewColumn(fromResource);
|
||||
|
||||
const didOpen = await this.tryOpen(engine, targetResource, args, column);
|
||||
if (didOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (extname(targetResource.path) === '') {
|
||||
await this.tryOpen(engine, targetResource.with({ path: targetResource.path + '.md' }), args, column);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static async tryOpen(engine: MarkdownEngine, resource: vscode.Uri, args: OpenDocumentLinkArgs, column: vscode.ViewColumn): Promise<boolean> {
|
||||
const tryUpdateForActiveFile = async (): Promise<boolean> => {
|
||||
if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
|
||||
if (vscode.window.activeTextEditor.document.uri.fsPath === resource.fsPath) {
|
||||
await this.tryRevealLine(engine, vscode.window.activeTextEditor, args.fragment);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (await tryUpdateForActiveFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let stat: vscode.FileStat;
|
||||
try {
|
||||
stat = await vscode.workspace.fs.stat(resource);
|
||||
if (stat.type === vscode.FileType.Directory) {
|
||||
await vscode.commands.executeCommand('revealInExplorer', resource);
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
// If resource doesn't exist, execute `vscode.open` either way so an error
|
||||
// notification is shown to the user with a create file action #113475
|
||||
}
|
||||
|
||||
try {
|
||||
await vscode.commands.executeCommand('vscode.open', resource, column);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
return tryUpdateForActiveFile();
|
||||
}
|
||||
|
||||
private static getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
|
||||
const config = vscode.workspace.getConfiguration('markdown', resource);
|
||||
const openLinks = config.get<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
|
||||
switch (openLinks) {
|
||||
case OpenMarkdownLinks.beside:
|
||||
return vscode.ViewColumn.Beside;
|
||||
case OpenMarkdownLinks.currentGroup:
|
||||
default:
|
||||
return vscode.ViewColumn.Active;
|
||||
}
|
||||
}
|
||||
|
||||
private static async tryRevealLine(engine: MarkdownEngine, editor: vscode.TextEditor, fragment?: string) {
|
||||
if (fragment) {
|
||||
const toc = new TableOfContentsProvider(engine, editor.document);
|
||||
const entry = await toc.lookup(fragment);
|
||||
if (entry) {
|
||||
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
|
||||
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
|
||||
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
|
||||
}
|
||||
const lineNumberFragment = fragment.match(/^L(\d+)$/i);
|
||||
if (lineNumberFragment) {
|
||||
const line = +lineNumberFragment[1] - 1;
|
||||
if (!isNaN(line)) {
|
||||
const lineStart = new vscode.Range(line, 0, line, 0);
|
||||
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
|
||||
return editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
const targetResource = reviveUri(args.parts).with({ fragment: args.fragment });
|
||||
return openDocumentLink(this.engine, targetResource, fromResource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,36 +64,3 @@ function reviveUri(parts: any) {
|
||||
}
|
||||
return vscode.Uri.parse('').with(parts);
|
||||
}
|
||||
|
||||
export async function resolveLinkToMarkdownFile(path: string): Promise<vscode.Uri | undefined> {
|
||||
try {
|
||||
const standardLink = await tryResolveLinkToMarkdownFile(path);
|
||||
if (standardLink) {
|
||||
return standardLink;
|
||||
}
|
||||
} catch {
|
||||
// Noop
|
||||
}
|
||||
|
||||
// If no extension, try with `.md` extension
|
||||
if (extname(path) === '') {
|
||||
return tryResolveLinkToMarkdownFile(path + '.md');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function tryResolveLinkToMarkdownFile(path: string): Promise<vscode.Uri | undefined> {
|
||||
const resource = vscode.Uri.file(path);
|
||||
|
||||
let document: vscode.TextDocument;
|
||||
try {
|
||||
document = await vscode.workspace.openTextDocument(resource);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (isMarkdownFile(document)) {
|
||||
return document.uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ async function showPreview(
|
||||
const resourceColumn = (vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One;
|
||||
webviewManager.openDynamicPreview(resource, {
|
||||
resourceColumn: resourceColumn,
|
||||
previewColumn: previewSettings.sideBySide ? resourceColumn + 1 : resourceColumn,
|
||||
previewColumn: previewSettings.sideBySide ? vscode.ViewColumn.Beside : resourceColumn,
|
||||
locked: !!previewSettings.locked
|
||||
});
|
||||
|
||||
|
||||
@@ -83,3 +83,4 @@ function registerMarkdownCommands(
|
||||
commandManager.register(new commands.ReloadPlugins(previewManager, engine));
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@ function parseLink(
|
||||
document: vscode.TextDocument,
|
||||
link: string,
|
||||
): { uri: vscode.Uri, tooltip?: string } | undefined {
|
||||
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
|
||||
|
||||
const cleanLink = stripAngleBrackets(link);
|
||||
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(cleanLink);
|
||||
if (externalSchemeUri) {
|
||||
// Normalize VS Code links to target currently running version
|
||||
if (isOfScheme(Schemes.vscode, link) || isOfScheme(Schemes['vscode-insiders'], link)) {
|
||||
@@ -89,6 +91,15 @@ function extractDocumentLink(
|
||||
}
|
||||
}
|
||||
|
||||
/* Used to strip brackets from the markdown link
|
||||
<http://example.com> will be transformed to
|
||||
http://example.com
|
||||
*/
|
||||
export function stripAngleBrackets(link: string) {
|
||||
const bracketMatcher = /^<(.*)>$/;
|
||||
return link.replace(bracketMatcher, '$1');
|
||||
}
|
||||
|
||||
export default class LinkProvider implements vscode.DocumentLinkProvider {
|
||||
private readonly linkPattern = /(\[((!\[[^\]]*?\]\(\s*)([^\s\(\)]+?)\s*\)\]|(?:\\\]|[^\]])*\])\(\s*)(([^\s\(\)]|\([^\s\(\)]*?\))+)\s*(".*?")?\)/g;
|
||||
private readonly referenceLinkPattern = /(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]/g;
|
||||
|
||||
@@ -3,13 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Token } from 'markdown-it';
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContentsProvider } from '../tableOfContentsProvider';
|
||||
|
||||
const rangeLimit = 5000;
|
||||
|
||||
interface MarkdownItTokenWithMap extends Token {
|
||||
map: [number, number];
|
||||
}
|
||||
|
||||
export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvider {
|
||||
|
||||
constructor(
|
||||
@@ -84,10 +88,14 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi
|
||||
const isStartRegion = (t: string) => /^\s*<!--\s*#?region\b.*-->/.test(t);
|
||||
const isEndRegion = (t: string) => /^\s*<!--\s*#?endregion\b.*-->/.test(t);
|
||||
|
||||
const isRegionMarker = (token: Token) =>
|
||||
token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
|
||||
const isRegionMarker = (token: Token): token is MarkdownItTokenWithMap =>
|
||||
!!token.map && token.type === 'html_block' && (isStartRegion(token.content) || isEndRegion(token.content));
|
||||
|
||||
const isFoldableToken = (token: Token): token is MarkdownItTokenWithMap => {
|
||||
if (!token.map) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isFoldableToken = (token: Token): boolean => {
|
||||
switch (token.type) {
|
||||
case 'fence':
|
||||
case 'list_item_open':
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { OpenDocumentLinkCommand, resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
|
||||
import { Logger } from '../logger';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { MarkdownContributionProvider } from '../markdownExtensions';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { isMarkdownFile } from '../util/file';
|
||||
import { openDocumentLink, resolveDocumentLink, resolveLinkToMarkdownFile } from '../util/openDocumentLink';
|
||||
import * as path from '../util/path';
|
||||
import { WebviewResourceProvider } from '../util/resources';
|
||||
import { getVisibleLine, LastScrollLocation, TopmostLineMonitor } from '../util/topmostLineMonitor';
|
||||
@@ -355,7 +355,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
}
|
||||
}
|
||||
|
||||
vscode.workspace.openTextDocument(this._resource)
|
||||
await vscode.workspace.openTextDocument(this._resource)
|
||||
.then(vscode.window.showTextDocument)
|
||||
.then(undefined, () => {
|
||||
vscode.window.showErrorMessage(localize('preview.clickOpenFailed', 'Could not open {0}', this._resource.toString()));
|
||||
@@ -406,6 +406,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
private getWebviewOptions(): vscode.WebviewOptions {
|
||||
return {
|
||||
enableScripts: true,
|
||||
enableForms: false,
|
||||
localResourceRoots: this.getLocalResourceRoots()
|
||||
};
|
||||
}
|
||||
@@ -428,29 +429,19 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider {
|
||||
|
||||
|
||||
private async onDidClickPreviewLink(href: string) {
|
||||
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
|
||||
|
||||
if (hrefPath[0] !== '/') {
|
||||
// We perviously already resolve absolute paths.
|
||||
// Now make sure we handle relative file paths
|
||||
const dirnameUri = vscode.Uri.parse(path.dirname(this.resource.path));
|
||||
hrefPath = vscode.Uri.joinPath(dirnameUri, hrefPath).path;
|
||||
} else {
|
||||
// Handle any normalized file paths
|
||||
hrefPath = vscode.Uri.parse(hrefPath.replace('/file', '')).path;
|
||||
}
|
||||
const targetResource = resolveDocumentLink(href, this.resource);
|
||||
|
||||
const config = vscode.workspace.getConfiguration('markdown', this.resource);
|
||||
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
|
||||
if (openLinks === 'inPreview') {
|
||||
const markdownLink = await resolveLinkToMarkdownFile(hrefPath);
|
||||
const markdownLink = await resolveLinkToMarkdownFile(targetResource);
|
||||
if (markdownLink) {
|
||||
this.delegate.openPreviewLinkToMarkdownFile(markdownLink, fragment);
|
||||
this.delegate.openPreviewLinkToMarkdownFile(markdownLink, targetResource.fragment);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
OpenDocumentLinkCommand.execute(this.engine, { parts: { path: hrefPath }, fragment, fromResource: this.resource.toJSON() });
|
||||
return openDocumentLink(this.engine, targetResource, this.resource);
|
||||
}
|
||||
|
||||
//#region WebviewResourceProvider
|
||||
@@ -531,7 +522,7 @@ export class StaticMarkdownPreview extends Disposable implements ManagedMarkdown
|
||||
}));
|
||||
|
||||
this._register(this.preview.onScroll((scrollInfo) => {
|
||||
topmostLineMonitor.setPreviousEditorLine(scrollInfo);
|
||||
topmostLineMonitor.setPreviousStaticEditorLine(scrollInfo);
|
||||
}));
|
||||
|
||||
this._register(topmostLineMonitor.onDidChanged(event => {
|
||||
|
||||
@@ -81,7 +81,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
|
||||
// When at a markdown file, apply existing scroll settings
|
||||
if (textEditor && textEditor.document && isMarkdownFile(textEditor.document)) {
|
||||
const line = this._topmostLineMonitor.getPreviousEditorLineByUri(textEditor.document.uri);
|
||||
const line = this._topmostLineMonitor.getPreviousStaticEditorLineByUri(textEditor.document.uri);
|
||||
if (line) {
|
||||
scrollEditorToLine(line, textEditor);
|
||||
}
|
||||
@@ -172,7 +172,7 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview
|
||||
document: vscode.TextDocument,
|
||||
webview: vscode.WebviewPanel
|
||||
): Promise<void> {
|
||||
const lineNumber = this._topmostLineMonitor.getPreviousEditorLineByUri(document.uri);
|
||||
const lineNumber = this._topmostLineMonitor.getPreviousTextEditorLineByUri(document.uri);
|
||||
const preview = StaticMarkdownPreview.revive(
|
||||
document.uri,
|
||||
webview,
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { Token } from 'markdown-it';
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContentsProvider, TocEntry } from '../tableOfContentsProvider';
|
||||
|
||||
interface MarkdownItTokenWithMap extends Token {
|
||||
map: [number, number];
|
||||
}
|
||||
|
||||
export default class MarkdownSmartSelect implements vscode.SelectionRangeProvider {
|
||||
|
||||
constructor(
|
||||
@@ -96,8 +100,8 @@ function createHeaderRange(header: TocEntry, isClosestHeaderToPosition: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): Token[] {
|
||||
const enclosingTokens = tokens.filter(token => token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
|
||||
function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, parent?: vscode.SelectionRange): MarkdownItTokenWithMap[] {
|
||||
const enclosingTokens = tokens.filter((token): token is MarkdownItTokenWithMap => !!token.map && (token.map[0] <= position.line && token.map[1] > position.line) && (!parent || (token.map[0] >= parent.range.start.line && token.map[1] <= parent.range.end.line + 1)) && isBlockElement(token));
|
||||
if (enclosingTokens.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -105,7 +109,7 @@ function getBlockTokensForPosition(tokens: Token[], position: vscode.Position, p
|
||||
return sortedTokens;
|
||||
}
|
||||
|
||||
function createBlockRange(block: Token, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
|
||||
function createBlockRange(block: MarkdownItTokenWithMap, document: vscode.TextDocument, cursorLine: number, parent?: vscode.SelectionRange): vscode.SelectionRange | undefined {
|
||||
if (block.type === 'fence') {
|
||||
return createFencedRange(block, cursorLine, document, parent);
|
||||
} else {
|
||||
@@ -144,7 +148,7 @@ function createInlineRange(document: vscode.TextDocument, cursorPosition: vscode
|
||||
return inlineCodeBlockSelection || linkSelection || comboSelection || boldSelection || italicSelection;
|
||||
}
|
||||
|
||||
function createFencedRange(token: Token, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
|
||||
function createFencedRange(token: MarkdownItTokenWithMap, cursorLine: number, document: vscode.TextDocument, parent?: vscode.SelectionRange): vscode.SelectionRange {
|
||||
const startLine = token.map[0];
|
||||
const endLine = token.map[1] - 1;
|
||||
const onFenceLine = cursorLine === startLine || cursorLine === endLine;
|
||||
|
||||
@@ -41,7 +41,7 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work
|
||||
|
||||
for (let i = 0; i < resources.length; i += maxConcurrent) {
|
||||
const resourceBatch = resources.slice(i, i + maxConcurrent);
|
||||
const documentBatch = (await Promise.all(resourceBatch.map(this.getMarkdownDocument))).filter((doc) => !!doc) as SkinnyTextDocument[];
|
||||
const documentBatch = (await Promise.all(resourceBatch.map(x => this.getMarkdownDocument(x)))).filter((doc) => !!doc) as SkinnyTextDocument[];
|
||||
docList.push(...documentBatch);
|
||||
}
|
||||
return docList;
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MarkdownIt, Token } from 'markdown-it';
|
||||
import MarkdownIt = require('markdown-it');
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions';
|
||||
import { Slugifier } from './slugify';
|
||||
@@ -14,11 +15,34 @@ import { WebviewResourceProvider } from './util/resources';
|
||||
|
||||
const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
|
||||
|
||||
interface MarkdownItConfig {
|
||||
readonly breaks: boolean;
|
||||
readonly linkify: boolean;
|
||||
readonly typographer: boolean;
|
||||
}
|
||||
/**
|
||||
* Adds begin line index to the output via the 'data-line' data attribute.
|
||||
*/
|
||||
const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => {
|
||||
// Set the attribute on every possible token.
|
||||
md.core.ruler.push('source_map_data_attribute', (state): void => {
|
||||
for (const token of state.tokens) {
|
||||
if (token.map && token.type !== 'inline') {
|
||||
token.attrSet('data-line', String(token.map[0]));
|
||||
token.attrJoin('class', 'code-line');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// The 'html_block' renderer doesn't respect `attrs`. We need to insert a marker.
|
||||
const originalHtmlBlockRenderer = md.renderer.rules['html_block'];
|
||||
if (originalHtmlBlockRenderer) {
|
||||
md.renderer.rules['html_block'] = (tokens, idx, options, env, self) => (
|
||||
`<div ${self.renderAttrs(tokens[idx])} ></div>\n` +
|
||||
originalHtmlBlockRenderer(tokens, idx, options, env, self)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The markdown-it options that we expose in the settings.
|
||||
*/
|
||||
type MarkdownItConfig = Readonly<Required<Pick<MarkdownIt.Options, 'breaks' | 'linkify' | 'typographer'>>>;
|
||||
|
||||
class TokenCache {
|
||||
private cachedDocument?: {
|
||||
@@ -85,14 +109,15 @@ export class MarkdownEngine {
|
||||
|
||||
private async getEngine(config: MarkdownItConfig): Promise<MarkdownIt> {
|
||||
if (!this.md) {
|
||||
this.md = import('markdown-it').then(async markdownIt => {
|
||||
this.md = (async () => {
|
||||
const markdownIt = await import('markdown-it');
|
||||
let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md));
|
||||
|
||||
for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) {
|
||||
try {
|
||||
md = (await plugin)(md);
|
||||
} catch {
|
||||
// noop
|
||||
} catch (e) {
|
||||
console.error('Could not load markdown it plugin', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,18 +136,15 @@ export class MarkdownEngine {
|
||||
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||
});
|
||||
|
||||
for (const renderName of ['paragraph_open', 'heading_open', 'image', 'code_block', 'fence', 'blockquote_open', 'list_item_open']) {
|
||||
this.addLineNumberRenderer(md, renderName);
|
||||
}
|
||||
|
||||
this.addImageRenderer(md);
|
||||
this.addFencedRenderer(md);
|
||||
this.addLinkNormalizer(md);
|
||||
this.addLinkValidator(md);
|
||||
this.addNamedHeaders(md);
|
||||
this.addLinkRenderer(md);
|
||||
md.use(pluginSourceMap);
|
||||
return md;
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
const md = await this.md!;
|
||||
@@ -170,7 +192,7 @@ export class MarkdownEngine {
|
||||
};
|
||||
|
||||
const html = engine.renderer.render(tokens, {
|
||||
...(engine as any).options,
|
||||
...engine.options,
|
||||
...config
|
||||
}, env);
|
||||
|
||||
@@ -199,26 +221,9 @@ export class MarkdownEngine {
|
||||
};
|
||||
}
|
||||
|
||||
private addLineNumberRenderer(md: MarkdownIt, ruleName: string): void {
|
||||
const original = md.renderer.rules[ruleName];
|
||||
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.attrJoin('class', 'code-line');
|
||||
}
|
||||
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options, env, self);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private addImageRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: Token[], idx: number, options: any, env: RenderEnv, self: any) => {
|
||||
md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => {
|
||||
const token = tokens[idx];
|
||||
token.attrJoin('class', 'loading');
|
||||
|
||||
@@ -237,20 +242,24 @@ export class MarkdownEngine {
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options, env, self);
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private addFencedRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules['fenced'];
|
||||
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
if (token.map && token.map.length) {
|
||||
token.attrJoin('class', 'hljs');
|
||||
}
|
||||
|
||||
return original(tokens, idx, options, env, self);
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -282,8 +291,8 @@ export class MarkdownEngine {
|
||||
|
||||
private addNamedHeaders(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.heading_open;
|
||||
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, '');
|
||||
md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => {
|
||||
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
|
||||
let slug = this.slugifier.fromHeading(title);
|
||||
|
||||
if (this._slugCount.has(slug.value)) {
|
||||
@@ -294,30 +303,31 @@ export class MarkdownEngine {
|
||||
this._slugCount.set(slug.value, 0);
|
||||
}
|
||||
|
||||
tokens[idx].attrs = tokens[idx].attrs || [];
|
||||
tokens[idx].attrs.push(['id', slug.value]);
|
||||
tokens[idx].attrSet('id', slug.value);
|
||||
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options, env, self);
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
const original = md.renderer.rules.link_open;
|
||||
|
||||
md.renderer.rules.link_open = (tokens: Token[], idx: number, options: any, env: any, self: any) => {
|
||||
md.renderer.rules.link_open = (tokens: Token[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const hrefIndex = token.attrIndex('href');
|
||||
if (hrefIndex >= 0) {
|
||||
const href = token.attrs[hrefIndex][1];
|
||||
token.attrPush(['data-href', href]);
|
||||
const href = token.attrGet('href');
|
||||
// A string, including empty string, may be `href`.
|
||||
if (typeof href === 'string') {
|
||||
token.attrSet('data-href', href);
|
||||
}
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
return old_render(tokens, idx, options, env, self);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -366,7 +376,7 @@ export class MarkdownEngine {
|
||||
}
|
||||
}
|
||||
|
||||
async function getMarkdownOptions(md: () => MarkdownIt) {
|
||||
async function getMarkdownOptions(md: () => MarkdownIt): Promise<MarkdownIt.Options> {
|
||||
const hljs = await import('highlight.js');
|
||||
return {
|
||||
html: true,
|
||||
|
||||
@@ -60,6 +60,10 @@ export class TableOfContentsProvider {
|
||||
const existingSlugEntries = new Map<string, { count: number }>();
|
||||
|
||||
for (const heading of tokens.filter(token => token.type === 'heading_open')) {
|
||||
if (!heading.map) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineNumber = heading.map[0];
|
||||
const line = document.lineAt(lineNumber);
|
||||
|
||||
|
||||
@@ -94,6 +94,17 @@ suite('Markdown Document links', () => {
|
||||
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 1);
|
||||
});
|
||||
|
||||
|
||||
test('Should navigate to line number within non-md file', async () => {
|
||||
await withFileContents(testFileA, '[b](sub/foo.txt#L3)');
|
||||
|
||||
const [link] = await getLinksForFile(testFileA);
|
||||
await executeLink(link);
|
||||
|
||||
assertActiveDocumentUri(workspaceFile('sub', 'foo.txt'));
|
||||
assert.strictEqual(vscode.window.activeTextEditor!.selection.start.line, 2);
|
||||
});
|
||||
|
||||
test('Should navigate to fragment within current file', async () => {
|
||||
await withFileContents(testFileA, joinLines(
|
||||
'[](a#header)',
|
||||
|
||||
@@ -15,7 +15,7 @@ const testFileName = vscode.Uri.file('test.md');
|
||||
suite('markdown.engine', () => {
|
||||
suite('rendering', () => {
|
||||
const input = '# hello\n\nworld!';
|
||||
const output = '<h1 id="hello" data-line="0" class="code-line">hello</h1>\n'
|
||||
const output = '<h1 data-line="0" class="code-line" id="hello">hello</h1>\n'
|
||||
+ '<p data-line="2" class="code-line">world!</p>\n';
|
||||
|
||||
test('Renders a document', async () => {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import { MarkdownEngine } from '../markdownEngine';
|
||||
import { TableOfContentsProvider } from '../tableOfContentsProvider';
|
||||
import { isMarkdownFile } from './file';
|
||||
import { extname } from './path';
|
||||
|
||||
export interface OpenDocumentLinkArgs {
|
||||
readonly parts: vscode.Uri;
|
||||
readonly fragment: string;
|
||||
readonly fromResource: vscode.Uri;
|
||||
}
|
||||
|
||||
enum OpenMarkdownLinks {
|
||||
beside = 'beside',
|
||||
currentGroup = 'currentGroup',
|
||||
}
|
||||
|
||||
export function resolveDocumentLink(href: string, markdownFile: vscode.Uri): vscode.Uri {
|
||||
let [hrefPath, fragment] = href.split('#').map(c => decodeURIComponent(c));
|
||||
|
||||
if (hrefPath[0] === '/') {
|
||||
// Absolute path. Try to resolve relative to the workspace
|
||||
const workspace = vscode.workspace.getWorkspaceFolder(markdownFile);
|
||||
if (workspace) {
|
||||
return vscode.Uri.joinPath(workspace.uri, hrefPath.slice(1)).with({ fragment });
|
||||
}
|
||||
}
|
||||
|
||||
// Relative path. Resolve relative to the md file
|
||||
const dirnameUri = markdownFile.with({ path: path.dirname(markdownFile.path) });
|
||||
return vscode.Uri.joinPath(dirnameUri, hrefPath).with({ fragment });
|
||||
}
|
||||
|
||||
export async function openDocumentLink(engine: MarkdownEngine, targetResource: vscode.Uri, fromResource: vscode.Uri): Promise<void> {
|
||||
const column = getViewColumn(fromResource);
|
||||
|
||||
if (await tryNavigateToFragmentInActiveEditor(engine, targetResource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let targetResourceStat: vscode.FileStat | undefined;
|
||||
try {
|
||||
targetResourceStat = await vscode.workspace.fs.stat(targetResource);
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
if (typeof targetResourceStat === 'undefined') {
|
||||
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
|
||||
if (extname(targetResource.path) === '') {
|
||||
const dotMdResource = targetResource.with({ path: targetResource.path + '.md' });
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(dotMdResource);
|
||||
if (stat.type === vscode.FileType.File) {
|
||||
await tryOpenMdFile(engine, dotMdResource, column);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
} else if (targetResourceStat.type === vscode.FileType.Directory) {
|
||||
return vscode.commands.executeCommand('revealInExplorer', targetResource);
|
||||
}
|
||||
|
||||
await tryOpenMdFile(engine, targetResource, column);
|
||||
}
|
||||
|
||||
async function tryOpenMdFile(engine: MarkdownEngine, resource: vscode.Uri, column: vscode.ViewColumn): Promise<boolean> {
|
||||
await vscode.commands.executeCommand('vscode.open', resource.with({ fragment: '' }), column);
|
||||
return tryNavigateToFragmentInActiveEditor(engine, resource);
|
||||
}
|
||||
|
||||
async function tryNavigateToFragmentInActiveEditor(engine: MarkdownEngine, resource: vscode.Uri): Promise<boolean> {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (activeEditor?.document.uri.fsPath === resource.fsPath) {
|
||||
if (isMarkdownFile(activeEditor.document)) {
|
||||
if (await tryRevealLineUsingTocFragment(engine, activeEditor, resource.fragment)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
tryRevealLineUsingLineFragment(activeEditor, resource.fragment);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getViewColumn(resource: vscode.Uri): vscode.ViewColumn {
|
||||
const config = vscode.workspace.getConfiguration('markdown', resource);
|
||||
const openLinks = config.get<OpenMarkdownLinks>('links.openLocation', OpenMarkdownLinks.currentGroup);
|
||||
switch (openLinks) {
|
||||
case OpenMarkdownLinks.beside:
|
||||
return vscode.ViewColumn.Beside;
|
||||
case OpenMarkdownLinks.currentGroup:
|
||||
default:
|
||||
return vscode.ViewColumn.Active;
|
||||
}
|
||||
}
|
||||
|
||||
async function tryRevealLineUsingTocFragment(engine: MarkdownEngine, editor: vscode.TextEditor, fragment: string): Promise<boolean> {
|
||||
const toc = new TableOfContentsProvider(engine, editor.document);
|
||||
const entry = await toc.lookup(fragment);
|
||||
if (entry) {
|
||||
const lineStart = new vscode.Range(entry.line, 0, entry.line, 0);
|
||||
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
|
||||
editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryRevealLineUsingLineFragment(editor: vscode.TextEditor, fragment: string): boolean {
|
||||
const lineNumberFragment = fragment.match(/^L(\d+)$/i);
|
||||
if (lineNumberFragment) {
|
||||
const line = +lineNumberFragment[1] - 1;
|
||||
if (!isNaN(line)) {
|
||||
const lineStart = new vscode.Range(line, 0, line, 0);
|
||||
editor.selection = new vscode.Selection(lineStart.start, lineStart.end);
|
||||
editor.revealRange(lineStart, vscode.TextEditorRevealType.AtTop);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function resolveLinkToMarkdownFile(resource: vscode.Uri): Promise<vscode.Uri | undefined> {
|
||||
try {
|
||||
const standardLink = await tryResolveLinkToMarkdownFile(resource);
|
||||
if (standardLink) {
|
||||
return standardLink;
|
||||
}
|
||||
} catch {
|
||||
// Noop
|
||||
}
|
||||
|
||||
// If no extension, try with `.md` extension
|
||||
if (extname(resource.path) === '') {
|
||||
return tryResolveLinkToMarkdownFile(resource.with({ path: resource.path + '.md' }));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function tryResolveLinkToMarkdownFile(resource: vscode.Uri): Promise<vscode.Uri | undefined> {
|
||||
let document: vscode.TextDocument;
|
||||
try {
|
||||
document = await vscode.workspace.openTextDocument(resource);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (isMarkdownFile(document)) {
|
||||
return document.uri;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -16,15 +16,15 @@ 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;
|
||||
private previousTextEditorInfo = new Map<string, LastScrollLocation>();
|
||||
private previousStaticEditorInfo = new Map<string, LastScrollLocation>();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (vscode.window.activeTextEditor) {
|
||||
const line = getVisibleLine(vscode.window.activeTextEditor);
|
||||
this.setPreviousEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 });
|
||||
this.setPreviousTextEditorLine({ uri: vscode.window.activeTextEditor.document.uri, line: line ?? 0 });
|
||||
}
|
||||
|
||||
this._register(vscode.window.onDidChangeTextEditorVisibleRanges(event => {
|
||||
@@ -32,7 +32,7 @@ export class TopmostLineMonitor extends Disposable {
|
||||
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 });
|
||||
this.setPreviousTextEditorLine({ uri: event.textEditor.document.uri, line: line });
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -41,12 +41,24 @@ 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;
|
||||
|
||||
public setPreviousEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this.previousEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
|
||||
public setPreviousStaticEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this.previousStaticEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
|
||||
}
|
||||
|
||||
public getPreviousEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this.previousEditorInfo.get(resource.toString());
|
||||
public getPreviousStaticEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this.previousStaticEditorInfo.get(resource.toString());
|
||||
this.previousStaticEditorInfo.delete(resource.toString());
|
||||
return scrollLoc?.line;
|
||||
}
|
||||
|
||||
|
||||
public setPreviousTextEditorLine(scrollLocation: LastScrollLocation): void {
|
||||
this.previousTextEditorInfo.set(scrollLocation.uri.toString(), scrollLocation);
|
||||
}
|
||||
|
||||
public getPreviousTextEditorLineByUri(resource: vscode.Uri): number | undefined {
|
||||
const scrollLoc = this.previousTextEditorInfo.get(resource.toString());
|
||||
this.previousTextEditorInfo.delete(resource.toString());
|
||||
return scrollLoc?.line;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
@@ -2,19 +2,17 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/dompurify@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.2.3.tgz#6e89677a07902ac1b6821c345f34bd85da239b08"
|
||||
integrity sha512-CLtc2mZK8+axmrz1JqtpklO/Kvn38arGc8o1l3UVopZaXXuer9ONdZwJ/9f226GrhRLtUmLr9WrvZsRSNpS8og==
|
||||
"@types/dompurify@^2.3.1":
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f"
|
||||
integrity sha512-nnVQSgRVuZ/843oAfhA25eRSNzUFcBPk/LOiw5gm8mD9/X7CNcbRkQu/OsjCewO8+VIYfPxUnXvPEVGenw14+w==
|
||||
dependencies:
|
||||
"@types/trusted-types" "*"
|
||||
|
||||
"@types/highlight.js@10.1.0":
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-10.1.0.tgz#89bb0c202997d7a90a07bd2ec1f7d00c56bb90b4"
|
||||
integrity sha512-77hF2dGBsOgnvZll1vymYiNUtqJ8cJfXPD6GG/2M0aLRc29PkvB7Au6sIDjIEFcSICBhCh2+Pyq6WSRS7LUm6A==
|
||||
dependencies:
|
||||
highlight.js "*"
|
||||
"@types/linkify-it@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
|
||||
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
|
||||
|
||||
"@types/lodash.throttle@^4.1.3":
|
||||
version "4.1.3"
|
||||
@@ -28,16 +26,29 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80"
|
||||
integrity sha512-ufQcVg4daO8xQ5kopxRHanqFdL4AI7ondQkV+2f+7mz3gvp0LkBx2zBRC6hfs3T87mzQFmf5Fck7Fi145Ul6NQ==
|
||||
|
||||
"@types/markdown-it@0.0.2":
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660"
|
||||
integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA=
|
||||
"@types/markdown-it@12.2.3":
|
||||
version "12.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
|
||||
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
|
||||
dependencies:
|
||||
"@types/linkify-it" "*"
|
||||
"@types/mdurl" "*"
|
||||
|
||||
"@types/mdurl@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
|
||||
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
|
||||
|
||||
"@types/trusted-types@*":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
||||
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
|
||||
|
||||
"@types/vscode-notebook-renderer@^1.60.0":
|
||||
version "1.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.60.0.tgz#8a67d561f48ddf46a95dfa9f712a79c72c7b8f7a"
|
||||
integrity sha512-u7TD2uuEZTVuitx0iijOJdKI0JLiQP6PsSBSRy2XmHXUOXcp5p1S56NrjOEDoF+PIHd3NL3eO6KTRSf5nukDqQ==
|
||||
|
||||
"@types/vscode-webview@^1.57.0":
|
||||
version "1.57.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf"
|
||||
@@ -58,7 +69,7 @@ entities@~2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||
|
||||
highlight.js@*, highlight.js@^10.4.1:
|
||||
highlight.js@^10.4.1:
|
||||
version "10.7.1"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955"
|
||||
integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA==
|
||||
@@ -101,10 +112,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
|
||||
integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg==
|
||||
|
||||
vscode-extension-telemetry@0.2.8:
|
||||
version "0.2.8"
|
||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz#670c625c44791237c040cee2cb9f567ca34784ac"
|
||||
integrity sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==
|
||||
vscode-extension-telemetry@0.4.2:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.4.2.tgz#6ef847a80c9cfc207eb15e3a254f235acebb65a5"
|
||||
integrity sha512-y0f51mVoFxHIzULQNCC26TBFIKdEC7uckS3tFoK++OOOl8mU2LlOxgmbd52T/SXoXNg5aI7xqs+4V2ug5ITvKw==
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
|
||||
Reference in New Issue
Block a user