Allow clients to run Zed tasks on remote projects (#12199)
Release Notes: - Enabled Zed tasks on remote projects with ssh connection string specified --------- Co-authored-by: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
parent
df35fd0026
commit
055a13a9b6
16 changed files with 1250 additions and 600 deletions
|
@ -36,7 +36,7 @@ use git::{blame::Blame, repository::GitRepository};
|
|||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use gpui::{
|
||||
AnyModel, AppContext, AsyncAppContext, BackgroundExecutor, BorrowAppContext, Context, Entity,
|
||||
EventEmitter, Model, ModelContext, PromptLevel, Task, WeakModel,
|
||||
EventEmitter, Model, ModelContext, PromptLevel, SharedString, Task, WeakModel,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
|
@ -47,10 +47,10 @@ use language::{
|
|||
serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel,
|
||||
Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, Event as BufferEvent,
|
||||
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
|
||||
Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
|
||||
ToPointUtf16, Transaction, Unclipped,
|
||||
ContextProvider, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
|
||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
|
||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp::{
|
||||
|
@ -80,6 +80,7 @@ use similar::{ChangeTag, TextDiff};
|
|||
use smol::channel::{Receiver, Sender};
|
||||
use smol::lock::Semaphore;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Ordering},
|
||||
convert::TryInto,
|
||||
env,
|
||||
|
@ -97,7 +98,10 @@ use std::{
|
|||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use task::static_source::{StaticSource, TrackedFile};
|
||||
use task::{
|
||||
static_source::{StaticSource, TrackedFile},
|
||||
RevealStrategy, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
||||
};
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId, LineEnding};
|
||||
use util::{
|
||||
|
@ -676,6 +680,8 @@ impl Project {
|
|||
client.add_model_request_handler(Self::handle_lsp_command::<lsp_ext_command::ExpandMacro>);
|
||||
client.add_model_request_handler(Self::handle_blame_buffer);
|
||||
client.add_model_request_handler(Self::handle_multi_lsp_query);
|
||||
client.add_model_request_handler(Self::handle_task_context_for_location);
|
||||
client.add_model_request_handler(Self::handle_task_templates);
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
|
@ -1257,6 +1263,19 @@ impl Project {
|
|||
self.dev_server_project_id
|
||||
}
|
||||
|
||||
pub fn ssh_connection_string(&self, cx: &ModelContext<Self>) -> Option<SharedString> {
|
||||
if self.is_local() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let dev_server_id = self.dev_server_project_id()?;
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(dev_server_id)?
|
||||
.ssh_connection_string
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn replica_id(&self) -> ReplicaId {
|
||||
match self.client_state {
|
||||
ProjectClientState::Remote { replica_id, .. } => replica_id,
|
||||
|
@ -7892,7 +7911,7 @@ impl Project {
|
|||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_tasks_for_worktree",
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
},
|
||||
|tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
|
||||
cx,
|
||||
|
@ -7912,7 +7931,7 @@ impl Project {
|
|||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_vscode_tasks_for_worktree",
|
||||
id_base: "local_vscode_tasks_for_worktree".into(),
|
||||
},
|
||||
|tx, cx| {
|
||||
StaticSource::new(TrackedFile::new_convertible::<
|
||||
|
@ -9424,6 +9443,122 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
async fn handle_task_context_for_location(
|
||||
project: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::TaskContextForLocation>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::TaskContext> {
|
||||
let location = envelope
|
||||
.payload
|
||||
.location
|
||||
.context("no location given for task context handling")?;
|
||||
let location = cx
|
||||
.update(|cx| deserialize_location(&project, location, cx))?
|
||||
.await?;
|
||||
let context_task = project.update(&mut cx, |project, cx| {
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
for range in location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.runnable_ranges(location.range.clone())
|
||||
{
|
||||
for (capture_name, value) in range.extra_captures {
|
||||
variables.insert(VariableName::Custom(capture_name.into()), value);
|
||||
}
|
||||
}
|
||||
variables
|
||||
};
|
||||
project.task_context_for_location(captured_variables, location, cx)
|
||||
})?;
|
||||
let task_context = context_task.await.unwrap_or_default();
|
||||
Ok(proto::TaskContext {
|
||||
cwd: task_context
|
||||
.cwd
|
||||
.map(|cwd| cwd.to_string_lossy().to_string()),
|
||||
task_variables: task_context
|
||||
.task_variables
|
||||
.into_iter()
|
||||
.map(|(variable_name, variable_value)| (variable_name.to_string(), variable_value))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_task_templates(
|
||||
project: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::TaskTemplates>,
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::TaskTemplatesResponse> {
|
||||
let worktree = envelope.payload.worktree_id.map(WorktreeId::from_proto);
|
||||
let location = match envelope.payload.location {
|
||||
Some(location) => Some(
|
||||
cx.update(|cx| deserialize_location(&project, location, cx))?
|
||||
.await
|
||||
.context("task templates request location deserializing")?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let templates = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.task_templates(worktree, location, cx)
|
||||
})?
|
||||
.await
|
||||
.context("receiving task templates")?
|
||||
.into_iter()
|
||||
.map(|(kind, template)| {
|
||||
let kind = Some(match kind {
|
||||
TaskSourceKind::UserInput => proto::task_source_kind::Kind::UserInput(
|
||||
proto::task_source_kind::UserInput {},
|
||||
),
|
||||
TaskSourceKind::Worktree {
|
||||
id,
|
||||
abs_path,
|
||||
id_base,
|
||||
} => {
|
||||
proto::task_source_kind::Kind::Worktree(proto::task_source_kind::Worktree {
|
||||
id: id.to_proto(),
|
||||
abs_path: abs_path.to_string_lossy().to_string(),
|
||||
id_base: id_base.to_string(),
|
||||
})
|
||||
}
|
||||
TaskSourceKind::AbsPath { id_base, abs_path } => {
|
||||
proto::task_source_kind::Kind::AbsPath(proto::task_source_kind::AbsPath {
|
||||
abs_path: abs_path.to_string_lossy().to_string(),
|
||||
id_base: id_base.to_string(),
|
||||
})
|
||||
}
|
||||
TaskSourceKind::Language { name } => {
|
||||
proto::task_source_kind::Kind::Language(proto::task_source_kind::Language {
|
||||
name: name.to_string(),
|
||||
})
|
||||
}
|
||||
});
|
||||
let kind = Some(proto::TaskSourceKind { kind });
|
||||
let template = Some(proto::TaskTemplate {
|
||||
label: template.label,
|
||||
command: template.command,
|
||||
args: template.args,
|
||||
env: template.env.into_iter().collect(),
|
||||
cwd: template.cwd,
|
||||
use_new_terminal: template.use_new_terminal,
|
||||
allow_concurrent_runs: template.allow_concurrent_runs,
|
||||
reveal: match template.reveal {
|
||||
RevealStrategy::Always => proto::RevealStrategy::Always as i32,
|
||||
RevealStrategy::Never => proto::RevealStrategy::Never as i32,
|
||||
},
|
||||
tags: template.tags,
|
||||
});
|
||||
proto::TemplatePair { kind, template }
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(proto::TaskTemplatesResponse { templates })
|
||||
}
|
||||
|
||||
async fn try_resolve_code_action(
|
||||
lang_server: &LanguageServer,
|
||||
action: &mut CodeAction,
|
||||
|
@ -10410,6 +10545,223 @@ impl Project {
|
|||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn task_context_for_location(
|
||||
&self,
|
||||
captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
cx: &mut ModelContext<'_, Project>,
|
||||
) -> Task<Option<TaskContext>> {
|
||||
if self.is_local() {
|
||||
let cwd = self.task_cwd(cx).log_err().flatten();
|
||||
|
||||
cx.spawn(|project, cx| async move {
|
||||
let mut task_variables = cx
|
||||
.update(|cx| {
|
||||
combine_task_variables(
|
||||
captured_variables,
|
||||
location,
|
||||
BasicContextProvider::new(project.upgrade()?),
|
||||
cx,
|
||||
)
|
||||
.log_err()
|
||||
})
|
||||
.ok()
|
||||
.flatten()?;
|
||||
// Remove all custom entries starting with _, as they're not intended for use by the end user.
|
||||
task_variables.sweep();
|
||||
Some(TaskContext {
|
||||
cwd,
|
||||
task_variables,
|
||||
})
|
||||
})
|
||||
} else if let Some(project_id) = self
|
||||
.remote_id()
|
||||
.filter(|_| self.ssh_connection_string(cx).is_some())
|
||||
{
|
||||
let task_context = self.client().request(proto::TaskContextForLocation {
|
||||
project_id,
|
||||
location: Some(proto::Location {
|
||||
buffer_id: location.buffer.read(cx).remote_id().into(),
|
||||
start: Some(serialize_anchor(&location.range.start)),
|
||||
end: Some(serialize_anchor(&location.range.end)),
|
||||
}),
|
||||
});
|
||||
cx.background_executor().spawn(async move {
|
||||
let task_context = task_context.await.log_err()?;
|
||||
Some(TaskContext {
|
||||
cwd: task_context.cwd.map(PathBuf::from),
|
||||
task_variables: task_context
|
||||
.task_variables
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|(variable_name, variable_value)| match variable_name.parse() {
|
||||
Ok(variable_name) => Some((variable_name, variable_value)),
|
||||
Err(()) => {
|
||||
log::error!("Unknown variable name: {variable_name}");
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn task_templates(
|
||||
&self,
|
||||
worktree: Option<WorktreeId>,
|
||||
location: Option<Location>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
|
||||
if self.is_local() {
|
||||
let language = location
|
||||
.and_then(|location| location.buffer.read(cx).language_at(location.range.start));
|
||||
Task::ready(Ok(self
|
||||
.task_inventory()
|
||||
.read(cx)
|
||||
.list_tasks(language, worktree)))
|
||||
} else if let Some(project_id) = self
|
||||
.remote_id()
|
||||
.filter(|_| self.ssh_connection_string(cx).is_some())
|
||||
{
|
||||
let remote_templates =
|
||||
self.query_remote_task_templates(project_id, worktree, location.as_ref(), cx);
|
||||
cx.background_executor().spawn(remote_templates)
|
||||
} else {
|
||||
Task::ready(Ok(Vec::new()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_remote_task_templates(
|
||||
&self,
|
||||
project_id: u64,
|
||||
worktree: Option<WorktreeId>,
|
||||
location: Option<&Location>,
|
||||
cx: &AppContext,
|
||||
) -> Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>> {
|
||||
let client = self.client();
|
||||
let location = location.map(|location| serialize_location(location, cx));
|
||||
cx.spawn(|_| async move {
|
||||
let response = client
|
||||
.request(proto::TaskTemplates {
|
||||
project_id,
|
||||
worktree_id: worktree.map(|id| id.to_proto()),
|
||||
location,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(response
|
||||
.templates
|
||||
.into_iter()
|
||||
.filter_map(|template_pair| {
|
||||
let task_source_kind = match template_pair.kind?.kind? {
|
||||
proto::task_source_kind::Kind::UserInput(_) => TaskSourceKind::UserInput,
|
||||
proto::task_source_kind::Kind::Worktree(worktree) => {
|
||||
TaskSourceKind::Worktree {
|
||||
id: WorktreeId::from_proto(worktree.id),
|
||||
abs_path: PathBuf::from(worktree.abs_path),
|
||||
id_base: Cow::Owned(worktree.id_base),
|
||||
}
|
||||
}
|
||||
proto::task_source_kind::Kind::AbsPath(abs_path) => {
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: Cow::Owned(abs_path.id_base),
|
||||
abs_path: PathBuf::from(abs_path.abs_path),
|
||||
}
|
||||
}
|
||||
proto::task_source_kind::Kind::Language(language) => {
|
||||
TaskSourceKind::Language {
|
||||
name: language.name.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let proto_template = template_pair.template?;
|
||||
let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
|
||||
.unwrap_or(proto::RevealStrategy::Always)
|
||||
{
|
||||
proto::RevealStrategy::Always => RevealStrategy::Always,
|
||||
proto::RevealStrategy::Never => RevealStrategy::Never,
|
||||
};
|
||||
let task_template = TaskTemplate {
|
||||
label: proto_template.label,
|
||||
command: proto_template.command,
|
||||
args: proto_template.args,
|
||||
env: proto_template.env.into_iter().collect(),
|
||||
cwd: proto_template.cwd,
|
||||
use_new_terminal: proto_template.use_new_terminal,
|
||||
allow_concurrent_runs: proto_template.allow_concurrent_runs,
|
||||
reveal,
|
||||
tags: proto_template.tags,
|
||||
};
|
||||
Some((task_source_kind, task_template))
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn task_cwd(&self, cx: &AppContext) -> anyhow::Result<Option<PathBuf>> {
|
||||
let available_worktrees = self
|
||||
.worktrees()
|
||||
.filter(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
worktree.is_visible()
|
||||
&& worktree.is_local()
|
||||
&& worktree.root_entry().map_or(false, |e| e.is_dir())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let cwd = match available_worktrees.len() {
|
||||
0 => None,
|
||||
1 => Some(available_worktrees[0].read(cx).abs_path()),
|
||||
_ => {
|
||||
let cwd_for_active_entry = self.active_entry().and_then(|entry_id| {
|
||||
available_worktrees.into_iter().find_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
if worktree.contains_entry(entry_id) {
|
||||
Some(worktree.abs_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
});
|
||||
anyhow::ensure!(
|
||||
cwd_for_active_entry.is_some(),
|
||||
"Cannot determine task cwd for multiple worktrees"
|
||||
);
|
||||
cwd_for_active_entry
|
||||
}
|
||||
};
|
||||
Ok(cwd.map(|path| path.to_path_buf()))
|
||||
}
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
mut captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
baseline: BasicContextProvider,
|
||||
cx: &mut AppContext,
|
||||
) -> anyhow::Result<TaskVariables> {
|
||||
let language_context_provider = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.and_then(|language| language.context_provider());
|
||||
let baseline = baseline
|
||||
.build_context(&captured_variables, &location, cx)
|
||||
.context("building basic default context")?;
|
||||
captured_variables.extend(baseline);
|
||||
if let Some(provider) = language_context_provider {
|
||||
captured_variables.extend(
|
||||
provider
|
||||
.build_context(&captured_variables, &location, cx)
|
||||
.context("building provider context")?,
|
||||
);
|
||||
}
|
||||
Ok(captured_variables)
|
||||
}
|
||||
|
||||
async fn populate_labels_for_symbols(
|
||||
|
@ -11238,3 +11590,40 @@ impl std::fmt::Display for NoRepositoryError {
|
|||
}
|
||||
|
||||
impl std::error::Error for NoRepositoryError {}
|
||||
|
||||
fn serialize_location(location: &Location, cx: &AppContext) -> proto::Location {
|
||||
proto::Location {
|
||||
buffer_id: location.buffer.read(cx).remote_id().into(),
|
||||
start: Some(serialize_anchor(&location.range.start)),
|
||||
end: Some(serialize_anchor(&location.range.end)),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_location(
|
||||
project: &Model<Project>,
|
||||
location: proto::Location,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<Location>> {
|
||||
let buffer_id = match BufferId::new(location.buffer_id) {
|
||||
Ok(id) => id,
|
||||
Err(e) => return Task::ready(Err(e)),
|
||||
};
|
||||
let buffer_task = project.update(cx, |project, cx| {
|
||||
project.wait_for_remote_buffer(buffer_id, cx)
|
||||
});
|
||||
cx.spawn(|_| async move {
|
||||
let buffer = buffer_task.await?;
|
||||
let start = location
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.context("missing task context location start")?;
|
||||
let end = location
|
||||
.end
|
||||
.and_then(deserialize_anchor)
|
||||
.context("missing task context location end")?;
|
||||
Ok(Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use serde_json::json;
|
|||
#[cfg(not(windows))]
|
||||
use std::os;
|
||||
use std::task::Poll;
|
||||
use task::{TaskContext, TaskTemplate, TaskTemplates};
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate, TaskTemplates};
|
||||
use unindent::Unindent as _;
|
||||
use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
|
||||
use worktree::WorktreeModelHandle as _;
|
||||
|
@ -129,101 +129,91 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
|||
let task_context = TaskContext::default();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
let workree_id = cx.update(|cx| {
|
||||
let worktree_id = cx.update(|cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
project.worktrees().next().unwrap().read(cx).id()
|
||||
})
|
||||
});
|
||||
let global_task_source_kind = TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
id: worktree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
};
|
||||
cx.update(|cx| {
|
||||
let tree = worktree.read(cx);
|
||||
|
||||
let settings_a = language_settings(
|
||||
None,
|
||||
Some(
|
||||
&(File::for_entry(
|
||||
tree.entry_for_path("a/a.rs").unwrap().clone(),
|
||||
worktree.clone(),
|
||||
) as _),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
let settings_b = language_settings(
|
||||
None,
|
||||
Some(
|
||||
&(File::for_entry(
|
||||
tree.entry_for_path("b/b.rs").unwrap().clone(),
|
||||
worktree.clone(),
|
||||
) as _),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
let all_tasks = cx
|
||||
.update(|cx| {
|
||||
let tree = worktree.read(cx);
|
||||
|
||||
assert_eq!(settings_a.tab_size.get(), 8);
|
||||
assert_eq!(settings_b.tab_size.get(), 2);
|
||||
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
vec![
|
||||
(
|
||||
global_task_source_kind.clone(),
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string(), "--all".to_string()],
|
||||
HashMap::default(),
|
||||
let settings_a = language_settings(
|
||||
None,
|
||||
Some(
|
||||
&(File::for_entry(
|
||||
tree.entry_for_path("a/a.rs").unwrap().clone(),
|
||||
worktree.clone(),
|
||||
) as _),
|
||||
),
|
||||
(
|
||||
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(),
|
||||
cx,
|
||||
);
|
||||
let settings_b = language_settings(
|
||||
None,
|
||||
Some(
|
||||
&(File::for_entry(
|
||||
tree.entry_for_path("b/b.rs").unwrap().clone(),
|
||||
worktree.clone(),
|
||||
) as _),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
cx,
|
||||
);
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
let inventory = project.task_inventory();
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (mut old, new) =
|
||||
inventory.used_and_current_resolved_tasks(None, Some(workree_id), &task_context);
|
||||
old.extend(new);
|
||||
let (_, resolved_task) = old
|
||||
.into_iter()
|
||||
.find(|(source_kind, _)| source_kind == &global_task_source_kind)
|
||||
.expect("should have one global task");
|
||||
inventory.task_scheduled(global_task_source_kind.clone(), resolved_task);
|
||||
assert_eq!(settings_a.tab_size.get(), 8);
|
||||
assert_eq!(settings_b.tab_size.get(), 2);
|
||||
|
||||
get_all_tasks(&project, Some(worktree_id), &task_context, cx)
|
||||
})
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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: worktree_id,
|
||||
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
},
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string()],
|
||||
HashMap::default(),
|
||||
),
|
||||
]
|
||||
);
|
||||
|
||||
let (_, resolved_task) = cx
|
||||
.update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))
|
||||
.await
|
||||
.into_iter()
|
||||
.find(|(source_kind, _)| source_kind == &global_task_source_kind)
|
||||
.expect("should have one global task");
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.task_scheduled(global_task_source_kind.clone(), resolved_task);
|
||||
});
|
||||
});
|
||||
|
||||
let tasks = serde_json::to_string(&TaskTemplates(vec![TaskTemplate {
|
||||
|
@ -257,63 +247,52 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext)
|
|||
tx.unbounded_send(tasks).unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
cx.update(|cx| {
|
||||
let all_tasks = project
|
||||
.update(cx, |project, cx| {
|
||||
project.task_inventory().update(cx, |inventory, _| {
|
||||
let (mut old, new) = inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
Some(workree_id),
|
||||
&task_context,
|
||||
);
|
||||
old.extend(new);
|
||||
old
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: workree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree",
|
||||
},
|
||||
"cargo check".to_string(),
|
||||
vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string()
|
||||
],
|
||||
HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-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(),
|
||||
),
|
||||
]
|
||||
);
|
||||
});
|
||||
let all_tasks = cx
|
||||
.update(|cx| get_all_tasks(&project, Some(worktree_id), &task_context, cx))
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|(source_kind, task)| {
|
||||
let resolved = task.resolved.unwrap();
|
||||
(
|
||||
source_kind,
|
||||
task.resolved_label,
|
||||
resolved.args,
|
||||
resolved.env,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
all_tasks,
|
||||
vec![
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_id,
|
||||
abs_path: PathBuf::from("/the-root/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
},
|
||||
"cargo check".to_string(),
|
||||
vec![
|
||||
"check".to_string(),
|
||||
"--all".to_string(),
|
||||
"--all-targets".to_string()
|
||||
],
|
||||
HashMap::from_iter(Some((
|
||||
"RUSTFLAGS".to_string(),
|
||||
"-Zunstable-options".to_string()
|
||||
))),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::Worktree {
|
||||
id: worktree_id,
|
||||
abs_path: PathBuf::from("/the-root/b/.zed/tasks.json"),
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
},
|
||||
"cargo check".to_string(),
|
||||
vec!["check".to_string()],
|
||||
HashMap::default(),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -5225,3 +5204,23 @@ fn tsx_lang() -> Arc<Language> {
|
|||
Some(tree_sitter_typescript::language_tsx()),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_all_tasks(
|
||||
project: &Model<Project>,
|
||||
worktree_id: Option<WorktreeId>,
|
||||
task_context: &TaskContext,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Vec<(TaskSourceKind, ResolvedTask)>> {
|
||||
let resolved_tasks = project.update(cx, |project, cx| {
|
||||
project
|
||||
.task_inventory()
|
||||
.read(cx)
|
||||
.used_and_current_resolved_tasks(None, worktree_id, None, task_context, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|_| async move {
|
||||
let (mut old, new) = resolved_tasks.await;
|
||||
old.extend(new);
|
||||
old
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Project-wide storage of the tasks available, capable of updating itself from the sources set.
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cmp::{self, Reverse},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
|
@ -20,7 +21,7 @@ use task::{
|
|||
TaskVariables, VariableName,
|
||||
};
|
||||
use text::{Point, ToPoint};
|
||||
use util::{post_inc, NumericPrefixWithSuffix};
|
||||
use util::{post_inc, NumericPrefixWithSuffix, ResultExt};
|
||||
use worktree::WorktreeId;
|
||||
|
||||
use crate::Project;
|
||||
|
@ -47,11 +48,11 @@ pub enum TaskSourceKind {
|
|||
Worktree {
|
||||
id: WorktreeId,
|
||||
abs_path: PathBuf,
|
||||
id_base: &'static str,
|
||||
id_base: Cow<'static, str>,
|
||||
},
|
||||
/// ~/.config/zed/task.json - like global files with task definitions, applicable to any path
|
||||
AbsPath {
|
||||
id_base: &'static str,
|
||||
id_base: Cow<'static, str>,
|
||||
abs_path: PathBuf,
|
||||
},
|
||||
/// Languages-specific tasks coming from extensions.
|
||||
|
@ -191,13 +192,18 @@ impl Inventory {
|
|||
/// 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>>,
|
||||
remote_templates_task: Option<Task<Result<Vec<(TaskSourceKind, TaskTemplate)>>>>,
|
||||
worktree: Option<WorktreeId>,
|
||||
location: Option<Location>,
|
||||
task_context: &TaskContext,
|
||||
) -> (
|
||||
cx: &AppContext,
|
||||
) -> Task<(
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
Vec<(TaskSourceKind, ResolvedTask)>,
|
||||
) {
|
||||
)> {
|
||||
let language = location
|
||||
.as_ref()
|
||||
.and_then(|location| location.buffer.read(cx).language_at(location.range.start));
|
||||
let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language {
|
||||
name: language.name(),
|
||||
});
|
||||
|
@ -229,7 +235,7 @@ impl Inventory {
|
|||
},
|
||||
);
|
||||
let not_used_score = post_inc(&mut lru_score);
|
||||
let currently_resolved_tasks = self
|
||||
let mut currently_resolved_tasks = self
|
||||
.sources
|
||||
.iter()
|
||||
.filter(|source| {
|
||||
|
@ -244,7 +250,7 @@ impl Inventory {
|
|||
.into_iter()
|
||||
.map(|task| (&source.kind, task))
|
||||
})
|
||||
.chain(language_tasks)
|
||||
.chain(language_tasks.filter(|_| remote_templates_task.is_none()))
|
||||
.filter_map(|(kind, task)| {
|
||||
let id_base = kind.to_id_base();
|
||||
Some((kind, task.resolve_task(&id_base, task_context)?))
|
||||
|
@ -259,66 +265,87 @@ impl Inventory {
|
|||
.collect::<Vec<_>>();
|
||||
let previously_spawned_tasks = task_usage
|
||||
.into_iter()
|
||||
.map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score));
|
||||
.map(|(_, (kind, task, lru_score))| (kind.clone(), task.clone(), lru_score))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
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())) {
|
||||
btree_map::Entry::Occupied(mut o) => {
|
||||
let (_, previous_lru_score) = o.get();
|
||||
if previous_lru_score >= &lru_score {
|
||||
o.insert((task, lru_score));
|
||||
let task_context = task_context.clone();
|
||||
cx.spawn(move |_| async move {
|
||||
let remote_templates = match remote_templates_task {
|
||||
Some(task) => match task.await.log_err() {
|
||||
Some(remote_templates) => remote_templates,
|
||||
None => return (Vec::new(), Vec::new()),
|
||||
},
|
||||
None => Vec::new(),
|
||||
};
|
||||
let remote_tasks = remote_templates.into_iter().filter_map(|(kind, task)| {
|
||||
let id_base = kind.to_id_base();
|
||||
Some((
|
||||
kind,
|
||||
task.resolve_task(&id_base, &task_context)?,
|
||||
not_used_score,
|
||||
))
|
||||
});
|
||||
currently_resolved_tasks.extend(remote_tasks);
|
||||
|
||||
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())) {
|
||||
btree_map::Entry::Occupied(mut o) => {
|
||||
let (_, previous_lru_score) = o.get();
|
||||
if previous_lru_score >= &lru_score {
|
||||
o.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
btree_map::Entry::Vacant(v) => {
|
||||
v.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
btree_map::Entry::Vacant(v) => {
|
||||
v.insert((task, lru_score));
|
||||
}
|
||||
}
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
tasks_by_label = currently_resolved_tasks.iter().fold(
|
||||
tasks_by_label,
|
||||
|mut tasks_by_label, (source, task, lru_score)| {
|
||||
match tasks_by_label.entry((source.clone(), task.resolved_label.clone())) {
|
||||
btree_map::Entry::Occupied(mut o) => {
|
||||
let (previous_task, _) = o.get();
|
||||
let new_template = task.original_task();
|
||||
if new_template != previous_task.original_task() {
|
||||
o.insert((task.clone(), *lru_score));
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
tasks_by_label = currently_resolved_tasks.iter().fold(
|
||||
tasks_by_label,
|
||||
|mut tasks_by_label, (source, task, lru_score)| {
|
||||
match tasks_by_label.entry((source.clone(), task.resolved_label.clone())) {
|
||||
btree_map::Entry::Occupied(mut o) => {
|
||||
let (previous_task, _) = o.get();
|
||||
let new_template = task.original_task();
|
||||
if new_template != previous_task.original_task() {
|
||||
o.insert((task.clone(), *lru_score));
|
||||
}
|
||||
}
|
||||
btree_map::Entry::Vacant(v) => {
|
||||
v.insert((task.clone(), *lru_score));
|
||||
}
|
||||
}
|
||||
btree_map::Entry::Vacant(v) => {
|
||||
v.insert((task.clone(), *lru_score));
|
||||
}
|
||||
}
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
tasks_by_label
|
||||
},
|
||||
);
|
||||
|
||||
let resolved = tasks_by_label
|
||||
.into_iter()
|
||||
.map(|((kind, _), (task, lru_score))| (kind, task, lru_score))
|
||||
.sorted_by(task_lru_comparator)
|
||||
.filter_map(|(kind, task, lru_score)| {
|
||||
if lru_score < not_used_score {
|
||||
Some((kind, task))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
(
|
||||
resolved,
|
||||
currently_resolved_tasks
|
||||
let resolved = tasks_by_label
|
||||
.into_iter()
|
||||
.sorted_unstable_by(task_lru_comparator)
|
||||
.map(|(kind, task, _)| (kind, task))
|
||||
.collect(),
|
||||
)
|
||||
.map(|((kind, _), (task, lru_score))| (kind, task, lru_score))
|
||||
.sorted_by(task_lru_comparator)
|
||||
.filter_map(|(kind, task, lru_score)| {
|
||||
if lru_score < not_used_score {
|
||||
Some((kind, task))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
resolved,
|
||||
currently_resolved_tasks
|
||||
.into_iter()
|
||||
.sorted_unstable_by(task_lru_comparator)
|
||||
.map(|(kind, task, _)| (kind, task))
|
||||
.collect(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the last scheduled task, if any of the sources contains one with the matching id.
|
||||
|
@ -443,21 +470,6 @@ mod test_inventory {
|
|||
})
|
||||
}
|
||||
|
||||
pub(super) fn resolved_task_names(
|
||||
inventory: &Model<Inventory>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (used, current) =
|
||||
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
|
||||
used.into_iter()
|
||||
.chain(current)
|
||||
.map(|(_, task)| task.original_task().label.clone())
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn register_task_used(
|
||||
inventory: &Model<Inventory>,
|
||||
task_name: &str,
|
||||
|
@ -478,21 +490,28 @@ mod test_inventory {
|
|||
});
|
||||
}
|
||||
|
||||
pub(super) fn list_tasks(
|
||||
pub(super) async fn list_tasks(
|
||||
inventory: &Model<Inventory>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<(TaskSourceKind, String)> {
|
||||
inventory.update(cx, |inventory, _| {
|
||||
let (used, current) =
|
||||
inventory.used_and_current_resolved_tasks(None, worktree, &TaskContext::default());
|
||||
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()
|
||||
})
|
||||
let (used, current) = inventory
|
||||
.update(cx, |inventory, cx| {
|
||||
inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
worktree,
|
||||
None,
|
||||
&TaskContext::default(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,9 +641,9 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_task_list_sorting(cx: &mut TestAppContext) {
|
||||
async fn test_task_list_sorting(cx: &mut TestAppContext) {
|
||||
let inventory = cx.update(Inventory::new);
|
||||
let initial_tasks = resolved_task_names(&inventory, None, cx);
|
||||
let initial_tasks = resolved_task_names(&inventory, None, cx).await;
|
||||
assert!(
|
||||
initial_tasks.is_empty(),
|
||||
"No tasks expected for empty inventory, but got {initial_tasks:?}"
|
||||
|
@ -671,7 +690,7 @@ mod tests {
|
|||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
&expected_initial_state,
|
||||
"Tasks with equal amount of usages should be sorted alphanumerically"
|
||||
);
|
||||
|
@ -682,7 +701,7 @@ mod tests {
|
|||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"2_task".to_string(),
|
||||
"2_task".to_string(),
|
||||
|
@ -701,7 +720,7 @@ mod tests {
|
|||
&expected_initial_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
|
@ -736,7 +755,7 @@ mod tests {
|
|||
&expected_updated_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"3_task".to_string(),
|
||||
"1_task".to_string(),
|
||||
|
@ -756,7 +775,7 @@ mod tests {
|
|||
&expected_updated_state,
|
||||
);
|
||||
assert_eq!(
|
||||
resolved_task_names(&inventory, None, cx),
|
||||
resolved_task_names(&inventory, None, cx).await,
|
||||
vec![
|
||||
"11_hello".to_string(),
|
||||
"3_task".to_string(),
|
||||
|
@ -773,7 +792,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_inventory_static_task_filters(cx: &mut TestAppContext) {
|
||||
async 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");
|
||||
|
@ -797,7 +816,7 @@ mod tests {
|
|||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_1.to_path_buf(),
|
||||
},
|
||||
|tx, cx| {
|
||||
|
@ -811,7 +830,7 @@ mod tests {
|
|||
);
|
||||
inventory.add_source(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_2.to_path_buf(),
|
||||
},
|
||||
|tx, cx| {
|
||||
|
@ -827,7 +846,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
|tx, cx| {
|
||||
static_test_source(
|
||||
|
@ -842,7 +861,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
|tx, cx| {
|
||||
static_test_source(
|
||||
|
@ -858,28 +877,28 @@ mod tests {
|
|||
let worktree_independent_tasks = vec![
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_1.to_path_buf(),
|
||||
},
|
||||
"static_source_1".to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_1.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_2.to_path_buf(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
(
|
||||
TaskSourceKind::AbsPath {
|
||||
id_base: "test source",
|
||||
id_base: "test source".into(),
|
||||
abs_path: path_2.to_path_buf(),
|
||||
},
|
||||
"static_source_2".to_string(),
|
||||
|
@ -892,7 +911,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
|
@ -900,7 +919,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_1,
|
||||
abs_path: worktree_path_1.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
"worktree_1".to_string(),
|
||||
),
|
||||
|
@ -910,7 +929,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
common_name.to_string(),
|
||||
),
|
||||
|
@ -918,7 +937,7 @@ mod tests {
|
|||
TaskSourceKind::Worktree {
|
||||
id: worktree_2,
|
||||
abs_path: worktree_path_2.to_path_buf(),
|
||||
id_base: "test_source",
|
||||
id_base: "test_source".into(),
|
||||
},
|
||||
"worktree_2".to_string(),
|
||||
),
|
||||
|
@ -933,9 +952,12 @@ mod tests {
|
|||
.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, Some(worktree_1), cx),
|
||||
list_tasks(&inventory_with_statics, None, cx).await,
|
||||
all_tasks
|
||||
);
|
||||
assert_eq!(
|
||||
list_tasks(&inventory_with_statics, Some(worktree_1), cx).await,
|
||||
worktree_1_tasks
|
||||
.iter()
|
||||
.chain(worktree_independent_tasks.iter())
|
||||
|
@ -944,7 +966,7 @@ mod tests {
|
|||
.collect::<Vec<_>>(),
|
||||
);
|
||||
assert_eq!(
|
||||
list_tasks(&inventory_with_statics, Some(worktree_2), cx),
|
||||
list_tasks(&inventory_with_statics, Some(worktree_2), cx).await,
|
||||
worktree_2_tasks
|
||||
.iter()
|
||||
.chain(worktree_independent_tasks.iter())
|
||||
|
@ -953,4 +975,26 @@ mod tests {
|
|||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) async fn resolved_task_names(
|
||||
inventory: &Model<Inventory>,
|
||||
worktree: Option<WorktreeId>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> Vec<String> {
|
||||
let (used, current) = inventory
|
||||
.update(cx, |inventory, cx| {
|
||||
inventory.used_and_current_resolved_tasks(
|
||||
None,
|
||||
worktree,
|
||||
None,
|
||||
&TaskContext::default(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
used.into_iter()
|
||||
.chain(current)
|
||||
.map(|(_, task)| task.original_task().label.clone())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue