Add the ability to edit remote directories over SSH (#14530)
This is a first step towards allowing you to edit remote projects directly over SSH. We'll start with a pretty bare-bones feature set, and incrementally add further features. ### Todo Distribution * [x] Build nightly releases of `zed-remote-server` binaries * [x] linux (arm + x86) * [x] mac (arm + x86) * [x] Build stable + preview releases of `zed-remote-server` * [x] download and cache remote server binaries as needed when opening ssh project * [x] ensure server has the latest version of the binary Auth * [x] allow specifying password at the command line * [x] auth via ssh keys * [x] UI password prompt Features * [x] upload remote server binary to server automatically * [x] opening directories * [x] tracking file system updates * [x] opening, editing, saving buffers * [ ] file operations (rename, delete, create) * [ ] git diffs * [ ] project search Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
parent
7733bf686b
commit
b9a53ffa0b
50 changed files with 2194 additions and 250 deletions
|
@ -74,15 +74,18 @@ use postage::watch;
|
|||
use prettier_support::{DefaultPrettier, PrettierInstance};
|
||||
use project_settings::{DirenvSettings, LspSettings, ProjectSettings};
|
||||
use rand::prelude::*;
|
||||
use rpc::ErrorCode;
|
||||
use remote::SshSession;
|
||||
use rpc::{proto::AddWorktree, ErrorCode};
|
||||
use search::SearchQuery;
|
||||
use search_history::SearchHistory;
|
||||
use serde::Serialize;
|
||||
use settings::{watch_config_file, Settings, SettingsLocation, SettingsStore};
|
||||
use sha2::{Digest, Sha256};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use smol::lock::Semaphore;
|
||||
use smol::{
|
||||
channel::{Receiver, Sender},
|
||||
lock::Semaphore,
|
||||
};
|
||||
use snippet::Snippet;
|
||||
use snippet_provider::SnippetProvider;
|
||||
use std::{
|
||||
|
@ -196,6 +199,7 @@ pub struct Project {
|
|||
>,
|
||||
user_store: Model<UserStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
ssh_session: Option<Arc<SshSession>>,
|
||||
client_state: ProjectClientState,
|
||||
collaborators: HashMap<proto::PeerId, Collaborator>,
|
||||
client_subscriptions: Vec<client::Subscription>,
|
||||
|
@ -793,6 +797,7 @@ impl Project {
|
|||
client,
|
||||
user_store,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
diagnostics: Default::default(),
|
||||
|
@ -825,6 +830,24 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn ssh(
|
||||
ssh_session: Arc<SshSession>,
|
||||
client: Arc<Client>,
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
user_store: Model<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Self> {
|
||||
let this = Self::local(client, node, user_store, languages, fs, cx);
|
||||
this.update(cx, |this, cx| {
|
||||
ssh_session.add_message_handler(cx.weak_model(), Self::handle_update_worktree);
|
||||
ssh_session.add_message_handler(cx.weak_model(), Self::handle_create_buffer_for_peer);
|
||||
this.ssh_session = Some(ssh_session);
|
||||
});
|
||||
this
|
||||
}
|
||||
|
||||
pub async fn remote(
|
||||
remote_id: u64,
|
||||
client: Arc<Client>,
|
||||
|
@ -924,6 +947,7 @@ impl Project {
|
|||
snippets,
|
||||
yarn,
|
||||
fs,
|
||||
ssh_session: None,
|
||||
next_entry_id: Default::default(),
|
||||
next_diagnostic_group_id: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
|
@ -1628,7 +1652,7 @@ impl Project {
|
|||
this.client.send(
|
||||
proto::UpdateDiagnosticSummary {
|
||||
project_id,
|
||||
worktree_id: cx.entity_id().as_u64(),
|
||||
worktree_id: worktree.id().to_proto(),
|
||||
summary: Some(
|
||||
summary.to_proto(server_id, path),
|
||||
),
|
||||
|
@ -2442,7 +2466,7 @@ impl Project {
|
|||
.send(proto::UpdateBufferFile {
|
||||
project_id,
|
||||
buffer_id: buffer_id.into(),
|
||||
file: Some(new_file.to_proto()),
|
||||
file: Some(new_file.to_proto(cx)),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -2464,11 +2488,23 @@ impl Project {
|
|||
self.request_buffer_diff_recalculation(&buffer, cx);
|
||||
}
|
||||
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
match event {
|
||||
BufferEvent::Operation(operation) => {
|
||||
let operation = language::proto::serialize_operation(operation);
|
||||
|
||||
if let Some(ssh) = &self.ssh_session {
|
||||
ssh.send(proto::UpdateBuffer {
|
||||
project_id: 0,
|
||||
buffer_id: buffer_id.to_proto(),
|
||||
operations: vec![operation.clone()],
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
self.enqueue_buffer_ordered_message(BufferOrderedMessage::Operation {
|
||||
buffer_id: buffer.read(cx).remote_id(),
|
||||
operation: language::proto::serialize_operation(operation),
|
||||
buffer_id,
|
||||
operation,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
@ -2948,9 +2984,10 @@ impl Project {
|
|||
language: Arc<Language>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx));
|
||||
let (root_file, is_local) =
|
||||
worktree.update(cx, |tree, cx| (tree.root_file(cx), tree.is_local()));
|
||||
let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx);
|
||||
if !settings.enable_language_server {
|
||||
if !settings.enable_language_server || !is_local {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -7632,7 +7669,9 @@ impl Project {
|
|||
) -> Task<Result<Model<Worktree>>> {
|
||||
let path: Arc<Path> = abs_path.as_ref().into();
|
||||
if !self.loading_worktrees.contains_key(&path) {
|
||||
let task = if self.is_local() {
|
||||
let task = if self.ssh_session.is_some() {
|
||||
self.create_ssh_worktree(abs_path, visible, cx)
|
||||
} else if self.is_local() {
|
||||
self.create_local_worktree(abs_path, visible, cx)
|
||||
} else if self.dev_server_project_id.is_some() {
|
||||
self.create_dev_server_worktree(abs_path, cx)
|
||||
|
@ -7651,6 +7690,39 @@ impl Project {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_ssh_worktree(
|
||||
&mut self,
|
||||
abs_path: impl AsRef<Path>,
|
||||
visible: bool,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
|
||||
let ssh = self.ssh_session.clone().unwrap();
|
||||
let abs_path = abs_path.as_ref();
|
||||
let root_name = abs_path.file_name().unwrap().to_string_lossy().to_string();
|
||||
let path = abs_path.to_string_lossy().to_string();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = ssh.request(AddWorktree { path: path.clone() }).await?;
|
||||
let worktree = cx.update(|cx| {
|
||||
Worktree::remote(
|
||||
0,
|
||||
0,
|
||||
proto::WorktreeMetadata {
|
||||
id: response.worktree_id,
|
||||
root_name,
|
||||
visible,
|
||||
abs_path: path,
|
||||
},
|
||||
ssh.clone().into(),
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
this.update(&mut cx, |this, cx| this.add_worktree(&worktree, cx))?;
|
||||
|
||||
Ok(worktree)
|
||||
})
|
||||
}
|
||||
|
||||
fn create_local_worktree(
|
||||
&mut self,
|
||||
abs_path: impl AsRef<Path>,
|
||||
|
@ -7922,7 +7994,7 @@ impl Project {
|
|||
.send(proto::UpdateBufferFile {
|
||||
project_id,
|
||||
buffer_id: buffer.read(cx).remote_id().into(),
|
||||
file: Some(new_file.to_proto()),
|
||||
file: Some(new_file.to_proto(cx)),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -9073,6 +9145,13 @@ impl Project {
|
|||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(ssh) = &this.ssh_session {
|
||||
let mut payload = envelope.payload.clone();
|
||||
payload.project_id = 0;
|
||||
cx.background_executor()
|
||||
.spawn(ssh.request(payload))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
this.buffer_store.update(cx, |buffer_store, cx| {
|
||||
buffer_store.handle_update_buffer(envelope, this.is_remote(), cx)
|
||||
})
|
||||
|
@ -9231,7 +9310,7 @@ impl Project {
|
|||
.send(proto::UpdateBufferFile {
|
||||
project_id,
|
||||
buffer_id: buffer_id.into(),
|
||||
file: Some(file.to_proto()),
|
||||
file: Some(file.to_proto(cx)),
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue