use super::PaneManager;
use crate::config::Config;
use crate::pane::tmux_helpers::{TmuxLayoutRebuildContext, extract_panes_from_node};
use crate::pane::types::{Pane, PaneId, PaneNode, SplitDirection};
use crate::tmux::{LayoutNode, TmuxLayout, TmuxPaneId};
use anyhow::Result;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::runtime::Runtime;
impl PaneManager {
pub fn set_from_tmux_layout(
&mut self,
layout: &TmuxLayout,
config: &Config,
runtime: Arc<Runtime>,
) -> Result<HashMap<TmuxPaneId, PaneId>> {
let mut pane_mappings = HashMap::new();
let new_root =
self.convert_layout_node(&layout.root, config, runtime.clone(), &mut pane_mappings)?;
self.root = Some(new_root);
if let Some(first_native_id) = pane_mappings.values().next() {
self.focused_pane_id = Some(*first_native_id);
}
if let Some(max_id) = pane_mappings.values().max() {
self.next_pane_id = max_id + 1;
}
self.recalculate_bounds();
log::info!(
"Set pane tree from tmux layout: {} panes",
pane_mappings.len()
);
Ok(pane_mappings)
}
pub fn rebuild_from_tmux_layout(
&mut self,
layout: &TmuxLayout,
existing_mappings: &HashMap<TmuxPaneId, PaneId>,
new_tmux_panes: &[TmuxPaneId],
config: &Config,
runtime: Arc<Runtime>,
) -> Result<HashMap<TmuxPaneId, PaneId>> {
let mut existing_panes: HashMap<PaneId, Pane> = HashMap::new();
if let Some(root) = self.root.take() {
extract_panes_from_node(root, &mut existing_panes);
}
log::debug!(
"Rebuilding layout: extracted {} existing panes, expecting {} new tmux panes",
existing_panes.len(),
new_tmux_panes.len()
);
let mut new_mappings = HashMap::new();
let mut rebuild_ctx = TmuxLayoutRebuildContext {
existing_mappings,
new_tmux_panes,
existing_panes: &mut existing_panes,
config,
runtime: runtime.clone(),
new_mappings: &mut new_mappings,
};
let new_root = self.rebuild_layout_node(&layout.root, &mut rebuild_ctx)?;
self.root = Some(new_root);
if self.focused_pane_id.is_none()
&& let Some(first_native_id) = new_mappings.values().next()
{
self.focused_pane_id = Some(*first_native_id);
}
if let Some(max_id) = new_mappings.values().max()
&& *max_id >= self.next_pane_id
{
self.next_pane_id = max_id + 1;
}
self.recalculate_bounds();
log::info!(
"Rebuilt pane tree from tmux layout: {} panes",
new_mappings.len()
);
Ok(new_mappings)
}
fn rebuild_layout_node(
&mut self,
node: &LayoutNode,
ctx: &mut TmuxLayoutRebuildContext<'_>,
) -> Result<PaneNode> {
match node {
LayoutNode::Pane { id: tmux_id, .. } => {
if let Some(&native_id) = ctx.existing_mappings.get(tmux_id)
&& let Some(pane) = ctx.existing_panes.remove(&native_id)
{
log::debug!(
"Reusing existing pane {} for tmux pane %{}",
native_id,
tmux_id
);
ctx.new_mappings.insert(*tmux_id, native_id);
return Ok(PaneNode::leaf(pane));
}
if ctx.new_tmux_panes.contains(tmux_id) {
let native_id = self.next_pane_id;
self.next_pane_id += 1;
let pane = Pane::new_for_tmux(native_id, ctx.config, ctx.runtime.clone())?;
log::debug!("Created new pane {} for tmux pane %{}", native_id, tmux_id);
ctx.new_mappings.insert(*tmux_id, native_id);
return Ok(PaneNode::leaf(pane));
}
log::warn!("Unexpected tmux pane %{} - creating new pane", tmux_id);
let native_id = self.next_pane_id;
self.next_pane_id += 1;
let pane = Pane::new_for_tmux(native_id, ctx.config, ctx.runtime.clone())?;
ctx.new_mappings.insert(*tmux_id, native_id);
Ok(PaneNode::leaf(pane))
}
LayoutNode::VerticalSplit {
width, children, ..
} => {
self.rebuild_multi_split_to_binary(children, SplitDirection::Vertical, *width, ctx)
}
LayoutNode::HorizontalSplit {
height, children, ..
} => {
self.rebuild_multi_split_to_binary(
children,
SplitDirection::Horizontal,
*height,
ctx,
)
}
}
}
fn rebuild_multi_split_to_binary(
&mut self,
children: &[LayoutNode],
direction: SplitDirection,
total_size: usize,
ctx: &mut TmuxLayoutRebuildContext<'_>,
) -> Result<PaneNode> {
if children.is_empty() {
anyhow::bail!("Empty children list in tmux layout");
}
if children.len() == 1 {
return self.rebuild_layout_node(&children[0], ctx);
}
let first_size = Self::get_node_size(&children[0], direction);
let ratio = (first_size as f32) / (total_size as f32);
let first = self.rebuild_layout_node(&children[0], ctx)?;
let remaining_size = total_size.saturating_sub(first_size + 1);
let second = if children.len() == 2 {
self.rebuild_layout_node(&children[1], ctx)?
} else {
self.rebuild_remaining_children(&children[1..], direction, remaining_size, ctx)?
};
Ok(PaneNode::split(direction, ratio, first, second))
}
fn rebuild_remaining_children(
&mut self,
children: &[LayoutNode],
direction: SplitDirection,
total_size: usize,
ctx: &mut TmuxLayoutRebuildContext<'_>,
) -> Result<PaneNode> {
if children.len() == 1 {
return self.rebuild_layout_node(&children[0], ctx);
}
let first_size = Self::get_node_size(&children[0], direction);
let ratio = (first_size as f32) / (total_size as f32);
let first = self.rebuild_layout_node(&children[0], ctx)?;
let remaining_size = total_size.saturating_sub(first_size + 1);
let second =
self.rebuild_remaining_children(&children[1..], direction, remaining_size, ctx)?;
Ok(PaneNode::split(direction, ratio, first, second))
}
pub(super) fn get_node_size(node: &LayoutNode, direction: SplitDirection) -> usize {
match node {
LayoutNode::Pane { width, height, .. } => match direction {
SplitDirection::Vertical => *width,
SplitDirection::Horizontal => *height,
},
LayoutNode::VerticalSplit { width, height, .. }
| LayoutNode::HorizontalSplit { width, height, .. } => match direction {
SplitDirection::Vertical => *width,
SplitDirection::Horizontal => *height,
},
}
}
}