https://github.com/zed-industries/zed/issues/30972 brought up another case where our context is not enough to track the actual source of the issue: we get a general top-level error without inner error. The reason for this was `.ok_or_else(|| anyhow!("failed to read HEAD SHA"))?; ` on the top level. The PR finally reworks the way we use anyhow to reduce such issues (or at least make it simpler to bubble them up later in a fix). On top of that, uses a few more anyhow methods for better readability. * `.ok_or_else(|| anyhow!("..."))`, `map_err` and other similar error conversion/option reporting cases are replaced with `context` and `with_context` calls * in addition to that, various `anyhow!("failed to do ...")` are stripped with `.context("Doing ...")` messages instead to remove the parasitic `failed to` text * `anyhow::ensure!` is used instead of `if ... { return Err(...); }` calls * `anyhow::bail!` is used instead of `return Err(anyhow!(...));` Release Notes: - N/A
98 lines
3.1 KiB
Rust
98 lines
3.1 KiB
Rust
use anyhow::Result;
|
|
use base64::{
|
|
Engine as _, alphabet,
|
|
engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
|
|
};
|
|
use gpui::{App, ClipboardItem, Image, ImageFormat, Pixels, RenderImage, Window, img};
|
|
use std::sync::Arc;
|
|
use ui::{IntoElement, Styled, div, prelude::*};
|
|
|
|
use crate::outputs::OutputContent;
|
|
|
|
/// ImageView renders an image inline in an editor, adapting to the line height to fit the image.
|
|
pub struct ImageView {
|
|
clipboard_image: Arc<Image>,
|
|
height: u32,
|
|
width: u32,
|
|
image: Arc<RenderImage>,
|
|
}
|
|
|
|
pub const STANDARD_INDIFFERENT: GeneralPurpose = GeneralPurpose::new(
|
|
&alphabet::STANDARD,
|
|
GeneralPurposeConfig::new()
|
|
.with_encode_padding(false)
|
|
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
|
);
|
|
|
|
impl ImageView {
|
|
pub fn from(base64_encoded_data: &str) -> Result<Self> {
|
|
let filtered =
|
|
base64_encoded_data.replace(&[' ', '\n', '\t', '\r', '\x0b', '\x0c'][..], "");
|
|
let bytes = STANDARD_INDIFFERENT.decode(filtered)?;
|
|
|
|
let format = image::guess_format(&bytes)?;
|
|
|
|
let mut data = image::load_from_memory_with_format(&bytes, format)?.into_rgba8();
|
|
|
|
// Convert from RGBA to BGRA.
|
|
for pixel in data.chunks_exact_mut(4) {
|
|
pixel.swap(0, 2);
|
|
}
|
|
|
|
let height = data.height();
|
|
let width = data.width();
|
|
|
|
let gpui_image_data = RenderImage::new(vec![image::Frame::new(data)]);
|
|
|
|
let format = match format {
|
|
image::ImageFormat::Png => ImageFormat::Png,
|
|
image::ImageFormat::Jpeg => ImageFormat::Jpeg,
|
|
image::ImageFormat::Gif => ImageFormat::Gif,
|
|
image::ImageFormat::WebP => ImageFormat::Webp,
|
|
image::ImageFormat::Tiff => ImageFormat::Tiff,
|
|
image::ImageFormat::Bmp => ImageFormat::Bmp,
|
|
format => {
|
|
anyhow::bail!("unsupported image format {format:?}");
|
|
}
|
|
};
|
|
|
|
// Convert back to a GPUI image for use with the clipboard
|
|
let clipboard_image = Arc::new(Image::from_bytes(format, bytes));
|
|
|
|
Ok(ImageView {
|
|
clipboard_image,
|
|
height,
|
|
width,
|
|
image: Arc::new(gpui_image_data),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Render for ImageView {
|
|
fn render(&mut self, window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
|
let line_height = window.line_height();
|
|
|
|
let (height, width) = if self.height as f32 / line_height.0 == u8::MAX as f32 {
|
|
let height = u8::MAX as f32 * line_height.0;
|
|
let width = self.width as f32 * height / self.height as f32;
|
|
(height, width)
|
|
} else {
|
|
(self.height as f32, self.width as f32)
|
|
};
|
|
|
|
let image = self.image.clone();
|
|
|
|
div().h(Pixels(height)).w(Pixels(width)).child(img(image))
|
|
}
|
|
}
|
|
|
|
impl OutputContent for ImageView {
|
|
fn clipboard_content(&self, _window: &Window, _cx: &App) -> Option<ClipboardItem> {
|
|
Some(ClipboardItem::new_image(self.clipboard_image.as_ref()))
|
|
}
|
|
|
|
fn has_clipboard_content(&self, _window: &Window, _cx: &App) -> bool {
|
|
true
|
|
}
|
|
}
|