1610 lines · 58.4 KB · Rust
RawDownload
//! 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::PathBuf,    sync::Arc,}; use anyhow::Result;use collections::{HashMap, HashSet, VecDeque};use dap::DapRegistry;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 _, post_inc, rel_path::RelPath};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 {    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<RelPath>, 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.clone(),                        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.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: Arc<RelPath>,        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.as_unix_str())            }            Self::Language { name } => format!("language_{name}"),            Self::Lsp {                server,                language_name,            } => format!("lsp_{language_name}_{server}"),        }    }} impl Inventory {    pub fn new(cx: &mut App) -> Entity<Self> {        cx.new(|_| Self {            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_front((            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 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(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 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(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 {                task.await.map(|templates| {                    templates                        .0                        .into_iter()

Lines 1–500 of 1610.