Restructure remote client crate, consolidate SSH logic (#36967)
This is a pure refactor that consolidates all SSH remoting logic such that it should be straightforward to add another transport to the remoting system. Release Notes: - N/A --------- Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
co-authored by
Mikayla Maki
parent
d713390366
commit
1eae76e856
@@ -5,11 +5,8 @@ use super::{
|
||||
session::{self, Session, SessionStateEvent},
|
||||
};
|
||||
use crate::{
|
||||
InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState,
|
||||
debugger::session::SessionQuirks,
|
||||
project_settings::ProjectSettings,
|
||||
terminals::{SshCommand, wrap_for_ssh},
|
||||
worktree_store::WorktreeStore,
|
||||
InlayHint, InlayHintLabel, ProjectEnvironment, ResolveState, debugger::session::SessionQuirks,
|
||||
project_settings::ProjectSettings, worktree_store::WorktreeStore,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
@@ -34,7 +31,7 @@ use http_client::HttpClient;
|
||||
use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind};
|
||||
use node_runtime::NodeRuntime;
|
||||
|
||||
use remote::{SshInfo, SshRemoteClient, ssh_session::SshArgs};
|
||||
use remote::RemoteClient;
|
||||
use rpc::{
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
proto::{self},
|
||||
@@ -68,7 +65,7 @@ pub enum DapStoreEvent {
|
||||
|
||||
enum DapStoreMode {
|
||||
Local(LocalDapStore),
|
||||
Ssh(SshDapStore),
|
||||
Remote(RemoteDapStore),
|
||||
Collab,
|
||||
}
|
||||
|
||||
@@ -80,8 +77,8 @@ pub struct LocalDapStore {
|
||||
toolchain_store: Arc<dyn LanguageToolchainStore>,
|
||||
}
|
||||
|
||||
pub struct SshDapStore {
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
pub struct RemoteDapStore {
|
||||
remote_client: Entity<RemoteClient>,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
}
|
||||
@@ -147,16 +144,16 @@ impl DapStore {
|
||||
Self::new(mode, breakpoint_store, worktree_store, cx)
|
||||
}
|
||||
|
||||
pub fn new_ssh(
|
||||
pub fn new_remote(
|
||||
project_id: u64,
|
||||
ssh_client: Entity<SshRemoteClient>,
|
||||
remote_client: Entity<RemoteClient>,
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let mode = DapStoreMode::Ssh(SshDapStore {
|
||||
upstream_client: ssh_client.read(cx).proto_client(),
|
||||
ssh_client,
|
||||
let mode = DapStoreMode::Remote(RemoteDapStore {
|
||||
upstream_client: remote_client.read(cx).proto_client(),
|
||||
remote_client,
|
||||
upstream_project_id: project_id,
|
||||
});
|
||||
|
||||
@@ -242,64 +239,51 @@ impl DapStore {
|
||||
Ok(binary)
|
||||
})
|
||||
}
|
||||
DapStoreMode::Ssh(ssh) => {
|
||||
let request = ssh.upstream_client.request(proto::GetDebugAdapterBinary {
|
||||
session_id: session_id.to_proto(),
|
||||
project_id: ssh.upstream_project_id,
|
||||
worktree_id: worktree.read(cx).id().to_proto(),
|
||||
definition: Some(definition.to_proto()),
|
||||
});
|
||||
let ssh_client = ssh.ssh_client.clone();
|
||||
DapStoreMode::Remote(remote) => {
|
||||
let request = remote
|
||||
.upstream_client
|
||||
.request(proto::GetDebugAdapterBinary {
|
||||
session_id: session_id.to_proto(),
|
||||
project_id: remote.upstream_project_id,
|
||||
worktree_id: worktree.read(cx).id().to_proto(),
|
||||
definition: Some(definition.to_proto()),
|
||||
});
|
||||
let remote = remote.remote_client.clone();
|
||||
|
||||
cx.spawn(async move |_, cx| {
|
||||
let response = request.await?;
|
||||
let binary = DebugAdapterBinary::from_proto(response)?;
|
||||
let (mut ssh_command, envs, path_style, ssh_shell) =
|
||||
ssh_client.read_with(cx, |ssh, _| {
|
||||
let SshInfo {
|
||||
args: SshArgs { arguments, envs },
|
||||
path_style,
|
||||
shell,
|
||||
} = ssh.ssh_info().context("SSH arguments not found")?;
|
||||
anyhow::Ok((
|
||||
SshCommand { arguments },
|
||||
envs.unwrap_or_default(),
|
||||
path_style,
|
||||
shell,
|
||||
))
|
||||
})??;
|
||||
|
||||
let mut connection = None;
|
||||
let port_forwarding;
|
||||
let connection;
|
||||
if let Some(c) = binary.connection {
|
||||
let local_bind_addr = Ipv4Addr::LOCALHOST;
|
||||
let port =
|
||||
dap::transport::TcpTransport::unused_port(local_bind_addr).await?;
|
||||
|
||||
ssh_command.add_port_forwarding(port, c.host.to_string(), c.port);
|
||||
let host = Ipv4Addr::LOCALHOST;
|
||||
let port = dap::transport::TcpTransport::unused_port(host).await?;
|
||||
port_forwarding = Some((port, c.host.to_string(), c.port));
|
||||
connection = Some(TcpArguments {
|
||||
port,
|
||||
host: local_bind_addr,
|
||||
host,
|
||||
timeout: c.timeout,
|
||||
})
|
||||
} else {
|
||||
port_forwarding = None;
|
||||
connection = None;
|
||||
}
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&ssh_shell,
|
||||
&ssh_command,
|
||||
binary
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &binary.arguments)),
|
||||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
None,
|
||||
path_style,
|
||||
);
|
||||
let command = remote.read_with(cx, |remote, _cx| {
|
||||
remote.build_command(
|
||||
binary.command,
|
||||
&binary.arguments,
|
||||
&binary.envs,
|
||||
binary.cwd.map(|path| path.display().to_string()),
|
||||
port_forwarding,
|
||||
)
|
||||
})??;
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
command: Some(program),
|
||||
arguments: args,
|
||||
envs,
|
||||
command: Some(command.program),
|
||||
arguments: command.args,
|
||||
envs: command.env,
|
||||
cwd: None,
|
||||
connection,
|
||||
request_args: binary.request_args,
|
||||
@@ -365,9 +349,9 @@ impl DapStore {
|
||||
)))
|
||||
}
|
||||
}
|
||||
DapStoreMode::Ssh(ssh) => {
|
||||
let request = ssh.upstream_client.request(proto::RunDebugLocators {
|
||||
project_id: ssh.upstream_project_id,
|
||||
DapStoreMode::Remote(remote) => {
|
||||
let request = remote.upstream_client.request(proto::RunDebugLocators {
|
||||
project_id: remote.upstream_project_id,
|
||||
build_command: Some(build_command.to_proto()),
|
||||
locator: locator_name.to_owned(),
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ use parking_lot::Mutex;
|
||||
use postage::stream::Stream as _;
|
||||
use rpc::{
|
||||
AnyProtoClient, TypedEnvelope,
|
||||
proto::{self, FromProto, SSH_PROJECT_ID, ToProto, git_reset, split_repository_update},
|
||||
proto::{self, FromProto, ToProto, git_reset, split_repository_update},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@@ -141,14 +141,10 @@ enum GitStoreState {
|
||||
project_environment: Entity<ProjectEnvironment>,
|
||||
fs: Arc<dyn Fs>,
|
||||
},
|
||||
Ssh {
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: ProjectId,
|
||||
downstream: Option<(AnyProtoClient, ProjectId)>,
|
||||
},
|
||||
Remote {
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: ProjectId,
|
||||
upstream_project_id: u64,
|
||||
downstream: Option<(AnyProtoClient, ProjectId)>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -355,7 +351,7 @@ impl GitStore {
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
upstream_client: AnyProtoClient,
|
||||
project_id: ProjectId,
|
||||
project_id: u64,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
@@ -364,23 +360,6 @@ impl GitStore {
|
||||
GitStoreState::Remote {
|
||||
upstream_client,
|
||||
upstream_project_id: project_id,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ssh(
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
buffer_store: Entity<BufferStore>,
|
||||
upstream_client: AnyProtoClient,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
worktree_store.clone(),
|
||||
buffer_store,
|
||||
GitStoreState::Ssh {
|
||||
upstream_client,
|
||||
upstream_project_id: ProjectId(SSH_PROJECT_ID),
|
||||
downstream: None,
|
||||
},
|
||||
cx,
|
||||
@@ -451,7 +430,7 @@ impl GitStore {
|
||||
|
||||
pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {
|
||||
match &mut self.state {
|
||||
GitStoreState::Ssh {
|
||||
GitStoreState::Remote {
|
||||
downstream: downstream_client,
|
||||
..
|
||||
} => {
|
||||
@@ -527,9 +506,6 @@ impl GitStore {
|
||||
}),
|
||||
});
|
||||
}
|
||||
GitStoreState::Remote { .. } => {
|
||||
debug_panic!("shared called on remote store");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,15 +517,12 @@ impl GitStore {
|
||||
} => {
|
||||
downstream_client.take();
|
||||
}
|
||||
GitStoreState::Ssh {
|
||||
GitStoreState::Remote {
|
||||
downstream: downstream_client,
|
||||
..
|
||||
} => {
|
||||
downstream_client.take();
|
||||
}
|
||||
GitStoreState::Remote { .. } => {
|
||||
debug_panic!("unshared called on remote store");
|
||||
}
|
||||
}
|
||||
self.shared_diffs.clear();
|
||||
}
|
||||
@@ -1047,21 +1020,17 @@ impl GitStore {
|
||||
} => downstream_client
|
||||
.as_ref()
|
||||
.map(|state| (state.client.clone(), state.project_id)),
|
||||
GitStoreState::Ssh {
|
||||
GitStoreState::Remote {
|
||||
downstream: downstream_client,
|
||||
..
|
||||
} => downstream_client.clone(),
|
||||
GitStoreState::Remote { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn upstream_client(&self) -> Option<AnyProtoClient> {
|
||||
match &self.state {
|
||||
GitStoreState::Local { .. } => None,
|
||||
GitStoreState::Ssh {
|
||||
upstream_client, ..
|
||||
}
|
||||
| GitStoreState::Remote {
|
||||
GitStoreState::Remote {
|
||||
upstream_client, ..
|
||||
} => Some(upstream_client.clone()),
|
||||
}
|
||||
@@ -1432,12 +1401,7 @@ impl GitStore {
|
||||
cx.background_executor()
|
||||
.spawn(async move { fs.git_init(&path, fallback_branch_name) })
|
||||
}
|
||||
GitStoreState::Ssh {
|
||||
upstream_client,
|
||||
upstream_project_id: project_id,
|
||||
..
|
||||
}
|
||||
| GitStoreState::Remote {
|
||||
GitStoreState::Remote {
|
||||
upstream_client,
|
||||
upstream_project_id: project_id,
|
||||
..
|
||||
@@ -1447,7 +1411,7 @@ impl GitStore {
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::GitInit {
|
||||
project_id: project_id.0,
|
||||
project_id: project_id,
|
||||
abs_path: path.to_string_lossy().to_string(),
|
||||
fallback_branch_name,
|
||||
})
|
||||
@@ -1471,13 +1435,18 @@ impl GitStore {
|
||||
cx.background_executor()
|
||||
.spawn(async move { fs.git_clone(&repo, &path).await })
|
||||
}
|
||||
GitStoreState::Ssh {
|
||||
GitStoreState::Remote {
|
||||
upstream_client,
|
||||
upstream_project_id,
|
||||
..
|
||||
} => {
|
||||
if upstream_client.is_via_collab() {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"Git Clone isn't supported for project guests"
|
||||
)));
|
||||
}
|
||||
let request = upstream_client.request(proto::GitClone {
|
||||
project_id: upstream_project_id.0,
|
||||
project_id: *upstream_project_id,
|
||||
abs_path: path.to_string_lossy().to_string(),
|
||||
remote_repo: repo,
|
||||
});
|
||||
@@ -1491,9 +1460,6 @@ impl GitStore {
|
||||
}
|
||||
})
|
||||
}
|
||||
GitStoreState::Remote { .. } => {
|
||||
Task::ready(Err(anyhow!("Git Clone isn't supported for remote users")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+154
-155
@@ -42,9 +42,7 @@ pub use manifest_tree::ManifestTree;
|
||||
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use buffer_store::{BufferStore, BufferStoreEvent};
|
||||
use client::{
|
||||
Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore, proto,
|
||||
};
|
||||
use client::{Client, Collaborator, PendingEntitySubscription, TypedEnvelope, UserStore, proto};
|
||||
use clock::ReplicaId;
|
||||
|
||||
use dap::client::DebugAdapterClient;
|
||||
@@ -89,10 +87,10 @@ use node_runtime::NodeRuntime;
|
||||
use parking_lot::Mutex;
|
||||
pub use prettier_store::PrettierStore;
|
||||
use project_settings::{ProjectSettings, SettingsObserver, SettingsObserverEvent};
|
||||
use remote::{SshConnectionOptions, SshRemoteClient};
|
||||
use remote::{RemoteClient, SshConnectionOptions};
|
||||
use rpc::{
|
||||
AnyProtoClient, ErrorCode,
|
||||
proto::{FromProto, LanguageServerPromptResponse, SSH_PROJECT_ID, ToProto},
|
||||
proto::{FromProto, LanguageServerPromptResponse, REMOTE_SERVER_PROJECT_ID, ToProto},
|
||||
};
|
||||
use search::{SearchInputKind, SearchQuery, SearchResult};
|
||||
use search_history::SearchHistory;
|
||||
@@ -177,12 +175,12 @@ pub struct Project {
|
||||
dap_store: Entity<DapStore>,
|
||||
|
||||
breakpoint_store: Entity<BreakpointStore>,
|
||||
client: Arc<client::Client>,
|
||||
collab_client: Arc<client::Client>,
|
||||
join_project_response_message_id: u32,
|
||||
task_store: Entity<TaskStore>,
|
||||
user_store: Entity<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
ssh_client: Option<Entity<SshRemoteClient>>,
|
||||
remote_client: Option<Entity<RemoteClient>>,
|
||||
client_state: ProjectClientState,
|
||||
git_store: Entity<GitStore>,
|
||||
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||
@@ -1154,12 +1152,12 @@ impl Project {
|
||||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
client,
|
||||
collab_client: client,
|
||||
task_store,
|
||||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
ssh_client: None,
|
||||
remote_client: None,
|
||||
breakpoint_store,
|
||||
dap_store,
|
||||
|
||||
@@ -1183,8 +1181,8 @@ impl Project {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ssh(
|
||||
ssh: Entity<SshRemoteClient>,
|
||||
pub fn remote(
|
||||
remote: Entity<RemoteClient>,
|
||||
client: Arc<Client>,
|
||||
node: NodeRuntime,
|
||||
user_store: Entity<UserStore>,
|
||||
@@ -1200,10 +1198,15 @@ impl Project {
|
||||
let snippets =
|
||||
SnippetProvider::new(fs.clone(), BTreeSet::from_iter([global_snippets_dir]), cx);
|
||||
|
||||
let (ssh_proto, path_style) =
|
||||
ssh.read_with(cx, |ssh, _| (ssh.proto_client(), ssh.path_style()));
|
||||
let (remote_proto, path_style) =
|
||||
remote.read_with(cx, |remote, _| (remote.proto_client(), remote.path_style()));
|
||||
let worktree_store = cx.new(|_| {
|
||||
WorktreeStore::remote(false, ssh_proto.clone(), SSH_PROJECT_ID, path_style)
|
||||
WorktreeStore::remote(
|
||||
false,
|
||||
remote_proto.clone(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
path_style,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
@@ -1215,31 +1218,32 @@ impl Project {
|
||||
let buffer_store = cx.new(|cx| {
|
||||
BufferStore::remote(
|
||||
worktree_store.clone(),
|
||||
ssh.read(cx).proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
remote.read(cx).proto_client(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let image_store = cx.new(|cx| {
|
||||
ImageStore::remote(
|
||||
worktree_store.clone(),
|
||||
ssh.read(cx).proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
remote.read(cx).proto_client(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
|
||||
.detach();
|
||||
let toolchain_store = cx
|
||||
.new(|cx| ToolchainStore::remote(SSH_PROJECT_ID, ssh.read(cx).proto_client(), cx));
|
||||
let toolchain_store = cx.new(|cx| {
|
||||
ToolchainStore::remote(REMOTE_SERVER_PROJECT_ID, remote.read(cx).proto_client(), cx)
|
||||
});
|
||||
let task_store = cx.new(|cx| {
|
||||
TaskStore::remote(
|
||||
fs.clone(),
|
||||
buffer_store.downgrade(),
|
||||
worktree_store.clone(),
|
||||
toolchain_store.read(cx).as_language_toolchain_store(),
|
||||
ssh.read(cx).proto_client(),
|
||||
SSH_PROJECT_ID,
|
||||
remote.read(cx).proto_client(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
@@ -1262,8 +1266,8 @@ impl Project {
|
||||
buffer_store.clone(),
|
||||
worktree_store.clone(),
|
||||
languages.clone(),
|
||||
ssh_proto.clone(),
|
||||
SSH_PROJECT_ID,
|
||||
remote_proto.clone(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
fs.clone(),
|
||||
cx,
|
||||
)
|
||||
@@ -1271,12 +1275,12 @@ impl Project {
|
||||
cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach();
|
||||
|
||||
let breakpoint_store =
|
||||
cx.new(|_| BreakpointStore::remote(SSH_PROJECT_ID, ssh_proto.clone()));
|
||||
cx.new(|_| BreakpointStore::remote(REMOTE_SERVER_PROJECT_ID, remote_proto.clone()));
|
||||
|
||||
let dap_store = cx.new(|cx| {
|
||||
DapStore::new_ssh(
|
||||
SSH_PROJECT_ID,
|
||||
ssh.clone(),
|
||||
DapStore::new_remote(
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
remote.clone(),
|
||||
breakpoint_store.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
@@ -1284,10 +1288,16 @@ impl Project {
|
||||
});
|
||||
|
||||
let git_store = cx.new(|cx| {
|
||||
GitStore::ssh(&worktree_store, buffer_store.clone(), ssh_proto.clone(), cx)
|
||||
GitStore::remote(
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
remote_proto.clone(),
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.subscribe(&ssh, Self::on_ssh_event).detach();
|
||||
cx.subscribe(&remote, Self::on_remote_client_event).detach();
|
||||
|
||||
let this = Self {
|
||||
buffer_ordered_messages_tx: tx,
|
||||
@@ -1306,11 +1316,13 @@ impl Project {
|
||||
_subscriptions: vec![
|
||||
cx.on_release(Self::release),
|
||||
cx.on_app_quit(|this, cx| {
|
||||
let shutdown = this.ssh_client.take().and_then(|client| {
|
||||
client.read(cx).shutdown_processes(
|
||||
Some(proto::ShutdownRemoteServer {}),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
let shutdown = this.remote_client.take().and_then(|client| {
|
||||
client.update(cx, |client, cx| {
|
||||
client.shutdown_processes(
|
||||
Some(proto::ShutdownRemoteServer {}),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
cx.background_executor().spawn(async move {
|
||||
@@ -1323,12 +1335,12 @@ impl Project {
|
||||
active_entry: None,
|
||||
snippets,
|
||||
languages,
|
||||
client,
|
||||
collab_client: client,
|
||||
task_store,
|
||||
user_store,
|
||||
settings_observer,
|
||||
fs,
|
||||
ssh_client: Some(ssh.clone()),
|
||||
remote_client: Some(remote.clone()),
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
terminals: Terminals {
|
||||
@@ -1346,52 +1358,34 @@ impl Project {
|
||||
agent_location: None,
|
||||
};
|
||||
|
||||
// ssh -> local machine handlers
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &cx.entity());
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.buffer_store);
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.worktree_store);
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.lsp_store);
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.dap_store);
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.settings_observer);
|
||||
ssh_proto.subscribe_to_entity(SSH_PROJECT_ID, &this.git_store);
|
||||
// remote server -> local machine handlers
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &cx.entity());
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.buffer_store);
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.worktree_store);
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.lsp_store);
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.dap_store);
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.settings_observer);
|
||||
remote_proto.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &this.git_store);
|
||||
|
||||
ssh_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
|
||||
ssh_proto.add_entity_message_handler(Self::handle_update_worktree);
|
||||
ssh_proto.add_entity_message_handler(Self::handle_update_project);
|
||||
ssh_proto.add_entity_message_handler(Self::handle_toast);
|
||||
ssh_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
|
||||
ssh_proto.add_entity_message_handler(Self::handle_hide_toast);
|
||||
ssh_proto.add_entity_request_handler(Self::handle_update_buffer_from_ssh);
|
||||
BufferStore::init(&ssh_proto);
|
||||
LspStore::init(&ssh_proto);
|
||||
SettingsObserver::init(&ssh_proto);
|
||||
TaskStore::init(Some(&ssh_proto));
|
||||
ToolchainStore::init(&ssh_proto);
|
||||
DapStore::init(&ssh_proto, cx);
|
||||
GitStore::init(&ssh_proto);
|
||||
remote_proto.add_entity_message_handler(Self::handle_create_buffer_for_peer);
|
||||
remote_proto.add_entity_message_handler(Self::handle_update_worktree);
|
||||
remote_proto.add_entity_message_handler(Self::handle_update_project);
|
||||
remote_proto.add_entity_message_handler(Self::handle_toast);
|
||||
remote_proto.add_entity_request_handler(Self::handle_language_server_prompt_request);
|
||||
remote_proto.add_entity_message_handler(Self::handle_hide_toast);
|
||||
remote_proto.add_entity_request_handler(Self::handle_update_buffer_from_remote_server);
|
||||
BufferStore::init(&remote_proto);
|
||||
LspStore::init(&remote_proto);
|
||||
SettingsObserver::init(&remote_proto);
|
||||
TaskStore::init(Some(&remote_proto));
|
||||
ToolchainStore::init(&remote_proto);
|
||||
DapStore::init(&remote_proto, cx);
|
||||
GitStore::init(&remote_proto);
|
||||
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remote(
|
||||
remote_id: u64,
|
||||
client: Arc<Client>,
|
||||
user_store: Entity<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: AsyncApp,
|
||||
) -> Result<Entity<Self>> {
|
||||
let project =
|
||||
Self::in_room(remote_id, client, user_store, languages, fs, cx.clone()).await?;
|
||||
cx.update(|cx| {
|
||||
connection_manager::Manager::global(cx).update(cx, |manager, cx| {
|
||||
manager.maintain_project_connection(&project, cx)
|
||||
})
|
||||
})?;
|
||||
Ok(project)
|
||||
}
|
||||
|
||||
pub async fn in_room(
|
||||
remote_id: u64,
|
||||
client: Arc<Client>,
|
||||
@@ -1523,7 +1517,7 @@ impl Project {
|
||||
&worktree_store,
|
||||
buffer_store.clone(),
|
||||
client.clone().into(),
|
||||
ProjectId(remote_id),
|
||||
remote_id,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
@@ -1574,11 +1568,11 @@ impl Project {
|
||||
task_store,
|
||||
snippets,
|
||||
fs,
|
||||
ssh_client: None,
|
||||
remote_client: None,
|
||||
settings_observer: settings_observer.clone(),
|
||||
client_subscriptions: Default::default(),
|
||||
_subscriptions: vec![cx.on_release(Self::release)],
|
||||
client: client.clone(),
|
||||
collab_client: client.clone(),
|
||||
client_state: ProjectClientState::Remote {
|
||||
sharing_has_stopped: false,
|
||||
capability: Capability::ReadWrite,
|
||||
@@ -1661,11 +1655,13 @@ impl Project {
|
||||
}
|
||||
|
||||
fn release(&mut self, cx: &mut App) {
|
||||
if let Some(client) = self.ssh_client.take() {
|
||||
let shutdown = client.read(cx).shutdown_processes(
|
||||
Some(proto::ShutdownRemoteServer {}),
|
||||
cx.background_executor().clone(),
|
||||
);
|
||||
if let Some(client) = self.remote_client.take() {
|
||||
let shutdown = client.update(cx, |client, cx| {
|
||||
client.shutdown_processes(
|
||||
Some(proto::ShutdownRemoteServer {}),
|
||||
cx.background_executor().clone(),
|
||||
)
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
if let Some(shutdown) = shutdown {
|
||||
@@ -1681,7 +1677,7 @@ impl Project {
|
||||
let _ = self.unshare_internal(cx);
|
||||
}
|
||||
ProjectClientState::Remote { remote_id, .. } => {
|
||||
let _ = self.client.send(proto::LeaveProject {
|
||||
let _ = self.collab_client.send(proto::LeaveProject {
|
||||
project_id: *remote_id,
|
||||
});
|
||||
self.disconnected_from_host_internal(cx);
|
||||
@@ -1808,11 +1804,11 @@ impl Project {
|
||||
}
|
||||
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
self.client.clone()
|
||||
self.collab_client.clone()
|
||||
}
|
||||
|
||||
pub fn ssh_client(&self) -> Option<Entity<SshRemoteClient>> {
|
||||
self.ssh_client.clone()
|
||||
pub fn remote_client(&self) -> Option<Entity<RemoteClient>> {
|
||||
self.remote_client.clone()
|
||||
}
|
||||
|
||||
pub fn user_store(&self) -> Entity<UserStore> {
|
||||
@@ -1893,30 +1889,30 @@ impl Project {
|
||||
if self.is_local() {
|
||||
return true;
|
||||
}
|
||||
if self.is_via_ssh() {
|
||||
if self.is_via_remote_server() {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn ssh_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> {
|
||||
self.ssh_client
|
||||
pub fn remote_connection_state(&self, cx: &App) -> Option<remote::ConnectionState> {
|
||||
self.remote_client
|
||||
.as_ref()
|
||||
.map(|ssh| ssh.read(cx).connection_state())
|
||||
.map(|remote| remote.read(cx).connection_state())
|
||||
}
|
||||
|
||||
pub fn ssh_connection_options(&self, cx: &App) -> Option<SshConnectionOptions> {
|
||||
self.ssh_client
|
||||
pub fn remote_connection_options(&self, cx: &App) -> Option<SshConnectionOptions> {
|
||||
self.remote_client
|
||||
.as_ref()
|
||||
.map(|ssh| ssh.read(cx).connection_options())
|
||||
.map(|remote| remote.read(cx).connection_options())
|
||||
}
|
||||
|
||||
pub fn replica_id(&self) -> ReplicaId {
|
||||
match self.client_state {
|
||||
ProjectClientState::Remote { replica_id, .. } => replica_id,
|
||||
_ => {
|
||||
if self.ssh_client.is_some() {
|
||||
if self.remote_client.is_some() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -2220,55 +2216,55 @@ impl Project {
|
||||
);
|
||||
|
||||
self.client_subscriptions.extend([
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&cx.entity(), &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.worktree_store, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.buffer_store, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.lsp_store, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.settings_observer, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.dap_store, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.breakpoint_store, &cx.to_async()),
|
||||
self.client
|
||||
self.collab_client
|
||||
.subscribe_to_entity(project_id)?
|
||||
.set_entity(&self.git_store, &cx.to_async()),
|
||||
]);
|
||||
|
||||
self.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store.shared(project_id, self.client.clone().into(), cx)
|
||||
buffer_store.shared(project_id, self.collab_client.clone().into(), cx)
|
||||
});
|
||||
self.worktree_store.update(cx, |worktree_store, cx| {
|
||||
worktree_store.shared(project_id, self.client.clone().into(), cx);
|
||||
worktree_store.shared(project_id, self.collab_client.clone().into(), cx);
|
||||
});
|
||||
self.lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store.shared(project_id, self.client.clone().into(), cx)
|
||||
lsp_store.shared(project_id, self.collab_client.clone().into(), cx)
|
||||
});
|
||||
self.breakpoint_store.update(cx, |breakpoint_store, _| {
|
||||
breakpoint_store.shared(project_id, self.client.clone().into())
|
||||
breakpoint_store.shared(project_id, self.collab_client.clone().into())
|
||||
});
|
||||
self.dap_store.update(cx, |dap_store, cx| {
|
||||
dap_store.shared(project_id, self.client.clone().into(), cx);
|
||||
dap_store.shared(project_id, self.collab_client.clone().into(), cx);
|
||||
});
|
||||
self.task_store.update(cx, |task_store, cx| {
|
||||
task_store.shared(project_id, self.client.clone().into(), cx);
|
||||
task_store.shared(project_id, self.collab_client.clone().into(), cx);
|
||||
});
|
||||
self.settings_observer.update(cx, |settings_observer, cx| {
|
||||
settings_observer.shared(project_id, self.client.clone().into(), cx)
|
||||
settings_observer.shared(project_id, self.collab_client.clone().into(), cx)
|
||||
});
|
||||
self.git_store.update(cx, |git_store, cx| {
|
||||
git_store.shared(project_id, self.client.clone().into(), cx)
|
||||
git_store.shared(project_id, self.collab_client.clone().into(), cx)
|
||||
});
|
||||
|
||||
self.client_state = ProjectClientState::Shared {
|
||||
@@ -2293,7 +2289,7 @@ impl Project {
|
||||
});
|
||||
if let Some(remote_id) = self.remote_id() {
|
||||
self.git_store.update(cx, |git_store, cx| {
|
||||
git_store.shared(remote_id, self.client.clone().into(), cx)
|
||||
git_store.shared(remote_id, self.collab_client.clone().into(), cx)
|
||||
});
|
||||
}
|
||||
cx.emit(Event::Reshared);
|
||||
@@ -2370,7 +2366,7 @@ impl Project {
|
||||
git_store.unshared(cx);
|
||||
});
|
||||
|
||||
self.client
|
||||
self.collab_client
|
||||
.send(proto::UnshareProject {
|
||||
project_id: remote_id,
|
||||
})
|
||||
@@ -2437,15 +2433,17 @@ impl Project {
|
||||
sharing_has_stopped,
|
||||
..
|
||||
} => *sharing_has_stopped,
|
||||
ProjectClientState::Local if self.is_via_ssh() => self.ssh_is_disconnected(cx),
|
||||
ProjectClientState::Local if self.is_via_remote_server() => {
|
||||
self.remote_client_is_disconnected(cx)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ssh_is_disconnected(&self, cx: &App) -> bool {
|
||||
self.ssh_client
|
||||
fn remote_client_is_disconnected(&self, cx: &App) -> bool {
|
||||
self.remote_client
|
||||
.as_ref()
|
||||
.map(|ssh| ssh.read(cx).is_disconnected())
|
||||
.map(|remote| remote.read(cx).is_disconnected())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -2463,16 +2461,16 @@ impl Project {
|
||||
pub fn is_local(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local | ProjectClientState::Shared { .. } => {
|
||||
self.ssh_client.is_none()
|
||||
self.remote_client.is_none()
|
||||
}
|
||||
ProjectClientState::Remote { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_via_ssh(&self) -> bool {
|
||||
pub fn is_via_remote_server(&self) -> bool {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local | ProjectClientState::Shared { .. } => {
|
||||
self.ssh_client.is_some()
|
||||
self.remote_client.is_some()
|
||||
}
|
||||
ProjectClientState::Remote { .. } => false,
|
||||
}
|
||||
@@ -2496,7 +2494,7 @@ impl Project {
|
||||
language: Option<Arc<Language>>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Entity<Buffer> {
|
||||
if self.is_via_collab() || self.is_via_ssh() {
|
||||
if self.is_via_collab() || self.is_via_remote_server() {
|
||||
panic!("called create_local_buffer on a remote project")
|
||||
}
|
||||
self.buffer_store.update(cx, |buffer_store, cx| {
|
||||
@@ -2620,10 +2618,10 @@ impl Project {
|
||||
) -> Task<Result<Entity<Buffer>>> {
|
||||
if let Some(buffer) = self.buffer_for_id(id, cx) {
|
||||
Task::ready(Ok(buffer))
|
||||
} else if self.is_local() || self.is_via_ssh() {
|
||||
} else if self.is_local() || self.is_via_remote_server() {
|
||||
Task::ready(Err(anyhow!("buffer {id} does not exist")))
|
||||
} else if let Some(project_id) = self.remote_id() {
|
||||
let request = self.client.request(proto::OpenBufferById {
|
||||
let request = self.collab_client.request(proto::OpenBufferById {
|
||||
project_id,
|
||||
id: id.into(),
|
||||
});
|
||||
@@ -2741,7 +2739,7 @@ impl Project {
|
||||
for (buffer_id, operations) in operations_by_buffer_id.drain() {
|
||||
let request = this.read_with(cx, |this, _| {
|
||||
let project_id = this.remote_id()?;
|
||||
Some(this.client.request(proto::UpdateBuffer {
|
||||
Some(this.collab_client.request(proto::UpdateBuffer {
|
||||
buffer_id: buffer_id.into(),
|
||||
project_id,
|
||||
operations,
|
||||
@@ -2808,7 +2806,7 @@ impl Project {
|
||||
project.read_with(cx, |project, _| {
|
||||
if let Some(project_id) = project.remote_id() {
|
||||
project
|
||||
.client
|
||||
.collab_client
|
||||
.send(proto::UpdateLanguageServer {
|
||||
project_id,
|
||||
server_name: name.map(|name| String::from(name.0)),
|
||||
@@ -2846,8 +2844,8 @@ impl Project {
|
||||
self.register_buffer(buffer, cx).log_err();
|
||||
}
|
||||
BufferStoreEvent::BufferDropped(buffer_id) => {
|
||||
if let Some(ref ssh_client) = self.ssh_client {
|
||||
ssh_client
|
||||
if let Some(ref remote_client) = self.remote_client {
|
||||
remote_client
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.send(proto::CloseBuffer {
|
||||
@@ -2995,16 +2993,14 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_ssh_event(
|
||||
fn on_remote_client_event(
|
||||
&mut self,
|
||||
_: Entity<SshRemoteClient>,
|
||||
event: &remote::SshRemoteEvent,
|
||||
_: Entity<RemoteClient>,
|
||||
event: &remote::RemoteClientEvent,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
remote::SshRemoteEvent::Disconnected => {
|
||||
// if self.is_via_ssh() {
|
||||
// self.collaborators.clear();
|
||||
remote::RemoteClientEvent::Disconnected => {
|
||||
self.worktree_store.update(cx, |store, cx| {
|
||||
store.disconnected_from_host(cx);
|
||||
});
|
||||
@@ -3110,8 +3106,9 @@ impl Project {
|
||||
}
|
||||
|
||||
fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
|
||||
if let Some(ssh) = &self.ssh_client {
|
||||
ssh.read(cx)
|
||||
if let Some(remote) = &self.remote_client {
|
||||
remote
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.send(proto::RemoveWorktree {
|
||||
worktree_id: id_to_remove.to_proto(),
|
||||
@@ -3144,8 +3141,9 @@ impl Project {
|
||||
} => {
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
|
||||
if let Some(ssh) = &self.ssh_client {
|
||||
ssh.read(cx)
|
||||
if let Some(remote) = &self.remote_client {
|
||||
remote
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.send(proto::UpdateBuffer {
|
||||
project_id: 0,
|
||||
@@ -3552,16 +3550,16 @@ impl Project {
|
||||
|
||||
pub fn open_server_settings(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<Buffer>>> {
|
||||
let guard = self.retain_remotely_created_models(cx);
|
||||
let Some(ssh_client) = self.ssh_client.as_ref() else {
|
||||
let Some(remote) = self.remote_client.as_ref() else {
|
||||
return Task::ready(Err(anyhow!("not an ssh project")));
|
||||
};
|
||||
|
||||
let proto_client = ssh_client.read(cx).proto_client();
|
||||
let proto_client = remote.read(cx).proto_client();
|
||||
|
||||
cx.spawn(async move |project, cx| {
|
||||
let buffer = proto_client
|
||||
.request(proto::OpenServerSettings {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -3948,10 +3946,11 @@ impl Project {
|
||||
) -> Receiver<Entity<Buffer>> {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
|
||||
let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.ssh_client {
|
||||
let (client, remote_id): (AnyProtoClient, _) = if let Some(ssh_client) = &self.remote_client
|
||||
{
|
||||
(ssh_client.read(cx).proto_client(), 0)
|
||||
} else if let Some(remote_id) = self.remote_id() {
|
||||
(self.client.clone().into(), remote_id)
|
||||
(self.collab_client.clone().into(), remote_id)
|
||||
} else {
|
||||
return rx;
|
||||
};
|
||||
@@ -4095,14 +4094,14 @@ impl Project {
|
||||
is_dir: metadata.is_dir,
|
||||
})
|
||||
})
|
||||
} else if let Some(ssh_client) = self.ssh_client.as_ref() {
|
||||
} else if let Some(ssh_client) = self.remote_client.as_ref() {
|
||||
let path_style = ssh_client.read(cx).path_style();
|
||||
let request_path = RemotePathBuf::from_str(path, path_style);
|
||||
let request = ssh_client
|
||||
.read(cx)
|
||||
.proto_client()
|
||||
.request(proto::GetPathMetadata {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||
path: request_path.to_proto(),
|
||||
});
|
||||
cx.background_spawn(async move {
|
||||
@@ -4202,10 +4201,10 @@ impl Project {
|
||||
) -> Task<Result<Vec<DirectoryItem>>> {
|
||||
if self.is_local() {
|
||||
DirectoryLister::Local(cx.entity(), self.fs.clone()).list_directory(query, cx)
|
||||
} else if let Some(session) = self.ssh_client.as_ref() {
|
||||
} else if let Some(session) = self.remote_client.as_ref() {
|
||||
let path_buf = PathBuf::from(query);
|
||||
let request = proto::ListRemoteDirectory {
|
||||
dev_server_id: SSH_PROJECT_ID,
|
||||
dev_server_id: REMOTE_SERVER_PROJECT_ID,
|
||||
path: path_buf.to_proto(),
|
||||
config: Some(proto::ListRemoteDirectoryConfig { is_dir: true }),
|
||||
};
|
||||
@@ -4420,7 +4419,7 @@ impl Project {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if this.is_local() || this.is_via_ssh() {
|
||||
if this.is_local() || this.is_via_remote_server() {
|
||||
this.unshare(cx)?;
|
||||
} else {
|
||||
this.disconnected_from_host(cx);
|
||||
@@ -4629,7 +4628,7 @@ impl Project {
|
||||
})?
|
||||
}
|
||||
|
||||
async fn handle_update_buffer_from_ssh(
|
||||
async fn handle_update_buffer_from_remote_server(
|
||||
this: Entity<Self>,
|
||||
envelope: TypedEnvelope<proto::UpdateBuffer>,
|
||||
cx: AsyncApp,
|
||||
@@ -4638,7 +4637,7 @@ impl Project {
|
||||
if let Some(remote_id) = this.remote_id() {
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = remote_id;
|
||||
cx.background_spawn(this.client.request(payload))
|
||||
cx.background_spawn(this.collab_client.request(payload))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
this.buffer_store.clone()
|
||||
@@ -4652,9 +4651,9 @@ impl Project {
|
||||
cx: AsyncApp,
|
||||
) -> Result<proto::Ack> {
|
||||
let buffer_store = this.read_with(&cx, |this, cx| {
|
||||
if let Some(ssh) = &this.ssh_client {
|
||||
if let Some(ssh) = &this.remote_client {
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = SSH_PROJECT_ID;
|
||||
payload.project_id = REMOTE_SERVER_PROJECT_ID;
|
||||
cx.background_spawn(ssh.read(cx).proto_client().request(payload))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
@@ -4704,7 +4703,7 @@ impl Project {
|
||||
mut cx: AsyncApp,
|
||||
) -> Result<proto::SynchronizeBuffersResponse> {
|
||||
let response = this.update(&mut cx, |this, cx| {
|
||||
let client = this.client.clone();
|
||||
let client = this.collab_client.clone();
|
||||
this.buffer_store.update(cx, |this, cx| {
|
||||
this.handle_synchronize_buffers(envelope, cx, client)
|
||||
})
|
||||
@@ -4841,7 +4840,7 @@ impl Project {
|
||||
}
|
||||
};
|
||||
|
||||
let client = self.client.clone();
|
||||
let client = self.collab_client.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
let (buffers, incomplete_buffer_ids) = this.update(cx, |this, cx| {
|
||||
this.buffer_store.read(cx).buffer_version_info(cx)
|
||||
|
||||
+109
-242
@@ -2,13 +2,11 @@ use crate::{Project, ProjectPath};
|
||||
use anyhow::{Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity};
|
||||
use itertools::Itertools;
|
||||
use language::LanguageName;
|
||||
use remote::{SshInfo, ssh_session::SshArgs};
|
||||
use remote::RemoteClient;
|
||||
use settings::{Settings, SettingsLocation};
|
||||
use smol::channel::bounded;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env::{self},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
@@ -18,10 +16,7 @@ use terminal::{
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
||||
terminal_settings::{self, ActivateScript, TerminalSettings, VenvSettings},
|
||||
};
|
||||
use util::{
|
||||
ResultExt,
|
||||
paths::{PathStyle, RemotePathBuf},
|
||||
};
|
||||
use util::{ResultExt, paths::RemotePathBuf};
|
||||
|
||||
/// The directory inside a Python virtual environment that contains executables
|
||||
const PYTHON_VENV_BIN_DIR: &str = if cfg!(target_os = "windows") {
|
||||
@@ -44,29 +39,6 @@ pub enum TerminalKind {
|
||||
Task(SpawnInTerminal),
|
||||
}
|
||||
|
||||
/// SshCommand describes how to connect to a remote server
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SshCommand {
|
||||
pub arguments: Vec<String>,
|
||||
}
|
||||
|
||||
impl SshCommand {
|
||||
pub fn add_port_forwarding(&mut self, local_port: u16, host: String, remote_port: u16) {
|
||||
self.arguments.push("-L".to_string());
|
||||
self.arguments
|
||||
.push(format!("{}:{}:{}", local_port, host, remote_port));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SshDetails {
|
||||
pub host: String,
|
||||
pub ssh_command: SshCommand,
|
||||
pub envs: Option<HashMap<String, String>>,
|
||||
pub path_style: PathStyle,
|
||||
pub shell: String,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn active_project_directory(&self, cx: &App) -> Option<Arc<Path>> {
|
||||
self.active_entry()
|
||||
@@ -86,28 +58,6 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ssh_details(&self, cx: &App) -> Option<SshDetails> {
|
||||
if let Some(ssh_client) = &self.ssh_client {
|
||||
let ssh_client = ssh_client.read(cx);
|
||||
if let Some(SshInfo {
|
||||
args: SshArgs { arguments, envs },
|
||||
path_style,
|
||||
shell,
|
||||
}) = ssh_client.ssh_info()
|
||||
{
|
||||
return Some(SshDetails {
|
||||
host: ssh_client.connection_options().host,
|
||||
ssh_command: SshCommand { arguments },
|
||||
envs,
|
||||
path_style,
|
||||
shell,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn create_terminal(
|
||||
&mut self,
|
||||
kind: TerminalKind,
|
||||
@@ -168,14 +118,14 @@ impl Project {
|
||||
TerminalSettings::get(settings_location, cx)
|
||||
}
|
||||
|
||||
pub fn exec_in_shell(&self, command: String, cx: &App) -> std::process::Command {
|
||||
pub fn exec_in_shell(&self, command: String, cx: &App) -> Result<std::process::Command> {
|
||||
let path = self.first_project_directory(cx);
|
||||
let ssh_details = self.ssh_details(cx);
|
||||
let remote_client = self.remote_client.as_ref();
|
||||
let settings = self.terminal_settings(&path, cx).clone();
|
||||
|
||||
let builder =
|
||||
ShellBuilder::new(ssh_details.as_ref().map(|ssh| &*ssh.shell), &settings.shell)
|
||||
.non_interactive();
|
||||
let remote_shell = remote_client
|
||||
.as_ref()
|
||||
.and_then(|remote_client| remote_client.read(cx).shell());
|
||||
let builder = ShellBuilder::new(remote_shell.as_deref(), &settings.shell).non_interactive();
|
||||
let (command, args) = builder.build(Some(command), &Vec::new());
|
||||
|
||||
let mut env = self
|
||||
@@ -185,29 +135,16 @@ impl Project {
|
||||
.unwrap_or_default();
|
||||
env.extend(settings.env);
|
||||
|
||||
match self.ssh_details(cx) {
|
||||
Some(SshDetails {
|
||||
ssh_command,
|
||||
envs,
|
||||
path_style,
|
||||
shell,
|
||||
..
|
||||
}) => {
|
||||
let (command, args) = wrap_for_ssh(
|
||||
&shell,
|
||||
&ssh_command,
|
||||
Some((&command, &args)),
|
||||
path.as_deref(),
|
||||
env,
|
||||
None,
|
||||
path_style,
|
||||
);
|
||||
let mut command = std::process::Command::new(command);
|
||||
command.args(args);
|
||||
if let Some(envs) = envs {
|
||||
command.envs(envs);
|
||||
}
|
||||
command
|
||||
match remote_client {
|
||||
Some(remote_client) => {
|
||||
let command_template =
|
||||
remote_client
|
||||
.read(cx)
|
||||
.build_command(Some(command), &args, &env, None, None)?;
|
||||
let mut command = std::process::Command::new(command_template.program);
|
||||
command.args(command_template.args);
|
||||
command.envs(command_template.env);
|
||||
Ok(command)
|
||||
}
|
||||
None => {
|
||||
let mut command = std::process::Command::new(command);
|
||||
@@ -216,7 +153,7 @@ impl Project {
|
||||
if let Some(path) = path {
|
||||
command.current_dir(path);
|
||||
}
|
||||
command
|
||||
Ok(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,13 +164,13 @@ impl Project {
|
||||
python_venv_directory: Option<PathBuf>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<Entity<Terminal>> {
|
||||
let this = &mut *self;
|
||||
let ssh_details = this.ssh_details(cx);
|
||||
let is_via_remote = self.remote_client.is_some();
|
||||
|
||||
let path: Option<Arc<Path>> = match &kind {
|
||||
TerminalKind::Shell(path) => path.as_ref().map(|path| Arc::from(path.as_ref())),
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
if let Some(cwd) = &spawn_task.cwd {
|
||||
if ssh_details.is_some() {
|
||||
if is_via_remote {
|
||||
Some(Arc::from(cwd.as_ref()))
|
||||
} else {
|
||||
let cwd = cwd.to_string_lossy();
|
||||
@@ -241,16 +178,14 @@ impl Project {
|
||||
Some(Arc::from(Path::new(tilde_substituted.as_ref())))
|
||||
}
|
||||
} else {
|
||||
this.active_project_directory(cx)
|
||||
self.active_project_directory(cx)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let is_ssh_terminal = ssh_details.is_some();
|
||||
|
||||
let mut settings_location = None;
|
||||
if let Some(path) = path.as_ref()
|
||||
&& let Some((worktree, _)) = this.find_worktree(path, cx)
|
||||
&& let Some((worktree, _)) = self.find_worktree(path, cx)
|
||||
{
|
||||
settings_location = Some(SettingsLocation {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
@@ -262,7 +197,7 @@ impl Project {
|
||||
let (completion_tx, completion_rx) = bounded(1);
|
||||
|
||||
// Start with the environment that we might have inherited from the Zed CLI.
|
||||
let mut env = this
|
||||
let mut env = self
|
||||
.environment
|
||||
.read(cx)
|
||||
.get_cli_environment()
|
||||
@@ -271,14 +206,17 @@ impl Project {
|
||||
// precedence.
|
||||
env.extend(settings.env);
|
||||
|
||||
let local_path = if is_ssh_terminal { None } else { path.clone() };
|
||||
let local_path = if is_via_remote { None } else { path.clone() };
|
||||
|
||||
let mut python_venv_activate_command = Task::ready(None);
|
||||
|
||||
let (spawn_task, shell) = match kind {
|
||||
let remote_client = self.remote_client.clone();
|
||||
let spawn_task;
|
||||
let shell;
|
||||
match kind {
|
||||
TerminalKind::Shell(_) => {
|
||||
if let Some(python_venv_directory) = &python_venv_directory {
|
||||
python_venv_activate_command = this.python_activate_command(
|
||||
python_venv_activate_command = self.python_activate_command(
|
||||
python_venv_directory,
|
||||
&settings.detect_venv,
|
||||
&settings.shell,
|
||||
@@ -286,63 +224,16 @@ impl Project {
|
||||
);
|
||||
}
|
||||
|
||||
match ssh_details {
|
||||
Some(SshDetails {
|
||||
host,
|
||||
ssh_command,
|
||||
envs,
|
||||
path_style,
|
||||
shell,
|
||||
}) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
|
||||
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
|
||||
// to properly display colors.
|
||||
// We do not have the luxury of assuming the host has it installed,
|
||||
// so we set it to a default that does not break the highlighting via ssh.
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&shell,
|
||||
&ssh_command,
|
||||
None,
|
||||
path.as_deref(),
|
||||
env,
|
||||
None,
|
||||
path_style,
|
||||
);
|
||||
env = HashMap::default();
|
||||
if let Some(envs) = envs {
|
||||
env.extend(envs);
|
||||
}
|
||||
(
|
||||
Option::<TaskState>::None,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
spawn_task = None;
|
||||
shell = match remote_client {
|
||||
Some(remote_client) => {
|
||||
create_remote_shell(None, &mut env, path, remote_client, cx)?
|
||||
}
|
||||
None => (None, settings.shell),
|
||||
}
|
||||
None => settings.shell,
|
||||
};
|
||||
}
|
||||
TerminalKind::Task(spawn_task) => {
|
||||
let task_state = Some(TaskState {
|
||||
id: spawn_task.id,
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
show_summary: spawn_task.show_summary,
|
||||
show_command: spawn_task.show_command,
|
||||
show_rerun: spawn_task.show_rerun,
|
||||
completion_rx,
|
||||
});
|
||||
|
||||
env.extend(spawn_task.env);
|
||||
TerminalKind::Task(task) => {
|
||||
env.extend(task.env);
|
||||
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
env.insert(
|
||||
@@ -351,41 +242,38 @@ impl Project {
|
||||
);
|
||||
}
|
||||
|
||||
match ssh_details {
|
||||
Some(SshDetails {
|
||||
host,
|
||||
ssh_command,
|
||||
envs,
|
||||
path_style,
|
||||
shell,
|
||||
}) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
let (program, args) = wrap_for_ssh(
|
||||
&shell,
|
||||
&ssh_command,
|
||||
spawn_task
|
||||
.command
|
||||
.as_ref()
|
||||
.map(|command| (command, &spawn_task.args)),
|
||||
path.as_deref(),
|
||||
env,
|
||||
python_venv_directory.as_deref(),
|
||||
path_style,
|
||||
);
|
||||
env = HashMap::default();
|
||||
if let Some(envs) = envs {
|
||||
env.extend(envs);
|
||||
spawn_task = Some(TaskState {
|
||||
id: task.id,
|
||||
full_label: task.full_label,
|
||||
label: task.label,
|
||||
command_label: task.command_label,
|
||||
hide: task.hide,
|
||||
status: TaskStatus::Running,
|
||||
show_summary: task.show_summary,
|
||||
show_command: task.show_command,
|
||||
show_rerun: task.show_rerun,
|
||||
completion_rx,
|
||||
});
|
||||
shell = match remote_client {
|
||||
Some(remote_client) => {
|
||||
let path_style = remote_client.read(cx).path_style();
|
||||
if let Some(venv_directory) = &python_venv_directory
|
||||
&& let Ok(str) =
|
||||
shlex::try_quote(venv_directory.to_string_lossy().as_ref())
|
||||
{
|
||||
let path =
|
||||
RemotePathBuf::new(PathBuf::from(str.to_string()), path_style)
|
||||
.to_string();
|
||||
env.insert("PATH".into(), format!("{}:$PATH ", path));
|
||||
}
|
||||
(
|
||||
task_state,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
|
||||
create_remote_shell(
|
||||
task.command.as_ref().map(|command| (command, &task.args)),
|
||||
&mut env,
|
||||
path,
|
||||
remote_client,
|
||||
cx,
|
||||
)?
|
||||
}
|
||||
None => {
|
||||
if let Some(venv_path) = &python_venv_directory {
|
||||
@@ -393,18 +281,17 @@ impl Project {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
let shell = if let Some(program) = spawn_task.command {
|
||||
if let Some(program) = task.command {
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: spawn_task.args,
|
||||
args: task.args,
|
||||
title_override: None,
|
||||
}
|
||||
} else {
|
||||
Shell::System
|
||||
};
|
||||
(task_state, shell)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
TerminalBuilder::new(
|
||||
@@ -416,7 +303,7 @@ impl Project {
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
settings.alternate_scroll,
|
||||
settings.max_scroll_history_lines,
|
||||
is_ssh_terminal,
|
||||
is_via_remote,
|
||||
cx.entity_id().as_u64(),
|
||||
completion_tx,
|
||||
cx,
|
||||
@@ -424,7 +311,7 @@ impl Project {
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
||||
this.terminals
|
||||
self.terminals
|
||||
.local_handles
|
||||
.push(terminal_handle.downgrade());
|
||||
|
||||
@@ -442,7 +329,7 @@ impl Project {
|
||||
})
|
||||
.detach();
|
||||
|
||||
this.activate_python_virtual_environment(
|
||||
self.activate_python_virtual_environment(
|
||||
python_venv_activate_command,
|
||||
&terminal_handle,
|
||||
cx,
|
||||
@@ -652,62 +539,42 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wrap_for_ssh(
|
||||
shell: &str,
|
||||
ssh_command: &SshCommand,
|
||||
command: Option<(&String, &Vec<String>)>,
|
||||
path: Option<&Path>,
|
||||
env: HashMap<String, String>,
|
||||
venv_directory: Option<&Path>,
|
||||
path_style: PathStyle,
|
||||
) -> (String, Vec<String>) {
|
||||
let to_run = if let Some((command, args)) = command {
|
||||
let command: Option<Cow<str>> = shlex::try_quote(command).ok();
|
||||
let args = args.iter().filter_map(|arg| shlex::try_quote(arg).ok());
|
||||
command.into_iter().chain(args).join(" ")
|
||||
} else {
|
||||
format!("exec {shell} -l")
|
||||
fn create_remote_shell(
|
||||
spawn_command: Option<(&String, &Vec<String>)>,
|
||||
env: &mut HashMap<String, String>,
|
||||
working_directory: Option<Arc<Path>>,
|
||||
remote_client: Entity<RemoteClient>,
|
||||
cx: &mut App,
|
||||
) -> Result<Shell> {
|
||||
// Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed
|
||||
// to properly display colors.
|
||||
// We do not have the luxury of assuming the host has it installed,
|
||||
// so we set it to a default that does not break the highlighting via ssh.
|
||||
env.entry("TERM".to_string())
|
||||
.or_insert_with(|| "xterm-256color".to_string());
|
||||
|
||||
let (program, args) = match spawn_command {
|
||||
Some((program, args)) => (Some(program.clone()), args),
|
||||
None => (None, &Vec::new()),
|
||||
};
|
||||
|
||||
let mut env_changes = String::new();
|
||||
for (k, v) in env.iter() {
|
||||
if let Some((k, v)) = shlex::try_quote(k).ok().zip(shlex::try_quote(v).ok()) {
|
||||
env_changes.push_str(&format!("{}={} ", k, v));
|
||||
}
|
||||
}
|
||||
if let Some(venv_directory) = venv_directory
|
||||
&& let Ok(str) = shlex::try_quote(venv_directory.to_string_lossy().as_ref())
|
||||
{
|
||||
let path = RemotePathBuf::new(PathBuf::from(str.to_string()), path_style).to_string();
|
||||
env_changes.push_str(&format!("PATH={}:$PATH ", path));
|
||||
}
|
||||
let command = remote_client.read(cx).build_command(
|
||||
program,
|
||||
args.as_slice(),
|
||||
env,
|
||||
working_directory.map(|path| path.display().to_string()),
|
||||
None,
|
||||
)?;
|
||||
*env = command.env;
|
||||
|
||||
let commands = if let Some(path) = path {
|
||||
let path = RemotePathBuf::new(path.to_path_buf(), path_style).to_string();
|
||||
// shlex will wrap the command in single quotes (''), disabling ~ expansion,
|
||||
// replace ith with something that works
|
||||
let tilde_prefix = "~/";
|
||||
if path.starts_with(tilde_prefix) {
|
||||
let trimmed_path = path
|
||||
.trim_start_matches("/")
|
||||
.trim_start_matches("~")
|
||||
.trim_start_matches("/");
|
||||
log::debug!("Connecting to a remote server: {:?}", command.program);
|
||||
let host = remote_client.read(cx).connection_options().host;
|
||||
|
||||
format!("cd \"$HOME/{trimmed_path}\"; {env_changes} {to_run}")
|
||||
} else {
|
||||
format!("cd \"{path}\"; {env_changes} {to_run}")
|
||||
}
|
||||
} else {
|
||||
format!("cd; {env_changes} {to_run}")
|
||||
};
|
||||
let shell_invocation = format!("{shell} -c {}", shlex::try_quote(&commands).unwrap());
|
||||
|
||||
let program = "ssh".to_string();
|
||||
let mut args = ssh_command.arguments.clone();
|
||||
|
||||
args.push("-t".to_string());
|
||||
args.push(shell_invocation);
|
||||
(program, args)
|
||||
Ok(Shell::WithArguments {
|
||||
program: command.program,
|
||||
args: command.args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_environment_path(env: &mut HashMap<String, String>, new_path: &Path) -> Result<()> {
|
||||
|
||||
@@ -18,7 +18,7 @@ use gpui::{
|
||||
use postage::oneshot;
|
||||
use rpc::{
|
||||
AnyProtoClient, ErrorExt, TypedEnvelope,
|
||||
proto::{self, FromProto, SSH_PROJECT_ID, ToProto},
|
||||
proto::{self, FromProto, REMOTE_SERVER_PROJECT_ID, ToProto},
|
||||
};
|
||||
use smol::{
|
||||
channel::{Receiver, Sender},
|
||||
@@ -278,7 +278,7 @@ impl WorktreeStore {
|
||||
let path = RemotePathBuf::new(abs_path.into(), path_style);
|
||||
let response = client
|
||||
.request(proto::AddWorktree {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
project_id: REMOTE_SERVER_PROJECT_ID,
|
||||
path: path.to_proto(),
|
||||
visible,
|
||||
})
|
||||
@@ -298,7 +298,7 @@ impl WorktreeStore {
|
||||
|
||||
let worktree = cx.update(|cx| {
|
||||
Worktree::remote(
|
||||
SSH_PROJECT_ID,
|
||||
REMOTE_SERVER_PROJECT_ID,
|
||||
0,
|
||||
proto::WorktreeMetadata {
|
||||
id: response.worktree_id,
|
||||
|
||||
Reference in New Issue
Block a user