Archive closed browser tabs
This commit is contained in:
@@ -12,6 +12,9 @@ pub enum CoreError {
|
||||
#[error("favorite limit reached: {limit}")]
|
||||
FavoriteLimitReached { limit: usize },
|
||||
|
||||
#[error("browser state has no archived tabs")]
|
||||
NoArchivedTabs,
|
||||
|
||||
#[error("browser state has no active tab")]
|
||||
MissingActiveTab,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ely_domain::{
|
||||
BrowserTab, CommandIntent, CommandScope, DomainError, Profile, ProfileId, ProfileKind, Space,
|
||||
SpaceId, TabId, UrlText,
|
||||
ArchiveSource, ArchivedTab, BrowserTab, CommandIntent, CommandScope, DomainError, Profile,
|
||||
ProfileId, ProfileKind, Space, SpaceId, TabId, UrlText,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@@ -33,6 +33,7 @@ pub struct BrowserSnapshot {
|
||||
pub tabs: Vec<BrowserTab>,
|
||||
pub favorites: Vec<BrowserTab>,
|
||||
pub pinned_tabs: Vec<BrowserTab>,
|
||||
pub archived_tabs: Vec<ArchivedTab>,
|
||||
pub active_tab_id: TabId,
|
||||
pub active_space_name: String,
|
||||
pub active_profile_name: String,
|
||||
@@ -44,6 +45,7 @@ pub struct BrowserCore {
|
||||
spaces: Vec<Space>,
|
||||
profiles: Vec<Profile>,
|
||||
tabs: Vec<BrowserTab>,
|
||||
archived_tabs: Vec<ArchivedTab>,
|
||||
active_space_id: SpaceId,
|
||||
active_profile_id: ProfileId,
|
||||
active_tab_id: TabId,
|
||||
@@ -71,6 +73,7 @@ impl BrowserCore {
|
||||
spaces: vec![space],
|
||||
profiles: vec![profile],
|
||||
tabs: vec![tab],
|
||||
archived_tabs: Vec::new(),
|
||||
command_query: String::new(),
|
||||
new_tab_url,
|
||||
})
|
||||
@@ -102,7 +105,8 @@ impl BrowserCore {
|
||||
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
|
||||
let was_active = &self.active_tab_id == tab_id;
|
||||
|
||||
self.tabs.remove(close_index);
|
||||
let closed_tab = self.tabs.remove(close_index);
|
||||
self.archived_tabs.push(ArchivedTab::new(closed_tab, ArchiveSource::ManualClose));
|
||||
|
||||
if self.tabs.is_empty() {
|
||||
let tab = self.build_tab(self.new_tab_url.clone());
|
||||
@@ -121,6 +125,21 @@ impl BrowserCore {
|
||||
Ok(self.active_tab_id.clone())
|
||||
}
|
||||
|
||||
pub fn restore_last_archived_tab(&mut self) -> Result<TabId, CoreError> {
|
||||
let archived_tab = self.archived_tabs.pop().ok_or(CoreError::NoArchivedTabs)?;
|
||||
let tab = archived_tab.into_tab();
|
||||
let tab_id = tab.id().clone();
|
||||
let insert_index = self
|
||||
.tabs
|
||||
.iter()
|
||||
.position(|existing| existing.id() == &self.active_tab_id)
|
||||
.map_or(self.tabs.len(), |index| index + 1);
|
||||
|
||||
self.tabs.insert(insert_index, tab);
|
||||
self.select_tab(&tab_id)?;
|
||||
Ok(tab_id)
|
||||
}
|
||||
|
||||
pub fn select_tab(&mut self, tab_id: &TabId) -> Result<(), CoreError> {
|
||||
let tab = self
|
||||
.tabs
|
||||
@@ -221,6 +240,7 @@ impl BrowserCore {
|
||||
Ok(BrowserSnapshot {
|
||||
favorites: self.favorites(),
|
||||
pinned_tabs: self.pinned_tabs(),
|
||||
archived_tabs: self.archived_tabs.clone(),
|
||||
tabs: self.tabs.clone(),
|
||||
active_tab_id: self.active_tab_id.clone(),
|
||||
active_space_name: active_space.name().to_string(),
|
||||
@@ -263,6 +283,10 @@ impl BrowserCore {
|
||||
self.toggle_active_tab_pinned()?;
|
||||
Ok(true)
|
||||
}
|
||||
"restore-tab" | "reopen-tab" => {
|
||||
self.restore_last_archived_tab()?;
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,23 @@ fn close_tab_command_closes_active_tab() -> Result<(), Box<dyn Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_tab_command_reopens_last_archived_tab() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let closed_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||
core.close_active_tab()?;
|
||||
|
||||
core.set_command_query(">restore-tab");
|
||||
let intent = core.submit_command()?;
|
||||
let snapshot = core.snapshot()?;
|
||||
|
||||
assert_eq!(intent, Some(CommandIntent::Command("restore-tab".to_string())));
|
||||
assert_eq!(snapshot.active_tab_id, closed_tab_id);
|
||||
assert!(snapshot.archived_tabs.is_empty());
|
||||
assert_eq!(snapshot.command_query, "");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_preserves_query() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
|
||||
@@ -36,6 +36,8 @@ fn closes_active_tab_and_selects_next_neighbor() -> Result<(), Box<dyn Error>> {
|
||||
assert_eq!(active_tab_id, third_tab_id);
|
||||
assert_eq!(ordered_ids, vec![first_tab_id, active_tab_id.clone()]);
|
||||
assert_eq!(snapshot.active_tab_id, active_tab_id);
|
||||
assert_eq!(snapshot.archived_tabs.len(), 1);
|
||||
assert_eq!(snapshot.archived_tabs[0].tab().id(), &second_tab_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -49,11 +51,41 @@ fn closing_last_tab_replaces_it_with_new_tab() -> Result<(), Box<dyn Error>> {
|
||||
let snapshot = core.snapshot()?;
|
||||
assert_eq!(snapshot.tabs.len(), 1);
|
||||
assert_eq!(snapshot.active_tab_id, active_tab_id);
|
||||
assert_eq!(snapshot.archived_tabs.len(), 1);
|
||||
let replacement_tab = snapshot.tabs.first().ok_or(CoreError::MissingActiveTab)?;
|
||||
assert_eq!(replacement_tab.url().as_str(), "ely://new-tab");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restores_last_archived_tab() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
let closed_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
|
||||
core.close_active_tab()?;
|
||||
|
||||
let restored_tab_id = core.restore_last_archived_tab()?;
|
||||
let snapshot = core.snapshot()?;
|
||||
|
||||
assert_eq!(restored_tab_id, closed_tab_id);
|
||||
assert_eq!(snapshot.active_tab_id, closed_tab_id);
|
||||
assert!(snapshot.archived_tabs.is_empty());
|
||||
assert!(snapshot.tabs.iter().any(|tab| tab.id() == &closed_tab_id));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_without_archived_tabs_returns_error() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
|
||||
let error = match core.restore_last_archived_tab() {
|
||||
Err(error) => error,
|
||||
Ok(_) => return Err("restore should require an archived tab".into()),
|
||||
};
|
||||
|
||||
assert_eq!(error, CoreError::NoArchivedTabs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selects_next_tab_with_wraparound() -> Result<(), Box<dyn Error>> {
|
||||
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||
|
||||
Reference in New Issue
Block a user