Close current browser tab

This commit is contained in:
2026-05-07 18:58:49 -04:00
parent e9ef92c41b
commit 510af7a9b2
3 changed files with 124 additions and 19 deletions
+83 -9
View File
@@ -42,18 +42,20 @@ pub struct BrowserCore {
active_profile_id: ProfileId,
active_tab_id: TabId,
command_query: String,
new_tab_url: UrlText,
}
impl BrowserCore {
pub fn new(config: InitialBrowserConfig) -> Result<Self, CoreError> {
let space = Space::new(config.space_name, config.space_icon, 0xf54e00);
let profile = Profile::new(config.profile_name, 0x26251e, ProfileKind::Standard);
let new_tab_url = config.initial_url;
let tab = BrowserTab::new(
TabId::new(),
space.id().clone(),
profile.id().clone(),
"New Tab",
config.initial_url,
new_tab_url.clone(),
);
Ok(Self {
@@ -64,18 +66,12 @@ impl BrowserCore {
profiles: vec![profile],
tabs: vec![tab],
command_query: String::new(),
new_tab_url,
})
}
pub fn open_tab(&mut self, url: UrlText) -> TabId {
let title = tab_title(&url);
let tab = BrowserTab::new(
TabId::new(),
self.active_space_id.clone(),
self.active_profile_id.clone(),
title,
url,
);
let tab = self.build_tab(url);
let tab_id = tab.id().clone();
let insert_index = self
.tabs
@@ -87,6 +83,38 @@ impl BrowserCore {
tab_id
}
pub fn close_active_tab(&mut self) -> Result<TabId, CoreError> {
let tab_id = self.active_tab_id.clone();
self.close_tab(&tab_id)
}
pub fn close_tab(&mut self, tab_id: &TabId) -> Result<TabId, CoreError> {
let close_index = self
.tabs
.iter()
.position(|tab| tab.id() == tab_id)
.ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?;
let was_active = &self.active_tab_id == tab_id;
self.tabs.remove(close_index);
if self.tabs.is_empty() {
let tab = self.build_tab(self.new_tab_url.clone());
let replacement_id = tab.id().clone();
self.tabs.push(tab);
self.active_tab_id = replacement_id.clone();
return Ok(replacement_id);
}
if was_active {
let next_index = close_index.min(self.tabs.len() - 1);
let next_tab_id = self.tabs[next_index].id().clone();
self.select_tab(&next_tab_id)?;
}
Ok(self.active_tab_id.clone())
}
pub fn select_tab(&mut self, tab_id: &TabId) -> Result<(), CoreError> {
let tab = self
.tabs
@@ -146,6 +174,17 @@ impl BrowserCore {
.find(|tab| tab.id() == &self.active_tab_id)
.ok_or(CoreError::MissingActiveTab)
}
fn build_tab(&self, url: UrlText) -> BrowserTab {
let title = tab_title(&url);
BrowserTab::new(
TabId::new(),
self.active_space_id.clone(),
self.active_profile_id.clone(),
title,
url,
)
}
}
fn tab_title(url: &UrlText) -> String {
@@ -163,6 +202,7 @@ mod tests {
use ely_domain::UrlText;
use super::{BrowserCore, InitialBrowserConfig};
use crate::CoreError;
#[test]
fn opens_new_tab_below_active_tab() -> Result<(), Box<dyn Error>> {
@@ -180,4 +220,38 @@ mod tests {
assert_eq!(snapshot.active_tab_id, third_tab_id);
Ok(())
}
#[test]
fn closes_active_tab_and_selects_next_neighbor() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let first_tab_id = core.active_tab()?.id().clone();
let second_tab_id = core.open_tab(UrlText::parse("https://example.com")?);
let third_tab_id = core.open_tab(UrlText::parse("https://servo.org")?);
core.select_tab(&second_tab_id)?;
let active_tab_id = core.close_active_tab()?;
let snapshot = core.snapshot()?;
let ordered_ids = snapshot.tabs.iter().map(|tab| tab.id().clone()).collect::<Vec<_>>();
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);
Ok(())
}
#[test]
fn closing_last_tab_replaces_it_with_new_tab() -> Result<(), Box<dyn Error>> {
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
let closed_tab_id = core.active_tab()?.id().clone();
let active_tab_id = core.close_tab(&closed_tab_id)?;
let snapshot = core.snapshot()?;
assert_eq!(snapshot.tabs.len(), 1);
assert_eq!(snapshot.active_tab_id, active_tab_id);
let replacement_tab = snapshot.tabs.first().ok_or(CoreError::MissingActiveTab)?;
assert_eq!(replacement_tab.url().as_str(), "ely://new-tab");
Ok(())
}
}