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
This commit is contained in:
Piotr Osiewicz 2024-04-08 11:41:54 +02:00 committed by GitHub
parent 3e6a9f6890
commit 4f9ad300a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 42 additions and 42 deletions

1
Cargo.lock generated
View file

@ -9496,6 +9496,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"editor", "editor",
"file_icons",
"fuzzy", "fuzzy",
"gpui", "gpui",
"itertools 0.11.0", "itertools 0.11.0",

View file

@ -54,18 +54,20 @@ impl FileIcons {
let suffix = path.icon_stem_or_suffix()?; let suffix = path.icon_stem_or_suffix()?;
if let Some(type_str) = this.stems.get(suffix) { if let Some(type_str) = this.stems.get(suffix) {
return this return this.get_type_icon(type_str);
.types
.get(type_str)
.map(|type_config| type_config.icon.clone());
} }
this.suffixes this.suffixes
.get(suffix) .get(suffix)
.and_then(|type_str| this.types.get(type_str)) .and_then(|type_str| this.get_type_icon(type_str))
.map(|type_config| type_config.icon.clone())
}) })
.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<Arc<str>> {
self.types
.get(typ)
.map(|type_config| type_config.icon.clone())
} }
pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> { pub fn get_folder_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
@ -77,9 +79,7 @@ impl FileIcons {
COLLAPSED_DIRECTORY_TYPE COLLAPSED_DIRECTORY_TYPE
}; };
this.types this.get_type_icon(key)
.get(key)
.map(|type_config| type_config.icon.clone())
} }
pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> { pub fn get_chevron_icon(expanded: bool, cx: &AppContext) -> Option<Arc<str>> {
@ -91,8 +91,6 @@ impl FileIcons {
COLLAPSED_CHEVRON_TYPE COLLAPSED_CHEVRON_TYPE
}; };
this.types this.get_type_icon(key)
.get(key)
.map(|type_config| type_config.icon.clone())
} }
} }

View file

@ -11,6 +11,7 @@ workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
editor.workspace = true editor.workspace = true
file_icons.workspace = true
fuzzy.workspace = true fuzzy.workspace = true
gpui.workspace = true gpui.workspace = true
picker.workspace = true picker.workspace = true
@ -23,6 +24,7 @@ workspace.workspace = true
language.workspace = true language.workspace = true
itertools.workspace = true itertools.workspace = true
[dev-dependencies] [dev-dependencies]
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }

View file

@ -3,22 +3,19 @@ use std::sync::Arc;
use crate::{active_item_selection_properties, schedule_task}; use crate::{active_item_selection_properties, schedule_task};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement, impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, Global,
Model, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, InteractiveElement, Model, ParentElement, Render, SharedString, Styled, Subscription, View,
VisualContext, WeakView, ViewContext, VisualContext, WeakView,
};
use picker::{
highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
Picker, PickerDelegate,
}; };
use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate};
use project::{Inventory, TaskSourceKind}; use project::{Inventory, TaskSourceKind};
use task::{oneshot_source::OneshotSource, Task, TaskContext}; use task::{oneshot_source::OneshotSource, Task, TaskContext};
use ui::{ 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, IconButtonShape, IconName, IconSize, ListItem, ListItemSpacing, RenderOnce, Selectable,
Tooltip, WindowContext, Tooltip, WindowContext,
}; };
use util::{paths::PathExt, ResultExt}; use util::ResultExt;
use workspace::{ModalView, Workspace}; use workspace::{ModalView, Workspace};
use serde::Deserialize; use serde::Deserialize;
@ -285,34 +282,31 @@ impl PickerDelegate for TasksModalDelegate {
cx: &mut ViewContext<picker::Picker<Self>>, cx: &mut ViewContext<picker::Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let candidates = self.candidates.as_ref()?; let candidates = self.candidates.as_ref()?;
let hit = &self.matches.get(ix)?; let hit = &self.matches[ix];
let (source_kind, _) = &candidates.get(hit.candidate_id)?; let (source_kind, _) = &candidates[hit.candidate_id];
let details = match source_kind { let language_name = if let TaskSourceKind::Language { name } = source_kind {
TaskSourceKind::UserInput => "user input".to_string(), Some(name)
TaskSourceKind::Language { name } => format!("{name} language"), } else {
TaskSourceKind::Worktree { abs_path, .. } | TaskSourceKind::AbsPath(abs_path) => { None
abs_path.compact().to_string_lossy().to_string()
}
}; };
let highlighted_location = HighlightedMatchWithPaths { let highlighted_location = HighlightedText {
match_label: HighlightedText { text: hit.string.clone(),
text: hit.string.clone(), highlight_positions: hit.positions.clone(),
highlight_positions: hit.positions.clone(), char_count: hit.string.chars().count(),
char_count: hit.string.chars().count(),
},
paths: vec![HighlightedText {
char_count: details.chars().count(),
highlight_positions: Vec::new(),
text: details,
}],
}; };
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( Some(
ListItem::new(SharedString::from(format!("tasks-modal-{ix}"))) ListItem::new(SharedString::from(format!("tasks-modal-{ix}")))
.inset(true) .inset(true)
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.map(|this| { .map(|this| {
if matches!(source_kind, TaskSourceKind::UserInput) { let this = if matches!(source_kind, TaskSourceKind::UserInput) {
let task_index = hit.candidate_id; let task_index = hit.candidate_id;
let delete_button = div().child( let delete_button = div().child(
IconButton::new("delete", IconName::Close) IconButton::new("delete", IconName::Close)
@ -332,6 +326,11 @@ impl PickerDelegate for TasksModalDelegate {
this.end_hover_slot(delete_button) this.end_hover_slot(delete_button)
} else { } else {
this this
};
if let Some(icon) = language_icon {
this.end_slot(icon)
} else {
this
} }
}) })
.selected(selected) .selected(selected)