Support more image/video/audio types for markdown copy into files

Fixes #239016
This commit is contained in:
Matt Bierner
2025-01-28 15:35:46 -08:00
parent 737074e1b7
commit f9213d7b48
4 changed files with 72 additions and 56 deletions

View File

@@ -6,7 +6,8 @@
import * as vscode from 'vscode';
import { Utils } from 'vscode-uri';
import { Command } from '../commandManager';
import { createUriListSnippet, linkEditKind, mediaFileExtensions } from '../languageFeatures/copyFiles/shared';
import { createUriListSnippet, linkEditKind } from '../languageFeatures/copyFiles/shared';
import { mediaFileExtensions } from '../util/mimes';
import { coalesce } from '../util/arrays';
import { getParentDocumentUri } from '../util/document';
import { Schemes } from '../util/schemes';

View File

@@ -7,12 +7,12 @@ import * as vscode from 'vscode';
import { IMdParser } from '../../markdownEngine';
import { coalesce } from '../../util/arrays';
import { getParentDocumentUri } from '../../util/document';
import { Mime, mediaMimes } from '../../util/mimes';
import { getMediaKindForMime, MediaKind, Mime, rootMediaMimesTypes } from '../../util/mimes';
import { Schemes } from '../../util/schemes';
import { NewFilePathGenerator } from './newFilePathGenerator';
import { DropOrPasteEdit, createInsertUriListEdit, createUriListSnippet, getSnippetLabelAndKind, baseLinkEditKind, linkEditKind, audioEditKind, videoEditKind, imageEditKind } from './shared';
import { InsertMarkdownLink, shouldInsertMarkdownLinkByDefault } from './smartDropOrPaste';
import { UriList } from '../../util/uriList';
import { NewFilePathGenerator } from './newFilePathGenerator';
import { audioEditKind, baseLinkEditKind, createInsertUriListEdit, createUriListSnippet, DropOrPasteEdit, getSnippetLabelAndKind, imageEditKind, linkEditKind, videoEditKind } from './shared';
import { InsertMarkdownLink, shouldInsertMarkdownLinkByDefault } from './smartDropOrPaste';
enum CopyFilesSettings {
Never = 'never',
@@ -33,7 +33,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
public static readonly mimeTypes = [
Mime.textUriList,
'files',
...mediaMimes,
...Object.values(rootMediaMimesTypes).map(type => `${type}/*`),
];
private readonly _yieldTo = [
@@ -206,12 +206,14 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
interface FileEntry {
readonly uri: vscode.Uri;
readonly kind: MediaKind;
readonly newFile?: { readonly contents: vscode.DataTransferFile; readonly overwrite: boolean };
}
const pathGenerator = new NewFilePathGenerator();
const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise<FileEntry | undefined> => {
if (!mediaMimes.has(mime)) {
const mediaKind = getMediaKindForMime(mime);
if (!mediaKind) {
return;
}
@@ -224,7 +226,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
// 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 };
return { uri: file.uri, kind: mediaKind };
}
}
@@ -232,7 +234,7 @@ class ResourcePasteOrDropProvider implements vscode.DocumentPasteEditProvider, v
if (!newFile) {
return;
}
return { uri: newFile.uri, newFile: { contents: file, overwrite: newFile.overwrite } };
return { uri: newFile.uri, kind: mediaKind, newFile: { contents: file, overwrite: newFile.overwrite } };
})));
if (!fileEntries.length) {
return;

View File

@@ -11,6 +11,7 @@ import { getDocumentDir } from '../../util/document';
import { Schemes } from '../../util/schemes';
import { UriList } from '../../util/uriList';
import { resolveSnippet } from './snippets';
import { mediaFileExtensions, MediaKind } from '../../util/mimes';
/** Base kind for any sort of markdown link, including both path and media links */
export const baseLinkEditKind = vscode.DocumentDropOrPasteEditKind.Empty.append('markdown', 'link');
@@ -22,39 +23,6 @@ export const imageEditKind = baseLinkEditKind.append('image');
export const audioEditKind = baseLinkEditKind.append('audio');
export const videoEditKind = baseLinkEditKind.append('video');
enum MediaKind {
Image,
Video,
Audio,
}
export const mediaFileExtensions = new Map<string, MediaKind>([
// Images
['avif', MediaKind.Image],
['bmp', MediaKind.Image],
['gif', MediaKind.Image],
['ico', MediaKind.Image],
['jpe', MediaKind.Image],
['jpeg', MediaKind.Image],
['jpg', MediaKind.Image],
['png', MediaKind.Image],
['psd', MediaKind.Image],
['svg', MediaKind.Image],
['tga', MediaKind.Image],
['tif', MediaKind.Image],
['tiff', MediaKind.Image],
['webp', MediaKind.Image],
// Videos
['ogg', MediaKind.Video],
['mp4', MediaKind.Video],
// Audio Files
['mp3', MediaKind.Audio],
['aac', MediaKind.Audio],
['wav', MediaKind.Audio],
]);
export function getSnippetLabelAndKind(counter: { readonly insertedAudioCount: number; readonly insertedVideoCount: number; readonly insertedImageCount: number; readonly insertedLinkCount: number }): {
label: string;
kind: vscode.DocumentDropOrPasteEditKind;
@@ -207,6 +175,7 @@ export function createUriListSnippet(
uris: ReadonlyArray<{
readonly uri: vscode.Uri;
readonly str?: string;
readonly kind?: MediaKind;
}>,
options?: UriListSnippetOptions,
): UriSnippet | undefined {
@@ -229,7 +198,7 @@ export function createUriListSnippet(
uris.forEach((uri, i) => {
const mdPath = (!options?.preserveAbsoluteUris ? getRelativeMdPath(documentDir, uri.uri) : undefined) ?? uri.str ?? uri.uri.toString();
const desiredKind = getDesiredLinkKind(uri.uri, options);
const desiredKind = getDesiredLinkKind(uri.uri, uri.kind, options);
if (desiredKind === DesiredLinkKind.Link) {
insertedLinkCount++;
@@ -276,7 +245,7 @@ enum DesiredLinkKind {
Audio,
}
function getDesiredLinkKind(uri: vscode.Uri, options: UriListSnippetOptions | undefined): DesiredLinkKind {
function getDesiredLinkKind(uri: vscode.Uri, uriFileKind: MediaKind | undefined, options: UriListSnippetOptions | undefined): DesiredLinkKind {
if (options?.linkKindHint instanceof vscode.DocumentDropOrPasteEditKind) {
if (linkEditKind.contains(options.linkKindHint)) {
return DesiredLinkKind.Link;
@@ -289,6 +258,14 @@ function getDesiredLinkKind(uri: vscode.Uri, options: UriListSnippetOptions | un
}
}
if (typeof uriFileKind !== 'undefined') {
switch (uriFileKind) {
case MediaKind.Video: return DesiredLinkKind.Video;
case MediaKind.Audio: return DesiredLinkKind.Audio;
case MediaKind.Image: return DesiredLinkKind.Image;
}
}
const normalizedExt = URI.Utils.extname(uri).toLowerCase().replace('.', '');
if (options?.linkKindHint === 'media' || mediaFileExtensions.has(normalizedExt)) {
switch (mediaFileExtensions.get(normalizedExt)) {

View File

@@ -8,16 +8,52 @@ export const Mime = {
textPlain: 'text/plain',
} as const;
export const mediaMimes = new Set([
'image/avif',
'image/bmp',
'image/gif',
'image/jpeg',
'image/png',
'image/webp',
'video/mp4',
'video/ogg',
'audio/mpeg',
'audio/aac',
'audio/x-wav',
export const rootMediaMimesTypes = Object.freeze({
image: 'image',
audio: 'audio',
video: 'video',
});
export enum MediaKind {
Image = 1,
Video,
Audio
}
export function getMediaKindForMime(mime: string): MediaKind | undefined {
const root = mime.toLowerCase().split('/').at(0);
switch (root) {
case 'image': return MediaKind.Image;
case 'video': return MediaKind.Video;
case 'audio': return MediaKind.Audio;
default: return undefined;
}
}
export const mediaFileExtensions = new Map<string, MediaKind>([
// Images
['avif', MediaKind.Image],
['bmp', MediaKind.Image],
['gif', MediaKind.Image],
['ico', MediaKind.Image],
['jpe', MediaKind.Image],
['jpeg', MediaKind.Image],
['jpg', MediaKind.Image],
['png', MediaKind.Image],
['psd', MediaKind.Image],
['svg', MediaKind.Image],
['tga', MediaKind.Image],
['tif', MediaKind.Image],
['tiff', MediaKind.Image],
['webp', MediaKind.Image],
// Videos
['ogg', MediaKind.Video],
['mp4', MediaKind.Video],
['mov', MediaKind.Video],
// Audio Files
['mp3', MediaKind.Audio],
['aac', MediaKind.Audio],
['wav', MediaKind.Audio],
]);