Rework remote task synchronization (#18746)
Reworks the way tasks are stored, accessed and synchronized in the `project`. Now both collab and ssh remote projects use the same TaskStorage kind to get the task context from the remote host, and worktree task templates are synchronized along with other worktree settings. Release Notes: - Adds ssh support to tasks, improves collab-remote projects' tasks sync
This commit is contained in:
parent
f1053ff525
commit
49c75eb062
18 changed files with 1262 additions and 1366 deletions
|
@ -9,6 +9,7 @@ pub mod prettier_store;
|
|||
pub mod project_settings;
|
||||
pub mod search;
|
||||
mod task_inventory;
|
||||
pub mod task_store;
|
||||
pub mod terminals;
|
||||
pub mod worktree_store;
|
||||
|
||||
|
@ -21,7 +22,7 @@ pub use environment::EnvironmentErrorMessage;
|
|||
pub mod search_history;
|
||||
mod yarn;
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use anyhow::{anyhow, Result};
|
||||
use buffer_store::{BufferStore, BufferStoreEvent};
|
||||
use client::{
|
||||
proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId,
|
||||
|
@ -44,11 +45,10 @@ use gpui::{
|
|||
};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::InlayHintKind,
|
||||
proto::{deserialize_anchor, serialize_anchor, split_operations},
|
||||
Buffer, BufferEvent, CachedLspAdapter, Capability, CodeLabel, ContextProvider, DiagnosticEntry,
|
||||
Documentation, File as _, Language, LanguageRegistry, LanguageServerName, PointUtf16, ToOffset,
|
||||
ToPointUtf16, Transaction, Unclipped,
|
||||
language_settings::InlayHintKind, proto::split_operations, Buffer, BufferEvent,
|
||||
CachedLspAdapter, Capability, CodeLabel, DiagnosticEntry, Documentation, File as _, Language,
|
||||
LanguageRegistry, LanguageServerName, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
|
@ -56,16 +56,13 @@ use lsp::{
|
|||
use lsp_command::*;
|
||||
use node_runtime::NodeRuntime;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
|
||||
pub use prettier_store::PrettierStore;
|
||||
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
|
||||
use remote::SshRemoteClient;
|
||||
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
|
||||
use search::{SearchInputKind, SearchQuery, SearchResult};
|
||||
use search_history::SearchHistory;
|
||||
use settings::{
|
||||
watch_config_file, InvalidSettingsError, Settings, SettingsLocation, SettingsStore,
|
||||
};
|
||||
use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsStore};
|
||||
use smol::channel::Receiver;
|
||||
use snippet::Snippet;
|
||||
use snippet_provider::SnippetProvider;
|
||||
|
@ -77,10 +74,7 @@ use std::{
|
|||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use task::{
|
||||
static_source::{StaticSource, TrackedFile},
|
||||
HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
||||
};
|
||||
use task_store::TaskStore;
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId};
|
||||
use util::{paths::compare_paths, ResultExt as _};
|
||||
|
@ -141,6 +135,7 @@ pub struct Project {
|
|||
languages: Arc<LanguageRegistry>,
|
||||
client: Arc<client::Client>,
|
||||
join_project_response_message_id: u32,
|
||||
task_store: Model<TaskStore>,
|
||||
user_store: Model<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
ssh_client: Option<Model<SshRemoteClient>>,
|
||||
|
@ -156,7 +151,6 @@ pub struct Project {
|
|||
remotely_created_models: Arc<Mutex<RemotelyCreatedModels>>,
|
||||
terminals: Terminals,
|
||||
node: Option<NodeRuntime>,
|
||||
tasks: Model<Inventory>,
|
||||
hosted_project_id: Option<ProjectId>,
|
||||
dev_server_project_id: Option<client::DevServerProjectId>,
|
||||
search_history: SearchHistory,
|
||||
|
@ -567,14 +561,13 @@ impl Project {
|
|||
client.add_model_request_handler(Self::handle_open_buffer_by_id);
|
||||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||
client.add_model_request_handler(Self::handle_open_new_buffer);
|
||||
client.add_model_request_handler(Self::handle_task_context_for_location);
|
||||
client.add_model_request_handler(Self::handle_task_templates);
|
||||
client.add_model_message_handler(Self::handle_create_buffer_for_peer);
|
||||
|
||||
WorktreeStore::init(&client);
|
||||
BufferStore::init(&client);
|
||||
LspStore::init(&client);
|
||||
SettingsObserver::init(&client);
|
||||
TaskStore::init(Some(&client));
|
||||
}
|
||||
|
||||
pub fn local(
|
||||
|
@ -590,7 +583,6 @@ impl Project {
|
|||
let (tx, rx) = mpsc::unbounded();
|
||||
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
|
||||
.detach();
|
||||
let tasks = Inventory::new(cx);
|
||||
let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
|
||||
let worktree_store = cx.new_model(|_| WorktreeStore::local(false, fs.clone()));
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
|
@ -610,13 +602,29 @@ impl Project {
|
|||
)
|
||||
});
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, env, cx);
|
||||
|
||||
let task_store = cx.new_model(|cx| {
|
||||
TaskStore::local(
|
||||
fs.clone(),
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
environment.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
|
||||
SettingsObserver::new_local(
|
||||
fs.clone(),
|
||||
worktree_store.clone(),
|
||||
task_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
||||
let environment = ProjectEnvironment::new(&worktree_store, env, cx);
|
||||
let lsp_store = cx.new_model(|cx| {
|
||||
LspStore::new_local(
|
||||
buffer_store.clone(),
|
||||
|
@ -645,6 +653,7 @@ impl Project {
|
|||
snippets,
|
||||
languages,
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
|
@ -655,7 +664,6 @@ impl Project {
|
|||
local_handles: Vec::new(),
|
||||
},
|
||||
node: Some(node),
|
||||
tasks,
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
|
@ -681,7 +689,6 @@ impl Project {
|
|||
let (tx, rx) = mpsc::unbounded();
|
||||
cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
|
||||
.detach();
|
||||
let tasks = Inventory::new(cx);
|
||||
let global_snippets_dir = paths::config_dir().join("snippets");
|
||||
let snippets =
|
||||
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
|
||||
|
@ -703,8 +710,24 @@ impl Project {
|
|||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
|
||||
let task_store = cx.new_model(|cx| {
|
||||
TaskStore::remote(
|
||||
fs.clone(),
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
ssh.read(cx).to_proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_ssh(ssh_proto.clone(), worktree_store.clone(), cx)
|
||||
SettingsObserver::new_ssh(
|
||||
ssh_proto.clone(),
|
||||
worktree_store.clone(),
|
||||
task_store.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&settings_observer, Self::on_settings_observer_event)
|
||||
.detach();
|
||||
|
@ -748,6 +771,7 @@ impl Project {
|
|||
snippets,
|
||||
languages,
|
||||
client,
|
||||
task_store,
|
||||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
|
@ -758,7 +782,6 @@ impl Project {
|
|||
local_handles: Vec::new(),
|
||||
},
|
||||
node: Some(node),
|
||||
tasks,
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
|
@ -783,6 +806,7 @@ impl Project {
|
|||
BufferStore::init(&ssh_proto);
|
||||
LspStore::init(&ssh_proto);
|
||||
SettingsObserver::init(&ssh_proto);
|
||||
TaskStore::init(Some(&ssh_proto));
|
||||
|
||||
this
|
||||
})
|
||||
|
@ -836,6 +860,7 @@ impl Project {
|
|||
response,
|
||||
subscriptions,
|
||||
client,
|
||||
false,
|
||||
user_store,
|
||||
languages,
|
||||
fs,
|
||||
|
@ -844,10 +869,12 @@ impl Project {
|
|||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn from_join_project_response(
|
||||
response: TypedEnvelope<proto::JoinProjectResponse>,
|
||||
subscriptions: [EntitySubscription; 5],
|
||||
client: Arc<Client>,
|
||||
run_tasks: bool,
|
||||
user_store: Model<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
|
@ -884,12 +911,27 @@ impl Project {
|
|||
lsp_store
|
||||
})?;
|
||||
|
||||
let settings_observer =
|
||||
cx.new_model(|cx| SettingsObserver::new_remote(worktree_store.clone(), cx))?;
|
||||
let task_store = cx.new_model(|cx| {
|
||||
if run_tasks {
|
||||
TaskStore::remote(
|
||||
fs.clone(),
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
client.clone().into(),
|
||||
remote_id,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
TaskStore::Noop
|
||||
}
|
||||
})?;
|
||||
|
||||
let settings_observer = cx.new_model(|cx| {
|
||||
SettingsObserver::new_remote(worktree_store.clone(), task_store.clone(), cx)
|
||||
})?;
|
||||
|
||||
let this = cx.new_model(|cx| {
|
||||
let replica_id = response.payload.replica_id as ReplicaId;
|
||||
let tasks = Inventory::new(cx);
|
||||
|
||||
let snippets = SnippetProvider::new(fs.clone(), BTreeSet::from_iter([]), cx);
|
||||
|
||||
|
@ -923,6 +965,7 @@ impl Project {
|
|||
join_project_response_message_id: response.message_id,
|
||||
languages,
|
||||
user_store: user_store.clone(),
|
||||
task_store,
|
||||
snippets,
|
||||
fs,
|
||||
ssh_client: None,
|
||||
|
@ -943,7 +986,6 @@ impl Project {
|
|||
local_handles: Vec::new(),
|
||||
},
|
||||
node: None,
|
||||
tasks,
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: response
|
||||
.payload
|
||||
|
@ -1032,6 +1074,7 @@ impl Project {
|
|||
response,
|
||||
subscriptions,
|
||||
client,
|
||||
true,
|
||||
user_store,
|
||||
languages,
|
||||
fs,
|
||||
|
@ -1283,8 +1326,8 @@ impl Project {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn task_inventory(&self) -> &Model<Inventory> {
|
||||
&self.tasks
|
||||
pub fn task_store(&self) -> &Model<TaskStore> {
|
||||
&self.task_store
|
||||
}
|
||||
|
||||
pub fn snippets(&self) -> &Model<SnippetProvider> {
|
||||
|
@ -1505,6 +1548,9 @@ impl Project {
|
|||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.shared(project_id, self.client.clone().into(), cx)
|
||||
});
|
||||
self.task_store.update(cx, |task_store, cx| {
|
||||
task_store.shared(project_id, self.client.clone().into(), cx);
|
||||
});
|
||||
self.settings_observer.update(cx, |settings_observer, cx| {
|
||||
settings_observer.shared(project_id, self.client.clone().into(), cx)
|
||||
});
|
||||
|
@ -1593,9 +1639,13 @@ impl Project {
|
|||
buffer_store.forget_shared_buffers();
|
||||
buffer_store.unshared(cx)
|
||||
});
|
||||
self.task_store.update(cx, |task_store, cx| {
|
||||
task_store.unshared(cx);
|
||||
});
|
||||
self.settings_observer.update(cx, |settings_observer, cx| {
|
||||
settings_observer.unshared(cx);
|
||||
});
|
||||
|
||||
self.client
|
||||
.send(proto::UnshareProject {
|
||||
project_id: remote_id,
|
||||
|
@ -2105,29 +2155,23 @@ impl Project {
|
|||
}
|
||||
}
|
||||
cx.observe(worktree, |_, _, cx| cx.notify()).detach();
|
||||
cx.subscribe(worktree, |this, worktree, event, cx| {
|
||||
let is_local = worktree.read(cx).is_local();
|
||||
match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
if is_local {
|
||||
this.update_local_worktree_settings(&worktree, changes, cx);
|
||||
}
|
||||
cx.subscribe(worktree, |project, worktree, event, cx| match event {
|
||||
worktree::Event::UpdatedEntries(changes) => {
|
||||
cx.emit(Event::WorktreeUpdatedEntries(
|
||||
worktree.read(cx).id(),
|
||||
changes.clone(),
|
||||
));
|
||||
|
||||
cx.emit(Event::WorktreeUpdatedEntries(
|
||||
worktree.read(cx).id(),
|
||||
changes.clone(),
|
||||
));
|
||||
|
||||
let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
|
||||
this.client()
|
||||
.telemetry()
|
||||
.report_discovered_project_events(worktree_id, changes);
|
||||
}
|
||||
worktree::Event::UpdatedGitRepositories(_) => {
|
||||
cx.emit(Event::WorktreeUpdatedGitRepositories);
|
||||
}
|
||||
worktree::Event::DeletedEntry(id) => cx.emit(Event::DeletedEntry(*id)),
|
||||
let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
|
||||
project
|
||||
.client()
|
||||
.telemetry()
|
||||
.report_discovered_project_events(worktree_id, changes);
|
||||
}
|
||||
worktree::Event::UpdatedGitRepositories(_) => {
|
||||
cx.emit(Event::WorktreeUpdatedGitRepositories);
|
||||
}
|
||||
worktree::Event::DeletedEntry(id) => cx.emit(Event::DeletedEntry(*id)),
|
||||
})
|
||||
.detach();
|
||||
cx.notify();
|
||||
|
@ -2157,10 +2201,6 @@ impl Project {
|
|||
return;
|
||||
}
|
||||
|
||||
self.task_inventory().update(cx, |inventory, _| {
|
||||
inventory.remove_worktree_sources(id_to_remove);
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -3139,77 +3179,6 @@ impl Project {
|
|||
});
|
||||
}
|
||||
|
||||
fn update_local_worktree_settings(
|
||||
&mut self,
|
||||
worktree: &Model<Worktree>,
|
||||
changes: &UpdatedEntriesSet,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if worktree.read(cx).is_remote() {
|
||||
return;
|
||||
}
|
||||
let remote_worktree_id = worktree.read(cx).id();
|
||||
|
||||
for (path, _, change) in changes.iter() {
|
||||
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 path.ends_with(local_tasks_file_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();
|
||||
let tasks_file_rx =
|
||||
watch_config_file(cx.background_executor(), fs, task_abs_path);
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_tasks_for_worktree".into(),
|
||||
},
|
||||
|tx, cx| StaticSource::new(TrackedFile::new(tasks_file_rx, tx, cx)),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
} else if path.ends_with(local_vscode_tasks_file_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();
|
||||
let tasks_file_rx =
|
||||
watch_config_file(cx.background_executor(), fs, task_abs_path);
|
||||
task_inventory.add_source(
|
||||
TaskSourceKind::Worktree {
|
||||
id: remote_worktree_id,
|
||||
abs_path,
|
||||
id_base: "local_vscode_tasks_for_worktree".into(),
|
||||
},
|
||||
|tx, cx| {
|
||||
StaticSource::new(TrackedFile::new_convertible::<
|
||||
task::VsCodeTaskFile,
|
||||
>(
|
||||
tasks_file_rx, tx, cx
|
||||
))
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active_path(&mut self, entry: Option<ProjectPath>, cx: &mut ModelContext<Self>) {
|
||||
let new_active_entry = entry.and_then(|project_path| {
|
||||
let worktree = self.worktree_for_id(project_path.worktree_id, cx)?;
|
||||
|
@ -3518,7 +3487,7 @@ impl Project {
|
|||
let buffer_store = this.read_with(&cx, |this, cx| {
|
||||
if let Some(ssh) = &this.ssh_client {
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = 0;
|
||||
payload.project_id = SSH_PROJECT_ID;
|
||||
cx.background_executor()
|
||||
.spawn(ssh.read(cx).to_proto_client().request(payload))
|
||||
.detach_and_log_err(cx);
|
||||
|
@ -3578,137 +3547,6 @@ impl Project {
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
async fn handle_task_context_for_location(
|
||||
project: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::TaskContextForLocation>,
|
||||
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 {
|
||||
project_env: task_context.project_env.into_iter().collect(),
|
||||
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>,
|
||||
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::RevealAlways as i32,
|
||||
RevealStrategy::Never => proto::RevealStrategy::RevealNever as i32,
|
||||
},
|
||||
hide: match template.hide {
|
||||
HideStrategy::Always => proto::HideStrategy::HideAlways as i32,
|
||||
HideStrategy::Never => proto::HideStrategy::HideNever as i32,
|
||||
HideStrategy::OnSuccess => proto::HideStrategy::HideOnSuccess as i32,
|
||||
},
|
||||
shell: Some(proto::Shell {
|
||||
shell_type: Some(match template.shell {
|
||||
Shell::System => proto::shell::ShellType::System(proto::System {}),
|
||||
Shell::Program(program) => proto::shell::ShellType::Program(program),
|
||||
Shell::WithArguments { program, args } => {
|
||||
proto::shell::ShellType::WithArguments(
|
||||
proto::shell::WithArguments { program, args },
|
||||
)
|
||||
}
|
||||
}),
|
||||
}),
|
||||
tags: template.tags,
|
||||
});
|
||||
proto::TemplatePair { kind, template }
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(proto::TaskTemplatesResponse { templates })
|
||||
}
|
||||
|
||||
async fn handle_search_candidate_buffers(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::FindSearchCandidates>,
|
||||
|
@ -3996,267 +3834,6 @@ impl Project {
|
|||
.read(cx)
|
||||
.language_server_for_buffer(buffer, server_id, cx)
|
||||
}
|
||||
|
||||
pub fn task_context_for_location(
|
||||
&self,
|
||||
captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
cx: &mut ModelContext<'_, Project>,
|
||||
) -> Task<Option<TaskContext>> {
|
||||
if self.is_local() {
|
||||
let (worktree_id, worktree_abs_path) = if let Some(worktree) = self.task_worktree(cx) {
|
||||
(
|
||||
Some(worktree.read(cx).id()),
|
||||
Some(worktree.read(cx).abs_path()),
|
||||
)
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
cx.spawn(|project, mut cx| async move {
|
||||
let project_env = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
let worktree_abs_path = worktree_abs_path.clone();
|
||||
project.environment.update(cx, |environment, cx| {
|
||||
environment.get_environment(worktree_id, worktree_abs_path, cx)
|
||||
})
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
let mut task_variables = cx
|
||||
.update(|cx| {
|
||||
combine_task_variables(
|
||||
captured_variables,
|
||||
location,
|
||||
project_env.as_ref(),
|
||||
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 {
|
||||
project_env: project_env.unwrap_or_default(),
|
||||
cwd: worktree_abs_path.map(|p| p.to_path_buf()),
|
||||
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 {
|
||||
project_env: task_context.project_env.into_iter().collect(),
|
||||
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 (file, language) = location
|
||||
.map(|location| {
|
||||
let buffer = location.buffer.read(cx);
|
||||
(
|
||||
buffer.file().cloned(),
|
||||
buffer.language_at(location.range.start),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
Task::ready(Ok(self
|
||||
.task_inventory()
|
||||
.read(cx)
|
||||
.list_tasks(file, language, worktree, cx)))
|
||||
} 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::RevealAlways)
|
||||
{
|
||||
proto::RevealStrategy::RevealAlways => RevealStrategy::Always,
|
||||
proto::RevealStrategy::RevealNever => RevealStrategy::Never,
|
||||
};
|
||||
let hide = match proto::HideStrategy::from_i32(proto_template.hide)
|
||||
.unwrap_or(proto::HideStrategy::HideNever)
|
||||
{
|
||||
proto::HideStrategy::HideAlways => HideStrategy::Always,
|
||||
proto::HideStrategy::HideNever => HideStrategy::Never,
|
||||
proto::HideStrategy::HideOnSuccess => HideStrategy::OnSuccess,
|
||||
};
|
||||
let shell = match proto_template
|
||||
.shell
|
||||
.and_then(|shell| shell.shell_type)
|
||||
.unwrap_or(proto::shell::ShellType::System(proto::System {}))
|
||||
{
|
||||
proto::shell::ShellType::System(_) => Shell::System,
|
||||
proto::shell::ShellType::Program(program) => Shell::Program(program),
|
||||
proto::shell::ShellType::WithArguments(with_arguments) => {
|
||||
Shell::WithArguments {
|
||||
program: with_arguments.program,
|
||||
args: with_arguments.args,
|
||||
}
|
||||
}
|
||||
};
|
||||
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,
|
||||
hide,
|
||||
shell,
|
||||
tags: proto_template.tags,
|
||||
};
|
||||
Some((task_source_kind, task_template))
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn task_worktree(&self, cx: &AppContext) -> Option<Model<Worktree>> {
|
||||
let available_worktrees = self
|
||||
.worktrees(cx)
|
||||
.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<_>>();
|
||||
|
||||
match available_worktrees.len() {
|
||||
0 => None,
|
||||
1 => Some(available_worktrees[0].clone()),
|
||||
_ => self.active_entry().and_then(|entry_id| {
|
||||
available_worktrees.into_iter().find_map(|worktree| {
|
||||
if worktree.read(cx).contains_entry(entry_id) {
|
||||
Some(worktree)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
mut captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
project_env: Option<&HashMap<String, String>>,
|
||||
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, project_env, 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, project_env, cx)
|
||||
.context("building provider context")?,
|
||||
);
|
||||
}
|
||||
Ok(captured_variables)
|
||||
}
|
||||
|
||||
fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
|
||||
|
@ -4509,43 +4086,6 @@ 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,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sort_worktree_entries(entries: &mut [Entry]) {
|
||||
entries.sort_by(|entry_a, entry_b| {
|
||||
compare_paths(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue