diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index 87de74270ca..598afb9bcdf 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -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, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts index 65bd9bf9d7c..6a8bdf88609 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropResourceProvider.ts @@ -44,12 +44,12 @@ class ResourceDropProvider implements vscode.DocumentDropEditProvider { private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { 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); diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts index 23292cbb97d..46b47e5198e 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteResourceProvider.ts @@ -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; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index b68b9cdaa2b..de9d6a05d15 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -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; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index dc8c15075f4..12f0f632ec6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -75,14 +75,13 @@ export function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument) return vscode.workspace.getConfiguration('markdown', document).get('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 { diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index f0133ad85cc..8081d7027d1 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -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}]()'); }); 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)'); }); });