diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1bf0fa36c3..44ef4ca61a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -401,7 +401,7 @@ impl Default for ScrollbarMarkerState { #[derive(Clone, Debug)] struct RunnableTasks { - templates: SmallVec<[(TaskSourceKind, TaskTemplate); 1]>, + templates: Vec<(TaskSourceKind, TaskTemplate)>, // We need the column at which the task context evaluation should take place. column: u32, } @@ -7725,10 +7725,7 @@ impl Editor { &self, runnable: &mut Runnable, cx: &WindowContext<'_>, - ) -> ( - SmallVec<[(TaskSourceKind, TaskTemplate); 1]>, - Option, - ) { + ) -> (Vec<(TaskSourceKind, TaskTemplate)>, Option) { let Some(project) = self.project.as_ref() else { return Default::default(); }; @@ -7743,22 +7740,32 @@ impl Editor { let inventory = inventory.read(cx); let tags = mem::take(&mut runnable.tags); - ( - SmallVec::from_iter( - tags.into_iter() - .flat_map(|tag| { - let tag = tag.0.clone(); - inventory - .list_tasks(Some(runnable.language.clone()), worktree_id) - .into_iter() - .filter(move |(_, template)| { - template.tags.iter().any(|source_tag| source_tag == &tag) - }) + let mut tags: Vec<_> = tags + .into_iter() + .flat_map(|tag| { + let tag = tag.0.clone(); + inventory + .list_tasks(Some(runnable.language.clone()), worktree_id) + .into_iter() + .filter(move |(_, template)| { + template.tags.iter().any(|source_tag| source_tag == &tag) }) - .sorted_by_key(|(kind, _)| kind.to_owned()), - ), - worktree_id, - ) + }) + .sorted_by_key(|(kind, _)| kind.to_owned()) + .collect(); + if let Some((leading_tag_source, _)) = tags.first() { + // Strongest source wins; if we have worktree tag binding, prefer that to + // global and language bindings; + // if we have a global binding, prefer that to language binding. + let first_mismatch = tags + .iter() + .position(|(tag_source, _)| tag_source != leading_tag_source); + if let Some(index) = first_mismatch { + tags.truncate(index); + } + } + + (tags, worktree_id) } pub fn move_to_enclosing_bracket( diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index e974ba2bef..4b2d53c61b 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -192,6 +192,12 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) assert_eq!( all_tasks, vec![ + ( + global_task_source_kind.clone(), + "cargo check".to_string(), + vec!["check".to_string(), "--all".to_string()], + HashMap::default(), + ), ( TaskSourceKind::Worktree { id: workree_id, @@ -202,12 +208,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) vec!["check".to_string()], HashMap::default(), ), - ( - global_task_source_kind.clone(), - "cargo check".to_string(), - vec!["check".to_string(), "--all".to_string()], - HashMap::default(), - ), ] ); }); @@ -278,16 +278,6 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) assert_eq!( all_tasks, vec![ - ( - TaskSourceKind::Worktree { - id: workree_id, - abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), - id_base: "local_tasks_for_worktree", - }, - "cargo check".to_string(), - vec!["check".to_string()], - HashMap::default(), - ), ( TaskSourceKind::Worktree { id: workree_id, @@ -305,6 +295,16 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) "-Zunstable-options".to_string() ))), ), + ( + TaskSourceKind::Worktree { + id: workree_id, + abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"), + id_base: "local_tasks_for_worktree", + }, + "cargo check".to_string(), + vec!["check".to_string()], + HashMap::default(), + ), ] ); }); diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 73ce0b1739..c4bf8dd7f5 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -6,7 +6,7 @@ use std::{ sync::Arc, }; -use collections::{hash_map, HashMap, VecDeque}; +use collections::{btree_map, BTreeMap, VecDeque}; use gpui::{AppContext, Context, Model, ModelContext}; use itertools::{Either, Itertools}; use language::Language; @@ -32,17 +32,17 @@ struct SourceInInventory { pub enum TaskSourceKind { /// bash-like commands spawned by users, not associated with any path UserInput, - /// ~/.config/zed/task.json - like global files with task definitions, applicable to any path - AbsPath { - id_base: &'static str, - abs_path: PathBuf, - }, /// Tasks from the worktree's .zed/task.json Worktree { id: WorktreeId, abs_path: PathBuf, id_base: &'static str, }, + /// ~/.config/zed/task.json - like global files with task definitions, applicable to any path + AbsPath { + id_base: &'static str, + abs_path: PathBuf, + }, /// Languages-specific tasks coming from extensions. Language { name: Arc }, } @@ -197,7 +197,7 @@ impl Inventory { } }) .fold( - HashMap::default(), + BTreeMap::default(), |mut tasks, (task_source_kind, resolved_task)| { tasks.entry(&resolved_task.id).or_insert_with(|| { (task_source_kind, resolved_task, post_inc(&mut lru_score)) @@ -238,18 +238,18 @@ impl Inventory { .into_iter() .map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score)); - let mut tasks_by_label = HashMap::default(); + let mut tasks_by_label = BTreeMap::default(); tasks_by_label = previously_spawned_tasks.into_iter().fold( tasks_by_label, |mut tasks_by_label, (source, task, lru_score)| { match tasks_by_label.entry((source, task.resolved_label.clone())) { - hash_map::Entry::Occupied(mut o) => { + btree_map::Entry::Occupied(mut o) => { let (_, previous_lru_score) = o.get(); if previous_lru_score >= &lru_score { o.insert((task, lru_score)); } } - hash_map::Entry::Vacant(v) => { + btree_map::Entry::Vacant(v) => { v.insert((task, lru_score)); } } @@ -260,7 +260,7 @@ impl Inventory { tasks_by_label, |mut tasks_by_label, (source, task, lru_score)| { match tasks_by_label.entry((source, task.resolved_label.clone())) { - hash_map::Entry::Occupied(mut o) => { + btree_map::Entry::Occupied(mut o) => { let (previous_task, _) = o.get(); let new_template = task.original_task(); if new_template.ignore_previously_resolved @@ -269,7 +269,7 @@ impl Inventory { o.insert((task, lru_score)); } } - hash_map::Entry::Vacant(v) => { + btree_map::Entry::Vacant(v) => { v.insert((task, lru_score)); } } @@ -665,13 +665,6 @@ mod tests { }); cx.run_until_parked(); let worktree_independent_tasks = vec![ - ( - TaskSourceKind::AbsPath { - id_base: "test source", - abs_path: path_2.to_path_buf(), - }, - common_name.to_string(), - ), ( TaskSourceKind::AbsPath { id_base: "test source", @@ -686,6 +679,13 @@ mod tests { }, common_name.to_string(), ), + ( + TaskSourceKind::AbsPath { + id_base: "test source", + abs_path: path_2.to_path_buf(), + }, + common_name.to_string(), + ), ( TaskSourceKind::AbsPath { id_base: "test source", diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 1e57dc7cde..b31b8db5df 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -16,7 +16,7 @@ pub use vscode_format::VsCodeTaskFile; /// Task identifier, unique within the application. /// Based on it, task reruns and terminal tabs are managed. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TaskId(pub String); /// Contains all information needed by Zed to spawn a new terminal tab for the given task.