turning highlighted Markdown text to link to pasted URL (#185924)

* turning highlighted Mardown text to link to pasted URL

* resolved comments

* resolved more comments

* preserved behavior of existing file pasting logic

---------

Co-authored-by: Meghan Kulkarni <t-mekulkarni@microsoft.com>
This commit is contained in:
Meghan Kulkarni
2023-06-26 17:25:52 -07:00
committed by GitHub
parent 680cbcc139
commit 6626f5f07a
9 changed files with 142 additions and 33 deletions

View File

@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { Schemes } from '../../util/schemes';
import { createEditForMediaFiles, mediaMimes, tryGetUriListSnippet } from './shared';
import { createEditForMediaFiles, getMarkdownLink, mediaMimes } from './shared';
class PasteEditProvider implements vscode.DocumentPasteEditProvider {
@@ -13,7 +13,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
async provideDocumentPasteEdits(
document: vscode.TextDocument,
_ranges: readonly vscode.Range[],
ranges: readonly vscode.Range[],
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<vscode.DocumentPasteEdit | undefined> {
@@ -27,12 +27,18 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider {
return createEdit;
}
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
if (!snippet) {
const label = vscode.l10n.t('Insert Markdown Media');
const uriEdit = new vscode.DocumentPasteEdit('', this._id, label);
const urlList = await dataTransfer.get('text/uri-list')?.asString();
if (!urlList) {
return;
}
const pasteEdit = await getMarkdownLink(document, ranges, urlList, token);
if (!pasteEdit) {
return;
}
const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
uriEdit.additionalEdit = pasteEdit.additionalEdits;
uriEdit.priority = this._getPriority(dataTransfer);
return uriEdit;
}

View File

@@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getMarkdownLink } from './shared';
class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider {
private readonly _id = 'insertMarkdownLink';
async provideDocumentPasteEdits(
document: vscode.TextDocument,
ranges: readonly vscode.Range[],
dataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken,
): Promise<vscode.DocumentPasteEdit | undefined> {
const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.pasteUrlAsFormattedLink.enabled', true);
if (!enabled) {
return;
}
// Check if dataTransfer contains a URL
const item = dataTransfer.get('text/plain');
try {
new URL(await item?.value);
} catch (error) {
return;
}
const label = vscode.l10n.t('Insert Markdown Link');
const uriEdit = new vscode.DocumentPasteEdit('', this._id, label);
const urlList = await item?.asString();
if (!urlList) {
return undefined;
}
const pasteEdit = await getMarkdownLink(document, ranges, urlList, token);
if (!pasteEdit) {
return;
}
uriEdit.additionalEdit = pasteEdit.additionalEdits;
return uriEdit;
}
}
export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) {
return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteLinkEditProvider(), {
pasteMimeTypes: [
'text/plain',
]
});
}

View File

@@ -30,7 +30,11 @@ class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider {
}
private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
const urlList = await dataTransfer.get('text/uri-list')?.asString();
if (!urlList) {
return undefined;
}
const snippet = await tryGetUriListSnippet(document, urlList, token);
if (!snippet) {
return undefined;
}

View File

@@ -56,10 +56,32 @@ export const mediaMimes = new Set([
'audio/x-wav',
]);
export async function getMarkdownLink(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> {
if (ranges.length === 0) {
return;
}
export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
const urlList = await dataTransfer.get('text/uri-list')?.asString();
if (!urlList || token.isCancellationRequested) {
const edits: vscode.SnippetTextEdit[] = [];
let placeHolderValue: number = ranges.length;
let label: string = '';
for (let i = 0; i < ranges.length; i++) {
const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(ranges[i]), placeHolderValue);
if (!snippet) {
return;
}
placeHolderValue--;
edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet));
label = snippet.label;
}
const additionalEdits = new vscode.WorkspaceEdit();
additionalEdits.set(document.uri, edits);
return { additionalEdits, label };
}
export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
if (token.isCancellationRequested) {
return undefined;
}
@@ -72,7 +94,7 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr
}
}
return createUriListSnippet(document, uris);
return createUriListSnippet(document, uris, title, placeHolderValue);
}
interface UriListSnippetOptions {
@@ -90,11 +112,12 @@ interface UriListSnippetOptions {
readonly separator?: string;
}
export function createUriListSnippet(
document: vscode.TextDocument,
uris: readonly vscode.Uri[],
options?: UriListSnippetOptions
title = '',
placeholderValue = 0,
options?: UriListSnippetOptions,
): { snippet: vscode.SnippetString; label: string } | undefined {
if (!uris.length) {
return;
@@ -119,27 +142,27 @@ export function createUriListSnippet(
if (insertAsVideo) {
insertedAudioVideoCount++;
snippet.appendText(`<video src="${escapeHtmlAttribute(mdPath)}" controls title="`);
snippet.appendPlaceholder('Title');
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
snippet.appendText('"></video>');
} else if (insertAsAudio) {
insertedAudioVideoCount++;
snippet.appendText(`<audio src="${escapeHtmlAttribute(mdPath)}" controls title="`);
snippet.appendPlaceholder('Title');
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
snippet.appendText('"></audio>');
} else {
if (insertAsMedia) {
insertedImageCount++;
snippet.appendText('![');
const placeholderText = options?.placeholderText ? (escapeBrackets(title) || 'Alt text') : 'label';
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : (placeholderValue === 0 ? undefined : placeholderValue);
snippet.appendPlaceholder(placeholderText, placeholderIndex);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
} else {
insertedLinkCount++;
snippet.appendText('[');
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
}
snippet.appendText(insertAsMedia ? '![' : '[');
const placeholderText = options?.placeholderText ?? (insertAsMedia ? 'Alt text' : 'label');
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
snippet.appendPlaceholder(placeholderText, placeholderIndex);
snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
}
if (i < uris.length - 1 && uris.length > 1) {
@@ -267,6 +290,12 @@ function escapeMarkdownLinkPath(mdPath: string): string {
return encodeURI(mdPath);
}
function escapeBrackets(value: string): string {
value = value.replace(/[\[\]]/g, '\\$&');
// value = value.replace(/\r\n\r\n/g, '\n\n');
return value;
}
function needsBracketLink(mdPath: string) {
// Links with whitespace or control characters must be enclosed in brackets
if (mdPath.startsWith('<') || /\s|[\u007F\u0000-\u001f]/.test(mdPath)) {