Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -3,34 +3,38 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as path from 'path';
import * as vscode from 'vscode';
import { OpenDocumentLinkCommand } from '../commands/openDocumentLink';
import { getUriForLinkWithKnownExternalScheme } from '../util/links';
function normalizeLink(
document: vscode.TextDocument,
link: string,
base: string
): vscode.Uri {
const uri = vscode.Uri.parse(link);
if (uri.scheme) {
return uri;
const externalSchemeUri = getUriForLinkWithKnownExternalScheme(link);
if (externalSchemeUri) {
return externalSchemeUri;
}
// assume it must be a file
let resourcePath = uri.path;
if (!uri.path) {
// Assume it must be an relative or absolute file path
// Use a fake scheme to avoid parse warnings
const tempUri = vscode.Uri.parse(`vscode-resource:${link}`);
let resourcePath = tempUri.path;
if (!tempUri.path && document.uri.scheme === 'file') {
resourcePath = document.uri.path;
} else if (uri.path[0] === '/') {
} else if (tempUri.path[0] === '/') {
const root = vscode.workspace.getWorkspaceFolder(document.uri);
if (root) {
resourcePath = path.join(root.uri.fsPath, uri.path);
resourcePath = path.join(root.uri.fsPath, tempUri.path);
}
} else {
resourcePath = path.join(base, uri.path);
resourcePath = base ? path.join(base, tempUri.path) : tempUri.path;
}
return OpenDocumentLinkCommand.createCommandUri(resourcePath, uri.fragment);
return OpenDocumentLinkCommand.createCommandUri(resourcePath, tempUri.fragment);
}
function matchAll(
@@ -55,7 +59,7 @@ export default class LinkProvider implements vscode.DocumentLinkProvider {
document: vscode.TextDocument,
_token: vscode.CancellationToken
): vscode.DocumentLink[] {
const base = path.dirname(document.uri.fsPath);
const base = document.uri.scheme === 'file' ? path.dirname(document.uri.fsPath) : '';
const text = document.getText();
return this.providerInlineLinks(text, document, base)

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Token } from 'markdown-it';
import * as vscode from 'vscode';
import { MarkdownEngine } from '../markdownEngine';
import { TableOfContentsProvider } from '../tableOfContentsProvider';
@@ -16,35 +16,83 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi
private readonly engine: MarkdownEngine
) { }
private async getRegions(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {
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 tokens = await this.engine.parse(document.uri, document.getText());
const regionMarkers = tokens.filter(isRegionMarker)
.map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) }));
const nestingStack: { line: number, isStart: boolean }[] = [];
return regionMarkers
.map(marker => {
if (marker.isStart) {
nestingStack.push(marker);
} else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) {
return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region);
} else {
// noop: invalid nesting (i.e. [end, start] or [start, end, end])
}
return null;
})
.filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region);
}
public async provideFoldingRanges(
document: vscode.TextDocument,
_: vscode.FoldingContext,
_token: vscode.CancellationToken
): Promise<vscode.FoldingRange[]> {
const tocProvider = new TableOfContentsProvider(this.engine, document);
let toc = await tocProvider.getToc();
if (toc.length > rangeLimit) {
toc = toc.slice(0, rangeLimit);
}
const foldingRanges = toc.map((entry, startIndex) => {
const start = entry.line;
let end: number | undefined = undefined;
for (let i = startIndex + 1; i < toc.length; ++i) {
if (toc[i].level <= entry.level) {
end = toc[i].line - 1;
if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) {
end = end - 1;
}
break;
}
}
return new vscode.FoldingRange(
start,
typeof end === 'number' ? end : document.lineCount - 1);
});
return foldingRanges;
const foldables = await Promise.all([
this.getRegions(document),
this.getHeaderFoldingRanges(document),
this.getBlockFoldingRanges(document)]);
return ([] as vscode.FoldingRange[]).concat.apply([], foldables).slice(0, rangeLimit);
}
}
private async getHeaderFoldingRanges(document: vscode.TextDocument) {
const tocProvider = new TableOfContentsProvider(this.engine, document);
const toc = await tocProvider.getToc();
return toc.map(entry => {
let endLine = entry.location.range.end.line;
if (document.lineAt(endLine).isEmptyOrWhitespace && endLine >= entry.line + 1) {
endLine = endLine - 1;
}
return new vscode.FoldingRange(entry.line, endLine);
});
}
private async getBlockFoldingRanges(document: vscode.TextDocument): Promise<vscode.FoldingRange[]> {
const isFoldableToken = (token: Token) => {
switch (token.type) {
case 'fence':
case 'list_item_open':
return token.map[1] > token.map[0];
case 'html_block':
return token.map[1] > token.map[0] + 1;
default:
return false;
}
};
const tokens = await this.engine.parse(document.uri, document.getText());
const multiLineListItems = tokens.filter(isFoldableToken);
return multiLineListItems.map(listItem => {
const start = listItem.map[0];
let end = listItem.map[1] - 1;
if (document.lineAt(end).isEmptyOrWhitespace && end >= start + 1) {
end = end - 1;
}
return new vscode.FoldingRange(start, end);
});
}
}

View File

@@ -15,8 +15,51 @@ import { getVisibleLine, MarkdownFileTopmostLineMonitor } from '../util/topmostL
import { MarkdownPreviewConfigurationManager } from './previewConfig';
import { MarkdownContributions } from '../markdownExtensions';
import { isMarkdownFile } from '../util/file';
import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink';
const localize = nls.loadMessageBundle();
interface WebviewMessage {
readonly source: string;
}
interface CacheImageSizesMessage extends WebviewMessage {
readonly type: 'cacheImageSizes';
readonly body: { id: string, width: number, height: number }[];
}
interface RevealLineMessage extends WebviewMessage {
readonly type: 'revealLine';
readonly body: {
readonly line: number;
};
}
interface DidClickMessage extends WebviewMessage {
readonly type: 'didClick';
readonly body: {
readonly line: number;
};
}
interface ClickLinkMessage extends WebviewMessage {
readonly type: 'clickLink';
readonly body: {
readonly path: string;
readonly fragment?: string;
};
}
interface ShowPreviewSecuritySelectorMessage extends WebviewMessage {
readonly type: 'showPreviewSecuritySelector';
}
interface PreviewStyleLoadErrorMessage extends WebviewMessage {
readonly type: 'previewStyleLoadError';
readonly body: {
readonly unloadedStyles: string[];
};
}
export class MarkdownPreview {
public static viewType = 'markdown.preview';
@@ -33,6 +76,7 @@ export class MarkdownPreview {
private forceUpdate = false;
private isScrolling = false;
private _disposed: boolean = false;
private imageInfo: { id: string, width: number, height: number }[] = [];
public static async revive(
webview: vscode.WebviewPanel,
@@ -117,14 +161,14 @@ export class MarkdownPreview {
this._onDidChangeViewStateEmitter.fire(e);
}, null, this.disposables);
this.editor.webview.onDidReceiveMessage(e => {
this.editor.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => {
if (e.source !== this._resource.toString()) {
return;
}
switch (e.type) {
case 'command':
vscode.commands.executeCommand(e.body.command, ...e.body.args);
case 'cacheImageSizes':
this.onCacheImageSizes(e.body);
break;
case 'revealLine':
@@ -135,6 +179,17 @@ export class MarkdownPreview {
this.onDidClickPreview(e.body.line);
break;
case 'clickLink':
this.onDidClickPreviewLink(e.body.path, e.body.fragment);
break;
case 'showPreviewSecuritySelector':
vscode.commands.executeCommand('markdown.showPreviewSecuritySelector', e.source);
break;
case 'previewStyleLoadError':
vscode.window.showWarningMessage(localize('onPreviewStyleLoadError', "Could not load 'markdown.styles': {0}", e.body.unloadedStyles.join(', ')));
break;
}
}, null, this.disposables);
@@ -181,7 +236,8 @@ export class MarkdownPreview {
return {
resource: this.resource.toString(),
locked: this._locked,
line: this.line
line: this.line,
imageInfo: this.imageInfo
};
}
@@ -347,7 +403,6 @@ export class MarkdownPreview {
): vscode.WebviewOptions {
return {
enableScripts: true,
enableCommandUris: true,
localResourceRoots: MarkdownPreview.getLocalResourceRoots(resource, contributions)
};
}
@@ -400,6 +455,24 @@ export class MarkdownPreview {
vscode.workspace.openTextDocument(this._resource).then(vscode.window.showTextDocument);
}
private async onDidClickPreviewLink(path: string, fragment: string | undefined) {
const config = vscode.workspace.getConfiguration('markdown', this.resource);
const openLinks = config.get<string>('preview.openMarkdownLinks', 'inPreview');
if (openLinks === 'inPreview') {
const markdownLink = await resolveLinkToMarkdownFile(path);
if (markdownLink) {
this.update(markdownLink);
return;
}
}
vscode.commands.executeCommand('_markdown.openDocumentLink', { path, fragment });
}
private async onCacheImageSizes(imageInfo: { id: string, width: number, height: number }[]) {
this.imageInfo = imageInfo;
}
}
export interface PreviewSettings {

View File

@@ -79,7 +79,7 @@ export class MarkdownContentProvider {
data-strings="${JSON.stringify(previewStrings).replace(/"/g, '&quot;')}"
data-state="${JSON.stringify(state || {}).replace(/"/g, '&quot;')}">
<script src="${this.extensionResourcePath('pre.js')}" nonce="${nonce}"></script>
${this.getStyles(sourceUri, nonce, config)}
${this.getStyles(sourceUri, nonce, config, state)}
<base href="${markdownDocument.uri.with({ scheme: 'vscode-resource' }).toString(true)}">
</head>
<body class="vscode-body ${config.scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''} ${config.wordWrap ? 'wordWrap' : ''} ${config.markEditorSelection ? 'showEditorSelection' : ''}">
@@ -104,7 +104,7 @@ export class MarkdownContentProvider {
// Use href if it is already an URL
const hrefUri = vscode.Uri.parse(href);
if (['http', 'https'].indexOf(hrefUri.scheme) >= 0) {
return hrefUri.toString();
return hrefUri.toString(true);
}
// Use href as file URI if it is absolute
@@ -131,7 +131,7 @@ export class MarkdownContentProvider {
private computeCustomStyleSheetIncludes(resource: vscode.Uri, config: MarkdownPreviewConfiguration): string {
if (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">`;
return `<link rel="stylesheet" class="code-user-style" data-source="${style.replace(/"/g, '&quot;')}" href="${this.fixHref(resource, style).replace(/"/g, '&quot;')}" type="text/css" media="screen">`;
}).join('\n');
}
return '';
@@ -147,14 +147,30 @@ export class MarkdownContentProvider {
</style>`;
}
private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration): string {
private getImageStabilizerStyles(state?: any) {
let ret = '<style>\n';
if (state && state.imageInfo) {
state.imageInfo.forEach((imgInfo: any) => {
ret += `#${imgInfo.id}.loading {
height: ${imgInfo.height}px;
width: ${imgInfo.width}px;
}\n`;
});
}
ret += '</style>\n';
return ret;
}
private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string {
const baseStyles = this.contributions.previewStyles
.map(resource => `<link rel="stylesheet" type="text/css" href="${resource.toString()}">`)
.join('\n');
return `${baseStyles}
${this.getSettingsOverrideStyles(nonce, config)}
${this.computeCustomStyleSheetIncludes(resource, config)}`;
${this.computeCustomStyleSheetIncludes(resource, config)}
${this.getImageStabilizerStyles(state)}`;
}
private getScripts(nonce: string): string {