1use crate::{
  2    Project, ProjectEntryId, ProjectItem, ProjectPath,
  3    worktree_store::{WorktreeStore, WorktreeStoreEvent},
  4};
  5use anyhow::{Context as _, Result};
  6use collections::{HashMap, HashSet, hash_map};
  7use futures::{StreamExt, channel::oneshot};
  8use gpui::{
  9    App, AsyncApp, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity, prelude::*,
 10};
 11pub use image::ImageFormat;
 12use image::{ExtendedColorType, GenericImageView, ImageReader};
 13use language::{DiskState, File};
 14use rpc::{AnyProtoClient, ErrorExt as _};
 15use std::num::NonZeroU64;
 16use std::path::PathBuf;
 17use std::sync::Arc;
 18use util::{ResultExt, rel_path::RelPath};
 19use worktree::{LoadedBinaryFile, PathChange, Worktree};
 20
 21#[derive(Clone, Copy, Debug, Hash, PartialEq, PartialOrd, Ord, Eq)]
 22pub struct ImageId(NonZeroU64);
 23
 24impl std::fmt::Display for ImageId {
 25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 26        write!(f, "{}", self.0)
 27    }
 28}
 29
 30impl From<NonZeroU64> for ImageId {
 31    fn from(id: NonZeroU64) -> Self {
 32        ImageId(id)
 33    }
 34}
 35
 36#[derive(Debug)]
 37pub enum ImageItemEvent {
 38    ReloadNeeded,
 39    Reloaded,
 40    FileHandleChanged,
 41    MetadataUpdated,
 42}
 43
 44impl EventEmitter<ImageItemEvent> for ImageItem {}
 45
 46pub enum ImageStoreEvent {
 47    ImageAdded(Entity<ImageItem>),
 48}
 49
 50impl EventEmitter<ImageStoreEvent> for ImageStore {}
 51
 52#[derive(Debug, Clone, Copy)]
 53pub struct ImageMetadata {
 54    pub width: u32,
 55    pub height: u32,
 56    pub file_size: u64,
 57    pub colors: Option<ImageColorInfo>,
 58    pub format: ImageFormat,
 59}
 60
 61#[derive(Debug, Clone, Copy)]
 62pub struct ImageColorInfo {
 63    pub channels: u8,
 64    pub bits_per_channel: u8,
 65}
 66
 67impl ImageColorInfo {
 68    pub fn from_color_type(color_type: impl Into<ExtendedColorType>) -> Option<Self> {
 69        let (channels, bits_per_channel) = match color_type.into() {
 70            ExtendedColorType::L8 => (1, 8),
 71            ExtendedColorType::L16 => (1, 16),
 72            ExtendedColorType::La8 => (2, 8),
 73            ExtendedColorType::La16 => (2, 16),
 74            ExtendedColorType::Rgb8 => (3, 8),
 75            ExtendedColorType::Rgb16 => (3, 16),
 76            ExtendedColorType::Rgba8 => (4, 8),
 77            ExtendedColorType::Rgba16 => (4, 16),
 78            ExtendedColorType::A8 => (1, 8),
 79            ExtendedColorType::Bgr8 => (3, 8),
 80            ExtendedColorType::Bgra8 => (4, 8),
 81            ExtendedColorType::Cmyk8 => (4, 8),
 82            _ => return None,
 83        };
 84
 85        Some(Self {
 86            channels,
 87            bits_per_channel,
 88        })
 89    }
 90
 91    pub const fn bits_per_pixel(&self) -> u8 {
 92        self.channels * self.bits_per_channel
 93    }
 94}
 95
 96pub struct ImageItem {
 97    pub id: ImageId,
 98    pub file: Arc<worktree::File>,
 99    pub image: Arc<gpui::Image>,
100    reload_task: Option<Task<()>>,
101    pub image_metadata: Option<ImageMetadata>,
102}
103
104impl ImageItem {
105    pub async fn load_image_metadata(
106        image: Entity<ImageItem>,
107        project: Entity<Project>,
108        cx: &mut AsyncApp,
109    ) -> Result<ImageMetadata> {
110        let (fs, image_path) = cx.update(|cx| {
111            let fs = project.read(cx).fs().clone();
112            let image_path = image
113                .read(cx)
114                .abs_path(cx)
115                .context("absolutizing image file path")?;
116            anyhow::Ok((fs, image_path))
117        })??;
118
119        let image_bytes = fs.load_bytes(&image_path).await?;
120        let image_format = image::guess_format(&image_bytes)?;
121
122        let mut image_reader = ImageReader::new(std::io::Cursor::new(image_bytes));
123        image_reader.set_format(image_format);
124        let image = image_reader.decode()?;
125
126        let (width, height) = image.dimensions();
127        let file_metadata = fs
128            .metadata(image_path.as_path())
129            .await?
130            .context("failed to load image metadata")?;
131
132        Ok(ImageMetadata {
133            width,
134            height,
135            file_size: file_metadata.len,
136            format: image_format,
137            colors: ImageColorInfo::from_color_type(image.color()),
138        })
139    }
140
141    pub fn project_path(&self, cx: &App) -> ProjectPath {
142        ProjectPath {
143            worktree_id: self.file.worktree_id(cx),
144            path: self.file.path().clone(),
145        }
146    }
147
148    pub fn abs_path(&self, cx: &App) -> Option<PathBuf> {
149        Some(self.file.as_local()?.abs_path(cx))
150    }
151
152    fn file_updated(&mut self, new_file: Arc<worktree::File>, cx: &mut Context<Self>) {
153        let mut file_changed = false;
154
155        let old_file = &self.file;
156        if new_file.path() != old_file.path() {
157            file_changed = true;
158        }
159
160        let old_state = old_file.disk_state();
161        let new_state = new_file.disk_state();
162        if old_state != new_state {
163            file_changed = true;
164            if matches!(new_state, DiskState::Present { .. }) {
165                cx.emit(ImageItemEvent::ReloadNeeded)
166            }
167        }
168
169        self.file = new_file;
170        if file_changed {
171            cx.emit(ImageItemEvent::FileHandleChanged);
172            cx.notify();
173        }
174    }
175
176    fn reload(&mut self, cx: &mut Context<Self>) -> Option<oneshot::Receiver<()>> {
177        let local_file = self.file.as_local()?;
178        let (tx, rx) = futures::channel::oneshot::channel();
179
180        let content = local_file.load_bytes(cx);
181        self.reload_task = Some(cx.spawn(async move |this, cx| {
182            if let Some(image) = content
183                .await
184                .context("Failed to load image content")
185                .and_then(create_gpui_image)
186                .log_err()
187            {
188                this.update(cx, |this, cx| {
189                    this.image = image;
190                    cx.emit(ImageItemEvent::Reloaded);
191                })
192                .log_err();
193            }
194            _ = tx.send(());
195        }));
196        Some(rx)
197    }
198}
199
200pub fn is_image_file(project: &Entity<Project>, path: &ProjectPath, cx: &App) -> bool {
201    let ext = util::maybe!({
202        let worktree_abs_path = project
203            .read(cx)
204            .worktree_for_id(path.worktree_id, cx)?
205            .read(cx)
206            .abs_path();
207        path.path
208            .extension()
209            .or_else(|| worktree_abs_path.extension()?.to_str())
210            .map(str::to_lowercase)
211    });
212
213    match ext {
214        Some(ext) => Img::extensions().contains(&ext.as_str()) && !ext.contains("svg"),
215        None => false,
216    }
217}
218
219impl ProjectItem for ImageItem {
220    fn try_open(
221        project: &Entity<Project>,
222        path: &ProjectPath,
223        cx: &mut App,
224    ) -> Option<Task<anyhow::Result<Entity<Self>>>> {
225        if is_image_file(project, path, cx) {
226            Some(cx.spawn({
227                let path = path.clone();
228                let project = project.clone();
229                async move |cx| {
230                    project
231                        .update(cx, |project, cx| project.open_image(path, cx))?
232                        .await
233                }
234            }))
235        } else {
236            None
237        }
238    }
239
240    fn entry_id(&self, _: &App) -> Option<ProjectEntryId> {
241        self.file.entry_id
242    }
243
244    fn project_path(&self, cx: &App) -> Option<ProjectPath> {
245        Some(self.project_path(cx))
246    }
247
248    fn is_dirty(&self) -> bool {
249        false
250    }
251}
252
253trait ImageStoreImpl {
254    fn open_image(
255        &self,
256        path: Arc<RelPath>,
257        worktree: Entity<Worktree>,
258        cx: &mut Context<ImageStore>,
259    ) -> Task<Result<Entity<ImageItem>>>;
260
261    fn reload_images(
262        &self,
263        images: HashSet<Entity<ImageItem>>,
264        cx: &mut Context<ImageStore>,
265    ) -> Task<Result<()>>;
266
267    fn as_local(&self) -> Option<Entity<LocalImageStore>>;
268}
269
270struct RemoteImageStore {}
271
272struct LocalImageStore {
273    local_image_ids_by_path: HashMap<ProjectPath, ImageId>,
274    local_image_ids_by_entry_id: HashMap<ProjectEntryId, ImageId>,
275    image_store: WeakEntity<ImageStore>,
276    _subscription: Subscription,
277}
278
279pub struct ImageStore {
280    state: Box<dyn ImageStoreImpl>,
281    opened_images: HashMap<ImageId, WeakEntity<ImageItem>>,
282    worktree_store: Entity<WorktreeStore>,
283    #[allow(clippy::type_complexity)]
284    loading_images_by_path: HashMap<
285        ProjectPath,
286        postage::watch::Receiver<Option<Result<Entity<ImageItem>, Arc<anyhow::Error>>>>,
287    >,
288}
289
290impl ImageStore {
291    pub fn local(worktree_store: Entity<WorktreeStore>, cx: &mut Context<Self>) -> Self {
292        let this = cx.weak_entity();
293        Self {
294            state: Box::new(cx.new(|cx| {
295                let subscription = cx.subscribe(
296                    &worktree_store,
297                    |this: &mut LocalImageStore, _, event, cx| {
298                        if let WorktreeStoreEvent::WorktreeAdded(worktree) = event {
299                            this.subscribe_to_worktree(worktree, cx);
300                        }
301                    },
302                );
303
304                LocalImageStore {
305                    local_image_ids_by_path: Default::default(),
306                    local_image_ids_by_entry_id: Default::default(),
307                    image_store: this,
308                    _subscription: subscription,
309                }
310            })),
311            opened_images: Default::default(),
312            loading_images_by_path: Default::default(),
313            worktree_store,
314        }
315    }
316
317    pub fn remote(
318        worktree_store: Entity<WorktreeStore>,
319        _upstream_client: AnyProtoClient,
320        _remote_id: u64,
321        cx: &mut Context<Self>,
322    ) -> Self {
323        Self {
324            state: Box::new(cx.new(|_| RemoteImageStore {})),
325            opened_images: Default::default(),
326            loading_images_by_path: Default::default(),
327            worktree_store,
328        }
329    }
330
331    pub fn images(&self) -> impl '_ + Iterator<Item = Entity<ImageItem>> {
332        self.opened_images
333            .values()
334            .filter_map(|image| image.upgrade())
335    }
336
337    pub fn get(&self, image_id: ImageId) -> Option<Entity<ImageItem>> {
338        self.opened_images
339            .get(&image_id)
340            .and_then(|image| image.upgrade())
341    }
342
343    pub fn get_by_path(&self, path: &ProjectPath, cx: &App) -> Option<Entity<ImageItem>> {
344        self.images()
345            .find(|image| &image.read(cx).project_path(cx) == path)
346    }
347
348    pub fn open_image(
349        &mut self,
350        project_path: ProjectPath,
351        cx: &mut Context<Self>,
352    ) -> Task<Result<Entity<ImageItem>>> {
353        let existing_image = self.get_by_path(&project_path, cx);
354        if let Some(existing_image) = existing_image {
355            return Task::ready(Ok(existing_image));
356        }
357
358        let Some(worktree) = self
359            .worktree_store
360            .read(cx)
361            .worktree_for_id(project_path.worktree_id, cx)
362        else {
363            return Task::ready(Err(anyhow::anyhow!("no such worktree")));
364        };
365
366        let loading_watch = match self.loading_images_by_path.entry(project_path.clone()) {
367            // If the given path is already being loaded, then wait for that existing
368            // task to complete and return the same image.
369            hash_map::Entry::Occupied(e) => e.get().clone(),
370
371            // Otherwise, record the fact that this path is now being loaded.
372            hash_map::Entry::Vacant(entry) => {
373                let (mut tx, rx) = postage::watch::channel();
374                entry.insert(rx.clone());
375
376                let load_image = self
377                    .state
378                    .open_image(project_path.path.clone(), worktree, cx);
379
380                cx.spawn(async move |this, cx| {
381                    let load_result = load_image.await;
382                    *tx.borrow_mut() = Some(this.update(cx, |this, _cx| {
383                        // Record the fact that the image is no longer loading.
384                        this.loading_images_by_path.remove(&project_path);
385                        let image = load_result.map_err(Arc::new)?;
386                        Ok(image)
387                    })?);
388                    anyhow::Ok(())
389                })
390                .detach();
391                rx
392            }
393        };
394
395        cx.background_spawn(async move {
396            Self::wait_for_loading_image(loading_watch)
397                .await
398                .map_err(|e| e.cloned())
399        })
400    }
401
402    pub async fn wait_for_loading_image(
403        mut receiver: postage::watch::Receiver<
404            Option<Result<Entity<ImageItem>, Arc<anyhow::Error>>>,
405        >,
406    ) -> Result<Entity<ImageItem>, Arc<anyhow::Error>> {
407        loop {
408            if let Some(result) = receiver.borrow().as_ref() {
409                match result {
410                    Ok(image) => return Ok(image.to_owned()),
411                    Err(e) => return Err(e.to_owned()),
412                }
413            }
414            receiver.next().await;
415        }
416    }
417
418    pub fn reload_images(
419        &self,
420        images: HashSet<Entity<ImageItem>>,
421        cx: &mut Context<ImageStore>,
422    ) -> Task<Result<()>> {
423        if images.is_empty() {
424            return Task::ready(Ok(()));
425        }
426
427        self.state.reload_images(images, cx)
428    }
429
430    fn add_image(&mut self, image: Entity<ImageItem>, cx: &mut Context<ImageStore>) -> Result<()> {
431        let image_id = image.read(cx).id;
432
433        self.opened_images.insert(image_id, image.downgrade());
434
435        cx.subscribe(&image, Self::on_image_event).detach();
436        cx.emit(ImageStoreEvent::ImageAdded(image));
437        Ok(())
438    }
439
440    fn on_image_event(
441        &mut self,
442        image: Entity<ImageItem>,
443        event: &ImageItemEvent,
444        cx: &mut Context<Self>,
445    ) {
446        if let ImageItemEvent::FileHandleChanged = event
447            && let Some(local) = self.state.as_local()
448        {
449            local.update(cx, |local, cx| {
450                local.image_changed_file(image, cx);
451            })
452        }
453    }
454}
455
456impl ImageStoreImpl for Entity<LocalImageStore> {
457    fn open_image(
458        &self,
459        path: Arc<RelPath>,
460        worktree: Entity<Worktree>,
461        cx: &mut Context<ImageStore>,
462    ) -> Task<Result<Entity<ImageItem>>> {
463        let this = self.clone();
464
465        let load_file = worktree.update(cx, |worktree, cx| {
466            worktree.load_binary_file(path.as_ref(), cx)
467        });
468        cx.spawn(async move |image_store, cx| {
469            let LoadedBinaryFile { file, content } = load_file.await?;
470            let image = create_gpui_image(content)?;
471
472            let entity = cx.new(|cx| ImageItem {
473                id: cx.entity_id().as_non_zero_u64().into(),
474                file: file.clone(),
475                image,
476                image_metadata: None,
477                reload_task: None,
478            })?;
479
480            let image_id = cx.read_entity(&entity, |model, _| model.id)?;
481
482            this.update(cx, |this, cx| {
483                image_store.update(cx, |image_store, cx| {
484                    image_store.add_image(entity.clone(), cx)
485                })??;
486                this.local_image_ids_by_path.insert(
487                    ProjectPath {
488                        worktree_id: file.worktree_id(cx),
489                        path: file.path.clone(),
490                    },
491                    image_id,
492                );
493
494                if let Some(entry_id) = file.entry_id {
495                    this.local_image_ids_by_entry_id.insert(entry_id, image_id);
496                }
497
498                anyhow::Ok(())
499            })??;
500
501            Ok(entity)
502        })
503    }
504
505    fn reload_images(
506        &self,
507        images: HashSet<Entity<ImageItem>>,
508        cx: &mut Context<ImageStore>,
509    ) -> Task<Result<()>> {
510        cx.spawn(async move |_, cx| {
511            for image in images {
512                if let Some(rec) = image.update(cx, |image, cx| image.reload(cx))? {
513                    rec.await?
514                }
515            }
516            Ok(())
517        })
518    }
519
520    fn as_local(&self) -> Option<Entity<LocalImageStore>> {
521        Some(self.clone())
522    }
523}
524
525impl LocalImageStore {
526    fn subscribe_to_worktree(&mut self, worktree: &Entity<Worktree>, cx: &mut Context<Self>) {
527        cx.subscribe(worktree, |this, worktree, event, cx| {
528            if worktree.read(cx).is_local()
529                && let worktree::Event::UpdatedEntries(changes) = event
530            {
531                this.local_worktree_entries_changed(&worktree, changes, cx);
532            }
533        })
534        .detach();
535    }
536
537    fn local_worktree_entries_changed(
538        &mut self,
539        worktree_handle: &Entity<Worktree>,
540        changes: &[(Arc<RelPath>, ProjectEntryId, PathChange)],
541        cx: &mut Context<Self>,
542    ) {
543        let snapshot = worktree_handle.read(cx).snapshot();
544        for (path, entry_id, _) in changes {
545            self.local_worktree_entry_changed(*entry_id, path, worktree_handle, &snapshot, cx);
546        }
547    }
548
549    fn local_worktree_entry_changed(
550        &mut self,
551        entry_id: ProjectEntryId,
552        path: &Arc<RelPath>,
553        worktree: &Entity<worktree::Worktree>,
554        snapshot: &worktree::Snapshot,
555        cx: &mut Context<Self>,
556    ) -> Option<()> {
557        let project_path = ProjectPath {
558            worktree_id: snapshot.id(),
559            path: path.clone(),
560        };
561        let image_id = match self.local_image_ids_by_entry_id.get(&entry_id) {
562            Some(&image_id) => image_id,
563            None => self.local_image_ids_by_path.get(&project_path).copied()?,
564        };
565
566        let image = self
567            .image_store
568            .update(cx, |image_store, _| {
569                if let Some(image) = image_store.get(image_id) {
570                    Some(image)
571                } else {
572                    image_store.opened_images.remove(&image_id);
573                    None
574                }
575            })
576            .ok()
577            .flatten();
578        let image = if let Some(image) = image {
579            image
580        } else {
581            self.local_image_ids_by_path.remove(&project_path);
582            self.local_image_ids_by_entry_id.remove(&entry_id);
583            return None;
584        };
585
586        image.update(cx, |image, cx| {
587            let old_file = &image.file;
588            if old_file.worktree != *worktree {
589                return;
590            }
591
592            let snapshot_entry = old_file
593                .entry_id
594                .and_then(|entry_id| snapshot.entry_for_id(entry_id))
595                .or_else(|| snapshot.entry_for_path(old_file.path.as_ref()));
596
597            let new_file = if let Some(entry) = snapshot_entry {
598                worktree::File {
599                    disk_state: match entry.mtime {
600                        Some(mtime) => DiskState::Present { mtime },
601                        None => old_file.disk_state,
602                    },
603                    is_local: true,
604                    entry_id: Some(entry.id),
605                    path: entry.path.clone(),
606                    worktree: worktree.clone(),
607                    is_private: entry.is_private,
608                }
609            } else {
610                worktree::File {
611                    disk_state: DiskState::Deleted,
612                    is_local: true,
613                    entry_id: old_file.entry_id,
614                    path: old_file.path.clone(),
615                    worktree: worktree.clone(),
616                    is_private: old_file.is_private,
617                }
618            };
619
620            if new_file == **old_file {
621                return;
622            }
623
624            if new_file.path != old_file.path {
625                self.local_image_ids_by_path.remove(&ProjectPath {
626                    path: old_file.path.clone(),
627                    worktree_id: old_file.worktree_id(cx),
628                });
629                self.local_image_ids_by_path.insert(
630                    ProjectPath {
631                        worktree_id: new_file.worktree_id(cx),
632                        path: new_file.path.clone(),
633                    },
634                    image_id,
635                );
636            }
637
638            if new_file.entry_id != old_file.entry_id {
639                if let Some(entry_id) = old_file.entry_id {
640                    self.local_image_ids_by_entry_id.remove(&entry_id);
641                }
642                if let Some(entry_id) = new_file.entry_id {
643                    self.local_image_ids_by_entry_id.insert(entry_id, image_id);
644                }
645            }
646
647            image.file_updated(Arc::new(new_file), cx);
648        });
649        None
650    }
651
652    fn image_changed_file(&mut self, image: Entity<ImageItem>, cx: &mut App) -> Option<()> {
653        let image = image.read(cx);
654        let file = &image.file;
655
656        let image_id = image.id;
657        if let Some(entry_id) = file.entry_id {
658            match self.local_image_ids_by_entry_id.get(&entry_id) {
659                Some(_) => {
660                    return None;
661                }
662                None => {
663                    self.local_image_ids_by_entry_id.insert(entry_id, image_id);
664                }
665            }
666        };
667        self.local_image_ids_by_path.insert(
668            ProjectPath {
669                worktree_id: file.worktree_id(cx),
670                path: file.path.clone(),
671            },
672            image_id,
673        );
674
675        Some(())
676    }
677}
678
679fn create_gpui_image(content: Vec<u8>) -> anyhow::Result<Arc<gpui::Image>> {
680    let format = image::guess_format(&content)?;
681
682    Ok(Arc::new(gpui::Image::from_bytes(
683        match format {
684            image::ImageFormat::Png => gpui::ImageFormat::Png,
685            image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg,
686            image::ImageFormat::WebP => gpui::ImageFormat::Webp,
687            image::ImageFormat::Gif => gpui::ImageFormat::Gif,
688            image::ImageFormat::Bmp => gpui::ImageFormat::Bmp,
689            image::ImageFormat::Tiff => gpui::ImageFormat::Tiff,
690            image::ImageFormat::Ico => gpui::ImageFormat::Ico,
691            format => anyhow::bail!("Image format {format:?} not supported"),
692        },
693        content,
694    )))
695}
696
697impl ImageStoreImpl for Entity<RemoteImageStore> {
698    fn open_image(
699        &self,
700        _path: Arc<RelPath>,
701        _worktree: Entity<Worktree>,
702        _cx: &mut Context<ImageStore>,
703    ) -> Task<Result<Entity<ImageItem>>> {
704        Task::ready(Err(anyhow::anyhow!(
705            "Opening images from remote is not supported"
706        )))
707    }
708
709    fn reload_images(
710        &self,
711        _images: HashSet<Entity<ImageItem>>,
712        _cx: &mut Context<ImageStore>,
713    ) -> Task<Result<()>> {
714        Task::ready(Err(anyhow::anyhow!(
715            "Reloading images from remote is not supported"
716        )))
717    }
718
719    fn as_local(&self) -> Option<Entity<LocalImageStore>> {
720        None
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727    use fs::FakeFs;
728    use gpui::TestAppContext;
729    use serde_json::json;
730    use settings::SettingsStore;
731    use util::rel_path::rel_path;
732
733    pub fn init_test(cx: &mut TestAppContext) {
734        zlog::init_test();
735
736        cx.update(|cx| {
737            let settings_store = SettingsStore::test(cx);
738            cx.set_global(settings_store);
739            language::init(cx);
740            Project::init_settings(cx);
741        });
742    }
743
744    #[gpui::test]
745    async fn test_image_not_loaded_twice(cx: &mut TestAppContext) {
746        init_test(cx);
747        let fs = FakeFs::new(cx.executor());
748
749        fs.insert_tree("/root", json!({})).await;
750        // Create a png file that consists of a single white pixel
751        fs.insert_file(
752            "/root/image_1.png",
753            vec![
754                0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48,
755                0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00,
756                0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78,
757                0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00,
758                0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82,
759            ],
760        )
761        .await;
762
763        let project = Project::test(fs, ["/root".as_ref()], cx).await;
764
765        let worktree_id =
766            cx.update(|cx| project.read(cx).worktrees(cx).next().unwrap().read(cx).id());
767
768        let project_path = ProjectPath {
769            worktree_id,
770            path: rel_path("image_1.png").into(),
771        };
772
773        let (task1, task2) = project.update(cx, |project, cx| {
774            (
775                project.open_image(project_path.clone(), cx),
776                project.open_image(project_path.clone(), cx),
777            )
778        });
779
780        let image1 = task1.await.unwrap();
781        let image2 = task2.await.unwrap();
782
783        assert_eq!(image1, image2);
784    }
785}