Skip to content

Commit

Permalink
Fix markdown link inserting (#198702)
Browse files Browse the repository at this point in the history
Fixes #190769
Fixes #195349

Also makes some small code cleanups
  • Loading branch information
mjbvz authored Nov 20, 2023
1 parent 8de751f commit ad31b06
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down Expand Up @@ -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)');
});
});
Expand Down

0 comments on commit ad31b06

Please sign in to comment.