use std::sync::Arc; use crate::TaskContexts;use editor::Editor;use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{ Action, AnyElement, App, AppContext as _, Context, DismissEvent, Entity, EventEmitter, Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, rems,};use itertools::Itertools;use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch};use project::{TaskSourceKind, task_store::TaskStore};use task::{DebugScenario, ResolvedTask, RevealTarget, TaskContext, TaskTemplate};use ui::{ ActiveTheme, Clickable, FluentBuilder as _, IconButtonShape, IconWithIndicator, Indicator, IntoElement, KeyBinding, ListItem, ListItemSpacing, RenderOnce, Toggleable, Tooltip, div, prelude::*,}; use util::{ResultExt, truncate_and_trailoff};use workspace::{ModalView, Workspace};pub use zed_actions::{Rerun, Spawn}; /// A modal used to spawn new tasks.pub struct TasksModalDelegate { task_store: Entity<TaskStore>, candidates: Option<Vec<(TaskSourceKind, ResolvedTask)>>, task_overrides: Option<TaskOverrides>, last_used_candidate_index: Option<usize>, divider_index: Option<usize>, matches: Vec<StringMatch>, selected_index: usize, workspace: WeakEntity<Workspace>, prompt: String, task_contexts: Arc<TaskContexts>, placeholder_text: Arc<str>,} /// Task template amendments to do before resolving the context.#[derive(Clone, Debug, Default, PartialEq, Eq)]pub struct TaskOverrides { /// See [`RevealTarget`]. pub reveal_target: Option<RevealTarget>,} impl TasksModalDelegate { fn new( task_store: Entity<TaskStore>, task_contexts: Arc<TaskContexts>, task_overrides: Option<TaskOverrides>, workspace: WeakEntity<Workspace>, ) -> Self { let placeholder_text = if let Some(TaskOverrides { reveal_target: Some(RevealTarget::Center), }) = &task_overrides { Arc::from("Find a task, or run a command in the central pane") } else { Arc::from("Find a task, or run a command") }; Self { task_store, workspace, candidates: None, matches: Vec::new(), last_used_candidate_index: None, divider_index: None, selected_index: 0, prompt: String::default(), task_contexts, task_overrides, placeholder_text, } } fn spawn_oneshot(&mut self) -> Option<(TaskSourceKind, ResolvedTask)> { if self.prompt.trim().is_empty() { return None; } let default_context = TaskContext::default(); let active_context = self .task_contexts .active_context() .unwrap_or(&default_context); let source_kind = TaskSourceKind::UserInput; let id_base = source_kind.to_id_base(); let mut new_oneshot = TaskTemplate { label: self.prompt.clone(), command: self.prompt.clone(), ..TaskTemplate::default() }; if let Some(TaskOverrides { reveal_target: Some(reveal_target), }) = &self.task_overrides { new_oneshot.reveal_target = *reveal_target; } Some(( source_kind, new_oneshot.resolve_task(&id_base, active_context)?, )) } fn delete_previously_used(&mut self, ix: usize, cx: &mut App) { let Some(candidates) = self.candidates.as_mut() else { return; }; let Some(task) = candidates.get(ix).map(|(_, task)| task.clone()) else { return; }; // We remove this candidate manually instead of .taking() the candidates, as we already know the index; // it doesn't make sense to requery the inventory for new candidates, as that's potentially costly and more often than not it should just return back // the original list without a removed entry. candidates.remove(ix); if let Some(inventory) = self.task_store.read(cx).task_inventory().cloned() { inventory.update(cx, |inventory, _| { inventory.delete_previously_used(&task.id); }) }; }} pub struct TasksModal { pub picker: Entity<Picker<TasksModalDelegate>>, _subscriptions: [Subscription; 2],} impl TasksModal { pub fn new( task_store: Entity<TaskStore>, task_contexts: Arc<TaskContexts>, task_overrides: Option<TaskOverrides>, is_modal: bool, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let picker = cx.new(|cx| { Picker::uniform_list( TasksModalDelegate::new( task_store.clone(), task_contexts, task_overrides, workspace.clone(), ), window, cx, ) .modal(is_modal) }); let mut _subscriptions = [ cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| { cx.emit(DismissEvent); }), cx.subscribe(&picker, |_, _, event: &ShowAttachModal, cx| { cx.emit(ShowAttachModal { debug_config: event.debug_config.clone(), }); }), ]; Self { picker, _subscriptions, } } pub fn tasks_loaded( &mut self, task_contexts: Arc<TaskContexts>, lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, used_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, window: &mut Window, cx: &mut Context<Self>, ) { let last_used_candidate_index = if used_tasks.is_empty() { None } else { Some(used_tasks.len() - 1) }; let mut new_candidates = used_tasks; new_candidates.extend(lsp_tasks); let hide_vscode = current_resolved_tasks.iter().any(|(kind, _)| match kind { TaskSourceKind::Worktree { id: _, directory_in_worktree: dir, id_base: _, } => dir.file_name().is_some_and(|name| name == ".zed"), _ => false, }); // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false // We should move the filter to new_candidates instead of on current // and add a test for this new_candidates.extend(current_resolved_tasks.into_iter().filter(|(task_kind, _)| { match task_kind { TaskSourceKind::Worktree { directory_in_worktree: dir, .. } => !(hide_vscode && dir.file_name().is_some_and(|name| name == ".vscode")), TaskSourceKind::Language { .. } => add_current_language_tasks, _ => true, } })); self.picker.update(cx, |picker, cx| { picker.delegate.task_contexts = task_contexts; picker.delegate.last_used_candidate_index = last_used_candidate_index; picker.delegate.candidates = Some(new_candidates); picker.refresh(window, cx); cx.notify(); }) }} impl Render for TasksModal { fn render( &mut self, _window: &mut Window, _: &mut Context<Self>, ) -> impl gpui::prelude::IntoElement { v_flex() .key_context("TasksModal") .w(rems(34.)) .child(self.picker.clone()) }} pub struct ShowAttachModal { pub debug_config: DebugScenario,} impl EventEmitter<DismissEvent> for TasksModal {}impl EventEmitter<ShowAttachModal> for TasksModal {}impl EventEmitter<ShowAttachModal> for Picker<TasksModalDelegate> {} impl Focusable for TasksModal { fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle { self.picker.read(cx).focus_handle(cx) }} impl ModalView for TasksModal {} const MAX_TAGS_LINE_LEN: usize = 30; impl PickerDelegate for TasksModalDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _: &mut App) -> Arc<str> { self.placeholder_text.clone() } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) -> Task<()> { let candidates = match &self.candidates { Some(candidates) => Task::ready(string_match_candidates(candidates)), None => { if let Some(task_inventory) = self.task_store.read(cx).task_inventory().cloned() { let task_list = task_inventory.update(cx, |this, cx| { this.used_and_current_resolved_tasks(self.task_contexts.clone(), cx) }); let workspace = self.workspace.clone(); let lsp_task_sources = self.task_contexts.lsp_task_sources.clone(); let task_position = self.task_contexts.latest_selection; cx.spawn(async move |picker, cx| { let (used, current) = task_list.await; let Ok((lsp_tasks, prefer_lsp)) = workspace.update(cx, |workspace, cx| { let lsp_tasks = editor::lsp_tasks( workspace.project().clone(), &lsp_task_sources, task_position, cx, ); let prefer_lsp = workspace .active_item(cx) .and_then(|item| item.downcast::<Editor>()) .map(|editor| { editor .read(cx) .buffer() .read(cx) .language_settings(cx) .tasks .prefer_lsp }) .unwrap_or(false); (lsp_tasks, prefer_lsp) }) else { return Vec::new(); }; let lsp_tasks = lsp_tasks.await; picker .update(cx, |picker, _| { picker.delegate.last_used_candidate_index = if used.is_empty() { None } else { Some(used.len() - 1) }; let mut new_candidates = used; let add_current_language_tasks = !prefer_lsp || lsp_tasks.is_empty(); new_candidates.extend(lsp_tasks.into_iter().flat_map( |(kind, tasks_with_locations)| { tasks_with_locations .into_iter() .sorted_by_key(|(location, task)| { (location.is_none(), task.resolved_label.clone()) }) .map(move |(_, task)| (kind.clone(), task)) }, )); // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false // We should move the filter to new_candidates instead of on current // and add a test for this new_candidates.extend(current.into_iter().filter( |(task_kind, _)| { add_current_language_tasks || !matches!(task_kind, TaskSourceKind::Language { .. }) }, )); let match_candidates = string_match_candidates(&new_candidates); let _ = picker.delegate.candidates.insert(new_candidates); match_candidates }) .ok() .unwrap_or_default() }) } else { Task::ready(Vec::new()) } } }; cx.spawn_in(window, async move |picker, cx| { let candidates = candidates.await; let matches = fuzzy::match_strings( &candidates, &query, true, true, 1000, &Default::default(), cx.background_executor().clone(), ) .await; picker .update(cx, |picker, _| { let delegate = &mut picker.delegate; delegate.matches = matches; if let Some(index) = delegate.last_used_candidate_index { delegate.matches.sort_by_key(|m| m.candidate_id > index); } delegate.prompt = query; delegate.divider_index = delegate.last_used_candidate_index.and_then(|index| { let index = delegate .matches .partition_point(|matching_task| matching_task.candidate_id <= index); Some(index).and_then(|index| (index != 0).then(|| index - 1)) }); if delegate.matches.is_empty() { delegate.selected_index = 0; } else { delegate.selected_index = delegate.selected_index.min(delegate.matches.len() - 1); } }) .log_err(); }) } fn confirm( &mut self, omit_history_entry: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) { let current_match_index = self.selected_index(); let task = self .matches .get(current_match_index) .and_then(|current_match| { let ix = current_match.candidate_id; self.candidates .as_ref() .map(|candidates| candidates[ix].clone()) }); let Some((task_source_kind, mut task)) = task else { return; }; if let Some(TaskOverrides { reveal_target: Some(reveal_target), }) = &self.task_overrides { task.resolved.reveal_target = *reveal_target; } self.workspace .update(cx, |workspace, cx| { workspace.schedule_resolved_task( task_source_kind, task, omit_history_entry, window, cx, ); }) .ok(); cx.emit(DismissEvent); } fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<picker::Picker<Self>>) { cx.emit(DismissEvent); } fn render_match( &self, ix: usize, selected: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) -> Option<Self::ListItem> { let candidates = self.candidates.as_ref()?; let hit = &self.matches.get(ix)?; 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 || source_kind == &TaskSourceKind::UserInput { resolved_task.resolved_label.clone() } else { String::new() }; if resolved_task.resolved.command_label != resolved_task.resolved_label { if !tooltip_label_text.trim().is_empty() { tooltip_label_text.push('\n'); } tooltip_label_text.push_str(&resolved_task.resolved.command_label); } if !template.tags.is_empty() { tooltip_label_text.push('\n'); tooltip_label_text.push_str( template .tags .iter() .map(|tag| format!("\n#{}", tag)) .collect::<Vec<_>>() .join("") .as_str(), ); } let tooltip_label = if tooltip_label_text.trim().is_empty() { None } else { Some(Tooltip::simple(tooltip_label_text, cx)) }; let highlighted_location = HighlightedMatch { text: hit.string.clone(), highlight_positions: hit.positions.clone(), color: Color::Default, }; let icon = match source_kind { TaskSourceKind::UserInput => Some(Icon::new(IconName::Terminal)), TaskSourceKind::AbsPath { .. } => Some(Icon::new(IconName::Settings)), TaskSourceKind::Worktree { .. } => Some(Icon::new(IconName::FileTree)), TaskSourceKind::Lsp { language_name: name, ..Lines 1–500 of 1321. Load more
use std::sync::Arc; use crate::TaskContexts; use editor::Editor; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ Action, AnyElement, App, AppContext as _, Context, DismissEvent, Entity, EventEmitter, Focusable, InteractiveElement, ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window, rems, }; use itertools::Itertools; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; use project::{TaskSourceKind, task_store::TaskStore}; use task::{DebugScenario, ResolvedTask, RevealTarget, TaskContext, TaskTemplate}; use ui::{ ActiveTheme, Clickable, FluentBuilder as _, IconButtonShape, IconWithIndicator, Indicator, IntoElement, KeyBinding, ListItem, ListItemSpacing, RenderOnce, Toggleable, Tooltip, div, prelude::*, }; use util::{ResultExt, truncate_and_trailoff}; use workspace::{ModalView, Workspace}; pub use zed_actions::{Rerun, Spawn}; /// A modal used to spawn new tasks. pub struct TasksModalDelegate { task_store: Entity<TaskStore>, candidates: Option<Vec<(TaskSourceKind, ResolvedTask)>>, task_overrides: Option<TaskOverrides>, last_used_candidate_index: Option<usize>, divider_index: Option<usize>, matches: Vec<StringMatch>, selected_index: usize, workspace: WeakEntity<Workspace>, prompt: String, task_contexts: Arc<TaskContexts>, placeholder_text: Arc<str>, } /// Task template amendments to do before resolving the context. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct TaskOverrides { /// See [`RevealTarget`]. pub reveal_target: Option<RevealTarget>, } impl TasksModalDelegate { fn new( task_store: Entity<TaskStore>, task_contexts: Arc<TaskContexts>, task_overrides: Option<TaskOverrides>, workspace: WeakEntity<Workspace>, ) -> Self { let placeholder_text = if let Some(TaskOverrides { reveal_target: Some(RevealTarget::Center), }) = &task_overrides { Arc::from("Find a task, or run a command in the central pane") } else { Arc::from("Find a task, or run a command") }; Self { task_store, workspace, candidates: None, matches: Vec::new(), last_used_candidate_index: None, divider_index: None, selected_index: 0, prompt: String::default(), task_contexts, task_overrides, placeholder_text, } } fn spawn_oneshot(&mut self) -> Option<(TaskSourceKind, ResolvedTask)> { if self.prompt.trim().is_empty() { return None; } let default_context = TaskContext::default(); let active_context = self .task_contexts .active_context() .unwrap_or(&default_context); let source_kind = TaskSourceKind::UserInput; let id_base = source_kind.to_id_base(); let mut new_oneshot = TaskTemplate { label: self.prompt.clone(), command: self.prompt.clone(), ..TaskTemplate::default() }; if let Some(TaskOverrides { reveal_target: Some(reveal_target), }) = &self.task_overrides { new_oneshot.reveal_target = *reveal_target; } Some(( source_kind, new_oneshot.resolve_task(&id_base, active_context)?, )) } fn delete_previously_used(&mut self, ix: usize, cx: &mut App) { let Some(candidates) = self.candidates.as_mut() else { return; }; let Some(task) = candidates.get(ix).map(|(_, task)| task.clone()) else { return; }; // We remove this candidate manually instead of .taking() the candidates, as we already know the index; // it doesn't make sense to requery the inventory for new candidates, as that's potentially costly and more often than not it should just return back // the original list without a removed entry. candidates.remove(ix); if let Some(inventory) = self.task_store.read(cx).task_inventory().cloned() { inventory.update(cx, |inventory, _| { inventory.delete_previously_used(&task.id); }) }; } } pub struct TasksModal { pub picker: Entity<Picker<TasksModalDelegate>>, _subscriptions: [Subscription; 2], } impl TasksModal { pub fn new( task_store: Entity<TaskStore>, task_contexts: Arc<TaskContexts>, task_overrides: Option<TaskOverrides>, is_modal: bool, workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let picker = cx.new(|cx| { Picker::uniform_list( TasksModalDelegate::new( task_store.clone(), task_contexts, task_overrides, workspace.clone(), ), window, cx, ) .modal(is_modal) }); let mut _subscriptions = [ cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| { cx.emit(DismissEvent); }), cx.subscribe(&picker, |_, _, event: &ShowAttachModal, cx| { cx.emit(ShowAttachModal { debug_config: event.debug_config.clone(), }); }), ]; Self { picker, _subscriptions, } } pub fn tasks_loaded( &mut self, task_contexts: Arc<TaskContexts>, lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, used_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, window: &mut Window, cx: &mut Context<Self>, ) { let last_used_candidate_index = if used_tasks.is_empty() { None } else { Some(used_tasks.len() - 1) }; let mut new_candidates = used_tasks; new_candidates.extend(lsp_tasks); let hide_vscode = current_resolved_tasks.iter().any(|(kind, _)| match kind { TaskSourceKind::Worktree { id: _, directory_in_worktree: dir, id_base: _, } => dir.file_name().is_some_and(|name| name == ".zed"), _ => false, }); // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false // We should move the filter to new_candidates instead of on current // and add a test for this new_candidates.extend(current_resolved_tasks.into_iter().filter(|(task_kind, _)| { match task_kind { TaskSourceKind::Worktree { directory_in_worktree: dir, .. } => !(hide_vscode && dir.file_name().is_some_and(|name| name == ".vscode")), TaskSourceKind::Language { .. } => add_current_language_tasks, _ => true, } })); self.picker.update(cx, |picker, cx| { picker.delegate.task_contexts = task_contexts; picker.delegate.last_used_candidate_index = last_used_candidate_index; picker.delegate.candidates = Some(new_candidates); picker.refresh(window, cx); cx.notify(); }) } } impl Render for TasksModal { fn render( &mut self, _window: &mut Window, _: &mut Context<Self>, ) -> impl gpui::prelude::IntoElement { v_flex() .key_context("TasksModal") .w(rems(34.)) .child(self.picker.clone()) } } pub struct ShowAttachModal { pub debug_config: DebugScenario, } impl EventEmitter<DismissEvent> for TasksModal {} impl EventEmitter<ShowAttachModal> for TasksModal {} impl EventEmitter<ShowAttachModal> for Picker<TasksModalDelegate> {} impl Focusable for TasksModal { fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle { self.picker.read(cx).focus_handle(cx) } } impl ModalView for TasksModal {} const MAX_TAGS_LINE_LEN: usize = 30; impl PickerDelegate for TasksModalDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index( &mut self, ix: usize, _window: &mut Window, _cx: &mut Context<picker::Picker<Self>>, ) { self.selected_index = ix; } fn placeholder_text(&self, _window: &mut Window, _: &mut App) -> Arc<str> { self.placeholder_text.clone() } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) -> Task<()> { let candidates = match &self.candidates { Some(candidates) => Task::ready(string_match_candidates(candidates)), None => { if let Some(task_inventory) = self.task_store.read(cx).task_inventory().cloned() { let task_list = task_inventory.update(cx, |this, cx| { this.used_and_current_resolved_tasks(self.task_contexts.clone(), cx) }); let workspace = self.workspace.clone(); let lsp_task_sources = self.task_contexts.lsp_task_sources.clone(); let task_position = self.task_contexts.latest_selection; cx.spawn(async move |picker, cx| { let (used, current) = task_list.await; let Ok((lsp_tasks, prefer_lsp)) = workspace.update(cx, |workspace, cx| { let lsp_tasks = editor::lsp_tasks( workspace.project().clone(), &lsp_task_sources, task_position, cx, ); let prefer_lsp = workspace .active_item(cx) .and_then(|item| item.downcast::<Editor>()) .map(|editor| { editor .read(cx) .buffer() .read(cx) .language_settings(cx) .tasks .prefer_lsp }) .unwrap_or(false); (lsp_tasks, prefer_lsp) }) else { return Vec::new(); }; let lsp_tasks = lsp_tasks.await; picker .update(cx, |picker, _| { picker.delegate.last_used_candidate_index = if used.is_empty() { None } else { Some(used.len() - 1) }; let mut new_candidates = used; let add_current_language_tasks = !prefer_lsp || lsp_tasks.is_empty(); new_candidates.extend(lsp_tasks.into_iter().flat_map( |(kind, tasks_with_locations)| { tasks_with_locations .into_iter() .sorted_by_key(|(location, task)| { (location.is_none(), task.resolved_label.clone()) }) .map(move |(_, task)| (kind.clone(), task)) }, )); // todo(debugger): We're always adding lsp tasks here even if prefer_lsp is false // We should move the filter to new_candidates instead of on current // and add a test for this new_candidates.extend(current.into_iter().filter( |(task_kind, _)| { add_current_language_tasks || !matches!(task_kind, TaskSourceKind::Language { .. }) }, )); let match_candidates = string_match_candidates(&new_candidates); let _ = picker.delegate.candidates.insert(new_candidates); match_candidates }) .ok() .unwrap_or_default() }) } else { Task::ready(Vec::new()) } } }; cx.spawn_in(window, async move |picker, cx| { let candidates = candidates.await; let matches = fuzzy::match_strings( &candidates, &query, true, true, 1000, &Default::default(), cx.background_executor().clone(), ) .await; picker .update(cx, |picker, _| { let delegate = &mut picker.delegate; delegate.matches = matches; if let Some(index) = delegate.last_used_candidate_index { delegate.matches.sort_by_key(|m| m.candidate_id > index); } delegate.prompt = query; delegate.divider_index = delegate.last_used_candidate_index.and_then(|index| { let index = delegate .matches .partition_point(|matching_task| matching_task.candidate_id <= index); Some(index).and_then(|index| (index != 0).then(|| index - 1)) }); if delegate.matches.is_empty() { delegate.selected_index = 0; } else { delegate.selected_index = delegate.selected_index.min(delegate.matches.len() - 1); } }) .log_err(); }) } fn confirm( &mut self, omit_history_entry: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) { let current_match_index = self.selected_index(); let task = self .matches .get(current_match_index) .and_then(|current_match| { let ix = current_match.candidate_id; self.candidates .as_ref() .map(|candidates| candidates[ix].clone()) }); let Some((task_source_kind, mut task)) = task else { return; }; if let Some(TaskOverrides { reveal_target: Some(reveal_target), }) = &self.task_overrides { task.resolved.reveal_target = *reveal_target; } self.workspace .update(cx, |workspace, cx| { workspace.schedule_resolved_task( task_source_kind, task, omit_history_entry, window, cx, ); }) .ok(); cx.emit(DismissEvent); } fn dismissed(&mut self, _window: &mut Window, cx: &mut Context<picker::Picker<Self>>) { cx.emit(DismissEvent); } fn render_match( &self, ix: usize, selected: bool, window: &mut Window, cx: &mut Context<picker::Picker<Self>>, ) -> Option<Self::ListItem> { let candidates = self.candidates.as_ref()?; let hit = &self.matches.get(ix)?; 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 || source_kind == &TaskSourceKind::UserInput { resolved_task.resolved_label.clone() } else { String::new() }; if resolved_task.resolved.command_label != resolved_task.resolved_label { if !tooltip_label_text.trim().is_empty() { tooltip_label_text.push('\n'); } tooltip_label_text.push_str(&resolved_task.resolved.command_label); } if !template.tags.is_empty() { tooltip_label_text.push('\n'); tooltip_label_text.push_str( template .tags .iter() .map(|tag| format!("\n#{}", tag)) .collect::<Vec<_>>() .join("") .as_str(), ); } let tooltip_label = if tooltip_label_text.trim().is_empty() { None } else { Some(Tooltip::simple(tooltip_label_text, cx)) }; let highlighted_location = HighlightedMatch { text: hit.string.clone(), highlight_positions: hit.positions.clone(), color: Color::Default, }; let icon = match source_kind { TaskSourceKind::UserInput => Some(Icon::new(IconName::Terminal)), TaskSourceKind::AbsPath { .. } => Some(Icon::new(IconName::Settings)), TaskSourceKind::Worktree { .. } => Some(Icon::new(IconName::FileTree)), TaskSourceKind::Lsp { language_name: name, ..