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
134
crates/remote_server/src/remote_editing_tests.rs
Normal file
134
crates/remote_server/src/remote_editing_tests.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use crate::headless_project::HeadlessProject;
|
||||
use client::{Client, UserStore};
|
||||
use clock::FakeSystemClock;
|
||||
use fs::{FakeFs, Fs as _};
|
||||
use gpui::{Context, Model, TestAppContext};
|
||||
use http::FakeHttpClient;
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
use project::Project;
|
||||
use remote::SshSession;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{path::Path, sync::Arc};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
let (client_ssh, server_ssh) = SshSession::fake(cx, server_cx);
|
||||
|
||||
let fs = FakeFs::new(server_cx.executor());
|
||||
fs.insert_tree(
|
||||
"/code",
|
||||
json!({
|
||||
"project1": {
|
||||
"README.md": "# project 1",
|
||||
"src": {
|
||||
"lib.rs": "fn one() -> usize { 1 }"
|
||||
}
|
||||
},
|
||||
"project2": {
|
||||
"README.md": "# project 2",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
server_cx.update(HeadlessProject::init);
|
||||
let _headless_project =
|
||||
server_cx.new_model(|cx| HeadlessProject::new(server_ssh, fs.clone(), cx));
|
||||
|
||||
let project = build_project(client_ssh, cx);
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project1", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// The client sees the worktree's contents.
|
||||
cx.executor().run_until_parked();
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||
worktree.update(cx, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
// The user opens a buffer in the remote worktree. The buffer's
|
||||
// contents are loaded from the remote filesystem.
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
|
||||
let ix = buffer.text().find('1').unwrap();
|
||||
buffer.edit([(ix..ix + 1, "100")], None, cx);
|
||||
});
|
||||
|
||||
// The user saves the buffer. The new contents are written to the
|
||||
// remote filesystem.
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
|
||||
"fn one() -> usize { 100 }"
|
||||
);
|
||||
|
||||
// A new file is created in the remote filesystem. The user
|
||||
// sees the new file.
|
||||
fs.save(
|
||||
"/code/project1/src/main.rs".as_ref(),
|
||||
&"fn main() {}".into(),
|
||||
Default::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.executor().run_until_parked();
|
||||
worktree.update(cx, |worktree, _cx| {
|
||||
assert_eq!(
|
||||
worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
|
||||
vec![
|
||||
Path::new("README.md"),
|
||||
Path::new("src"),
|
||||
Path::new("src/lib.rs"),
|
||||
Path::new("src/main.rs"),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn build_project(ssh: Arc<SshSession>, cx: &mut TestAppContext) -> Model<Project> {
|
||||
cx.update(|cx| {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
});
|
||||
|
||||
let client = cx.update(|cx| {
|
||||
Client::new(
|
||||
Arc::new(FakeSystemClock::default()),
|
||||
FakeHttpClient::with_404_response(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let node = FakeNodeRuntime::new();
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
let languages = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
cx.update(|cx| {
|
||||
Project::init(&client, cx);
|
||||
language::init(cx);
|
||||
});
|
||||
|
||||
cx.update(|cx| Project::ssh(ssh, client, node, user_store, languages, fs, cx))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue