From 5588237a1f498d18011f8b1a41e3e7acb6f7df41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 11:05:54 -0400 Subject: [PATCH] Add tab group rename command --- crates/ely_browser_core/src/navigation.rs | 7 +++ crates/ely_browser_core/src/state/commands.rs | 9 ++-- .../ely_browser_core/src/state/tab_groups.rs | 20 ++++++++ .../tests/tab_group_commands.rs | 47 +++++++++++++++++++ docs/ui-shell.md | 16 +++++++ 5 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 crates/ely_browser_core/tests/tab_group_commands.rs diff --git a/crates/ely_browser_core/src/navigation.rs b/crates/ely_browser_core/src/navigation.rs index 5687629..4141ab0 100644 --- a/crates/ely_browser_core/src/navigation.rs +++ b/crates/ely_browser_core/src/navigation.rs @@ -105,6 +105,13 @@ pub(crate) fn tab_group_name(command: &str) -> Option<&str> { command_argument(command, &["group-tab ", "group tab ", "tab-group ", "tab group "]) } +pub(crate) fn rename_tab_group_name(command: &str) -> Option<&str> { + command_argument( + command, + &["rename-tab-group ", "rename tab group ", "rename-group ", "rename group "], + ) +} + pub(crate) fn split_group_name(command: &str) -> Option<&str> { command_argument( command, diff --git a/crates/ely_browser_core/src/state/commands.rs b/crates/ely_browser_core/src/state/commands.rs index ba30ac1..801fcbd 100644 --- a/crates/ely_browser_core/src/state/commands.rs +++ b/crates/ely_browser_core/src/state/commands.rs @@ -10,9 +10,9 @@ use crate::{ about_url, archive_idle_days, archive_url, bookmarks_url, downloads_url, history_url, move_tab_space_name, new_private_profile_name, new_profile_name, new_space_name, note_body, notes_url, plugin_detail_url, plugins_url, reading_list_url, reading_progress_percent, - search_url, settings_page_url, settings_url, shortcut_settings_url, space_icon, - split_group_name, switch_profile_name, sync_status_url, tab_group_name, tab_note_body, - task_manager_url, + rename_tab_group_name, search_url, settings_page_url, settings_url, shortcut_settings_url, + space_icon, split_group_name, switch_profile_name, sync_status_url, tab_group_name, + tab_note_body, task_manager_url, }, }; @@ -146,6 +146,9 @@ impl BrowserCore { self.group_active_tab(name)?; return Ok(true); } + if let Some(name) = rename_tab_group_name(command) { + return Ok(self.rename_active_tab_group(name)?.is_some()); + } if let Some(body) = tab_note_body(command) { self.save_active_tab_note(body)?; return Ok(true); diff --git a/crates/ely_browser_core/src/state/tab_groups.rs b/crates/ely_browser_core/src/state/tab_groups.rs index c3f8ff5..5b0bedc 100644 --- a/crates/ely_browser_core/src/state/tab_groups.rs +++ b/crates/ely_browser_core/src/state/tab_groups.rs @@ -80,6 +80,26 @@ impl BrowserCore { Ok(true) } + pub fn rename_active_tab_group( + &mut self, + name: impl Into, + ) -> Result, CoreError> { + let Some(group_id) = self.active_tab_group_id()? else { + return Ok(None); + }; + self.rename_tab_group(&group_id, name)?; + Ok(Some(group_id)) + } + + pub fn rename_tab_group( + &mut self, + group_id: &TabGroupId, + name: impl Into, + ) -> Result<(), CoreError> { + let group = self.tab_group_mut(group_id)?; + group.rename(name).map_err(CoreError::from) + } + pub fn discard_active_tab_group(&mut self) -> Result, CoreError> { let Some(group_id) = self.active_tab_group_id()? else { return Ok(None); diff --git a/crates/ely_browser_core/tests/tab_group_commands.rs b/crates/ely_browser_core/tests/tab_group_commands.rs new file mode 100644 index 0000000..c4af996 --- /dev/null +++ b/crates/ely_browser_core/tests/tab_group_commands.rs @@ -0,0 +1,47 @@ +use std::error::Error; + +use ely_browser_core::{BrowserCore, InitialBrowserConfig}; +use ely_domain::{CommandIntent, TabGroupId, UrlText}; + +#[test] +fn rename_tab_group_command_updates_active_group_name() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let first_tab_id = core.open_tab(UrlText::parse("https://example.com/a")?); + core.group_active_tab("Docs")?; + let second_tab_id = core.open_tab(UrlText::parse("https://example.com/b")?); + core.group_active_tab("Docs")?; + + core.set_command_query(">rename-tab-group Research"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + let group = snapshot.tab_groups.first().ok_or("missing tab group")?; + + assert_eq!(intent, Some(CommandIntent::Command("rename-tab-group Research".to_string()))); + assert_eq!(snapshot.command_query, ""); + assert_eq!(snapshot.tab_groups.len(), 1); + assert_eq!(group.name(), "Research"); + assert_eq!(tab_group_id(&snapshot, &first_tab_id), Some(group.id())); + assert_eq!(tab_group_id(&snapshot, &second_tab_id), Some(group.id())); + Ok(()) +} + +#[test] +fn rename_tab_group_command_preserves_query_without_active_group() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + + core.set_command_query(">rename-tab-group Research"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + + assert_eq!(intent, Some(CommandIntent::Command("rename-tab-group Research".to_string()))); + assert_eq!(snapshot.command_query, ">rename-tab-group Research"); + assert!(snapshot.tab_groups.is_empty()); + Ok(()) +} + +fn tab_group_id<'a>( + snapshot: &'a ely_browser_core::BrowserSnapshot, + tab_id: &ely_domain::TabId, +) -> Option<&'a TabGroupId> { + snapshot.tabs.iter().find(|tab| tab.id() == tab_id).and_then(|tab| tab.group_id()) +} diff --git a/docs/ui-shell.md b/docs/ui-shell.md index c19c95a..9c04e5a 100644 --- a/docs/ui-shell.md +++ b/docs/ui-shell.md @@ -87,6 +87,22 @@ Domain auto grouping keeps manual groups intact and groups matching ungrouped ho └──────────────────────────────┴───────────────────────────────────────────────┘ ``` +Group rename updates the active group row in place: + +```text +┌──────────────────────────────────────────────────────────────────────────────┐ +│ ELY Browser [ >rename-tab-group Research................. ] [pin] [*] [+] │ +├──────────────────────────────┬───────────────────────────────────────────────┤ +│ Tabs │ example.com │ +│ [folder] Research │ https://example.com/b │ +│ 2 tabs - Expanded │ │ +│ example.com │ Group Research │ +│ example.com │ │ +│ example.com │ │ +│ example.com │ │ +└──────────────────────────────┴───────────────────────────────────────────────┘ +``` + Group sleep applies the sleeping state to every tab in the active group: ```text