From 6d1ea782a4324255f5be28e35b866c334e092892 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 19 Apr 2024 01:43:52 +0300 Subject: [PATCH] Show tooltip in task spawn modal (#10744) Tooltip shows original task template's label, if it differs from the one displayed in the modal. Also, a resolved command with args will be shown in the tooltip if different from the modal entry text. Screenshot 2024-04-19 at 00 40 28 Screenshot 2024-04-19 at 00 40 32 Screenshot 2024-04-19 at 00 40 56 Screenshot 2024-04-19 at 00 41 01 Release Notes: - Added tooltips into task spawn modal --- Cargo.lock | 1 - crates/task/src/lib.rs | 20 +++++++++++++++++ crates/tasks_ui/Cargo.toml | 1 - crates/tasks_ui/src/modal.rs | 43 +++++++++++++++++++++++------------- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8958e55fca..3fdb1501f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9736,7 +9736,6 @@ dependencies = [ "file_icons", "fuzzy", "gpui", - "itertools 0.11.0", "language", "picker", "project", diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 4916cbb38b..327af8c3ff 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -75,6 +75,26 @@ impl ResolvedTask { pub fn substituted_variables(&self) -> &HashSet { &self.substituted_variables } + + /// If the resolution produced a task with the command, returns a string, combined from its command and arguments. + pub fn resolved_command(&self) -> Option { + self.resolved.as_ref().map(|resolved| { + let mut command = resolved.command.clone(); + for arg in &resolved.args { + command.push(' '); + command.push_str(arg); + } + command + }) + } + + /// A human-readable label to display in the UI. + pub fn display_label(&self) -> &str { + self.resolved + .as_ref() + .map(|resolved| resolved.label.as_str()) + .unwrap_or_else(|| self.resolved_label.as_str()) + } } /// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`]. diff --git a/crates/tasks_ui/Cargo.toml b/crates/tasks_ui/Cargo.toml index 223e9be61c..b19122f1de 100644 --- a/crates/tasks_ui/Cargo.toml +++ b/crates/tasks_ui/Cargo.toml @@ -25,7 +25,6 @@ util.workspace = true terminal.workspace = true workspace.workspace = true language.workspace = true -itertools.workspace = true [dev-dependencies] diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 4fb18e6cbc..2662997cf2 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -233,11 +233,7 @@ impl PickerDelegate for TasksModalDelegate { .map(|(index, (_, candidate))| StringMatchCandidate { id: index, char_bag: candidate.resolved_label.chars().collect(), - string: candidate - .resolved - .as_ref() - .map(|resolved| resolved.label.clone()) - .unwrap_or_else(|| candidate.resolved_label.clone()), + string: candidate.display_label().to_owned(), }) .collect::>() }) @@ -306,7 +302,28 @@ impl PickerDelegate for TasksModalDelegate { ) -> Option { let candidates = self.candidates.as_ref()?; let hit = &self.matches[ix]; - let (source_kind, _) = &candidates.get(hit.candidate_id)?; + let (source_kind, resolved_task) = &candidates.get(hit.candidate_id)?; + let template = resolved_task.original_task(); + let display_label = resolved_task.display_label(); + + let mut tooltip_label_text = if display_label != &template.label { + template.label.clone() + } else { + String::new() + }; + if let Some(resolved_command) = resolved_task.resolved_command() { + if display_label != resolved_command { + if !tooltip_label_text.trim().is_empty() { + tooltip_label_text.push('\n'); + } + tooltip_label_text.push_str(&resolved_command); + } + } + let tooltip_label = if tooltip_label_text.trim().is_empty() { + None + } else { + Some(Tooltip::text(tooltip_label_text, cx)) + }; let highlighted_location = HighlightedText { text: hit.string.clone(), @@ -321,10 +338,14 @@ impl PickerDelegate for TasksModalDelegate { .get_type_icon(&name.to_lowercase()) .map(|icon_path| Icon::from_path(icon_path)), }; + Some( ListItem::new(SharedString::from(format!("tasks-modal-{ix}"))) .inset(true) .spacing(ListItemSpacing::Sparse) + .when_some(tooltip_label, |list_item, item_label| { + list_item.tooltip(move |_| item_label.clone()) + }) .map(|item| { let item = if matches!(source_kind, TaskSourceKind::UserInput) || Some(ix) <= self.last_used_candidate_index @@ -368,18 +389,10 @@ impl PickerDelegate for TasksModalDelegate { } fn selected_as_query(&self) -> Option { - use itertools::intersperse; let task_index = self.matches.get(self.selected_index())?.candidate_id; let tasks = self.candidates.as_ref()?; let (_, task) = tasks.get(task_index)?; - task.resolved.as_ref().map(|spawn_in_terminal| { - let mut command = spawn_in_terminal.command.clone(); - if !spawn_in_terminal.args.is_empty() { - command.push(' '); - command.extend(intersperse(spawn_in_terminal.args.clone(), " ".to_string())); - } - command - }) + task.resolved_command() } fn confirm_input(&mut self, omit_history_entry: bool, cx: &mut ViewContext>) {