Merge branch 'main' into storybook

This commit is contained in:
Nathan Sobo 2023-09-11 09:13:46 -06:00
commit 15ea4af5e7
22 changed files with 734 additions and 96 deletions

View file

@ -43,6 +43,19 @@ use std::{
};
use theme::{Theme, ThemeSettings};
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum SaveBehavior {
/// ask before overwriting conflicting files (used by default with %s)
PromptOnConflict,
/// ask before writing any file that wouldn't be auto-saved (used by default with %w)
PromptOnWrite,
/// never prompt, write on conflict (used with vim's :w!)
SilentlyOverwrite,
/// skip all save-related behaviour (used with vim's :cq)
DontSave,
}
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivateItem(pub usize);
@ -64,13 +77,17 @@ pub struct CloseItemsToTheRightById {
pub pane: WeakViewHandle<Pane>,
}
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
pub struct CloseActiveItem {
pub save_behavior: Option<SaveBehavior>,
}
actions!(
pane,
[
ActivatePrevItem,
ActivateNextItem,
ActivateLastItem,
CloseActiveItem,
CloseInactiveItems,
CloseCleanItems,
CloseItemsToTheLeft,
@ -86,7 +103,7 @@ actions!(
]
);
impl_actions!(pane, [ActivateItem]);
impl_actions!(pane, [ActivateItem, CloseActiveItem]);
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
@ -696,22 +713,29 @@ impl Pane {
pub fn close_active_item(
&mut self,
_: &CloseActiveItem,
action: &CloseActiveItem,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
if self.items.is_empty() {
return None;
}
let active_item_id = self.items[self.active_item_index].id();
Some(self.close_item_by_id(active_item_id, cx))
Some(self.close_item_by_id(
active_item_id,
action.save_behavior.unwrap_or(SaveBehavior::PromptOnWrite),
cx,
))
}
pub fn close_item_by_id(
&mut self,
item_id_to_close: usize,
save_behavior: SaveBehavior,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.close_items(cx, move |view_id| view_id == item_id_to_close)
self.close_items(cx, save_behavior, move |view_id| {
view_id == item_id_to_close
})
}
pub fn close_inactive_items(
@ -724,7 +748,11 @@ impl Pane {
}
let active_item_id = self.items[self.active_item_index].id();
Some(self.close_items(cx, move |item_id| item_id != active_item_id))
Some(
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
item_id != active_item_id
}),
)
}
pub fn close_clean_items(
@ -737,7 +765,11 @@ impl Pane {
.filter(|item| !item.is_dirty(cx))
.map(|item| item.id())
.collect();
Some(self.close_items(cx, move |item_id| item_ids.contains(&item_id)))
Some(
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
item_ids.contains(&item_id)
}),
)
}
pub fn close_items_to_the_left(
@ -762,7 +794,9 @@ impl Pane {
.take_while(|item| item.id() != item_id)
.map(|item| item.id())
.collect();
self.close_items(cx, move |item_id| item_ids.contains(&item_id))
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
item_ids.contains(&item_id)
})
}
pub fn close_items_to_the_right(
@ -788,7 +822,9 @@ impl Pane {
.take_while(|item| item.id() != item_id)
.map(|item| item.id())
.collect();
self.close_items(cx, move |item_id| item_ids.contains(&item_id))
self.close_items(cx, SaveBehavior::PromptOnWrite, move |item_id| {
item_ids.contains(&item_id)
})
}
pub fn close_all_items(
@ -800,12 +836,13 @@ impl Pane {
return None;
}
Some(self.close_items(cx, move |_| true))
Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true))
}
pub fn close_items(
&mut self,
cx: &mut ViewContext<Pane>,
save_behavior: SaveBehavior,
should_close: impl 'static + Fn(usize) -> bool,
) -> Task<Result<()>> {
// Find the items to close.
@ -858,8 +895,15 @@ impl Pane {
.any(|id| saved_project_items_ids.insert(*id));
if should_save
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
.await?
&& !Self::save_item(
project.clone(),
&pane,
item_ix,
&*item,
save_behavior,
&mut cx,
)
.await?
{
break;
}
@ -954,13 +998,17 @@ impl Pane {
pane: &WeakViewHandle<Pane>,
item_ix: usize,
item: &dyn ItemHandle,
should_prompt_for_save: bool,
save_behavior: SaveBehavior,
cx: &mut AsyncAppContext,
) -> Result<bool> {
const CONFLICT_MESSAGE: &str =
"This file has changed on disk since you started editing it. Do you want to overwrite it?";
const DIRTY_MESSAGE: &str = "This file contains unsaved edits. Do you want to save it?";
if save_behavior == SaveBehavior::DontSave {
return Ok(true);
}
let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| {
(
item.has_conflict(cx),
@ -971,18 +1019,22 @@ impl Pane {
});
if has_conflict && can_save {
let mut answer = pane.update(cx, |pane, cx| {
pane.activate_item(item_ix, true, true, cx);
cx.prompt(
PromptLevel::Warning,
CONFLICT_MESSAGE,
&["Overwrite", "Discard", "Cancel"],
)
})?;
match answer.next().await {
Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
_ => return Ok(false),
if save_behavior == SaveBehavior::SilentlyOverwrite {
pane.update(cx, |_, cx| item.save(project, cx))?.await?;
} else {
let mut answer = pane.update(cx, |pane, cx| {
pane.activate_item(item_ix, true, true, cx);
cx.prompt(
PromptLevel::Warning,
CONFLICT_MESSAGE,
&["Overwrite", "Discard", "Cancel"],
)
})?;
match answer.next().await {
Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
_ => return Ok(false),
}
}
} else if is_dirty && (can_save || is_singleton) {
let will_autosave = cx.read(|cx| {
@ -991,7 +1043,7 @@ impl Pane {
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
) && Self::can_autosave_item(&*item, cx)
});
let should_save = if should_prompt_for_save && !will_autosave {
let should_save = if save_behavior == SaveBehavior::PromptOnWrite && !will_autosave {
let mut answer = pane.update(cx, |pane, cx| {
pane.activate_item(item_ix, true, true, cx);
cx.prompt(
@ -1113,7 +1165,12 @@ impl Pane {
AnchorCorner::TopLeft,
if is_active_item {
vec![
ContextMenuItem::action("Close Active Item", CloseActiveItem),
ContextMenuItem::action(
"Close Active Item",
CloseActiveItem {
save_behavior: None,
},
),
ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
@ -1128,8 +1185,12 @@ impl Pane {
move |cx| {
if let Some(pane) = pane.upgrade(cx) {
pane.update(cx, |pane, cx| {
pane.close_item_by_id(target_item_id, cx)
.detach_and_log_err(cx);
pane.close_item_by_id(
target_item_id,
SaveBehavior::PromptOnWrite,
cx,
)
.detach_and_log_err(cx);
})
}
}
@ -1278,7 +1339,12 @@ impl Pane {
.on_click(MouseButton::Middle, {
let item_id = item.id();
move |_, pane, cx| {
pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
pane.close_item_by_id(
item_id,
SaveBehavior::PromptOnWrite,
cx,
)
.detach_and_log_err(cx);
}
})
.on_down(
@ -1486,7 +1552,8 @@ impl Pane {
cx.window_context().defer(move |cx| {
if let Some(pane) = pane.upgrade(cx) {
pane.update(cx, |pane, cx| {
pane.close_item_by_id(item_id, cx).detach_and_log_err(cx);
pane.close_item_by_id(item_id, SaveBehavior::PromptOnWrite, cx)
.detach_and_log_err(cx);
});
}
});
@ -2087,7 +2154,14 @@ mod tests {
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
pane.update(cx, |pane, cx| {
assert!(pane.close_active_item(&CloseActiveItem, cx).is_none())
assert!(pane
.close_active_item(
&CloseActiveItem {
save_behavior: None
},
cx
)
.is_none())
});
}
@ -2337,31 +2411,59 @@ mod tests {
add_labeled_item(&pane, "1", false, cx);
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
.unwrap()
.await
.unwrap();
pane.update(cx, |pane, cx| {
pane.close_active_item(
&CloseActiveItem {
save_behavior: None,
},
cx,
)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
.unwrap()
.await
.unwrap();
pane.update(cx, |pane, cx| {
pane.close_active_item(
&CloseActiveItem {
save_behavior: None,
},
cx,
)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A", "B*", "C"], cx);
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
.unwrap()
.await
.unwrap();
pane.update(cx, |pane, cx| {
pane.close_active_item(
&CloseActiveItem {
save_behavior: None,
},
cx,
)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A", "C*"], cx);
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
.unwrap()
.await
.unwrap();
pane.update(cx, |pane, cx| {
pane.close_active_item(
&CloseActiveItem {
save_behavior: None,
},
cx,
)
})
.unwrap()
.await
.unwrap();
assert_item_labels(&pane, ["A*"], cx);
}