diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 5ae43398d626fd5b5208e5e73d2c034644a29d95..ef1f9a1fbe16f0c6f9aaaf48201282e731383aeb 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -562,10 +562,6 @@ impl Item for AgentDiffPane { self.editor.for_each_project_item(cx, f) } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn set_nav_history( &mut self, nav_history: ItemNavHistory, diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 61b3e05e48a9fe3da35957b05fcd7dbf7206f146..4f559e5a1567b6454d1e74d81fd0c182296a9b8d 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -508,10 +508,6 @@ impl Item for ChannelView { })) } - fn is_singleton(&self, _cx: &App) -> bool { - false - } - fn navigate( &mut self, data: Box, diff --git a/crates/debugger_ui/src/stack_trace_view.rs b/crates/debugger_ui/src/stack_trace_view.rs index 7cfb111a2bf0a2ab4c60acbff16825eb9e4cf41d..ea6bf770428d91812cc3b98eacc298dbcd4aa87c 100644 --- a/crates/debugger_ui/src/stack_trace_view.rs +++ b/crates/debugger_ui/src/stack_trace_view.rs @@ -354,10 +354,6 @@ impl Item for StackTraceView { self.editor.for_each_project_item(cx, f) } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn set_nav_history( &mut self, nav_history: ItemNavHistory, diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index cb3e6eaa7e6b71f8a4105d26444acb795f92559f..e25d3a7702e02c93e38f5808434aff57e743defe 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -730,10 +730,6 @@ impl Item for BufferDiagnosticsEditor { self.multibuffer.read(cx).is_dirty(cx) } - fn is_singleton(&self, _cx: &App) -> bool { - false - } - fn navigate( &mut self, data: Box, diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 500a5320195a230686488969d6db153f7f043f31..5fbd958141f658ee74ad91d51a5c5081227b436f 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -716,10 +716,6 @@ impl Item for ProjectDiagnosticsEditor { self.editor.for_each_project_item(cx, f) } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn set_nav_history( &mut self, nav_history: ItemNavHistory, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdfcef01ae06bbf13d5e07544d60e20674808c03..9ac6b5447b1f256d2bee01878a0f0c3addd2edd3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -201,7 +201,7 @@ use workspace::{ CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings, - item::{ItemHandle, PreviewTabsSettings, SaveOptions}, + item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions}, notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt}, searchable::SearchEvent, }; @@ -7286,7 +7286,7 @@ impl Editor { } pub fn supports_minimap(&self, cx: &App) -> bool { - !self.minimap_visibility.disabled() && self.is_singleton(cx) + !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton } fn edit_predictions_enabled_in_buffer( @@ -17952,7 +17952,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if self.is_singleton(cx) { + if self.buffer_kind(cx) == ItemBufferKind::Singleton { let selection = self.selections.newest::(cx); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); @@ -18018,7 +18018,7 @@ impl Editor { } pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context) { - if self.is_singleton(cx) { + if self.buffer_kind(cx) == ItemBufferKind::Singleton { let mut to_fold = Vec::new(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.selections.all_adjusted(cx); @@ -18275,7 +18275,7 @@ impl Editor { } pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context) { - if self.is_singleton(cx) { + if self.buffer_kind(cx) == ItemBufferKind::Singleton { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; let selections = self.selections.all::(cx); @@ -19056,7 +19056,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) -> Option> { - (minimap_settings.minimap_enabled() && self.is_singleton(cx)) + (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton) .then(|| self.initialize_new_minimap(minimap_settings, window, cx)) } @@ -21834,7 +21834,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if self.is_singleton(cx) + if self.buffer_kind(cx) == ItemBufferKind::Singleton && !self.mode.is_minimap() && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 1c5f1e4c7e18652810cdded73f04ccb166368e89..4bfb2c06f4324ad7c41079fcdd2e440b9178c489 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -18798,8 +18798,9 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { let active_item = workspace .active_item(cx) .expect("should have an active item after adding the multi buffer"); - assert!( - !active_item.is_singleton(cx), + assert_eq!( + active_item.buffer_kind(cx), + ItemBufferKind::Multibuffer, "A multi buffer was expected to active after adding" ); active_item.item_id() @@ -18827,8 +18828,9 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { first_item_id, multibuffer_item_id, "Should navigate into the 1st buffer and activate it" ); - assert!( - active_item.is_singleton(cx), + assert_eq!( + active_item.buffer_kind(cx), + ItemBufferKind::Singleton, "New active item should be a singleton buffer" ); assert_eq!( @@ -18858,7 +18860,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { multibuffer_item_id, "Should navigate back to the multi buffer" ); - assert!(!active_item.is_singleton(cx)); + assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer); }) .unwrap(); @@ -18886,8 +18888,9 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { second_item_id, first_item_id, "Should navigate into the 2nd buffer and activate it" ); - assert!( - active_item.is_singleton(cx), + assert_eq!( + active_item.buffer_kind(cx), + ItemBufferKind::Singleton, "New active item should be a singleton buffer" ); assert_eq!( @@ -18917,7 +18920,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { multibuffer_item_id, "Should navigate back from the 2nd buffer to the multi buffer" ); - assert!(!active_item.is_singleton(cx)); + assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer); }) .unwrap(); @@ -18943,8 +18946,9 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { ); assert_ne!(third_item_id, first_item_id); assert_ne!(third_item_id, second_item_id); - assert!( - active_item.is_singleton(cx), + assert_eq!( + active_item.buffer_kind(cx), + ItemBufferKind::Singleton, "New active item should be a singleton buffer" ); assert_eq!( @@ -18972,7 +18976,7 @@ async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) { multibuffer_item_id, "Should navigate back from the 3rd buffer to the multi buffer" ); - assert!(!active_item.is_singleton(cx)); + assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer); }) .unwrap(); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8b0001a9eeb53f54e40e36b6046614b41e29f8d1..663604e756e3bf9bfbf747d4623471ba8ccad6b9 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -96,7 +96,8 @@ use util::post_inc; use util::{RangeExt, ResultExt, debug_panic}; use workspace::{ CollaboratorId, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel, Workspace, - item::Item, notifications::NotifyTaskExt, + item::{Item, ItemBufferKind}, + notifications::NotifyTaskExt, }; /// Determines what kinds of highlights should be applied to a lines background. @@ -376,7 +377,7 @@ impl EditorElement { register_action(editor, window, Editor::move_to_enclosing_bracket); register_action(editor, window, Editor::undo_selection); register_action(editor, window, Editor::redo_selection); - if !editor.read(cx).is_singleton(cx) { + if editor.read(cx).buffer_kind(cx) == ItemBufferKind::Multibuffer { register_action(editor, window, Editor::expand_excerpts); register_action(editor, window, Editor::expand_excerpts_up); register_action(editor, window, Editor::expand_excerpts_down); @@ -1765,7 +1766,7 @@ impl EditorElement { let show_scrollbars = match scrollbar_settings.show { ShowScrollbar::Auto => { let editor = self.editor.read(cx); - let is_singleton = editor.is_singleton(cx); + let is_singleton = editor.buffer_kind(cx) == ItemBufferKind::Singleton; // Git (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_diff_hunks()) || @@ -3281,7 +3282,7 @@ impl EditorElement { ) -> Vec> { let include_fold_statuses = EditorSettings::get_global(cx).gutter.folds && snapshot.mode.is_full() - && self.editor.read(cx).is_singleton(cx); + && self.editor.read(cx).buffer_kind(cx) == ItemBufferKind::Singleton; if include_fold_statuses { row_infos .iter() @@ -5810,7 +5811,7 @@ impl EditorElement { } fn paint_line_numbers(&mut self, layout: &mut EditorLayout, window: &mut Window, cx: &mut App) { - let is_singleton = self.editor.read(cx).is_singleton(cx); + let is_singleton = self.editor.read(cx).buffer_kind(cx) == ItemBufferKind::Singleton; let line_height = layout.position_map.line_height; window.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox); @@ -6622,7 +6623,7 @@ impl EditorElement { cx: &mut App, ) { self.editor.update(cx, |editor, cx| { - if !editor.is_singleton(cx) + if editor.buffer_kind(cx) != ItemBufferKind::Singleton || !editor .scrollbar_marker_state .should_refresh(scrollbar_layout.hitbox.size) @@ -8774,25 +8775,26 @@ impl Element for EditorElement { .map(|editor| { editor.update(cx, |editor, cx| { let all_selections = editor.selections.all::(cx); - let selected_buffer_ids = if editor.is_singleton(cx) { - Vec::new() - } else { - let mut selected_buffer_ids = - Vec::with_capacity(all_selections.len()); - - for selection in all_selections { - for buffer_id in snapshot - .buffer_snapshot - .buffer_ids_for_range(selection.range()) - { - if selected_buffer_ids.last() != Some(&buffer_id) { - selected_buffer_ids.push(buffer_id); + let selected_buffer_ids = + if editor.buffer_kind(cx) == ItemBufferKind::Singleton { + Vec::new() + } else { + let mut selected_buffer_ids = + Vec::with_capacity(all_selections.len()); + + for selection in all_selections { + for buffer_id in snapshot + .buffer_snapshot + .buffer_ids_for_range(selection.range()) + { + if selected_buffer_ids.last() != Some(&buffer_id) { + selected_buffer_ids.push(buffer_id); + } } } - } - selected_buffer_ids - }; + selected_buffer_ids + }; let mut selections = editor .selections diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 7a6a3cd464bb4156cc62b31558e57094a241fb58..1eed80abf68131361533d1780fe9eae00f56610b 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -43,7 +43,7 @@ use util::{ResultExt, TryFutureExt, paths::PathExt}; use workspace::{ CollaboratorId, ItemId, ItemNavHistory, ToolbarItemLocation, ViewId, Workspace, WorkspaceId, invalid_buffer_view::InvalidBufferView, - item::{FollowableItem, Item, ItemEvent, ProjectItem, SaveOptions}, + item::{FollowableItem, Item, ItemBufferKind, ItemEvent, ProjectItem, SaveOptions}, searchable::{ Direction, FilteredSearchRange, SearchEvent, SearchableItem, SearchableItemHandle, }, @@ -747,8 +747,11 @@ impl Item for Editor { .for_each_buffer(|buffer| f(buffer.entity_id(), buffer.read(cx))); } - fn is_singleton(&self, cx: &App) -> bool { - self.buffer.read(cx).is_singleton() + fn buffer_kind(&self, cx: &App) -> ItemBufferKind { + match self.buffer.read(cx).is_singleton() { + true => ItemBufferKind::Singleton, + false => ItemBufferKind::Multibuffer, + } } fn can_save_as(&self, cx: &App) -> bool { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 791dbc1bb9cc750b01bb6e26cc54a1954a52d3e0..a87db56c968ce5f8347eda801e99900326ede5ad 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -452,10 +452,6 @@ impl Item for CommitView { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn act_as_type<'a>( &'a self, type_id: TypeId, diff --git a/crates/git_ui/src/file_diff_view.rs b/crates/git_ui/src/file_diff_view.rs index e24fe4a6e0b5bc400a50410193781a3ce503a3d8..b13ce28b8aa18f5f2af4722f06518a42dfea2563 100644 --- a/crates/git_ui/src/file_diff_view.rs +++ b/crates/git_ui/src/file_diff_view.rs @@ -263,10 +263,6 @@ impl Item for FileDiffView { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn act_as_type<'a>( &'a self, type_id: TypeId, diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 85abb1312c171dabeaeb55b75d385a97d5811de4..1d4bad9f63584c7ec268f39acd8dbaf13711cac6 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -613,10 +613,6 @@ impl Item for ProjectDiff { self.editor.for_each_project_item(cx, f) } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn set_nav_history( &mut self, nav_history: ItemNavHistory, diff --git a/crates/git_ui/src/text_diff_view.rs b/crates/git_ui/src/text_diff_view.rs index 8fc4b741daa51e512d155a4aacd736ae3f43f985..3cafcd43d0c0593ef38e0e4d40c099594d7499fd 100644 --- a/crates/git_ui/src/text_diff_view.rs +++ b/crates/git_ui/src/text_diff_view.rs @@ -324,10 +324,6 @@ impl Item for TextDiffView { .update(cx, |editor, cx| editor.deactivated(window, cx)); } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn act_as_type<'a>( &'a self, type_id: TypeId, diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index d091761bda3c69786cff314442cfcdc29d2f821e..8a4f2ebfe237c66d33e5d97a461b34552d0e5974 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -98,10 +98,6 @@ impl Item for ImageView { f(self.image_item.entity_id(), self.image_item.read(cx)) } - fn is_singleton(&self, _cx: &App) -> bool { - true - } - fn tab_tooltip_text(&self, cx: &App) -> Option { let abs_path = self.image_item.read(cx).abs_path(cx)?; let file_path = abs_path.compact().to_string_lossy().into_owned(); diff --git a/crates/onboarding/src/multibuffer_hint.rs b/crates/onboarding/src/multibuffer_hint.rs index 3a20cbb6bd4443356db3a9fc1402b1102558ea02..9d290306d83308125f13985cc3e950b7d88d4866 100644 --- a/crates/onboarding/src/multibuffer_hint.rs +++ b/crates/onboarding/src/multibuffer_hint.rs @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use db::kvp::KEY_VALUE_STORE; use gpui::{App, EntityId, EventEmitter, Subscription}; use ui::{IconButtonShape, Tooltip, prelude::*}; -use workspace::item::{ItemEvent, ItemHandle}; +use workspace::item::{ItemBufferKind, ItemEvent, ItemHandle}; use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; pub struct MultibufferHint { @@ -79,7 +79,7 @@ impl MultibufferHint { return ToolbarItemLocation::Hidden; }; - if active_pane_item.is_singleton(cx) + if active_pane_item.buffer_kind(cx) == ItemBufferKind::Singleton || active_pane_item.breadcrumbs(cx.theme(), cx).is_none() || !active_pane_item.can_save(cx) { diff --git a/crates/repl/src/notebook/notebook_ui.rs b/crates/repl/src/notebook/notebook_ui.rs index 081c474cdad86a5340520ef09345bd456f55b5ba..20b2bc62e001cc565495924ffa80cbe466abe649 100644 --- a/crates/repl/src/notebook/notebook_ui.rs +++ b/crates/repl/src/notebook/notebook_ui.rs @@ -724,10 +724,6 @@ impl Item for NotebookEditor { f(self.notebook_item.entity_id(), self.notebook_item.read(cx)) } - fn is_singleton(&self, _cx: &App) -> bool { - true - } - fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement { Label::new(self.tab_content_text(params.detail.unwrap_or(0), cx)) .single_line() diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e342a8d4b199711411f0d5db87218d477f5d8488..9014f1764d7a6097908624e0d93bc6174e998445 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -522,10 +522,6 @@ impl Item for ProjectSearchView { self.results_editor.for_each_project_item(cx, f) } - fn is_singleton(&self, _: &App) -> bool { - false - } - fn can_save(&self, _: &App) -> bool { true } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index a99d68609a6bef5b449c7217a1613846d7c4b9ce..16f4b7603e8390656e89923fac319a29b796423b 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1257,10 +1257,6 @@ impl Item for TerminalView { false } - fn is_singleton(&self, _cx: &App) -> bool { - true - } - fn as_searchable(&self, handle: &Entity) -> Option> { Some(Box::new(handle.clone())) } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 1a4c748f66b74d11bac2c40b5637155b1daedd1e..572dd26cd779b974412cbf0476f9b9de11fb6315 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -196,6 +196,13 @@ pub enum TabTooltipContent { Custom(Box AnyView>), } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ItemBufferKind { + Multibuffer, + Singleton, + None, +} + pub trait Item: Focusable + EventEmitter + Render + Sized { type Event; @@ -260,8 +267,8 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem), ) { } - fn is_singleton(&self, _cx: &App) -> bool { - false + fn buffer_kind(&self, _cx: &App) -> ItemBufferKind { + ItemBufferKind::None } fn set_nav_history(&mut self, _: ItemNavHistory, _window: &mut Window, _: &mut Context) {} fn clone_on_split( @@ -467,7 +474,7 @@ pub trait ItemHandle: 'static + Send { _: &App, _: &mut dyn FnMut(EntityId, &dyn project::ProjectItem), ); - fn is_singleton(&self, cx: &App) -> bool; + fn buffer_kind(&self, cx: &App) -> ItemBufferKind; fn boxed_clone(&self) -> Box; fn clone_on_split( &self, @@ -616,7 +623,7 @@ impl ItemHandle for Entity { fn project_path(&self, cx: &App) -> Option { let this = self.read(cx); let mut result = None; - if this.is_singleton(cx) { + if this.buffer_kind(cx) == ItemBufferKind::Singleton { this.for_each_project_item(cx, &mut |_, item| { result = item.project_path(cx); }); @@ -674,8 +681,8 @@ impl ItemHandle for Entity { self.read(cx).for_each_project_item(cx, f) } - fn is_singleton(&self, cx: &App) -> bool { - self.read(cx).is_singleton(cx) + fn buffer_kind(&self, cx: &App) -> ItemBufferKind { + self.read(cx).buffer_kind(cx) } fn boxed_clone(&self) -> Box { @@ -1292,7 +1299,10 @@ impl WeakFollowableItemHandle for WeakEntity { #[cfg(any(test, feature = "test-support"))] pub mod test { use super::{Item, ItemEvent, SerializableItem, TabContentParams}; - use crate::{ItemId, ItemNavHistory, Workspace, WorkspaceId, item::SaveOptions}; + use crate::{ + ItemId, ItemNavHistory, Workspace, WorkspaceId, + item::{ItemBufferKind, SaveOptions}, + }; use gpui::{ AnyElement, App, AppContext as _, Context, Entity, EntityId, EventEmitter, Focusable, InteractiveElement, IntoElement, Render, SharedString, Task, WeakEntity, Window, @@ -1315,7 +1325,7 @@ pub mod test { pub save_as_count: usize, pub reload_count: usize, pub is_dirty: bool, - pub is_singleton: bool, + pub buffer_kind: ItemBufferKind, pub has_conflict: bool, pub has_deleted_file: bool, pub project_items: Vec>, @@ -1399,7 +1409,7 @@ pub mod test { has_conflict: false, has_deleted_file: false, project_items: Vec::new(), - is_singleton: true, + buffer_kind: ItemBufferKind::Singleton, nav_history: None, tab_descriptions: None, tab_detail: Default::default(), @@ -1420,8 +1430,8 @@ pub mod test { self } - pub fn with_singleton(mut self, singleton: bool) -> Self { - self.is_singleton = singleton; + pub fn with_buffer_kind(mut self, buffer_kind: ItemBufferKind) -> Self { + self.buffer_kind = buffer_kind; self } @@ -1516,8 +1526,8 @@ pub mod test { .for_each(|item| f(item.entity_id(), item.read(cx))) } - fn is_singleton(&self, _: &App) -> bool { - self.is_singleton + fn buffer_kind(&self, _: &App) -> ItemBufferKind { + self.buffer_kind } fn set_nav_history( @@ -1564,7 +1574,7 @@ pub mod test { save_as_count: self.save_as_count, reload_count: self.reload_count, is_dirty: self.is_dirty, - is_singleton: self.is_singleton, + buffer_kind: self.buffer_kind, has_conflict: self.has_conflict, has_deleted_file: self.has_deleted_file, project_items: self.project_items.clone(), @@ -1598,7 +1608,7 @@ pub mod test { } fn can_save_as(&self, _cx: &App) -> bool { - self.is_singleton + self.buffer_kind == ItemBufferKind::Singleton } fn save( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 5f36c8e83fa4d0eb454786ae5c5c5470eff12072..29e95de6f34cc68b744257843f56642a360b3ac0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -4,9 +4,9 @@ use crate::{ WorkspaceItemBuilder, invalid_buffer_view::InvalidBufferView, item::{ - ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, - ProjectItemKind, SaveOptions, ShowCloseButton, ShowDiagnostics, TabContentParams, - TabTooltipContent, WeakItemHandle, + ActivateOnClose, ClosePosition, Item, ItemBufferKind, ItemHandle, ItemSettings, + PreviewTabsSettings, ProjectItemKind, SaveOptions, ShowCloseButton, ShowDiagnostics, + TabContentParams, TabTooltipContent, WeakItemHandle, }, move_item, notifications::NotifyResultExt, @@ -125,6 +125,17 @@ pub struct CloseOtherItems { pub close_pinned: bool, } +/// Closes all multibuffers in the pane. +#[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] +#[action(namespace = pane)] +#[serde(deny_unknown_fields)] +pub struct CloseMultibufferItems { + #[serde(default)] + pub save_intent: Option, + #[serde(default)] + pub close_pinned: bool, +} + /// Closes all items in the pane. #[derive(Clone, PartialEq, Debug, Deserialize, JsonSchema, Default, Action)] #[action(namespace = pane)] @@ -900,7 +911,7 @@ impl Pane { let mut existing_item = None; if let Some(project_entry_id) = project_entry_id { for (index, item) in self.items.iter().enumerate() { - if item.is_singleton(cx) + if item.buffer_kind(cx) == ItemBufferKind::Singleton && item.project_entry_ids(cx).as_slice() == [project_entry_id] { let item = item.boxed_clone(); @@ -910,7 +921,9 @@ impl Pane { } } else { for (index, item) in self.items.iter().enumerate() { - if item.is_singleton(cx) && item.project_path(cx).as_ref() == Some(&project_path) { + if item.buffer_kind(cx) == ItemBufferKind::Singleton + && item.project_path(cx).as_ref() == Some(&project_path) + { let item = item.boxed_clone(); existing_item = Some((index, item)); break; @@ -1046,7 +1059,7 @@ impl Pane { self.close_items_on_item_open(window, cx); } - if item.is_singleton(cx) + if item.buffer_kind(cx) == ItemBufferKind::Singleton && let Some(&entry_id) = item.project_entry_ids(cx).first() { let Some(project) = self.project.upgrade() else { @@ -1077,7 +1090,7 @@ impl Pane { }; // Does the item already exist? - let project_entry_id = if item.is_singleton(cx) { + let project_entry_id = if item.buffer_kind(cx) == ItemBufferKind::Singleton { item.project_entry_ids(cx).first().copied() } else { None @@ -1086,7 +1099,7 @@ impl Pane { let existing_item_index = self.items.iter().position(|existing_item| { if existing_item.item_id() == item.item_id() { true - } else if existing_item.is_singleton(cx) { + } else if existing_item.buffer_kind(cx) == ItemBufferKind::Singleton { existing_item .project_entry_ids(cx) .first() @@ -1202,7 +1215,9 @@ impl Pane { cx: &App, ) -> Option> { self.items.iter().find_map(|item| { - if item.is_singleton(cx) && (item.project_entry_ids(cx).as_slice() == [entry_id]) { + if item.buffer_kind(cx) == ItemBufferKind::Singleton + && (item.project_entry_ids(cx).as_slice() == [entry_id]) + { Some(item.boxed_clone()) } else { None @@ -1216,7 +1231,8 @@ impl Pane { cx: &App, ) -> Option> { self.items.iter().find_map(move |item| { - if item.is_singleton(cx) && (item.project_path(cx).as_slice() == [project_path.clone()]) + if item.buffer_kind(cx) == ItemBufferKind::Singleton + && (item.project_path(cx).as_slice() == [project_path.clone()]) { Some(item.boxed_clone()) } else { @@ -1475,6 +1491,30 @@ impl Pane { ) } + pub fn close_multibuffer_items( + &mut self, + action: &CloseMultibufferItems, + window: &mut Window, + cx: &mut Context, + ) -> Task> { + if self.items.is_empty() { + return Task::ready(Ok(())); + } + + let pinned_item_ids = self.pinned_item_ids(); + let multibuffer_items = self.multibuffer_item_ids(cx); + + self.close_items( + window, + cx, + action.save_intent.unwrap_or(SaveIntent::Close), + move |item_id| { + (action.close_pinned || !pinned_item_ids.contains(&item_id)) + && multibuffer_items.contains(&item_id) + }, + ) + } + pub fn close_clean_items( &mut self, action: &CloseCleanItems, @@ -1637,14 +1677,14 @@ impl Pane { } }); if dirty_project_item_ids.is_empty() { - return !(item.is_singleton(cx) && item.is_dirty(cx)); + return !(item.buffer_kind(cx) == ItemBufferKind::Singleton && item.is_dirty(cx)); } for open_item in workspace.items(cx) { if open_item.item_id() == item.item_id() { continue; } - if !open_item.is_singleton(cx) { + if open_item.buffer_kind(cx) != ItemBufferKind::Singleton { continue; } let other_project_item_ids = open_item.project_item_model_ids(cx); @@ -1999,7 +2039,7 @@ impl Pane { item.is_dirty(cx), item.can_save(cx), item.can_save_as(cx), - item.is_singleton(cx), + item.buffer_kind(cx) == ItemBufferKind::Singleton, item.has_deleted_file(cx), ) })?; @@ -2281,7 +2321,9 @@ impl Pane { cx: &mut Context, ) -> Option<()> { let item_id = self.items().find_map(|item| { - if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] { + if item.buffer_kind(cx) == ItemBufferKind::Singleton + && item.project_entry_ids(cx).as_slice() == [entry_id] + { Some(item.item_id()) } else { None @@ -2701,12 +2743,15 @@ impl Pane { .child(label), ); - let single_entry_to_resolve = self.items[ix] - .is_singleton(cx) + let single_entry_to_resolve = (self.items[ix].buffer_kind(cx) == ItemBufferKind::Singleton) .then(|| self.items[ix].project_entry_ids(cx).get(0).copied()) .flatten(); let total_items = self.items.len(); + let has_multibuffer_items = self + .items + .iter() + .any(|item| item.buffer_kind(cx) == ItemBufferKind::Multibuffer); let has_items_to_left = ix > 0; let has_items_to_right = ix < total_items - 1; let has_clean_items = self.items.iter().any(|item| !item.is_dirty(cx)); @@ -2727,6 +2772,10 @@ impl Pane { save_intent: None, close_pinned: false, }; + let close_multibuffers_action = CloseMultibufferItems { + save_intent: None, + close_pinned: false, + }; let close_items_to_the_left_action = CloseItemsToTheLeft { close_pinned: false, }; @@ -2764,6 +2813,24 @@ impl Pane { .detach_and_log_err(cx); })), )) + // We make this optional, instead of using disabled as to not overwhelm the context menu unnecessarily + .extend(has_multibuffer_items.then(|| { + ContextMenuItem::Entry( + ContextMenuEntry::new("Close Multibuffers") + .action(Box::new(close_multibuffers_action.clone())) + .handler(window.handler_for( + &pane, + move |pane, window, cx| { + pane.close_multibuffer_items( + &close_multibuffers_action, + window, + cx, + ) + .detach_and_log_err(cx); + }, + )), + ) + })) .separator() .item(ContextMenuItem::Entry( ContextMenuEntry::new("Close Left") @@ -3485,6 +3552,13 @@ impl Pane { } } + fn multibuffer_item_ids(&self, cx: &mut Context) -> Vec { + self.items() + .filter(|item| item.buffer_kind(cx) == ItemBufferKind::Multibuffer) + .map(|item| item.item_id()) + .collect() + } + pub fn drag_split_direction(&self) -> Option { self.drag_split_direction } @@ -3706,6 +3780,12 @@ impl Render for Pane { .detach_and_log_err(cx) }), ) + .on_action(cx.listener( + |pane: &mut Self, action: &CloseMultibufferItems, window, cx| { + pane.close_multibuffer_items(action, window, cx) + .detach_and_log_err(cx) + }, + )) .on_action( cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, _, cx| { let entry_id = action @@ -5655,7 +5735,7 @@ mod tests { pane.add_item( Box::new(cx.new(|cx| { TestItem::new(cx) - .with_singleton(true) + .with_buffer_kind(ItemBufferKind::Singleton) .with_label("buffer 1") .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)]) })), @@ -5673,7 +5753,7 @@ mod tests { pane.add_item( Box::new(cx.new(|cx| { TestItem::new(cx) - .with_singleton(true) + .with_buffer_kind(ItemBufferKind::Singleton) .with_label("buffer 1") .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) })), @@ -5691,7 +5771,7 @@ mod tests { pane.add_item( Box::new(cx.new(|cx| { TestItem::new(cx) - .with_singleton(true) + .with_buffer_kind(ItemBufferKind::Singleton) .with_label("buffer 2") .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)]) })), @@ -5709,7 +5789,7 @@ mod tests { pane.add_item( Box::new(cx.new(|cx| { TestItem::new(cx) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_label("multibuffer 1") .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) })), @@ -5727,7 +5807,7 @@ mod tests { pane.add_item( Box::new(cx.new(|cx| { TestItem::new(cx) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_label("multibuffer 1b") .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)]) })), @@ -6281,6 +6361,139 @@ mod tests { assert_item_labels(&pane, [], cx); } + #[gpui::test] + async fn test_close_multibuffer_items(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + + let project = Project::test(fs, None, cx).await; + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + + let add_labeled_item = |pane: &Entity, + label, + is_dirty, + kind: ItemBufferKind, + cx: &mut VisualTestContext| { + pane.update_in(cx, |pane, window, cx| { + let labeled_item = Box::new(cx.new(|cx| { + TestItem::new(cx) + .with_label(label) + .with_dirty(is_dirty) + .with_buffer_kind(kind) + })); + pane.add_item(labeled_item.clone(), false, false, None, window, cx); + labeled_item + }) + }; + + let item_a = add_labeled_item(&pane, "A", false, ItemBufferKind::Multibuffer, cx); + add_labeled_item(&pane, "B", false, ItemBufferKind::Multibuffer, cx); + add_labeled_item(&pane, "C", false, ItemBufferKind::Singleton, cx); + assert_item_labels(&pane, ["A", "B", "C*"], cx); + + pane.update_in(cx, |pane, window, cx| { + let ix = pane.index_for_item_id(item_a.item_id()).unwrap(); + pane.pin_tab_at(ix, window, cx); + pane.close_multibuffer_items( + &CloseMultibufferItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }) + .await + .unwrap(); + assert_item_labels(&pane, ["A!", "C*"], cx); + + pane.update_in(cx, |pane, window, cx| { + let ix = pane.index_for_item_id(item_a.item_id()).unwrap(); + pane.unpin_tab_at(ix, window, cx); + pane.close_multibuffer_items( + &CloseMultibufferItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }) + .await + .unwrap(); + + assert_item_labels(&pane, ["C*"], cx); + + add_labeled_item(&pane, "A", true, ItemBufferKind::Singleton, cx).update(cx, |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(1, "A.txt", cx)) + }); + add_labeled_item(&pane, "B", true, ItemBufferKind::Multibuffer, cx).update( + cx, + |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(2, "B.txt", cx)) + }, + ); + add_labeled_item(&pane, "D", true, ItemBufferKind::Multibuffer, cx).update( + cx, + |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(3, "D.txt", cx)) + }, + ); + assert_item_labels(&pane, ["C", "A^", "B^", "D*^"], cx); + + let save = pane.update_in(cx, |pane, window, cx| { + pane.close_multibuffer_items( + &CloseMultibufferItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer("Save all"); + save.await.unwrap(); + assert_item_labels(&pane, ["C", "A*^"], cx); + + add_labeled_item(&pane, "B", true, ItemBufferKind::Multibuffer, cx).update( + cx, + |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(2, "B.txt", cx)) + }, + ); + add_labeled_item(&pane, "D", true, ItemBufferKind::Multibuffer, cx).update( + cx, + |item, cx| { + item.project_items + .push(TestProjectItem::new_dirty(3, "D.txt", cx)) + }, + ); + assert_item_labels(&pane, ["C", "A^", "B^", "D*^"], cx); + let save = pane.update_in(cx, |pane, window, cx| { + pane.close_multibuffer_items( + &CloseMultibufferItems { + save_intent: None, + close_pinned: false, + }, + window, + cx, + ) + }); + + cx.executor().run_until_parked(); + cx.simulate_prompt_answer("Discard all"); + save.await.unwrap(); + assert_item_labels(&pane, ["C", "A*^"], cx); + } + #[gpui::test] async fn test_close_with_save_intent(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 188fb2fd0046d12aafc7426fd3b20951f8ca9307..0886a202f00fddcbbd072b7cb3913cb2d37ce788 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -60,7 +60,10 @@ use notifications::{ simple_message_notification::MessageNotification, }; pub use pane::*; -pub use pane_group::*; +pub use pane_group::{ + ActivePaneDecorator, HANDLE_HITBOX_SIZE, Member, PaneAxis, PaneGroup, PaneRenderContext, + SplitDirection, +}; use persistence::{DB, SerializedWindowBounds, model::SerializedWorkspace}; pub use persistence::{ DB as WORKSPACE_DB, WorkspaceDb, delete_unloaded_items, @@ -117,11 +120,11 @@ pub use workspace_settings::{ }; use zed_actions::{Spawn, feedback::FileBugReport}; -use crate::notifications::NotificationId; use crate::persistence::{ SerializedAxis, model::{DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup}, }; +use crate::{item::ItemBufferKind, notifications::NotificationId}; pub const SERIALIZATION_THROTTLE_TIME: Duration = Duration::from_millis(200); @@ -2521,8 +2524,12 @@ impl Workspace { }; for (pane, item) in dirty_items { - let (singleton, project_entry_ids) = - cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?; + let (singleton, project_entry_ids) = cx.update(|_, cx| { + ( + item.buffer_kind(cx) == ItemBufferKind::Singleton, + item.project_entry_ids(cx), + ) + })?; if (singleton || !project_entry_ids.is_empty()) && !Pane::save_item(project.clone(), &pane, &*item, save_intent, cx).await? { @@ -4805,7 +4812,7 @@ impl Workspace { .items() .find_map(|item| { let item = item.to_followable_item_handle(cx)?; - if item.is_singleton(cx) + if item.buffer_kind(cx) == ItemBufferKind::Singleton && item.project_item_model_ids(cx).as_slice() == [buffer_entity_id] { @@ -8192,7 +8199,7 @@ mod tests { use crate::{ dock::{PanelEvent, test::TestPanel}, item::{ - ItemEvent, + ItemBufferKind, ItemEvent, test::{TestItem, TestProjectItem}, }, }; @@ -8553,7 +8560,7 @@ mod tests { let item_2_3 = cx.new(|cx| { TestItem::new(cx) .with_dirty(true) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_project_items(&[ single_entry_items[2].read(cx).project_items[0].clone(), single_entry_items[3].read(cx).project_items[0].clone(), @@ -8562,7 +8569,7 @@ mod tests { let item_3_4 = cx.new(|cx| { TestItem::new(cx) .with_dirty(true) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_project_items(&[ single_entry_items[3].read(cx).project_items[0].clone(), single_entry_items[4].read(cx).project_items[0].clone(), @@ -9583,7 +9590,7 @@ mod tests { let dirty_multi_buffer_with_both = cx.new(|cx| { TestItem::new(cx) .with_dirty(true) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_label("Fake Project Search") .with_project_items(&[ dirty_regular_buffer.read(cx).project_items[0].clone(), @@ -9734,7 +9741,7 @@ mod tests { let dirty_multi_buffer_with_both = cx.new(|cx| { TestItem::new(cx) .with_dirty(true) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_label("Fake Project Search") .with_project_items(&[ dirty_regular_buffer.read(cx).project_items[0].clone(), @@ -10148,7 +10155,7 @@ mod tests { let dirty_multi_buffer = cx.new(|cx| { TestItem::new(cx) .with_dirty(true) - .with_singleton(false) + .with_buffer_kind(ItemBufferKind::Multibuffer) .with_label("Fake Project Search") .with_project_items(&[ dirty_regular_buffer.read(cx).project_items[0].clone(), diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index df1a417f5815753698a18b077d69c81c5b7ba3ed..c721e1e8b6e7c8d1a3caf5c9a57e0159a5a3c031 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -23,6 +23,7 @@ use ui::{ IconName, IconSize, PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*, }; use vim_mode_setting::VimModeSetting; +use workspace::item::ItemBufferKind; use workspace::{ ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::ItemHandle, }; @@ -131,7 +132,7 @@ impl Render for QuickActionBar { let code_action_enabled = editor_value.code_actions_enabled_for_toolbar(cx); let focus_handle = editor_value.focus_handle(cx); - let search_button = editor.is_singleton(cx).then(|| { + let search_button = (editor.buffer_kind(cx) == ItemBufferKind::Singleton).then(|| { QuickActionBarButton::new( "toggle buffer search", search::SEARCH_ICON,