//! Project-wide storage of the tasks available, capable of updating itself from the sources set. use std::{ borrow::Cow, cmp::{self, Reverse}, collections::hash_map, path::{Path, PathBuf}, sync::Arc,}; use anyhow::Result;use collections::{HashMap, HashSet, VecDeque};use dap::DapRegistry;use fs::Fs;use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity};use itertools::Itertools;use language::{ Buffer, ContextLocation, ContextProvider, File, Language, LanguageToolchainStore, Location, language_settings::language_settings,};use lsp::{LanguageServerId, LanguageServerName};use paths::{debug_task_file_name, task_file_name};use settings::{InvalidSettingsError, parse_json_with_comments};use task::{ DebugScenario, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates, TaskVariables, VariableName,};use text::{BufferId, Point, ToPoint};use util::{NumericPrefixWithSuffix, ResultExt as _, paths::PathExt as _, post_inc};use worktree::WorktreeId; use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore}; #[derive(Clone, Debug, Default)]pub struct DebugScenarioContext { pub task_context: TaskContext, pub worktree_id: Option<WorktreeId>, pub active_buffer: Option<WeakEntity<Buffer>>,} /// Inventory tracks available tasks for a given project.pub struct Inventory { fs: Arc<dyn Fs>, last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>, last_scheduled_scenarios: VecDeque<(DebugScenario, DebugScenarioContext)>, templates_from_settings: InventoryFor<TaskTemplate>, scenarios_from_settings: InventoryFor<DebugScenario>,} impl std::fmt::Debug for Inventory { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Inventory") .field("last_scheduled_tasks", &self.last_scheduled_tasks) .field("last_scheduled_scenarios", &self.last_scheduled_scenarios) .field("templates_from_settings", &self.templates_from_settings) .field("scenarios_from_settings", &self.scenarios_from_settings) .finish() }} // Helper trait for better error messages in [InventoryFor]trait InventoryContents: Clone { const GLOBAL_SOURCE_FILE: &'static str; const LABEL: &'static str;} impl InventoryContents for TaskTemplate { const GLOBAL_SOURCE_FILE: &'static str = "tasks.json"; const LABEL: &'static str = "tasks";} impl InventoryContents for DebugScenario { const GLOBAL_SOURCE_FILE: &'static str = "debug.json"; const LABEL: &'static str = "debug scenarios";} #[derive(Debug)]struct InventoryFor<T> { global: HashMap<PathBuf, Vec<T>>, worktree: HashMap<WorktreeId, HashMap<Arc<Path>, Vec<T>>>,} impl<T: InventoryContents> InventoryFor<T> { fn worktree_scenarios( &self, worktree: WorktreeId, ) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> { self.worktree .get(&worktree) .into_iter() .flatten() .flat_map(|(directory, templates)| { templates.iter().map(move |template| (directory, template)) }) .map(move |(directory, template)| { ( TaskSourceKind::Worktree { id: worktree, directory_in_worktree: directory.to_path_buf(), id_base: Cow::Owned(format!( "local worktree {} from directory {directory:?}", T::LABEL )), }, template.clone(), ) }) } fn global_scenarios(&self) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> { self.global.iter().flat_map(|(file_path, templates)| { templates.into_iter().map(|template| { ( TaskSourceKind::AbsPath { id_base: Cow::Owned(format!("global {}", T::GLOBAL_SOURCE_FILE)), abs_path: file_path.clone(), }, template.clone(), ) }) }) }} impl<T> Default for InventoryFor<T> { fn default() -> Self { Self { global: HashMap::default(), worktree: HashMap::default(), } }} /// Kind of a source the tasks are fetched from, used to display more source information in the UI.#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]pub enum TaskSourceKind { /// bash-like commands spawned by users, not associated with any path UserInput, /// Tasks from the worktree's .zed/task.json Worktree { id: WorktreeId, directory_in_worktree: PathBuf, id_base: Cow<'static, str>, }, /// ~/.config/zed/task.json - like global files with task definitions, applicable to any path AbsPath { id_base: Cow<'static, str>, abs_path: PathBuf, }, /// Languages-specific tasks coming from extensions. Language { name: SharedString }, /// Language-specific tasks coming from LSP servers. Lsp { language_name: SharedString, server: LanguageServerId, },} /// A collection of task contexts, derived from the current state of the workspace./// Only contains worktrees that are visible and with their root being a directory.#[derive(Debug, Default)]pub struct TaskContexts { /// A context, related to the currently opened item. /// Item can be opened from an invisible worktree, or any other, not necessarily active worktree. pub active_item_context: Option<(Option<WorktreeId>, Option<Location>, TaskContext)>, /// A worktree that corresponds to the active item, or the only worktree in the workspace. pub active_worktree_context: Option<(WorktreeId, TaskContext)>, /// If there are multiple worktrees in the workspace, all non-active ones are included here. pub other_worktree_contexts: Vec<(WorktreeId, TaskContext)>, pub lsp_task_sources: HashMap<LanguageServerName, Vec<BufferId>>, pub latest_selection: Option<text::Anchor>,} impl TaskContexts { pub fn active_context(&self) -> Option<&TaskContext> { self.active_item_context .as_ref() .map(|(_, _, context)| context) .or_else(|| { self.active_worktree_context .as_ref() .map(|(_, context)| context) }) } pub fn location(&self) -> Option<&Location> { self.active_item_context .as_ref() .and_then(|(_, location, _)| location.as_ref()) } pub fn file(&self, cx: &App) -> Option<Arc<dyn File>> { self.active_item_context .as_ref() .and_then(|(_, location, _)| location.as_ref()) .and_then(|location| location.buffer.read(cx).file().cloned()) } pub fn worktree(&self) -> Option<WorktreeId> { self.active_item_context .as_ref() .and_then(|(worktree_id, _, _)| worktree_id.as_ref()) .or_else(|| { self.active_worktree_context .as_ref() .map(|(worktree_id, _)| worktree_id) }) .copied() } pub fn task_context_for_worktree_id(&self, worktree_id: WorktreeId) -> Option<&TaskContext> { self.active_worktree_context .iter() .chain(self.other_worktree_contexts.iter()) .find(|(id, _)| *id == worktree_id) .map(|(_, context)| context) }} impl TaskSourceKind { pub fn to_id_base(&self) -> String { match self { Self::UserInput => "oneshot".to_string(), Self::AbsPath { id_base, abs_path } => { format!("{id_base}_{}", abs_path.display()) } Self::Worktree { id, id_base, directory_in_worktree, } => { format!("{id_base}_{id}_{}", directory_in_worktree.display()) } Self::Language { name } => format!("language_{name}"), Self::Lsp { server, language_name, } => format!("lsp_{language_name}_{server}"), } }} impl Inventory { pub fn new(fs: Arc<dyn Fs>, cx: &mut App) -> Entity<Self> { cx.new(|_| Self { fs, last_scheduled_tasks: VecDeque::default(), last_scheduled_scenarios: VecDeque::default(), templates_from_settings: InventoryFor::default(), scenarios_from_settings: InventoryFor::default(), }) } pub fn scenario_scheduled( &mut self, scenario: DebugScenario, task_context: TaskContext, worktree_id: Option<WorktreeId>, active_buffer: Option<WeakEntity<Buffer>>, ) { self.last_scheduled_scenarios .retain(|(s, _)| s.label != scenario.label); self.last_scheduled_scenarios.push_back(( scenario, DebugScenarioContext { task_context, worktree_id, active_buffer, }, )); if self.last_scheduled_scenarios.len() > 5_000 { self.last_scheduled_scenarios.pop_front(); } } pub fn last_scheduled_scenario(&self) -> Option<&(DebugScenario, DebugScenarioContext)> { self.last_scheduled_scenarios.back() } pub fn list_debug_scenarios( &self, task_contexts: &TaskContexts, lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, cx: &mut App, ) -> Task<( Vec<(DebugScenario, DebugScenarioContext)>, Vec<(TaskSourceKind, DebugScenario)>, )> { let mut scenarios = Vec::new(); if let Some(worktree_id) = task_contexts .active_worktree_context .iter() .chain(task_contexts.other_worktree_contexts.iter()) .map(|context| context.0) .next() { scenarios.extend(self.worktree_scenarios_from_settings(worktree_id)); } scenarios.extend(self.global_debug_scenarios_from_settings()); let last_scheduled_scenarios = self.last_scheduled_scenarios.iter().cloned().collect(); let adapter = task_contexts.location().and_then(|location| { let (file, language) = { let buffer = location.buffer.read(cx); (buffer.file(), buffer.language()) }; let language_name = language.as_ref().map(|l| l.name()); let adapter = language_settings(language_name, file, cx) .debuggers .first() .map(SharedString::from) .or_else(|| { language.and_then(|l| l.config().debuggers.first().map(SharedString::from)) }); adapter.map(|adapter| (adapter, DapRegistry::global(cx).locators())) }); cx.background_spawn(async move { if let Some((adapter, locators)) = adapter { for (kind, task) in lsp_tasks .into_iter() .chain(current_resolved_tasks.into_iter().filter(|(kind, _)| { add_current_language_tasks || !matches!(kind, TaskSourceKind::Language { .. }) })) { let adapter = adapter.clone().into(); for locator in locators.values() { if let Some(scenario) = locator .create_scenario(&task.original_task(), &task.display_label(), &adapter) .await { scenarios.push((kind, scenario)); break; } } } } (last_scheduled_scenarios, scenarios) }) } pub fn task_template_by_label( &self, buffer: Option<Entity<Buffer>>, worktree_id: Option<WorktreeId>, label: &str, cx: &App, ) -> Task<Option<TaskTemplate>> { let (buffer_worktree_id, file, language) = buffer .map(|buffer| { let buffer = buffer.read(cx); let file = buffer.file().cloned(); ( file.as_ref().map(|file| file.worktree_id(cx)), file, buffer.language().cloned(), ) }) .unwrap_or((None, None, None)); let tasks = self.list_tasks(file, language, worktree_id.or(buffer_worktree_id), cx); let label = label.to_owned(); cx.background_spawn(async move { tasks .await .into_iter() .find(|(_, template)| template.label == label) .map(|val| val.1) }) } /// Pulls its task sources relevant to the worktree and the language given, /// returns all task templates with their source kinds, worktree tasks first, language tasks second /// and global tasks last. No specific order inside source kinds groups. pub fn list_tasks( &self, file: Option<Arc<dyn File>>, language: Option<Arc<Language>>, worktree: Option<WorktreeId>, cx: &App, ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> { let global_tasks = self.global_templates_from_settings().collect::<Vec<_>>(); let fs = self.fs.clone(); let mut worktree_tasks = worktree .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::<Vec<_>>(); let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); let language_tasks = language .filter(|language| { language_settings(Some(language.name()), file.as_ref(), cx) .tasks .enabled }) .and_then(|language| { language .context_provider() .map(|provider| provider.associated_tasks(fs, file, cx)) }); cx.background_spawn(async move { if let Some(t) = language_tasks { worktree_tasks.extend(t.await.into_iter().flat_map(|tasks| { tasks .0 .into_iter() .filter_map(|task| Some((task_source_kind.clone()?, task))) })); } worktree_tasks.extend(global_tasks); worktree_tasks }) } /// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContexts`] given. /// Joins the new resolutions with the resolved tasks that were used (spawned) before, /// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first. /// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks. pub fn used_and_current_resolved_tasks( &self, task_contexts: Arc<TaskContexts>, cx: &mut Context<Self>, ) -> Task<( Vec<(TaskSourceKind, ResolvedTask)>, Vec<(TaskSourceKind, ResolvedTask)>, )> { let fs = self.fs.clone(); let worktree = task_contexts.worktree(); let location = task_contexts.location(); let language = location.and_then(|location| location.buffer.read(cx).language()); let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); let file = location.and_then(|location| location.buffer.read(cx).file().cloned()); let mut task_labels_to_ids = HashMap::<String, HashSet<TaskId>>::default(); let mut lru_score = 0_u32; let previously_spawned_tasks = self .last_scheduled_tasks .iter() .rev() .filter(|(task_kind, _)| { if matches!(task_kind, TaskSourceKind::Language { .. }) { Some(task_kind) == task_source_kind.as_ref() } else { true } }) .filter(|(_, resolved_task)| { match task_labels_to_ids.entry(resolved_task.resolved_label.clone()) { hash_map::Entry::Occupied(mut o) => { o.get_mut().insert(resolved_task.id.clone()); // Neber allow duplicate reused tasks with the same labels false } hash_map::Entry::Vacant(v) => { v.insert(HashSet::from_iter(Some(resolved_task.id.clone()))); true } } }) .map(|(task_source_kind, resolved_task)| { ( task_source_kind.clone(), resolved_task.clone(), post_inc(&mut lru_score), ) }) .sorted_unstable_by(task_lru_comparator) .map(|(kind, task, _)| (kind, task)) .collect::<Vec<_>>(); let not_used_score = post_inc(&mut lru_score); let global_tasks = self.global_templates_from_settings().collect::<Vec<_>>(); let associated_tasks = language .filter(|language| { language_settings(Some(language.name()), file.as_ref(), cx) .tasks .enabled }) .and_then(|language| { language .context_provider() .map(|provider| provider.associated_tasks(fs, file, cx)) }); let worktree_tasks = worktree .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::<Vec<_>>(); let task_contexts = task_contexts.clone(); cx.background_spawn(async move { let language_tasks = if let Some(task) = associated_tasks {Lines 1–500 of 1622. Load more
//! Project-wide storage of the tasks available, capable of updating itself from the sources set. use std::{ borrow::Cow, cmp::{self, Reverse}, collections::hash_map, path::{Path, PathBuf}, sync::Arc, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use dap::DapRegistry; use fs::Fs; use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity}; use itertools::Itertools; use language::{ Buffer, ContextLocation, ContextProvider, File, Language, LanguageToolchainStore, Location, language_settings::language_settings, }; use lsp::{LanguageServerId, LanguageServerName}; use paths::{debug_task_file_name, task_file_name}; use settings::{InvalidSettingsError, parse_json_with_comments}; use task::{ DebugScenario, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates, TaskVariables, VariableName, }; use text::{BufferId, Point, ToPoint}; use util::{NumericPrefixWithSuffix, ResultExt as _, paths::PathExt as _, post_inc}; use worktree::WorktreeId; use crate::{task_store::TaskSettingsLocation, worktree_store::WorktreeStore}; #[derive(Clone, Debug, Default)] pub struct DebugScenarioContext { pub task_context: TaskContext, pub worktree_id: Option<WorktreeId>, pub active_buffer: Option<WeakEntity<Buffer>>, } /// Inventory tracks available tasks for a given project. pub struct Inventory { fs: Arc<dyn Fs>, last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>, last_scheduled_scenarios: VecDeque<(DebugScenario, DebugScenarioContext)>, templates_from_settings: InventoryFor<TaskTemplate>, scenarios_from_settings: InventoryFor<DebugScenario>, } impl std::fmt::Debug for Inventory { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Inventory") .field("last_scheduled_tasks", &self.last_scheduled_tasks) .field("last_scheduled_scenarios", &self.last_scheduled_scenarios) .field("templates_from_settings", &self.templates_from_settings) .field("scenarios_from_settings", &self.scenarios_from_settings) .finish() } } // Helper trait for better error messages in [InventoryFor] trait InventoryContents: Clone { const GLOBAL_SOURCE_FILE: &'static str; const LABEL: &'static str; } impl InventoryContents for TaskTemplate { const GLOBAL_SOURCE_FILE: &'static str = "tasks.json"; const LABEL: &'static str = "tasks"; } impl InventoryContents for DebugScenario { const GLOBAL_SOURCE_FILE: &'static str = "debug.json"; const LABEL: &'static str = "debug scenarios"; } #[derive(Debug)] struct InventoryFor<T> { global: HashMap<PathBuf, Vec<T>>, worktree: HashMap<WorktreeId, HashMap<Arc<Path>, Vec<T>>>, } impl<T: InventoryContents> InventoryFor<T> { fn worktree_scenarios( &self, worktree: WorktreeId, ) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> { self.worktree .get(&worktree) .into_iter() .flatten() .flat_map(|(directory, templates)| { templates.iter().map(move |template| (directory, template)) }) .map(move |(directory, template)| { ( TaskSourceKind::Worktree { id: worktree, directory_in_worktree: directory.to_path_buf(), id_base: Cow::Owned(format!( "local worktree {} from directory {directory:?}", T::LABEL )), }, template.clone(), ) }) } fn global_scenarios(&self) -> impl '_ + Iterator<Item = (TaskSourceKind, T)> { self.global.iter().flat_map(|(file_path, templates)| { templates.into_iter().map(|template| { ( TaskSourceKind::AbsPath { id_base: Cow::Owned(format!("global {}", T::GLOBAL_SOURCE_FILE)), abs_path: file_path.clone(), }, template.clone(), ) }) }) } } impl<T> Default for InventoryFor<T> { fn default() -> Self { Self { global: HashMap::default(), worktree: HashMap::default(), } } } /// Kind of a source the tasks are fetched from, used to display more source information in the UI. #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum TaskSourceKind { /// bash-like commands spawned by users, not associated with any path UserInput, /// Tasks from the worktree's .zed/task.json Worktree { id: WorktreeId, directory_in_worktree: PathBuf, id_base: Cow<'static, str>, }, /// ~/.config/zed/task.json - like global files with task definitions, applicable to any path AbsPath { id_base: Cow<'static, str>, abs_path: PathBuf, }, /// Languages-specific tasks coming from extensions. Language { name: SharedString }, /// Language-specific tasks coming from LSP servers. Lsp { language_name: SharedString, server: LanguageServerId, }, } /// A collection of task contexts, derived from the current state of the workspace. /// Only contains worktrees that are visible and with their root being a directory. #[derive(Debug, Default)] pub struct TaskContexts { /// A context, related to the currently opened item. /// Item can be opened from an invisible worktree, or any other, not necessarily active worktree. pub active_item_context: Option<(Option<WorktreeId>, Option<Location>, TaskContext)>, /// A worktree that corresponds to the active item, or the only worktree in the workspace. pub active_worktree_context: Option<(WorktreeId, TaskContext)>, /// If there are multiple worktrees in the workspace, all non-active ones are included here. pub other_worktree_contexts: Vec<(WorktreeId, TaskContext)>, pub lsp_task_sources: HashMap<LanguageServerName, Vec<BufferId>>, pub latest_selection: Option<text::Anchor>, } impl TaskContexts { pub fn active_context(&self) -> Option<&TaskContext> { self.active_item_context .as_ref() .map(|(_, _, context)| context) .or_else(|| { self.active_worktree_context .as_ref() .map(|(_, context)| context) }) } pub fn location(&self) -> Option<&Location> { self.active_item_context .as_ref() .and_then(|(_, location, _)| location.as_ref()) } pub fn file(&self, cx: &App) -> Option<Arc<dyn File>> { self.active_item_context .as_ref() .and_then(|(_, location, _)| location.as_ref()) .and_then(|location| location.buffer.read(cx).file().cloned()) } pub fn worktree(&self) -> Option<WorktreeId> { self.active_item_context .as_ref() .and_then(|(worktree_id, _, _)| worktree_id.as_ref()) .or_else(|| { self.active_worktree_context .as_ref() .map(|(worktree_id, _)| worktree_id) }) .copied() } pub fn task_context_for_worktree_id(&self, worktree_id: WorktreeId) -> Option<&TaskContext> { self.active_worktree_context .iter() .chain(self.other_worktree_contexts.iter()) .find(|(id, _)| *id == worktree_id) .map(|(_, context)| context) } } impl TaskSourceKind { pub fn to_id_base(&self) -> String { match self { Self::UserInput => "oneshot".to_string(), Self::AbsPath { id_base, abs_path } => { format!("{id_base}_{}", abs_path.display()) } Self::Worktree { id, id_base, directory_in_worktree, } => { format!("{id_base}_{id}_{}", directory_in_worktree.display()) } Self::Language { name } => format!("language_{name}"), Self::Lsp { server, language_name, } => format!("lsp_{language_name}_{server}"), } } } impl Inventory { pub fn new(fs: Arc<dyn Fs>, cx: &mut App) -> Entity<Self> { cx.new(|_| Self { fs, last_scheduled_tasks: VecDeque::default(), last_scheduled_scenarios: VecDeque::default(), templates_from_settings: InventoryFor::default(), scenarios_from_settings: InventoryFor::default(), }) } pub fn scenario_scheduled( &mut self, scenario: DebugScenario, task_context: TaskContext, worktree_id: Option<WorktreeId>, active_buffer: Option<WeakEntity<Buffer>>, ) { self.last_scheduled_scenarios .retain(|(s, _)| s.label != scenario.label); self.last_scheduled_scenarios.push_back(( scenario, DebugScenarioContext { task_context, worktree_id, active_buffer, }, )); if self.last_scheduled_scenarios.len() > 5_000 { self.last_scheduled_scenarios.pop_front(); } } pub fn last_scheduled_scenario(&self) -> Option<&(DebugScenario, DebugScenarioContext)> { self.last_scheduled_scenarios.back() } pub fn list_debug_scenarios( &self, task_contexts: &TaskContexts, lsp_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, current_resolved_tasks: Vec<(TaskSourceKind, task::ResolvedTask)>, add_current_language_tasks: bool, cx: &mut App, ) -> Task<( Vec<(DebugScenario, DebugScenarioContext)>, Vec<(TaskSourceKind, DebugScenario)>, )> { let mut scenarios = Vec::new(); if let Some(worktree_id) = task_contexts .active_worktree_context .iter() .chain(task_contexts.other_worktree_contexts.iter()) .map(|context| context.0) .next() { scenarios.extend(self.worktree_scenarios_from_settings(worktree_id)); } scenarios.extend(self.global_debug_scenarios_from_settings()); let last_scheduled_scenarios = self.last_scheduled_scenarios.iter().cloned().collect(); let adapter = task_contexts.location().and_then(|location| { let (file, language) = { let buffer = location.buffer.read(cx); (buffer.file(), buffer.language()) }; let language_name = language.as_ref().map(|l| l.name()); let adapter = language_settings(language_name, file, cx) .debuggers .first() .map(SharedString::from) .or_else(|| { language.and_then(|l| l.config().debuggers.first().map(SharedString::from)) }); adapter.map(|adapter| (adapter, DapRegistry::global(cx).locators())) }); cx.background_spawn(async move { if let Some((adapter, locators)) = adapter { for (kind, task) in lsp_tasks .into_iter() .chain(current_resolved_tasks.into_iter().filter(|(kind, _)| { add_current_language_tasks || !matches!(kind, TaskSourceKind::Language { .. }) })) { let adapter = adapter.clone().into(); for locator in locators.values() { if let Some(scenario) = locator .create_scenario(&task.original_task(), &task.display_label(), &adapter) .await { scenarios.push((kind, scenario)); break; } } } } (last_scheduled_scenarios, scenarios) }) } pub fn task_template_by_label( &self, buffer: Option<Entity<Buffer>>, worktree_id: Option<WorktreeId>, label: &str, cx: &App, ) -> Task<Option<TaskTemplate>> { let (buffer_worktree_id, file, language) = buffer .map(|buffer| { let buffer = buffer.read(cx); let file = buffer.file().cloned(); ( file.as_ref().map(|file| file.worktree_id(cx)), file, buffer.language().cloned(), ) }) .unwrap_or((None, None, None)); let tasks = self.list_tasks(file, language, worktree_id.or(buffer_worktree_id), cx); let label = label.to_owned(); cx.background_spawn(async move { tasks .await .into_iter() .find(|(_, template)| template.label == label) .map(|val| val.1) }) } /// Pulls its task sources relevant to the worktree and the language given, /// returns all task templates with their source kinds, worktree tasks first, language tasks second /// and global tasks last. No specific order inside source kinds groups. pub fn list_tasks( &self, file: Option<Arc<dyn File>>, language: Option<Arc<Language>>, worktree: Option<WorktreeId>, cx: &App, ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> { let global_tasks = self.global_templates_from_settings().collect::<Vec<_>>(); let fs = self.fs.clone(); let mut worktree_tasks = worktree .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::<Vec<_>>(); let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); let language_tasks = language .filter(|language| { language_settings(Some(language.name()), file.as_ref(), cx) .tasks .enabled }) .and_then(|language| { language .context_provider() .map(|provider| provider.associated_tasks(fs, file, cx)) }); cx.background_spawn(async move { if let Some(t) = language_tasks { worktree_tasks.extend(t.await.into_iter().flat_map(|tasks| { tasks .0 .into_iter() .filter_map(|task| Some((task_source_kind.clone()?, task))) })); } worktree_tasks.extend(global_tasks); worktree_tasks }) } /// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContexts`] given. /// Joins the new resolutions with the resolved tasks that were used (spawned) before, /// orders them so that the most recently used come first, all equally used ones are ordered so that the most specific tasks come first. /// Deduplicates the tasks by their labels and context and splits the ordered list into two: used tasks and the rest, newly resolved tasks. pub fn used_and_current_resolved_tasks( &self, task_contexts: Arc<TaskContexts>, cx: &mut Context<Self>, ) -> Task<( Vec<(TaskSourceKind, ResolvedTask)>, Vec<(TaskSourceKind, ResolvedTask)>, )> { let fs = self.fs.clone(); let worktree = task_contexts.worktree(); let location = task_contexts.location(); let language = location.and_then(|location| location.buffer.read(cx).language()); let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); let file = location.and_then(|location| location.buffer.read(cx).file().cloned()); let mut task_labels_to_ids = HashMap::<String, HashSet<TaskId>>::default(); let mut lru_score = 0_u32; let previously_spawned_tasks = self .last_scheduled_tasks .iter() .rev() .filter(|(task_kind, _)| { if matches!(task_kind, TaskSourceKind::Language { .. }) { Some(task_kind) == task_source_kind.as_ref() } else { true } }) .filter(|(_, resolved_task)| { match task_labels_to_ids.entry(resolved_task.resolved_label.clone()) { hash_map::Entry::Occupied(mut o) => { o.get_mut().insert(resolved_task.id.clone()); // Neber allow duplicate reused tasks with the same labels false } hash_map::Entry::Vacant(v) => { v.insert(HashSet::from_iter(Some(resolved_task.id.clone()))); true } } }) .map(|(task_source_kind, resolved_task)| { ( task_source_kind.clone(), resolved_task.clone(), post_inc(&mut lru_score), ) }) .sorted_unstable_by(task_lru_comparator) .map(|(kind, task, _)| (kind, task)) .collect::<Vec<_>>(); let not_used_score = post_inc(&mut lru_score); let global_tasks = self.global_templates_from_settings().collect::<Vec<_>>(); let associated_tasks = language .filter(|language| { language_settings(Some(language.name()), file.as_ref(), cx) .tasks .enabled }) .and_then(|language| { language .context_provider() .map(|provider| provider.associated_tasks(fs, file, cx)) }); let worktree_tasks = worktree .into_iter() .flat_map(|worktree| self.worktree_templates_from_settings(worktree)) .collect::<Vec<_>>(); let task_contexts = task_contexts.clone(); cx.background_spawn(async move { let language_tasks = if let Some(task) = associated_tasks {