Merge Workspace::save_item into Pane::save_item

These methods were slightly different which caused (for example) there
to be no "Discard" option in the conflict case at the workspace level.

To make this work, a new SaveBehavior (::PromptForNewPath) was added to
support SaveAs.
This commit is contained in:
Conrad Irwin 2023-09-19 23:48:21 -06:00
parent a4f96e6452
commit 88a32ae48d
2 changed files with 58 additions and 64 deletions

View file

@ -46,13 +46,15 @@ use theme::{Theme, ThemeSettings};
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum SaveBehavior { pub enum SaveBehavior {
/// ask before overwriting conflicting files (used by default with %s) /// ask before overwriting conflicting files (used by default with cmd-s)
PromptOnConflict, PromptOnConflict,
/// ask before writing any file that wouldn't be auto-saved (used by default with %w) /// ask for a new path before writing (used with cmd-shift-s)
PromptForNewPath,
/// ask before writing any file that wouldn't be auto-saved (used by default with cmd-w)
PromptOnWrite, PromptOnWrite,
/// never prompt, write on conflict (used with vim's :w!) /// never prompt, write on conflict (used with vim's :w!)
SilentlyOverwrite, SilentlyOverwrite,
/// skip all save-related behaviour (used with vim's :cq) /// skip all save-related behaviour (used with vim's :q!)
DontSave, DontSave,
} }
@ -1019,7 +1021,7 @@ impl Pane {
return Ok(true); return Ok(true);
} }
let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| { let (mut has_conflict, mut is_dirty, mut can_save, is_singleton) = cx.read(|cx| {
( (
item.has_conflict(cx), item.has_conflict(cx),
item.is_dirty(cx), item.is_dirty(cx),
@ -1028,6 +1030,12 @@ impl Pane {
) )
}); });
if save_behavior == SaveBehavior::PromptForNewPath {
has_conflict = false;
is_dirty = true;
can_save = false;
}
if has_conflict && can_save { if has_conflict && can_save {
if save_behavior == SaveBehavior::SilentlyOverwrite { if save_behavior == SaveBehavior::SilentlyOverwrite {
pane.update(cx, |_, cx| item.save(project, cx))?.await?; pane.update(cx, |_, cx| item.save(project, cx))?.await?;
@ -2589,10 +2597,17 @@ mod tests {
add_labeled_item(&pane, "C", false, cx); add_labeled_item(&pane, "C", false, cx);
assert_item_labels(&pane, ["A", "B", "C*"], cx); assert_item_labels(&pane, ["A", "B", "C*"], cx);
pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) pane.update(cx, |pane, cx| {
.unwrap() pane.close_all_items(
.await &CloseAllItems {
.unwrap(); save_behavior: None,
},
cx,
)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, [], cx); assert_item_labels(&pane, [], cx);
} }

View file

@ -122,6 +122,7 @@ actions!(
Open, Open,
NewFile, NewFile,
NewWindow, NewWindow,
CloseWindow,
CloseInactiveTabsAndPanes, CloseInactiveTabsAndPanes,
AddFolderToProject, AddFolderToProject,
Unfollow, Unfollow,
@ -168,12 +169,6 @@ pub struct Save {
pub save_behavior: Option<SaveBehavior>, pub save_behavior: Option<SaveBehavior>,
} }
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct CloseWindow {
pub save_behavior: Option<SaveBehavior>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default)] #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CloseAllItemsAndPanes { pub struct CloseAllItemsAndPanes {
@ -236,10 +231,9 @@ impl_actions!(
ActivatePane, ActivatePane,
ActivatePaneInDirection, ActivatePaneInDirection,
Toast, Toast,
OpenTerminal, OpenTerminal,
SaveAll, SaveAll,
Save, Save,
CloseWindow,
CloseAllItemsAndPanes, CloseAllItemsAndPanes,
] ]
); );
@ -294,13 +288,22 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
}, },
); );
cx.add_action( cx.add_action(
|workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
workspace.save_active_item(false, cx).detach_and_log_err(cx); workspace
.save_active_item(
action
.save_behavior
.unwrap_or(SaveBehavior::PromptOnConflict),
cx,
)
.detach_and_log_err(cx);
}, },
); );
cx.add_action( cx.add_action(
|workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
workspace.save_active_item(true, cx).detach_and_log_err(cx); workspace
.save_active_item(SaveBehavior::PromptForNewPath, cx)
.detach_and_log_err(cx);
}, },
); );
cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| { cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
@ -1294,15 +1297,11 @@ impl Workspace {
pub fn close( pub fn close(
&mut self, &mut self,
action: &CloseWindow, _: &CloseWindow,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
let window = cx.window(); let window = cx.window();
let prepare = self.prepare_to_close( let prepare = self.prepare_to_close(false, cx);
false,
action.save_behavior.unwrap_or(SaveBehavior::PromptOnWrite),
cx,
);
Some(cx.spawn(|_, mut cx| async move { Some(cx.spawn(|_, mut cx| async move {
if prepare.await? { if prepare.await? {
window.remove(&mut cx); window.remove(&mut cx);
@ -1685,51 +1684,31 @@ impl Workspace {
pub fn save_active_item( pub fn save_active_item(
&mut self, &mut self,
force_name_change: bool, save_behavior: SaveBehavior,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Task<Result<()>> { ) -> Task<Result<()>> {
let project = self.project.clone(); let project = self.project.clone();
if let Some(item) = self.active_item(cx) { let pane = self.active_pane();
if !force_name_change && item.can_save(cx) { let item_ix = pane.read(cx).active_item_index();
if item.has_conflict(cx) { let item = pane.read(cx).active_item();
const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?"; let pane = pane.downgrade();
let mut answer = cx.prompt( cx.spawn(|_, mut cx| async move {
PromptLevel::Warning, if let Some(item) = item {
CONFLICT_MESSAGE, Pane::save_item(
&["Overwrite", "Cancel"], project,
); &pane,
cx.spawn(|this, mut cx| async move { item_ix,
let answer = answer.recv().await; item.as_ref(),
if answer == Some(0) { save_behavior,
this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))? &mut cx,
.await?; )
} .await
Ok(()) .map(|_| ())
})
} else {
item.save(self.project.clone(), cx)
}
} else if item.is_singleton(cx) {
let worktree = self.worktrees(cx).next();
let start_abs_path = worktree
.and_then(|w| w.read(cx).as_local())
.map_or(Path::new(""), |w| w.abs_path())
.to_path_buf();
let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
cx.spawn(|this, mut cx| async move {
if let Some(abs_path) = abs_path.recv().await.flatten() {
this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
.await?;
}
Ok(())
})
} else { } else {
Task::ready(Ok(())) Ok(())
} }
} else { })
Task::ready(Ok(()))
}
} }
pub fn close_inactive_items_and_panes( pub fn close_inactive_items_and_panes(