svg_preview: Update preview on every buffer edit (#41270)

Finn Evers created

Closes https://github.com/zed-industries/zed/issues/39104

This fixes an issue where the preview would not work for remote buffers
in the process.

Release Notes:

- Fixed an issue where the SVG preview would not work in remote
scenarios.
- The SVG preview will now rerender on every keypress instead of only on
saves.

Change summary

Cargo.lock                                 |   2 
crates/gpui/src/elements/img.rs            |  36 -
crates/gpui/src/gpui.rs                    |   2 
crates/gpui/src/platform.rs                |  12 
crates/gpui/src/svg_renderer.rs            |  44 ++
crates/gpui/src/window.rs                  |   2 
crates/svg_preview/Cargo.toml              |   2 
crates/svg_preview/src/svg_preview_view.rs | 379 ++++++++++++-----------
crates/zlog/src/filter.rs                  |   3 
9 files changed, 267 insertions(+), 215 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -16495,7 +16495,7 @@ dependencies = [
  "editor",
  "file_icons",
  "gpui",
- "multi_buffer",
+ "language",
  "ui",
  "workspace",
 ]

crates/gpui/src/elements/img.rs 🔗

@@ -2,14 +2,13 @@ use crate::{
     AnyElement, AnyImageCache, App, Asset, AssetLogger, Bounds, DefiniteLength, Element, ElementId,
     Entity, GlobalElementId, Hitbox, Image, ImageCache, InspectorElementId, InteractiveElement,
     Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource,
-    SMOOTH_SVG_SCALE_FACTOR, SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task,
-    Window, px, swap_rgba_pa_to_bgra,
+    SharedString, SharedUri, StyleRefinement, Styled, Task, Window, px,
 };
 use anyhow::{Context as _, Result};
 
 use futures::{AsyncReadExt, Future};
 use image::{
-    AnimationDecoder, DynamicImage, Frame, ImageBuffer, ImageError, ImageFormat, Rgba,
+    AnimationDecoder, DynamicImage, Frame, ImageError, ImageFormat, Rgba,
     codecs::{gif::GifDecoder, webp::WebPDecoder},
 };
 use smallvec::SmallVec;
@@ -160,13 +159,15 @@ pub trait StyledImage: Sized {
         self
     }
 
-    /// Set the object fit for the image.
+    /// Set a fallback function that will be invoked to render an error view should
+    /// the image fail to load.
     fn with_fallback(mut self, fallback: impl Fn() -> AnyElement + 'static) -> Self {
         self.image_style().fallback = Some(Box::new(fallback));
         self
     }
 
-    /// Set the object fit for the image.
+    /// Set a fallback function that will be invoked to render a view while the image
+    /// is still being loaded.
     fn with_loading(mut self, loading: impl Fn() -> AnyElement + 'static) -> Self {
         self.image_style().loading = Some(Box::new(loading));
         self
@@ -631,7 +632,7 @@ impl Asset for ImageAssetLoader {
                 }
             };
 
-            let data = if let Ok(format) = image::guess_format(&bytes) {
+            if let Ok(format) = image::guess_format(&bytes) {
                 let data = match format {
                     ImageFormat::Gif => {
                         let decoder = GifDecoder::new(Cursor::new(&bytes))?;
@@ -689,25 +690,12 @@ impl Asset for ImageAssetLoader {
                     }
                 };
 
-                RenderImage::new(data)
+                Ok(Arc::new(RenderImage::new(data)))
             } else {
-                let pixmap =
-                    // TODO: Can we make svgs always rescale?
-                    svg_renderer.render_pixmap(&bytes, SvgSize::ScaleFactor(SMOOTH_SVG_SCALE_FACTOR))?;
-
-                let mut buffer =
-                    ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take()).unwrap();
-
-                for pixel in buffer.chunks_exact_mut(4) {
-                    swap_rgba_pa_to_bgra(pixel);
-                }
-
-                let mut image = RenderImage::new(SmallVec::from_elem(Frame::new(buffer), 1));
-                image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
-                image
-            };
-
-            Ok(Arc::new(data))
+                svg_renderer
+                    .render_single_frame(&bytes, 1.0, true)
+                    .map_err(Into::into)
+            }
         }
     }
 }

crates/gpui/src/gpui.rs 🔗

@@ -95,7 +95,7 @@ pub use smol::Timer;
 pub use style::*;
 pub use styled::*;
 pub use subscription::*;
-use svg_renderer::*;
+pub use svg_renderer::*;
 pub(crate) use tab_stop::*;
 pub use taffy::{AvailableSpace, LayoutId};
 #[cfg(any(test, feature = "test-support"))]

crates/gpui/src/platform.rs 🔗

@@ -40,7 +40,7 @@ use crate::{
     DEFAULT_WINDOW_SIZE, DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun,
     ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
     Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, Scene, ShapedGlyph,
-    ShapedRun, SharedString, Size, SvgRenderer, SvgSize, SystemWindowTab, Task, TaskLabel, Window,
+    ShapedRun, SharedString, Size, SvgRenderer, SystemWindowTab, Task, TaskLabel, Window,
     WindowControlArea, hash, point, px, size,
 };
 use anyhow::Result;
@@ -1825,13 +1825,9 @@ impl Image {
             ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
             ImageFormat::Ico => frames_for_image(&self.bytes, image::ImageFormat::Ico)?,
             ImageFormat::Svg => {
-                let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
-
-                let buffer =
-                    image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
-                        .unwrap();
-
-                SmallVec::from_elem(Frame::new(buffer), 1)
+                return svg_renderer
+                    .render_single_frame(&self.bytes, 1.0, false)
+                    .map_err(Into::into);
             }
         };
 

crates/gpui/src/svg_renderer.rs 🔗

@@ -1,5 +1,10 @@
-use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
+use crate::{
+    AssetSource, DevicePixels, IsZero, RenderImage, Result, SharedString, Size,
+    swap_rgba_pa_to_bgra,
+};
+use image::Frame;
 use resvg::tiny_skia::Pixmap;
