use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::layout::{Node, Removed, Tab, TabId};
use crate::session::PaneId;
#[derive(Serialize, Deserialize)]
struct LayoutDoc {
tabs: Vec<TabDoc>,
active: usize,
}
#[derive(Serialize, Deserialize)]
struct TabDoc {
title: Option<String>,
root: Node,
focused: PaneId,
zoomed: bool,
broadcast: bool,
}
pub fn encode(tabs: &[Tab], active: usize) -> Value {
let doc = LayoutDoc {
tabs: tabs
.iter()
.map(|t| TabDoc {
title: t.title.clone(),
root: t.root.clone(),
focused: t.focused,
zoomed: t.zoomed,
broadcast: t.broadcast,
})
.collect(),
active,
};
serde_json::to_value(doc).unwrap_or(Value::Null)
}
pub fn decode(layout: &Value, panes: &[PaneId], next_tab: &mut TabId) -> (Vec<Tab>, usize) {
let mut tabs = Vec::new();
let mut placed: Vec<PaneId> = Vec::new();
let mut active = 0;
if let Ok(doc) = serde_json::from_value::<LayoutDoc>(layout.clone()) {
active = doc.active;
for tab in doc.tabs {
let mut root = tab.root;
let mut alive = true;
for id in root.panes() {
if !panes.contains(&id) || placed.contains(&id) {
if let Removed::Empty = root.remove(id) {
alive = false;
break;
}
}
}
if !alive {
continue;
}
let ids = root.panes();
placed.extend(ids.iter().copied());
let focused = if ids.contains(&tab.focused) {
tab.focused
} else {
ids[0]
};
let mut t = Tab::new(*next_tab, focused);
*next_tab += 1;
t.root = root;
t.title = tab.title;
t.zoomed = tab.zoomed && ids.len() > 1;
t.broadcast = tab.broadcast && ids.len() > 1;
tabs.push(t);
}
}
for id in panes.iter().filter(|id| !placed.contains(id)) {
tabs.push(Tab::new(*next_tab, *id));
*next_tab += 1;
}
let active = active.min(tabs.len().saturating_sub(1));
(tabs, active)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::Axis;
#[test]
fn round_trips_and_prunes_missing_panes() {
let mut t1 = Tab::new(0, 1);
t1.root.split(1, Axis::Horizontal, 2, false);
t1.root.split(2, Axis::Vertical, 3, false);
t1.focused = 3;
t1.title = Some("dev".into());
let t2 = Tab::new(1, 4);
let saved = encode(&[t1, t2], 1);
let mut next = 10;
let (tabs, active) = decode(&saved, &[1, 2, 3, 4], &mut next);
assert_eq!(tabs.len(), 2);
assert_eq!(active, 1);
assert_eq!(tabs[0].root.panes(), vec![1, 2, 3]);
assert_eq!(tabs[0].focused, 3);
assert_eq!(tabs[0].title.as_deref(), Some("dev"));
assert_eq!((tabs[0].id, tabs[1].id, next), (10, 11, 12));
let (tabs, _) = decode(&saved, &[1, 2, 4, 9], &mut next);
assert_eq!(tabs[0].root.panes(), vec![1, 2]);
assert_eq!(tabs[0].focused, 1);
assert_eq!(tabs.last().unwrap().root.panes(), vec![9]);
let (tabs, active) = decode(&saved, &[1, 2, 3], &mut next);
assert_eq!(tabs.len(), 1);
assert_eq!(active, 0);
}
#[test]
fn no_layout_means_a_tab_per_pane() {
let mut next = 0;
let (tabs, active) = decode(&Value::Null, &[5, 6], &mut next);
assert_eq!(
tabs.iter().map(|t| t.focused).collect::<Vec<_>>(),
vec![5, 6]
);
assert_eq!(active, 0);
let (tabs, _) = decode(&Value::Null, &[], &mut next);
assert!(tabs.is_empty());
}
}