Fix markdown link inserting (#198702)

Fixes #190769
Fixes #195349

Also makes some small code cleanups
This commit is contained in:
Matt Bierner 2023-11-20 12:10:43 -08:00 committed by GitHub
parent 8de751f794
commit ad31b067ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 47 deletions

View file

@ -79,7 +79,7 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false) {
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
const selectionText = activeEditor.document.getText(selection);
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, [], title, placeholderValue, pasteAsMarkdownLink, isExternalLink, {
const snippet = createUriListSnippet(activeEditor.document, selectedFiles.map(uri => ({ uri })), title, placeholderValue, pasteAsMarkdownLink, isExternalLink, {
insertAsMedia,
placeholderText: selectionText,
placeholderStartIndex: (i + 1) * selectedFiles.length,

View file

@ -44,12 +44,12 @@ class ResourceDropProvider implements vscode.DocumentDropEditProvider {
private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
const urlList = await dataTransfer.get(Mime.textUriList)?.asString();
if (!urlList || token.isCancellationRequested) {
return undefined;
return;
}
const snippet = await tryGetUriListSnippet(document, urlList, token);
const snippet = tryGetUriListSnippet(document, urlList);
if (!snippet) {
return undefined;
return;
}
const edit = new vscode.DocumentDropEdit(snippet.snippet);

View file

@ -53,7 +53,7 @@ class PasteResourceEditProvider implements vscode.DocumentPasteEditProvider {
}
const pasteUrlSetting = getPasteUrlAsFormattedLinkSetting(document);
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
const pasteEdit = createEditAddingLinksForUriList(document, ranges, uriList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
if (!pasteEdit) {
return;
}

View file

@ -32,7 +32,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider {
return;
}
const pasteEdit = await createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token);
const pasteEdit = createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart);
if (!pasteEdit) {
return;
}

View file

@ -75,14 +75,13 @@ export function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument)
return vscode.workspace.getConfiguration('markdown', document).get<PasteUrlAsFormattedLink>('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart);
}
export async function createEditAddingLinksForUriList(
export function createEditAddingLinksForUriList(
document: SkinnyTextDocument,
ranges: readonly vscode.Range[],
urlList: string,
isExternalLink: boolean,
useSmartPaste: boolean,
token: vscode.CancellationToken,
): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined> {
useSmartPaste: boolean
): { additionalEdits: vscode.WorkspaceEdit; label: string; markdownLink: boolean } | undefined {
if (ranges.length === 0) {
return;
@ -104,7 +103,7 @@ export async function createEditAddingLinksForUriList(
markdownLink = pasteAsMarkdownLink; // FIX: this will only match the last range
}
const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(range), placeHolderValue, pasteAsMarkdownLink, isExternalLink);
const snippet = tryGetUriListSnippet(document, urlList, document.getText(range), placeHolderValue, pasteAsMarkdownLink, isExternalLink);
if (!snippet) {
return;
}
@ -158,21 +157,16 @@ export function validateLink(urlList: string): { isValid: boolean; cleanedUrlLis
return { isValid, cleanedUrlList: splitUrlList[0] };
}
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;
}
const uriStrings: string[] = [];
const uris: vscode.Uri[] = [];
for (const resource of urlList.split(/\r?\n/g)) {
export function tryGetUriListSnippet(document: SkinnyTextDocument, urlList: String, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): { snippet: vscode.SnippetString; label: string } | undefined {
const entries = coalesce(urlList.split(/\r?\n/g).map(resource => {
try {
uris.push(vscode.Uri.parse(resource));
uriStrings.push(resource);
return { uri: vscode.Uri.parse(resource), str: resource };
} catch {
// noop
// Uri parse failure
return undefined;
}
}
return createUriListSnippet(document, uris, uriStrings, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink);
}));
return createUriListSnippet(document, entries, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink);
}
interface UriListSnippetOptions {
@ -193,20 +187,21 @@ interface UriListSnippetOptions {
export function appendToLinkSnippet(
snippet: vscode.SnippetString,
title: string,
uriString: string,
link: string,
placeholderValue: number,
isExternalLink: boolean,
): vscode.SnippetString {
): void {
snippet.appendText('[');
snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue);
snippet.appendText(`](${escapeMarkdownLinkPath(uriString, isExternalLink)})`);
return snippet;
snippet.appendText(`](${escapeMarkdownLinkPath(link, isExternalLink)})`);
}
export function createUriListSnippet(
document: SkinnyTextDocument,
uris: readonly vscode.Uri[],
uriStrings?: readonly string[],
uris: ReadonlyArray<{
readonly uri: vscode.Uri;
readonly str?: string;
}>,
title = '',
placeholderValue = 0,
pasteAsMarkdownLink = true,
@ -219,15 +214,15 @@ export function createUriListSnippet(
const documentDir = getDocumentDir(document.uri);
let snippet = new vscode.SnippetString();
const snippet = new vscode.SnippetString();
let insertedLinkCount = 0;
let insertedImageCount = 0;
let insertedAudioVideoCount = 0;
uris.forEach((uri, i) => {
const mdPath = getMdPath(documentDir, uri);
const mdPath = getRelativeMdPath(documentDir, uri.uri) ?? uri.str ?? uri.uri.toString();
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', '');
const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia;
const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
@ -257,11 +252,7 @@ export function createUriListSnippet(
}
} else {
insertedLinkCount++;
if (uriStrings && isExternalLink) {
snippet = appendToLinkSnippet(snippet, title, uriStrings[i], placeholderValue, isExternalLink);
} else {
snippet.appendText(escapeMarkdownLinkPath(mdPath, isExternalLink));
}
appendToLinkSnippet(snippet, title, mdPath, placeholderValue, isExternalLink);
}
if (i < uris.length - 1 && uris.length > 1) {
@ -349,7 +340,7 @@ export async function createEditForMediaFiles(
}
}
const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri));
const snippet = createUriListSnippet(document, fileEntries);
if (!snippet) {
return;
}
@ -361,7 +352,7 @@ export async function createEditForMediaFiles(
};
}
function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): string | undefined {
if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {
if (file.scheme === Schemes.file) {
// On windows, we must use the native `path.relative` to generate the relative path
@ -373,8 +364,7 @@ function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
return path.posix.relative(dir.path, file.path);
}
return file.toString(false);
return undefined;
}
function escapeHtmlAttribute(attr: string): string {

View file

@ -18,7 +18,7 @@ suite('createEditAddingLinksForUriList', () => {
getText: function () { return 'hello world!'; },
};
const result = await createEditAddingLinksForUriList(skinnyDocument, [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true, new vscode.CancellationTokenSource().token);
const result = createEditAddingLinksForUriList(skinnyDocument, [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true);
// need to check the actual result -> snippet value
assert.strictEqual(result?.label, 'Insert Markdown Link');
});
@ -95,31 +95,36 @@ suite('createEditAddingLinksForUriList', () => {
test('Should create snippet with < > when pasted link has an mismatched parentheses', () => {
const uriString = 'https://www.mic(rosoft.com';
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), 'abc', uriString, 0, true);
const snippet = new vscode.SnippetString('');
appendToLinkSnippet(snippet, 'abc', uriString, 0, true);
assert.strictEqual(snippet?.value, '[${0:abc}](<https://www.mic(rosoft.com>)');
});
test('Should create Markdown link snippet when pasteAsMarkdownLink is true', () => {
const uriString = 'https://www.microsoft.com';
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
const snippet = new vscode.SnippetString('');
appendToLinkSnippet(snippet, '', uriString, 0, true);
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)');
});
test('Should use an unencoded URI string in Markdown link when passing in an external browser link', () => {
const uriString = 'https://www.microsoft.com';
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
const snippet = new vscode.SnippetString('');
appendToLinkSnippet(snippet, '', uriString, 0, true);
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com)');
});
test('Should not decode an encoded URI string when passing in an external browser link', () => {
const uriString = 'https://www.microsoft.com/%20';
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
const snippet = new vscode.SnippetString('');
appendToLinkSnippet(snippet, '', uriString, 0, true);
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/%20)');
});
test('Should not encode an unencoded URI string when passing in an external browser link', () => {
const uriString = 'https://www.example.com/path?query=value&another=value#fragment';
const snippet = appendToLinkSnippet(new vscode.SnippetString(''), '', uriString, 0, true);
const snippet = new vscode.SnippetString('');
appendToLinkSnippet(snippet, '', uriString, 0, true);
assert.strictEqual(snippet?.value, '[${0:Title}](https://www.example.com/path?query=value&another=value#fragment)');
});
});