+use smallvec::SmallVec;
 use std::{
     hash::Hash,
     sync::{Arc, LazyLock},
@@ -15,17 +20,22 @@ pub(crate) struct RenderSvgParams {
 }
 
 #[derive(Clone)]
+/// A struct holding everything necessary to render SVGs.
 pub struct SvgRenderer {
     asset_source: Arc<dyn AssetSource>,
     usvg_options: Arc<usvg::Options<'static>>,
 }
 
+/// The size in which to render the SVG.
 pub enum SvgSize {
+    /// An absolute size in device pixels.
     Size(Size<DevicePixels>),
+    /// A scaling factor to apply to the size provided by the SVG.
     ScaleFactor(f32),
 }
 
 impl SvgRenderer {
+    /// Creates a new SVG renderer with the provided asset source.
     pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
         static FONT_DB: LazyLock<Arc<usvg::fontdb::Database>> = LazyLock::new(|| {
             let mut db = usvg::fontdb::Database::new();
@@ -54,7 +64,35 @@ impl SvgRenderer {
         }
     }
 
-    pub(crate) fn render(
+    /// Renders the given bytes into an image buffer.
+    pub fn render_single_frame(
+        &self,
+        bytes: &[u8],
+        scale_factor: f32,
+        to_brga: bool,
+    ) -> Result<Arc<RenderImage>, usvg::Error> {
+        self.render_pixmap(
+            bytes,
+            SvgSize::ScaleFactor(scale_factor * SMOOTH_SVG_SCALE_FACTOR),
+        )
+        .map(|pixmap| {
+            let mut buffer =
+                image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
+                    .unwrap();
+
+            if to_brga {
+                for pixel in buffer.chunks_exact_mut(4) {
+                    swap_rgba_pa_to_bgra(pixel);
+                }
+            }
+
+            let mut image = RenderImage::new(SmallVec::from_const([Frame::new(buffer)]));
+            image.scale_factor = SMOOTH_SVG_SCALE_FACTOR;
+            Arc::new(image)
+        })
+    }
+
+    pub(crate) fn render_alpha_mask(
         &self,
         params: &RenderSvgParams,
     ) -> Result<Option<(Size<DevicePixels>, Vec<u8>)>> {
@@ -80,7 +118,7 @@ impl SvgRenderer {
         Ok(Some((size, alpha_mask)))
     }
 
-    pub fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
+    fn render_pixmap(&self, bytes: &[u8], size: SvgSize) -> Result<Pixmap, usvg::Error> {
         let tree = usvg::Tree::from_data(bytes, &self.usvg_options)?;
         let svg_size = tree.size();
         let scale = match size {

crates/gpui/src/window.rs 🔗

@@ -3104,7 +3104,7 @@ impl Window {
         let Some(tile) =
             self.sprite_atlas
                 .get_or_insert_with(&params.clone().into(), &mut || {
-                    let Some((size, bytes)) = cx.svg_renderer.render(&params)? else {
+                    let Some((size, bytes)) = cx.svg_renderer.render_alpha_mask(&params)? else {
                         return Ok(None);
                     };
                     Ok(Some((size, Cow::Owned(bytes))))

crates/svg_preview/Cargo.toml 🔗

@@ -15,6 +15,6 @@ path = "src/svg_preview.rs"
 editor.workspace = true
 file_icons.workspace = true
 gpui.workspace = true
-multi_buffer.workspace = true
+language.workspace = true
 ui.workspace = true
 workspace.workspace = true

crates/svg_preview/src/svg_preview_view.rs 🔗

@@ -1,13 +1,13 @@
-use std::path::PathBuf;
+use std::mem;
+use std::sync::Arc;
 
 use editor::Editor;
 use file_icons::FileIcons;
 use gpui::{
-    App, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageSource, IntoElement,
-    ParentElement, Render, Resource, RetainAllImageCache, Styled, Subscription, WeakEntity, Window,
-    div, img,
+    App, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render,
+    RenderImage, Styled, Subscription, Task, WeakEntity, Window, div, img,
 };
-use multi_buffer::{Event as MultiBufferEvent, MultiBuffer};
+use language::{Buffer, BufferEvent};
 use ui::prelude::*;
 use workspace::item::Item;
 use workspace::{Pane, Workspace};
@@ -16,9 +16,10 @@ use crate::{OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide};
 
 pub struct SvgPreviewView {
     focus_handle: FocusHandle,
-    svg_path: Option<PathBuf>,
-    image_cache: Entity<RetainAllImageCache>,
-    _buffer_subscription: Subscription,
+    buffer: Option<Entity<Buffer>>,
+    current_svg: Option<Result<Arc<RenderImage>, SharedString>>,
+    _refresh: Task<()>,
+    _buffer_subscription: Option<Subscription>,
     _workspace_subscription: Option<Subscription>,
 }
 
@@ -31,6 +32,182 @@ pub enum SvgPreviewMode {
 }
 
 impl SvgPreviewView {
+    pub fn new(
+        mode: SvgPreviewMode,
+        active_editor: Entity<Editor>,
+        workspace_handle: WeakEntity<Workspace>,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) -> Entity<Self> {
+        cx.new(|cx| {
+            let workspace_subscription = if mode == SvgPreviewMode::Follow
+                && let Some(workspace) = workspace_handle.upgrade()
+            {
+                Some(Self::subscribe_to_workspace(workspace, window, cx))
+            } else {
+                None
+            };
+
+            let buffer = active_editor
+                .read(cx)
+                .buffer()
+                .clone()
+                .read_with(cx, |buffer, _cx| buffer.as_singleton());
+
+            let subscription = buffer
+                .as_ref()
+                .map(|buffer| Self::create_buffer_subscription(buffer, window, cx));
+
+            let mut this = Self {
+                focus_handle: cx.focus_handle(),
+                buffer,
+                current_svg: None,
+                _buffer_subscription: subscription,
+                _workspace_subscription: workspace_subscription,
+                _refresh: Task::ready(()),
+            };
+            this.render_image(window, cx);
+
+            this
+        })
+    }
+
+    fn subscribe_to_workspace(
+        workspace: Entity<Workspace>,
+        window: &Window,
+        cx: &mut Context<Self>,
+    ) -> Subscription {
+        cx.subscribe_in(
+            &workspace,
+            window,
+            move |this: &mut SvgPreviewView, workspace, event: &workspace::Event, window, cx| {
+                if let workspace::Event::ActiveItemChanged = event {
+                    let workspace = workspace.read(cx);
+                    if let Some(active_item) = workspace.active_item(cx)
+                        && let Some(editor) = active_item.downcast::<Editor>()
+                        && Self::is_svg_file(&editor, cx)
+                    {
+                        let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() else {
+                            return;
+                        };
+                        if this.buffer.as_ref() != Some(&buffer) {
+                            this._buffer_subscription =
+                                Some(Self::create_buffer_subscription(&buffer, window, cx));
+                            this.buffer = Some(buffer);
+                            this.render_image(window, cx);
+                            cx.notify();
+                        }
+                    } else {
+                        this.set_current(None, window, cx);
+                    }
+                }
+            },
+        )
+    }
+
+    fn render_image(&mut self, window: &Window, cx: &mut Context<Self>) {
+        let Some(buffer) = self.buffer.as_ref() else {
+            return;
+        };
+        const SCALE_FACTOR: f32 = 1.0;
+
+        let renderer = cx.svg_renderer();
+        let content = buffer.read(cx).snapshot();
+        let background_task = cx.background_spawn(async move {
+            renderer.render_single_frame(content.text().as_bytes(), SCALE_FACTOR, true)
+        });
+
+        self._refresh = cx.spawn_in(window, async move |this, cx| {
+            let result = background_task.await;
+
+            this.update_in(cx, |view, window, cx| {
+                let current = result.map_err(|e| e.to_string().into());
+                view.set_current(Some(current), window, cx);
+            })
+            .ok();
+        });
+    }
+
+    fn set_current(
+        &mut self,
+        image: Option<Result<Arc<RenderImage>, SharedString>>,
+        window: &mut Window,
+        cx: &mut Context<Self>,
+    ) {
+        if let Some(Ok(image)) = mem::replace(&mut self.current_svg, image) {
+            window.drop_image(image).ok();
+        }
+        cx.notify();
+    }
+
+    fn find_existing_preview_item_idx(
+        pane: &Pane,
+        editor: &Entity<Editor>,
+        cx: &App,
+    ) -> Option<usize> {
+        let buffer_id = editor.read(cx).buffer().entity_id();
+        pane.items_of_type::<SvgPreviewView>()
+            .find(|view| {
+                view.read(cx)
+                    .buffer
+                    .as_ref()
+                    .is_some_and(|buffer| buffer.entity_id() == buffer_id)
+            })
+            .and_then(|view| pane.index_for_item(&view))
+    }
+
+    pub fn resolve_active_item_as_svg_editor(
+        workspace: &Workspace,
+        cx: &mut Context<Workspace>,
+    ) -> Option<Entity<Editor>> {
+        workspace
+            .active_item(cx)?
+            .act_as::<Editor>(cx)
+            .filter(|editor| Self::is_svg_file(&editor, cx))
+    }
+
+    fn create_svg_view(
+        mode: SvgPreviewMode,
+        workspace: &mut Workspace,
+        editor: Entity<Editor>,
+        window: &mut Window,
+        cx: &mut Context<Workspace>,
+    ) -> Entity<SvgPreviewView> {
+        let workspace_handle = workspace.weak_handle();
+        SvgPreviewView::new(mode, editor, workspace_handle, window, cx)
+    }
+
+    fn create_buffer_subscription(
+        buffer: &Entity<Buffer>,
+        window: &Window,
+        cx: &mut Context<Self>,
+    ) -> Subscription {
+        cx.subscribe_in(
+            buffer,
+            window,
+            move |this, _buffer, event: &BufferEvent, window, cx| match event {
+                BufferEvent::Edited | BufferEvent::Saved => {
+                    this.render_image(window, cx);
+                }
+                _ => {}
+            },
+        )
+    }
+
+    pub fn is_svg_file(editor: &Entity<Editor>, cx: &App) -> bool {
+        editor
+            .read(cx)
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .and_then(|buffer| buffer.read(cx).file())
+            .is_some_and(|file| {
+                file.path()
+                    .extension()
+                    .is_some_and(|ext| ext.eq_ignore_ascii_case("svg"))
+            })
+    }
+
     pub fn register(workspace: &mut Workspace, _window: &mut Window, _cx: &mut Context<Workspace>) {
         workspace.register_action(move |workspace, _: &OpenPreview, window, cx| {
             if let Some(editor) = Self::resolve_active_item_as_svg_editor(workspace, cx)
@@ -104,154 +281,6 @@ impl SvgPreviewView {
             }
         });
     }
-
-    fn find_existing_preview_item_idx(
-        pane: &Pane,
-        editor: &Entity<Editor>,
-        cx: &App,
-    ) -> Option<usize> {
-        let editor_path = Self::get_svg_path(editor.read(cx).buffer(), cx);
-        pane.items_of_type::<SvgPreviewView>()
-            .find(|view| {
-                let view_read = view.read(cx);
-                view_read.svg_path.is_some() && view_read.svg_path == editor_path
-            })
-            .and_then(|view| pane.index_for_item(&view))
-    }
-
-    pub fn resolve_active_item_as_svg_editor(
-        workspace: &Workspace,
-        cx: &mut Context<Workspace>,
-    ) -> Option<Entity<Editor>> {
-        let editor = workspace.active_item(cx)?.act_as::<Editor>(cx)?;
-
-        if Self::is_svg_file(&editor, cx) {
-            Some(editor)
-        } else {
-            None
-        }
-    }
-
-    fn create_svg_view(
-        mode: SvgPreviewMode,
-        workspace: &mut Workspace,
-        editor: Entity<Editor>,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> Entity<SvgPreviewView> {
-        let workspace_handle = workspace.weak_handle();
-        SvgPreviewView::new(mode, editor, workspace_handle, window, cx)
-    }
-
-    pub fn new(
-        mode: SvgPreviewMode,
-        active_editor: Entity<Editor>,
-        workspace_handle: WeakEntity<Workspace>,
-        window: &mut Window,
-        cx: &mut Context<Workspace>,
-    ) -> Entity<Self> {
-        cx.new(|cx| {
-            let image_cache = RetainAllImageCache::new(cx);
-            let buffer = active_editor.read(cx).buffer();
-            let svg_path = Self::get_svg_path(buffer, cx);
-            let subscription = Self::create_buffer_subscription(&buffer.clone(), window, cx);
-
-            // Subscribe to workspace active item changes to follow SVG files
-            let workspace_subscription = if mode == SvgPreviewMode::Follow {
-                workspace_handle.upgrade().map(|workspace_handle| {
-                    cx.subscribe_in(
-                        &workspace_handle,
-                        window,
-                        |this: &mut SvgPreviewView,
-                         workspace,
-                         event: &workspace::Event,
-                         window,
-                         cx| {
-                            if let workspace::Event::ActiveItemChanged = event {
-                                let workspace_read = workspace.read(cx);
-                                if let Some(active_item) = workspace_read.active_item(cx)
-                                    && let Some(editor) = active_item.downcast::<Editor>()
-                                    && Self::is_svg_file(&editor, cx)
-                                {
-                                    let buffer = editor.read(cx).buffer();
-                                    let new_path = Self::get_svg_path(&buffer, cx);
-                                    if this.svg_path != new_path {
-                                        this.svg_path = new_path;
-                                        this._buffer_subscription =
-                                            Self::create_buffer_subscription(
-                                                &buffer.clone(),
-                                                window,
-                                                cx,
-                                            );
-                                        cx.notify();
-                                    }
-                                }
-                            }
-                        },
-                    )
-                })
-            } else {
-                None
-            };
-
-            Self {
-                focus_handle: cx.focus_handle(),
-                svg_path,
-                image_cache,
-                _buffer_subscription: subscription,
-                _workspace_subscription: workspace_subscription,
-            }
-        })
-    }
-
-    fn create_buffer_subscription(
-        active_buffer: &Entity<MultiBuffer>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Subscription {
-        cx.subscribe_in(
-            active_buffer,
-            window,
-            |this: &mut SvgPreviewView, buffer, event: &MultiBufferEvent, window, cx| {
-                let potential_path_change = event == &MultiBufferEvent::FileHandleChanged;
-                if event == &MultiBufferEvent::Saved || potential_path_change {
-                    // Remove cached image to force reload
-                    if let Some(svg_path) = &this.svg_path {
-                        let resource = Resource::Path(svg_path.clone().into());
-                        this.image_cache.update(cx, |cache, cx| {
-                            cache.remove(&resource, window, cx);
-                        });
-                    }
-
-                    if potential_path_change {
-                        this.svg_path = Self::get_svg_path(buffer, cx);
-                    }
-                    cx.notify();
-                }
-            },
-        )
-    }
-
-    pub fn is_svg_file(editor: &Entity<Editor>, cx: &App) -> bool {
-        let buffer = editor.read(cx).buffer().read(cx);
-        if let Some(buffer) = buffer.as_singleton()
-            && let Some(file) = buffer.read(cx).file()
-        {
-            return file
-                .path()
-                .extension()
-                .map(|ext| ext.eq_ignore_ascii_case("svg"))
-                .unwrap_or(false);
-        }
-        false
-    }
-
-    fn get_svg_path(buffer: &Entity<MultiBuffer>, cx: &App) -> Option<PathBuf> {
-        let buffer = buffer.read(cx).as_singleton()?;
-        let file = buffer.read(cx).file()?;
-        let local_file = file.as_local()?;
-        Some(local_file.abs_path(cx))
-    }
 }
 
 impl Render for SvgPreviewView {
@@ -265,20 +294,19 @@ impl Render for SvgPreviewView {
             .flex()
             .justify_center()
             .items_center()
-            .child(if let Some(svg_path) = &self.svg_path {
-                img(ImageSource::from(svg_path.clone()))
-                    .image_cache(&self.image_cache)
-                    .max_w_full()
-                    .max_h_full()
-                    .with_fallback(|| {
-                        div()
+            .map(|this| match self.current_svg.clone() {
+                Some(Ok(image)) => {
+                    this.child(img(image).max_w_full().max_h_full().with_fallback(|| {
+                        h_flex()
                             .p_4()
-                            .child("Failed to load SVG file")
+                            .gap_2()
+                            .child(Icon::new(IconName::Warning))
+                            .child("Failed to load SVG image")
                             .into_any_element()
-                    })
-                    .into_any_element()
-            } else {
-                div().p_4().child("No SVG file selected").into_any_element()
+                    }))
+                }
+                Some(Err(e)) => this.child(div().p_4().child(e).into_any_element()),
+                None => this.child(div().p_4().child("No SVG file selected")),
             })
     }
 }
@@ -295,20 +323,19 @@ impl Item for SvgPreviewView {
     type Event = ();
 
     fn tab_icon(&self, _window: &Window, cx: &App) -> Option<Icon> {
-        // Use the same icon as SVG files in the file tree
-        self.svg_path
+        self.buffer
             .as_ref()
-            .and_then(|svg_path| FileIcons::get_icon(svg_path, cx))
+            .and_then(|buffer| buffer.read(cx).file())
+            .and_then(|file| FileIcons::get_icon(file.path().as_std_path(), cx))
             .map(Icon::from_path)
             .or_else(|| Some(Icon::new(IconName::Image)))
     }
 
-    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
-        self.svg_path
+    fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
+        self.buffer
             .as_ref()
-            .and_then(|svg_path| svg_path.file_name())
-            .map(|name| name.to_string_lossy())
-            .map(|name| format!("Preview {}", name).into())
+            .and_then(|svg_path| svg_path.read(cx).file())
+            .map(|name| format!("Preview {}", name.file_name(cx)).into())
             .unwrap_or_else(|| "SVG Preview".into())
     }
 

crates/zlog/src/filter.rs 🔗

@@ -41,6 +41,9 @@ const DEFAULT_FILTERS: &[(&str, log::LevelFilter)] = &[
     ("blade_graphics", log::LevelFilter::Warn),
     #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))]
     ("naga::back::spv::writer", log::LevelFilter::Warn),
+    // usvg prints a lot of warnings on rendering an SVG with partial errors, which
+    // can happen a lot with the SVG preview
+    ("usvg::parser::style", log::LevelFilter::Error),
 ];
 
 pub fn init_env_filter(filter: env_config::EnvFilter) {