Improve Zed prompts for file path selection (#32014)
Part of https://github.com/zed-industries/zed/discussions/31653 `"use_system_path_prompts": false` is needed in settings for these to appear as modals for new file save and file open. Fixed a very subpar experience of the "save new file" Zed modal, compared to a similar "open file path" Zed modal by uniting their code. Before: https://github.com/user-attachments/assets/c4082b70-6cdc-4598-a416-d491011c8ac4 After: https://github.com/user-attachments/assets/21ca672a-ae40-426c-b68f-9efee4f93c8c Also * alters both prompts to start in the current worktree directory, with the fallback to home directory. * adjusts the code to handle Windows paths better Release Notes: - Improved Zed prompts for file path selection --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
parent
8c46a4f594
commit
4aabba6cf6
10 changed files with 721 additions and 792 deletions
|
@ -25,7 +25,7 @@ use gpui::{
|
|||
use itertools::Itertools;
|
||||
use language::DiagnosticSeverity;
|
||||
use parking_lot::Mutex;
|
||||
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use project::{DirectoryLister, Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use settings::{Settings, SettingsStore};
|
||||
|
@ -1921,24 +1921,56 @@ impl Pane {
|
|||
})?
|
||||
.await?;
|
||||
} else if can_save_as && is_singleton {
|
||||
let abs_path = pane.update_in(cx, |pane, window, cx| {
|
||||
let new_path = pane.update_in(cx, |pane, window, cx| {
|
||||
pane.activate_item(item_ix, true, true, window, cx);
|
||||
pane.workspace.update(cx, |workspace, cx| {
|
||||
workspace.prompt_for_new_path(window, cx)
|
||||
let lister = if workspace.project().read(cx).is_local() {
|
||||
DirectoryLister::Local(
|
||||
workspace.project().clone(),
|
||||
workspace.app_state().fs.clone(),
|
||||
)
|
||||
} else {
|
||||
DirectoryLister::Project(workspace.project().clone())
|
||||
};
|
||||
workspace.prompt_for_new_path(lister, window, cx)
|
||||
})
|
||||
})??;
|
||||
if let Some(abs_path) = abs_path.await.ok().flatten() {
|
||||
let Some(new_path) = new_path.await.ok().flatten().into_iter().flatten().next()
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
let project_path = pane
|
||||
.update(cx, |pane, cx| {
|
||||
pane.project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(new_path, true, cx)
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
let save_task = if let Some(project_path) = project_path {
|
||||
let (worktree, path) = project_path.await?;
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?;
|
||||
let new_path = ProjectPath {
|
||||
worktree_id,
|
||||
path: path.into(),
|
||||
};
|
||||
|
||||
pane.update_in(cx, |pane, window, cx| {
|
||||
if let Some(item) = pane.item_for_path(abs_path.clone(), cx) {
|
||||
if let Some(item) = pane.item_for_path(new_path.clone(), cx) {
|
||||
pane.remove_item(item.item_id(), false, false, window, cx);
|
||||
}
|
||||
|
||||
item.save_as(project, abs_path, window, cx)
|
||||
item.save_as(project, new_path, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
save_task.await?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -899,9 +899,10 @@ pub enum OpenVisible {
|
|||
type PromptForNewPath = Box<
|
||||
dyn Fn(
|
||||
&mut Workspace,
|
||||
DirectoryLister,
|
||||
&mut Window,
|
||||
&mut Context<Workspace>,
|
||||
) -> oneshot::Receiver<Option<ProjectPath>>,
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
|
||||
>;
|
||||
|
||||
type PromptForOpenPath = Box<
|
||||
|
@ -1874,25 +1875,25 @@ impl Workspace {
|
|||
let (tx, rx) = oneshot::channel();
|
||||
let abs_path = cx.prompt_for_paths(path_prompt_options);
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let Ok(result) = abs_path.await else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(result) => {
|
||||
tx.send(result).log_err();
|
||||
tx.send(result).ok();
|
||||
}
|
||||
Err(err) => {
|
||||
let rx = this.update_in(cx, |this, window, cx| {
|
||||
this.show_portal_error(err.to_string(), cx);
|
||||
let prompt = this.on_prompt_for_open_path.take().unwrap();
|
||||
let rx = prompt(this, lister, window, cx);
|
||||
this.on_prompt_for_open_path = Some(prompt);
|
||||
let rx = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
let prompt = workspace.on_prompt_for_open_path.take().unwrap();
|
||||
let rx = prompt(workspace, lister, window, cx);
|
||||
workspace.on_prompt_for_open_path = Some(prompt);
|
||||
rx
|
||||
})?;
|
||||
if let Ok(path) = rx.await {
|
||||
tx.send(path).log_err();
|
||||
tx.send(path).ok();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1906,77 +1907,58 @@ impl Workspace {
|
|||
|
||||
pub fn prompt_for_new_path(
|
||||
&mut self,
|
||||
lister: DirectoryLister,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> oneshot::Receiver<Option<ProjectPath>> {
|
||||
if (self.project.read(cx).is_via_collab() || self.project.read(cx).is_via_ssh())
|
||||
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
|
||||
if self.project.read(cx).is_via_collab()
|
||||
|| self.project.read(cx).is_via_ssh()
|
||||
|| !WorkspaceSettings::get_global(cx).use_system_path_prompts
|
||||
{
|
||||
let prompt = self.on_prompt_for_new_path.take().unwrap();
|
||||
let rx = prompt(self, window, cx);
|
||||
let rx = prompt(self, lister, window, cx);
|
||||
self.on_prompt_for_new_path = Some(prompt);
|
||||
return rx;
|
||||
}
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let abs_path = this.update(cx, |this, cx| {
|
||||
let mut relative_to = this
|
||||
cx.spawn_in(window, async move |workspace, cx| {
|
||||
let abs_path = workspace.update(cx, |workspace, cx| {
|
||||
let relative_to = workspace
|
||||
.most_recent_active_path(cx)
|
||||
.and_then(|p| p.parent().map(|p| p.to_path_buf()));
|
||||
if relative_to.is_none() {
|
||||
let project = this.project.read(cx);
|
||||
relative_to = project
|
||||
.visible_worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
|
||||
.or_else(|| {
|
||||
let project = workspace.project.read(cx);
|
||||
project.visible_worktrees(cx).find_map(|worktree| {
|
||||
Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
|
||||
})
|
||||
.next()
|
||||
};
|
||||
|
||||
cx.prompt_for_new_path(&relative_to.unwrap_or_else(|| PathBuf::from("")))
|
||||
})
|
||||
.or_else(std::env::home_dir)
|
||||
.unwrap_or_else(|| PathBuf::from(""));
|
||||
cx.prompt_for_new_path(&relative_to)
|
||||
})?;
|
||||
let abs_path = match abs_path.await? {
|
||||
Ok(path) => path,
|
||||
Err(err) => {
|
||||
let rx = this.update_in(cx, |this, window, cx| {
|
||||
this.show_portal_error(err.to_string(), cx);
|
||||
let rx = workspace.update_in(cx, |workspace, window, cx| {
|
||||
workspace.show_portal_error(err.to_string(), cx);
|
||||
|
||||
let prompt = this.on_prompt_for_new_path.take().unwrap();
|
||||
let rx = prompt(this, window, cx);
|
||||
this.on_prompt_for_new_path = Some(prompt);
|
||||
let prompt = workspace.on_prompt_for_new_path.take().unwrap();
|
||||
let rx = prompt(workspace, lister, window, cx);
|
||||
workspace.on_prompt_for_new_path = Some(prompt);
|
||||
rx
|
||||
})?;
|
||||
if let Ok(path) = rx.await {
|
||||
tx.send(path).log_err();
|
||||
tx.send(path).ok();
|
||||
}
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let project_path = abs_path.and_then(|abs_path| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.project.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(abs_path, true, cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
if let Some(project_path) = project_path {
|
||||
let (worktree, path) = project_path.await?;
|
||||
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id())?;
|
||||
tx.send(Some(ProjectPath {
|
||||
worktree_id,
|
||||
path: path.into(),
|
||||
}))
|
||||
.ok();
|
||||
} else {
|
||||
tx.send(None).ok();
|
||||
}
|
||||
tx.send(abs_path.map(|path| vec![path])).ok();
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach();
|
||||
|
||||
rx
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue