new path picker (#11015)

Still TODO:

* Disable the new save-as for local projects
* Wire up sending the new path to the remote server

Release Notes:

- Added the ability to "Save-as" in remote projects

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Bennet <bennetbo@gmx.de>
This commit is contained in:
Conrad Irwin 2024-04-26 13:25:25 -06:00 committed by GitHub
parent 314b723292
commit 664f779eb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 775 additions and 149 deletions

View file

@ -26,7 +26,6 @@ use std::{
any::{Any, TypeId},
cell::RefCell,
ops::Range,
path::PathBuf,
rc::Rc,
sync::Arc,
time::Duration,
@ -196,7 +195,7 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
fn save_as(
&mut self,
_project: Model<Project>,
_abs_path: PathBuf,
_path: ProjectPath,
_cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
unimplemented!("save_as() must be implemented if can_save() returns true")
@ -309,7 +308,7 @@ pub trait ItemHandle: 'static + Send {
fn save_as(
&self,
project: Model<Project>,
abs_path: PathBuf,
path: ProjectPath,
cx: &mut WindowContext,
) -> Task<Result<()>>;
fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
@ -647,10 +646,10 @@ impl<T: Item> ItemHandle for View<T> {
fn save_as(
&self,
project: Model<Project>,
abs_path: PathBuf,
path: ProjectPath,
cx: &mut WindowContext,
) -> Task<anyhow::Result<()>> {
self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
self.update(cx, |item, cx| item.save_as(project, path, cx))
}
fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
@ -1126,7 +1125,7 @@ pub mod test {
fn save_as(
&mut self,
_: Model<Project>,
_: std::path::PathBuf,
_: ProjectPath,
_: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
self.save_as_count += 1;

View file

@ -263,7 +263,7 @@ impl Render for LanguageServerPrompt {
PromptLevel::Warning => {
Some(DiagnosticSeverity::WARNING)
}
PromptLevel::Critical => {
PromptLevel::Critical | PromptLevel::Destructive => {
Some(DiagnosticSeverity::ERROR)
}
}

View file

@ -26,7 +26,7 @@ use std::{
any::Any,
cmp, fmt, mem,
ops::ControlFlow,
path::{Path, PathBuf},
path::PathBuf,
rc::Rc,
sync::{
atomic::{AtomicUsize, Ordering},
@ -1322,14 +1322,10 @@ impl Pane {
pane.update(cx, |_, cx| item.save(should_format, project, cx))?
.await?;
} else if can_save_as {
let start_abs_path = project
.update(cx, |project, cx| {
let worktree = project.visible_worktrees(cx).next()?;
Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
})?
.unwrap_or_else(|| Path::new("").into());
let abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path))?;
let abs_path = pane.update(cx, |pane, cx| {
pane.workspace
.update(cx, |workspace, cx| workspace.prompt_for_new_path(cx))
})??;
if let Some(abs_path) = abs_path.await.ok().flatten() {
pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
.await?;

View file

@ -544,6 +544,10 @@ pub enum OpenVisible {
OnlyDirectories,
}
type PromptForNewPath = Box<
dyn Fn(&mut Workspace, &mut ViewContext<Workspace>) -> oneshot::Receiver<Option<ProjectPath>>,
>;
/// 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`.
///
@ -585,6 +589,7 @@ pub struct Workspace {
bounds: Bounds<Pixels>,
centered_layout: bool,
bounds_save_task_queued: Option<Task<()>>,
on_prompt_for_new_path: Option<PromptForNewPath>,
}
impl EventEmitter<Event> for Workspace {}
@ -875,6 +880,7 @@ impl Workspace {
bounds: Default::default(),
centered_layout: false,
bounds_save_task_queued: None,
on_prompt_for_new_path: None,
}
}
@ -1223,6 +1229,59 @@ impl Workspace {
cx.notify();
}
pub fn set_prompt_for_new_path(&mut self, prompt: PromptForNewPath) {
self.on_prompt_for_new_path = Some(prompt)
}
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() {
let rx = prompt(self, cx);
self.on_prompt_for_new_path = Some(prompt);
rx
} else {
let start_abs_path = self
.project
.update(cx, |project, cx| {
let worktree = project.visible_worktrees(cx).next()?;
Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
})
.unwrap_or_else(|| Path::new("").into());
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 = abs_path.await?;
let project_path = abs_path.and_then(|abs_path| {
this.update(&mut cx, |this, cx| {
this.project.update(cx, |project, cx| {
project.find_or_create_local_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();
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
rx
}
}
pub fn titlebar_item(&self) -> Option<AnyView> {
self.titlebar_item.clone()
}