open picker (#14524)

Release Notes:

- linux: Added a fallback Open picker for when XDG is not working
- Added a new setting `use_system_path_prompts` (default true) that can
be disabled to use Zed's builtin keyboard-driven prompts.

---------

Co-authored-by: Max <max@zed.dev>
This commit is contained in:
Conrad Irwin 2024-07-15 17:04:15 -06:00 committed by GitHub
parent da33aac156
commit abc5abcd8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 453 additions and 69 deletions

View file

@ -604,6 +604,10 @@ type PromptForNewPath = Box<
dyn Fn(&mut Workspace, &mut ViewContext<Workspace>) -> oneshot::Receiver<Option<ProjectPath>>,
>;
type PromptForOpenPath = Box<
dyn Fn(&mut Workspace, &mut ViewContext<Workspace>) -> oneshot::Receiver<Option<Vec<PathBuf>>>,
>;
/// Collects everything project-related for a certain window opened.
/// In some way, is a counterpart of a window, as the [`WindowHandle`] could be downcast into `Workspace`.
///
@ -646,6 +650,7 @@ pub struct Workspace {
centered_layout: bool,
bounds_save_task_queued: Option<Task<()>>,
on_prompt_for_new_path: Option<PromptForNewPath>,
on_prompt_for_open_path: Option<PromptForOpenPath>,
render_disconnected_overlay:
Option<Box<dyn Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement>>,
}
@ -931,6 +936,7 @@ impl Workspace {
centered_layout: false,
bounds_save_task_queued: None,
on_prompt_for_new_path: None,
on_prompt_for_open_path: None,
render_disconnected_overlay: None,
}
}
@ -1312,6 +1318,10 @@ impl Workspace {
self.on_prompt_for_new_path = Some(prompt)
}
pub fn set_prompt_for_open_path(&mut self, prompt: PromptForOpenPath) {
self.on_prompt_for_open_path = Some(prompt)
}
pub fn set_render_disconnected_overlay(
&mut self,
render: impl Fn(&mut Self, &mut ViewContext<Self>) -> AnyElement + 'static,
@ -1319,11 +1329,60 @@ impl Workspace {
self.render_disconnected_overlay = Some(Box::new(render))
}
pub fn prompt_for_open_path(
&mut self,
path_prompt_options: PathPromptOptions,
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
if self.project.read(cx).is_remote()
|| !WorkspaceSettings::get_global(cx).use_system_path_prompts
{
let prompt = self.on_prompt_for_open_path.take().unwrap();
let rx = prompt(self, cx);
self.on_prompt_for_open_path = Some(prompt);
rx
} else {
let (tx, rx) = oneshot::channel();
let abs_path = cx.prompt_for_paths(path_prompt_options);
cx.spawn(|this, mut cx| async move {
let Ok(result) = abs_path.await else {
return Ok(());
};
match result {
Ok(result) => {
tx.send(result).log_err();
}
Err(err) => {
let rx = this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
let prompt = this.on_prompt_for_open_path.take().unwrap();
let rx = prompt(this, cx);
this.on_prompt_for_open_path = Some(prompt);
rx
})?;
if let Ok(path) = rx.await {
tx.send(path).log_err();
}
}
};
anyhow::Ok(())
})
.detach();
rx
}
}
pub fn prompt_for_new_path(
&mut self,
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<Option<ProjectPath>> {
if let Some(prompt) = self.on_prompt_for_new_path.take() {
if self.project.read(cx).is_remote()
|| !WorkspaceSettings::get_global(cx).use_system_path_prompts
{
let prompt = self.on_prompt_for_new_path.take().unwrap();
let rx = prompt(self, cx);
self.on_prompt_for_new_path = Some(prompt);
rx
@ -1339,14 +1398,23 @@ impl Workspace {
let (tx, rx) = oneshot::channel();
let abs_path = cx.prompt_for_new_path(&start_abs_path);
cx.spawn(|this, mut cx| async move {
let abs_path: Option<PathBuf> =
Flatten::flatten(abs_path.await.map_err(|e| e.into())).map_err(|err| {
this.update(&mut cx, |this, cx| {
let abs_path = match abs_path.await? {
Ok(path) => path,
Err(err) => {
let rx = this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
err
})?;
let prompt = this.on_prompt_for_new_path.take().unwrap();
let rx = prompt(this, cx);
this.on_prompt_for_new_path = Some(prompt);
rx
})?;
if let Ok(path) = rx.await {
tx.send(path).log_err();
}
return anyhow::Ok(());
}
};
let project_path = abs_path.and_then(|abs_path| {
this.update(&mut cx, |this, cx| {
@ -1629,23 +1697,18 @@ impl Workspace {
self.client()
.telemetry()
.report_app_event("open project".to_string());
let paths = cx.prompt_for_paths(PathPromptOptions {
files: true,
directories: true,
multiple: true,
});
let paths = self.prompt_for_open_path(
PathPromptOptions {
files: true,
directories: true,
multiple: true,
},
cx,
);
cx.spawn(|this, mut cx| async move {
let paths = match Flatten::flatten(paths.await.map_err(|e| e.into())) {
Ok(Some(paths)) => paths,
Ok(None) => return,
Err(err) => {
this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
return;
}
let Some(paths) = paths.await.log_err().flatten() else {
return;
};
if let Some(task) = this
@ -1801,20 +1864,16 @@ impl Workspace {
);
return;
}
let paths = cx.prompt_for_paths(PathPromptOptions {
files: false,
directories: true,
multiple: true,
});
let paths = self.prompt_for_open_path(
PathPromptOptions {
files: false,
directories: true,
multiple: true,
},
cx,
);
cx.spawn(|this, mut cx| async move {
let paths = Flatten::flatten(paths.await.map_err(|e| e.into())).map_err(|err| {
this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
err
})?;
if let Some(paths) = paths {
if let Some(paths) = paths.await.log_err().flatten() {
let results = this
.update(&mut cx, |this, cx| {
this.open_paths(paths, OpenVisible::All, None, cx)