Handle more already supported image formats across the codebase (#54326)

Kirill Bulatov created

Change summary

crates/agent_ui/src/mention_set.rs           |  6 +
crates/gpui/src/platform.rs                  | 22 ++++---
crates/gpui_linux/src/linux/x11/clipboard.rs | 62 +++++++--------------
3 files changed, 40 insertions(+), 50 deletions(-)

Detailed changes

crates/agent_ui/src/mention_set.rs 🔗

@@ -839,7 +839,11 @@ fn image_format_from_external_content(format: image::ImageFormat) -> Option<Imag
         image::ImageFormat::Bmp => Some(ImageFormat::Bmp),
         image::ImageFormat::Tiff => Some(ImageFormat::Tiff),
         image::ImageFormat::Ico => Some(ImageFormat::Ico),
-        _ => None,
+        image::ImageFormat::Pnm => Some(ImageFormat::Pnm),
+        _ => {
+            debug_panic!("An unhandled image format: {format:?}");
+            None
+        }
     }
 }
 

crates/gpui/src/platform.rs 🔗

@@ -2001,17 +2001,21 @@ impl ImageFormat {
         }
     }
 
-    /// Returns the ImageFormat for the given mime type
+    /// Returns the ImageFormat for the given mime type, including known aliases.
     pub fn from_mime_type(mime_type: &str) -> Option<Self> {
+        use strum::IntoEnumIterator;
+        Self::iter()
+            .find(|format| format.mime_type() == mime_type)
+            .or_else(|| Self::from_mime_type_alias(mime_type))
+    }
+
+    /// Non-canonical mime types that some producers use in the wild.
+    /// Unlike `mime_type()` which returns the single canonical form,
+    /// these are legacy or shortened variants we still need to recognize.
+    fn from_mime_type_alias(mime_type: &str) -> Option<Self> {
         match mime_type {
-            "image/png" => Some(Self::Png),
-            "image/jpeg" | "image/jpg" => Some(Self::Jpeg),
-            "image/webp" => Some(Self::Webp),
-            "image/gif" => Some(Self::Gif),
-            "image/svg+xml" => Some(Self::Svg),
-            "image/bmp" => Some(Self::Bmp),
-            "image/tiff" | "image/tif" => Some(Self::Tiff),
-            "image/ico" => Some(Self::Ico),
+            "image/jpg" => Some(Self::Jpeg),
+            "image/tif" => Some(Self::Tiff),
             _ => None,
         }
     }

crates/gpui_linux/src/linux/x11/clipboard.rs 🔗

@@ -48,6 +48,7 @@ use x11rb::{
 };
 
 use gpui::{ClipboardItem, Image, ImageFormat, hash};
+use strum::IntoEnumIterator;
 
 type Result<T, E = Error> = std::result::Result<T, E>;
 
@@ -989,14 +990,8 @@ impl Clipboard {
         self.inner.write(data, selection, wait)
     }
 
-    #[allow(unused)]
-    pub(crate) fn set_image(
-        &self,
-        image: Image,
-        selection: ClipboardKind,
-        wait: WaitConfig,
-    ) -> Result<()> {
-        let format = match image.format {
+    fn image_format_atom(&self, format: ImageFormat) -> Atom {
+        match format {
             ImageFormat::Png => self.inner.atoms.PNG__MIME,
             ImageFormat::Jpeg => self.inner.atoms.JPEG_MIME,
             ImageFormat::Webp => self.inner.atoms.WEBP_MIME,
@@ -1006,7 +1001,17 @@ impl Clipboard {
             ImageFormat::Tiff => self.inner.atoms.TIFF_MIME,
             ImageFormat::Ico => self.inner.atoms.ICO__MIME,
             ImageFormat::Pnm => self.inner.atoms.PNM__MIME,
-        };
+        }
+    }
+
+    #[allow(unused)]
+    pub(crate) fn set_image(
+        &self,
+        image: Image,
+        selection: ClipboardKind,
+        wait: WaitConfig,
+    ) -> Result<()> {
+        let format = self.image_format_atom(image.format);
         let data = vec![ClipboardData {
             bytes: image.bytes,
             format: self.inner.atoms.PNG__MIME,
@@ -1015,28 +1020,11 @@ impl Clipboard {
     }
 
     pub(crate) fn get_any(&self, selection: ClipboardKind) -> Result<ClipboardItem> {
-        const IMAGE_FORMAT_COUNT: usize = 7;
-        let image_format_atoms: [Atom; IMAGE_FORMAT_COUNT] = [
-            self.inner.atoms.PNG__MIME,
-            self.inner.atoms.JPEG_MIME,
-            self.inner.atoms.WEBP_MIME,
-            self.inner.atoms.GIF__MIME,
-            self.inner.atoms.SVG__MIME,
-            self.inner.atoms.BMP__MIME,
-            self.inner.atoms.TIFF_MIME,
-        ];
-        let image_formats: [ImageFormat; IMAGE_FORMAT_COUNT] = [
-            ImageFormat::Png,
-            ImageFormat::Jpeg,
-            ImageFormat::Webp,
-            ImageFormat::Gif,
-            ImageFormat::Svg,
-            ImageFormat::Bmp,
-            ImageFormat::Tiff,
-        ];
+        let image_entries = ImageFormat::iter()
+            .map(|format| (self.image_format_atom(format), format))
+            .collect::<Vec<_>>();
 
-        const TEXT_FORMAT_COUNT: usize = 6;
-        let text_format_atoms: [Atom; TEXT_FORMAT_COUNT] = [
+        let text_format_atoms: &[Atom] = &[
             self.inner.atoms.UTF8_STRING,
             self.inner.atoms.UTF8_MIME_0,
             self.inner.atoms.UTF8_MIME_1,
@@ -1045,17 +1033,11 @@ impl Clipboard {
             self.inner.atoms.TEXT_MIME_UNKNOWN,
         ];
 
-        let atom_none: Atom = AtomEnum::NONE.into();
-
-        const FORMAT_ATOM_COUNT: usize = TEXT_FORMAT_COUNT + IMAGE_FORMAT_COUNT;
-
-        let mut format_atoms: [Atom; FORMAT_ATOM_COUNT] = [atom_none; FORMAT_ATOM_COUNT];
-
         // image formats first, as they are more specific, and read will return the first
         // format that the contents can be converted to
-        format_atoms[0..IMAGE_FORMAT_COUNT].copy_from_slice(&image_format_atoms);
-        format_atoms[IMAGE_FORMAT_COUNT..].copy_from_slice(&text_format_atoms);
-        debug_assert!(!format_atoms.contains(&atom_none));
+        let mut format_atoms = Vec::with_capacity(image_entries.len() + text_format_atoms.len());
+        format_atoms.extend(image_entries.iter().map(|(atom, _)| *atom));
+        format_atoms.extend_from_slice(text_format_atoms);
 
         let result = self.inner.read(&format_atoms, selection)?;
 
@@ -1064,7 +1046,7 @@ impl Clipboard {
             self.inner.atom_name(result.format)
         );
 
-        for (format_atom, image_format) in image_format_atoms.into_iter().zip(image_formats) {
+        for (format_atom, image_format) in image_entries {
             if result.format == format_atom {
                 let bytes = result.bytes;
                 let id = hash(&bytes);