pub use par_term_config::snapshot_types::TabSnapshot;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
pub type ArrangementId = Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitorInfo {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default)]
pub index: usize,
#[serde(default)]
pub position: (i32, i32),
#[serde(default)]
pub size: (u32, u32),
#[serde(default = "default_scale_factor", skip_serializing_if = "is_one")]
pub scale_factor: f64,
}
fn default_scale_factor() -> f64 {
1.0
}
fn is_one(v: &f64) -> bool {
(*v - 1.0).abs() < f64::EPSILON
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowSnapshot {
pub monitor: MonitorInfo,
pub position_relative: (i32, i32),
pub size: (u32, u32),
pub tabs: Vec<TabSnapshot>,
#[serde(default)]
pub active_tab_index: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WindowArrangement {
pub id: ArrangementId,
pub name: String,
pub monitor_layout: Vec<MonitorInfo>,
pub windows: Vec<WindowSnapshot>,
#[serde(default)]
pub created_at: String,
#[serde(default)]
pub order: usize,
}
#[derive(Debug, Clone, Default)]
pub struct ArrangementManager {
arrangements: HashMap<ArrangementId, WindowArrangement>,
order: Vec<ArrangementId>,
}
impl ArrangementManager {
pub fn new() -> Self {
Self {
arrangements: HashMap::new(),
order: Vec::new(),
}
}
pub fn from_arrangements(arrangements: Vec<WindowArrangement>) -> Self {
let mut manager = Self::new();
for arrangement in arrangements {
manager.add(arrangement);
}
manager.sort_by_order();
manager
}
pub fn add(&mut self, arrangement: WindowArrangement) {
let id = arrangement.id;
if !self.order.contains(&id) {
self.order.push(id);
}
self.arrangements.insert(id, arrangement);
}
pub fn get(&self, id: &ArrangementId) -> Option<&WindowArrangement> {
self.arrangements.get(id)
}
pub fn get_mut(&mut self, id: &ArrangementId) -> Option<&mut WindowArrangement> {
self.arrangements.get_mut(id)
}
pub fn update(&mut self, arrangement: WindowArrangement) {
let id = arrangement.id;
if self.arrangements.contains_key(&id) {
self.arrangements.insert(id, arrangement);
}
}
pub fn remove(&mut self, id: &ArrangementId) -> Option<WindowArrangement> {
self.order.retain(|aid| aid != id);
self.arrangements.remove(id)
}
pub fn arrangements_ordered(&self) -> Vec<&WindowArrangement> {
self.order
.iter()
.filter_map(|id| self.arrangements.get(id))
.collect()
}
pub fn to_vec(&self) -> Vec<WindowArrangement> {
self.arrangements_ordered().into_iter().cloned().collect()
}
pub fn len(&self) -> usize {
self.arrangements.len()
}
pub fn is_empty(&self) -> bool {
self.arrangements.is_empty()
}
pub fn find_by_name(&self, name: &str) -> Option<&WindowArrangement> {
let lower = name.to_lowercase();
self.arrangements
.values()
.find(|a| a.name.to_lowercase() == lower)
}
pub fn move_up(&mut self, id: &ArrangementId) {
if let Some(pos) = self.order.iter().position(|aid| aid == id)
&& pos > 0
{
self.order.swap(pos, pos - 1);
self.update_orders();
}
}
pub fn move_down(&mut self, id: &ArrangementId) {
if let Some(pos) = self.order.iter().position(|aid| aid == id)
&& pos < self.order.len() - 1
{
self.order.swap(pos, pos + 1);
self.update_orders();
}
}
fn sort_by_order(&mut self) {
self.order.sort_by_key(|id| {
self.arrangements
.get(id)
.map(|a| a.order)
.unwrap_or(usize::MAX)
});
}
fn update_orders(&mut self) {
for (i, id) in self.order.iter().enumerate() {
if let Some(arrangement) = self.arrangements.get_mut(id) {
arrangement.order = i;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_arrangement(name: &str, order: usize) -> WindowArrangement {
WindowArrangement {
id: Uuid::new_v4(),
name: name.to_string(),
monitor_layout: Vec::new(),
windows: Vec::new(),
created_at: String::new(),
order,
}
}
#[test]
fn test_manager_basic_operations() {
let mut manager = ArrangementManager::new();
assert!(manager.is_empty());
let arr = make_arrangement("Test", 0);
let id = arr.id;
manager.add(arr);
assert_eq!(manager.len(), 1);
assert!(manager.get(&id).is_some());
assert_eq!(manager.get(&id).unwrap().name, "Test");
let removed = manager.remove(&id);
assert!(removed.is_some());
assert!(manager.is_empty());
}
#[test]
fn test_manager_ordering() {
let mut manager = ArrangementManager::new();
let a1 = make_arrangement("First", 0);
let a2 = make_arrangement("Second", 1);
let a3 = make_arrangement("Third", 2);
let id1 = a1.id;
let id2 = a2.id;
let id3 = a3.id;
manager.add(a1);
manager.add(a2);
manager.add(a3);
let ordered = manager.arrangements_ordered();
assert_eq!(ordered.len(), 3);
assert_eq!(ordered[0].id, id1);
assert_eq!(ordered[1].id, id2);
assert_eq!(ordered[2].id, id3);
manager.move_up(&id2);
let ordered = manager.arrangements_ordered();
assert_eq!(ordered[0].id, id2);
assert_eq!(ordered[1].id, id1);
manager.move_down(&id2);
let ordered = manager.arrangements_ordered();
assert_eq!(ordered[0].id, id1);
assert_eq!(ordered[1].id, id2);
}
#[test]
fn test_find_by_name() {
let mut manager = ArrangementManager::new();
manager.add(make_arrangement("Work Setup", 0));
manager.add(make_arrangement("Home Setup", 1));
assert!(manager.find_by_name("work setup").is_some());
assert!(manager.find_by_name("HOME SETUP").is_some());
assert!(manager.find_by_name("nonexistent").is_none());
}
#[test]
fn test_serialization() {
let arr = WindowArrangement {
id: Uuid::new_v4(),
name: "Test".to_string(),
monitor_layout: vec![MonitorInfo {
name: Some("DELL U2720Q".to_string()),
index: 0,
position: (0, 0),
size: (2560, 1440),
scale_factor: 1.0,
}],
windows: vec![WindowSnapshot {
monitor: MonitorInfo {
name: Some("DELL U2720Q".to_string()),
index: 0,
position: (0, 0),
size: (2560, 1440),
scale_factor: 1.0,
},
position_relative: (100, 200),
size: (800, 600),
tabs: vec![TabSnapshot {
cwd: Some("/home/user".to_string()),
title: "bash".to_string(),
custom_color: None,
user_title: None,
custom_icon: None,
}],
active_tab_index: 0,
}],
created_at: "2024-01-01T00:00:00Z".to_string(),
order: 0,
};
let yaml = serde_yaml_ng::to_string(&arr).unwrap();
let deserialized: WindowArrangement = serde_yaml_ng::from_str(&yaml).unwrap();
assert_eq!(deserialized.id, arr.id);
assert_eq!(deserialized.name, arr.name);
assert_eq!(deserialized.windows.len(), 1);
assert_eq!(deserialized.windows[0].tabs.len(), 1);
assert_eq!(
deserialized.windows[0].tabs[0].cwd,
Some("/home/user".to_string())
);
}
}