diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts index b1264e7f665..a98ec376f6c 100644 --- a/extensions/ipynb/src/notebookImagePaste.ts +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -18,6 +18,16 @@ enum MimeType { uriList = 'text/uri-list', } +const imageMimeTypes: ReadonlySet = new Set([ + MimeType.bmp, + MimeType.gif, + MimeType.ico, + MimeType.jpeg, + MimeType.png, + MimeType.tiff, + MimeType.webp, +]); + const imageExtToMime: ReadonlyMap = new Map([ ['.bmp', MimeType.bmp], ['.gif', MimeType.gif], @@ -126,16 +136,21 @@ async function getDroppedImageData( ): Promise { // Prefer using image data in the clipboard - // TODO: dataTransfer.get() limits to one image pasted. Should we support multiple? - const pngDataItem = dataTransfer.get(MimeType.png); - if (pngDataItem) { - const fileItem = pngDataItem.asFile(); - if (!fileItem) { - return []; + const files = coalesce(await Promise.all(Array.from(dataTransfer, async ([mimeType, item]): Promise => { + if (!imageMimeTypes.has(mimeType)) { + return; } - const data = await fileItem.data(); - return [{ fileName: fileItem.name, mimeType: MimeType.png, data }]; + const file = item.asFile(); + if (!file) { + return; + } + + const data = await file.data(); + return { fileName: file.name, mimeType, data }; + }))); + if (files.length) { + return files; } // Then fallback to image files in the uri-list diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts index dc1e5924829..60092e882c3 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts @@ -8,23 +8,42 @@ import * as vscode from 'vscode'; import { Utils } from 'vscode-uri'; import { getParentDocumentUri } from './dropIntoEditor'; +export class NewFilePathGenerator { -export async function getNewFileName(document: vscode.TextDocument, file: vscode.DataTransferFile): Promise { - const desiredPath = getDesiredNewFilePath(document, file); + private readonly _usedPaths = new Set(); - const root = Utils.dirname(desiredPath); - const ext = path.extname(file.name); - const baseName = path.basename(file.name, ext); - for (let i = 0; ; ++i) { - const name = i === 0 ? baseName : `${baseName}-${i}`; - const uri = vscode.Uri.joinPath(root, `${name}${ext}`); - try { - await vscode.workspace.fs.stat(uri); - } catch { - // Does not exist - return uri; + async getNewFilePath(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise { + const desiredPath = getDesiredNewFilePath(document, file); + + const root = Utils.dirname(desiredPath); + const ext = path.extname(file.name); + const baseName = path.basename(file.name, ext); + for (let i = 0; ; ++i) { + if (token.isCancellationRequested) { + return undefined; + } + + const name = i === 0 ? baseName : `${baseName}-${i}`; + const uri = vscode.Uri.joinPath(root, name + ext); + if (this._wasPathAlreadyUsed(uri)) { + continue; + } + + try { + await vscode.workspace.fs.stat(uri); + } catch { + if (!this._wasPathAlreadyUsed(uri)) { + // Does not exist + this._usedPaths.add(uri.toString()); + return uri; + } + } } } + + private _wasPathAlreadyUsed(uri: vscode.Uri) { + return this._usedPaths.has(uri.toString()); + } } function getDesiredNewFilePath(document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri { diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts index 86119c236b2..c047c46a06b 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts @@ -4,13 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { coalesce } from '../../util/arrays'; import { Schemes } from '../../util/schemes'; -import { getNewFileName } from './copyFiles'; +import { NewFilePathGenerator } from './copyFiles'; import { createUriListSnippet, tryGetUriListSnippet } from './dropIntoEditor'; const supportedImageMimes = new Set([ + 'image/bmp', + 'image/gif', + 'image/jpeg', 'image/png', - 'image/jpg', + 'image/webp', ]); class PasteEditProvider implements vscode.DocumentPasteEditProvider { @@ -26,53 +30,63 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { return; } - for (const imageMime of supportedImageMimes) { - const item = dataTransfer.get(imageMime); - const file = item?.asFile(); - if (item && file) { - const edit = await this._makeCreateImagePasteEdit(document, file, token); - if (token.isCancellationRequested) { - return; - } - - if (edit) { - return edit; - } - } + const edit = await this._makeCreateImagePasteEdit(document, dataTransfer, token); + if (edit) { + return edit; } const snippet = await tryGetUriListSnippet(document, dataTransfer, token); return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, snippet.label) : undefined; } - private async _makeCreateImagePasteEdit(document: vscode.TextDocument, file: vscode.DataTransferFile, token: vscode.CancellationToken): Promise { + private async _makeCreateImagePasteEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise { if (document.uri.scheme === Schemes.untitled) { - return undefined; + return; } - if (file.uri) { - // If file is already in workspace, we don't want to create a copy of it - const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri); - if (workspaceFolder) { - const snippet = createUriListSnippet(document, [file.uri]); - return snippet ? new vscode.DocumentPasteEdit(snippet.snippet, snippet.label) : undefined; + interface FileEntry { + readonly uri: vscode.Uri; + readonly newFileContents?: vscode.DataTransferFile; + } + + const pathGenerator = new NewFilePathGenerator(); + const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise => { + if (!supportedImageMimes.has(mime)) { + return; + } + + const file = item?.asFile(); + if (!file) { + return; + } + + if (file.uri) { + // If the file is already in a workspace, we don't want to create a copy of it + const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri); + if (workspaceFolder) { + return { uri: file.uri }; + } + } + + const uri = await pathGenerator.getNewFilePath(document, file, token); + return uri ? { uri, newFileContents: file } : undefined; + }))); + if (!fileEntries.length) { + return; + } + + const workspaceEdit = new vscode.WorkspaceEdit(); + for (const entry of fileEntries) { + if (entry.newFileContents) { + workspaceEdit.createFile(entry.uri, { contents: entry.newFileContents }); } } - const uri = await getNewFileName(document, file); - if (token.isCancellationRequested) { - return; - } - - const snippet = createUriListSnippet(document, [uri]); + const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri)); if (!snippet) { return; } - // Note that there is currently no way to undo the file creation :/ - const workspaceEdit = new vscode.WorkspaceEdit(); - workspaceEdit.createFile(uri, { contents: file }); - const pasteEdit = new vscode.DocumentPasteEdit(snippet.snippet, snippet.label); pasteEdit.additionalEdit = workspaceEdit; return pasteEdit;