Rework task modal (#10341)

New list (used tasks are above the separator line, sorted by the usage
recency), then all language tasks, then project-local and global tasks
are listed.
Note that there are two test tasks (for `test_name_1` and `test_name_2`
functions) that are created from the same task template:
<img width="563" alt="Screenshot 2024-04-10 at 01 00 46"
src="https://github.com/zed-industries/zed/assets/2690773/7455a82f-2af2-47bf-99bd-d9c5a36e64ab">

Tasks are deduplicated by labels, with the used tasks left in case of
the conflict with the new tasks from the template:
<img width="555" alt="Screenshot 2024-04-10 at 01 01 06"
src="https://github.com/zed-industries/zed/assets/2690773/8f5a249e-abec-46ef-a991-08c6d0348648">

Regular recent tasks can be now removed too:
<img width="565" alt="Screenshot 2024-04-10 at 01 00 55"
src="https://github.com/zed-industries/zed/assets/2690773/0976b8fe-b5d7-4d2a-953d-1d8b1f216192">

When the caret is in the place where no function symbol could be
retrieved, no cargo tests for function are listed in tasks:
<img width="556" alt="image"
src="https://github.com/zed-industries/zed/assets/2690773/df30feba-fe27-4645-8be9-02afc70f02da">


Part of https://github.com/zed-industries/zed/issues/10132
Reworks the task code to simplify it and enable proper task labels.

* removes `trait Task`, renames `Definition` into `TaskTemplate` and use
that instead of `Arc<dyn Task>` everywhere
* implement more generic `TaskId` generation that depends on the
`TaskContext` and `TaskTemplate`
* remove `TaskId` out of the template and only create it after
"resolving" the template into the `ResolvedTask`: this way, task
templates, task state (`TaskContext`) and task "result" (resolved state)
are clearly separated and are not mixed
* implement the logic for filtering out non-related language tasks and
tasks that have non-resolved Zed task variables
* rework Zed template-vs-resolved-task display in modal: now all reruns
and recently used tasks are resolved tasks with "fixed" context (unless
configured otherwise in the task json) that are always shown, and Zed
can add on top tasks with different context that are derived from the
same template as the used, resolved tasks
* sort the tasks list better, showing more specific and least recently
used tasks higher
* shows a separator between used and unused tasks, allow removing the
used tasks same as the oneshot ones
* remote the Oneshot task source as redundant: all oneshot tasks are now
stored in the inventory's history
* when reusing the tasks as query in the modal, paste the expanded task
label now, show trimmed resolved label in the modal
* adjusts Rust and Elixir task labels to be more descriptive and closer
to bash scripts

Release Notes:

- Improved task modal ordering, run and deletion capabilities
This commit is contained in:
Kirill Bulatov 2024-04-11 01:02:04 +02:00 committed by GitHub
parent b0eda77d73
commit d1ad96782c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 1103 additions and 671 deletions

View file

@ -112,8 +112,6 @@ pub use fs::*;
pub use language::Location;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
#[cfg(feature = "test-support")]
pub use task_inventory::test_inventory::*;
pub use task_inventory::{Inventory, TaskSourceKind};
pub use worktree::{
DiagnosticSummary, Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId,
@ -7452,15 +7450,12 @@ impl Project {
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(
format!("local_tasks_for_workspace_{remote_worktree_id}"),
TrackedFile::new(tasks_file_rx, cx),
cx,
)
StaticSource::new(TrackedFile::new(tasks_file_rx, cx), cx)
},
cx,
);
@ -7477,14 +7472,12 @@ impl Project {
TaskSourceKind::Worktree {
id: remote_worktree_id,
abs_path,
id_base: "local_vscode_tasks_for_worktree",
},
|cx| {
let tasks_file_rx =
watch_config_file(&cx.background_executor(), fs, task_abs_path);
StaticSource::new(
format!(
"local_vscode_tasks_for_workspace_{remote_worktree_id}"
),
TrackedFile::new_convertible::<task::VsCodeTaskFile>(
tasks_file_rx,
cx,

View file

@ -159,12 +159,12 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
});
let all_tasks = project
.update(cx, |project, cx| {
project.task_inventory().update(cx, |inventory, cx| {
inventory.list_tasks(None, None, false, cx)
})
project
.task_inventory()
.update(cx, |inventory, cx| inventory.list_tasks(None, None, cx))
})
.into_iter()
.map(|(source_kind, task)| (source_kind, task.name().to_string()))
.map(|(source_kind, task)| (source_kind, task.label))
.collect::<Vec<_>>();
assert_eq!(
all_tasks,
@ -172,14 +172,16 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
(
TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/.zed/tasks.json")
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
},
"cargo check".to_string()
),
(
TaskSourceKind::Worktree {
id: workree_id,
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json")
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
id_base: "local_tasks_for_worktree",
},
"cargo check".to_string()
),

View file

@ -2,22 +2,23 @@
use std::{
any::TypeId,
cmp,
path::{Path, PathBuf},
sync::Arc,
};
use collections::{HashMap, VecDeque};
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
use itertools::Itertools;
use itertools::{Either, Itertools};
use language::Language;
use task::{static_source::tasks_for, Task, TaskContext, TaskSource};
use task::{ResolvedTask, TaskContext, TaskId, TaskSource, TaskTemplate};
use util::{post_inc, NumericPrefixWithSuffix};
use worktree::WorktreeId;
/// Inventory tracks available tasks for a given project.
pub struct Inventory {
sources: Vec<SourceInInventory>,
last_scheduled_tasks: VecDeque<(Arc<dyn Task>, TaskContext)>,
last_scheduled_tasks: VecDeque<(TaskSourceKind, ResolvedTask)>,
}
struct SourceInInventory {
@ -28,32 +29,56 @@ struct SourceInInventory {
}
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
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(PathBuf),
AbsPath {
id_base: &'static str,
abs_path: PathBuf,
},
/// Tasks from the worktree's .zed/task.json
Worktree { id: WorktreeId, abs_path: PathBuf },
Worktree {
id: WorktreeId,
abs_path: PathBuf,
id_base: &'static str,
},
/// Languages-specific tasks coming from extensions.
Language { name: Arc<str> },
}
impl TaskSourceKind {
fn abs_path(&self) -> Option<&Path> {
pub fn abs_path(&self) -> Option<&Path> {
match self {
Self::AbsPath(abs_path) | Self::Worktree { abs_path, .. } => Some(abs_path),
Self::AbsPath { abs_path, .. } | Self::Worktree { abs_path, .. } => Some(abs_path),
Self::UserInput | Self::Language { .. } => None,
}
}
fn worktree(&self) -> Option<WorktreeId> {
pub fn worktree(&self) -> Option<WorktreeId> {
match self {
Self::Worktree { id, .. } => Some(*id),
_ => None,
}
}
pub fn to_id_base(&self) -> String {
match self {
TaskSourceKind::UserInput => "oneshot".to_string(),
TaskSourceKind::AbsPath { id_base, abs_path } => {
format!("{id_base}_{}", abs_path.display())
}
TaskSourceKind::Worktree {
id,
id_base,
abs_path,
} => {
format!("{id_base}_{id}_{}", abs_path.display())
}
TaskSourceKind::Language { name } => format!("language_{name}"),
}
}
}
impl Inventory {
@ -111,14 +136,17 @@ impl Inventory {
self.sources.retain(|s| s.kind.worktree() != Some(worktree));
}
pub fn source<T: TaskSource>(&self) -> Option<Model<Box<dyn TaskSource>>> {
pub fn source<T: TaskSource>(&self) -> Option<(Model<Box<dyn TaskSource>>, TaskSourceKind)> {
let target_type_id = std::any::TypeId::of::<T>();
self.sources.iter().find_map(
|SourceInInventory {
type_id, source, ..
type_id,
source,
kind,
..
}| {
if &target_type_id == type_id {
Some(source.clone())
Some((source.clone(), kind.clone()))
} else {
None
}
@ -126,47 +154,23 @@ impl Inventory {
)
}
/// Pulls its sources to list runnables for the editor given, or all runnables for no editor.
/// Pulls its task sources relevant to the worktree and the language given,
/// returns all task templates with their source kinds, in no specific order.
pub fn list_tasks(
&self,
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
lru: bool,
cx: &mut AppContext,
) -> Vec<(TaskSourceKind, Arc<dyn Task>)> {
) -> Vec<(TaskSourceKind, TaskTemplate)> {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(),
});
let language_tasks = language
.and_then(|language| {
let tasks = language.context_provider()?.associated_tasks()?;
Some((tasks, language))
})
.map(|(tasks, language)| {
let language_name = language.name();
let id_base = format!("buffer_source_{language_name}");
tasks_for(tasks, &id_base)
})
.unwrap_or_default()
.and_then(|language| language.context_provider()?.associated_tasks())
.into_iter()
.flat_map(|tasks| tasks.0.into_iter())
.flat_map(|task| Some((task_source_kind.as_ref()?, task)));
let mut lru_score = 0_u32;
let tasks_by_usage = if lru {
self.last_scheduled_tasks.iter().rev().fold(
HashMap::default(),
|mut tasks, (task, context)| {
tasks
.entry(task.id().clone())
.or_insert_with(|| (post_inc(&mut lru_score), Some(context)));
tasks
},
)
} else {
HashMap::default()
};
let not_used_task_context = None;
let not_used_score = (post_inc(&mut lru_score), not_used_task_context);
self.sources
.iter()
.filter(|source| {
@ -177,101 +181,173 @@ impl Inventory {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.0
.into_iter()
.map(|task| (&source.kind, task))
})
.chain(language_tasks)
.map(|task| {
let usages = if lru {
tasks_by_usage
.get(&task.1.id())
.copied()
.unwrap_or(not_used_score)
} else {
not_used_score
};
(task, usages)
})
.sorted_unstable_by(
|((kind_a, task_a), usages_a), ((kind_b, task_b), usages_b)| {
usages_a
.0
.cmp(&usages_b.0)
.then(
kind_a
.worktree()
.is_none()
.cmp(&kind_b.worktree().is_none()),
)
.then(kind_a.worktree().cmp(&kind_b.worktree()))
.then(
kind_a
.abs_path()
.is_none()
.cmp(&kind_b.abs_path().is_none()),
)
.then(kind_a.abs_path().cmp(&kind_b.abs_path()))
.then({
NumericPrefixWithSuffix::from_numeric_prefixed_str(task_a.name())
.cmp(&NumericPrefixWithSuffix::from_numeric_prefixed_str(
task_b.name(),
))
.then(task_a.name().cmp(task_b.name()))
})
},
)
.map(|((kind, task), _)| (kind.clone(), task))
.map(|(task_source_kind, task)| (task_source_kind.clone(), task))
.collect()
}
/// Pulls its task sources relevant to the worktree and the language given and resolves them with the [`TaskContext`] 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 splits the ordered list into two: used tasks and the rest, newly resolved tasks.
pub fn used_and_current_resolved_tasks(
&self,
language: Option<Arc<Language>>,
worktree: Option<WorktreeId>,
task_context: TaskContext,
cx: &mut AppContext,
) -> (
Vec<(TaskSourceKind, ResolvedTask)>,
Vec<(TaskSourceKind, ResolvedTask)>,
) {
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
name: language.name(),
});
let language_tasks = language
.and_then(|language| language.context_provider()?.associated_tasks())
.into_iter()
.flat_map(|tasks| tasks.0.into_iter())
.flat_map(|task| Some((task_source_kind.as_ref()?, task)));
let mut lru_score = 0_u32;
let mut task_usage = self.last_scheduled_tasks.iter().rev().fold(
HashMap::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)));
tasks
},
);
let not_used_score = post_inc(&mut lru_score);
let current_resolved_tasks = self
.sources
.iter()
.filter(|source| {
let source_worktree = source.kind.worktree();
worktree.is_none() || source_worktree.is_none() || source_worktree == worktree
})
.flat_map(|source| {
source
.source
.update(cx, |source, cx| source.tasks_to_schedule(cx))
.0
.into_iter()
.map(|task| (&source.kind, task))
})
.chain(language_tasks)
.filter_map(|(kind, task)| {
let id_base = kind.to_id_base();
Some((kind, task.resolve_task(&id_base, task_context.clone())?))
})
.map(|(kind, task)| {
let lru_score = task_usage
.remove(&task.id)
.map(|(_, _, lru_score)| lru_score)
.unwrap_or(not_used_score);
(kind.clone(), task, lru_score)
})
.collect::<Vec<_>>();
let previous_resolved_tasks = task_usage
.into_iter()
.map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score));
previous_resolved_tasks
.chain(current_resolved_tasks)
.sorted_unstable_by(task_lru_comparator)
.unique_by(|(kind, task, _)| (kind.clone(), task.resolved_label.clone()))
.partition_map(|(kind, task, lru_index)| {
if lru_index < not_used_score {
Either::Left((kind, task))
} else {
Either::Right((kind, task))
}
})
}
/// Returns the last scheduled task, if any of the sources contains one with the matching id.
pub fn last_scheduled_task(&self) -> Option<(Arc<dyn Task>, TaskContext)> {
pub fn last_scheduled_task(&self) -> Option<(TaskSourceKind, ResolvedTask)> {
self.last_scheduled_tasks.back().cloned()
}
/// Registers task "usage" as being scheduled to be used for LRU sorting when listing all tasks.
pub fn task_scheduled(&mut self, task: Arc<dyn Task>, task_context: TaskContext) {
self.last_scheduled_tasks.push_back((task, task_context));
pub fn task_scheduled(
&mut self,
task_source_kind: TaskSourceKind,
resolved_task: ResolvedTask,
) {
self.last_scheduled_tasks
.push_back((task_source_kind, resolved_task));
if self.last_scheduled_tasks.len() > 5_000 {
self.last_scheduled_tasks.pop_front();
}
}
/// Deletes a resolved task from history, using its id.
/// A similar may still resurface in `used_and_current_resolved_tasks` when its [`TaskTemplate`] is resolved again.
pub fn delete_previously_used(&mut self, id: &TaskId) {
self.last_scheduled_tasks.retain(|(_, task)| &task.id != id);
}
}
#[cfg(any(test, feature = "test-support"))]
pub mod test_inventory {
use std::sync::Arc;
fn task_lru_comparator(
(kind_a, task_a, lru_score_a): &(TaskSourceKind, ResolvedTask, u32),
(kind_b, task_b, lru_score_b): &(TaskSourceKind, ResolvedTask, u32),
) -> cmp::Ordering {
lru_score_a
.cmp(&lru_score_b)
.then(task_source_kind_preference(kind_a).cmp(&task_source_kind_preference(kind_b)))
.then(
kind_a
.worktree()
.is_none()
.cmp(&kind_b.worktree().is_none()),
)
.then(kind_a.worktree().cmp(&kind_b.worktree()))
.then(
kind_a
.abs_path()
.is_none()
.cmp(&kind_b.abs_path().is_none()),
)
.then(kind_a.abs_path().cmp(&kind_b.abs_path()))
.then({
NumericPrefixWithSuffix::from_numeric_prefixed_str(&task_a.resolved_label)
.cmp(&NumericPrefixWithSuffix::from_numeric_prefixed_str(
&task_b.resolved_label,
))
.then(task_a.resolved_label.cmp(&task_b.resolved_label))
})
}
fn task_source_kind_preference(kind: &TaskSourceKind) -> u32 {
match kind {
TaskSourceKind::Language { .. } => 1,
TaskSourceKind::UserInput => 2,
TaskSourceKind::Worktree { .. } => 3,
TaskSourceKind::AbsPath { .. } => 4,
}
}
#[cfg(test)]
mod test_inventory {
use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext};
use task::{Task, TaskContext, TaskId, TaskSource};
use itertools::Itertools;
use task::{TaskContext, TaskId, TaskSource, TaskTemplate, TaskTemplates};
use worktree::WorktreeId;
use crate::Inventory;
use super::TaskSourceKind;
use super::{task_source_kind_preference, TaskSourceKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestTask {
pub id: task::TaskId,
pub name: String,
}
impl Task for TestTask {
fn id(&self) -> &TaskId {
&self.id
}
fn name(&self) -> &str {
&self.name
}
fn cwd(&self) -> Option<&str> {
None
}
fn prepare_exec(&self, _cwd: TaskContext) -> Option<task::SpawnInTerminal> {
None
}
id: task::TaskId,
name: String,
}
pub struct StaticTestSource {
@ -279,7 +355,7 @@ pub mod test_inventory {
}
impl StaticTestSource {
pub fn new(
pub(super) fn new(
task_names: impl IntoIterator<Item = String>,
cx: &mut AppContext,
) -> Model<Box<dyn TaskSource>> {
@ -302,12 +378,18 @@ pub mod test_inventory {
fn tasks_to_schedule(
&mut self,
_cx: &mut ModelContext<Box<dyn TaskSource>>,
) -> Vec<Arc<dyn Task>> {
self.tasks
.clone()
.into_iter()
.map(|task| Arc::new(task) as Arc<dyn Task>)
.collect()
) -> TaskTemplates {
TaskTemplates(
self.tasks
.clone()
.into_iter()
.map(|task| TaskTemplate {
label: task.name,
command: "test command".to_string(),
..TaskTemplate::default()
})
.collect(),
)
}
fn as_any(&mut self) -> &mut dyn std::any::Any {
@ -315,47 +397,77 @@ pub mod test_inventory {
}
}
pub fn list_task_names(
pub(super) fn task_template_names(
inventory: &Model<Inventory>,
worktree: Option<WorktreeId>,
lru: bool,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
inventory
.list_tasks(None, worktree, lru, cx)
.list_tasks(None, worktree, cx)
.into_iter()
.map(|(_, task)| task.name().to_string())
.map(|(_, task)| task.label)
.sorted()
.collect()
})
}
pub fn register_task_used(
pub(super) fn resolved_task_names(
inventory: &Model<Inventory>,
worktree: Option<WorktreeId>,
cx: &mut TestAppContext,
) -> Vec<String> {
inventory.update(cx, |inventory, cx| {
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
TaskContext::default(),
cx,
);
used.into_iter()
.chain(current)
.map(|(_, task)| task.original_task.label)
.collect()
})
}
pub(super) fn register_task_used(
inventory: &Model<Inventory>,
task_name: &str,
cx: &mut TestAppContext,
) {
inventory.update(cx, |inventory, cx| {
let task = inventory
.list_tasks(None, None, false, cx)
let (task_source_kind, task) = inventory
.list_tasks(None, None, cx)
.into_iter()
.find(|(_, task)| task.name() == task_name)
.find(|(_, task)| task.label == task_name)
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
inventory.task_scheduled(task.1, TaskContext::default());
let id_base = task_source_kind.to_id_base();
inventory.task_scheduled(
task_source_kind.clone(),
task.resolve_task(&id_base, TaskContext::default())
.unwrap_or_else(|| panic!("Failed to resolve task with name {task_name}")),
);
});
}
pub fn list_tasks(
pub(super) fn list_tasks(
inventory: &Model<Inventory>,
worktree: Option<WorktreeId>,
lru: bool,
cx: &mut TestAppContext,
) -> Vec<(TaskSourceKind, String)> {
inventory.update(cx, |inventory, cx| {
inventory
.list_tasks(None, worktree, lru, cx)
.into_iter()
.map(|(source_kind, task)| (source_kind, task.name().to_string()))
let (used, current) = inventory.used_and_current_resolved_tasks(
None,
worktree,
TaskContext::default(),
cx,
);
let mut all = used;
all.extend(current);
all.into_iter()
.map(|(source_kind, task)| (source_kind, task.resolved_label))
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect()
})
}
@ -371,12 +483,12 @@ mod tests {
#[gpui::test]
fn test_task_list_sorting(cx: &mut TestAppContext) {
let inventory = cx.update(Inventory::new);
let initial_tasks = list_task_names(&inventory, None, true, cx);
let initial_tasks = resolved_task_names(&inventory, None, cx);
assert!(
initial_tasks.is_empty(),
"No tasks expected for empty inventory, but got {initial_tasks:?}"
);
let initial_tasks = list_task_names(&inventory, None, false, cx);
let initial_tasks = task_template_names(&inventory, None, cx);
assert!(
initial_tasks.is_empty(),
"No tasks expected for empty inventory, but got {initial_tasks:?}"
@ -413,24 +525,22 @@ mod tests {
"3_task".to_string(),
];
assert_eq!(
list_task_names(&inventory, None, false, cx),
task_template_names(&inventory, None, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
resolved_task_names(&inventory, None, cx),
&expected_initial_state,
"Tasks with equal amount of usages should be sorted alphanumerically"
);
register_task_used(&inventory, "2_task", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
task_template_names(&inventory, None, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
resolved_task_names(&inventory, None, cx),
vec![
"2_task".to_string(),
"1_a_task".to_string(),
@ -444,12 +554,11 @@ mod tests {
register_task_used(&inventory, "1_task", cx);
register_task_used(&inventory, "3_task", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
task_template_names(&inventory, None, cx),
&expected_initial_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
resolved_task_names(&inventory, None, cx),
vec![
"3_task".to_string(),
"1_task".to_string(),
@ -468,20 +577,19 @@ mod tests {
);
});
let expected_updated_state = [
"10_hello".to_string(),
"11_hello".to_string(),
"1_a_task".to_string(),
"1_task".to_string(),
"2_task".to_string(),
"3_task".to_string(),
"10_hello".to_string(),
"11_hello".to_string(),
];
assert_eq!(
list_task_names(&inventory, None, false, cx),
task_template_names(&inventory, None, cx),
&expected_updated_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
resolved_task_names(&inventory, None, cx),
vec![
"3_task".to_string(),
"1_task".to_string(),
@ -494,12 +602,11 @@ mod tests {
register_task_used(&inventory, "11_hello", cx);
assert_eq!(
list_task_names(&inventory, None, false, cx),
task_template_names(&inventory, None, cx),
&expected_updated_state,
"Task list without lru sorting, should be sorted alphanumerically"
);
assert_eq!(
list_task_names(&inventory, None, true, cx),
resolved_task_names(&inventory, None, cx),
vec![
"11_hello".to_string(),
"3_task".to_string(),
@ -533,7 +640,10 @@ mod tests {
cx,
);
inventory.add_source(
TaskSourceKind::AbsPath(path_1.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_1".to_string(), common_name.to_string()],
@ -543,7 +653,10 @@ mod tests {
cx,
);
inventory.add_source(
TaskSourceKind::AbsPath(path_2.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
|cx| {
StaticTestSource::new(
vec!["static_source_2".to_string(), common_name.to_string()],
@ -556,6 +669,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_1,
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
@ -569,6 +683,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_2,
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
|cx| {
StaticTestSource::new(
@ -582,19 +697,31 @@ mod tests {
let worktree_independent_tasks = vec![
(
TaskSourceKind::AbsPath(path_1.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
common_name.to_string(),
),
(
TaskSourceKind::AbsPath(path_1.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_1.to_path_buf(),
},
"static_source_1".to_string(),
),
(
TaskSourceKind::AbsPath(path_2.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
common_name.to_string(),
),
(
TaskSourceKind::AbsPath(path_2.to_path_buf()),
TaskSourceKind::AbsPath {
id_base: "test source",
abs_path: path_2.to_path_buf(),
},
"static_source_2".to_string(),
),
(TaskSourceKind::UserInput, common_name.to_string()),
@ -605,6 +732,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_1,
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
common_name.to_string(),
),
@ -612,6 +740,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_1,
abs_path: worktree_path_1.to_path_buf(),
id_base: "test_source",
},
"worktree_1".to_string(),
),
@ -621,6 +750,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_2,
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
common_name.to_string(),
),
@ -628,6 +758,7 @@ mod tests {
TaskSourceKind::Worktree {
id: worktree_2,
abs_path: worktree_path_2.to_path_buf(),
id_base: "test_source",
},
"worktree_2".to_string(),
),
@ -639,26 +770,26 @@ mod tests {
// worktree-less tasks come later in the list
.chain(worktree_independent_tasks.iter())
.cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>();
assert_eq!(list_tasks(&inventory_with_statics, None, cx), all_tasks);
assert_eq!(
list_tasks(&inventory_with_statics, None, false, cx),
all_tasks,
);
assert_eq!(
list_tasks(&inventory_with_statics, Some(worktree_1), false, cx),
list_tasks(&inventory_with_statics, Some(worktree_1), cx),
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(&inventory_with_statics, Some(worktree_2), false, cx),
list_tasks(&inventory_with_statics, Some(worktree_2), cx),
worktree_2_tasks
.iter()
.chain(worktree_independent_tasks.iter())
.cloned()
.sorted_by_key(|(kind, label)| (task_source_kind_preference(kind), label.clone()))
.collect::<Vec<_>>(),
);
}