From 2143c59fba8a2b2a93cbff1e7f8835b534bc8248 Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Fri, 12 Sep 2025 20:08:21 +0200 Subject: [PATCH] svg_preview: Ensure preview properly updates in follow mode (#38081) This fixes an issue where we would not update neither the path nor the editor that was listened to during follow mode, which in turn would cause the preview to become stale. Fix here is to update the subscription whenever the active item changes and also update the associated path accordingly. Release Notes: - Fixed an issue where the SVG preview would not update when following the active editor. --- Cargo.lock | 3 +- crates/svg_preview/Cargo.toml | 3 +- crates/svg_preview/src/svg_preview_view.rs | 168 +++++++-------------- 3 files changed, 56 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 485ff81602d6b47f929c6eea9edeefb252a82223..987114a61bda04fb2a606d184a6b9c4ae1a85a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15922,11 +15922,10 @@ dependencies = [ "editor", "file_icons", "gpui", - "project", + "multi_buffer", "ui", "workspace", "workspace-hack", - "worktree", ] [[package]] diff --git a/crates/svg_preview/Cargo.toml b/crates/svg_preview/Cargo.toml index 1054aee23fded31fda6e694e9ea6569d908f0f73..63e9e41bbe4d40945f854319a0adc88388f485cd 100644 --- a/crates/svg_preview/Cargo.toml +++ b/crates/svg_preview/Cargo.toml @@ -15,8 +15,7 @@ path = "src/svg_preview.rs" editor.workspace = true file_icons.workspace = true gpui.workspace = true -project.workspace = true +multi_buffer.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 d021f94856f5119ddb91432b5c2d5ab44ce6a5b9..dfc346704e58c6f82c2dfbffc7822ad37156131f 100644 --- a/crates/svg_preview/src/svg_preview_view.rs +++ b/crates/svg_preview/src/svg_preview_view.rs @@ -1,29 +1,25 @@ use std::path::PathBuf; -use editor::{Editor, EditorEvent}; +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, }; -use project::ProjectPath; +use multi_buffer::{Event as MultiBufferEvent, MultiBuffer}; 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, + _buffer_subscription: Subscription, _workspace_subscription: Option, - _project_subscription: Option, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -114,7 +110,7 @@ impl SvgPreviewView { editor: &Entity, cx: &App, ) -> Option { - let editor_path = Self::get_svg_path(editor, cx); + let editor_path = Self::get_svg_path(editor.read(cx).buffer(), cx); pane.items_of_type::() .find(|view| { let view_read = view.read(cx); @@ -155,26 +151,10 @@ impl SvgPreviewView { cx: &mut Context, ) -> 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( - &active_editor, - window, - |this: &mut SvgPreviewView, _editor, event: &EditorEvent, window, cx| { - if event == &EditorEvent::Saved { - // 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); - }); - } - cx.notify(); - } - }, - ); + 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 { @@ -185,17 +165,24 @@ impl SvgPreviewView { |this: &mut SvgPreviewView, workspace, event: &workspace::Event, - _window, + 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_entity) = active_item.downcast::() - && Self::is_svg_file(&editor_entity, cx) + && let Some(editor) = active_item.downcast::() + && Self::is_svg_file(&editor, cx) { - let new_path = Self::get_svg_path(&editor_entity, 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(); } } @@ -207,74 +194,48 @@ impl SvgPreviewView { None }; - // We'll set up the project subscription after the entity is created - let project_subscription = None; - - let view = Self { + Self { focus_handle: cx.focus_handle(), svg_path, - project_path, image_cache, - workspace_handle, - _editor_subscription: subscription, + _buffer_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) - }; + fn create_buffer_subscription( + active_buffer: &Entity, + window: &mut Window, + cx: &mut Context, + ) -> 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 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; - } - } - } - } - }, - )); - } - } + if potential_path_change { + this.svg_path = Self::get_svg_path(buffer, cx); + } + cx.notify(); + } + }, + ) } - pub fn is_svg_file(editor: &Entity, cx: &C) -> bool - where - C: std::borrow::Borrow, - { - let app = cx.borrow(); - let buffer = editor.read(app).buffer().read(app); + pub fn is_svg_file(editor: &Entity, cx: &App) -> bool { + let buffer = editor.read(cx).buffer().read(cx); if let Some(buffer) = buffer.as_singleton() - && let Some(file) = buffer.read(app).file() + && let Some(file) = buffer.read(cx).file() { return file .path() @@ -286,37 +247,16 @@ impl SvgPreviewView { false } - fn get_svg_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()?; + fn get_svg_path(buffer: &Entity, cx: &App) -> Option { + let buffer = buffer.read(cx).as_singleton()?; + let file = buffer.read(cx).file()?; 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(), - }) + Some(local_file.abs_path(cx)) } } impl Render for SvgPreviewView { - 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); - } + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .id("SvgPreview") .key_context("SvgPreview")