diff --git a/Cargo.lock b/Cargo.lock index f79af953cc..bc4752bf11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14312,6 +14312,7 @@ dependencies = [ "ctor", "editor", "env_logger 0.11.8", + "fuzzy", "gpui", "language", "menu", @@ -14321,6 +14322,7 @@ dependencies = [ "serde", "serde_json", "settings", + "smol", "theme", "ui", "util", diff --git a/crates/agent/src/agent_diff.rs b/crates/agent/src/agent_diff.rs index 0e66308f37..b09c0015c5 100644 --- a/crates/agent/src/agent_diff.rs +++ b/crates/agent/src/agent_diff.rs @@ -597,6 +597,10 @@ impl Item for AgentDiff { editor.added_to_workspace(workspace, window, cx) }); } + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Agent Diff".into() + } } impl Render for AgentDiff { diff --git a/crates/agent/src/context_picker/completion_provider.rs b/crates/agent/src/context_picker/completion_provider.rs index b5c8bf0248..d610470e63 100644 --- a/crates/agent/src/context_picker/completion_provider.rs +++ b/crates/agent/src/context_picker/completion_provider.rs @@ -1045,6 +1045,10 @@ mod tests { fn include_in_nav_history() -> bool { false } + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Test".into() + } } impl EventEmitter<()> for AtMentionEditor {} diff --git a/crates/assistant/src/assistant_configuration.rs b/crates/assistant/src/assistant_configuration.rs index cb3d268a63..6b96051a5f 100644 --- a/crates/assistant/src/assistant_configuration.rs +++ b/crates/assistant/src/assistant_configuration.rs @@ -193,7 +193,7 @@ impl Focusable for ConfigurationView { impl Item for ConfigurationView { type Event = ConfigurationViewEvent; - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Configuration".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Configuration".into() } } diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 94e267c85a..840ed0eda3 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -3160,8 +3160,8 @@ impl Focusable for ContextEditor { impl Item for ContextEditor { type Event = editor::EditorEvent; - fn tab_content_text(&self, _window: &Window, cx: &App) -> Option { - Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into()) + fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { + util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into() } fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) { diff --git a/crates/assistant_context_editor/src/context_history.rs b/crates/assistant_context_editor/src/context_history.rs index 35b932158b..560033be34 100644 --- a/crates/assistant_context_editor/src/context_history.rs +++ b/crates/assistant_context_editor/src/context_history.rs @@ -108,8 +108,8 @@ impl EventEmitter<()> for ContextHistory {} impl Item for ContextHistory { type Event = (); - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("History".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "History".into() } } diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index b0fd67add0..044a6b2922 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -91,7 +91,7 @@ fn view_release_notes_locally( let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); - let tab_description = SharedString::from(body.title.to_string()); + let tab_content = SharedString::from(body.title.to_string()); let editor = cx.new(|cx| { Editor::for_multibuffer(buffer, Some(project), window, cx) }); @@ -102,7 +102,7 @@ fn view_release_notes_locally( editor, workspace_handle, language_registry, - Some(tab_description), + tab_content, window, cx, ); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 78d30adb4a..57494bd42b 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1517,10 +1517,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut workspace.leader_for_pane(workspace.active_pane()) ); let item = workspace.active_item(cx).unwrap(); - assert_eq!( - item.tab_description(0, cx).unwrap(), - SharedString::from("w.rs") - ); + assert_eq!(item.tab_content_text(0, cx), SharedString::from("w.rs")); }); // TODO: in app code, this would be done by the collab_ui. @@ -1546,10 +1543,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut executor.run_until_parked(); workspace_b_project_a.update(&mut cx_b2, |workspace, cx| { let item = workspace.active_item(cx).unwrap(); - assert_eq!( - item.tab_description(0, cx).unwrap(), - SharedString::from("x.rs") - ); + assert_eq!(item.tab_content_text(0, cx), SharedString::from("x.rs")); }); workspace_a.update_in(cx_a, |workspace, window, cx| { @@ -1564,7 +1558,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut workspace.leader_for_pane(workspace.active_pane()) ); let item = workspace.active_pane().read(cx).active_item().unwrap(); - assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs"); + assert_eq!(item.tab_content_text(0, cx), "x.rs"); }); // b moves to y.rs in b's project, a is still following but can't yet see @@ -1625,10 +1619,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut workspace.leader_for_pane(workspace.active_pane()) ); let item = workspace.active_item(cx).unwrap(); - assert_eq!( - item.tab_description(0, cx).unwrap(), - SharedString::from("y.rs") - ); + assert_eq!(item.tab_content_text(0, cx), SharedString::from("y.rs")); }); } @@ -1885,13 +1876,7 @@ fn pane_summaries(workspace: &Entity, cx: &mut VisualTestContext) -> items: pane .items() .enumerate() - .map(|(ix, item)| { - ( - ix == active_ix, - item.tab_description(0, cx) - .map_or(String::new(), |s| s.to_string()), - ) - }) + .map(|(ix, item)| (ix == active_ix, item.tab_content_text(0, cx).into())) .collect(), } }) @@ -2179,7 +2164,7 @@ async fn test_following_to_channel_notes_other_workspace( cx_a.run_until_parked(); workspace_a.update(cx_a, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // b joins channel and is following a @@ -2188,7 +2173,7 @@ async fn test_following_to_channel_notes_other_workspace( let (workspace_b, cx_b) = client_b.active_workspace(cx_b); workspace_b.update(cx_b, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // a opens a second workspace and the channel notes @@ -2212,13 +2197,13 @@ async fn test_following_to_channel_notes_other_workspace( workspace_a.update(cx_a, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // b should follow a back workspace_b.update(cx_b, |workspace, cx| { let editor = workspace.active_item_as::(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); } @@ -2238,7 +2223,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut cx_a.run_until_parked(); workspace_a.update(cx_a, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // b joins channel and is following a @@ -2247,7 +2232,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut let (workspace_b, cx_b) = client_b.active_workspace(cx_b); workspace_b.update(cx_b, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // stop following @@ -2260,7 +2245,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut workspace_b.update(cx_b, |workspace, cx| { let editor = workspace.active_item_as::(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt"); + assert_eq!(editor.tab_content_text(0, cx), "1.txt"); }); // a opens a file in a new window @@ -2281,12 +2266,12 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut workspace_a.update(cx_a, |workspace, cx| { let editor = workspace.active_item(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "2.js"); + assert_eq!(editor.tab_content_text(0, cx), "2.js"); }); // b should follow a back workspace_b.update(cx_b, |workspace, cx| { let editor = workspace.active_item_as::(cx).unwrap(); - assert_eq!(editor.tab_description(0, cx).unwrap(), "2.js"); + assert_eq!(editor.tab_content_text(0, cx), "2.js"); }); } diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 554cfe113a..bb7192026e 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -540,6 +540,10 @@ impl Item for ChannelView { fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) { Editor::to_item_events(event, f) } + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Channels".into() + } } impl FollowableItem for ChannelView { diff --git a/crates/component_preview/src/component_preview.rs b/crates/component_preview/src/component_preview.rs index 74105a0213..9a2abb9929 100644 --- a/crates/component_preview/src/component_preview.rs +++ b/crates/component_preview/src/component_preview.rs @@ -735,8 +735,8 @@ impl From for ActivePageId { impl Item for ComponentPreview { type Event = ItemEvent; - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Component Preview".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Component Preview".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index acd26e2d7f..eca26d2b73 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -727,8 +727,8 @@ impl Item for DapLogView { Editor::to_item_events(event, f) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("DAP Logs".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "DAP Logs".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/debugger_ui/src/session.rs b/crates/debugger_ui/src/session.rs index 974f205fa6..756f866bf1 100644 --- a/crates/debugger_ui/src/session.rs +++ b/crates/debugger_ui/src/session.rs @@ -170,6 +170,9 @@ impl Focusable for DebugSession { impl Item for DebugSession { type Event = DebugPanelItemEvent; + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Debugger".into() + } } impl FollowableItem for DebugSession { diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index 45af03623a..9d4f34b5cd 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -139,8 +139,8 @@ impl Item for SubView { /// This is used to serialize debugger pane layouts /// A SharedString gets converted to a enum and back during serialization/deserialization. - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some(self.kind.to_shared_string()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + self.kind.to_shared_string() } fn tab_content( diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 18602356c5..b0ead9ea9b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -568,6 +568,10 @@ impl Item for ProjectDiagnosticsEditor { Some("Project Diagnostics".into()) } + fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString { + "Diagnostics".into() + } + fn tab_content(&self, params: TabContentParams, _window: &Window, _: &App) -> AnyElement { h_flex() .gap_1() diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 476a05b29f..232024e554 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -619,9 +619,12 @@ impl Item for Editor { None } - fn tab_description(&self, detail: usize, cx: &App) -> Option { - let path = path_for_buffer(&self.buffer, detail, true, cx)?; - Some(path.to_string_lossy().to_string().into()) + fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString { + if let Some(path) = path_for_buffer(&self.buffer, detail, true, cx) { + path.to_string_lossy().to_string().into() + } else { + "untitled".into() + } } fn tab_icon(&self, _: &Window, cx: &App) -> Option { diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 0eebddb640..734d39cfe6 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -285,8 +285,8 @@ impl Item for ProposedChangesEditor { Some(Icon::new(IconName::Diff)) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some(self.title.clone()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + self.title.clone() } fn as_searchable(&self, _: &Entity) -> Option> { diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 92570f72c9..430b656f09 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1398,8 +1398,8 @@ impl Focusable for ExtensionsPage { impl Item for ExtensionsPage { type Event = ItemEvent; - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Extensions".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Extensions".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index d7ec189028..3f8b2f52d5 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -19,7 +19,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use ui::{Color, Icon, IconName, Label, LabelCommon as _}; +use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString}; use util::{ResultExt, truncate_and_trailoff}; use workspace::{ Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace, @@ -409,10 +409,8 @@ impl Item for CommitView { Some(Icon::new(IconName::GitBranch).color(Color::Muted)) } - fn tab_content(&self, params: TabContentParams, _window: &Window, _: &App) -> AnyElement { - let short_sha = self.commit.sha.get(0..7).unwrap_or(&*self.commit.sha); - let subject = truncate_and_trailoff(self.commit.message.split('\n').next().unwrap(), 20); - Label::new(format!("{short_sha} - {subject}",)) + fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { + Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx)) .color(if params.selected { Color::Default } else { @@ -421,6 +419,12 @@ impl Item for CommitView { .into_any_element() } + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + let short_sha = self.commit.sha.get(0..7).unwrap_or(&*self.commit.sha); + let subject = truncate_and_trailoff(self.commit.message.split('\n').next().unwrap(), 20); + format!("{short_sha} - {subject}").into() + } + fn tab_tooltip_text(&self, _: &App) -> Option { let short_sha = self.commit.sha.get(0..16).unwrap_or(&*self.commit.sha); let subject = self.commit.message.split('\n').next().unwrap(); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index eb93f97d3f..36ad7e528c 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -547,6 +547,10 @@ impl Item for ProjectDiff { .into_any_element() } + fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString { + "Uncommitted Changes".into() + } + fn telemetry_event_text(&self) -> Option<&'static str> { Some("Project Diff Opened") } diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 0a8043a1c6..5795265e36 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -99,7 +99,7 @@ impl Item for ImageView { Some(file_path.into()) } - fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> AnyElement { + fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { let project_path = self.image_item.read(cx).project_path(cx); let label_color = if ItemSettings::get_global(cx).git_status { @@ -121,20 +121,23 @@ impl Item for ImageView { params.text_color() }; - let title = self - .image_item - .read(cx) - .file - .file_name(cx) - .to_string_lossy() - .to_string(); - Label::new(title) + Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx)) .single_line() .color(label_color) .when(params.preview, |this| this.italic()) .into_any_element() } + fn tab_content_text(&self, _: usize, cx: &App) -> SharedString { + self.image_item + .read(cx) + .file + .file_name(cx) + .to_string_lossy() + .to_string() + .into() + } + fn tab_icon(&self, _: &Window, cx: &App) -> Option { let path = self.image_item.read(cx).path(); ItemSettings::get_global(cx) diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index 68f54e0528..0969b0edf6 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -150,8 +150,8 @@ impl Item for KeyContextView { fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {} - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Keyboard Context".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Keyboard Context".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index b90e925fed..8b29ab6298 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1058,8 +1058,8 @@ impl Item for LspLogView { Editor::to_item_events(event, f) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("LSP Logs".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "LSP Logs".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 2098c86673..3a14181db0 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -401,8 +401,8 @@ impl Item for SyntaxTreeView { fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {} - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Syntax Tree".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Syntax Tree".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index c6b554349d..bbcb196b29 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -35,8 +35,7 @@ pub struct MarkdownPreviewView { contents: Option, selected_block: usize, list_state: ListState, - tab_description: Option, - fallback_tab_description: SharedString, + tab_content_text: SharedString, language_registry: Arc, parsing_markdown_task: Option>>, } @@ -130,7 +129,7 @@ impl MarkdownPreviewView { editor, workspace_handle, language_registry, - None, + "Markdown Preview".into(), window, cx, ) @@ -141,7 +140,7 @@ impl MarkdownPreviewView { active_editor: Entity, workspace: WeakEntity, language_registry: Arc, - fallback_description: Option, + tab_content_text: SharedString, window: &mut Window, cx: &mut Context, ) -> Entity { @@ -262,10 +261,8 @@ impl MarkdownPreviewView { workspace: workspace.clone(), contents: None, list_state, - tab_description: None, + tab_content_text, language_registry, - fallback_tab_description: fallback_description - .unwrap_or_else(|| "Markdown Preview".into()), parsing_markdown_task: None, }; @@ -343,10 +340,8 @@ impl MarkdownPreviewView { }, ); - self.tab_description = editor - .read(cx) - .tab_description(0, cx) - .map(|tab_description| format!("Preview {}", tab_description)); + let tab_content = editor.read(cx).tab_content_text(0, cx); + self.tab_content_text = format!("Preview {}", tab_content).into(); self.active_editor = Some(EditorState { editor, @@ -496,12 +491,8 @@ impl Item for MarkdownPreviewView { Some(Icon::new(IconName::FileDoc)) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some(if let Some(description) = &self.tab_description { - description.clone().into() - } else { - self.fallback_tab_description.clone() - }) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + self.tab_content_text.clone() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/project_panel/src/project_panel_tests.rs b/crates/project_panel/src/project_panel_tests.rs index 65eeae795e..8badba4738 100644 --- a/crates/project_panel/src/project_panel_tests.rs +++ b/crates/project_panel/src/project_panel_tests.rs @@ -5308,6 +5308,10 @@ impl ProjectItem for TestProjectItemView { impl Item for TestProjectItemView { type Event = (); + + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Test".into() + } } impl EventEmitter<()> for TestProjectItemView {} diff --git a/crates/repl/src/notebook/notebook_ui.rs b/crates/repl/src/notebook/notebook_ui.rs index 4a539a64a7..07f3e63b24 100644 --- a/crates/repl/src/notebook/notebook_ui.rs +++ b/crates/repl/src/notebook/notebook_ui.rs @@ -731,17 +731,21 @@ impl Item for NotebookEditor { } 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() + .color(params.text_color()) + .when(params.preview, |this| this.italic()) + .into_any_element() + } + + fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { let path = &self.notebook_item.read(cx).path; let title = path .file_name() .unwrap_or_else(|| path.as_os_str()) .to_string_lossy() .to_string(); - Label::new(title) - .single_line() - .color(params.text_color()) - .when(params.preview, |this| this.italic()) - .into_any_element() + title.into() } fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { diff --git a/crates/repl/src/repl_sessions_ui.rs b/crates/repl/src/repl_sessions_ui.rs index abae381276..df7ce574ab 100644 --- a/crates/repl/src/repl_sessions_ui.rs +++ b/crates/repl/src/repl_sessions_ui.rs @@ -178,8 +178,8 @@ impl Focusable for ReplSessionsPage { impl Item for ReplSessionsPage { type Event = ItemEvent; - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("REPL Sessions".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "REPL Sessions".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 6223e32190..40447cf2fc 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -446,7 +446,7 @@ impl Item for ProjectSearchView { Some(Icon::new(IconName::MagnifyingGlass)) } - fn tab_content_text(&self, _: &Window, cx: &App) -> Option { + fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { let last_query: Option = self .entity .read(cx) @@ -457,11 +457,10 @@ impl Item for ProjectSearchView { let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN); query_text.into() }); - Some( - last_query - .filter(|query| !query.is_empty()) - .unwrap_or_else(|| "Project Search".into()), - ) + + last_query + .filter(|query| !query.is_empty()) + .unwrap_or_else(|| "Project Search".into()) } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/semantic_index/src/project_index_debug_view.rs b/crates/semantic_index/src/project_index_debug_view.rs index 140599d22a..15b86c3f77 100644 --- a/crates/semantic_index/src/project_index_debug_view.rs +++ b/crates/semantic_index/src/project_index_debug_view.rs @@ -289,8 +289,8 @@ impl EventEmitter<()> for ProjectIndexDebugView {} impl Item for ProjectIndexDebugView { type Event = (); - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Project Index (Debug)".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Project Index (Debug)".into() } fn clone_on_split( diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 65c420c6bf..3428a99bf8 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -160,8 +160,8 @@ impl Item for SettingsPage { Some(Icon::new(IconName::Settings)) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Settings".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Settings".into() } fn show_toolbar(&self) -> bool { diff --git a/crates/tab_switcher/Cargo.toml b/crates/tab_switcher/Cargo.toml index 55545016d5..027268e7d7 100644 --- a/crates/tab_switcher/Cargo.toml +++ b/crates/tab_switcher/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [dependencies] collections.workspace = true editor.workspace = true +fuzzy.workspace = true gpui.workspace = true menu.workspace = true picker.workspace = true @@ -22,6 +23,7 @@ project.workspace = true schemars.workspace = true serde.workspace = true settings.workspace = true +smol.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 14553c016e..25cfcfba7b 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -3,6 +3,7 @@ mod tab_switcher_tests; use collections::HashMap; use editor::items::entry_git_aware_label_color; +use fuzzy::StringMatchCandidate; use gpui::{ Action, AnyElement, App, Context, DismissEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Modifiers, ModifiersChangedEvent, MouseButton, MouseUpEvent, ParentElement, Render, @@ -13,7 +14,7 @@ use project::Project; use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; -use std::sync::Arc; +use std::{cmp::Reverse, sync::Arc}; use ui::{ListItem, ListItemSpacing, Tooltip, prelude::*}; use util::ResultExt; use workspace::{ @@ -32,7 +33,7 @@ pub struct Toggle { } impl_actions!(tab_switcher, [Toggle]); -actions!(tab_switcher, [CloseSelectedItem]); +actions!(tab_switcher, [CloseSelectedItem, ToggleAll]); pub struct TabSwitcher { picker: Entity>, @@ -53,7 +54,19 @@ impl TabSwitcher { ) { workspace.register_action(|workspace, action: &Toggle, window, cx| { let Some(tab_switcher) = workspace.active_modal::(cx) else { - Self::open(action, workspace, window, cx); + Self::open(workspace, action.select_last, false, window, cx); + return; + }; + + tab_switcher.update(cx, |tab_switcher, cx| { + tab_switcher + .picker + .update(cx, |picker, cx| picker.cycle_selection(window, cx)) + }); + }); + workspace.register_action(|workspace, _action: &ToggleAll, window, cx| { + let Some(tab_switcher) = workspace.active_modal::(cx) else { + Self::open(workspace, false, true, window, cx); return; }; @@ -66,8 +79,9 @@ impl TabSwitcher { } fn open( - action: &Toggle, workspace: &mut Workspace, + select_last: bool, + is_global: bool, window: &mut Window, cx: &mut Context, ) { @@ -90,24 +104,43 @@ impl TabSwitcher { }) } + let weak_workspace = workspace.weak_handle(); let project = workspace.project().clone(); workspace.toggle_modal(window, cx, |window, cx| { let delegate = TabSwitcherDelegate::new( project, - action, + select_last, cx.entity().downgrade(), weak_pane, + weak_workspace, + is_global, window, cx, ); - TabSwitcher::new(delegate, window, cx) + TabSwitcher::new(delegate, window, is_global, cx) }); } - fn new(delegate: TabSwitcherDelegate, window: &mut Window, cx: &mut Context) -> Self { + fn new( + delegate: TabSwitcherDelegate, + window: &mut Window, + is_global: bool, + cx: &mut Context, + ) -> Self { + let init_modifiers = if is_global { + None + } else { + window.modifiers().modified().then_some(window.modifiers()) + }; Self { - picker: cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx)), - init_modifiers: window.modifiers().modified().then_some(window.modifiers()), + picker: cx.new(|cx| { + if is_global { + Picker::uniform_list(delegate, window, cx) + } else { + Picker::nonsearchable_uniform_list(delegate, window, cx) + } + }), + init_modifiers, } } @@ -163,7 +196,9 @@ impl Render for TabSwitcher { } } +#[derive(Clone)] struct TabMatch { + pane: WeakEntity, item_index: usize, item: Box, detail: usize, @@ -175,27 +210,34 @@ pub struct TabSwitcherDelegate { tab_switcher: WeakEntity, selected_index: usize, pane: WeakEntity, + workspace: WeakEntity, project: Entity, matches: Vec, + is_all_panes: bool, } impl TabSwitcherDelegate { + #[allow(clippy::complexity)] fn new( project: Entity, - action: &Toggle, + select_last: bool, tab_switcher: WeakEntity, pane: WeakEntity, + workspace: WeakEntity, + is_all_panes: bool, window: &mut Window, cx: &mut Context, ) -> Self { Self::subscribe_to_updates(&pane, window, cx); Self { - select_last: action.select_last, + select_last, tab_switcher, selected_index: 0, pane, + workspace, project, matches: Vec::new(), + is_all_panes, } } @@ -212,7 +254,8 @@ impl TabSwitcherDelegate { PaneEvent::AddItem { .. } | PaneEvent::RemovedItem { .. } | PaneEvent::Remove { .. } => tab_switcher.picker.update(cx, |picker, cx| { - picker.delegate.update_matches(window, cx); + let query = picker.query(cx); + picker.delegate.update_matches(query, window, cx); cx.notify(); }), _ => {} @@ -221,7 +264,91 @@ impl TabSwitcherDelegate { .detach(); } - fn update_matches(&mut self, _window: &mut Window, cx: &mut App) { + fn update_all_pane_matches(&mut self, query: String, window: &mut Window, cx: &mut App) { + let Some(workspace) = self.workspace.upgrade() else { + return; + }; + let mut all_items = Vec::new(); + let mut item_index = 0; + for pane_handle in workspace.read(cx).panes() { + let pane = pane_handle.read(cx); + let items: Vec> = + pane.items().map(|item| item.boxed_clone()).collect(); + for ((_detail, item), detail) in items + .iter() + .enumerate() + .zip(tab_details(&items, window, cx)) + { + all_items.push(TabMatch { + pane: pane_handle.downgrade(), + item_index, + item: item.clone(), + detail, + preview: pane.is_active_preview_item(item.item_id()), + }); + item_index += 1; + } + } + + let matches = if query.is_empty() { + let history = workspace.read(cx).recently_activated_items(cx); + for item in &all_items { + eprintln!( + "{:?} {:?}", + item.item.tab_content_text(0, cx), + (Reverse(history.get(&item.item.item_id())), item.item_index) + ) + } + eprintln!(""); + all_items + .sort_by_key(|tab| (Reverse(history.get(&tab.item.item_id())), tab.item_index)); + all_items + } else { + let candidates = all_items + .iter() + .enumerate() + .flat_map(|(ix, tab_match)| { + Some(StringMatchCandidate::new( + ix, + &tab_match.item.tab_content_text(0, cx), + )) + }) + .collect::>(); + smol::block_on(fuzzy::match_strings( + &candidates, + &query, + true, + 10000, + &Default::default(), + cx.background_executor().clone(), + )) + .into_iter() + .map(|m| all_items[m.candidate_id].clone()) + .collect() + }; + + let selected_item_id = self.selected_item_id(); + self.matches = matches; + self.selected_index = self.compute_selected_index(selected_item_id); + } + + fn update_matches( + &mut self, + query: String, + window: &mut Window, + cx: &mut Context>, + ) { + if self.is_all_panes { + // needed because we need to borrow the workspace, but that may be borrowed when the picker + // calls update_matches. + let this = cx.entity(); + window.defer(cx, move |window, cx| { + this.update(cx, |this, cx| { + this.delegate.update_all_pane_matches(query, window, cx); + }) + }); + return; + } let selected_item_id = self.selected_item_id(); self.matches.clear(); let Some(pane) = self.pane.upgrade() else { @@ -240,8 +367,9 @@ impl TabSwitcherDelegate { items .iter() .enumerate() - .zip(tab_details(&items, cx)) + .zip(tab_details(&items, window, cx)) .map(|((item_index, item), detail)| TabMatch { + pane: self.pane.clone(), item_index, item: item.boxed_clone(), detail, @@ -348,11 +476,11 @@ impl PickerDelegate for TabSwitcherDelegate { fn update_matches( &mut self, - _raw_query: String, + raw_query: String, window: &mut Window, cx: &mut Context>, ) -> Task<()> { - self.update_matches(window, cx); + self.update_matches(raw_query, window, cx); Task::ready(()) } @@ -362,15 +490,17 @@ impl PickerDelegate for TabSwitcherDelegate { window: &mut Window, cx: &mut Context>, ) { - let Some(pane) = self.pane.upgrade() else { - return; - }; let Some(selected_match) = self.matches.get(self.selected_index()) else { return; }; - pane.update(cx, |pane, cx| { - pane.activate_item(selected_match.item_index, true, true, window, cx); - }); + selected_match + .pane + .update(cx, |pane, cx| { + if let Some(index) = pane.index_for_item(selected_match.item.as_ref()) { + pane.activate_item(index, true, true, window, cx); + } + }) + .ok(); } fn dismissed(&mut self, _: &mut Window, cx: &mut Context>) { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 74853569bc..3323c1de6a 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1462,6 +1462,11 @@ impl Item for TerminalView { .into_any() } + fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString { + let terminal = self.terminal().read(cx); + terminal.title(detail == 0).into() + } + fn telemetry_event_text(&self) -> Option<&'static str> { None } diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 1e55e5a9f4..3645993eda 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -796,8 +796,8 @@ fn generate_commands(_: &App) -> Vec { VimCommand::new(("bf", "irst"), workspace::ActivateItem(0)), VimCommand::new(("br", "ewind"), workspace::ActivateItem(0)), VimCommand::new(("bl", "ast"), workspace::ActivateLastItem), - VimCommand::str(("buffers", ""), "tab_switcher::Toggle"), - VimCommand::str(("ls", ""), "tab_switcher::Toggle"), + VimCommand::str(("buffers", ""), "tab_switcher::ToggleAll"), + VimCommand::str(("ls", ""), "tab_switcher::ToggleAll"), VimCommand::new(("new", ""), workspace::NewFileSplitHorizontal), VimCommand::new(("vne", "w"), workspace::NewFileSplitVertical), VimCommand::new(("tabe", "dit"), workspace::NewFile), diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 540a3de990..52e7c0ea5d 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -420,8 +420,8 @@ impl Focusable for WelcomePage { impl Item for WelcomePage { type Event = ItemEvent; - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some("Welcome".into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "Welcome".into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 26440ce1e4..00aa340e66 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -30,7 +30,7 @@ use std::{ time::Duration, }; use theme::Theme; -use ui::{Color, Element as _, Icon, IntoElement, Label, LabelCommon}; +use ui::{Color, Icon, IntoElement, Label, LabelCommon}; use util::ResultExt; pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); @@ -247,10 +247,8 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { /// /// By default this returns a [`Label`] that displays that text from /// `tab_content_text`. - fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement { - let Some(text) = self.tab_content_text(window, cx) else { - return gpui::Empty.into_any(); - }; + fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { + let text = self.tab_content_text(params.detail.unwrap_or_default(), cx); Label::new(text) .color(params.text_color()) @@ -258,11 +256,7 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { } /// Returns the textual contents of the tab. - /// - /// Use this if you don't need to customize the tab contents. - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - None - } + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString; fn tab_icon(&self, _window: &Window, _cx: &App) -> Option { None @@ -283,10 +277,6 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { self.tab_tooltip_text(cx).map(TabTooltipContent::Text) } - fn tab_description(&self, _: usize, _: &App) -> Option { - None - } - fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {} fn deactivated(&mut self, _window: &mut Window, _: &mut Context) {} @@ -492,8 +482,8 @@ pub trait ItemHandle: 'static + Send { cx: &mut App, handler: Box, ) -> gpui::Subscription; - fn tab_description(&self, detail: usize, cx: &App) -> Option; fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement; + fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString; fn tab_icon(&self, window: &Window, cx: &App) -> Option; fn tab_tooltip_text(&self, cx: &App) -> Option; fn tab_tooltip_content(&self, cx: &App) -> Option; @@ -616,13 +606,12 @@ impl ItemHandle for Entity { self.read(cx).telemetry_event_text() } - fn tab_description(&self, detail: usize, cx: &App) -> Option { - self.read(cx).tab_description(detail, cx) - } - fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement { self.read(cx).tab_content(params, window, cx) } + fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString { + self.read(cx).tab_content_text(detail, cx) + } fn tab_icon(&self, window: &Window, cx: &App) -> Option { self.read(cx).tab_icon(window, cx) @@ -1450,11 +1439,15 @@ pub mod test { f(*event) } - fn tab_description(&self, detail: usize, _: &App) -> Option { - self.tab_descriptions.as_ref().and_then(|descriptions| { - let description = *descriptions.get(detail).or_else(|| descriptions.last())?; - Some(description.into()) - }) + fn tab_content_text(&self, detail: usize, _cx: &App) -> SharedString { + self.tab_descriptions + .as_ref() + .and_then(|descriptions| { + let description = *descriptions.get(detail).or_else(|| descriptions.last())?; + description.into() + }) + .unwrap_or_default() + .into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e3556a5ad2..1eb142cffe 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -502,6 +502,7 @@ impl Pane { fn focus_in(&mut self, window: &mut Window, cx: &mut Context) { if !self.was_focused { self.was_focused = true; + self.update_history(self.active_item_index); cx.emit(Event::Focus); cx.notify(); } @@ -1095,17 +1096,7 @@ impl Pane { prev_item.deactivated(window, cx); } } - if let Some(newly_active_item) = self.items.get(index) { - self.activation_history - .retain(|entry| entry.entity_id != newly_active_item.item_id()); - self.activation_history.push(ActivationHistoryEntry { - entity_id: newly_active_item.item_id(), - timestamp: self - .next_activation_timestamp - .fetch_add(1, Ordering::SeqCst), - }); - } - + self.update_history(index); self.update_toolbar(window, cx); self.update_status_bar(window, cx); @@ -1127,6 +1118,19 @@ impl Pane { } } + fn update_history(&mut self, index: usize) { + if let Some(newly_active_item) = self.items.get(index) { + self.activation_history + .retain(|entry| entry.entity_id != newly_active_item.item_id()); + self.activation_history.push(ActivationHistoryEntry { + entity_id: newly_active_item.item_id(), + timestamp: self + .next_activation_timestamp + .fetch_add(1, Ordering::SeqCst), + }); + } + } + pub fn activate_prev_item( &mut self, activate_pane: bool, @@ -2634,7 +2638,7 @@ impl Pane { .items .iter() .enumerate() - .zip(tab_details(&self.items, cx)) + .zip(tab_details(&self.items, window, cx)) .map(|((ix, item), detail)| { self.render_tab(ix, &**item, detail, &focus_handle, window, cx) }) @@ -3632,7 +3636,7 @@ fn dirty_message_for(buffer_path: Option) -> String { format!("{path} contains unsaved edits. Do you want to save it?") } -pub fn tab_details(items: &[Box], cx: &App) -> Vec { +pub fn tab_details(items: &[Box], _window: &Window, cx: &App) -> Vec { let mut tab_details = items.iter().map(|_| 0).collect::>(); let mut tab_descriptions = HashMap::default(); let mut done = false; @@ -3641,15 +3645,12 @@ pub fn tab_details(items: &[Box], cx: &App) -> Vec { // Store item indices by their tab description. for (ix, (item, detail)) in items.iter().zip(&tab_details).enumerate() { - if let Some(description) = item.tab_description(*detail, cx) { - if *detail == 0 - || Some(&description) != item.tab_description(detail - 1, cx).as_ref() - { - tab_descriptions - .entry(description) - .or_insert(Vec::new()) - .push(ix); - } + let description = item.tab_content_text(*detail, cx); + if *detail == 0 || description != item.tab_content_text(detail - 1, cx) { + tab_descriptions + .entry(description) + .or_insert(Vec::new()) + .push(ix); } } diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 156424dde6..febb83d683 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -93,8 +93,8 @@ impl Item for SharedScreen { Some(Icon::new(IconName::Screen)) } - fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option { - Some(format!("{}'s screen", self.user.github_login).into()) + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + format!("{}'s screen", self.user.github_login).into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/workspace/src/theme_preview.rs b/crates/workspace/src/theme_preview.rs index 60ec195c40..8bdb4c614e 100644 --- a/crates/workspace/src/theme_preview.rs +++ b/crates/workspace/src/theme_preview.rs @@ -80,9 +80,9 @@ impl Item for ThemePreview { fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {} - fn tab_content_text(&self, window: &Window, cx: &App) -> Option { + fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString { let name = cx.theme().name.clone(); - Some(format!("{} Preview", name).into()) + format!("{} Preview", name).into() } fn telemetry_event_text(&self) -> Option<&'static str> { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3ecc847bed..52f0bf8748 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1460,6 +1460,27 @@ impl Workspace { &self.project } + pub fn recently_activated_items(&self, cx: &App) -> HashMap { + let mut history: HashMap = HashMap::default(); + + for pane_handle in &self.panes { + let pane = pane_handle.read(cx); + + for entry in pane.activation_history() { + history.insert( + entry.entity_id, + history + .get(&entry.entity_id) + .cloned() + .unwrap_or(0) + .max(entry.timestamp), + ); + } + } + + history + } + pub fn recent_navigation_history_iter( &self, cx: &App, @@ -2105,7 +2126,7 @@ impl Workspace { .flat_map(|pane| { pane.read(cx).items().filter_map(|item| { if item.is_dirty(cx) { - item.tab_description(0, cx); + item.tab_content_text(0, cx); Some((pane.downgrade(), item.boxed_clone())) } else { None @@ -9022,6 +9043,9 @@ mod tests { impl Item for TestPngItemView { type Event = (); + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "".into() + } } impl EventEmitter<()> for TestPngItemView {} impl Focusable for TestPngItemView { @@ -9094,6 +9118,9 @@ mod tests { impl Item for TestIpynbItemView { type Event = (); + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "".into() + } } impl EventEmitter<()> for TestIpynbItemView {} impl Focusable for TestIpynbItemView { @@ -9137,6 +9164,9 @@ mod tests { impl Item for TestAlternatePngItemView { type Event = (); + fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { + "".into() + } } impl EventEmitter<()> for TestAlternatePngItemView {}