ssh: Fix abs paths in file history & repeated go-to-def (#19027)

This fixes two things:

- Go-to-def to absolute paths (i.e. opening stdlib files) multiple times
(opening, dropping, and re-opening worktrees)
- Re-opening abs paths from the file picker history that were added
there by go-to-def


Release Notes:

- N/A

---------

Co-authored-by: Bennet <bennet@zed.dev>
This commit is contained in:
Thorsten Ball 2024-10-11 11:21:34 +02:00 committed by GitHub
parent 4726f30bd6
commit 1691652948
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 145 additions and 74 deletions

View file

@ -758,11 +758,8 @@ impl FileFinderDelegate {
cx: &mut ViewContext<'_, Picker<Self>>, cx: &mut ViewContext<'_, Picker<Self>>,
) -> Task<()> { ) -> Task<()> {
cx.spawn(|picker, mut cx| async move { cx.spawn(|picker, mut cx| async move {
let Some((project, fs)) = picker let Some(project) = picker
.update(&mut cx, |picker, cx| { .update(&mut cx, |picker, _| picker.delegate.project.clone())
let fs = Arc::clone(&picker.delegate.project.read(cx).fs());
(picker.delegate.project.clone(), fs)
})
.log_err() .log_err()
else { else {
return; return;
@ -770,8 +767,16 @@ impl FileFinderDelegate {
let query_path = Path::new(query.path_query()); let query_path = Path::new(query.path_query());
let mut path_matches = Vec::new(); let mut path_matches = Vec::new();
match fs.metadata(query_path).await.log_err() {
Some(Some(_metadata)) => { let abs_file_exists = if let Ok(task) = project.update(&mut cx, |this, cx| {
this.abs_file_path_exists(query.path_query(), cx)
}) {
task.await
} else {
false
};
if abs_file_exists {
let update_result = project let update_result = project
.update(&mut cx, |project, cx| { .update(&mut cx, |project, cx| {
if let Some((worktree, relative_path)) = if let Some((worktree, relative_path)) =
@ -793,9 +798,6 @@ impl FileFinderDelegate {
return; return;
} }
} }
Some(None) => {}
None => return,
}
picker picker
.update(&mut cx, |picker, cx| { .update(&mut cx, |picker, cx| {
@ -888,7 +890,8 @@ impl PickerDelegate for FileFinderDelegate {
project project
.worktree_for_id(history_item.project.worktree_id, cx) .worktree_for_id(history_item.project.worktree_id, cx)
.is_some() .is_some()
|| (project.is_local() && history_item.absolute.is_some()) || ((project.is_local() || project.is_via_ssh())
&& history_item.absolute.is_some())
}), }),
self.currently_opened_path.as_ref(), self.currently_opened_path.as_ref(),
None, None,

View file

@ -2242,6 +2242,15 @@ impl Project {
return; return;
} }
if let Some(ssh) = &self.ssh_client {
ssh.read(cx)
.to_proto_client()
.send(proto::RemoveWorktree {
worktree_id: id_to_remove.to_proto(),
})
.log_err();
}
cx.notify(); cx.notify();
} }
@ -3070,7 +3079,7 @@ impl Project {
} }
} }
// Returns the resolved version of `path`, that was found in `buffer`, if it exists. /// Returns the resolved version of `path`, that was found in `buffer`, if it exists.
pub fn resolve_existing_file_path( pub fn resolve_existing_file_path(
&self, &self,
path: &str, path: &str,
@ -3079,6 +3088,25 @@ impl Project {
) -> Task<Option<ResolvedPath>> { ) -> Task<Option<ResolvedPath>> {
let path_buf = PathBuf::from(path); let path_buf = PathBuf::from(path);
if path_buf.is_absolute() || path.starts_with("~") { if path_buf.is_absolute() || path.starts_with("~") {
self.resolve_abs_file_path(path, cx)
} else {
self.resolve_path_in_worktrees(path_buf, buffer, cx)
}
}
pub fn abs_file_path_exists(&self, path: &str, cx: &mut ModelContext<Self>) -> Task<bool> {
let resolve_task = self.resolve_abs_file_path(path, cx);
cx.background_executor().spawn(async move {
let resolved_path = resolve_task.await;
resolved_path.is_some()
})
}
fn resolve_abs_file_path(
&self,
path: &str,
cx: &mut ModelContext<Self>,
) -> Task<Option<ResolvedPath>> {
if self.is_local() { if self.is_local() {
let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned()); let expanded = PathBuf::from(shellexpand::tilde(&path).into_owned());
@ -3090,8 +3118,7 @@ impl Project {
exists.then(|| ResolvedPath::AbsPath(expanded)) exists.then(|| ResolvedPath::AbsPath(expanded))
}) })
} else if let Some(ssh_client) = self.ssh_client.as_ref() { } else if let Some(ssh_client) = self.ssh_client.as_ref() {
let request = let request = ssh_client
ssh_client
.read(cx) .read(cx)
.to_proto_client() .to_proto_client()
.request(proto::CheckFileExists { .request(proto::CheckFileExists {
@ -3109,9 +3136,6 @@ impl Project {
} else { } else {
return Task::ready(None); return Task::ready(None);
} }
} else {
self.resolve_path_in_worktrees(path_buf, buffer, cx)
}
} }
fn resolve_path_in_worktrees( fn resolve_path_in_worktrees(

View file

@ -237,10 +237,13 @@ impl WorktreeStore {
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let this = this.upgrade().context("Dropped worktree store")?;
let response = client let response = client
.request(proto::AddWorktree { .request(proto::AddWorktree {
project_id: SSH_PROJECT_ID, project_id: SSH_PROJECT_ID,
path: abs_path.clone(), path: abs_path.clone(),
visible,
}) })
.await?; .await?;
@ -252,7 +255,7 @@ impl WorktreeStore {
let worktree = cx.update(|cx| { let worktree = cx.update(|cx| {
Worktree::remote( Worktree::remote(
0, SSH_PROJECT_ID,
0, 0,
proto::WorktreeMetadata { proto::WorktreeMetadata {
id: response.worktree_id, id: response.worktree_id,
@ -509,11 +512,6 @@ impl WorktreeStore {
for worktree in &self.worktrees { for worktree in &self.worktrees {
if let Some(worktree) = worktree.upgrade() { if let Some(worktree) = worktree.upgrade() {
worktree.update(cx, |worktree, _| { worktree.update(cx, |worktree, _| {
println!(
"worktree. is_local: {:?}, is_remote: {:?}",
worktree.is_local(),
worktree.is_remote()
);
if let Some(worktree) = worktree.as_remote_mut() { if let Some(worktree) = worktree.as_remote_mut() {
worktree.disconnected_from_host(); worktree.disconnected_from_host();
} }

View file

@ -282,7 +282,9 @@ message Envelope {
CheckFileExists check_file_exists = 255; CheckFileExists check_file_exists = 255;
CheckFileExistsResponse check_file_exists_response = 256; CheckFileExistsResponse check_file_exists_response = 256;
ShutdownRemoteServer shutdown_remote_server = 257; // current max ShutdownRemoteServer shutdown_remote_server = 257;
RemoveWorktree remove_worktree = 258; // current max
} }
reserved 87 to 88; reserved 87 to 88;
@ -2432,6 +2434,7 @@ message GetLlmTokenResponse {
message AddWorktree { message AddWorktree {
uint64 project_id = 2; uint64 project_id = 2;
string path = 1; string path = 1;
bool visible = 3;
} }
message AddWorktreeResponse { message AddWorktreeResponse {
@ -2460,3 +2463,7 @@ message CheckFileExistsResponse {
} }
message ShutdownRemoteServer {} message ShutdownRemoteServer {}
message RemoveWorktree {
uint64 worktree_id = 1;
}

View file

@ -364,6 +364,7 @@ messages!(
(CheckFileExists, Background), (CheckFileExists, Background),
(CheckFileExistsResponse, Background), (CheckFileExistsResponse, Background),
(ShutdownRemoteServer, Foreground), (ShutdownRemoteServer, Foreground),
(RemoveWorktree, Foreground),
); );
request_messages!( request_messages!(
@ -486,7 +487,8 @@ request_messages!(
(LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse), (LspExtSwitchSourceHeader, LspExtSwitchSourceHeaderResponse),
(AddWorktree, AddWorktreeResponse), (AddWorktree, AddWorktreeResponse),
(CheckFileExists, CheckFileExistsResponse), (CheckFileExists, CheckFileExistsResponse),
(ShutdownRemoteServer, Ack) (ShutdownRemoteServer, Ack),
(RemoveWorktree, Ack)
); );
entity_messages!( entity_messages!(

View file

@ -135,6 +135,8 @@ impl HeadlessProject {
client.add_request_handler(cx.weak_model(), Self::handle_ping); client.add_request_handler(cx.weak_model(), Self::handle_ping);
client.add_model_request_handler(Self::handle_add_worktree); client.add_model_request_handler(Self::handle_add_worktree);
client.add_request_handler(cx.weak_model(), Self::handle_remove_worktree);
client.add_model_request_handler(Self::handle_open_buffer_by_path); client.add_model_request_handler(Self::handle_open_buffer_by_path);
client.add_model_request_handler(Self::handle_find_search_candidates); client.add_model_request_handler(Self::handle_find_search_candidates);
@ -238,7 +240,7 @@ impl HeadlessProject {
.update(&mut cx.clone(), |this, _| { .update(&mut cx.clone(), |this, _| {
Worktree::local( Worktree::local(
Arc::from(canonicalized), Arc::from(canonicalized),
true, message.payload.visible,
this.fs.clone(), this.fs.clone(),
this.next_entry_id.clone(), this.next_entry_id.clone(),
&mut cx, &mut cx,
@ -246,14 +248,49 @@ impl HeadlessProject {
})? })?
.await?; .await?;
let response = this.update(&mut cx, |_, cx| {
worktree.update(cx, |worktree, _| proto::AddWorktreeResponse {
worktree_id: worktree.id().to_proto(),
})
})?;
// We spawn this asynchronously, so that we can send the response back
// *before* `worktree_store.add()` can send out UpdateProject requests
// to the client about the new worktree.
//
// That lets the client manage the reference/handles of the newly-added
// worktree, before getting interrupted by an UpdateProject request.
//
// This fixes the problem of the client sending the AddWorktree request,
// headless project sending out a project update, client receiving it
// and immediately dropping the reference of the new client, causing it
// to be dropped on the headless project, and the client only then
// receiving a response to AddWorktree.
cx.spawn(|mut cx| async move {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.worktree_store.update(cx, |worktree_store, cx| { this.worktree_store.update(cx, |worktree_store, cx| {
worktree_store.add(&worktree, cx); worktree_store.add(&worktree, cx);
}); });
worktree.update(cx, |worktree, _| proto::AddWorktreeResponse {
worktree_id: worktree.id().to_proto(),
}) })
.log_err();
}) })
.detach();
Ok(response)
}
pub async fn handle_remove_worktree(
this: Model<Self>,
envelope: TypedEnvelope<proto::RemoveWorktree>,
mut cx: AsyncAppContext,
) -> Result<proto::Ack> {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
this.update(&mut cx, |this, cx| {
this.worktree_store.update(cx, |worktree_store, cx| {
worktree_store.remove_worktree(worktree_id, cx);
});
})?;
Ok(proto::Ack {})
} }
pub async fn handle_open_buffer_by_path( pub async fn handle_open_buffer_by_path(