mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 08:38:56 +01:00
Use markdown engine to enable/disable smart paste (#202192)
Fixes #188863 Fixes #188958 Fixes #188868 This is more reliable than using the regular expressions. However the regular expressions are still needed for inline elements
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { IMdParser } from '../../markdownEngine';
|
||||
import { ITextDocument } from '../../types/textDocument';
|
||||
import { Mime } from '../../util/mimes';
|
||||
import { createInsertUriListEdit, externalUriSchemes } from './shared';
|
||||
@@ -31,6 +32,10 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
public static readonly pasteMimeTypes = [Mime.textPlain];
|
||||
|
||||
constructor(
|
||||
private readonly _parser: IMdParser,
|
||||
) { }
|
||||
|
||||
async provideDocumentPasteEdits(
|
||||
document: vscode.TextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
@@ -63,7 +68,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
workspaceEdit.set(document.uri, edit.edits);
|
||||
pasteEdit.additionalEdit = workspaceEdit;
|
||||
|
||||
if (!shouldInsertMarkdownLinkByDefault(document, pasteUrlSetting, ranges)) {
|
||||
if (!shouldInsertMarkdownLinkByDefault(this._parser, document, pasteUrlSetting, ranges, token)) {
|
||||
pasteEdit.yieldTo = [{ mimeType: Mime.textPlain }];
|
||||
}
|
||||
|
||||
@@ -71,45 +76,59 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
}
|
||||
}
|
||||
|
||||
export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) {
|
||||
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(), {
|
||||
export function registerPasteUrlSupport(selector: vscode.DocumentSelector, parser: IMdParser) {
|
||||
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteUrlEditProvider(parser), {
|
||||
id: PasteUrlEditProvider.id,
|
||||
pasteMimeTypes: PasteUrlEditProvider.pasteMimeTypes,
|
||||
});
|
||||
}
|
||||
|
||||
const smartPasteRegexes = [
|
||||
const smartPasteLineRegexes = [
|
||||
{ regex: /(\[[^\[\]]*](?:\([^\(\)]*\)|\[[^\[\]]*]))/g }, // In a Markdown link
|
||||
{ regex: /^```[\s\S]*?```$/gm }, // In a backtick fenced code block
|
||||
{ regex: /^~~~[\s\S]*?~~~$/gm }, // In a tildefenced code block
|
||||
{ regex: /^\$\$[\s\S]*?\$\$$/gm }, // In a fenced math block
|
||||
{ regex: /\$\$[\s\S]*?\$\$/gm }, // In a fenced math block
|
||||
{ regex: /`[^`]*`/g }, // In inline code
|
||||
{ regex: /\$[^$]*\$/g }, // In inline math
|
||||
{ regex: /^[ ]{0,3}\[\w+\]:\s.*$/g }, // Block link definition (needed as tokens are not generated for these)
|
||||
];
|
||||
|
||||
export function shouldInsertMarkdownLinkByDefault(document: ITextDocument, pasteUrlSetting: PasteUrlAsMarkdownLink, ranges: readonly vscode.Range[]): boolean {
|
||||
export async function shouldInsertMarkdownLinkByDefault(
|
||||
parser: IMdParser,
|
||||
document: ITextDocument,
|
||||
pasteUrlSetting: PasteUrlAsMarkdownLink,
|
||||
ranges: readonly vscode.Range[],
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<boolean> {
|
||||
switch (pasteUrlSetting) {
|
||||
case PasteUrlAsMarkdownLink.Always: {
|
||||
return true;
|
||||
}
|
||||
case PasteUrlAsMarkdownLink.Smart: {
|
||||
return ranges.every(range => shouldSmartPasteForSelection(document, range));
|
||||
return checkSmart();
|
||||
}
|
||||
case PasteUrlAsMarkdownLink.SmartWithSelection: {
|
||||
return (
|
||||
// At least one range must not be empty
|
||||
ranges.some(range => document.getText(range).trim().length > 0)
|
||||
// And all ranges must be smart
|
||||
&& ranges.every(range => shouldSmartPasteForSelection(document, range))
|
||||
);
|
||||
// At least one range must not be empty
|
||||
if (!ranges.some(range => document.getText(range).trim().length > 0)) {
|
||||
return false;
|
||||
}
|
||||
// And all ranges must be smart
|
||||
return checkSmart();
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function checkSmart(): Promise<boolean> {
|
||||
return (await Promise.all(ranges.map(range => shouldSmartPasteForSelection(parser, document, range, token)))).every(x => x);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vscode.Range): boolean {
|
||||
async function shouldSmartPasteForSelection(
|
||||
parser: IMdParser,
|
||||
document: ITextDocument,
|
||||
selectedRange: vscode.Range,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<boolean> {
|
||||
// Disable for multi-line selections
|
||||
if (selectedRange.start.line !== selectedRange.end.line) {
|
||||
return false;
|
||||
@@ -125,18 +144,25 @@ function shouldSmartPasteForSelection(document: ITextDocument, selectedRange: vs
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: use proper parsing instead of regexes
|
||||
for (const regex of smartPasteRegexes) {
|
||||
const matches = [...document.getText().matchAll(regex.regex)];
|
||||
for (const match of matches) {
|
||||
if (match.index !== undefined) {
|
||||
const matchRange = new vscode.Range(
|
||||
document.positionAt(match.index),
|
||||
document.positionAt(match.index + match[0].length)
|
||||
);
|
||||
if (matchRange.intersection(selectedRange)) {
|
||||
return false;
|
||||
}
|
||||
// Check if selection is inside a special block level element using markdown engine
|
||||
const tokens = await parser.tokenize(document);
|
||||
if (token.isCancellationRequested) {
|
||||
return false;
|
||||
}
|
||||
for (const token of tokens) {
|
||||
if (token.map && token.map[0] <= selectedRange.start.line && token.map[1] > selectedRange.start.line) {
|
||||
if (!['paragraph_open', 'inline', 'heading_open', 'ordered_list_open', 'bullet_list_open', 'list_item_open', 'blockquote_open'].includes(token.type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run additional regex checks on the current line to check if we are inside an inline element
|
||||
const line = document.getText(new vscode.Range(selectedRange.start.line, 0, selectedRange.start.line, Number.MAX_SAFE_INTEGER));
|
||||
for (const regex of smartPasteLineRegexes) {
|
||||
for (const match of line.matchAll(regex.regex)) {
|
||||
if (match.index !== undefined && selectedRange.start.character >= match.index && selectedRange.start.character <= match.index + match[0].length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user