mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-28 09:54:33 +00:00
SSH Remoting: Add the ability to resolve file paths on the remote host (#18250)
Some checks are pending
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Some checks are pending
CI / Check formatting and spelling (push) Waiting to run
CI / (macOS) Run Clippy and tests (push) Waiting to run
CI / (Linux) Run Clippy and tests (push) Waiting to run
CI / (Windows) Run Clippy and tests (push) Waiting to run
CI / Create a macOS bundle (push) Blocked by required conditions
CI / Create a Linux bundle (push) Blocked by required conditions
CI / Create arm64 Linux bundle (push) Blocked by required conditions
Deploy Docs / Deploy Docs (push) Waiting to run
Docs / Check formatting (push) Waiting to run
Release Notes: - N/A
This commit is contained in:
parent
d989183f94
commit
20c06545b6
5 changed files with 119 additions and 33 deletions
|
@ -3037,15 +3037,11 @@ impl Project {
|
|||
buffer: &Model<Buffer>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Option<ResolvedPath>> {
|
||||
// TODO: ssh based remoting.
|
||||
if self.ssh_session.is_some() {
|
||||
return Task::ready(None);
|
||||
}
|
||||
let path_buf = PathBuf::from(path);
|
||||
if path_buf.is_absolute() || path.starts_with("~") {
|
||||
if self.is_local() {
|
||||
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
|
||||
|
||||
if self.is_local_or_ssh() {
|
||||
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
|
||||
|
||||
if expanded.is_absolute() {
|
||||
let fs = self.fs.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let path = expanded.as_path();
|
||||
|
@ -3053,16 +3049,24 @@ impl Project {
|
|||
|
||||
exists.then(|| ResolvedPath::AbsPath(expanded))
|
||||
})
|
||||
} else if let Some(ssh_session) = self.ssh_session.as_ref() {
|
||||
let request = ssh_session.request(proto::CheckFileExists {
|
||||
project_id: SSH_PROJECT_ID,
|
||||
path: path.to_string(),
|
||||
});
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = request.await.log_err()?;
|
||||
if response.exists {
|
||||
Some(ResolvedPath::AbsPath(PathBuf::from(response.path)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.resolve_path_in_worktrees(expanded, buffer, cx)
|
||||
}
|
||||
} else {
|
||||
let path = PathBuf::from(path);
|
||||
if path.is_absolute() || path.starts_with("~") {
|
||||
return Task::ready(None);
|
||||
}
|
||||
|
||||
self.resolve_path_in_worktrees(path, buffer, cx)
|
||||
} else {
|
||||
self.resolve_path_in_worktrees(path_buf, buffer, cx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4016,17 +4020,7 @@ impl Project {
|
|||
}
|
||||
|
||||
pub fn worktree_metadata_protos(&self, cx: &AppContext) -> Vec<proto::WorktreeMetadata> {
|
||||
self.worktrees(cx)
|
||||
.map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
proto::WorktreeMetadata {
|
||||
id: worktree.id().to_proto(),
|
||||
root_name: worktree.root_name().into(),
|
||||
visible: worktree.is_visible(),
|
||||
abs_path: worktree.abs_path().to_string_lossy().into(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
self.worktree_store.read(cx).worktree_metadata_protos(cx)
|
||||
}
|
||||
|
||||
fn set_worktrees_from_proto(
|
||||
|
@ -4035,10 +4029,9 @@ impl Project {
|
|||
cx: &mut ModelContext<Project>,
|
||||
) -> Result<()> {
|
||||
cx.notify();
|
||||
let result = self.worktree_store.update(cx, |worktree_store, cx| {
|
||||
self.worktree_store.update(cx, |worktree_store, cx| {
|
||||
worktree_store.set_worktrees_from_proto(worktrees, self.replica_id(), cx)
|
||||
});
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn set_collaborators_from_proto(
|
||||
|
@ -4547,6 +4540,22 @@ pub enum ResolvedPath {
|
|||
AbsPath(PathBuf),
|
||||
}
|
||||
|
||||
impl ResolvedPath {
|
||||
pub fn abs_path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::AbsPath(path) => Some(path.as_path()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project_path(&self) -> Option<&ProjectPath> {
|
||||
match self {
|
||||
Self::ProjectPath(path) => Some(&path),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for Buffer {
|
||||
fn try_open(
|
||||
project: &Model<Project>,
|
||||
|
|
|
@ -293,7 +293,10 @@ message Envelope {
|
|||
|
||||
TryExec try_exec = 252;
|
||||
ReadTextFile read_text_file = 253;
|
||||
ReadTextFileResponse read_text_file_response = 254; // current max
|
||||
ReadTextFileResponse read_text_file_response = 254;
|
||||
|
||||
CheckFileExists check_file_exists = 255;
|
||||
CheckFileExistsResponse check_file_exists_response = 256; // current max
|
||||
}
|
||||
|
||||
reserved 158 to 161;
|
||||
|
@ -2574,3 +2577,13 @@ message TryExec {
|
|||
message TryExecResponse {
|
||||
string text = 1;
|
||||
}
|
||||
|
||||
message CheckFileExists {
|
||||
uint64 project_id = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
||||
message CheckFileExistsResponse {
|
||||
bool exists = 1;
|
||||
string path = 2;
|
||||
}
|
||||
|
|
|
@ -372,7 +372,9 @@ messages!(
|
|||
(ShellEnvResponse, Foreground),
|
||||
(TryExec, Foreground),
|
||||
(ReadTextFile, Foreground),
|
||||
(ReadTextFileResponse, Foreground)
|
||||
(ReadTextFileResponse, Foreground),
|
||||
(CheckFileExists, Background),
|
||||
(CheckFileExistsResponse, Background)
|
||||
);
|
||||
|
||||
request_messages!(
|
||||
|
@ -501,6 +503,7 @@ request_messages!(
|
|||
(ShellEnv, ShellEnvResponse),
|
||||
(ReadTextFile, ReadTextFileResponse),
|
||||
(TryExec, Ack),
|
||||
(CheckFileExists, CheckFileExistsResponse)
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
@ -578,7 +581,8 @@ entity_messages!(
|
|||
WhichCommand,
|
||||
ShellEnv,
|
||||
TryExec,
|
||||
ReadTextFile
|
||||
ReadTextFile,
|
||||
CheckFileExists,
|
||||
);
|
||||
|
||||
entity_messages!(
|
||||
|
|
|
@ -108,6 +108,7 @@ impl HeadlessProject {
|
|||
session.subscribe_to_entity(SSH_PROJECT_ID, &settings_observer);
|
||||
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_list_remote_directory);
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_check_file_exists);
|
||||
|
||||
client.add_model_request_handler(Self::handle_add_worktree);
|
||||
client.add_model_request_handler(Self::handle_open_buffer_by_path);
|
||||
|
@ -298,4 +299,20 @@ impl HeadlessProject {
|
|||
}
|
||||
Ok(proto::ListRemoteDirectoryResponse { entries })
|
||||
}
|
||||
|
||||
pub async fn handle_check_file_exists(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::CheckFileExists>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::CheckFileExistsResponse> {
|
||||
let fs = cx.read_model(&this, |this, _| this.fs.clone())?;
|
||||
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
|
||||
|
||||
let exists = fs.is_file(&PathBuf::from(expanded.clone())).await;
|
||||
|
||||
Ok(proto::CheckFileExistsResponse {
|
||||
exists,
|
||||
path: expanded,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind};
|
|||
use node_runtime::NodeRuntime;
|
||||
use project::{
|
||||
search::{SearchQuery, SearchResult},
|
||||
Project,
|
||||
Project, ProjectPath,
|
||||
};
|
||||
use remote::SshSession;
|
||||
use serde_json::json;
|
||||
|
@ -440,6 +440,49 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
|
|||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_remote_resolve_file_path(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
|
||||
let (project, _headless, _fs) = init_test(cx, server_cx).await;
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree("/code/project1", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let worktree_id = cx.update(|cx| worktree.read(cx).id());
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let path = project
|
||||
.update(cx, |project, cx| {
|
||||
project.resolve_existing_file_path("/code/project1/README.md", &buffer, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
path.abs_path().unwrap().to_string_lossy(),
|
||||
"/code/project1/README.md"
|
||||
);
|
||||
|
||||
let path = project
|
||||
.update(cx, |project, cx| {
|
||||
project.resolve_existing_file_path("../README.md", &buffer, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
path.project_path().unwrap().clone(),
|
||||
ProjectPath::from((worktree_id, "README.md"))
|
||||
);
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::try_init().ok();
|
||||
|
|
Loading…
Reference in a new issue