From a05b662fd65c8ffcaf1f9716e4b5145efa636a06 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 10:40:24 -0400 Subject: [PATCH] Add tab group sleep command --- crates/ely_browser_core/src/state/commands.rs | 3 ++ .../ely_browser_core/src/state/tab_groups.rs | 17 +++++++ crates/ely_browser_core/tests/tab_groups.rs | 45 ++++++++++++++++++- docs/ui-shell.md | 17 +++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/crates/ely_browser_core/src/state/commands.rs b/crates/ely_browser_core/src/state/commands.rs index 0fb0128..71cc928 100644 --- a/crates/ely_browser_core/src/state/commands.rs +++ b/crates/ely_browser_core/src/state/commands.rs @@ -213,6 +213,9 @@ impl BrowserCore { Ok(self.set_active_tab_group_collapsed(false)?.is_some()) } "ungroup-tab" | "ungroup tab" => self.ungroup_active_tab(), + "sleep-tab-group" | "sleep tab group" | "discard-tab-group" | "discard tab group" => { + Ok(self.discard_active_tab_group()?.is_some()) + } "downloads" | "open-downloads" | "open downloads" => { self.open_tab(downloads_url()?); 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 60974af..f733c59 100644 --- a/crates/ely_browser_core/src/state/tab_groups.rs +++ b/crates/ely_browser_core/src/state/tab_groups.rs @@ -80,6 +80,19 @@ impl BrowserCore { Ok(true) } + pub fn discard_active_tab_group(&mut self) -> Result, CoreError> { + let Some(group_id) = self.active_tab_group_id()? else { + return Ok(None); + }; + let tab_ids = self.active_space_group_tab_ids(&group_id); + + for tab_id in &tab_ids { + self.discard_tab(tab_id)?; + } + + Ok(Some(tab_ids.len())) + } + pub fn split_active_tab_group(&mut self) -> Result, CoreError> { let Some(group_id) = self.active_tab_group_id()? else { return Ok(None); @@ -269,6 +282,10 @@ impl BrowserCore { ) } + fn active_space_group_tab_ids(&self, group_id: &TabGroupId) -> Vec { + self.active_space_group_tabs(group_id).iter().map(|tab| tab.id().clone()).collect() + } + fn split_pane_space_id( &self, split_id: &SplitId, diff --git a/crates/ely_browser_core/tests/tab_groups.rs b/crates/ely_browser_core/tests/tab_groups.rs index 07856e8..a7ebc9d 100644 --- a/crates/ely_browser_core/tests/tab_groups.rs +++ b/crates/ely_browser_core/tests/tab_groups.rs @@ -1,7 +1,7 @@ use std::error::Error; use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig}; -use ely_domain::{CommandIntent, MAX_SPLIT_PANES, SplitAxis, UrlText}; +use ely_domain::{CommandIntent, MAX_SPLIT_PANES, SplitAxis, TabState, UrlText}; #[test] fn group_active_tab_creates_visible_space_group() -> Result<(), Box> { @@ -167,6 +167,42 @@ fn auto_group_domains_preserves_existing_manual_groups() -> Result<(), Box Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let ungrouped_tab_id = core.open_tab(UrlText::parse("https://servo.org")?); + let first_group_tab_id = core.open_tab(UrlText::parse("https://example.com/a")?); + core.group_active_tab("Docs")?; + let second_group_tab_id = core.open_tab(UrlText::parse("https://example.com/b")?); + core.group_active_tab("Docs")?; + + core.set_command_query(">sleep-tab-group"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + + assert_eq!(intent, Some(CommandIntent::Command("sleep-tab-group".to_string()))); + assert_eq!(snapshot.command_query, ""); + assert_eq!(tab_state(&snapshot, &first_group_tab_id), Some(&TabState::Discarded)); + assert_eq!(tab_state(&snapshot, &second_group_tab_id), Some(&TabState::Discarded)); + assert_eq!(tab_state(&snapshot, &ungrouped_tab_id), Some(&TabState::Ready)); + Ok(()) +} + +#[test] +fn sleep_tab_group_command_preserves_query_without_active_group() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.open_tab(UrlText::parse("https://example.com")?); + + core.set_command_query(">sleep-tab-group"); + let intent = core.submit_command()?; + let snapshot = core.snapshot()?; + + assert_eq!(intent, Some(CommandIntent::Command("sleep-tab-group".to_string()))); + assert_eq!(snapshot.command_query, ">sleep-tab-group"); + assert_eq!(tab_state(&snapshot, &tab_id), Some(&TabState::Ready)); + Ok(()) +} + #[test] fn tab_group_collapse_commands_update_active_group() -> Result<(), Box> { let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; @@ -333,3 +369,10 @@ fn tab_group_id<'a>( ) -> Option<&'a ely_domain::TabGroupId> { snapshot.tabs.iter().find(|tab| tab.id() == tab_id).and_then(|tab| tab.group_id()) } + +fn tab_state<'a>( + snapshot: &'a ely_browser_core::BrowserSnapshot, + tab_id: &ely_domain::TabId, +) -> Option<&'a TabState> { + snapshot.tabs.iter().find(|tab| tab.id() == tab_id).map(|tab| tab.state()) +} diff --git a/docs/ui-shell.md b/docs/ui-shell.md index f17d938..143dae6 100644 --- a/docs/ui-shell.md +++ b/docs/ui-shell.md @@ -86,3 +86,20 @@ Domain auto grouping keeps manual groups intact and groups matching ungrouped ho │ servo.org │ │ └──────────────────────────────┴───────────────────────────────────────────────┘ ``` + +Group sleep applies the sleeping state to every tab in the active group: + +```text +┌──────────────────────────────────────────────────────────────────────────────┐ +│ ELY Browser [ >sleep-tab-group............................. ] [pin] [*] [+] │ +├──────────────────────────────┬───────────────────────────────────────────────┤ +│ Tabs │ Sleeping Tab │ +│ [folder] Docs │ Sleeping https://example.com/b │ +│ 2 tabs - Expanded │ │ +│ example.com │ URL https://example.com/b │ +│ example.com │ Title example.com │ +│ example.com │ Session Page session remains attached │ +│ example.com │ │ +│ servo.org │ [Restore] │ +└──────────────────────────────┴───────────────────────────────────────────────┘ +```