mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-20 08:38:56 +01:00
markdown link smart pasting (#188437)
* making markdown link pasting feature smarter * update validateLink
This commit is contained in:
@@ -81,7 +81,7 @@ export class NewFilePathGenerator {
|
||||
}
|
||||
|
||||
function getDesiredNewFilePath(config: CopyFileConfiguration, document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri {
|
||||
const docUri = getParentDocumentUri(document);
|
||||
const docUri = getParentDocumentUri(document.uri);
|
||||
for (const [rawGlob, rawDest] of Object.entries(config.destination)) {
|
||||
for (const glob of parseGlob(rawGlob)) {
|
||||
if (picomatch.isMatch(docUri.path, glob, { dot: true })) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Schemes } from '../../util/schemes';
|
||||
import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes } from './shared';
|
||||
import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared';
|
||||
|
||||
class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
@@ -32,7 +32,8 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
if (!urlList) {
|
||||
return;
|
||||
}
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, false);
|
||||
const pasteUrlSetting = await getPasteUrlAsFormattedLinkSetting(document);
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { externalUriSchemes, createEditAddingLinksForUriList } from './shared';
|
||||
import { externalUriSchemes, createEditAddingLinksForUriList, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared';
|
||||
class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
|
||||
readonly id = 'insertMarkdownLink';
|
||||
@@ -14,8 +14,8 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
dataTransfer: vscode.DataTransfer,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<vscode.DocumentPasteEdit | undefined> {
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'smart');
|
||||
if (enabled === 'never') {
|
||||
const pasteUrlSetting = await getPasteUrlAsFormattedLinkSetting(document);
|
||||
if (pasteUrlSetting === PasteUrlAsFormattedLink.Never) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateLink(urlList)) {
|
||||
if (!validateLink(urlList).isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,8 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
if (!urlList) {
|
||||
return undefined;
|
||||
}
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, true);
|
||||
|
||||
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
|
||||
if (!pasteEdit) {
|
||||
return;
|
||||
}
|
||||
@@ -45,12 +46,20 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateLink(urlList: string): boolean {
|
||||
const url = urlList?.split(/\s+/);
|
||||
if (url.length > 1 || !externalUriSchemes.includes(vscode.Uri.parse(url[0]).scheme)) {
|
||||
return false;
|
||||
export function validateLink(urlList: string): { isValid: boolean; cleanedUrlList: string } {
|
||||
let isValid = false;
|
||||
let uri = undefined;
|
||||
const trimmedUrlList = urlList?.trim(); //remove leading and trailing whitespace and new lines
|
||||
try {
|
||||
uri = vscode.Uri.parse(trimmedUrlList);
|
||||
} catch (error) {
|
||||
return { isValid: false, cleanedUrlList: urlList };
|
||||
}
|
||||
return true;
|
||||
const splitUrlList = trimmedUrlList.split(' ').filter(item => item !== ''); //split on spaces and remove empty strings
|
||||
if (uri) {
|
||||
isValid = splitUrlList.length === 1 && !splitUrlList[0].includes('\n') && externalUriSchemes.includes(vscode.Uri.parse(splitUrlList[0]).scheme);
|
||||
}
|
||||
return { isValid, cleanedUrlList: splitUrlList[0] };
|
||||
}
|
||||
|
||||
export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) {
|
||||
|
||||
@@ -21,7 +21,6 @@ export const externalUriSchemes = [
|
||||
'http',
|
||||
'https',
|
||||
'mailto',
|
||||
'ftp',
|
||||
];
|
||||
|
||||
export const mediaFileExtensions = new Map<string, MediaKind>([
|
||||
@@ -64,14 +63,21 @@ export const mediaMimes = new Set([
|
||||
]);
|
||||
|
||||
const smartPasteRegexes = [
|
||||
{ regex: /\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Link
|
||||
{ regex: /!\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Image Link
|
||||
{ regex: /\[([^\]]*)\]\(([^)]*)\)/g, is_markdown_link: false }, // In a Markdown link
|
||||
{ regex: /^```[\s\S]*?```$/gm, is_markdown_link: false }, // In a fenced code block
|
||||
{ regex: /^\$\$[\s\S]*?\$\$$/gm, is_markdown_link: false }, // In a fenced math block
|
||||
{ regex: /`[^`]*`/g, is_markdown_link: false }, // In inline code
|
||||
{ regex: /\$[^$]*\$/g, is_markdown_link: false }, // In inline math
|
||||
{ regex: /\[.*\]\(.*\)/g, isMarkdownLink: true, isInline: true }, // Is a Markdown Link
|
||||
{ regex: /!\[.*\]\(.*\)/g, isMarkdownLink: true, isInline: true }, // Is a Markdown Image Link
|
||||
{ regex: /\[([^\]]*)\]\(([^)]*)\)/g, isMarkdownLink: false, isInline: true }, // In a Markdown link
|
||||
{ regex: /^```[\s\S]*?```$/gm, isMarkdownLink: false, isInline: false }, // In a fenced code block
|
||||
{ regex: /^\$\$[\s\S]*?\$\$$/gm, isMarkdownLink: false, isInline: false }, // In a fenced math block
|
||||
{ regex: /`[^`]*`/g, isMarkdownLink: false, isInline: true }, // In inline code
|
||||
{ regex: /\$[^$]*\$/g, isMarkdownLink: false, isInline: true }, // In inline math
|
||||
];
|
||||
|
||||
export interface SkinnyTextDocument {
|
||||
offsetAt(position: vscode.Position): number;
|
||||
getText(range?: vscode.Range): string;
|
||||
readonly uri: vscode.Uri;
|
||||
}
|
||||
|
||||
export interface SmartPaste {
|
||||
|
||||
/**
|
||||
@@ -86,22 +92,43 @@ export interface SmartPaste {
|
||||
|
||||
}
|
||||
|
||||
export async function createEditAddingLinksForUriList(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken, isExternalLink: boolean): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> {
|
||||
export enum PasteUrlAsFormattedLink {
|
||||
Always = 'always',
|
||||
Smart = 'smart',
|
||||
Never = 'never'
|
||||
}
|
||||
|
||||
export async function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): Promise<PasteUrlAsFormattedLink> {
|
||||
return vscode.workspace.getConfiguration('markdown', document).get<PasteUrlAsFormattedLink>('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart);
|
||||
}
|
||||
|
||||
export async function createEditAddingLinksForUriList(
|
||||
document: SkinnyTextDocument,
|
||||
ranges: readonly vscode.Range[],
|
||||
urlList: string,
|
||||
isExternalLink: boolean,
|
||||
useSmartPaste: boolean,
|
||||
token: vscode.CancellationToken,
|
||||
): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> {
|
||||
|
||||
if (ranges.length === 0) {
|
||||
return;
|
||||
}
|
||||
const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'always');
|
||||
const edits: vscode.SnippetTextEdit[] = [];
|
||||
let placeHolderValue: number = ranges.length;
|
||||
let label: string = '';
|
||||
let smartPaste = { pasteAsMarkdownLink: true, updateTitle: false };
|
||||
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
for (const range of ranges) {
|
||||
let title = document.getText(range);
|
||||
const selectedRange: vscode.Range = new vscode.Range(
|
||||
new vscode.Position(range.start.line, document.offsetAt(range.start)),
|
||||
new vscode.Position(range.end.line, document.offsetAt(range.end))
|
||||
);
|
||||
|
||||
let title = document.getText(ranges[i]);
|
||||
if (enabled === 'smart') {
|
||||
smartPaste = checkSmartPaste(document.getText(), document.offsetAt(ranges[i].start), document.offsetAt(ranges[i].end));
|
||||
title = smartPaste.updateTitle ? '' : document.getText(ranges[i]);
|
||||
if (useSmartPaste) {
|
||||
smartPaste = checkSmartPaste(document, selectedRange);
|
||||
title = smartPaste.updateTitle ? '' : document.getText(range);
|
||||
}
|
||||
|
||||
const snippet = await tryGetUriListSnippet(document, urlList, token, title, placeHolderValue, smartPaste.pasteAsMarkdownLink, isExternalLink);
|
||||
@@ -111,7 +138,7 @@ export async function createEditAddingLinksForUriList(document: vscode.TextDocum
|
||||
|
||||
smartPaste.pasteAsMarkdownLink = true;
|
||||
placeHolderValue--;
|
||||
edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet));
|
||||
edits.push(new vscode.SnippetTextEdit(range, snippet.snippet));
|
||||
label = snippet.label;
|
||||
}
|
||||
|
||||
@@ -121,15 +148,15 @@ export async function createEditAddingLinksForUriList(document: vscode.TextDocum
|
||||
return { additionalEdits, label };
|
||||
}
|
||||
|
||||
export function checkSmartPaste(documentText: string, start: number, end: number): SmartPaste {
|
||||
export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vscode.Range): SmartPaste {
|
||||
const SmartPaste: SmartPaste = { pasteAsMarkdownLink: true, updateTitle: false };
|
||||
for (const regex of smartPasteRegexes) {
|
||||
const matches = [...documentText.matchAll(regex.regex)];
|
||||
const matches = [...document.getText().matchAll(regex.regex)];
|
||||
for (const match of matches) {
|
||||
if (match.index !== undefined) {
|
||||
const useDefaultPaste = start > match.index && end < match.index + match[0].length;
|
||||
const useDefaultPaste = selectedRange.start.character > match.index && selectedRange.end.character < match.index + match[0].length;
|
||||
SmartPaste.pasteAsMarkdownLink = !useDefaultPaste;
|
||||
SmartPaste.updateTitle = regex.is_markdown_link && start === match.index && end === match.index + match[0].length;
|
||||
SmartPaste.updateTitle = regex.isMarkdownLink && selectedRange.start.character === match.index && selectedRange.end.character === match.index + match[0].length;
|
||||
if (!SmartPaste.pasteAsMarkdownLink || SmartPaste.updateTitle) {
|
||||
return SmartPaste;
|
||||
}
|
||||
@@ -139,7 +166,7 @@ export function checkSmartPaste(documentText: string, start: number, end: number
|
||||
return SmartPaste;
|
||||
}
|
||||
|
||||
export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
export async function tryGetUriListSnippet(document: SkinnyTextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
|
||||
if (token.isCancellationRequested) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -171,7 +198,8 @@ interface UriListSnippetOptions {
|
||||
readonly separator?: string;
|
||||
}
|
||||
|
||||
export function createLinkSnippet(
|
||||
export function appendToLinkSnippet(
|
||||
snippet: vscode.SnippetString,
|
||||
pasteAsMarkdownLink: boolean,
|
||||
mdPath: string,
|
||||
title: string,
|
||||
@@ -180,7 +208,6 @@ export function createLinkSnippet(
|
||||
isExternalLink: boolean,
|
||||
): vscode.SnippetString {
|
||||
const uriString = uri.toString(true);
|
||||
const snippet = new vscode.SnippetString();
|
||||
if (pasteAsMarkdownLink) {
|
||||
snippet.appendText('[');
|
||||
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
|
||||
@@ -192,7 +219,7 @@ export function createLinkSnippet(
|
||||
}
|
||||
|
||||
export function createUriListSnippet(
|
||||
document: vscode.TextDocument,
|
||||
document: SkinnyTextDocument,
|
||||
uris: readonly vscode.Uri[],
|
||||
title = '',
|
||||
placeholderValue = 0,
|
||||
@@ -204,7 +231,7 @@ export function createUriListSnippet(
|
||||
return;
|
||||
}
|
||||
|
||||
const documentDir = getDocumentDir(document);
|
||||
const documentDir = getDocumentDir(document.uri);
|
||||
|
||||
let snippet = new vscode.SnippetString();
|
||||
let insertedLinkCount = 0;
|
||||
@@ -244,7 +271,7 @@ export function createUriListSnippet(
|
||||
}
|
||||
} else {
|
||||
insertedLinkCount++;
|
||||
snippet = createLinkSnippet(pasteAsMarkdownLink, mdPath, title, uri, placeholderValue, isExternalLink);
|
||||
snippet = appendToLinkSnippet(snippet, pasteAsMarkdownLink, mdPath, title, uri, placeholderValue, isExternalLink);
|
||||
}
|
||||
|
||||
if (i < uris.length - 1 && uris.length > 1) {
|
||||
|
||||
Reference in New Issue
Block a user