use crate::{ Project, ProjectEntryId, ProjectItem, ProjectPath, worktree_store::{WorktreeStore, WorktreeStoreEvent},};use anyhow::{Context as _, Result};use collections::{HashMap, HashSet, hash_map};use futures::{StreamExt, channel::oneshot};use gpui::{ App, AsyncApp, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity, prelude::*,};pub use image::ImageFormat;use image::{ExtendedColorType, GenericImageView, ImageReader};use language::{DiskState, File};use rpc::{AnyProtoClient, ErrorExt as _, TypedEnvelope, proto};use std::num::NonZeroU64;use std::path::PathBuf;use std::sync::Arc;use util::{ResultExt, rel_path::RelPath};use worktree::{LoadedBinaryFile, PathChange, Worktree, WorktreeId}; #[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]pub struct ImageId(NonZeroU64); impl ImageId { pub fn to_proto(&self) -> u64 { self.0.get() }} 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) }} #[derive(Debug)]pub enum ImageItemEvent { ReloadNeeded, Reloaded, FileHandleChanged, MetadataUpdated,} impl EventEmitter<ImageItemEvent> for ImageItem {} pub enum ImageStoreEvent { ImageAdded(Entity<ImageItem>),} impl EventEmitter<ImageStoreEvent> for ImageStore {} #[derive(Debug, Clone, Copy)]pub struct ImageMetadata { pub width: u32, pub height: u32, pub file_size: u64, pub colors: Option<ImageColorInfo>, pub format: ImageFormat,} #[derive(Debug, Clone, Copy)]pub struct ImageColorInfo { pub channels: u8, pub bits_per_channel: u8,} impl ImageColorInfo { pub fn from_color_type(color_type: impl Into<ExtendedColorType>) -> Option<Self> { let (channels, bits_per_channel) = match color_type.into() { ExtendedColorType::L8 => (1, 8), ExtendedColorType::L16 => (1, 16), ExtendedColorType::La8 => (2, 8), ExtendedColorType::La16 => (2, 16), ExtendedColorType::Rgb8 => (3, 8), ExtendedColorType::Rgb16 => (3, 16), ExtendedColorType::Rgba8 => (4, 8), ExtendedColorType::Rgba16 => (4, 16), ExtendedColorType::A8 => (1, 8), ExtendedColorType::Bgr8 => (3, 8), ExtendedColorType::Bgra8 => (4, 8), ExtendedColorType::Cmyk8 => (4, 8), _ => return None, }; Some(Self { channels, bits_per_channel, }) } pub const fn bits_per_pixel(&self) -> u8 { self.channels * self.bits_per_channel }} pub struct ImageItem { pub id: ImageId, pub file: Arc<worktree::File>, pub image: Arc<gpui::Image>, reload_task: Option<Task<()>>, pub image_metadata: Option<ImageMetadata>,} impl ImageItem { pub fn compute_metadata_from_bytes(image_bytes: &[u8]) -> Result<ImageMetadata> { let image_format = image::guess_format(image_bytes)?; let mut image_reader = ImageReader::new(std::io::Cursor::new(image_bytes)); image_reader.set_format(image_format); let image = image_reader.decode()?; let (width, height) = image.dimensions(); Ok(ImageMetadata { width, height, file_size: image_bytes.len() as u64, format: image_format, colors: ImageColorInfo::from_color_type(image.color()), }) } pub async fn load_image_metadata( image: Entity<ImageItem>, project: Entity<Project>, cx: &mut AsyncApp, ) -> Result<ImageMetadata> { let (fs, image_path) = cx.update(|cx| { let fs = project.read(cx).fs().clone(); let image_path = image .read(cx) .abs_path(cx) .context("absolutizing image file path")?; anyhow::Ok((fs, image_path)) })?; let image_bytes = fs.load_bytes(&image_path).await?; Self::compute_metadata_from_bytes(&image_bytes) } pub fn project_path(&self, cx: &App) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), path: self.file.path().clone(), } } pub fn abs_path(&self, cx: &App) -> Option<PathBuf> { Some(self.file.as_local()?.abs_path(cx)) } fn file_updated(&mut self, new_file: Arc<worktree::File>, cx: &mut Context<Self>) { let mut file_changed = false; let old_file = &self.file; 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 Context<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(async move |this, cx| { if let Some(image) = content .await .context("Failed to load image content") .and_then(create_gpui_image) .log_err() { this.update(cx, |this, cx| { this.image = image; cx.emit(ImageItemEvent::Reloaded); }) .log_err(); } _ = tx.send(()); })); Some(rx) }} pub fn is_image_file(project: &Entity<Project>, path: &ProjectPath, cx: &App) -> bool { let ext = util::maybe!({ let worktree_abs_path = project .read(cx) .worktree_for_id(path.worktree_id, cx)? .read(cx) .abs_path(); path.path .extension() .or_else(|| worktree_abs_path.extension()?.to_str()) .map(str::to_lowercase) }); match ext { Some(ext) => Img::extensions().contains(&ext.as_str()) && !ext.contains("svg"), None => false, }} impl ProjectItem for ImageItem { fn try_open( project: &Entity<Project>, path: &ProjectPath, cx: &mut App, ) -> Option<Task<anyhow::Result<Entity<Self>>>> { if is_image_file(project, path, cx) { Some(cx.spawn({ let path = path.clone(); let project = project.clone(); async move |cx| { project .update(cx, |project, cx| project.open_image(path, cx)) .await } })) } else { None } } fn entry_id(&self, _: &App) -> Option<ProjectEntryId> { self.file.entry_id } fn project_path(&self, cx: &App) -> Option<ProjectPath> { Some(self.project_path(cx)) } fn is_dirty(&self) -> bool { false }} trait ImageStoreImpl { fn open_image( &self, path: Arc<RelPath>, worktree: Entity<Worktree>, cx: &mut Context<ImageStore>, ) -> Task<Result<Entity<ImageItem>>>; fn reload_images( &self, images: HashSet<Entity<ImageItem>>, cx: &mut Context<ImageStore>, ) -> Task<Result<()>>; fn as_local(&self) -> Option<Entity<LocalImageStore>>; fn as_remote(&self) -> Option<Entity<RemoteImageStore>>;} struct RemoteImageStore { upstream_client: AnyProtoClient, project_id: u64, loading_remote_images_by_id: HashMap<ImageId, LoadingRemoteImage>, remote_image_listeners: HashMap<ImageId, Vec<oneshot::Sender<anyhow::Result<Entity<ImageItem>>>>>, loaded_images: HashMap<ImageId, Entity<ImageItem>>,} struct LoadingRemoteImage { state: proto::ImageState, chunks: Vec<Vec<u8>>, received_size: u64,} struct LocalImageStore { local_image_ids_by_path: HashMap<ProjectPath, ImageId>, local_image_ids_by_entry_id: HashMap<ProjectEntryId, ImageId>, image_store: WeakEntity<ImageStore>, _subscription: Subscription,} pub struct ImageStore { state: Box<dyn ImageStoreImpl>, opened_images: HashMap<ImageId, WeakEntity<ImageItem>>, worktree_store: Entity<WorktreeStore>, #[allow(clippy::type_complexity)] loading_images_by_path: HashMap< ProjectPath, postage::watch::Receiver<Option<Result<Entity<ImageItem>, Arc<anyhow::Error>>>>, >,} impl ImageStore { pub fn local(worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>) -> Self { let this = cx.weak_entity(); Self { state: Box::new(cx.new(|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: Entity<WorktreeStore>, upstream_client: AnyProtoClient, project_id: u64, cx: &mut Context<Self>, ) -> Self { Self { state: Box::new(cx.new(|_| RemoteImageStore { upstream_client, project_id, loading_remote_images_by_id: Default::default(), remote_image_listeners: Default::default(), loaded_images: Default::default(), })), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn images(&self) -> impl '_ + Iterator<Item = Entity<ImageItem>> { self.opened_images .values() .filter_map(|image| image.upgrade()) } pub fn get(&self, image_id: ImageId) -> Option<Entity<ImageItem>> { self.opened_images .get(&image_id) .and_then(|image| image.upgrade()) } pub fn get_by_path(&self, path: &ProjectPath, cx: &App) -> Option<Entity<ImageItem>> { self.images() .find(|image| &image.read(cx).project_path(cx) == path) } pub fn open_image( &mut self, project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<Entity<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 load_image = self .state .open_image(project_path.path.clone(), worktree, cx); cx.spawn(async move |this, cx| { let load_result = load_image.await; *tx.borrow_mut() = Some(this.update(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_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<Entity<ImageItem>, Arc<anyhow::Error>>>, >, ) -> Result<Entity<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<Entity<ImageItem>>, cx: &mut Context<ImageStore>, ) -> Task<Result<()>> { if images.is_empty() { return Task::ready(Ok(())); } self.state.reload_images(images, cx) } fn add_image(&mut self, image: Entity<ImageItem>, cx: &mut Context<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: Entity<ImageItem>, event: &ImageItemEvent, cx: &mut Context<Self>, ) { if let ImageItemEvent::FileHandleChanged = event && let Some(local) = self.state.as_local() { local.update(cx, |local, cx| { local.image_changed_file(image, cx); }) } } pub fn handle_create_image_for_peer( &mut self, envelope: TypedEnvelope<proto::CreateImageForPeer>, cx: &mut Context<Self>, ) -> Result<()> { if let Some(remote) = self.state.as_remote() { let worktree_store = self.worktree_store.clone(); let image = remote.update(cx, |remote, cx| { remote.handle_create_image_for_peer(envelope, &worktree_store, cx) })?; if let Some(image) = image { remote.update(cx, |this, cx| { let image = image.clone(); let image_id = image.read(cx).id; this.loaded_images.insert(image_id, image) }); self.add_image(image, cx)?; } } Ok(())Lines 1–500 of 909. Load more
use crate::{ Project, ProjectEntryId, ProjectItem, ProjectPath, worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; use anyhow::{Context as _, Result}; use collections::{HashMap, HashSet, hash_map}; use futures::{StreamExt, channel::oneshot}; use gpui::{ App, AsyncApp, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity, prelude::*, }; pub use image::ImageFormat; use image::{ExtendedColorType, GenericImageView, ImageReader}; use language::{DiskState, File}; use rpc::{AnyProtoClient, ErrorExt as _, TypedEnvelope, proto}; use std::num::NonZeroU64; use std::path::PathBuf; use std::sync::Arc; use util::{ResultExt, rel_path::RelPath}; use worktree::{LoadedBinaryFile, PathChange, Worktree, WorktreeId}; #[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)] pub struct ImageId(NonZeroU64); impl ImageId { pub fn to_proto(&self) -> u64 { self.0.get() } } 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) } } #[derive(Debug)] pub enum ImageItemEvent { ReloadNeeded, Reloaded, FileHandleChanged, MetadataUpdated, } impl EventEmitter<ImageItemEvent> for ImageItem {} pub enum ImageStoreEvent { ImageAdded(Entity<ImageItem>), } impl EventEmitter<ImageStoreEvent> for ImageStore {} #[derive(Debug, Clone, Copy)] pub struct ImageMetadata { pub width: u32, pub height: u32, pub file_size: u64, pub colors: Option<ImageColorInfo>, pub format: ImageFormat, } #[derive(Debug, Clone, Copy)] pub struct ImageColorInfo { pub channels: u8, pub bits_per_channel: u8, } impl ImageColorInfo { pub fn from_color_type(color_type: impl Into<ExtendedColorType>) -> Option<Self> { let (channels, bits_per_channel) = match color_type.into() { ExtendedColorType::L8 => (1, 8), ExtendedColorType::L16 => (1, 16), ExtendedColorType::La8 => (2, 8), ExtendedColorType::La16 => (2, 16), ExtendedColorType::Rgb8 => (3, 8), ExtendedColorType::Rgb16 => (3, 16), ExtendedColorType::Rgba8 => (4, 8), ExtendedColorType::Rgba16 => (4, 16), ExtendedColorType::A8 => (1, 8), ExtendedColorType::Bgr8 => (3, 8), ExtendedColorType::Bgra8 => (4, 8), ExtendedColorType::Cmyk8 => (4, 8), _ => return None, }; Some(Self { channels, bits_per_channel, }) } pub const fn bits_per_pixel(&self) -> u8 { self.channels * self.bits_per_channel } } pub struct ImageItem { pub id: ImageId, pub file: Arc<worktree::File>, pub image: Arc<gpui::Image>, reload_task: Option<Task<()>>, pub image_metadata: Option<ImageMetadata>, } impl ImageItem { pub fn compute_metadata_from_bytes(image_bytes: &[u8]) -> Result<ImageMetadata> { let image_format = image::guess_format(image_bytes)?; let mut image_reader = ImageReader::new(std::io::Cursor::new(image_bytes)); image_reader.set_format(image_format); let image = image_reader.decode()?; let (width, height) = image.dimensions(); Ok(ImageMetadata { width, height, file_size: image_bytes.len() as u64, format: image_format, colors: ImageColorInfo::from_color_type(image.color()), }) } pub async fn load_image_metadata( image: Entity<ImageItem>, project: Entity<Project>, cx: &mut AsyncApp, ) -> Result<ImageMetadata> { let (fs, image_path) = cx.update(|cx| { let fs = project.read(cx).fs().clone(); let image_path = image .read(cx) .abs_path(cx) .context("absolutizing image file path")?; anyhow::Ok((fs, image_path)) })?; let image_bytes = fs.load_bytes(&image_path).await?; Self::compute_metadata_from_bytes(&image_bytes) } pub fn project_path(&self, cx: &App) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), path: self.file.path().clone(), } } pub fn abs_path(&self, cx: &App) -> Option<PathBuf> { Some(self.file.as_local()?.abs_path(cx)) } fn file_updated(&mut self, new_file: Arc<worktree::File>, cx: &mut Context<Self>) { let mut file_changed = false; let old_file = &self.file; 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 Context<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(async move |this, cx| { if let Some(image) = content .await .context("Failed to load image content") .and_then(create_gpui_image) .log_err() { this.update(cx, |this, cx| { this.image = image; cx.emit(ImageItemEvent::Reloaded); }) .log_err(); } _ = tx.send(()); })); Some(rx) } } pub fn is_image_file(project: &Entity<Project>, path: &ProjectPath, cx: &App) -> bool { let ext = util::maybe!({ let worktree_abs_path = project .read(cx) .worktree_for_id(path.worktree_id, cx)? .read(cx) .abs_path(); path.path .extension() .or_else(|| worktree_abs_path.extension()?.to_str()) .map(str::to_lowercase) }); match ext { Some(ext) => Img::extensions().contains(&ext.as_str()) && !ext.contains("svg"), None => false, } } impl ProjectItem for ImageItem { fn try_open( project: &Entity<Project>, path: &ProjectPath, cx: &mut App, ) -> Option<Task<anyhow::Result<Entity<Self>>>> { if is_image_file(project, path, cx) { Some(cx.spawn({ let path = path.clone(); let project = project.clone(); async move |cx| { project .update(cx, |project, cx| project.open_image(path, cx)) .await } })) } else { None } } fn entry_id(&self, _: &App) -> Option<ProjectEntryId> { self.file.entry_id } fn project_path(&self, cx: &App) -> Option<ProjectPath> { Some(self.project_path(cx)) } fn is_dirty(&self) -> bool { false } } trait ImageStoreImpl { fn open_image( &self, path: Arc<RelPath>, worktree: Entity<Worktree>, cx: &mut Context<ImageStore>, ) -> Task<Result<Entity<ImageItem>>>; fn reload_images( &self, images: HashSet<Entity<ImageItem>>, cx: &mut Context<ImageStore>, ) -> Task<Result<()>>; fn as_local(&self) -> Option<Entity<LocalImageStore>>; fn as_remote(&self) -> Option<Entity<RemoteImageStore>>; } struct RemoteImageStore { upstream_client: AnyProtoClient, project_id: u64, loading_remote_images_by_id: HashMap<ImageId, LoadingRemoteImage>, remote_image_listeners: HashMap<ImageId, Vec<oneshot::Sender<anyhow::Result<Entity<ImageItem>>>>>, loaded_images: HashMap<ImageId, Entity<ImageItem>>, } struct LoadingRemoteImage { state: proto::ImageState, chunks: Vec<Vec<u8>>, received_size: u64, } struct LocalImageStore { local_image_ids_by_path: HashMap<ProjectPath, ImageId>, local_image_ids_by_entry_id: HashMap<ProjectEntryId, ImageId>, image_store: WeakEntity<ImageStore>, _subscription: Subscription, } pub struct ImageStore { state: Box<dyn ImageStoreImpl>, opened_images: HashMap<ImageId, WeakEntity<ImageItem>>, worktree_store: Entity<WorktreeStore>, #[allow(clippy::type_complexity)] loading_images_by_path: HashMap< ProjectPath, postage::watch::Receiver<Option<Result<Entity<ImageItem>, Arc<anyhow::Error>>>>, >, } impl ImageStore { pub fn local(worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>) -> Self { let this = cx.weak_entity(); Self { state: Box::new(cx.new(|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: Entity<WorktreeStore>, upstream_client: AnyProtoClient, project_id: u64, cx: &mut Context<Self>, ) -> Self { Self { state: Box::new(cx.new(|_| RemoteImageStore { upstream_client, project_id, loading_remote_images_by_id: Default::default(), remote_image_listeners: Default::default(), loaded_images: Default::default(), })), opened_images: Default::default(), loading_images_by_path: Default::default(), worktree_store, } } pub fn images(&self) -> impl '_ + Iterator<Item = Entity<ImageItem>> { self.opened_images .values() .filter_map(|image| image.upgrade()) } pub fn get(&self, image_id: ImageId) -> Option<Entity<ImageItem>> { self.opened_images .get(&image_id) .and_then(|image| image.upgrade()) } pub fn get_by_path(&self, path: &ProjectPath, cx: &App) -> Option<Entity<ImageItem>> { self.images() .find(|image| &image.read(cx).project_path(cx) == path) } pub fn open_image( &mut self, project_path: ProjectPath, cx: &mut Context<Self>, ) -> Task<Result<Entity<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 load_image = self .state .open_image(project_path.path.clone(), worktree, cx); cx.spawn(async move |this, cx| { let load_result = load_image.await; *tx.borrow_mut() = Some(this.update(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_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<Entity<ImageItem>, Arc<anyhow::Error>>>, >, ) -> Result<Entity<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<Entity<ImageItem>>, cx: &mut Context<ImageStore>, ) -> Task<Result<()>> { if images.is_empty() { return Task::ready(Ok(())); } self.state.reload_images(images, cx) } fn add_image(&mut self, image: Entity<ImageItem>, cx: &mut Context<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: Entity<ImageItem>, event: &ImageItemEvent, cx: &mut Context<Self>, ) { if let ImageItemEvent::FileHandleChanged = event && let Some(local) = self.state.as_local() { local.update(cx, |local, cx| { local.image_changed_file(image, cx); }) } } pub fn handle_create_image_for_peer( &mut self, envelope: TypedEnvelope<proto::CreateImageForPeer>, cx: &mut Context<Self>, ) -> Result<()> { if let Some(remote) = self.state.as_remote() { let worktree_store = self.worktree_store.clone(); let image = remote.update(cx, |remote, cx| { remote.handle_create_image_for_peer(envelope, &worktree_store, cx) })?; if let Some(image) = image { remote.update(cx, |this, cx| { let image = image.clone(); let image_id = image.read(cx).id; this.loaded_images.insert(image_id, image) }); self.add_image(image, cx)?; } } Ok(())