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:
Max Brunsfeld
2025-08-27 00:15:39 +00:00
committed by GitHub
co-authored by Mikayla Maki
parent d713390366
commit 1eae76e856
39 changed files with 3330 additions and 3411 deletions
+26 -26
View File
@@ -21,7 +21,7 @@ use project::{
};
use rpc::{
AnyProtoClient, TypedEnvelope,
proto::{self, SSH_PEER_ID, SSH_PROJECT_ID},
proto::{self, REMOTE_SERVER_PEER_ID, REMOTE_SERVER_PROJECT_ID},
};
use settings::initial_server_settings_content;
@@ -83,7 +83,7 @@ impl HeadlessProject {
let worktree_store = cx.new(|cx| {
let mut store = WorktreeStore::local(true, fs.clone());
store.shared(SSH_PROJECT_ID, session.clone(), cx);
store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
store
});
@@ -101,7 +101,7 @@ impl HeadlessProject {
let buffer_store = cx.new(|cx| {
let mut buffer_store = BufferStore::local(worktree_store.clone(), cx);
buffer_store.shared(SSH_PROJECT_ID, session.clone(), cx);
buffer_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
buffer_store
});
@@ -119,7 +119,7 @@ impl HeadlessProject {
breakpoint_store.clone(),
cx,
);
dap_store.shared(SSH_PROJECT_ID, session.clone(), cx);
dap_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
dap_store
});
@@ -131,7 +131,7 @@ impl HeadlessProject {
fs.clone(),
cx,
);
store.shared(SSH_PROJECT_ID, session.clone(), cx);
store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
store
});
@@ -154,7 +154,7 @@ impl HeadlessProject {
environment.clone(),
cx,
);
task_store.shared(SSH_PROJECT_ID, session.clone(), cx);
task_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
task_store
});
let settings_observer = cx.new(|cx| {
@@ -164,7 +164,7 @@ impl HeadlessProject {
task_store.clone(),
cx,
);
observer.shared(SSH_PROJECT_ID, session.clone(), cx);
observer.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
observer
});
@@ -185,7 +185,7 @@ impl HeadlessProject {
fs.clone(),
cx,
);
lsp_store.shared(SSH_PROJECT_ID, session.clone(), cx);
lsp_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
lsp_store
});
@@ -213,15 +213,15 @@ impl HeadlessProject {
);
// local_machine -> ssh handlers
session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &buffer_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &cx.entity());
session.subscribe_to_entity(SSH_PROJECT_ID, &lsp_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &task_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &toolchain_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &dap_store);
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
session.subscribe_to_entity(SSH_PROJECT_ID, &git_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &worktree_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &buffer_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &cx.entity());
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &lsp_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &task_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &toolchain_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &dap_store);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &settings_observer);
session.subscribe_to_entity(REMOTE_SERVER_PROJECT_ID, &git_store);
session.add_request_handler(cx.weak_entity(), Self::handle_list_remote_directory);
session.add_request_handler(cx.weak_entity(), Self::handle_get_path_metadata);
@@ -288,7 +288,7 @@ impl HeadlessProject {
} = event
{
cx.background_spawn(self.session.request(proto::UpdateBuffer {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
buffer_id: buffer.read(cx).remote_id().to_proto(),
operations: vec![serialize_operation(operation)],
}))
@@ -310,7 +310,7 @@ impl HeadlessProject {
} => {
self.session
.send(proto::UpdateLanguageServer {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
server_name: name.as_ref().map(|name| name.to_string()),
language_server_id: language_server_id.to_proto(),
variant: Some(message.clone()),
@@ -320,7 +320,7 @@ impl HeadlessProject {
LspStoreEvent::Notification(message) => {
self.session
.send(proto::Toast {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
notification_id: "lsp".to_string(),
message: message.clone(),
})
@@ -329,7 +329,7 @@ impl HeadlessProject {
LspStoreEvent::LanguageServerLog(language_server_id, log_type, message) => {
self.session
.send(proto::LanguageServerLog {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
language_server_id: language_server_id.to_proto(),
message: message.clone(),
log_type: Some(log_type.to_proto()),
@@ -338,7 +338,7 @@ impl HeadlessProject {
}
LspStoreEvent::LanguageServerPrompt(prompt) => {
let request = self.session.request(proto::LanguageServerPromptRequest {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
actions: prompt
.actions
.iter()
@@ -474,7 +474,7 @@ impl HeadlessProject {
let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
buffer_store.update(&mut cx, |buffer_store, cx| {
buffer_store
.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
.create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
.detach_and_log_err(cx);
})?;
@@ -500,7 +500,7 @@ impl HeadlessProject {
let buffer_id = buffer.read_with(&cx, |b, _| b.remote_id())?;
buffer_store.update(&mut cx, |buffer_store, cx| {
buffer_store
.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
.create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
.detach_and_log_err(cx);
})?;
@@ -550,7 +550,7 @@ impl HeadlessProject {
buffer_store.update(cx, |buffer_store, cx| {
buffer_store
.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
.create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
.detach_and_log_err(cx);
});
@@ -586,7 +586,7 @@ impl HeadlessProject {
response.buffer_ids.push(buffer_id.to_proto());
buffer_store
.update(&mut cx, |buffer_store, cx| {
buffer_store.create_buffer_for_peer(&buffer, SSH_PEER_ID, cx)
buffer_store.create_buffer_for_peer(&buffer, REMOTE_SERVER_PEER_ID, cx)
})?
.await?;
}
@@ -22,7 +22,7 @@ use project::{
Project, ProjectPath,
search::{SearchQuery, SearchResult},
};
use remote::SshRemoteClient;
use remote::RemoteClient;
use serde_json::json;
use settings::{Settings, SettingsLocation, SettingsStore, initial_server_settings_content};
use smol::stream::StreamExt;
@@ -1119,7 +1119,7 @@ async fn test_reconnect(cx: &mut TestAppContext, server_cx: &mut TestAppContext)
buffer.edit([(ix..ix + 1, "100")], None, cx);
});
let client = cx.read(|cx| project.read(cx).ssh_client().unwrap());
let client = cx.read(|cx| project.read(cx).remote_client().unwrap());
client
.update(cx, |client, cx| client.simulate_disconnect(cx))
.detach();
@@ -1782,7 +1782,7 @@ pub async fn init_test(
});
init_logger();
let (opts, ssh_server_client) = SshRemoteClient::fake_server(cx, server_cx);
let (opts, ssh_server_client) = RemoteClient::fake_server(cx, server_cx);
let http_client = Arc::new(BlockedHttpClient);
let node_runtime = NodeRuntime::unavailable();
let languages = Arc::new(LanguageRegistry::new(cx.executor()));
@@ -1804,7 +1804,7 @@ pub async fn init_test(
)
});
let ssh = SshRemoteClient::fake_client(opts, cx).await;
let ssh = RemoteClient::fake_client(opts, cx).await;
let project = build_project(ssh, cx);
project
.update(cx, {
@@ -1819,7 +1819,7 @@ fn init_logger() {
zlog::init_test();
}
fn build_project(ssh: Entity<SshRemoteClient>, cx: &mut TestAppContext) -> Entity<Project> {
fn build_project(ssh: Entity<RemoteClient>, cx: &mut TestAppContext) -> Entity<Project> {
cx.update(|cx| {
if !cx.has_global::<SettingsStore>() {
let settings_store = SettingsStore::test(cx);
@@ -1845,5 +1845,5 @@ fn build_project(ssh: Entity<SshRemoteClient>, cx: &mut TestAppContext) -> Entit
language::init(cx);
});
cx.update(|cx| Project::ssh(ssh, client, node, user_store, languages, fs, cx))
cx.update(|cx| Project::remote(ssh, client, node, user_store, languages, fs, cx))
}
+6 -19
View File
@@ -19,14 +19,14 @@ use project::project_settings::ProjectSettings;
use proto::CrashReport;
use release_channel::{AppVersion, RELEASE_CHANNEL, ReleaseChannel};
use remote::SshRemoteClient;
use remote::RemoteClient;
use remote::{
json_log::LogRecord,
protocol::{read_message, write_message},
proxy::ProxyLaunchError,
};
use reqwest_client::ReqwestClient;
use rpc::proto::{self, Envelope, SSH_PROJECT_ID};
use rpc::proto::{self, Envelope, REMOTE_SERVER_PROJECT_ID};
use rpc::{AnyProtoClient, TypedEnvelope};
use settings::{Settings, SettingsStore, watch_config_file};
use smol::channel::{Receiver, Sender};
@@ -396,7 +396,7 @@ fn start_server(
})
.detach();
SshRemoteClient::proto_client_from_channels(incoming_rx, outgoing_tx, cx, "server")
RemoteClient::proto_client_from_channels(incoming_rx, outgoing_tx, cx, "server")
}
fn init_paths() -> anyhow::Result<()> {
@@ -867,34 +867,21 @@ where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
use remote::protocol::read_message_raw;
use remote::protocol::{read_message_raw, write_size_prefixed_buffer};
let mut buffer = Vec::new();
loop {
read_message_raw(&mut reader, &mut buffer)
.await
.with_context(|| format!("failed to read message from {}", socket_name))?;
write_size_prefixed_buffer(&mut writer, &mut buffer)
.await
.with_context(|| format!("failed to write message to {}", socket_name))?;
writer.flush().await?;
buffer.clear();
}
}
async fn write_size_prefixed_buffer<S: AsyncWrite + Unpin>(
stream: &mut S,
buffer: &mut Vec<u8>,
) -> Result<()> {
let len = buffer.len() as u32;
stream.write_all(len.to_le_bytes().as_slice()).await?;
stream.write_all(buffer).await?;
Ok(())
}
fn initialize_settings(
session: AnyProtoClient,
fs: Arc<dyn Fs>,
@@ -910,7 +897,7 @@ fn initialize_settings(
session
.send(proto::Toast {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
notification_id: "server-settings-failed".to_string(),
message: format!(
"Error in settings on remote host {:?}: {}",
@@ -922,7 +909,7 @@ fn initialize_settings(
} else {
session
.send(proto::HideToast {
project_id: SSH_PROJECT_ID,
project_id: REMOTE_SERVER_PROJECT_ID,
notification_id: "server-settings-failed".to_string(),
})
.log_err();