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:
parent
4726f30bd6
commit
1691652948
6 changed files with 145 additions and 74 deletions
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue