From 392e6f8d6f3332bf0b59435896df34d5a00dd080 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Sat, 7 Dec 2024 03:49:10 +0100 Subject: [PATCH 01/17] chore: show image info --- Cargo.lock | 1 + crates/image_viewer/Cargo.toml | 1 + crates/image_viewer/src/image_viewer.rs | 46 ++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 09930893337d77..b56797cbd4cd01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6173,6 +6173,7 @@ dependencies = [ "db", "file_icons", "gpui", + "image", "project", "settings", "theme", diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index 9c431e5edc6ecf..539d68d0c7f185 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -23,3 +23,4 @@ theme.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +image = "0.25.5" diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index c3f264d863b63b..c5f228cfe5eb85 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -1,3 +1,5 @@ +use image::GenericImageView; +use std::fs::metadata; use std::path::PathBuf; use anyhow::Context as _; @@ -122,8 +124,21 @@ impl Item for ImageView { fn breadcrumbs(&self, _theme: &Theme, cx: &AppContext) -> Option> { let text = breadcrumbs_text_for_image(self.project.read(cx), self.image_item.read(cx), cx); + + let img_info = image_info(self.image_item.read(cx), self.project.read(cx), cx) + .map(|(width, height, size)| { + format!( + "{} | Dimension: {}x{} | Image size: {:.2} KB", + text, + width, + height, + size as f64 / 1024.0 + ) + }) + .unwrap_or_else(|err| format!("{} | Image info is not available: {}", text, err)); + Some(vec![BreadcrumbText { - text, + text: img_info, highlights: None, font: None, }]) @@ -145,6 +160,35 @@ impl Item for ImageView { } } +fn image_info( + image: &ImageItem, + project: &Project, + cx: &AppContext, +) -> Result<(u32, u32, u64), String> { + let worktree = project + .worktree_for_id(image.project_path(cx).worktree_id, cx) + .ok_or_else(|| "Could not find worktree for image".to_string())?; + let worktree_root = worktree.read(cx).abs_path(); + + let path = if image.path().is_absolute() { + image.path().to_path_buf() + } else { + worktree_root.join(image.path()) + }; + + if !path.exists() { + return Err(format!("File does not exist at path: {:?}", path)); + } + + let img = image::open(&path).map_err(|e| format!("Failed to open image: {}", e))?; + let dimensions = img.dimensions(); + + let file_metadata = metadata(&path).map_err(|e| format!("Cannot access image data: {}", e))?; + let file_size = file_metadata.len(); + + Ok((dimensions.0, dimensions.1, file_size)) +} + fn breadcrumbs_text_for_image(project: &Project, image: &ImageItem, cx: &AppContext) -> String { let path = image.path(); if project.visible_worktrees(cx).count() <= 1 { From da13757f5c0a6b2446d0fb135724ef78fc47eef8 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Sun, 15 Dec 2024 00:36:15 +0100 Subject: [PATCH 02/17] implement image_info StatusItemView --- Cargo.toml | 2 +- crates/image_viewer/Cargo.toml | 2 +- crates/image_viewer/src/image_info.rs | 86 ++++++++++++++++++++++ crates/image_viewer/src/image_viewer.rs | 47 +------------ crates/project/src/image_store.rs | 94 +++++++++++++++++++++++++ crates/zed/src/zed.rs | 4 ++ 6 files changed, 189 insertions(+), 46 deletions(-) create mode 100644 crates/image_viewer/src/image_info.rs diff --git a/Cargo.toml b/Cargo.toml index 7ff0ad6ce3534d..28146d3f0c5748 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -386,7 +386,7 @@ html5ever = "0.27.0" hyper = "0.14" http = "1.1" ignore = "0.4.22" -image = "0.25.1" +image = "0.25.5" indexmap = { version = "1.6.2", features = ["serde"] } indoc = "2" itertools = "0.13.0" diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index 539d68d0c7f185..af54b8986b07ac 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -22,5 +22,5 @@ settings.workspace = true theme.workspace = true ui.workspace = true util.workspace = true +image.workspace = true workspace.workspace = true -image = "0.25.5" diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs new file mode 100644 index 00000000000000..aefe3a364c59d5 --- /dev/null +++ b/crates/image_viewer/src/image_info.rs @@ -0,0 +1,86 @@ +use gpui::{div, prelude::*, Model, Render, ViewContext, WeakView}; +use project::ImageItem; +use workspace::{ItemHandle, StatusItemView, Workspace}; + +pub struct ImageInfoView { + workspace: WeakView, + width: Option, + height: Option, + file_size: Option, + color_type: Option<&'static str>, +} + +impl ImageInfoView { + pub fn new(workspace: &Workspace) -> Self { + Self { + workspace: workspace.weak_handle(), + width: None, + height: None, + file_size: None, + color_type: None, + } + } + + fn format_file_size(&self) -> String { + self.file_size.map_or("--".to_string(), |size| { + if size < 1024 { + format!("{} B", size) + } else if size < 1024 * 1024 { + format!("{:.1} KB", size as f64 / 1024.0) + } else { + format!("{:.1} MB", size as f64 / (1024.0 * 1024.0)) + } + }) + } +} + +impl Render for ImageInfoView { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + if self.width.is_some() + || self.height.is_some() + || self.file_size.is_some() + || self.color_type.is_some() + { + div().flex().items_center().gap_2().text_xs().child(format!( + "Whole image {} × {} {} {}", + self.width.map_or("--".to_string(), |w| w.to_string()), + self.height.map_or("--".to_string(), |h| h.to_string()), + self.format_file_size(), + self.color_type.as_deref().unwrap_or("") + )) + } else { + div() + } + } +} + +impl StatusItemView for ImageInfoView { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn ItemHandle>, + cx: &mut ViewContext, + ) { + // Reset fields + self.width = None; + self.height = None; + self.file_size = None; + self.color_type = None; + + // Extract metadata if the item is an ImageItem + if let Some(item) = active_pane_item { + if let Some(image_model) = item.downcast::>() { + let image_item = image_model.read(cx); + + // Assign the properties + self.width = image_item.read(cx).width; // `Option` is directly assignable + self.height = image_item.read(cx).height; // `Option` is directly assignable + self.file_size = image_item.read(cx).file_size; // `Option` is directly assignable + + // Handle `color_type` + self.color_type = image_item.read(cx).color_type; + } + } + + cx.notify(); + } +} diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index c5f228cfe5eb85..2daac2f278b867 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -1,5 +1,5 @@ -use image::GenericImageView; -use std::fs::metadata; +pub mod image_info; + use std::path::PathBuf; use anyhow::Context as _; @@ -125,20 +125,8 @@ impl Item for ImageView { fn breadcrumbs(&self, _theme: &Theme, cx: &AppContext) -> Option> { let text = breadcrumbs_text_for_image(self.project.read(cx), self.image_item.read(cx), cx); - let img_info = image_info(self.image_item.read(cx), self.project.read(cx), cx) - .map(|(width, height, size)| { - format!( - "{} | Dimension: {}x{} | Image size: {:.2} KB", - text, - width, - height, - size as f64 / 1024.0 - ) - }) - .unwrap_or_else(|err| format!("{} | Image info is not available: {}", text, err)); - Some(vec![BreadcrumbText { - text: img_info, + text: text, highlights: None, font: None, }]) @@ -160,35 +148,6 @@ impl Item for ImageView { } } -fn image_info( - image: &ImageItem, - project: &Project, - cx: &AppContext, -) -> Result<(u32, u32, u64), String> { - let worktree = project - .worktree_for_id(image.project_path(cx).worktree_id, cx) - .ok_or_else(|| "Could not find worktree for image".to_string())?; - let worktree_root = worktree.read(cx).abs_path(); - - let path = if image.path().is_absolute() { - image.path().to_path_buf() - } else { - worktree_root.join(image.path()) - }; - - if !path.exists() { - return Err(format!("File does not exist at path: {:?}", path)); - } - - let img = image::open(&path).map_err(|e| format!("Failed to open image: {}", e))?; - let dimensions = img.dimensions(); - - let file_metadata = metadata(&path).map_err(|e| format!("Cannot access image data: {}", e))?; - let file_size = file_metadata.len(); - - Ok((dimensions.0, dimensions.1, file_size)) -} - fn breadcrumbs_text_for_image(project: &Project, image: &ImageItem, cx: &AppContext) -> String { let path = image.path(); if project.visible_worktrees(cx).count() <= 1 { diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 949e1f484e22d1..ea5a5b3df37f25 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -4,11 +4,13 @@ use crate::{ }; use anyhow::{Context as _, Result}; use collections::{hash_map, HashMap, HashSet}; +use fs::Fs; use futures::{channel::oneshot, StreamExt}; use gpui::{ hash, prelude::*, AppContext, EventEmitter, Img, Model, ModelContext, Subscription, Task, WeakModel, }; +use image::{ColorType, GenericImageView}; use language::{DiskState, File}; use rpc::{AnyProtoClient, ErrorExt as _}; use std::ffi::OsStr; @@ -52,9 +54,73 @@ pub struct ImageItem { pub file: Arc, pub image: Arc, reload_task: Option>, + pub width: Option, + pub height: Option, + pub file_size: Option, + pub color_type: Option<&'static str>, +} + +fn image_color_type_description(color_type: ColorType) -> &'static str { + match color_type { + ColorType::L8 => "Grayscale (8-bit)", + ColorType::La8 => "Grayscale with Alpha (8-bit)", + ColorType::Rgba8 => "PNG (32-bit color)", + ColorType::Rgb8 => "RGB (24-bit color)", + ColorType::Rgb16 => "RGB (48-bit color)", + ColorType::Rgba16 => "PNG (64-bit color)", + ColorType::L16 => "Grayscale (16-bit)", + ColorType::La16 => "Grayscale with Alpha (16-bit)", + + _ => "unknown color type", + } } impl ImageItem { + async fn image_info( + image: &ImageItem, + project: &Project, + cx: &AppContext, + fs: &F, + ) -> Result<(u32, u32, u64, &'static str), String> { + let worktree = project + .worktree_for_id(image.project_path(cx).worktree_id, cx) + .ok_or_else(|| "Could not find worktree for image".to_string())?; + let worktree_root = worktree.read(cx).abs_path(); + + let path = if image.path().is_absolute() { + image.path().to_path_buf() + } else { + worktree_root.join(image.path()) + }; + + if !path.exists() { + return Err(format!("File does not exist at path: {:?}", path)); + } + + let img = image::open(&path).map_err(|e| format!("Failed to open image: {}", e))?; + let dimensions = img.dimensions(); + let img_color_type = image_color_type_description(img.color()); + + let file_metadata = fs + .metadata(path.as_path()) + .await + .map_err(|e| format!("Cannot access image data: {}", e))? + .ok_or_else(|| "No metadata found".to_string())?; + + let file_size = file_metadata.len; + + Ok((dimensions.0, dimensions.1, file_size, img_color_type)) + } + + pub async fn try_open( + &self, + project: &Project, + cx: &AppContext, + fs: &F, + ) -> Result<(u32, u32, u64, &'static str), String> { + Self::image_info(self, project, cx, fs).await + } + pub fn project_path(&self, cx: &AppContext) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), @@ -66,6 +132,30 @@ impl ImageItem { self.file.path() } + pub async fn load_metadata( + &mut self, + project: &Project, + cx: &AppContext, + fs: &F, + ) -> Result<(), String> { + println!("Loading metadata for image: {:?}", self.path()); + + match Self::try_open(self, project, cx, fs).await { + Ok(metadata) => { + self.width = Some(metadata.0); + self.height = Some(metadata.1); + self.file_size = Some(metadata.2); + self.color_type = Some(metadata.3); + println!("Metadata loaded: {:?}", metadata); + Ok(()) + } + Err(err) => { + println!("Failed to load metadata: {}", err); + Err(err) + } + } + } + fn file_updated(&mut self, new_file: Arc, cx: &mut ModelContext) { let mut file_changed = false; @@ -388,6 +478,10 @@ impl ImageStoreImpl for Model { id: cx.entity_id().as_non_zero_u64().into(), file: file.clone(), image, + width: None, + file_size: None, + height: None, + color_type: None, reload_task: None, })?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 2adb287b4de98d..a4145caf4873bb 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -26,6 +26,7 @@ use gpui::{ PathPromptOptions, PromptLevel, ReadGlobal, Task, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, }; +use image_viewer::image_info; pub use open_listener::*; use outline_panel::OutlinePanel; use paths::{local_settings_file_relative_path, local_tasks_file_relative_path}; @@ -211,6 +212,8 @@ pub fn initialize_workspace( let vim_mode_indicator = cx.new_view(vim::ModeIndicator::new); let cursor_position = cx.new_view(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); + let image_metadata = cx.new_view(|_| image_info::ImageInfoView::new(workspace)); + workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); @@ -219,6 +222,7 @@ pub fn initialize_workspace( status_bar.add_right_item(active_toolchain_language, cx); status_bar.add_right_item(vim_mode_indicator, cx); status_bar.add_right_item(cursor_position, cx); + status_bar.add_right_item(image_metadata, cx); }); auto_update_ui::notify_of_any_new_update(cx); From b27afc46f8b0c8e1ac3a27354a3b041750565fe4 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Sun, 15 Dec 2024 00:39:17 +0100 Subject: [PATCH 03/17] minor --- crates/image_viewer/src/image_info.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index aefe3a364c59d5..bdaa2bd8840640 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -60,23 +60,18 @@ impl StatusItemView for ImageInfoView { active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { - // Reset fields self.width = None; self.height = None; self.file_size = None; self.color_type = None; - // Extract metadata if the item is an ImageItem if let Some(item) = active_pane_item { if let Some(image_model) = item.downcast::>() { let image_item = image_model.read(cx); - // Assign the properties - self.width = image_item.read(cx).width; // `Option` is directly assignable - self.height = image_item.read(cx).height; // `Option` is directly assignable - self.file_size = image_item.read(cx).file_size; // `Option` is directly assignable - - // Handle `color_type` + self.width = image_item.read(cx).width; + self.height = image_item.read(cx).height; + self.file_size = image_item.read(cx).file_size; self.color_type = image_item.read(cx).color_type; } } From bd466efff2778b2788906caf00afb27c2dee2ca7 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Sun, 29 Dec 2024 16:02:48 +0100 Subject: [PATCH 04/17] fix: use async Fs instead in favour of std::fs The standard fs crate does filesystem operations synchronously which may lead to blocking the main thread when trying to access the image metadata, specifically fs.metadata to obtain the file size. This PR addresses that issue, and also renders other image information like the dimensions (width & height), and the image type (PNG|JPG etc) --- Cargo.lock | 1 + crates/image_viewer/Cargo.toml | 1 + crates/image_viewer/src/image_info.rs | 112 +++++++++++++++----------- crates/project/src/image_store.rs | 55 +++++++------ crates/zed/src/zed.rs | 4 +- 5 files changed, 99 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b56797cbd4cd01..31698ebd36eb15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6172,6 +6172,7 @@ dependencies = [ "anyhow", "db", "file_icons", + "fs", "gpui", "image", "project", diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index af54b8986b07ac..4acf8dad47f5ec 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -24,3 +24,4 @@ ui.workspace = true util.workspace = true image.workspace = true workspace.workspace = true +fs.workspace = true diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index bdaa2bd8840640..a749ab498eba0b 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -1,81 +1,95 @@ -use gpui::{div, prelude::*, Model, Render, ViewContext, WeakView}; -use project::ImageItem; +use crate::ImageView; +use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext}; +use ui::{prelude::*, Button, LabelSize}; use workspace::{ItemHandle, StatusItemView, Workspace}; -pub struct ImageInfoView { - workspace: WeakView, +pub struct ImageInfo { width: Option, height: Option, file_size: Option, - color_type: Option<&'static str>, + color_type: Option, + _observe_active_image: Option, } -impl ImageInfoView { - pub fn new(workspace: &Workspace) -> Self { +impl ImageInfo { + pub fn new(_workspace: &Workspace) -> Self { Self { - workspace: workspace.weak_handle(), width: None, height: None, file_size: None, color_type: None, + _observe_active_image: None, } } - fn format_file_size(&self) -> String { - self.file_size.map_or("--".to_string(), |size| { - if size < 1024 { - format!("{} B", size) - } else if size < 1024 * 1024 { - format!("{:.1} KB", size as f64 / 1024.0) - } else { - format!("{:.1} MB", size as f64 / (1024.0 * 1024.0)) - } - }) + fn update_metadata(&mut self, image_view: &View, cx: &mut ViewContext) { + let image_item = image_view.read(cx).image_item.read(cx); + + self.width = image_item.width; + self.height = image_item.height; + self.file_size = image_item.file_size; + self.color_type = image_item.color_type.map(String::from); + + cx.notify(); + } + + fn format_file_size(size: u64) -> String { + if size < 1024 { + format!("{}B", size) + } else if size < 1024 * 1024 { + format!("{:.1}KB", size as f64 / 1024.0) + } else { + format!("{:.1}MB", size as f64 / (1024.0 * 1024.0)) + } } } -impl Render for ImageInfoView { +impl Render for ImageInfo { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - if self.width.is_some() - || self.height.is_some() - || self.file_size.is_some() - || self.color_type.is_some() - { - div().flex().items_center().gap_2().text_xs().child(format!( - "Whole image {} × {} {} {}", - self.width.map_or("--".to_string(), |w| w.to_string()), - self.height.map_or("--".to_string(), |h| h.to_string()), - self.format_file_size(), - self.color_type.as_deref().unwrap_or("") - )) - } else { - div() + let mut text = String::new(); + + if let (Some(width), Some(height)) = (self.width, self.height) { + text.push_str(&format!("{}×{}", width, height)); + } + + if let Some(size) = self.file_size { + if !text.is_empty() { + text.push_str(" • "); + } + text.push_str(&Self::format_file_size(size)); + } + + if let Some(color_type) = &self.color_type { + if !text.is_empty() { + text.push_str(" • "); + } + text.push_str(color_type); } + + div().when(!text.is_empty(), |el| { + el.child(Button::new("image-metadata", text).label_size(LabelSize::Small)) + }) } } -impl StatusItemView for ImageInfoView { +impl StatusItemView for ImageInfo { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { - self.width = None; - self.height = None; - self.file_size = None; - self.color_type = None; - - if let Some(item) = active_pane_item { - if let Some(image_model) = item.downcast::>() { - let image_item = image_model.read(cx); - - self.width = image_item.read(cx).width; - self.height = image_item.read(cx).height; - self.file_size = image_item.read(cx).file_size; - self.color_type = image_item.read(cx).color_type; - } + if let Some(image_view) = active_pane_item.and_then(|item| item.act_as::(cx)) { + self.update_metadata(&image_view, cx); + self._observe_active_image = Some(cx.observe(&image_view, |this, view, cx| { + this.update_metadata(&view, cx); + })); + } else { + self.width = None; + self.height = None; + self.file_size = None; + self.color_type = None; + self._observe_active_image = None; } - cx.notify(); } } diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index ea5a5b3df37f25..3a7a1d7b0552a3 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -4,7 +4,6 @@ use crate::{ }; use anyhow::{Context as _, Result}; use collections::{hash_map, HashMap, HashSet}; -use fs::Fs; use futures::{channel::oneshot, StreamExt}; use gpui::{ hash, prelude::*, AppContext, EventEmitter, Img, Model, ModelContext, Subscription, Task, @@ -76,11 +75,11 @@ fn image_color_type_description(color_type: ColorType) -> &'static str { } impl ImageItem { - async fn image_info( + async fn image_info( image: &ImageItem, project: &Project, cx: &AppContext, - fs: &F, + fs: Arc, ) -> Result<(u32, u32, u64, &'static str), String> { let worktree = project .worktree_for_id(image.project_path(cx).worktree_id, cx) @@ -112,15 +111,6 @@ impl ImageItem { Ok((dimensions.0, dimensions.1, file_size, img_color_type)) } - pub async fn try_open( - &self, - project: &Project, - cx: &AppContext, - fs: &F, - ) -> Result<(u32, u32, u64, &'static str), String> { - Self::image_info(self, project, cx, fs).await - } - pub fn project_path(&self, cx: &AppContext) -> ProjectPath { ProjectPath { worktree_id: self.file.worktree_id(cx), @@ -132,22 +122,20 @@ impl ImageItem { self.file.path() } - pub async fn load_metadata( + pub async fn load_metadata( &mut self, project: &Project, cx: &AppContext, - fs: &F, - ) -> Result<(), String> { - println!("Loading metadata for image: {:?}", self.path()); - - match Self::try_open(self, project, cx, fs).await { + fs: Arc, + ) -> Result<(u32, u32, u64, &'static str), String> { + match Self::image_info(self, project, cx, fs).await { Ok(metadata) => { self.width = Some(metadata.0); self.height = Some(metadata.1); self.file_size = Some(metadata.2); self.color_type = Some(metadata.3); - println!("Metadata loaded: {:?}", metadata); - Ok(()) + + Ok(metadata) } Err(err) => { println!("Failed to load metadata: {}", err); @@ -212,7 +200,6 @@ impl ProjectItem for ImageItem { ) -> Option>>> { let path = path.clone(); let project = project.clone(); - let ext = path .path .extension() @@ -225,9 +212,31 @@ impl ProjectItem for ImageItem { // 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 + let image_model = project .update(&mut cx, |project, cx| project.open_image(path, cx))? - .await + .await?; + + let fs = Arc::new(fs::RealFs::default()); + let project_clone = project.clone(); + + if let Ok(()) = image_model.update(&mut cx, |image, cx| { + let project_ref = project_clone.read(cx); + + if let Ok(metadata) = futures::executor::block_on(image.load_metadata( + &project_ref, + cx, + fs.clone(), + )) { + image.width = Some(metadata.0); + image.height = Some(metadata.1); + image.file_size = Some(metadata.2); + image.color_type = Some(metadata.3); + } + }) { + // image metadata should be avialable now + } + + Ok(image_model) })) } else { None diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a4145caf4873bb..55c98d9c39a457 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -26,7 +26,7 @@ use gpui::{ PathPromptOptions, PromptLevel, ReadGlobal, Task, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, }; -use image_viewer::image_info; +use image_viewer::image_info::ImageInfo; pub use open_listener::*; use outline_panel::OutlinePanel; use paths::{local_settings_file_relative_path, local_tasks_file_relative_path}; @@ -212,7 +212,7 @@ pub fn initialize_workspace( let vim_mode_indicator = cx.new_view(vim::ModeIndicator::new); let cursor_position = cx.new_view(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); - let image_metadata = cx.new_view(|_| image_info::ImageInfoView::new(workspace)); + let image_metadata = cx.new_view(|_| ImageInfo::new(workspace)); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); From 5511e2fb5568ad6ccce6562781d3016883bbc5fb Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Tue, 31 Dec 2024 13:54:32 +0100 Subject: [PATCH 05/17] feat: allow users to chose their preferred units of measurements for image sizes --- assets/settings/default.json | 4 +++ crates/image_viewer/src/image_info.rs | 38 ++++++++++++++++++++------- crates/settings/src/settings.rs | 6 +++-- crates/settings/src/settings_store.rs | 24 ++++++++++++++++- crates/zed/src/zed.rs | 2 +- 5 files changed, 60 insertions(+), 14 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index dd9098e0c038c6..1ad02883621f7d 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -92,6 +92,10 @@ // workspace when the centered layout is used. "right_padding": 0.2 }, + // The unit type for image file sizes. + // By default we're setting it to binary. + // The second option is decimal + "image_file_unit_type": "binary", // The key to use for adding multiple cursors // Currently "alt" or "cmd_or_ctrl" (also aliased as // "cmd" and "ctrl") are supported. diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index a749ab498eba0b..95d2cee03e1ac6 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -1,5 +1,6 @@ use crate::ImageView; -use gpui::{div, IntoElement, ParentElement, Render, Subscription, View, ViewContext}; +use gpui::{div, AppContext, IntoElement, ParentElement, Render, Subscription, View, ViewContext}; +use settings::{ImageFileSizeUnitType, Settings}; use ui::{prelude::*, Button, LabelSize}; use workspace::{ItemHandle, StatusItemView, Workspace}; @@ -9,16 +10,20 @@ pub struct ImageInfo { file_size: Option, color_type: Option, _observe_active_image: Option, + image_unit_type: ImageFileSizeUnitType, } impl ImageInfo { - pub fn new(_workspace: &Workspace) -> Self { + pub fn new(_workspace: &Workspace, cx: &mut AppContext) -> Self { + let unit_type = ImageFileSizeUnitType::get_global(cx); + Self { width: None, height: None, file_size: None, color_type: None, _observe_active_image: None, + image_unit_type: unit_type.clone(), } } @@ -33,13 +38,26 @@ impl ImageInfo { cx.notify(); } - fn format_file_size(size: u64) -> String { - if size < 1024 { - format!("{}B", size) - } else if size < 1024 * 1024 { - format!("{:.1}KB", size as f64 / 1024.0) - } else { - format!("{:.1}MB", size as f64 / (1024.0 * 1024.0)) + fn format_file_size(&self, size: u64) -> String { + match self.image_unit_type { + ImageFileSizeUnitType::Binary => { + if size < 1024 { + format!("{}B", size) + } else if size < 1024 * 1024 { + format!("{:.1}KB", size as f64 / 1024.0) + } else { + format!("{:.1}MB", size as f64 / (1024.0 * 1024.0)) + } + } + ImageFileSizeUnitType::Decimal => { + if size < 1000 { + format!("{}B", size) + } else if size < 1000 * 1000 { + format!("{:.1}KB", size as f64 / 1000.0) + } else { + format!("{:.1}MB", size as f64 / (1000.0 * 1000.0)) + } + } } } } @@ -56,7 +74,7 @@ impl Render for ImageInfo { if !text.is_empty() { text.push_str(" • "); } - text.push_str(&Self::format_file_size(size)); + text.push_str(&Self::format_file_size(self, size)); } if let Some(color_type) = &self.color_type { diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index de40cb7b2d3969..3c20e53b65ba56 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -16,8 +16,8 @@ pub use key_equivalents::*; pub use keymap_file::KeymapFile; pub use settings_file::*; pub use settings_store::{ - parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, - SettingsSources, SettingsStore, + parse_json_with_comments, ImageFileSizeUnitType, InvalidSettingsError, + LocalSettingsKind, Settings, SettingsLocation, SettingsSources, SettingsStore, }; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] @@ -66,6 +66,8 @@ pub fn init(cx: &mut AppContext) { .set_default_settings(&default_settings(), cx) .unwrap(); cx.set_global(settings); + + ImageFileSizeUnitType::register(cx); } pub fn default_settings() -> Cow<'static, str> { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 2e867a21280e18..ae14be9ce5b73f 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -6,7 +6,7 @@ use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal}; use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME}; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; -use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use smallvec::SmallVec; use std::{ any::{type_name, Any, TypeId}, @@ -238,6 +238,28 @@ trait AnySettingValue: 'static + Send + Sync { ) -> RootSchema; } +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ImageFileSizeUnitType { + Binary, + Decimal, +} + +impl Settings for ImageFileSizeUnitType { + const KEY: Option<&'static str> = Some("image_file_unit_type"); + type FileContent = Self; + + fn load(sources: SettingsSources, _: &mut AppContext) -> Result { + sources.json_merge().or_else(|_| Ok(Self::Binary)) + } +} + +impl Default for ImageFileSizeUnitType { + fn default() -> Self { + ImageFileSizeUnitType::Binary + } +} + struct DeserializedSetting(Box); impl SettingsStore { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 55c98d9c39a457..604662f0e34255 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -212,7 +212,7 @@ pub fn initialize_workspace( let vim_mode_indicator = cx.new_view(vim::ModeIndicator::new); let cursor_position = cx.new_view(|_| go_to_line::cursor_position::CursorPosition::new(workspace)); - let image_metadata = cx.new_view(|_| ImageInfo::new(workspace)); + let image_metadata = cx.new_view(|cx| ImageInfo::new(workspace, cx)); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic_summary, cx); From 1a6229f7cb2c465a4ec2f49b309ec9b894ed4cbe Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Fri, 3 Jan 2025 19:57:02 +0100 Subject: [PATCH 06/17] refactor - create a standalaone type (ImageItemMeta) to hold image metadata - add smol for async I/O actions when retrieving image properties - move ImageFileSizeUnitType into image_info.rs - used a call_once method from std::sync to register file size unit setting --- Cargo.lock | 3 ++ crates/image_viewer/Cargo.toml | 3 ++ crates/image_viewer/src/image_info.rs | 63 ++++++++++++++++++++----- crates/image_viewer/src/image_viewer.rs | 1 - crates/project/src/image_store.rs | 62 ++++++++++++++---------- crates/settings/src/settings.rs | 4 +- crates/settings/src/settings_store.rs | 22 --------- 7 files changed, 93 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cefd40e822f0c6..86fc3a354ed9cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6198,6 +6198,9 @@ dependencies = [ "gpui", "image", "project", + "schemars", + "serde", + "serde_derive", "settings", "theme", "ui", diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index 8f578a868c66de..06732c9c96e6a3 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -26,3 +26,6 @@ util.workspace = true image.workspace = true workspace.workspace = true fs.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 95d2cee03e1ac6..c753e025917b92 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -1,21 +1,52 @@ use crate::ImageView; +use anyhow; use gpui::{div, AppContext, IntoElement, ParentElement, Render, Subscription, View, ViewContext}; -use settings::{ImageFileSizeUnitType, Settings}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources}; use ui::{prelude::*, Button, LabelSize}; use workspace::{ItemHandle, StatusItemView, Workspace}; +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ImageFileSizeUnitType { + Binary, + Decimal, +} + +impl Settings for ImageFileSizeUnitType { + const KEY: Option<&'static str> = Some("image_file_unit_type"); + + type FileContent = Self; + + fn load( + sources: SettingsSources, + _: &mut AppContext, + ) -> Result { + sources.json_merge().or_else(|_| Ok(Self::Binary)) + } +} + +impl Default for ImageFileSizeUnitType { + fn default() -> Self { + ImageFileSizeUnitType::Binary + } +} + pub struct ImageInfo { width: Option, height: Option, file_size: Option, color_type: Option, _observe_active_image: Option, - image_unit_type: ImageFileSizeUnitType, } impl ImageInfo { pub fn new(_workspace: &Workspace, cx: &mut AppContext) -> Self { - let unit_type = ImageFileSizeUnitType::get_global(cx); + static INIT: std::sync::Once = std::sync::Once::new(); + INIT.call_once(|| { + ImageFileSizeUnitType::register(cx); + }); Self { width: None, @@ -23,23 +54,28 @@ impl ImageInfo { file_size: None, color_type: None, _observe_active_image: None, - image_unit_type: unit_type.clone(), } } fn update_metadata(&mut self, image_view: &View, cx: &mut ViewContext) { let image_item = image_view.read(cx).image_item.read(cx); - self.width = image_item.width; - self.height = image_item.height; - self.file_size = image_item.file_size; - self.color_type = image_item.color_type.map(String::from); - + if let Some(meta) = &image_item.image_meta { + self.width = Some(meta.width); + self.height = Some(meta.height); + self.file_size = Some(meta.file_size); + self.color_type = Some(meta.color_type.to_string()); + } else { + self.width = None; + self.height = None; + self.file_size = None; + self.color_type = None; + } cx.notify(); } - fn format_file_size(&self, size: u64) -> String { - match self.image_unit_type { + fn format_file_size(&self, size: u64, image_unit_type: &ImageFileSizeUnitType) -> String { + match image_unit_type { ImageFileSizeUnitType::Binary => { if size < 1024 { format!("{}B", size) @@ -63,8 +99,9 @@ impl ImageInfo { } impl Render for ImageInfo { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let mut text = String::new(); + let unit_type = ImageFileSizeUnitType::get_global(cx); if let (Some(width), Some(height)) = (self.width, self.height) { text.push_str(&format!("{}×{}", width, height)); @@ -74,7 +111,7 @@ impl Render for ImageInfo { if !text.is_empty() { text.push_str(" • "); } - text.push_str(&Self::format_file_size(self, size)); + text.push_str(&Self::format_file_size(self, size, unit_type)); } if let Some(color_type) = &self.color_type { diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 22d3b9353adc01..34285017ef1d1b 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -1,7 +1,6 @@ pub mod image_info; use std::path::PathBuf; - use anyhow::Context as _; use editor::items::entry_git_aware_label_color; use gpui::{ diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 48106ce7f45d13..636e9ee643e0b9 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -48,15 +48,20 @@ pub enum ImageStoreEvent { impl EventEmitter for ImageStore {} +#[derive(Clone)] +pub struct ImageItemMeta { + pub width: u32, + pub height: u32, + pub file_size: u64, + pub color_type: &'static str, +} + pub struct ImageItem { pub id: ImageId, pub file: Arc, pub image: Arc, reload_task: Option>, - pub width: Option, - pub height: Option, - pub file_size: Option, - pub color_type: Option<&'static str>, + pub image_meta: Option, } fn image_color_type_description(color_type: ColorType) -> &'static str { @@ -80,35 +85,46 @@ impl ImageItem { project: &Project, cx: &AppContext, fs: Arc, - ) -> Result<(u32, u32, u64, &'static str), String> { + ) -> Result { let worktree = project .worktree_for_id(image.project_path(cx).worktree_id, cx) .ok_or_else(|| "Could not find worktree for image".to_string())?; let worktree_root = worktree.read(cx).abs_path(); - let path = if image.path().is_absolute() { image.path().to_path_buf() } else { worktree_root.join(image.path()) }; - if !path.exists() { return Err(format!("File does not exist at path: {:?}", path)); } + let path_clone = path.to_path_buf(); + let img = smol::unblock(move || image::open(&path_clone)) + .await + .map_err(|e| format!("Failed to open image: {}", e))?; - let img = image::open(&path).map_err(|e| format!("Failed to open image: {}", e))?; - let dimensions = img.dimensions(); - let img_color_type = image_color_type_description(img.color()); + let (width, height, color_type) = + smol::unblock(move || -> Result<(u32, u32, &'static str), String> { + let dimensions = img.dimensions(); + let img_color_type = image_color_type_description(img.color()); + Ok((dimensions.0, dimensions.1, img_color_type)) + }) + .await + .map_err(|e| format!("Failed to process image: {}", e))?; let file_metadata = fs .metadata(path.as_path()) .await .map_err(|e| format!("Cannot access image data: {}", e))? .ok_or_else(|| "No metadata found".to_string())?; - let file_size = file_metadata.len; - Ok((dimensions.0, dimensions.1, file_size, img_color_type)) + Ok(ImageItemMeta { + width, + height, + file_size, + color_type, + }) } pub fn project_path(&self, cx: &AppContext) -> ProjectPath { @@ -127,14 +143,10 @@ impl ImageItem { project: &Project, cx: &AppContext, fs: Arc, - ) -> Result<(u32, u32, u64, &'static str), String> { + ) -> Result { match Self::image_info(self, project, cx, fs).await { Ok(metadata) => { - self.width = Some(metadata.0); - self.height = Some(metadata.1); - self.file_size = Some(metadata.2); - self.color_type = Some(metadata.3); - + self.image_meta = Some(metadata.clone()); Ok(metadata) } Err(err) => { @@ -236,10 +248,7 @@ impl ProjectItem for ImageItem { cx, fs.clone(), )) { - image.width = Some(metadata.0); - image.height = Some(metadata.1); - image.file_size = Some(metadata.2); - image.color_type = Some(metadata.3); + image.image_meta = Some(metadata) } }) { // image metadata should be avialable now @@ -496,10 +505,11 @@ impl ImageStoreImpl for Model { id: cx.entity_id().as_non_zero_u64().into(), file: file.clone(), image, - width: None, - file_size: None, - height: None, - color_type: None, + // width: None, + // file_size: None, + // height: None, + // color_type: None, + image_meta: None, reload_task: None, })?; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 3c20e53b65ba56..6c7b54dc710572 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -16,7 +16,7 @@ pub use key_equivalents::*; pub use keymap_file::KeymapFile; pub use settings_file::*; pub use settings_store::{ - parse_json_with_comments, ImageFileSizeUnitType, InvalidSettingsError, + parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, SettingsStore, }; @@ -66,8 +66,6 @@ pub fn init(cx: &mut AppContext) { .set_default_settings(&default_settings(), cx) .unwrap(); cx.set_global(settings); - - ImageFileSizeUnitType::register(cx); } pub fn default_settings() -> Cow<'static, str> { diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index ae14be9ce5b73f..04333ded7b3f17 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -238,28 +238,6 @@ trait AnySettingValue: 'static + Send + Sync { ) -> RootSchema; } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum ImageFileSizeUnitType { - Binary, - Decimal, -} - -impl Settings for ImageFileSizeUnitType { - const KEY: Option<&'static str> = Some("image_file_unit_type"); - type FileContent = Self; - - fn load(sources: SettingsSources, _: &mut AppContext) -> Result { - sources.json_merge().or_else(|_| Ok(Self::Binary)) - } -} - -impl Default for ImageFileSizeUnitType { - fn default() -> Self { - ImageFileSizeUnitType::Binary - } -} - struct DeserializedSetting(Box); impl SettingsStore { From ad8627e2d59555c7e21f826445981a5ea36db493 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Wed, 8 Jan 2025 12:37:56 +0100 Subject: [PATCH 07/17] get fs from project instead of creating a new pointer --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- crates/project/src/image_store.rs | 15 ++++++--------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9b8608dab2832..12878e71aca7eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5875,7 +5875,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -9926,7 +9926,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes 1.9.0", "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.12.1", "log", "multimap 0.10.0", "once_cell", @@ -9959,7 +9959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.90", diff --git a/Cargo.toml b/Cargo.toml index 5c0dee14d1483a..1714da3272b47a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -391,7 +391,7 @@ hyper = "0.14" http = "1.1" ignore = "0.4.22" image = "0.25.5" -indexmap = { version = "1.6.2", features = ["serde"] } +indexmap = { version = "2.7.0", features = ["serde"] } indoc = "2" itertools = "0.13.0" jsonwebtoken = "9.3" diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 636e9ee643e0b9..5e66b66329e315 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -84,7 +84,6 @@ impl ImageItem { image: &ImageItem, project: &Project, cx: &AppContext, - fs: Arc, ) -> Result { let worktree = project .worktree_for_id(image.project_path(cx).worktree_id, cx) @@ -112,6 +111,8 @@ impl ImageItem { .await .map_err(|e| format!("Failed to process image: {}", e))?; + let fs = project.fs(); + let file_metadata = fs .metadata(path.as_path()) .await @@ -142,9 +143,8 @@ impl ImageItem { &mut self, project: &Project, cx: &AppContext, - fs: Arc, ) -> Result { - match Self::image_info(self, project, cx, fs).await { + match Self::image_info(self, project, cx).await { Ok(metadata) => { self.image_meta = Some(metadata.clone()); Ok(metadata) @@ -237,17 +237,14 @@ impl ProjectItem for ImageItem { .update(&mut cx, |project, cx| project.open_image(path, cx))? .await?; - let fs = Arc::new(fs::RealFs::default()); let project_clone = project.clone(); if let Ok(()) = image_model.update(&mut cx, |image, cx| { let project_ref = project_clone.read(cx); - if let Ok(metadata) = futures::executor::block_on(image.load_metadata( - &project_ref, - cx, - fs.clone(), - )) { + if let Ok(metadata) = + futures::executor::block_on(image.load_metadata(&project_ref, cx)) + { image.image_meta = Some(metadata) } }) { From 00f076ba7522e68b08dd07d10ee5f1d107fc093f Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Sat, 11 Jan 2025 18:16:39 +0100 Subject: [PATCH 08/17] update cargo lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 12878e71aca7eb..477bef2a6015d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5875,7 +5875,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.4.10", "tokio", "tower-service", "tracing", From 120732fba9e2019178cd91043f89815aa72facb5 Mon Sep 17 00:00:00 2001 From: tims <0xtimsb@gmail.com> Date: Wed, 15 Jan 2025 14:09:10 +0530 Subject: [PATCH 09/17] fix block on --- crates/project/src/image_store.rs | 114 ++++++++++++++---------------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 5e66b66329e315..cc23dce584367b 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -6,8 +6,8 @@ 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, + hash, prelude::*, AppContext, AsyncAppContext, EventEmitter, Img, Model, ModelContext, + Subscription, Task, WeakModel, }; use image::{ColorType, GenericImageView}; use language::{DiskState, File}; @@ -81,43 +81,64 @@ fn image_color_type_description(color_type: ColorType) -> &'static str { impl ImageItem { async fn image_info( - image: &ImageItem, - project: &Project, - cx: &AppContext, - ) -> Result { - let worktree = project - .worktree_for_id(image.project_path(cx).worktree_id, cx) - .ok_or_else(|| "Could not find worktree for image".to_string())?; - let worktree_root = worktree.read(cx).abs_path(); - let path = if image.path().is_absolute() { - image.path().to_path_buf() + image: Model, + project: Model, + cx: &mut AsyncAppContext, + ) -> Result { + let project_path = cx + .update(|cx| image.read(cx).project_path(cx)) + .context("Failed to get project path")?; + + let worktree = cx + .update(|cx| { + project + .read(cx) + .worktree_for_id(project_path.worktree_id, cx) + }) + .context("Failed to get worktree")? + .ok_or_else(|| anyhow::anyhow!("Worktree not found"))?; + + let worktree_root = cx + .update(|cx| worktree.read(cx).abs_path()) + .context("Failed to get worktree root path")?; + + let image_path = cx + .update(|cx| image.read(cx).path().clone()) + .context("Failed to get image path")?; + + let path = if image_path.is_absolute() { + image_path.to_path_buf() } else { - worktree_root.join(image.path()) + worktree_root.join(image_path) }; + if !path.exists() { - return Err(format!("File does not exist at path: {:?}", path)); + anyhow::bail!("File does not exist at path: {:?}", path); } - let path_clone = path.to_path_buf(); - let img = smol::unblock(move || image::open(&path_clone)) - .await - .map_err(|e| format!("Failed to open image: {}", e))?; - let (width, height, color_type) = - smol::unblock(move || -> Result<(u32, u32, &'static str), String> { - let dimensions = img.dimensions(); - let img_color_type = image_color_type_description(img.color()); - Ok((dimensions.0, dimensions.1, img_color_type)) - }) - .await - .map_err(|e| format!("Failed to process image: {}", e))?; + let path_clone = path.clone(); + let image_result = + smol::unblock(move || image::open(&path_clone).context("Failed to open image")).await?; + + let img = image_result; + let dimensions_result = smol::unblock(move || { + let dimensions = img.dimensions(); + let img_color_type = image_color_type_description(img.color()); + Ok::<_, anyhow::Error>((dimensions.0, dimensions.1, img_color_type)) + }) + .await?; - let fs = project.fs(); + let (width, height, color_type) = dimensions_result; + + let fs = project + .update(cx, move |project, _| project.fs().clone()) + .context("Failed to get filesystem")?; let file_metadata = fs .metadata(path.as_path()) .await - .map_err(|e| format!("Cannot access image data: {}", e))? - .ok_or_else(|| "No metadata found".to_string())?; + .context("Failed to access image data")? + .ok_or_else(|| anyhow::anyhow!("No metadata found"))?; let file_size = file_metadata.len; Ok(ImageItemMeta { @@ -139,23 +160,6 @@ impl ImageItem { self.file.path() } - pub async fn load_metadata( - &mut self, - project: &Project, - cx: &AppContext, - ) -> Result { - match Self::image_info(self, project, cx).await { - Ok(metadata) => { - self.image_meta = Some(metadata.clone()); - Ok(metadata) - } - Err(err) => { - println!("Failed to load metadata: {}", err); - Err(err) - } - } - } - fn file_updated(&mut self, new_file: Arc, cx: &mut ModelContext) { let mut file_changed = false; @@ -236,20 +240,12 @@ impl ProjectItem for ImageItem { let image_model = project .update(&mut cx, |project, cx| project.open_image(path, cx))? .await?; + let image_metadata = + Self::image_info(image_model.clone(), project, &mut cx).await?; - let project_clone = project.clone(); - - if let Ok(()) = image_model.update(&mut cx, |image, cx| { - let project_ref = project_clone.read(cx); - - if let Ok(metadata) = - futures::executor::block_on(image.load_metadata(&project_ref, cx)) - { - image.image_meta = Some(metadata) - } - }) { - // image metadata should be avialable now - } + image_model.update(&mut cx, |image_model, _| { + image_model.image_meta = Some(image_metadata); + })?; Ok(image_model) })) From 6a2d7b21d993759b97ebdb34f31e707ae75926c3 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Tue, 21 Jan 2025 18:11:57 +0100 Subject: [PATCH 10/17] fix issue with generic color type mismatch in color_type_description --- crates/project/src/image_store.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index cc23dce584367b..3802eb3cc64b42 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -68,10 +68,10 @@ fn image_color_type_description(color_type: ColorType) -> &'static str { match color_type { ColorType::L8 => "Grayscale (8-bit)", ColorType::La8 => "Grayscale with Alpha (8-bit)", - ColorType::Rgba8 => "PNG (32-bit color)", + ColorType::Rgba8 => "RGBA (32-bit color)", ColorType::Rgb8 => "RGB (24-bit color)", ColorType::Rgb16 => "RGB (48-bit color)", - ColorType::Rgba16 => "PNG (64-bit color)", + ColorType::Rgba16 => "RGBA (64-bit color)", ColorType::L16 => "Grayscale (16-bit)", ColorType::La16 => "Grayscale with Alpha (16-bit)", From abdc7362226cd040560417f6ea5da2323f81e946 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Wed, 22 Jan 2025 03:10:21 +0100 Subject: [PATCH 11/17] chore: consider image's appropriate bit depth/bit per pixel for the color type instead of the former where i used ColorType in the pattern matching block to infer wrong values, this commit fixes that by using the ExtendedColorType for each image color type description considering the bit depth and channels. it also includes a new property, format to annotate each image format, instead of relying on the file extensions, which may not be correct all the time. --- crates/image_viewer/src/image_info.rs | 37 ++++++------ crates/project/src/image_store.rs | 82 +++++++++++++++++++-------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index c753e025917b92..3eb57640c54e30 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -37,6 +37,7 @@ pub struct ImageInfo { width: Option, height: Option, file_size: Option, + format: Option, color_type: Option, _observe_active_image: Option, } @@ -52,6 +53,7 @@ impl ImageInfo { width: None, height: None, file_size: None, + format: None, color_type: None, _observe_active_image: None, } @@ -64,12 +66,14 @@ impl ImageInfo { self.width = Some(meta.width); self.height = Some(meta.height); self.file_size = Some(meta.file_size); + self.format = Some(meta.format.clone()); self.color_type = Some(meta.color_type.to_string()); } else { self.width = None; self.height = None; self.file_size = None; - self.color_type = None; + self.format = None; + self.color_type = None } cx.notify(); } @@ -100,26 +104,21 @@ impl ImageInfo { impl Render for ImageInfo { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let mut text = String::new(); let unit_type = ImageFileSizeUnitType::get_global(cx); - if let (Some(width), Some(height)) = (self.width, self.height) { - text.push_str(&format!("{}×{}", width, height)); - } - - if let Some(size) = self.file_size { - if !text.is_empty() { - text.push_str(" • "); - } - text.push_str(&Self::format_file_size(self, size, unit_type)); - } - - if let Some(color_type) = &self.color_type { - if !text.is_empty() { - text.push_str(" • "); - } - text.push_str(color_type); - } + let components = [ + self.width + .and_then(|w| self.height.map(|h| format!("{}x{}", w, h))), + self.file_size.map(|s| self.format_file_size(s, unit_type)), + self.color_type.clone(), + self.format.clone(), + ]; + + let text = components + .into_iter() + .flatten() + .collect::>() + .join(" • "); div().when(!text.is_empty(), |el| { el.child(Button::new("image-metadata", text).label_size(LabelSize::Small)) diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 3802eb3cc64b42..b918de619c56c5 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -9,7 +9,7 @@ use gpui::{ hash, prelude::*, AppContext, AsyncAppContext, EventEmitter, Img, Model, ModelContext, Subscription, Task, WeakModel, }; -use image::{ColorType, GenericImageView}; +use image::{ExtendedColorType, GenericImageView, ImageFormat, ImageReader}; use language::{DiskState, File}; use rpc::{AnyProtoClient, ErrorExt as _}; use std::ffi::OsStr; @@ -53,7 +53,8 @@ pub struct ImageItemMeta { pub width: u32, pub height: u32, pub file_size: u64, - pub color_type: &'static str, + pub color_type: String, + pub format: String, } pub struct ImageItem { @@ -64,18 +65,29 @@ pub struct ImageItem { pub image_meta: Option, } -fn image_color_type_description(color_type: ColorType) -> &'static str { - match color_type { - ColorType::L8 => "Grayscale (8-bit)", - ColorType::La8 => "Grayscale with Alpha (8-bit)", - ColorType::Rgba8 => "RGBA (32-bit color)", - ColorType::Rgb8 => "RGB (24-bit color)", - ColorType::Rgb16 => "RGB (48-bit color)", - ColorType::Rgba16 => "RGBA (64-bit color)", - ColorType::L16 => "Grayscale (16-bit)", - ColorType::La16 => "Grayscale with Alpha (16-bit)", - - _ => "unknown color type", +fn image_color_type_description(color_type: ExtendedColorType) -> String { + let (channels, bits_per_channel) = match color_type { + 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), + + _ => (0, 0), + }; + + if channels == 0 { + "unknown color type".to_string() + } else { + let bits_per_pixel = channels * bits_per_channel; + format!("{} channels, {} bits per pixel", channels, bits_per_pixel) } } @@ -116,23 +128,46 @@ impl ImageItem { anyhow::bail!("File does not exist at path: {:?}", path); } + let fs = project + .update(cx, |project, _| project.fs().clone()) + .context("Failed to get filesystem")?; + + let img_bytes = fs + .load_bytes(&path) + .await + .context("Could not load image bytes")?; + let img_format = image::guess_format(&img_bytes).context("Could not guess image format")?; + + let img_format_str = match img_format { + ImageFormat::Png => "PNG", + ImageFormat::Jpeg => "JPEG", + ImageFormat::Gif => "GIF", + ImageFormat::WebP => "WebP", + ImageFormat::Tiff => "TIFF", + ImageFormat::Bmp => "BMP", + ImageFormat::Ico => "ICO", + ImageFormat::Avif => "Avif", + + _ => "Unknown", + }; + let path_clone = path.clone(); - let image_result = - smol::unblock(move || image::open(&path_clone).context("Failed to open image")).await?; + let image_result = smol::unblock(move || ImageReader::open(&path_clone)?.decode()).await?; let img = image_result; let dimensions_result = smol::unblock(move || { let dimensions = img.dimensions(); - let img_color_type = image_color_type_description(img.color()); - Ok::<_, anyhow::Error>((dimensions.0, dimensions.1, img_color_type)) + let img_color_type = image_color_type_description(img.color().into()); + Ok::<_, anyhow::Error>(( + dimensions.0, + dimensions.1, + img_format_str.to_string(), + img_color_type, + )) }) .await?; - let (width, height, color_type) = dimensions_result; - - let fs = project - .update(cx, move |project, _| project.fs().clone()) - .context("Failed to get filesystem")?; + let (width, height, format, color_type) = dimensions_result; let file_metadata = fs .metadata(path.as_path()) @@ -145,6 +180,7 @@ impl ImageItem { width, height, file_size, + format, color_type, }) } From 0f96d67daed219087363994ef7e4274f64c41050 Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Wed, 22 Jan 2025 23:30:50 +0100 Subject: [PATCH 12/17] chore: update UnitFileSizeType enum to derive Binary as default instead of doing a manual impl --- crates/image_viewer/src/image_info.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 3eb57640c54e30..9366510fde1db1 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -7,9 +7,10 @@ use settings::{Settings, SettingsSources}; use ui::{prelude::*, Button, LabelSize}; use workspace::{ItemHandle, StatusItemView, Workspace}; -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)] #[serde(rename_all = "snake_case")] pub enum ImageFileSizeUnitType { + #[default] Binary, Decimal, } @@ -27,11 +28,6 @@ impl Settings for ImageFileSizeUnitType { } } -impl Default for ImageFileSizeUnitType { - fn default() -> Self { - ImageFileSizeUnitType::Binary - } -} pub struct ImageInfo { width: Option, From 60367b3ff83cf55fd63222886a3c01655721bd2d Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Thu, 23 Jan 2025 08:11:22 +0100 Subject: [PATCH 13/17] cargo-machete don't complain about dependencies in use --- crates/image_viewer/Cargo.toml | 3 +++ crates/image_viewer/src/image_info.rs | 1 - crates/image_viewer/src/image_viewer.rs | 2 +- crates/settings/src/settings.rs | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/image_viewer/Cargo.toml b/crates/image_viewer/Cargo.toml index e146c1b2116641..e726af91cf53bf 100644 --- a/crates/image_viewer/Cargo.toml +++ b/crates/image_viewer/Cargo.toml @@ -32,3 +32,6 @@ serde_derive.workspace = true [features] test-support = ["gpui/test-support"] + +[package.metadata.cargo-machete] +ignored = ["fs", "serde_derive", "image"] diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 9366510fde1db1..685cd85a8e0db2 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -28,7 +28,6 @@ impl Settings for ImageFileSizeUnitType { } } - pub struct ImageInfo { width: Option, height: Option, diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 9dfc801baec709..b6c94ef896efb2 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -1,6 +1,5 @@ pub mod image_info; -use std::path::PathBuf; use anyhow::Context as _; use editor::items::entry_git_aware_label_color; use gpui::{ @@ -9,6 +8,7 @@ use gpui::{ Render, Styled, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use persistence::IMAGE_VIEWER; +use std::path::PathBuf; use theme::Theme; use ui::prelude::*; diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 6c7b54dc710572..de40cb7b2d3969 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -16,8 +16,8 @@ pub use key_equivalents::*; pub use keymap_file::KeymapFile; pub use settings_file::*; pub use settings_store::{ - parse_json_with_comments, InvalidSettingsError, - LocalSettingsKind, Settings, SettingsLocation, SettingsSources, SettingsStore, + parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, + SettingsSources, SettingsStore, }; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] From 06e065e73135c6477fc56b1f051b1a39527b9d7a Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Thu, 23 Jan 2025 14:40:59 +0100 Subject: [PATCH 14/17] remove duplicate imports --- crates/image_viewer/src/image_viewer.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 2a60f0c3199dc5..5648e9e203af5a 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -15,8 +15,6 @@ use ui::prelude::*; use project::{image_store::ImageItemEvent, ImageItem, Project, ProjectPath}; use settings::Settings; -use theme::Theme; -use ui::prelude::*; use util::paths::PathExt; use workspace::{ item::{BreadcrumbText, Item, ProjectItem, SerializableItem, TabContentParams}, From e12e000ac704d69f38a90a6175e143c4bbd95bad Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Thu, 23 Jan 2025 16:25:32 +0100 Subject: [PATCH 15/17] fix: image format should not persist when there's no image view --- crates/image_viewer/src/image_info.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 685cd85a8e0db2..a341a17d2ba845 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -137,6 +137,7 @@ impl StatusItemView for ImageInfo { self.height = None; self.file_size = None; self.color_type = None; + self.format = None; self._observe_active_image = None; } cx.notify(); From af38049556f38914e951c03fe8af0ca79c8c327c Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Thu, 23 Jan 2025 21:37:14 +0100 Subject: [PATCH 16/17] fix: load image metadata in image view when it renders for the first time --- crates/image_viewer/src/image_info.rs | 5 +++-- crates/image_viewer/src/image_viewer.rs | 22 ++++++++++++++++++++++ crates/project/src/image_store.rs | 5 +++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index a341a17d2ba845..9c538c6c67d568 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -127,6 +127,8 @@ impl StatusItemView for ImageInfo { active_pane_item: Option<&dyn ItemHandle>, cx: &mut ViewContext, ) { + self._observe_active_image = None; + if let Some(image_view) = active_pane_item.and_then(|item| item.act_as::(cx)) { self.update_metadata(&image_view, cx); self._observe_active_image = Some(cx.observe(&image_view, |this, view, cx| { @@ -136,9 +138,8 @@ impl StatusItemView for ImageInfo { self.width = None; self.height = None; self.file_size = None; - self.color_type = None; self.format = None; - self._observe_active_image = None; + self.color_type = None; } cx.notify(); } diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 5648e9e203af5a..1b324203e6e593 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -36,6 +36,28 @@ impl ImageView { cx: &mut ViewContext, ) -> Self { cx.subscribe(&image_item, Self::on_image_event).detach(); + + if image_item.read(cx).image_meta.is_none() { + let image_item_clone = image_item.downgrade(); + let project_clone = project.downgrade(); + + cx.spawn(|view, mut cx| async move { + if let (Some(image_item), Some(project)) = + (image_item_clone.upgrade(), project_clone.upgrade()) + { + let metadata = + ImageItem::image_info(image_item.clone(), project, &mut cx).await?; + + image_item.update(&mut cx, |image_item, _cx| { + image_item.image_meta = Some(metadata); + })?; + } + + view.update(&mut cx, |_, cx| cx.notify()) + }) + .detach(); + } + Self { image_item, project, diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index b918de619c56c5..61de2c8e8d7a5e 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -34,6 +34,7 @@ impl From for ImageId { } } +#[derive(Debug)] pub enum ImageItemEvent { ReloadNeeded, Reloaded, @@ -92,7 +93,7 @@ fn image_color_type_description(color_type: ExtendedColorType) -> String { } impl ImageItem { - async fn image_info( + pub async fn image_info( image: Model, project: Model, cx: &mut AsyncAppContext, @@ -279,7 +280,7 @@ impl ProjectItem for ImageItem { let image_metadata = Self::image_info(image_model.clone(), project, &mut cx).await?; - image_model.update(&mut cx, |image_model, _| { + image_model.update(&mut cx, |image_model, _cx| { image_model.image_meta = Some(image_metadata); })?; From 645fad64e8e24751ae0fed1d0bc994298d4cc1cf Mon Sep 17 00:00:00 2001 From: kaf-lamed-beyt Date: Mon, 27 Jan 2025 15:22:23 +0100 Subject: [PATCH 17/17] chore: update implementation to use new GPUI3 APIs --- crates/image_viewer/src/image_info.rs | 15 ++++++++------- crates/project/src/image_store.rs | 6 +++--- crates/zed/src/zed.rs | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 9c538c6c67d568..74c84fad9a2f2a 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -1,10 +1,10 @@ use crate::ImageView; use anyhow; -use gpui::{div, AppContext, IntoElement, ParentElement, Render, Subscription, View, ViewContext}; +use gpui::{div, Context, Entity, IntoElement, ParentElement, Render, Subscription}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -use ui::{prelude::*, Button, LabelSize}; +use ui::{prelude::*, Button, LabelSize, Window}; use workspace::{ItemHandle, StatusItemView, Workspace}; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)] @@ -22,7 +22,7 @@ impl Settings for ImageFileSizeUnitType { fn load( sources: SettingsSources, - _: &mut AppContext, + _: &mut App, ) -> Result { sources.json_merge().or_else(|_| Ok(Self::Binary)) } @@ -38,7 +38,7 @@ pub struct ImageInfo { } impl ImageInfo { - pub fn new(_workspace: &Workspace, cx: &mut AppContext) -> Self { + pub fn new(_workspace: &Workspace, cx: &mut App) -> Self { static INIT: std::sync::Once = std::sync::Once::new(); INIT.call_once(|| { ImageFileSizeUnitType::register(cx); @@ -54,7 +54,7 @@ impl ImageInfo { } } - fn update_metadata(&mut self, image_view: &View, cx: &mut ViewContext) { + fn update_metadata(&mut self, image_view: &Entity, cx: &mut Context) { let image_item = image_view.read(cx).image_item.read(cx); if let Some(meta) = &image_item.image_meta { @@ -98,7 +98,7 @@ impl ImageInfo { } impl Render for ImageInfo { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let unit_type = ImageFileSizeUnitType::get_global(cx); let components = [ @@ -125,7 +125,8 @@ impl StatusItemView for ImageInfo { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn ItemHandle>, - cx: &mut ViewContext, + _window: &mut Window, + cx: &mut Context, ) { self._observe_active_image = None; diff --git a/crates/project/src/image_store.rs b/crates/project/src/image_store.rs index 5683eb98cccd6b..68014179d7f6dc 100644 --- a/crates/project/src/image_store.rs +++ b/crates/project/src/image_store.rs @@ -6,7 +6,7 @@ use anyhow::{Context as _, Result}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::oneshot, StreamExt}; use gpui::{ - hash, prelude::*, App, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity, + hash, prelude::*, App, AsyncAppContext, Context, Entity, EventEmitter, Img, Subscription, Task, WeakEntity }; use image::{ExtendedColorType, GenericImageView, ImageFormat, ImageReader}; use language::{DiskState, File}; @@ -93,8 +93,8 @@ fn image_color_type_description(color_type: ExtendedColorType) -> String { impl ImageItem { pub async fn image_info( - image: Model, - project: Model, + image: Entity, + project: Entity, cx: &mut AsyncAppContext, ) -> Result { let project_path = cx diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 210adbf9d6faef..547e59e81efd91 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -212,7 +212,7 @@ pub fn initialize_workspace( status_bar.add_right_item(active_toolchain_language, window, cx); status_bar.add_right_item(vim_mode_indicator, window, cx); status_bar.add_right_item(cursor_position, window, cx); - status_bar.add_right_item(image_metadata, cx); + status_bar.add_right_item(image_metadata, window, cx); }); auto_update_ui::notify_of_any_new_update(window, cx);