Merge branch 'main' into storybook
This commit is contained in:
commit
15ea4af5e7
22 changed files with 734 additions and 96 deletions
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue