Prefer project (worktree) tasks to language/global tasks in task::Spawn (#21706)

`Inventory::list_tasks()` in `project` crate now is ordered by task
types. Worktree tasks comes first, language tasks second and global
tasks last.
That leads to `spawn_task_with_name()` from `task_ui` crate will find
worktree task first, so it's possible to override global tasks at
project level.

* `Inventory::templates_from_settings()` splitted to
`Inventory::global_templates_from_settings()` and
`Inventory::worktree_templates_from_settings()`.

* In tests function `list_tasks()` renamed to
`list_tasks_sorted_by_last_used()`, because it call's
`Inventory::used_and_current_resolved_tasks()`. Also added
`list_tasks()` which calls `Inventory::list_tasks()`.

Closes #20987 

Release Notes:

- Fix task::Spawn to search for task name in project tasks first.
This commit is contained in:
IViktorov 2024-12-14 10:30:48 +03:00 committed by GitHub
parent ff2d20780f
commit cbc226597c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -81,7 +81,8 @@ impl Inventory {
} }
/// Pulls its task sources relevant to the worktree and the language given, /// Pulls its task sources relevant to the worktree and the language given,
/// returns all task templates with their source kinds, in no specific order. /// 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( pub fn list_tasks(
&self, &self,
file: Option<Arc<dyn File>>, file: Option<Arc<dyn File>>,
@ -92,13 +93,15 @@ impl Inventory {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name().0, name: language.name().0,
}); });
let global_tasks = self.global_templates_from_settings();
let language_tasks = language let language_tasks = language
.and_then(|language| language.context_provider()?.associated_tasks(file, cx)) .and_then(|language| language.context_provider()?.associated_tasks(file, cx))
.into_iter() .into_iter()
.flat_map(|tasks| tasks.0.into_iter()) .flat_map(|tasks| tasks.0.into_iter())
.flat_map(|task| Some((task_source_kind.clone()?, task))); .flat_map(|task| Some((task_source_kind.clone()?, task)))
.chain(global_tasks);
self.templates_from_settings(worktree) self.worktree_templates_from_settings(worktree)
.chain(language_tasks) .chain(language_tasks)
.collect() .collect()
} }
@ -165,14 +168,18 @@ impl Inventory {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let not_used_score = post_inc(&mut lru_score); let not_used_score = post_inc(&mut lru_score);
let global_tasks = self.global_templates_from_settings();
let language_tasks = language let language_tasks = language
.and_then(|language| language.context_provider()?.associated_tasks(file, cx)) .and_then(|language| language.context_provider()?.associated_tasks(file, cx))
.into_iter() .into_iter()
.flat_map(|tasks| tasks.0.into_iter()) .flat_map(|tasks| tasks.0.into_iter())
.flat_map(|task| Some((task_source_kind.clone()?, task))); .flat_map(|task| Some((task_source_kind.clone()?, task)))
let new_resolved_tasks = self .chain(global_tasks);
.templates_from_settings(worktree) let worktree_tasks = self
.chain(language_tasks) .worktree_templates_from_settings(worktree)
.chain(language_tasks);
let new_resolved_tasks = worktree_tasks
.filter_map(|(kind, task)| { .filter_map(|(kind, task)| {
let id_base = kind.to_id_base(); let id_base = kind.to_id_base();
Some(( Some((
@ -235,9 +242,8 @@ impl Inventory {
self.last_scheduled_tasks.retain(|(_, task)| &task.id != id); self.last_scheduled_tasks.retain(|(_, task)| &task.id != id);
} }
fn templates_from_settings( fn global_templates_from_settings(
&self, &self,
worktree: Option<WorktreeId>,
) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> { ) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
self.templates_from_settings self.templates_from_settings
.global .global
@ -252,7 +258,13 @@ impl Inventory {
template, template,
) )
}) })
.chain(worktree.into_iter().flat_map(|worktree| { }
fn worktree_templates_from_settings(
&self,
worktree: Option<WorktreeId>,
) -> impl '_ + Iterator<Item = (TaskSourceKind, TaskTemplate)> {
worktree.into_iter().flat_map(|worktree| {
self.templates_from_settings self.templates_from_settings
.worktree .worktree
.get(&worktree) .get(&worktree)
@ -273,7 +285,7 @@ impl Inventory {
template.clone(), template.clone(),
) )
}) })
})) })
} }
/// Updates in-memory task metadata from the JSON string given. /// Updates in-memory task metadata from the JSON string given.
@ -407,6 +419,25 @@ mod test_inventory {
inventory: &Model<Inventory>, inventory: &Model<Inventory>,
worktree: Option<WorktreeId>, worktree: Option<WorktreeId>,
cx: &mut TestAppContext, cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> {
inventory.update(cx, |inventory, cx| {
let task_context = &TaskContext::default();
inventory
.list_tasks(None, None, worktree, cx)
.into_iter()
.filter_map(|(source_kind, task)| {
let id_base = source_kind.to_id_base();
Some((source_kind, task.resolve_task(&id_base, task_context)?))
})
.map(|(source_kind, resolved_task)| (source_kind, resolved_task.resolved_label))
.collect()
})
}
pub(super) async fn list_tasks_sorted_by_last_used(
inventory: &Model<Inventory>,
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> { ) -> Vec<(TaskSourceKind, String)> {
let (used, current) = inventory.update(cx, |inventory, cx| { let (used, current) = inventory.update(cx, |inventory, cx| {
inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx) inventory.used_and_current_resolved_tasks(worktree, None, &TaskContext::default(), cx)
@ -789,6 +820,30 @@ mod tests {
.unwrap(); .unwrap();
}); });
assert_eq!(
list_tasks_sorted_by_last_used(&inventory, None, cx).await,
worktree_independent_tasks,
"Without a worktree, only worktree-independent tasks should be listed"
);
assert_eq!(
list_tasks_sorted_by_last_used(&inventory, Some(worktree_1), cx).await,
worktree_1_tasks
.iter()
.chain(worktree_independent_tasks.iter())
.cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>(),
);
assert_eq!(
list_tasks_sorted_by_last_used(&inventory, Some(worktree_2), cx).await,
worktree_2_tasks
.iter()
.chain(worktree_independent_tasks.iter())
.cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>(),
);
assert_eq!( assert_eq!(
list_tasks(&inventory, None, cx).await, list_tasks(&inventory, None, cx).await,
worktree_independent_tasks, worktree_independent_tasks,
@ -800,7 +855,6 @@ mod tests {
.iter() .iter()
.chain(worktree_independent_tasks.iter()) .chain(worktree_independent_tasks.iter())
.cloned() .cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
assert_eq!( assert_eq!(
@ -809,7 +863,6 @@ mod tests {
.iter() .iter()
.chain(worktree_independent_tasks.iter()) .chain(worktree_independent_tasks.iter())
.cloned() .cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
} }