Detailed changes
@@ -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,
@@ -508,10 +508,6 @@ impl Item for ChannelView {
}))
}
- fn is_singleton(&self, _cx: &App) -> bool {
- false
- }
-
fn navigate(
&mut self,
data: Box<dyn Any>,
@@ -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,
@@ -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<dyn Any>,
@@ -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,
@@ -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<Self>,
) {
- if self.is_singleton(cx) {
+ if self.buffer_kind(cx) == ItemBufferKind::Singleton {
let selection = self.selections.newest::<Point>(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<Self>) {
- 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<Self>) {
- 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::<Point>(cx);
@@ -19056,7 +19056,7 @@ impl Editor {
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Entity<Self>> {
- (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<Editor>,
) {
- 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
{
@@ -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();
}
@@ -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<Option<AnyElement>> {
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::<Point>(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
@@ -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 {
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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<SharedString> {
let abs_path = self.image_item.read(cx).abs_path(cx)?;
let file_path = abs_path.compact().to_string_lossy().into_owned();
@@ -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)
{
@@ -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()
@@ -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
}
@@ -1257,10 +1257,6 @@ impl Item for TerminalView {
false
}
- fn is_singleton(&self, _cx: &App) -> bool {
- true
- }
-
fn as_searchable(&self, handle: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
@@ -196,6 +196,13 @@ pub enum TabTooltipContent {
Custom(Box<dyn Fn(&mut Window, &mut App) -> AnyView>),
}
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ItemBufferKind {
+ Multibuffer,
+ Singleton,
+ None,
+}
+
pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
type Event;
@@ -260,8 +267,8 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + 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<Self>) {}
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<dyn ItemHandle>;
fn clone_on_split(
&self,
@@ -616,7 +623,7 @@ impl<T: Item> ItemHandle for Entity<T> {
fn project_path(&self, cx: &App) -> Option<ProjectPath> {
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<T: Item> ItemHandle for Entity<T> {
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<dyn ItemHandle> {
@@ -1292,7 +1299,10 @@ impl<T: FollowableItem> WeakFollowableItemHandle for WeakEntity<T> {
#[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<Entity<TestProjectItem>>,
@@ -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(
@@ -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<SaveIntent>,
+ #[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<Box<dyn ItemHandle>> {
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<Box<dyn ItemHandle>> {
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<Self>,
+ ) -> Task<Result<()>> {
+ 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<Pane>,
) -> 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<Pane>) -> Vec<EntityId> {
+ self.items()
+ .filter(|item| item.buffer_kind(cx) == ItemBufferKind::Multibuffer)
+ .map(|item| item.item_id())
+ .collect()
+ }
+
pub fn drag_split_direction(&self) -> Option<SplitDirection> {
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<Pane>,
+ 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);
@@ -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(),
@@ -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,