vim: Add ZZ and ZQ
The major change here is a refactoring to allow controling the save behaviour when closing items, which is pre-work needed for vim command palette. For zed-industries/community#1868
This commit is contained in:
parent
5d782b6cf0
commit
ba1c350dad
9 changed files with 258 additions and 89 deletions
|
@ -198,6 +198,18 @@
|
||||||
"z c": "editor::Fold",
|
"z c": "editor::Fold",
|
||||||
"z o": "editor::UnfoldLines",
|
"z o": "editor::UnfoldLines",
|
||||||
"z f": "editor::FoldSelectedRanges",
|
"z f": "editor::FoldSelectedRanges",
|
||||||
|
"shift-z shift-q": [
|
||||||
|
"pane::CloseActiveItem",
|
||||||
|
{
|
||||||
|
"saveBehavior": "dontSave"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shift-z shift-z": [
|
||||||
|
"pane::CloseActiveItem",
|
||||||
|
{
|
||||||
|
"saveBehavior": "promptOnConflict"
|
||||||
|
}
|
||||||
|
],
|
||||||
// Count support
|
// Count support
|
||||||
"1": [
|
"1": [
|
||||||
"vim::Number",
|
"vim::Number",
|
||||||
|
|
|
@ -1530,7 +1530,9 @@ async fn test_host_disconnect(
|
||||||
|
|
||||||
// Ensure client B is not prompted to save edits when closing window after disconnecting.
|
// Ensure client B is not prompted to save edits when closing window after disconnecting.
|
||||||
let can_close = workspace_b
|
let can_close = workspace_b
|
||||||
.update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
|
.update(cx_b, |workspace, cx| {
|
||||||
|
workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx)
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(can_close);
|
assert!(can_close);
|
||||||
|
|
|
@ -1528,8 +1528,13 @@ mod tests {
|
||||||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||||
active_pane
|
active_pane
|
||||||
.update(cx, |pane, cx| {
|
.update(cx, |pane, cx| {
|
||||||
pane.close_active_item(&workspace::CloseActiveItem, cx)
|
pane.close_active_item(
|
||||||
.unwrap()
|
&workspace::CloseActiveItem {
|
||||||
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -283,7 +283,12 @@ impl TerminalView {
|
||||||
pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext<Self>) {
|
pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext<Self>) {
|
||||||
let menu_entries = vec![
|
let menu_entries = vec![
|
||||||
ContextMenuItem::action("Clear", Clear),
|
ContextMenuItem::action("Clear", Clear),
|
||||||
ContextMenuItem::action("Close", pane::CloseActiveItem),
|
ContextMenuItem::action(
|
||||||
|
"Close",
|
||||||
|
pane::CloseActiveItem {
|
||||||
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
self.context_menu.update(cx, |menu, cx| {
|
self.context_menu.update(cx, |menu, cx| {
|
||||||
|
|
|
@ -474,8 +474,14 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
|
||||||
for item_event in T::to_item_events(event).into_iter() {
|
for item_event in T::to_item_events(event).into_iter() {
|
||||||
match item_event {
|
match item_event {
|
||||||
ItemEvent::CloseItem => {
|
ItemEvent::CloseItem => {
|
||||||
pane.update(cx, |pane, cx| pane.close_item_by_id(item.id(), cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.detach_and_log_err(cx);
|
pane.close_item_by_id(
|
||||||
|
item.id(),
|
||||||
|
crate::SaveBehavior::PromptOnWrite,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,19 @@ use std::{
|
||||||
};
|
};
|
||||||
use theme::{Theme, ThemeSettings};
|
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)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
pub struct ActivateItem(pub usize);
|
pub struct ActivateItem(pub usize);
|
||||||
|
|
||||||
|
@ -64,13 +77,17 @@ pub struct CloseItemsToTheRightById {
|
||||||
pub pane: WeakViewHandle<Pane>,
|
pub pane: WeakViewHandle<Pane>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
|
||||||
|
pub struct CloseActiveItem {
|
||||||
|
pub save_behavior: Option<SaveBehavior>,
|
||||||
|
}
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
pane,
|
pane,
|
||||||
[
|
[
|
||||||
ActivatePrevItem,
|
ActivatePrevItem,
|
||||||
ActivateNextItem,
|
ActivateNextItem,
|
||||||
ActivateLastItem,
|
ActivateLastItem,
|
||||||
CloseActiveItem,
|
|
||||||
CloseInactiveItems,
|
CloseInactiveItems,
|
||||||
CloseCleanItems,
|
CloseCleanItems,
|
||||||
CloseItemsToTheLeft,
|
CloseItemsToTheLeft,
|
||||||
|
@ -86,7 +103,7 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
impl_actions!(pane, [ActivateItem]);
|
impl_actions!(pane, [ActivateItem, CloseActiveItem]);
|
||||||
|
|
||||||
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
|
||||||
|
|
||||||
|
@ -696,22 +713,29 @@ impl Pane {
|
||||||
|
|
||||||
pub fn close_active_item(
|
pub fn close_active_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &CloseActiveItem,
|
action: &CloseActiveItem,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Option<Task<Result<()>>> {
|
) -> Option<Task<Result<()>>> {
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let active_item_id = self.items[self.active_item_index].id();
|
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(
|
pub fn close_item_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
item_id_to_close: usize,
|
item_id_to_close: usize,
|
||||||
|
save_behavior: SaveBehavior,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> 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(
|
pub fn close_inactive_items(
|
||||||
|
@ -724,7 +748,11 @@ impl Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
let active_item_id = self.items[self.active_item_index].id();
|
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(
|
pub fn close_clean_items(
|
||||||
|
@ -737,7 +765,11 @@ impl Pane {
|
||||||
.filter(|item| !item.is_dirty(cx))
|
.filter(|item| !item.is_dirty(cx))
|
||||||
.map(|item| item.id())
|
.map(|item| item.id())
|
||||||
.collect();
|
.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(
|
pub fn close_items_to_the_left(
|
||||||
|
@ -762,7 +794,9 @@ impl Pane {
|
||||||
.take_while(|item| item.id() != item_id)
|
.take_while(|item| item.id() != item_id)
|
||||||
.map(|item| item.id())
|
.map(|item| item.id())
|
||||||
.collect();
|
.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(
|
pub fn close_items_to_the_right(
|
||||||
|
@ -788,7 +822,9 @@ impl Pane {
|
||||||
.take_while(|item| item.id() != item_id)
|
.take_while(|item| item.id() != item_id)
|
||||||
.map(|item| item.id())
|
.map(|item| item.id())
|
||||||
.collect();
|
.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(
|
pub fn close_all_items(
|
||||||
|
@ -800,12 +836,13 @@ impl Pane {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(self.close_items(cx, move |_| true))
|
Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_items(
|
pub fn close_items(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ViewContext<Pane>,
|
cx: &mut ViewContext<Pane>,
|
||||||
|
save_behavior: SaveBehavior,
|
||||||
should_close: impl 'static + Fn(usize) -> bool,
|
should_close: impl 'static + Fn(usize) -> bool,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
// Find the items to close.
|
// Find the items to close.
|
||||||
|
@ -858,8 +895,15 @@ impl Pane {
|
||||||
.any(|id| saved_project_items_ids.insert(*id));
|
.any(|id| saved_project_items_ids.insert(*id));
|
||||||
|
|
||||||
if should_save
|
if should_save
|
||||||
&& !Self::save_item(project.clone(), &pane, item_ix, &*item, true, &mut cx)
|
&& !Self::save_item(
|
||||||
.await?
|
project.clone(),
|
||||||
|
&pane,
|
||||||
|
item_ix,
|
||||||
|
&*item,
|
||||||
|
save_behavior,
|
||||||
|
&mut cx,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -954,13 +998,17 @@ impl Pane {
|
||||||
pane: &WeakViewHandle<Pane>,
|
pane: &WeakViewHandle<Pane>,
|
||||||
item_ix: usize,
|
item_ix: usize,
|
||||||
item: &dyn ItemHandle,
|
item: &dyn ItemHandle,
|
||||||
should_prompt_for_save: bool,
|
save_behavior: SaveBehavior,
|
||||||
cx: &mut AsyncAppContext,
|
cx: &mut AsyncAppContext,
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
const CONFLICT_MESSAGE: &str =
|
const CONFLICT_MESSAGE: &str =
|
||||||
"This file has changed on disk since you started editing it. Do you want to overwrite it?";
|
"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?";
|
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| {
|
let (has_conflict, is_dirty, can_save, is_singleton) = cx.read(|cx| {
|
||||||
(
|
(
|
||||||
item.has_conflict(cx),
|
item.has_conflict(cx),
|
||||||
|
@ -971,18 +1019,22 @@ impl Pane {
|
||||||
});
|
});
|
||||||
|
|
||||||
if has_conflict && can_save {
|
if has_conflict && can_save {
|
||||||
let mut answer = pane.update(cx, |pane, cx| {
|
if save_behavior == SaveBehavior::SilentlyOverwrite {
|
||||||
pane.activate_item(item_ix, true, true, cx);
|
pane.update(cx, |_, cx| item.save(project, cx))?.await?;
|
||||||
cx.prompt(
|
} else {
|
||||||
PromptLevel::Warning,
|
let mut answer = pane.update(cx, |pane, cx| {
|
||||||
CONFLICT_MESSAGE,
|
pane.activate_item(item_ix, true, true, cx);
|
||||||
&["Overwrite", "Discard", "Cancel"],
|
cx.prompt(
|
||||||
)
|
PromptLevel::Warning,
|
||||||
})?;
|
CONFLICT_MESSAGE,
|
||||||
match answer.next().await {
|
&["Overwrite", "Discard", "Cancel"],
|
||||||
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),
|
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) {
|
} else if is_dirty && (can_save || is_singleton) {
|
||||||
let will_autosave = cx.read(|cx| {
|
let will_autosave = cx.read(|cx| {
|
||||||
|
@ -991,7 +1043,7 @@ impl Pane {
|
||||||
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||||
) && Self::can_autosave_item(&*item, cx)
|
) && 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| {
|
let mut answer = pane.update(cx, |pane, cx| {
|
||||||
pane.activate_item(item_ix, true, true, cx);
|
pane.activate_item(item_ix, true, true, cx);
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
|
@ -1113,7 +1165,12 @@ impl Pane {
|
||||||
AnchorCorner::TopLeft,
|
AnchorCorner::TopLeft,
|
||||||
if is_active_item {
|
if is_active_item {
|
||||||
vec![
|
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 Inactive Items", CloseInactiveItems),
|
||||||
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
|
ContextMenuItem::action("Close Clean Items", CloseCleanItems),
|
||||||
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
|
ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
|
||||||
|
@ -1128,8 +1185,12 @@ impl Pane {
|
||||||
move |cx| {
|
move |cx| {
|
||||||
if let Some(pane) = pane.upgrade(cx) {
|
if let Some(pane) = pane.upgrade(cx) {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.close_item_by_id(target_item_id, cx)
|
pane.close_item_by_id(
|
||||||
.detach_and_log_err(cx);
|
target_item_id,
|
||||||
|
SaveBehavior::PromptOnWrite,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1278,7 +1339,12 @@ impl Pane {
|
||||||
.on_click(MouseButton::Middle, {
|
.on_click(MouseButton::Middle, {
|
||||||
let item_id = item.id();
|
let item_id = item.id();
|
||||||
move |_, pane, cx| {
|
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(
|
.on_down(
|
||||||
|
@ -1486,7 +1552,8 @@ impl Pane {
|
||||||
cx.window_context().defer(move |cx| {
|
cx.window_context().defer(move |cx| {
|
||||||
if let Some(pane) = pane.upgrade(cx) {
|
if let Some(pane) = pane.upgrade(cx) {
|
||||||
pane.update(cx, |pane, 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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2089,7 +2156,14 @@ mod tests {
|
||||||
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| {
|
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())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2339,31 +2413,59 @@ mod tests {
|
||||||
add_labeled_item(&pane, "1", false, cx);
|
add_labeled_item(&pane, "1", false, cx);
|
||||||
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
|
assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.unwrap()
|
pane.close_active_item(
|
||||||
.await
|
&CloseActiveItem {
|
||||||
.unwrap();
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
|
assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
|
pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
|
||||||
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
|
assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.unwrap()
|
pane.close_active_item(
|
||||||
.await
|
&CloseActiveItem {
|
||||||
.unwrap();
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_item_labels(&pane, ["A", "B*", "C"], cx);
|
assert_item_labels(&pane, ["A", "B*", "C"], cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.unwrap()
|
pane.close_active_item(
|
||||||
.await
|
&CloseActiveItem {
|
||||||
.unwrap();
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_item_labels(&pane, ["A", "C*"], cx);
|
assert_item_labels(&pane, ["A", "C*"], cx);
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_active_item(&CloseActiveItem, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.unwrap()
|
pane.close_active_item(
|
||||||
.await
|
&CloseActiveItem {
|
||||||
.unwrap();
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_item_labels(&pane, ["A*"], cx);
|
assert_item_labels(&pane, ["A*"], cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1258,7 +1258,7 @@ impl Workspace {
|
||||||
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(false, cx);
|
let prepare = self.prepare_to_close(false, 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);
|
||||||
|
@ -1270,6 +1270,7 @@ impl Workspace {
|
||||||
pub fn prepare_to_close(
|
pub fn prepare_to_close(
|
||||||
&mut self,
|
&mut self,
|
||||||
quitting: bool,
|
quitting: bool,
|
||||||
|
save_behavior: SaveBehavior,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<bool>> {
|
) -> Task<Result<bool>> {
|
||||||
let active_call = self.active_call().cloned();
|
let active_call = self.active_call().cloned();
|
||||||
|
@ -1308,13 +1309,15 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(this
|
Ok(this
|
||||||
.update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
|
.update(&mut cx, |this, cx| {
|
||||||
|
this.save_all_internal(save_behavior, cx)
|
||||||
|
})?
|
||||||
.await?)
|
.await?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
|
||||||
let save_all = self.save_all_internal(false, cx);
|
let save_all = self.save_all_internal(SaveBehavior::PromptOnConflict, cx);
|
||||||
Some(cx.foreground().spawn(async move {
|
Some(cx.foreground().spawn(async move {
|
||||||
save_all.await?;
|
save_all.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1323,7 +1326,7 @@ impl Workspace {
|
||||||
|
|
||||||
fn save_all_internal(
|
fn save_all_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
should_prompt_to_save: bool,
|
save_behaviour: SaveBehavior,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Task<Result<bool>> {
|
) -> Task<Result<bool>> {
|
||||||
if self.project.read(cx).is_read_only() {
|
if self.project.read(cx).is_read_only() {
|
||||||
|
@ -1358,7 +1361,7 @@ impl Workspace {
|
||||||
&pane,
|
&pane,
|
||||||
ix,
|
ix,
|
||||||
&*item,
|
&*item,
|
||||||
should_prompt_to_save,
|
save_behaviour,
|
||||||
&mut cx,
|
&mut cx,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -1404,7 +1407,7 @@ impl Workspace {
|
||||||
let close_task = if is_remote || has_worktree || has_dirty_items {
|
let close_task = if is_remote || has_worktree || has_dirty_items {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(self.prepare_to_close(false, cx))
|
Some(self.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx))
|
||||||
};
|
};
|
||||||
let app_state = self.app_state.clone();
|
let app_state = self.app_state.clone();
|
||||||
|
|
||||||
|
@ -4099,7 +4102,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||||
// If the user cancels any save prompt, then keep the app open.
|
// If the user cancels any save prompt, then keep the app open.
|
||||||
for window in workspace_windows {
|
for window in workspace_windows {
|
||||||
if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
|
if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
|
||||||
workspace.prepare_to_close(true, cx)
|
workspace.prepare_to_close(true, SaveBehavior::PromptOnWrite, cx)
|
||||||
}) {
|
}) {
|
||||||
if !should_close.await? {
|
if !should_close.await? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -4289,7 +4292,9 @@ mod tests {
|
||||||
// When there are no dirty items, there's nothing to do.
|
// When there are no dirty items, there's nothing to do.
|
||||||
let item1 = window.add_view(cx, |_| TestItem::new());
|
let item1 = window.add_view(cx, |_| TestItem::new());
|
||||||
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
|
workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
let task = workspace.update(cx, |w, cx| {
|
||||||
|
w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx)
|
||||||
|
});
|
||||||
assert!(task.await.unwrap());
|
assert!(task.await.unwrap());
|
||||||
|
|
||||||
// When there are dirty untitled items, prompt to save each one. If the user
|
// When there are dirty untitled items, prompt to save each one. If the user
|
||||||
|
@ -4304,7 +4309,9 @@ mod tests {
|
||||||
w.add_item(Box::new(item2.clone()), cx);
|
w.add_item(Box::new(item2.clone()), cx);
|
||||||
w.add_item(Box::new(item3.clone()), cx);
|
w.add_item(Box::new(item3.clone()), cx);
|
||||||
});
|
});
|
||||||
let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
|
let task = workspace.update(cx, |w, cx| {
|
||||||
|
w.prepare_to_close(false, SaveBehavior::PromptOnWrite, cx)
|
||||||
|
});
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
window.simulate_prompt_answer(2, cx); // cancel
|
window.simulate_prompt_answer(2, cx); // cancel
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
@ -4358,7 +4365,9 @@ mod tests {
|
||||||
let item1_id = item1.id();
|
let item1_id = item1.id();
|
||||||
let item3_id = item3.id();
|
let item3_id = item3.id();
|
||||||
let item4_id = item4.id();
|
let item4_id = item4.id();
|
||||||
pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
|
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| {
|
||||||
|
[item1_id, item3_id, item4_id].contains(&id)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
|
@ -4493,7 +4502,9 @@ mod tests {
|
||||||
// once for project entry 0, and once for project entry 2. After those two
|
// once for project entry 0, and once for project entry 2. After those two
|
||||||
// prompts, the task should complete.
|
// prompts, the task should complete.
|
||||||
|
|
||||||
let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
|
let close = left_pane.update(cx, |pane, cx| {
|
||||||
|
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true)
|
||||||
|
});
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
left_pane.read_with(cx, |pane, cx| {
|
left_pane.read_with(cx, |pane, cx| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -4609,9 +4620,11 @@ mod tests {
|
||||||
item.is_dirty = true;
|
item.is_dirty = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
|
pane.update(cx, |pane, cx| {
|
||||||
.await
|
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
|
||||||
.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert!(!window.has_pending_prompt(cx));
|
assert!(!window.has_pending_prompt(cx));
|
||||||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||||
|
|
||||||
|
@ -4630,8 +4643,9 @@ mod tests {
|
||||||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||||
|
|
||||||
// Ensure autosave is prevented for deleted files also when closing the buffer.
|
// Ensure autosave is prevented for deleted files also when closing the buffer.
|
||||||
let _close_items =
|
let _close_items = pane.update(cx, |pane, cx| {
|
||||||
pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
|
pane.close_items(cx, SaveBehavior::PromptOnWrite, move |id| id == item_id)
|
||||||
|
});
|
||||||
deterministic.run_until_parked();
|
deterministic.run_until_parked();
|
||||||
assert!(window.has_pending_prompt(cx));
|
assert!(window.has_pending_prompt(cx));
|
||||||
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
|
||||||
|
|
|
@ -41,7 +41,12 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||||
MenuItem::action("Save", workspace::Save),
|
MenuItem::action("Save", workspace::Save),
|
||||||
MenuItem::action("Save As…", workspace::SaveAs),
|
MenuItem::action("Save As…", workspace::SaveAs),
|
||||||
MenuItem::action("Save All", workspace::SaveAll),
|
MenuItem::action("Save All", workspace::SaveAll),
|
||||||
MenuItem::action("Close Editor", workspace::CloseActiveItem),
|
MenuItem::action(
|
||||||
|
"Close Editor",
|
||||||
|
workspace::CloseActiveItem {
|
||||||
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
MenuItem::action("Close Window", workspace::CloseWindow),
|
MenuItem::action("Close Window", workspace::CloseWindow),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -438,7 +438,7 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
|
||||||
// If the user cancels any save prompt, then keep the app open.
|
// If the user cancels any save prompt, then keep the app open.
|
||||||
for window in workspace_windows {
|
for window in workspace_windows {
|
||||||
if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
|
if let Some(should_close) = window.update_root(&mut cx, |workspace, cx| {
|
||||||
workspace.prepare_to_close(true, cx)
|
workspace.prepare_to_close(true, workspace::SaveBehavior::PromptOnWrite, cx)
|
||||||
}) {
|
}) {
|
||||||
if !should_close.await? {
|
if !should_close.await? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -733,7 +733,7 @@ mod tests {
|
||||||
use theme::{ThemeRegistry, ThemeSettings};
|
use theme::{ThemeRegistry, ThemeSettings};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemHandle},
|
item::{Item, ItemHandle},
|
||||||
open_new, open_paths, pane, NewFile, SplitDirection, WorkspaceHandle,
|
open_new, open_paths, pane, NewFile, SaveBehavior, SplitDirection, WorkspaceHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
@ -1495,7 +1495,12 @@ mod tests {
|
||||||
|
|
||||||
pane2_item.downcast::<Editor>().unwrap().downgrade()
|
pane2_item.downcast::<Editor>().unwrap().downgrade()
|
||||||
});
|
});
|
||||||
cx.dispatch_action(window.into(), workspace::CloseActiveItem);
|
cx.dispatch_action(
|
||||||
|
window.into(),
|
||||||
|
workspace::CloseActiveItem {
|
||||||
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
workspace.read_with(cx, |workspace, _| {
|
workspace.read_with(cx, |workspace, _| {
|
||||||
|
@ -1503,7 +1508,12 @@ mod tests {
|
||||||
assert_eq!(workspace.active_pane(), &pane_1);
|
assert_eq!(workspace.active_pane(), &pane_1);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(window.into(), workspace::CloseActiveItem);
|
cx.dispatch_action(
|
||||||
|
window.into(),
|
||||||
|
workspace::CloseActiveItem {
|
||||||
|
save_behavior: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
window.simulate_prompt_answer(1, cx);
|
window.simulate_prompt_answer(1, cx);
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
@ -1661,7 +1671,7 @@ mod tests {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
let editor3_id = editor3.id();
|
let editor3_id = editor3.id();
|
||||||
drop(editor3);
|
drop(editor3);
|
||||||
pane.close_item_by_id(editor3_id, cx)
|
pane.close_item_by_id(editor3_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1696,7 +1706,7 @@ mod tests {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
let editor2_id = editor2.id();
|
let editor2_id = editor2.id();
|
||||||
drop(editor2);
|
drop(editor2);
|
||||||
pane.close_item_by_id(editor2_id, cx)
|
pane.close_item_by_id(editor2_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -1852,24 +1862,32 @@ mod tests {
|
||||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||||
|
|
||||||
// Close all the pane items in some arbitrary order.
|
// Close all the pane items in some arbitrary order.
|
||||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file1_item_id, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.await
|
pane.close_item_by_id(file1_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file4_item_id, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.await
|
pane.close_item_by_id(file4_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file2_item_id, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.await
|
pane.close_item_by_id(file2_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
|
||||||
|
|
||||||
pane.update(cx, |pane, cx| pane.close_item_by_id(file3_item_id, cx))
|
pane.update(cx, |pane, cx| {
|
||||||
.await
|
pane.close_item_by_id(file3_item_id, SaveBehavior::PromptOnWrite, cx)
|
||||||
.unwrap();
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
assert_eq!(active_path(&workspace, cx), None);
|
assert_eq!(active_path(&workspace, cx), None);
|
||||||
|
|
||||||
// Reopen all the closed items, ensuring they are reopened in the same order
|
// Reopen all the closed items, ensuring they are reopened in the same order
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue