From 4f9ad300a7de6be8413e8aff022382ef88335391 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:41:54 +0200 Subject: [PATCH] tasks: Use icons instead of secondary text in a modal (#10264) Before: ![image](https://github.com/zed-industries/zed/assets/24362066/feae9c98-37d4-437d-965a-047d2e089a7b) After: ![image](https://github.com/zed-industries/zed/assets/24362066/43e48985-5aba-44d9-9128-cfafb9b61fd4) Release Notes: - N/A --- Cargo.lock | 1 + crates/file_icons/src/file_icons.rs | 24 ++++++------ crates/tasks_ui/Cargo.toml | 2 + crates/tasks_ui/src/modal.rs | 57 ++++++++++++++--------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae41755be8..054f48e2af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9496,6 +9496,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", + "file_icons", "fuzzy", "gpui", "itertools 0.11.0", diff --git a/crates/file_icons/src/file_icons.rs b/crates/file_icons/src/file_icons.rs index 029d0b9979..4c72bad43e 100644 --- a/crates/file_icons/src/file_icons.rs +++ b/crates/file_icons/src/file_icons.rs @@ -54,18 +54,20 @@ impl FileIcons { let suffix = path.icon_stem_or_suffix()?; if let Some(type_str) = this.stems.get(suffix) { - return this - .types - .get(type_str) - .map(|type_config| type_config.icon.clone()); + return this.get_type_icon(type_str); } this.suffixes .get(suffix) - .and_then(|type_str| this.types.get(type_str)) - .map(|type_config| type_config.icon.clone()) + .and_then(|type_str| this.get_type_icon(type_str)) }) - .or_else(|| this.types.get("default").map(|config| config.icon.clone())) + .or_else(|| this.get_type_icon("default")) + } + + pub fn get_type_icon(&self, typ: &str) -> Option> { + self.types + .get(typ) + .map(|type_config| type_config.icon.clone()) } pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option> { @@ -77,9 +79,7 @@ impl FileIcons { COLLAPSED_DIRECTORY_TYPE }; - this.types - .get(key) - .map(|type_config| type_config.icon.clone()) + this.get_type_icon(key) } pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option> { @@ -91,8 +91,6 @@ impl FileIcons { COLLAPSED_CHEVRON_TYPE }; - this.types - .get(key) - .map(|type_config| type_config.icon.clone()) + this.get_type_icon(key) } } diff --git a/crates/tasks_ui/Cargo.toml b/crates/tasks_ui/Cargo.toml index b3d47f492d..d71ac4e620 100644 --- a/crates/tasks_ui/Cargo.toml +++ b/crates/tasks_ui/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] anyhow.workspace = true editor.workspace = true +file_icons.workspace = true fuzzy.workspace = true gpui.workspace = true picker.workspace = true @@ -23,6 +24,7 @@ workspace.workspace = true language.workspace = true itertools.workspace = true + [dev-dependencies] editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 1d0386b6c9..0e5443d81c 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -3,22 +3,19 @@ use std::sync::Arc; use crate::{active_item_selection_properties, schedule_task}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement, - Model, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, - VisualContext, WeakView, -}; -use picker::{ - highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText}, - Picker, PickerDelegate, + impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, Global, + InteractiveElement, Model, ParentElement, Render, SharedString, Styled, Subscription, View, + ViewContext, VisualContext, WeakView, }; +use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate}; use project::{Inventory, TaskSourceKind}; use task::{oneshot_source::OneshotSource, Task, TaskContext}; use ui::{ - div, v_flex, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, IconButton, + div, v_flex, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, Icon, IconButton, IconButtonShape, IconName, IconSize, ListItem, ListItemSpacing, RenderOnce, Selectable, Tooltip, WindowContext, }; -use util::{paths::PathExt, ResultExt}; +use util::ResultExt; use workspace::{ModalView, Workspace}; use serde::Deserialize; @@ -285,34 +282,31 @@ impl PickerDelegate for TasksModalDelegate { cx: &mut ViewContext>, ) -> Option { let candidates = self.candidates.as_ref()?; - let hit = &self.matches.get(ix)?; - let (source_kind, _) = &candidates.get(hit.candidate_id)?; - let details = match source_kind { - TaskSourceKind::UserInput => "user input".to_string(), - TaskSourceKind::Language { name } => format!("{name} language"), - TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => { - abs_path.compact().to_string_lossy().to_string() - } + let hit = &self.matches[ix]; + let (source_kind, _) = &candidates[hit.candidate_id]; + let language_name = if let TaskSourceKind::Language { name } = source_kind { + Some(name) + } else { + None }; - let highlighted_location = HighlightedMatchWithPaths { - match_label: HighlightedText { - text: hit.string.clone(), - highlight_positions: hit.positions.clone(), - char_count: hit.string.chars().count(), - }, - paths: vec![HighlightedText { - char_count: details.chars().count(), - highlight_positions: Vec::new(), - text: details, - }], + let highlighted_location = HighlightedText { + text: hit.string.clone(), + highlight_positions: hit.positions.clone(), + char_count: hit.string.chars().count(), }; + let language_icon = language_name + .and_then(|language| { + let language = language.to_lowercase(); + file_icons::FileIcons::get(cx).get_type_icon(&language) + }) + .map(|icon_path| Icon::from_path(icon_path)); Some( ListItem::new(SharedString::from(format!("tasks-modal-{ix}"))) .inset(true) .spacing(ListItemSpacing::Sparse) .map(|this| { - if matches!(source_kind, TaskSourceKind::UserInput) { + let this = if matches!(source_kind, TaskSourceKind::UserInput) { let task_index = hit.candidate_id; let delete_button = div().child( IconButton::new("delete", IconName::Close) @@ -332,6 +326,11 @@ impl PickerDelegate for TasksModalDelegate { this.end_hover_slot(delete_button) } else { this + }; + if let Some(icon) = language_icon { + this.end_slot(icon) + } else { + this } }) .selected(selected)