Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -8,20 +8,61 @@
import * as vscode from 'vscode';
import * as path from 'path';
export default class MarkdownDocumentLinkProvider implements vscode.DocumentLinkProvider {
function normalizeLink(document: vscode.TextDocument, link: string, base: string): vscode.Uri {
const uri = vscode.Uri.parse(link);
if (uri.scheme) {
return uri;
}
private _linkPattern = /(\[[^\]]*\]\(\s*?)(((((?=.*\)\)+)|(?=.*\)\]+))[^\s\)]+?)|([^\s]+)))\)/g;
// assume it must be a file
let resourcePath = uri.path;
if (!uri.path) {
resourcePath = document.uri.path;
} else if (uri.path[0] === '/') {
const root = vscode.workspace.getWorkspaceFolder(document.uri);
if (root) {
resourcePath = path.join(root.uri.fsPath, uri.path);
}
} else {
resourcePath = path.join(base, uri.path);
}
constructor() { }
return vscode.Uri.parse(`command:_markdown.openDocumentLink?${encodeURIComponent(JSON.stringify({ fragment: uri.fragment, path: resourcePath }))}`);
}
public provideDocumentLinks(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.DocumentLink[] {
const results: vscode.DocumentLink[] = [];
function matchAll(pattern: RegExp, text: string): Array<RegExpMatchArray> {
const out: RegExpMatchArray[] = [];
pattern.lastIndex = 0;
let match: RegExpMatchArray | null;
while ((match = pattern.exec(text))) {
out.push(match);
}
return out;
}
export default class LinkProvider implements vscode.DocumentLinkProvider {
private linkPattern = /(\[[^\]]*\]\(\s*?)(((((?=.*\)\)+)|(?=.*\)\]+))[^\s\)]+?)|([^\s]+)))\)/g;
private referenceLinkPattern = /(\[([^\]]+)\]\[\s*?)([^\s\]]*?)\]/g;
private definitionPattern = /^([\t ]*\[([^\]]+)\]:\s*)(\S+)/gm;
public provideDocumentLinks(
document: vscode.TextDocument,
_token: vscode.CancellationToken
): vscode.DocumentLink[] {
const base = path.dirname(document.uri.fsPath);
const text = document.getText();
this._linkPattern.lastIndex = 0;
let match: RegExpMatchArray | null;
while ((match = this._linkPattern.exec(text))) {
return this.providerInlineLinks(text, document, base)
.concat(this.provideReferenceLinks(text, document, base));
}
private providerInlineLinks(
text: string,
document: vscode.TextDocument,
base: string
): vscode.DocumentLink[] {
const results: vscode.DocumentLink[] = [];
for (const match of matchAll(this.linkPattern, text)) {
const pre = match[1];
const link = match[2];
const offset = (match.index || 0) + pre.length;
@@ -30,7 +71,7 @@ export default class MarkdownDocumentLinkProvider implements vscode.DocumentLink
try {
results.push(new vscode.DocumentLink(
new vscode.Range(linkStart, linkEnd),
this.normalizeLink(document, link, base)));
normalizeLink(document, link, base)));
} catch (e) {
// noop
}
@@ -39,25 +80,73 @@ export default class MarkdownDocumentLinkProvider implements vscode.DocumentLink
return results;
}
private normalizeLink(document: vscode.TextDocument, link: string, base: string): vscode.Uri {
const uri = vscode.Uri.parse(link);
if (uri.scheme) {
return uri;
}
private provideReferenceLinks(
text: string,
document: vscode.TextDocument,
base: string
): vscode.DocumentLink[] {
const results: vscode.DocumentLink[] = [];
// assume it must be a file
let resourcePath = uri.path;
if (!uri.path) {
resourcePath = document.uri.path;
} else if (uri.path[0] === '/') {
const root = vscode.workspace.getWorkspaceFolder(document.uri);
if (root) {
resourcePath = path.join(root.uri.fsPath, uri.path);
const definitions = this.getDefinitions(text, document);
for (const match of matchAll(this.referenceLinkPattern, text)) {
let linkStart: vscode.Position;
let linkEnd: vscode.Position;
let reference = match[3];
if (reference) { // [text][ref]
const pre = match[1];
const offset = (match.index || 0) + pre.length;
linkStart = document.positionAt(offset);
linkEnd = document.positionAt(offset + reference.length);
} else if (match[2]) { // [ref][]
reference = match[2];
const offset = (match.index || 0) + 1;
linkStart = document.positionAt(offset);
linkEnd = document.positionAt(offset + match[2].length);
} else {
continue;
}
try {
const link = definitions.get(reference);
if (link) {
results.push(new vscode.DocumentLink(
new vscode.Range(linkStart, linkEnd),
vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([link.linkRange.start.line, link.linkRange.start.character]))}`)));
}
} catch (e) {
// noop
}
} else {
resourcePath = path.join(base, uri.path);
}
return vscode.Uri.parse(`command:_markdown.openDocumentLink?${encodeURIComponent(JSON.stringify({ fragment: uri.fragment, path: resourcePath }))}`);
for (const definition of Array.from(definitions.values())) {
try {
results.push(new vscode.DocumentLink(
definition.linkRange,
normalizeLink(document, definition.link, base)));
} catch (e) {
// noop
}
}
return results;
}
private getDefinitions(text: string, document: vscode.TextDocument) {
const out = new Map<string, { link: string, linkRange: vscode.Range }>();
for (const match of matchAll(this.definitionPattern, text)) {
const pre = match[1];
const reference = match[2];
const link = match[3].trim();
const offset = (match.index || 0) + pre.length;
const linkStart = document.positionAt(offset);
const linkEnd = document.positionAt(offset + link.length);
out.set(reference, {
link: link,
linkRange: new vscode.Range(linkStart, linkEnd)
});
}
return out;
}
}

View File

@@ -11,7 +11,7 @@ import * as vscode from 'vscode';
import * as path from 'path';
import TelemetryReporter from 'vscode-extension-telemetry';
import { MarkdownEngine } from './markdownEngine';
import DocumentLinkProvider from './documentLinkProvider';
import LinkProvider from './documentLinkProvider';
import MDDocumentSymbolProvider from './documentSymbolProvider';
import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security';
import { MDDocumentContentProvider, getMarkdownUri, isMarkdownFile } from './previewContentProvider';
@@ -96,18 +96,28 @@ export function activate(context: vscode.ExtensionContext) {
const symbolsProviderRegistration = vscode.languages.registerDocumentSymbolProvider({ language: 'markdown' }, symbolsProvider);
context.subscriptions.push(contentProviderRegistration, symbolsProviderRegistration);
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider('markdown', new DocumentLinkProvider()));
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider('markdown', new LinkProvider()));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreview', (uri) => showPreview(cspArbiter, uri, false)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(cspArbiter, uri, true)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showSource', showSource));
context.subscriptions.push(vscode.commands.registerCommand('_markdown.moveCursorToPosition', (line: number, character: number) => {
if (!vscode.window.activeTextEditor) {
return;
}
const position = new vscode.Position(line, character);
const selection = new vscode.Selection(position, position);
vscode.window.activeTextEditor.revealRange(selection);
vscode.window.activeTextEditor.selection = selection;
}));
context.subscriptions.push(vscode.commands.registerCommand('_markdown.revealLine', (uri, line) => {
const sourceUri = vscode.Uri.parse(decodeURIComponent(uri));
logger.log('revealLine', { uri, sourceUri: sourceUri.toString(), line });
vscode.window.visibleTextEditors
.filter(editor => isMarkdownFile(editor.document) && editor.document.uri.fsPath === sourceUri.fsPath)
.filter(editor => isMarkdownFile(editor.document) && editor.document.uri.toString() === sourceUri.toString())
.forEach(editor => {
const sourceLine = Math.floor(line);
const fraction = line - sourceLine;
@@ -243,13 +253,19 @@ function showPreview(cspArbiter: ExtensionContentSecurityPolicyArbiter, uri?: vs
const thenable = vscode.commands.executeCommand('vscode.previewHtml',
getMarkdownUri(resource),
getViewColumn(sideBySide),
`Preview '${path.basename(resource.fsPath)}'`,
localize('previewTitle', 'Preview {0}', path.basename(resource.fsPath)),
{
allowScripts: true,
allowSvgs: cspArbiter.shouldAllowSvgsForResource(resource)
});
if (telemetryReporter) {
/* __GDPR__
"openPreview" : {
"where" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"how": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
telemetryReporter.sendTelemetryEvent('openPreview', {
where: sideBySide ? 'sideBySide' : 'inPlace',
how: (uri instanceof vscode.Uri) ? 'action' : 'pallete'
@@ -286,7 +302,7 @@ function showSource(mdUri: vscode.Uri) {
const docUri = vscode.Uri.parse(mdUri.query);
for (const editor of vscode.window.visibleTextEditors) {
if (editor.document.uri.scheme === docUri.scheme && editor.document.uri.fsPath === docUri.fsPath) {
if (editor.document.uri.scheme === docUri.scheme && editor.document.uri.toString() === docUri.toString()) {
return vscode.window.showTextDocument(editor.document, editor.viewColumn);
}
}

View File

@@ -35,7 +35,7 @@ export class MarkdownEngine {
}
}
private async getEngine(): Promise<MarkdownIt> {
private async getEngine(resource: vscode.Uri): Promise<MarkdownIt> {
if (!this.md) {
const hljs = await import('highlight.js');
const mdnh = await import('markdown-it-named-headers');
@@ -66,10 +66,10 @@ export class MarkdownEngine {
this.addLinkValidator(this.md);
}
const config = vscode.workspace.getConfiguration('markdown');
const config = vscode.workspace.getConfiguration('markdown', resource);
this.md.set({
breaks: config.get('preview.breaks', false),
linkify: config.get('preview.linkify', true)
breaks: config.get<boolean>('preview.breaks', false),
linkify: config.get<boolean>('preview.linkify', true)
});
return this.md;
}
@@ -94,14 +94,14 @@ export class MarkdownEngine {
}
this.currentDocument = document;
this.firstLine = offset;
const engine = await this.getEngine();
const engine = await this.getEngine(document);
return engine.render(text);
}
public async parse(document: vscode.Uri, source: string): Promise<Token[]> {
const { text, offset } = this.stripFrontmatter(source);
this.currentDocument = document;
const engine = await this.getEngine();
const engine = await this.getEngine(document);
return engine.parse(text, {}).map(token => {
if (token.map) {

View File

@@ -38,8 +38,8 @@ export function getMarkdownUri(uri: vscode.Uri) {
}
class MarkdownPreviewConfig {
public static getCurrentConfig() {
return new MarkdownPreviewConfig();
public static getConfigForResource(resource: vscode.Uri) {
return new MarkdownPreviewConfig(resource);
}
public readonly scrollBeyondLastLine: boolean;
@@ -56,9 +56,9 @@ class MarkdownPreviewConfig {
public readonly fontFamily: string | undefined;
public readonly styles: string[];
private constructor() {
const editorConfig = vscode.workspace.getConfiguration('editor');
const markdownConfig = vscode.workspace.getConfiguration('markdown');
private constructor(resource: vscode.Uri) {
const editorConfig = vscode.workspace.getConfiguration('editor', resource);
const markdownConfig = vscode.workspace.getConfiguration('markdown', resource);
const markdownEditorConfig = vscode.workspace.getConfiguration('[markdown]');
this.scrollBeyondLastLine = editorConfig.get<boolean>('scrollBeyondLastLine', false);
@@ -107,11 +107,41 @@ class MarkdownPreviewConfig {
[key: string]: any;
}
class PreviewConfigManager {
private previewConfigurationsForWorkspaces = new Map<string, MarkdownPreviewConfig>();
public loadAndCacheConfiguration(
resource: vscode.Uri
) {
const config = MarkdownPreviewConfig.getConfigForResource(resource);
this.previewConfigurationsForWorkspaces.set(this.getKey(resource), config);
return config;
}
public shouldUpdateConfiguration(
resource: vscode.Uri
): boolean {
const key = this.getKey(resource);
const currentConfig = this.previewConfigurationsForWorkspaces.get(key);
const newConfig = MarkdownPreviewConfig.getConfigForResource(resource);
return (!currentConfig || !currentConfig.isEqualTo(newConfig));
}
private getKey(
resource: vscode.Uri
): string {
const folder = vscode.workspace.getWorkspaceFolder(resource);
if (!folder) {
return '';
}
return folder.uri.toString();
}
}
export class MDDocumentContentProvider implements vscode.TextDocumentContentProvider {
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
private _waiting: boolean = false;
private config: MarkdownPreviewConfig;
private previewConfigurations = new PreviewConfigManager();
private extraStyles: Array<vscode.Uri> = [];
private extraScripts: Array<vscode.Uri> = [];
@@ -121,9 +151,7 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
private context: vscode.ExtensionContext,
private cspArbiter: ContentSecurityPolicyArbiter,
private logger: Logger
) {
this.config = MarkdownPreviewConfig.getCurrentConfig();
}
) { }
public addScript(resource: vscode.Uri): void {
this.extraScripts.push(resource);
@@ -163,34 +191,34 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
return vscode.Uri.file(path.join(path.dirname(resource.fsPath), href)).toString();
}
private computeCustomStyleSheetIncludes(uri: vscode.Uri): string {
if (this.config.styles && Array.isArray(this.config.styles)) {
return this.config.styles.map((style) => {
return `<link rel="stylesheet" class="code-user-style" data-source="${style.replace(/"/g, '&quot;')}" href="${this.fixHref(uri, style)}" type="text/css" media="screen">`;
private computeCustomStyleSheetIncludes(resource: vscode.Uri, config: MarkdownPreviewConfig): string {
if (config.styles && Array.isArray(config.styles)) {
return config.styles.map(style => {
return `<link rel="stylesheet" class="code-user-style" data-source="${style.replace(/"/g, '&quot;')}" href="${this.fixHref(resource, style)}" type="text/css" media="screen">`;
}).join('\n');
}
return '';
}
private getSettingsOverrideStyles(nonce: string): string {
private getSettingsOverrideStyles(nonce: string, config: MarkdownPreviewConfig): string {
return `<style nonce="${nonce}">
body {
${this.config.fontFamily ? `font-family: ${this.config.fontFamily};` : ''}
${isNaN(this.config.fontSize) ? '' : `font-size: ${this.config.fontSize}px;`}
${isNaN(this.config.lineHeight) ? '' : `line-height: ${this.config.lineHeight};`}
${config.fontFamily ? `font-family: ${config.fontFamily};` : ''}
${isNaN(config.fontSize) ? '' : `font-size: ${config.fontSize}px;`}
${isNaN(config.lineHeight) ? '' : `line-height: ${config.lineHeight};`}
}
</style>`;
}
private getStyles(resource: vscode.Uri, nonce: string): string {
private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfig): string {
const baseStyles = [
this.getMediaPath('markdown.css'),
this.getMediaPath('tomorrow.css')
].concat(this.extraStyles.map(resource => resource.toString()));
return `${baseStyles.map(href => `<link rel="stylesheet" type="text/css" href="${href}">`).join('\n')}
${this.getSettingsOverrideStyles(nonce)}
${this.computeCustomStyleSheetIncludes(resource)}`;
${this.getSettingsOverrideStyles(nonce, config)}
${this.computeCustomStyleSheetIncludes(resource, config)}`;
}
private getScripts(nonce: string): string {
@@ -205,20 +233,20 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
let initialLine: number | undefined = undefined;
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.fsPath === sourceUri.fsPath) {
if (editor && editor.document.uri.toString() === sourceUri.toString()) {
initialLine = editor.selection.active.line;
}
const document = await vscode.workspace.openTextDocument(sourceUri);
this.config = MarkdownPreviewConfig.getCurrentConfig();
const config = this.previewConfigurations.loadAndCacheConfiguration(sourceUri);
const initialData = {
previewUri: uri.toString(),
source: sourceUri.toString(),
line: initialLine,
scrollPreviewWithEditorSelection: this.config.scrollPreviewWithEditorSelection,
scrollEditorWithPreview: this.config.scrollEditorWithPreview,
doubleClickToSwitchToEditor: this.config.doubleClickToSwitchToEditor
scrollPreviewWithEditorSelection: config.scrollPreviewWithEditorSelection,
scrollEditorWithPreview: config.scrollEditorWithPreview,
doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor
};
this.logger.log('provideTextDocumentContent', initialData);
@@ -227,7 +255,7 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
const nonce = new Date().getTime() + '' + new Date().getMilliseconds();
const csp = this.getCspForResource(sourceUri, nonce);
const body = await this.engine.render(sourceUri, this.config.previewFrontMatter === 'hide', document.getText());
const body = await this.engine.render(sourceUri, config.previewFrontMatter === 'hide', document.getText());
return `<!DOCTYPE html>
<html>
<head>
@@ -236,10 +264,10 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
<meta id="vscode-markdown-preview-data" data-settings="${JSON.stringify(initialData).replace(/"/g, '&quot;')}" data-strings="${JSON.stringify(previewStrings).replace(/"/g, '&quot;')}">
<script src="${this.getMediaPath('csp.js')}" nonce="${nonce}"></script>
<script src="${this.getMediaPath('loading.js')}" nonce="${nonce}"></script>
${this.getStyles(sourceUri, nonce)}
${this.getStyles(sourceUri, nonce, config)}
<base href="${document.uri.toString(true)}">
</head>
<body class="vscode-body ${this.config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${this.config.wordWrap ? 'wordWrap' : ''} ${this.config.markEditorSelection ? 'showEditorSelection' : ''}">
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
${body}
<div class="code-line" data-line="${document.lineCount}"></div>
${this.getScripts(nonce)}
@@ -248,12 +276,11 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
}
public updateConfiguration() {
const newConfig = MarkdownPreviewConfig.getCurrentConfig();
if (!this.config.isEqualTo(newConfig)) {
this.config = newConfig;
// update all generated md documents
for (const document of vscode.workspace.textDocuments) {
if (document.uri.scheme === 'markdown') {
// update all generated md documents
for (const document of vscode.workspace.textDocuments) {
if (document.uri.scheme === 'markdown') {
const sourceUri = vscode.Uri.parse(document.uri.query);
if (this.previewConfigurations.shouldUpdateConfiguration(sourceUri)) {
this.update(document.uri);
}
}

View File

@@ -79,7 +79,7 @@ export class TableOfContentsProvider {
}
private static getHeaderText(header: string): string {
return header.replace(/^\s*#+\s*(.*?)\s*\1*$/, (_, word) => `${word.trim()}`);
return header.replace(/^\s*#+\s*(.*?)\s*#*$/, (_, word) => word.trim());
}
public static slugify(header: string): string {