From 18c6d9d394f99fa0e2e95dff385bff117a1ef5cb Mon Sep 17 00:00:00 2001 From: 0xshadow Date: Wed, 10 Sep 2025 01:06:35 +0530 Subject: [PATCH] Fix SVG preview not refreshing on external file changes (#37316) Closes #37208 ## Release Notes: - Fixed: SVG preview now refreshes automatically when files are modified by external programs ## Summary Previously, SVG preview would only refresh when files were saved within the Zed editor, but not when modified by external programs (like scripts, other editors, etc.) ## What Changed The SVG preview now subscribes to file system events through the worktree system. When an external program modifies an SVG file, the worktree detects the change and notifies the preview. The preview then clears its cache and refreshes to show the updated content. ## Before the fix https://github.com/user-attachments/assets/e7f9a2b2-50f9-4b43-95e9-93a0720749f5 ## After the fix https://github.com/user-attachments/assets/b23511e3-8e59-45a1-b29b-d5105d32bd2c AI Usage: Used Cursor for code generation --- Cargo.lock | 2 + crates/svg_preview/Cargo.toml | 2 + crates/svg_preview/src/svg_preview_view.rs | 79 +++++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6dd0ba845149f621ed904504743161018baabf9d..8aaea1a1f81ff18d367cc1448e5c8b146536f8bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15870,9 +15870,11 @@ dependencies = [ "editor", "file_icons", "gpui", + "project", "ui", "workspace", "workspace-hack", + "worktree", ] [[package]] diff --git a/crates/svg_preview/Cargo.toml b/crates/svg_preview/Cargo.toml index b783d7192cce888218617b46e935f8c689b70a56..1054aee23fded31fda6e694e9ea6569d908f0f73 100644 --- a/crates/svg_preview/Cargo.toml +++ b/crates/svg_preview/Cargo.toml @@ -15,6 +15,8 @@ path = "src/svg_preview.rs" editor.workspace = true file_icons.workspace = true gpui.workspace = true +project.workspace = true ui.workspace = true workspace.workspace = true +worktree.workspace = true workspace-hack.workspace = true diff --git a/crates/svg_preview/src/svg_preview_view.rs b/crates/svg_preview/src/svg_preview_view.rs index 12dd97f0c8f3fbec1bbbcaabadc4118c3a4e0229..d021f94856f5119ddb91432b5c2d5ab44ce6a5b9 100644 --- a/crates/svg_preview/src/svg_preview_view.rs +++ b/crates/svg_preview/src/svg_preview_view.rs @@ -7,18 +7,23 @@ use gpui::{ ParentElement, Render, Resource, RetainAllImageCache, Styled, Subscription, WeakEntity, Window, div, img, }; +use project::ProjectPath; use ui::prelude::*; use workspace::item::Item; use workspace::{Pane, Workspace}; +use worktree::Event as WorktreeEvent; use crate::{OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide}; pub struct SvgPreviewView { focus_handle: FocusHandle, svg_path: Option, + project_path: Option, image_cache: Entity, + workspace_handle: WeakEntity, _editor_subscription: Subscription, _workspace_subscription: Option, + _project_subscription: Option, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -151,6 +156,7 @@ impl SvgPreviewView { ) -> Entity { cx.new(|cx| { let svg_path = Self::get_svg_path(&active_editor, cx); + let project_path = Self::get_project_path(&active_editor, cx); let image_cache = RetainAllImageCache::new(cx); let subscription = cx.subscribe_in( @@ -201,16 +207,66 @@ impl SvgPreviewView { None }; - Self { + // We'll set up the project subscription after the entity is created + let project_subscription = None; + + let view = Self { focus_handle: cx.focus_handle(), svg_path, + project_path, image_cache, + workspace_handle, _editor_subscription: subscription, _workspace_subscription: workspace_subscription, - } + _project_subscription: project_subscription, + }; + + view }) } + fn setup_project_subscription(&mut self, window: &mut Window, cx: &mut Context) { + if let (Some(workspace), Some(project_path)) = + (self.workspace_handle.upgrade(), &self.project_path) + { + let project = workspace.read(cx).project().clone(); + let worktree_id = project_path.worktree_id; + let worktree = { + let project_read = project.read(cx); + project_read + .worktrees(cx) + .find(|worktree| worktree.read(cx).id() == worktree_id) + }; + + if let Some(worktree) = worktree { + self._project_subscription = Some(cx.subscribe_in( + &worktree, + window, + |this: &mut SvgPreviewView, _worktree, event: &WorktreeEvent, window, cx| { + if let WorktreeEvent::UpdatedEntries(changes) = event { + if let Some(project_path) = &this.project_path { + // Check if our SVG file was modified + for (path, _entry_id, _change) in changes.iter() { + if path.as_ref() == project_path.path.as_ref() { + // File was modified externally, clear cache and refresh + 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); + }); + } + cx.notify(); + break; + } + } + } + } + }, + )); + } + } + } + pub fn is_svg_file(editor: &Entity, cx: &C) -> bool where C: std::borrow::Borrow, @@ -240,10 +296,27 @@ impl SvgPreviewView { let local_file = file.as_local()?; Some(local_file.abs_path(app)) } + + fn get_project_path(editor: &Entity, cx: &C) -> Option + where + C: std::borrow::Borrow, + { + let app = cx.borrow(); + let buffer = editor.read(app).buffer().read(app).as_singleton()?; + let file = buffer.read(app).file()?; + Some(ProjectPath { + worktree_id: file.worktree_id(app), + path: file.path().clone(), + }) + } } impl Render for SvgPreviewView { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + // Set up project subscription on first render if not already done + if self._project_subscription.is_none() { + self.setup_project_subscription(window, cx); + } v_flex() .id("SvgPreview") .key_context("SvgPreview")