Allow .zed/tasks.json local configs (#8536)
 Follow-up of https://github.com/zed-industries/zed/issues/7108#issuecomment-1960746397 Makes more clear where each task came from, auto (re)load .zed/config.json changes, properly filtering out other worktree tasks. Release Notes: - Added local task configurations
This commit is contained in:
parent
7f954cbbb8
commit
ac30ded80e
12 changed files with 715 additions and 281 deletions
|
@ -59,7 +59,7 @@ use rand::prelude::*;
|
|||
use rpc::{ErrorCode, ErrorExt as _};
|
||||
use search::SearchQuery;
|
||||
use serde::Serialize;
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::{watch_config_file, Settings, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smol::channel::{Receiver, Sender};
|
||||
|
@ -82,11 +82,15 @@ use std::{
|
|||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use task::static_source::StaticSource;
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId};
|
||||
use util::{
|
||||
debug_panic, defer, http::HttpClient, merge_json_value_into,
|
||||
paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _,
|
||||
debug_panic, defer,
|
||||
http::HttpClient,
|
||||
merge_json_value_into,
|
||||
paths::{LOCAL_SETTINGS_RELATIVE_PATH, LOCAL_TASKS_RELATIVE_PATH},
|
||||
post_inc, ResultExt, TryFutureExt as _,
|
||||
};
|
||||
|
||||
pub use fs::*;
|
||||
|
@ -95,7 +99,7 @@ pub use language::Location;
|
|||
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
|
||||
pub use project_core::project_settings;
|
||||
pub use project_core::worktree::{self, *};
|
||||
pub use task_inventory::Inventory;
|
||||
pub use task_inventory::{Inventory, TaskSourceKind};
|
||||
|
||||
const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
|
||||
const SERVER_REINSTALL_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
@ -6615,6 +6619,10 @@ impl Project {
|
|||
})
|
||||
.detach();
|
||||
|
||||
self.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.remove_worktree_sources(id_to_remove);
|
||||
});
|
||||
|
||||
self.worktrees.retain(|worktree| {
|
||||
if let Some(worktree) = worktree.upgrade() {
|
||||
let id = worktree.read(cx).id();
|
||||
|
@ -6972,32 +6980,66 @@ impl Project {
|
|||
changes: &UpdatedEntriesSet,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if worktree.read(cx).as_local().is_none() {
|
||||
return;
|
||||
}
|
||||
let project_id = self.remote_id();
|
||||
let worktree_id = worktree.entity_id();
|
||||
let worktree = worktree.read(cx).as_local().unwrap();
|
||||
let remote_worktree_id = worktree.id();
|
||||
let remote_worktree_id = worktree.read(cx).id();
|
||||
|
||||
let mut settings_contents = Vec::new();
|
||||
for (path, _, change) in changes.iter() {
|
||||
if path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) {
|
||||
let removed = change == &PathChange::Removed;
|
||||
let abs_path = match worktree.read(cx).absolutize(path) {
|
||||
Ok(abs_path) => abs_path,
|
||||
Err(e) => {
|
||||
log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if abs_path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) {
|
||||
let settings_dir = Arc::from(
|
||||
path.ancestors()
|
||||
.nth(LOCAL_SETTINGS_RELATIVE_PATH.components().count())
|
||||
.unwrap(),
|
||||
);
|
||||
let fs = self.fs.clone();
|
||||
let removed = *change == PathChange::Removed;
|
||||
let abs_path = worktree.absolutize(path);
|
||||
settings_contents.push(async move {
|
||||
(
|
||||
settings_dir,
|
||||
if removed {
|
||||
None
|
||||
} else {
|
||||
Some(async move { fs.load(&abs_path?).await }.await)
|
||||
Some(async move { fs.load(&abs_path).await }.await)
|
||||
},
|
||||
)
|
||||
});
|
||||
} else if abs_path.ends_with(&*LOCAL_TASKS_RELATIVE_PATH) {
|
||||
self.task_inventory().update(cx, |task_inventory, cx| {
|
||||
if removed {
|
||||
task_inventory.remove_local_static_source(&abs_path);
|
||||
} else {
|
||||
let fs = self.fs.clone();
|
||||
let task_abs_path = abs_path.clone();
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
},
|
||||
|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}"),
|
||||
tasks_file_rx,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,14 +95,24 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
|||
"/the-root",
|
||||
json!({
|
||||
".zed": {
|
||||
"settings.json": r#"{ "tab_size": 8 }"#
|
||||
"settings.json": r#"{ "tab_size": 8 }"#,
|
||||
"tasks.json": r#"[{
|
||||
"label": "cargo check",
|
||||
"command": "cargo",
|
||||
"args": ["check", "--all"]
|
||||
},]"#,
|
||||
},
|
||||
"a": {
|
||||
"a.rs": "fn a() {\n A\n}"
|
||||
},
|
||||
"b": {
|
||||
".zed": {
|
||||
"settings.json": r#"{ "tab_size": 2 }"#
|
||||
"settings.json": r#"{ "tab_size": 2 }"#,
|
||||
"tasks.json": r#"[{
|
||||
"label": "cargo check",
|
||||
"command": "cargo",
|
||||
"args": ["check"]
|
||||
},]"#,
|
||||
},
|
||||
"b.rs": "fn b() {\n B\n}"
|
||||
}
|
||||
|
@ -140,6 +150,38 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
|||
|
||||
assert_eq!(settings_a.tab_size.get(), 8);
|
||||
assert_eq!(settings_b.tab_size.get(), 2);
|
||||
|
||||
let workree_id = project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
});
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, cx| {
|
||||
inventory.list_tasks(None, None, false, cx)
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| (source_kind, task.name().to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json")
|
||||
},
|
||||
"cargo check".to_string()
|
||||
),
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json")
|
||||
},
|
||||
"cargo check".to_string()
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
|
||||
|
||||
use std::{any::TypeId, path::Path, sync::Arc};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use collections::{HashMap, VecDeque};
|
||||
use gpui::{AppContext, Context, Model, ModelContext, Subscription};
|
||||
use itertools::Itertools;
|
||||
use project_core::worktree::WorktreeId;
|
||||
use task::{Task, TaskId, TaskSource};
|
||||
use util::{post_inc, NumericPrefixWithSuffix};
|
||||
|
||||
|
@ -18,6 +23,34 @@ struct SourceInInventory {
|
|||
source: Model<Box<dyn TaskSource>>,
|
||||
_subscription: Subscription,
|
||||
type_id: TypeId,
|
||||
kind: TaskSourceKind,
|
||||
}
|
||||
|
||||
/// Kind of a source the tasks are fetched from, used to display more source information in the UI.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
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),
|
||||
/// Worktree-specific task definitions, e.g. dynamic tasks from open worktree file, or tasks from the worktree's .zed/task.json
|
||||
Worktree { id: WorktreeId, abs_path: PathBuf },
|
||||
}
|
||||
|
||||
impl TaskSourceKind {
|
||||
fn abs_path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::AbsPath(abs_path) | Self::Worktree { abs_path, .. } => Some(abs_path),
|
||||
Self::UserInput => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn worktree(&self) -> Option<WorktreeId> {
|
||||
match self {
|
||||
Self::Worktree { id, .. } => Some(*id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory {
|
||||
|
@ -28,21 +61,53 @@ impl Inventory {
|
|||
})
|
||||
}
|
||||
|
||||
/// Registers a new tasks source, that would be fetched for available tasks.
|
||||
pub fn add_source(&mut self, source: Model<Box<dyn TaskSource>>, cx: &mut ModelContext<Self>) {
|
||||
let _subscription = cx.observe(&source, |_, _, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
/// If the task with the same path was not added yet,
|
||||
/// registers a new tasks source to fetch for available tasks later.
|
||||
/// Unless a source is removed, ignores future additions for the same path.
|
||||
pub fn add_source(
|
||||
&mut self,
|
||||
kind: TaskSourceKind,
|
||||
create_source: impl FnOnce(&mut ModelContext<Self>) -> Model<Box<dyn TaskSource>>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let abs_path = kind.abs_path();
|
||||
if abs_path.is_some() {
|
||||
if let Some(a) = self.sources.iter().find(|s| s.kind.abs_path() == abs_path) {
|
||||
log::debug!("Source for path {abs_path:?} already exists, not adding. Old kind: {OLD_KIND:?}, new kind: {kind:?}", OLD_KIND = a.kind);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let source = create_source(cx);
|
||||
let type_id = source.read(cx).type_id();
|
||||
let source = SourceInInventory {
|
||||
_subscription: cx.observe(&source, |_, _, cx| {
|
||||
cx.notify();
|
||||
}),
|
||||
source,
|
||||
_subscription,
|
||||
type_id,
|
||||
kind,
|
||||
};
|
||||
self.sources.push(source);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// If present, removes the local static source entry that has the given path,
|
||||
/// making corresponding task definitions unavailable in the fetch results.
|
||||
///
|
||||
/// Now, entry for this path can be re-added again.
|
||||
pub fn remove_local_static_source(&mut self, abs_path: &Path) {
|
||||
self.sources.retain(|s| s.kind.abs_path() != Some(abs_path));
|
||||
}
|
||||
|
||||
/// If present, removes the worktree source entry that has the given worktree id,
|
||||
/// making corresponding task definitions unavailable in the fetch results.
|
||||
///
|
||||
/// Now, entry for this path can be re-added again.
|
||||
pub fn remove_worktree_sources(&mut self, worktree: WorktreeId) {
|
||||
self.sources.retain(|s| s.kind.worktree() != Some(worktree));
|
||||
}
|
||||
|
||||
pub fn source<T: TaskSource>(&self) -> Option<Model<Box<dyn TaskSource>>> {
|
||||
let target_type_id = std::any::TypeId::of::<T>();
|
||||
self.sources.iter().find_map(
|
||||
|
@ -62,9 +127,10 @@ impl Inventory {
|
|||
pub fn list_tasks(
|
||||
&self,
|
||||
path: Option<&Path>,
|
||||
worktree: Option<WorktreeId>,
|
||||
lru: bool,
|
||||
cx: &mut AppContext,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
) -> Vec<(TaskSourceKind, Arc<dyn Task>)> {
|
||||
let mut lru_score = 0_u32;
|
||||
let tasks_by_usage = if lru {
|
||||
self.last_scheduled_tasks
|
||||
|
@ -78,18 +144,23 @@ impl Inventory {
|
|||
HashMap::default()
|
||||
};
|
||||
let not_used_score = post_inc(&mut lru_score);
|
||||
|
||||
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_for_path(path, cx))
|
||||
.into_iter()
|
||||
.map(|task| (&source.kind, task))
|
||||
})
|
||||
.map(|task| {
|
||||
let usages = if lru {
|
||||
tasks_by_usage
|
||||
.get(&task.id())
|
||||
.get(&task.1.id())
|
||||
.copied()
|
||||
.unwrap_or(not_used_score)
|
||||
} else {
|
||||
|
@ -97,16 +168,34 @@ impl Inventory {
|
|||
};
|
||||
(task, usages)
|
||||
})
|
||||
.sorted_unstable_by(|(task_a, usages_a), (task_b, usages_b)| {
|
||||
usages_a.cmp(usages_b).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(|(task, _)| task)
|
||||
.sorted_unstable_by(
|
||||
|((kind_a, task_a), usages_a), ((kind_b, task_b), usages_b)| {
|
||||
usages_a
|
||||
.cmp(usages_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.name())
|
||||
.cmp(&NumericPrefixWithSuffix::from_numeric_prefixed_str(
|
||||
task_b.name(),
|
||||
))
|
||||
.then(task_a.name().cmp(task_b.name()))
|
||||
})
|
||||
},
|
||||
)
|
||||
.map(|((kind, task), _)| (kind.clone(), task))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -114,9 +203,10 @@ impl Inventory {
|
|||
pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<Arc<dyn Task>> {
|
||||
self.last_scheduled_tasks.back().and_then(|id| {
|
||||
// TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future.
|
||||
self.list_tasks(None, false, cx)
|
||||
self.list_tasks(None, None, false, cx)
|
||||
.into_iter()
|
||||
.find(|task| task.id() == id)
|
||||
.find(|(_, task)| task.id() == id)
|
||||
.map(|(_, task)| task)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -140,30 +230,37 @@ 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 = list_task_names(&inventory, None, None, true, 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 = list_task_names(&inventory, None, None, false, cx);
|
||||
assert!(
|
||||
initial_tasks.is_empty(),
|
||||
"No tasks expected for empty inventory, but got {initial_tasks:?}"
|
||||
);
|
||||
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(TestSource::new(vec!["3_task".to_string()], cx), cx);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| StaticTestSource::new(vec!["3_task".to_string()], cx),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TestSource::new(
|
||||
vec![
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
cx,
|
||||
),
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec![
|
||||
"1_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
@ -175,24 +272,24 @@ mod tests {
|
|||
"3_task".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
list_task_names(&inventory, None, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
list_task_names(&inventory, None, None, true, 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),
|
||||
list_task_names(&inventory, None, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
list_task_names(&inventory, None, None, true, cx),
|
||||
vec![
|
||||
"2_task".to_string(),
|
||||
"1_a_task".to_string(),
|
||||
|
@ -206,12 +303,12 @@ 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),
|
||||
list_task_names(&inventory, None, None, false, cx),
|
||||
&expected_initial_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
list_task_names(&inventory, None, None, true, cx),
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
|
@ -222,7 +319,10 @@ mod tests {
|
|||
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx),
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(vec!["10_hello".to_string(), "11_hello".to_string()], cx)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
@ -235,12 +335,12 @@ mod tests {
|
|||
"11_hello".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
list_task_names(&inventory, None, None, false, cx),
|
||||
&expected_updated_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
list_task_names(&inventory, None, None, true, cx),
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
|
@ -253,12 +353,12 @@ mod tests {
|
|||
|
||||
register_task_used(&inventory, "11_hello", cx);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, false, cx),
|
||||
list_task_names(&inventory, None, None, false, cx),
|
||||
&expected_updated_state,
|
||||
"Task list without lru sorting, should be sorted alphanumerically"
|
||||
);
|
||||
assert_eq!(
|
||||
list_task_names(&inventory, None, true, cx),
|
||||
list_task_names(&inventory, None, None, true, cx),
|
||||
vec![
|
||||
"11_hello".to_string(),
|
||||
"3_task".to_string(),
|
||||
|
@ -270,6 +370,169 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inventory_static_task_filters(cx: &mut TestAppContext) {
|
||||
let inventory_with_statics = cx.update(Inventory::new);
|
||||
let common_name = "common_task_name";
|
||||
let path_1 = Path::new("path_1");
|
||||
let path_2 = Path::new("path_2");
|
||||
let worktree_1 = WorktreeId::from_usize(1);
|
||||
let worktree_path_1 = Path::new("worktree_path_1");
|
||||
let worktree_2 = WorktreeId::from_usize(2);
|
||||
let worktree_path_2 = Path::new("worktree_path_2");
|
||||
inventory_with_statics.update(cx, |inventory, cx| {
|
||||
inventory.add_source(
|
||||
TaskSourceKind::UserInput,
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["user_input".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::AbsPath(path_1.to_path_buf()),
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["static_source_1".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::AbsPath(path_2.to_path_buf()),
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["static_source_2".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["worktree_1".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
},
|
||||
|cx| {
|
||||
StaticTestSource::new(
|
||||
vec!["worktree_2".to_string(), common_name.to_string()],
|
||||
cx,
|
||||
)
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let worktree_independent_tasks = vec![
|
||||
(
|
||||
TaskSourceKind::AbsPath(path_1.to_path_buf()),
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath(path_1.to_path_buf()),
|
||||
"static_source_1".to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath(path_2.to_path_buf()),
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath(path_2.to_path_buf()),
|
||||
"static_source_2".to_string(),
|
||||
),
|
||||
(TaskSourceKind::UserInput, common_name.to_string()),
|
||||
(TaskSourceKind::UserInput, "user_input".to_string()),
|
||||
];
|
||||
let worktree_1_tasks = vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
},
|
||||
"worktree_1".to_string(),
|
||||
),
|
||||
];
|
||||
let worktree_2_tasks = vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
},
|
||||
"worktree_2".to_string(),
|
||||
),
|
||||
];
|
||||
|
||||
let all_tasks = worktree_1_tasks
|
||||
.iter()
|
||||
.chain(worktree_2_tasks.iter())
|
||||
// worktree-less tasks come later in the list
|
||||
.chain(worktree_independent_tasks.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for path in [
|
||||
None,
|
||||
Some(path_1),
|
||||
Some(path_2),
|
||||
Some(worktree_path_1),
|
||||
Some(worktree_path_2),
|
||||
] {
|
||||
assert_eq!(
|
||||
list_tasks(&inventory_with_statics, path, None, false, cx),
|
||||
all_tasks,
|
||||
"Path {path:?} choice should not adjust static runnables"
|
||||
);
|
||||
assert_eq!(
|
||||
list_tasks(&inventory_with_statics, path, Some(worktree_1), false, cx),
|
||||
worktree_1_tasks
|
||||
.iter()
|
||||
.chain(worktree_independent_tasks.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
"Path {path:?} choice should not adjust static runnables for worktree_1"
|
||||
);
|
||||
assert_eq!(
|
||||
list_tasks(&inventory_with_statics, path, Some(worktree_2), false, cx),
|
||||
worktree_2_tasks
|
||||
.iter()
|
||||
.chain(worktree_independent_tasks.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>(),
|
||||
"Path {path:?} choice should not adjust static runnables for worktree_2"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct TestTask {
|
||||
id: TaskId,
|
||||
|
@ -294,11 +557,11 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
struct TestSource {
|
||||
struct StaticTestSource {
|
||||
tasks: Vec<TestTask>,
|
||||
}
|
||||
|
||||
impl TestSource {
|
||||
impl StaticTestSource {
|
||||
fn new(
|
||||
task_names: impl IntoIterator<Item = String>,
|
||||
cx: &mut AppContext,
|
||||
|
@ -318,10 +581,11 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
impl TaskSource for TestSource {
|
||||
impl TaskSource for StaticTestSource {
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
_path: Option<&Path>,
|
||||
// static task source does not depend on path input
|
||||
_: Option<&Path>,
|
||||
_cx: &mut ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn Task>> {
|
||||
self.tasks
|
||||
|
@ -339,24 +603,41 @@ mod tests {
|
|||
fn list_task_names(
|
||||
inventory: &Model<Inventory>,
|
||||
path: Option<&Path>,
|
||||
worktree: Option<WorktreeId>,
|
||||
lru: bool,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory
|
||||
.list_tasks(path, lru, cx)
|
||||
.list_tasks(path, worktree, lru, cx)
|
||||
.into_iter()
|
||||
.map(|task| task.name().to_string())
|
||||
.map(|(_, task)| task.name().to_string())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn list_tasks(
|
||||
inventory: &Model<Inventory>,
|
||||
path: Option<&Path>,
|
||||
worktree: Option<WorktreeId>,
|
||||
lru: bool,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<(TaskSourceKind, String)> {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
inventory
|
||||
.list_tasks(path, worktree, lru, cx)
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| (source_kind, task.name().to_string()))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn register_task_used(inventory: &Model<Inventory>, task_name: &str, cx: &mut TestAppContext) {
|
||||
inventory.update(cx, |inventory, cx| {
|
||||
let task = inventory
|
||||
.list_tasks(None, false, cx)
|
||||
let (_, task) = inventory
|
||||
.list_tasks(None, None, false, cx)
|
||||
.into_iter()
|
||||
.find(|task| task.name() == task_name)
|
||||
.find(|(_, task)| task.name() == task_name)
|
||||
.unwrap_or_else(|| panic!("Failed to find task with name {task_name}"));
|
||||
inventory.task_scheduled(task.id().clone());
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue