use crate::{ worktree_store::{WorktreeStore, WorktreeStoreEvent}, Project, ProjectEntryId, ProjectItem, ProjectPath,};use anyhow::{Context as _, Result};use collections::{hash_map, HashMap, HashSet};use futures::{channel::oneshot, StreamExt};use gpui::{ hash, prelude::*, AppContext, EventEmitter, Img, Model, ModelContext, Subscription, Task, WeakModel,};use language::{DiskState, File};use rpc::{AnyProtoClient, ErrorExt as _};use std::ffi::OsStr;use std::num::NonZeroU64;use std::path::Path;use std::sync::Arc;use util::ResultExt;use worktree::{LoadedBinaryFile, PathChange, Worktree}; #[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]pub struct ImageId(NonZeroU64); impl std::fmt::Display for ImageId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }} impl From<NonZeroU64> for ImageId { fn from(id: NonZeroU64) -> Self { ImageId(id) }} pub enum ImageItemEvent { ReloadNeeded, Reloaded, FileHandleChanged,} impl EventEmitter<ImageItemEvent> for ImageItem {} pub enum ImageStoreEvent { ImageAdded(Model<ImageItem>),} impl EventEmitter<ImageStoreEvent> for ImageStore {} pub struct ImageItem { pub id: ImageId, pub file: Arc<dyn File>, pub image: Arc<gpui::Image>, reload_task: Option<Task<()>>,} impl ImageItem { pub fn project_path(&self, cx: &AppContext) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), path: self.file.path().clone(), } } pub fn path(&self) -> &Arc<Path> { self.file.path() } fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) { let mut file_changed = false; let old_file = self.file.as_ref(); if new_file.path() != old_file.path() { file_changed = true; } let old_state = old_file.disk_state(); let new_state = new_file.disk_state(); if old_state != new_state { file_changed = true; if matches!(new_state, DiskState::Present { .. }) { cx.emit(ImageItemEvent::ReloadNeeded) } } self.file = new_file; if file_changed { cx.emit(ImageItemEvent::FileHandleChanged); cx.notify(); } } fn reload(&mut self, cx: &mut ModelContext<Self>) -> Option<oneshot::Receiver<()>> { let local_file = self.file.as_local()?; let (tx, rx) = futures::channel::oneshot::channel(); let content = local_file.load_bytes(cx); self.reload_task = Some(cx.spawn(|this, mut cx| async move { if let Some(image) = content .await .context("Failed to load image content") .and_then(create_gpui_image) .log_err() { this.update(&mut cx, |this, cx| { this.image = image; cx.emit(ImageItemEvent::Reloaded); }) .log_err(); } _ = tx.send(()); })); Some(rx) }} impl ProjectItem for ImageItem { fn try_open( project: &Model<Project>, path: &ProjectPath, cx: &mut AppContext, ) -> Option<Task<gpui::Result<Model<Self>>>> { let path = path.clone(); let project = project.clone(); let worktree_abs_path = project .read(cx) .worktree_for_id(path.worktree_id, cx)? .read(cx) .abs_path(); // Resolve the file extension from either the worktree path (if it's a single file) // or from the project path's subpath. let ext = worktree_abs_path .extension() .or_else(|| path.path.extension()) .and_then(OsStr::to_str) .map(str::to_lowercase) .unwrap_or_default(); let ext = ext.as_str(); // Only open the item if it's a binary image (no SVGs, etc.) // Since we do not have a way to toggle to an editor if Img::extensions().contains(&ext) && !ext.contains("svg") { Some(cx.spawn(|mut cx| async move { project .update(&mut cx, |project, cx| project.open_image(path, cx))? .await })) } else { None } } fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> { worktree::File::from_dyn(Some(&self.file))?.entry_id } fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { Some(self.project_path(cx).clone()) } fn is_dirty(&self) -> bool { false }} trait ImageStoreImpl { fn open_image( &self, path: Arc<Path>, worktree: Model<Worktree>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<Model<ImageItem>>>; fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>>; fn as_local(&self) -> Option<Model<LocalImageStore>>;} struct RemoteImageStore {} struct LocalImageStore { local_image_ids_by_path: HashMap<ProjectPath, ImageId>, local_image_ids_by_entry_id: HashMap<ProjectEntryId, ImageId>, image_store: WeakModel<ImageStore>, _subscription: Subscription,} pub struct ImageStore { state: Box<dyn ImageStoreImpl>, opened_images: HashMap<ImageId, WeakModel<ImageItem>>, worktree_store: Model<WorktreeStore>, #[allow(clippy::type_complexity)] loading_images_by_path: HashMap< ProjectPath, postage::watch::Receiver<Option<Result<Model<ImageItem>, Arc<anyhow::Error>>>>, >,} impl ImageStore { pub fn local(worktree_store: Model<WorktreeStore>, cx: &mut ModelContext<Self>) -> Self { let this = cx.weak_model(); Self { state: Box::new(cx.new_model(|cx| { let subscription = cx.subscribe( &worktree_store, |this: &mut LocalImageStore, _, event, cx| { if let WorktreeStoreEvent::WorktreeAdded(worktree) = event { this.subscribe_to_worktree(worktree, cx); } }, ); LocalImageStore { local_image_ids_by_path: Default::default(), local_image_ids_by_entry_id: Default::default(), image_store: this, _subscription: subscription, } })), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn remote( worktree_store: Model<WorktreeStore>, _upstream_client: AnyProtoClient, _remote_id: u64, cx: &mut ModelContext<Self>, ) -> Self { Self { state: Box::new(cx.new_model(|_| RemoteImageStore {})), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn images(&self) -> impl '_ + Iterator<Item = Model<ImageItem>> { self.opened_images .values() .filter_map(|image| image.upgrade()) } pub fn get(&self, image_id: ImageId) -> Option<Model<ImageItem>> { self.opened_images .get(&image_id) .and_then(|image| image.upgrade()) } pub fn get_by_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Model<ImageItem>> { self.images() .find(|image| &image.read(cx).project_path(cx) == path) } pub fn open_image( &mut self, project_path: ProjectPath, cx: &mut ModelContext<Self>, ) -> Task<Result<Model<ImageItem>>> { let existing_image = self.get_by_path(&project_path, cx); if let Some(existing_image) = existing_image { return Task::ready(Ok(existing_image)); } let Some(worktree) = self .worktree_store .read(cx) .worktree_for_id(project_path.worktree_id, cx) else { return Task::ready(Err(anyhow::anyhow!("no such worktree"))); }; let loading_watch = match self.loading_images_by_path.entry(project_path.clone()) { // If the given path is already being loaded, then wait for that existing // task to complete and return the same image. hash_map::Entry::Occupied(e) => e.get().clone(), // Otherwise, record the fact that this path is now being loaded. hash_map::Entry::Vacant(entry) => { let (mut tx, rx) = postage::watch::channel(); entry.insert(rx.clone()); let project_path = project_path.clone(); let load_image = self .state .open_image(project_path.path.clone(), worktree, cx); cx.spawn(move |this, mut cx| async move { let load_result = load_image.await; *tx.borrow_mut() = Some(this.update(&mut cx, |this, _cx| { // Record the fact that the image is no longer loading. this.loading_images_by_path.remove(&project_path); let image = load_result.map_err(Arc::new)?; Ok(image) })?); anyhow::Ok(()) }) .detach(); rx } }; cx.background_executor().spawn(async move { Self::wait_for_loading_image(loading_watch) .await .map_err(|e| e.cloned()) }) } pub async fn wait_for_loading_image( mut receiver: postage::watch::Receiver< Option<Result<Model<ImageItem>, Arc<anyhow::Error>>>, >, ) -> Result<Model<ImageItem>, Arc<anyhow::Error>> { loop { if let Some(result) = receiver.borrow().as_ref() { match result { Ok(image) => return Ok(image.to_owned()), Err(e) => return Err(e.to_owned()), } } receiver.next().await; } } pub fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>> { if images.is_empty() { return Task::ready(Ok(())); } self.state.reload_images(images, cx) } fn add_image( &mut self, image: Model<ImageItem>, cx: &mut ModelContext<ImageStore>, ) -> Result<()> { let image_id = image.read(cx).id; self.opened_images.insert(image_id, image.downgrade()); cx.subscribe(&image, Self::on_image_event).detach(); cx.emit(ImageStoreEvent::ImageAdded(image)); Ok(()) } fn on_image_event( &mut self, image: Model<ImageItem>, event: &ImageItemEvent, cx: &mut ModelContext<Self>, ) { match event { ImageItemEvent::FileHandleChanged => { if let Some(local) = self.state.as_local() { local.update(cx, |local, cx| { local.image_changed_file(image, cx); }) } } _ => {} } }} impl ImageStoreImpl for Model<LocalImageStore> { fn open_image( &self, path: Arc<Path>, worktree: Model<Worktree>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<Model<ImageItem>>> { let this = self.clone(); let load_file = worktree.update(cx, |worktree, cx| { worktree.load_binary_file(path.as_ref(), cx) }); cx.spawn(move |image_store, mut cx| async move { let LoadedBinaryFile { file, content } = load_file.await?; let image = create_gpui_image(content)?; let model = cx.new_model(|cx| ImageItem { id: cx.entity_id().as_non_zero_u64().into(), file: file.clone(), image, reload_task: None, })?; let image_id = cx.read_model(&model, |model, _| model.id)?; this.update(&mut cx, |this, cx| { image_store.update(cx, |image_store, cx| { image_store.add_image(model.clone(), cx) })??; this.local_image_ids_by_path.insert( ProjectPath { worktree_id: file.worktree_id(cx), path: file.path.clone(), }, image_id, ); if let Some(entry_id) = file.entry_id { this.local_image_ids_by_entry_id.insert(entry_id, image_id); } anyhow::Ok(()) })??; Ok(model) }) } fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>> { cx.spawn(move |_, mut cx| async move { for image in images { if let Some(rec) = image.update(&mut cx, |image, cx| image.reload(cx))? { rec.await? } } Ok(()) }) } fn as_local(&self) -> Option<Model<LocalImageStore>> { Some(self.clone()) }} impl LocalImageStore { fn subscribe_to_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) { cx.subscribe(worktree, |this, worktree, event, cx| { if worktree.read(cx).is_local() { match event { worktree::Event::UpdatedEntries(changes) => { this.local_worktree_entries_changed(&worktree, changes, cx); } _ => {} } } }) .detach(); } fn local_worktree_entries_changed( &mut self, worktree_handle: &Model<Worktree>, changes: &[(Arc<Path>, ProjectEntryId, PathChange)], cx: &mut ModelContext<Self>, ) { let snapshot = worktree_handle.read(cx).snapshot(); for (path, entry_id, _) in changes { self.local_worktree_entry_changed(*entry_id, path, worktree_handle, &snapshot, cx); } } fn local_worktree_entry_changed( &mut self, entry_id: ProjectEntryId, path: &Arc<Path>, worktree: &Model<worktree::Worktree>, snapshot: &worktree::Snapshot, cx: &mut ModelContext<Self>, ) -> Option<()> { let project_path = ProjectPath { worktree_id: snapshot.id(), path: path.clone(), }; let image_id = match self.local_image_ids_by_entry_id.get(&entry_id) { Some(&image_id) => image_id, None => self.local_image_ids_by_path.get(&project_path).copied()?, }; let image = self .image_store .update(cx, |image_store, _| { if let Some(image) = image_store.get(image_id) { Some(image) } else { image_store.opened_images.remove(&image_id); None } })Lines 1–500 of 713. Load more
use crate::{ worktree_store::{WorktreeStore, WorktreeStoreEvent}, Project, ProjectEntryId, ProjectItem, ProjectPath, }; use anyhow::{Context as _, Result}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::oneshot, StreamExt}; use gpui::{ hash, prelude::*, AppContext, EventEmitter, Img, Model, ModelContext, Subscription, Task, WeakModel, }; use language::{DiskState, File}; use rpc::{AnyProtoClient, ErrorExt as _}; use std::ffi::OsStr; use std::num::NonZeroU64; use std::path::Path; use std::sync::Arc; use util::ResultExt; use worktree::{LoadedBinaryFile, PathChange, Worktree}; #[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)] pub struct ImageId(NonZeroU64); impl std::fmt::Display for ImageId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl From<NonZeroU64> for ImageId { fn from(id: NonZeroU64) -> Self { ImageId(id) } } pub enum ImageItemEvent { ReloadNeeded, Reloaded, FileHandleChanged, } impl EventEmitter<ImageItemEvent> for ImageItem {} pub enum ImageStoreEvent { ImageAdded(Model<ImageItem>), } impl EventEmitter<ImageStoreEvent> for ImageStore {} pub struct ImageItem { pub id: ImageId, pub file: Arc<dyn File>, pub image: Arc<gpui::Image>, reload_task: Option<Task<()>>, } impl ImageItem { pub fn project_path(&self, cx: &AppContext) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), path: self.file.path().clone(), } } pub fn path(&self) -> &Arc<Path> { self.file.path() } fn file_updated(&mut self, new_file: Arc<dyn File>, cx: &mut ModelContext<Self>) { let mut file_changed = false; let old_file = self.file.as_ref(); if new_file.path() != old_file.path() { file_changed = true; } let old_state = old_file.disk_state(); let new_state = new_file.disk_state(); if old_state != new_state { file_changed = true; if matches!(new_state, DiskState::Present { .. }) { cx.emit(ImageItemEvent::ReloadNeeded) } } self.file = new_file; if file_changed { cx.emit(ImageItemEvent::FileHandleChanged); cx.notify(); } } fn reload(&mut self, cx: &mut ModelContext<Self>) -> Option<oneshot::Receiver<()>> { let local_file = self.file.as_local()?; let (tx, rx) = futures::channel::oneshot::channel(); let content = local_file.load_bytes(cx); self.reload_task = Some(cx.spawn(|this, mut cx| async move { if let Some(image) = content .await .context("Failed to load image content") .and_then(create_gpui_image) .log_err() { this.update(&mut cx, |this, cx| { this.image = image; cx.emit(ImageItemEvent::Reloaded); }) .log_err(); } _ = tx.send(()); })); Some(rx) } } impl ProjectItem for ImageItem { fn try_open( project: &Model<Project>, path: &ProjectPath, cx: &mut AppContext, ) -> Option<Task<gpui::Result<Model<Self>>>> { let path = path.clone(); let project = project.clone(); let worktree_abs_path = project .read(cx) .worktree_for_id(path.worktree_id, cx)? .read(cx) .abs_path(); // Resolve the file extension from either the worktree path (if it's a single file) // or from the project path's subpath. let ext = worktree_abs_path .extension() .or_else(|| path.path.extension()) .and_then(OsStr::to_str) .map(str::to_lowercase) .unwrap_or_default(); let ext = ext.as_str(); // Only open the item if it's a binary image (no SVGs, etc.) // Since we do not have a way to toggle to an editor if Img::extensions().contains(&ext) && !ext.contains("svg") { Some(cx.spawn(|mut cx| async move { project .update(&mut cx, |project, cx| project.open_image(path, cx))? .await })) } else { None } } fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> { worktree::File::from_dyn(Some(&self.file))?.entry_id } fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> { Some(self.project_path(cx).clone()) } fn is_dirty(&self) -> bool { false } } trait ImageStoreImpl { fn open_image( &self, path: Arc<Path>, worktree: Model<Worktree>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<Model<ImageItem>>>; fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>>; fn as_local(&self) -> Option<Model<LocalImageStore>>; } struct RemoteImageStore {} struct LocalImageStore { local_image_ids_by_path: HashMap<ProjectPath, ImageId>, local_image_ids_by_entry_id: HashMap<ProjectEntryId, ImageId>, image_store: WeakModel<ImageStore>, _subscription: Subscription, } pub struct ImageStore { state: Box<dyn ImageStoreImpl>, opened_images: HashMap<ImageId, WeakModel<ImageItem>>, worktree_store: Model<WorktreeStore>, #[allow(clippy::type_complexity)] loading_images_by_path: HashMap< ProjectPath, postage::watch::Receiver<Option<Result<Model<ImageItem>, Arc<anyhow::Error>>>>, >, } impl ImageStore { pub fn local(worktree_store: Model<WorktreeStore>, cx: &mut ModelContext<Self>) -> Self { let this = cx.weak_model(); Self { state: Box::new(cx.new_model(|cx| { let subscription = cx.subscribe( &worktree_store, |this: &mut LocalImageStore, _, event, cx| { if let WorktreeStoreEvent::WorktreeAdded(worktree) = event { this.subscribe_to_worktree(worktree, cx); } }, ); LocalImageStore { local_image_ids_by_path: Default::default(), local_image_ids_by_entry_id: Default::default(), image_store: this, _subscription: subscription, } })), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn remote( worktree_store: Model<WorktreeStore>, _upstream_client: AnyProtoClient, _remote_id: u64, cx: &mut ModelContext<Self>, ) -> Self { Self { state: Box::new(cx.new_model(|_| RemoteImageStore {})), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn images(&self) -> impl '_ + Iterator<Item = Model<ImageItem>> { self.opened_images .values() .filter_map(|image| image.upgrade()) } pub fn get(&self, image_id: ImageId) -> Option<Model<ImageItem>> { self.opened_images .get(&image_id) .and_then(|image| image.upgrade()) } pub fn get_by_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Model<ImageItem>> { self.images() .find(|image| &image.read(cx).project_path(cx) == path) } pub fn open_image( &mut self, project_path: ProjectPath, cx: &mut ModelContext<Self>, ) -> Task<Result<Model<ImageItem>>> { let existing_image = self.get_by_path(&project_path, cx); if let Some(existing_image) = existing_image { return Task::ready(Ok(existing_image)); } let Some(worktree) = self .worktree_store .read(cx) .worktree_for_id(project_path.worktree_id, cx) else { return Task::ready(Err(anyhow::anyhow!("no such worktree"))); }; let loading_watch = match self.loading_images_by_path.entry(project_path.clone()) { // If the given path is already being loaded, then wait for that existing // task to complete and return the same image. hash_map::Entry::Occupied(e) => e.get().clone(), // Otherwise, record the fact that this path is now being loaded. hash_map::Entry::Vacant(entry) => { let (mut tx, rx) = postage::watch::channel(); entry.insert(rx.clone()); let project_path = project_path.clone(); let load_image = self .state .open_image(project_path.path.clone(), worktree, cx); cx.spawn(move |this, mut cx| async move { let load_result = load_image.await; *tx.borrow_mut() = Some(this.update(&mut cx, |this, _cx| { // Record the fact that the image is no longer loading. this.loading_images_by_path.remove(&project_path); let image = load_result.map_err(Arc::new)?; Ok(image) })?); anyhow::Ok(()) }) .detach(); rx } }; cx.background_executor().spawn(async move { Self::wait_for_loading_image(loading_watch) .await .map_err(|e| e.cloned()) }) } pub async fn wait_for_loading_image( mut receiver: postage::watch::Receiver< Option<Result<Model<ImageItem>, Arc<anyhow::Error>>>, >, ) -> Result<Model<ImageItem>, Arc<anyhow::Error>> { loop { if let Some(result) = receiver.borrow().as_ref() { match result { Ok(image) => return Ok(image.to_owned()), Err(e) => return Err(e.to_owned()), } } receiver.next().await; } } pub fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>> { if images.is_empty() { return Task::ready(Ok(())); } self.state.reload_images(images, cx) } fn add_image( &mut self, image: Model<ImageItem>, cx: &mut ModelContext<ImageStore>, ) -> Result<()> { let image_id = image.read(cx).id; self.opened_images.insert(image_id, image.downgrade()); cx.subscribe(&image, Self::on_image_event).detach(); cx.emit(ImageStoreEvent::ImageAdded(image)); Ok(()) } fn on_image_event( &mut self, image: Model<ImageItem>, event: &ImageItemEvent, cx: &mut ModelContext<Self>, ) { match event { ImageItemEvent::FileHandleChanged => { if let Some(local) = self.state.as_local() { local.update(cx, |local, cx| { local.image_changed_file(image, cx); }) } } _ => {} } } } impl ImageStoreImpl for Model<LocalImageStore> { fn open_image( &self, path: Arc<Path>, worktree: Model<Worktree>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<Model<ImageItem>>> { let this = self.clone(); let load_file = worktree.update(cx, |worktree, cx| { worktree.load_binary_file(path.as_ref(), cx) }); cx.spawn(move |image_store, mut cx| async move { let LoadedBinaryFile { file, content } = load_file.await?; let image = create_gpui_image(content)?; let model = cx.new_model(|cx| ImageItem { id: cx.entity_id().as_non_zero_u64().into(), file: file.clone(), image, reload_task: None, })?; let image_id = cx.read_model(&model, |model, _| model.id)?; this.update(&mut cx, |this, cx| { image_store.update(cx, |image_store, cx| { image_store.add_image(model.clone(), cx) })??; this.local_image_ids_by_path.insert( ProjectPath { worktree_id: file.worktree_id(cx), path: file.path.clone(), }, image_id, ); if let Some(entry_id) = file.entry_id { this.local_image_ids_by_entry_id.insert(entry_id, image_id); } anyhow::Ok(()) })??; Ok(model) }) } fn reload_images( &self, images: HashSet<Model<ImageItem>>, cx: &mut ModelContext<ImageStore>, ) -> Task<Result<()>> { cx.spawn(move |_, mut cx| async move { for image in images { if let Some(rec) = image.update(&mut cx, |image, cx| image.reload(cx))? { rec.await? } } Ok(()) }) } fn as_local(&self) -> Option<Model<LocalImageStore>> { Some(self.clone()) } } impl LocalImageStore { fn subscribe_to_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) { cx.subscribe(worktree, |this, worktree, event, cx| { if worktree.read(cx).is_local() { match event { worktree::Event::UpdatedEntries(changes) => { this.local_worktree_entries_changed(&worktree, changes, cx); } _ => {} } } }) .detach(); } fn local_worktree_entries_changed( &mut self, worktree_handle: &Model<Worktree>, changes: &[(Arc<Path>, ProjectEntryId, PathChange)], cx: &mut ModelContext<Self>, ) { let snapshot = worktree_handle.read(cx).snapshot(); for (path, entry_id, _) in changes { self.local_worktree_entry_changed(*entry_id, path, worktree_handle, &snapshot, cx); } } fn local_worktree_entry_changed( &mut self, entry_id: ProjectEntryId, path: &Arc<Path>, worktree: &Model<worktree::Worktree>, snapshot: &worktree::Snapshot, cx: &mut ModelContext<Self>, ) -> Option<()> { let project_path = ProjectPath { worktree_id: snapshot.id(), path: path.clone(), }; let image_id = match self.local_image_ids_by_entry_id.get(&entry_id) { Some(&image_id) => image_id, None => self.local_image_ids_by_path.get(&project_path).copied()?, }; let image = self .image_store .update(cx, |image_store, _| { if let Some(image) = image_store.get(image_id) { Some(image) } else { image_store.opened_images.remove(&image_id); None } })