diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index 9369141465e..61649c08017 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; import { Command } from '../commandManager'; -import { createUriListSnippet, mediaFileExtensions } from '../languageFeatures/copyFiles/shared'; +import { createUriListSnippet, linkEditKind, mediaFileExtensions } from '../languageFeatures/copyFiles/shared'; import { coalesce } from '../util/arrays'; import { getParentDocumentUri } from '../util/document'; import { Schemes } from '../util/schemes'; @@ -84,7 +84,7 @@ function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: re const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => { const selectionText = activeEditor.document.getText(selection); const snippet = createUriListSnippet(activeEditor.document.uri, selectedFiles.map(uri => ({ uri })), { - insertAsMedia: insertAsMedia, + linkKindHint: insertAsMedia ? 'media' : linkEditKind, placeholderText: selectionText, placeholderStartIndex: (i + 1) * selectedFiles.length, separator: insertAsMedia ? '\n' : ' ', diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts index 7677bdadcd3..f74ff677e0d 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropOrPasteResource.ts @@ -106,10 +106,10 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, - settings: { + settings: Readonly<{ insert: InsertMarkdownLink; copyIntoWorkspace: CopyFilesSettings; - }, + }>, context: vscode.DocumentPasteEditContext | undefined, token: vscode.CancellationToken, ): Promise { @@ -172,7 +172,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v } } - const edit = createInsertUriListEdit(document, ranges, uriList); + const edit = createInsertUriListEdit(document, ranges, uriList, { linkKindHint: context?.only }); if (!edit) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts index 4cae4f60cf9..a947216fe32 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/pasteUrlProvider.ts @@ -29,7 +29,7 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { document: vscode.TextDocument, ranges: readonly vscode.Range[], dataTransfer: vscode.DataTransfer, - _context: vscode.DocumentPasteEditContext, + context: vscode.DocumentPasteEditContext, token: vscode.CancellationToken, ): Promise { const pasteUrlSetting = vscode.workspace.getConfiguration('markdown', document) @@ -44,12 +44,17 @@ class PasteUrlEditProvider implements vscode.DocumentPasteEditProvider { return; } + // TODO: If the user has explicitly requested to paste as a markdown link, + // try to paste even if we don't have a valid uri const uriText = findValidUriInText(text); if (!uriText) { return; } - const edit = createInsertUriListEdit(document, ranges, UriList.from(uriText), { preserveAbsoluteUris: true }); + const edit = createInsertUriListEdit(document, ranges, UriList.from(uriText), { + linkKindHint: context.only, + preserveAbsoluteUris: true + }); if (!edit) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 2d71883dc78..cdd2476ad38 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -177,11 +177,11 @@ interface UriListSnippetOptions { readonly placeholderStartIndex?: number; /** - * Controls if a media link (`![](...)`) is inserted instead of a normal markdown link. + * Hints how links should be inserted, e.g. as normal markdown link or as an image. * - * By default tries to infer this from the uri. + * By default this is inferred from the uri. If you use `media`, we will insert the resource as an image, video, or audio. */ - readonly insertAsMedia?: boolean; + readonly linkKindHint?: vscode.DocumentDropOrPasteEditKind | 'media'; readonly separator?: string; @@ -229,12 +229,16 @@ export function createUriListSnippet( uris.forEach((uri, i) => { const mdPath = (!options?.preserveAbsoluteUris ? getRelativeMdPath(documentDir, uri.uri) : undefined) ?? uri.str ?? uri.uri.toString(); - const ext = URI.Utils.extname(uri.uri).toLowerCase().replace('.', ''); - const insertAsMedia = options?.insertAsMedia || (typeof options?.insertAsMedia === 'undefined' && mediaFileExtensions.has(ext)); + const desiredKind = getDesiredLinkKind(uri.uri, options); - if (insertAsMedia) { - const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video; - const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio; + if (desiredKind === DesiredLinkKind.Link) { + insertedLinkCount++; + snippet.appendText('['); + snippet.appendPlaceholder(escapeBrackets(options?.placeholderText ?? 'text'), placeholderIndex); + snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); + } else { + const insertAsVideo = desiredKind === DesiredLinkKind.Video; + const insertAsAudio = desiredKind === DesiredLinkKind.Audio; if (insertAsVideo || insertAsAudio) { if (insertAsVideo) { insertedVideoCount++; @@ -255,11 +259,6 @@ export function createUriListSnippet( snippet.appendPlaceholder(placeholderText, placeholderIndex); snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); } - } else { - insertedLinkCount++; - snippet.appendText('['); - snippet.appendPlaceholder(escapeBrackets(options?.placeholderText ?? 'text'), placeholderIndex); - snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); } if (i < uris.length - 1 && uris.length > 1) { @@ -270,6 +269,37 @@ export function createUriListSnippet( return { snippet, insertedAudioCount, insertedVideoCount, insertedImageCount, insertedLinkCount }; } +enum DesiredLinkKind { + Link, + Image, + Video, + Audio, +} + +function getDesiredLinkKind(uri: vscode.Uri, options: UriListSnippetOptions | undefined): DesiredLinkKind { + if (options?.linkKindHint instanceof vscode.DocumentDropOrPasteEditKind) { + if (linkEditKind.contains(options.linkKindHint)) { + return DesiredLinkKind.Link; + } else if (imageEditKind.contains(options.linkKindHint)) { + return DesiredLinkKind.Image; + } else if (audioEditKind.contains(options.linkKindHint)) { + return DesiredLinkKind.Audio; + } else if (videoEditKind.contains(options.linkKindHint)) { + return DesiredLinkKind.Video; + } + } + + const normalizedExt = URI.Utils.extname(uri).toLowerCase().replace('.', ''); + if (options?.linkKindHint === 'media' || mediaFileExtensions.has(normalizedExt)) { + switch (mediaFileExtensions.get(normalizedExt)) { + case MediaKind.Video: return DesiredLinkKind.Video; + case MediaKind.Audio: return DesiredLinkKind.Audio; + default: return DesiredLinkKind.Image; + } + } + + return DesiredLinkKind.Link; +} function getRelativeMdPath(dir: vscode.Uri | undefined, file: vscode.Uri): string | undefined { if (dir && dir.scheme === file.scheme && dir.authority === file.authority) { diff --git a/extensions/markdown-language-features/src/test/pasteUrl.test.ts b/extensions/markdown-language-features/src/test/pasteUrl.test.ts index fdb6e6c2f71..45737c354da 100644 --- a/extensions/markdown-language-features/src/test/pasteUrl.test.ts +++ b/extensions/markdown-language-features/src/test/pasteUrl.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; import { InMemoryDocument } from '../client/inMemoryDocument'; -import { createInsertUriListEdit } from '../languageFeatures/copyFiles/shared'; +import { createInsertUriListEdit, imageEditKind, linkEditKind } from '../languageFeatures/copyFiles/shared'; import { InsertMarkdownLink, findValidUriInText, shouldInsertMarkdownLinkByDefault } from '../languageFeatures/copyFiles/smartDropOrPaste'; import { noopToken } from '../util/cancellation'; import { UriList } from '../util/uriList'; @@ -20,8 +20,6 @@ function makeTestDoc(contents: string) { suite('createEditAddingLinksForUriList', () => { test('Markdown Link Pasting should occur for a valid link (end to end)', async () => { - // createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet - const result = createInsertUriListEdit( new InMemoryDocument(vscode.Uri.file('test.md'), 'hello world!'), [new vscode.Range(0, 0, 0, 12)], UriList.from('https://www.microsoft.com/')); // need to check the actual result -> snippet value @@ -110,7 +108,6 @@ suite('createEditAddingLinksForUriList', () => { }); suite('createInsertUriListEdit', () => { - test('Should create snippet with < > when pasted link has an mismatched parentheses', () => { const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.mic(rosoft.com')); assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}]()'); @@ -135,6 +132,25 @@ suite('createEditAddingLinksForUriList', () => { const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/path?query=value&another=value#fragment')); assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.example.com/path?query=value&another=value#fragment)'); }); + + test('Should add image for image file by default', () => { + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/cat.png')); + assert.strictEqual(edit?.edits?.[0].snippet.value, '![${1:alt text}](https://www.example.com/cat.png)'); + }); + + test('Should be able to override insert style to use link', () => { + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/cat.png'), { + linkKindHint: linkEditKind, + }); + assert.strictEqual(edit?.edits?.[0].snippet.value, '[${1:text}](https://www.example.com/cat.png)'); + }); + + test('Should be able to override insert style to use images', () => { + const edit = createInsertUriListEdit(makeTestDoc(''), [new vscode.Range(0, 0, 0, 0)], UriList.from('https://www.example.com/'), { + linkKindHint: imageEditKind, + }); + assert.strictEqual(edit?.edits?.[0].snippet.value, '![${1:alt text}](https://www.example.com/)'); + }); });