diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 629f9500147533a85154995fe2462f5b6d5f5297..3f83f8e37a5c493062d2291b7f56995ca527254b 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -41,8 +41,6 @@ pub fn truncate(s: &str, max_chars: usize) -> &str { } } -/// Removes characters from the end of the string if it's length is greater than `max_chars` and -/// appends "..." to the string. Returns string unchanged if it's length is smaller than max_chars. pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { debug_assert!(max_chars >= 5); @@ -53,18 +51,6 @@ pub fn truncate_and_trailoff(s: &str, max_chars: usize) -> String { } } -/// Removes characters from the front of the string if it's length is greater than `max_chars` and -/// prepends the string with "...". Returns string unchanged if it's length is smaller than max_chars. -pub fn truncate_and_remove_front(s: &str, max_chars: usize) -> String { - debug_assert!(max_chars >= 5); - - let truncation_ix = s.char_indices().map(|(i, _)| i).nth_back(max_chars); - match truncation_ix { - Some(length) => "…".to_string() + &s[length..], - None => s.to_string(), - } -} - pub fn post_inc + AddAssign + Copy>(value: &mut T) -> T { let prev = *value; *value += T::from(1); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index a191adcc05e0798316d311b26b5368b711eebee5..a3e6a547ddfd73c1fd67c50c4b6c4df2d6ff51d1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -42,7 +42,6 @@ use std::{ }, }; use theme::{Theme, ThemeSettings}; -use util::truncate_and_remove_front; #[derive(PartialEq, Clone, Copy, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -840,45 +839,10 @@ impl Pane { Some(self.close_items(cx, SaveBehavior::PromptOnWrite, |_| true)) } - pub(super) fn file_names_for_prompt( - items: &mut dyn Iterator>, - all_dirty_items: usize, - cx: &AppContext, - ) -> String { - /// Quantity of item paths displayed in prompt prior to cutoff.. - const FILE_NAMES_CUTOFF_POINT: usize = 10; - let mut file_names: Vec<_> = items - .filter_map(|item| { - item.project_path(cx).and_then(|project_path| { - project_path - .path - .file_name() - .and_then(|name| name.to_str().map(ToOwned::to_owned)) - }) - }) - .take(FILE_NAMES_CUTOFF_POINT) - .collect(); - let should_display_followup_text = - all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items; - if should_display_followup_text { - let not_shown_files = all_dirty_items - file_names.len(); - if not_shown_files == 1 { - file_names.push(".. 1 file not shown".into()); - } else { - file_names.push(format!(".. {} files not shown", not_shown_files).into()); - } - } - let file_names = file_names.join("\n"); - format!( - "Do you want to save changes to the following {} files?\n{file_names}", - all_dirty_items - ) - } - pub fn close_items( &mut self, cx: &mut ViewContext, - mut save_behavior: SaveBehavior, + save_behavior: SaveBehavior, should_close: impl 'static + Fn(usize) -> bool, ) -> Task> { // Find the items to close. @@ -897,25 +861,6 @@ impl Pane { let workspace = self.workspace.clone(); cx.spawn(|pane, mut cx| async move { - if save_behavior == SaveBehavior::PromptOnWrite && items_to_close.len() > 1 { - let mut answer = pane.update(&mut cx, |_, cx| { - let prompt = Self::file_names_for_prompt( - &mut items_to_close.iter(), - items_to_close.len(), - cx, - ); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save all", "Discard all", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => save_behavior = SaveBehavior::PromptOnConflict, - Some(1) => save_behavior = SaveBehavior::DontSave, - _ => {} - } - } let mut saved_project_items_ids = HashSet::default(); for item in items_to_close.clone() { // Find the item's current index and its set of project item models. Avoid @@ -1058,6 +1003,7 @@ impl Pane { ) -> Result { 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); @@ -1100,10 +1046,9 @@ impl Pane { 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); - let prompt = dirty_message_for(item.project_path(cx)); cx.prompt( PromptLevel::Warning, - &prompt, + DIRTY_MESSAGE, &["Save", "Don't Save", "Cancel"], ) })?; @@ -2190,15 +2135,6 @@ impl Element for PaneBackdrop { } } -fn dirty_message_for(buffer_path: Option) -> String { - let path = buffer_path - .as_ref() - .and_then(|p| p.path.to_str()) - .unwrap_or(&"Untitled buffer"); - let path = truncate_and_remove_front(path, 80); - format!("{path} contains unsaved edits. Do you want to save it?") -} - #[cfg(test)] mod tests { use super::*; @@ -2543,14 +2479,12 @@ mod tests { set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - let task = pane - .update(cx, |pane, cx| { - pane.close_inactive_items(&CloseInactiveItems, cx) - }) - .unwrap(); - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - task.await.unwrap(); + pane.update(cx, |pane, cx| { + pane.close_inactive_items(&CloseInactiveItems, cx) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*"], cx); } @@ -2571,12 +2505,10 @@ mod tests { add_labeled_item(&pane, "E", false, cx); assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx); - let task = pane - .update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx)) + .unwrap() + .await .unwrap(); - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - task.await.unwrap(); assert_item_labels(&pane, ["A^", "C*^"], cx); } @@ -2592,14 +2524,12 @@ mod tests { set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - let task = pane - .update(cx, |pane, cx| { - pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) - }) - .unwrap(); - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - task.await.unwrap(); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_left(&CloseItemsToTheLeft, cx) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["C*", "D", "E"], cx); } @@ -2615,14 +2545,12 @@ mod tests { set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx); - let task = pane - .update(cx, |pane, cx| { - pane.close_items_to_the_right(&CloseItemsToTheRight, cx) - }) - .unwrap(); - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - task.await.unwrap(); + pane.update(cx, |pane, cx| { + pane.close_items_to_the_right(&CloseItemsToTheRight, cx) + }) + .unwrap() + .await + .unwrap(); assert_item_labels(&pane, ["A", "B", "C*"], cx); } @@ -2641,12 +2569,10 @@ mod tests { add_labeled_item(&pane, "C", false, cx); assert_item_labels(&pane, ["A", "B", "C*"], cx); - let t = pane - .update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) + pane.update(cx, |pane, cx| pane.close_all_items(&CloseAllItems, cx)) + .unwrap() + .await .unwrap(); - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); - t.await.unwrap(); assert_item_labels(&pane, [], cx); } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 263652184a89c85810861831d6b1b660454875df..feab53d0941d1823fc79930d3697c39e54822207 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1333,12 +1333,13 @@ impl Workspace { fn save_all_internal( &mut self, - mut save_behaviour: SaveBehavior, + save_behaviour: SaveBehavior, cx: &mut ViewContext, ) -> Task> { if self.project.read(cx).is_read_only() { return Task::ready(Ok(true)); } + let dirty_items = self .panes .iter() @@ -1354,27 +1355,7 @@ impl Workspace { .collect::>(); let project = self.project.clone(); - cx.spawn(|workspace, mut cx| async move { - // Override save mode and display "Save all files" prompt - if save_behaviour == SaveBehavior::PromptOnWrite && dirty_items.len() > 1 { - let mut answer = workspace.update(&mut cx, |_, cx| { - let prompt = Pane::file_names_for_prompt( - &mut dirty_items.iter().map(|(_, handle)| handle), - dirty_items.len(), - cx, - ); - cx.prompt( - PromptLevel::Warning, - &prompt, - &["Save all", "Discard all", "Cancel"], - ) - })?; - match answer.next().await { - Some(0) => save_behaviour = SaveBehavior::PromptOnConflict, - Some(1) => save_behaviour = SaveBehavior::DontSave, - _ => {} - } - } + cx.spawn(|_, mut cx| async move { for (pane, item) in dirty_items { let (singleton, project_entry_ids) = cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx))); @@ -4339,9 +4320,7 @@ mod tests { }); let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx)); cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); // cancel save all - cx.foreground().run_until_parked(); - window.simulate_prompt_answer(2, cx); // cancel save all + window.simulate_prompt_answer(2, cx); // cancel cx.foreground().run_until_parked(); assert!(!window.has_pending_prompt(cx)); assert!(!task.await.unwrap()); @@ -4399,15 +4378,13 @@ mod tests { }); cx.foreground().run_until_parked(); - assert!(window.has_pending_prompt(cx)); - // Ignore "Save all" prompt - window.simulate_prompt_answer(2, cx); - cx.foreground().run_until_parked(); // There's a prompt to save item 1. pane.read_with(cx, |pane, _| { assert_eq!(pane.items_len(), 4); assert_eq!(pane.active_item().unwrap().id(), item1.id()); }); + assert!(window.has_pending_prompt(cx)); + // Confirm saving item 1. window.simulate_prompt_answer(0, cx); cx.foreground().run_until_parked(); @@ -4535,10 +4512,6 @@ mod tests { let close = left_pane.update(cx, |pane, cx| { pane.close_items(cx, SaveBehavior::PromptOnWrite, move |_| true) }); - cx.foreground().run_until_parked(); - // Discard "Save all" prompt - window.simulate_prompt_answer(2, cx); - cx.foreground().run_until_parked(); left_pane.read_with(cx, |pane, cx| { assert_eq!(