mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from master
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -79,7 +79,7 @@ export class MarkdownContentProvider {
|
||||
data-strings="${JSON.stringify(previewStrings).replace(/"/g, '"')}"
|
||||
data-state="${JSON.stringify(state || {}).replace(/"/g, '"')}">
|
||||
<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, '"')}" href="${this.fixHref(resource, style)}" type="text/css" media="screen">`;
|
||||
return `<link rel="stylesheet" class="code-user-style" data-source="${style.replace(/"/g, '"')}" href="${this.fixHref(resource, style).replace(/"/g, '"')}" 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 {
|
||||
|
||||
Reference in New Issue
Block a user