use std::collections::HashMap;
use std::time::Instant;
use crate::layout::Layout;
use crate::pane::Pane;
use crate::project;
pub struct Tab {
pub name: String,
pub layout: Layout,
pub panes: HashMap<usize, Pane>,
pub active_pane: usize,
pub restart_policies: HashMap<usize, project::RestartPolicy>,
pub restart_state: HashMap<usize, (Instant, u32)>,
pub zoomed_pane: Option<usize>,
pub broadcast: bool,
}
impl Tab {
pub fn new(
name: String,
layout: Layout,
panes: HashMap<usize, Pane>,
active_pane: usize,
) -> Self {
Self {
name,
layout,
panes,
active_pane,
restart_policies: HashMap::new(),
restart_state: HashMap::new(),
zoomed_pane: None,
broadcast: false,
}
}
}
pub struct TabManager {
tabs: Vec<Tab>,
pub active_idx: usize,
pub count: usize,
next_id: usize,
}
impl TabManager {
pub fn new() -> Self {
Self {
tabs: Vec::new(),
active_idx: 0,
count: 1,
next_id: 2, }
}
pub fn from_tabs(mut tabs: Vec<Tab>, active_idx: usize) -> (Self, Tab) {
assert!(!tabs.is_empty());
let active_idx = active_idx.min(tabs.len() - 1);
let active_tab = tabs.remove(active_idx);
let count = tabs.len() + 1; let next_id = count + 1;
let mgr = Self {
tabs,
active_idx,
count,
next_id,
};
(mgr, active_tab)
}
pub fn switch_to(&mut self, target_idx: usize, current: Tab) -> Option<Tab> {
if target_idx == self.active_idx || target_idx >= self.count {
return None;
}
let insert_pos = self.active_idx.min(self.tabs.len());
self.tabs.insert(insert_pos, current);
let target_tab = self.tabs.remove(target_idx);
self.active_idx = target_idx;
Some(target_tab)
}
pub fn create_tab(&mut self, current: Tab) -> String {
let insert_pos = self.active_idx.min(self.tabs.len());
self.tabs.insert(insert_pos, current);
let name = format!("{}", self.next_id);
self.next_id += 1;
self.count += 1;
self.active_idx = self.count - 1;
name
}
pub fn close_active(&mut self) -> Option<Tab> {
if self.count <= 1 {
return None;
}
let old_active = self.active_idx;
self.count -= 1;
let new_active = if old_active >= self.count {
self.count - 1 } else {
old_active };
self.active_idx = new_active;
Some(self.tabs.remove(new_active))
}
pub fn next_idx(&self) -> usize {
if self.count <= 1 {
self.active_idx
} else {
(self.active_idx + 1) % self.count
}
}
pub fn prev_idx(&self) -> usize {
if self.count <= 1 {
self.active_idx
} else if self.active_idx == 0 {
self.count - 1
} else {
self.active_idx - 1
}
}
pub fn kill_all_inactive(&mut self) {
for tab in &mut self.tabs {
for pane in tab.panes.values_mut() {
pane.kill();
}
}
}
pub fn get_inactive(&self, logical_idx: usize) -> Option<&Tab> {
if logical_idx == self.active_idx || logical_idx >= self.count {
return None;
}
let storage_idx = if logical_idx < self.active_idx {
logical_idx
} else {
logical_idx - 1
};
self.tabs.get(storage_idx)
}
pub fn tab_names(&self, active_tab_name: &str) -> Vec<(usize, String, bool)> {
let mut result = Vec::with_capacity(self.count);
let mut storage_iter = self.tabs.iter();
for i in 0..self.count {
if i == self.active_idx {
result.push((i, active_tab_name.to_string(), true));
} else if let Some(tab) = storage_iter.next() {
result.push((i, tab.name.clone(), false));
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::Layout;
fn dummy_tab(name: &str) -> Tab {
Tab::new(name.to_string(), Layout::from_grid(1, 1), HashMap::new(), 0)
}
#[test]
fn new_tab_manager_has_one_tab() {
let mgr = TabManager::new();
assert_eq!(mgr.count, 1);
assert_eq!(mgr.active_idx, 0);
}
#[test]
fn create_tab_increments_count() {
let mut mgr = TabManager::new();
let name = mgr.create_tab(dummy_tab("1"));
assert_eq!(mgr.count, 2);
assert_eq!(mgr.active_idx, 1); assert_eq!(name, "2");
assert_eq!(mgr.tabs.len(), 1); }
#[test]
fn switch_to_from_first_tab() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("1"));
assert_eq!(mgr.active_idx, 1);
let tab = mgr.switch_to(0, dummy_tab("2")).unwrap();
assert_eq!(tab.name, "1");
assert_eq!(mgr.active_idx, 0);
}
#[test]
fn switch_to_from_zero() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A")); let tab_a = mgr.switch_to(0, dummy_tab("B")).unwrap(); assert_eq!(tab_a.name, "A");
assert_eq!(mgr.active_idx, 0);
let tab_b = mgr.switch_to(1, dummy_tab("A-restored")).unwrap();
assert_eq!(tab_b.name, "B");
assert_eq!(mgr.active_idx, 1);
}
#[test]
fn switch_three_tabs() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A")); mgr.create_tab(dummy_tab("B"));
let tab = mgr.switch_to(0, dummy_tab("C")).unwrap();
assert_eq!(tab.name, "A");
assert_eq!(mgr.active_idx, 0);
let tab = mgr.switch_to(2, dummy_tab("A")).unwrap();
assert_eq!(tab.name, "C");
assert_eq!(mgr.active_idx, 2);
let tab = mgr.switch_to(1, dummy_tab("C")).unwrap();
assert_eq!(tab.name, "B");
assert_eq!(mgr.active_idx, 1);
}
#[test]
fn close_last_tab_switches_to_prev() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A"));
let tab = mgr.close_active().unwrap();
assert_eq!(tab.name, "A");
assert_eq!(mgr.active_idx, 0);
assert_eq!(mgr.count, 1);
}
#[test]
fn close_first_tab_with_two() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A")); mgr.switch_to(0, dummy_tab("B"));
let tab = mgr.close_active().unwrap();
assert_eq!(mgr.count, 1);
assert_eq!(mgr.active_idx, 0);
assert_eq!(tab.name, "B");
}
#[test]
fn close_single_tab_returns_none() {
let mut mgr = TabManager::new();
assert!(mgr.close_active().is_none());
}
#[test]
fn tab_names_correct_order() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A")); mgr.create_tab(dummy_tab("B"));
let names = mgr.tab_names("C");
assert_eq!(names.len(), 3);
assert_eq!(names[0], (0, "A".to_string(), false));
assert_eq!(names[1], (1, "B".to_string(), false));
assert_eq!(names[2], (2, "C".to_string(), true));
}
#[test]
fn next_prev_wrap() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A"));
mgr.create_tab(dummy_tab("B"));
assert_eq!(mgr.next_idx(), 0); assert_eq!(mgr.prev_idx(), 1);
}
#[test]
fn switch_to_self_returns_none() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A"));
assert!(mgr.switch_to(mgr.active_idx, dummy_tab("X")).is_none());
}
#[test]
fn switch_to_out_of_bounds_returns_none() {
let mut mgr = TabManager::new();
assert!(mgr.switch_to(99, dummy_tab("X")).is_none());
}
#[test]
fn close_middle_tab_with_three() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("A")); mgr.create_tab(dummy_tab("B")); mgr.switch_to(1, dummy_tab("C"));
assert_eq!(mgr.active_idx, 1);
let new = mgr.close_active().unwrap();
assert_eq!(mgr.count, 2);
assert!(new.name == "C" || new.name == "B");
assert_eq!(mgr.active_idx, 1.min(mgr.count - 1));
}
#[test]
fn tab_names_active_at_zero() {
let mut mgr = TabManager::new();
mgr.create_tab(dummy_tab("B")); mgr.switch_to(0, dummy_tab("B-saved"));
let names = mgr.tab_names("active-0");
assert_eq!(names[0], (0, "active-0".to_string(), true));
assert_eq!(names[1], (1, "B-saved".to_string(), false));
}
#[test]
fn from_tabs_preserves_order() {
let tabs = vec![dummy_tab("A"), dummy_tab("B"), dummy_tab("C")];
let (mgr, active) = TabManager::from_tabs(tabs, 1); assert_eq!(active.name, "B");
assert_eq!(mgr.count, 3);
assert_eq!(mgr.active_idx, 1);
let names = mgr.tab_names("B");
assert_eq!(names[0], (0, "A".to_string(), false));
assert_eq!(names[1], (1, "B".to_string(), true));
assert_eq!(names[2], (2, "C".to_string(), false));
}
#[test]
fn from_tabs_active_at_zero() {
let tabs = vec![dummy_tab("X"), dummy_tab("Y")];
let (mgr, active) = TabManager::from_tabs(tabs, 0);
assert_eq!(active.name, "X");
assert_eq!(mgr.active_idx, 0);
let names = mgr.tab_names("X");
assert_eq!(names[0], (0, "X".to_string(), true));
assert_eq!(names[1], (1, "Y".to_string(), false));
}
#[test]
fn from_tabs_active_at_last() {
let tabs = vec![dummy_tab("A"), dummy_tab("B"), dummy_tab("C")];
let (mgr, active) = TabManager::from_tabs(tabs, 2);
assert_eq!(active.name, "C");
assert_eq!(mgr.active_idx, 2);
let names = mgr.tab_names("C");
assert_eq!(names[0], (0, "A".to_string(), false));
assert_eq!(names[1], (1, "B".to_string(), false));
assert_eq!(names[2], (2, "C".to_string(), true));
}
#[test]
fn from_tabs_single() {
let tabs = vec![dummy_tab("only")];
let (mgr, active) = TabManager::from_tabs(tabs, 0);
assert_eq!(active.name, "only");
assert_eq!(mgr.count, 1);
assert_eq!(mgr.active_idx, 0);
}
}