Skip to main content

par_term/pane/manager/
tmux_update.rs

1//! In-place tmux layout ratio and direction updates.
2//!
3//! Provides `update_layout_from_tmux`, `update_from_tmux_layout`, and their
4//! recursive helpers. These methods update the split ratios of an existing pane
5//! tree to match a new tmux layout without recreating terminal sessions.
6//!
7//! For full-replace and rebuild operations, see `tmux_layout.rs`.
8//! For creating new pane trees from tmux layouts, see `tmux_convert.rs`.
9
10use super::PaneManager;
11use crate::config::Config;
12use crate::pane::types::{PaneId, PaneNode, SplitDirection};
13use crate::tmux::{LayoutNode, TmuxLayout, TmuxPaneId};
14use anyhow::Result;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::runtime::Runtime;
18
19impl PaneManager {
20    /// Update the layout structure (ratios) from a tmux layout without recreating terminals
21    ///
22    /// This is called when the tmux pane IDs haven't changed but the layout
23    /// dimensions have (e.g., due to resize or another client connecting).
24    /// It updates the split ratios in our pane tree to match the tmux layout.
25    pub fn update_layout_from_tmux(
26        &mut self,
27        layout: &TmuxLayout,
28        pane_mappings: &HashMap<TmuxPaneId, PaneId>,
29    ) {
30        // Calculate ratios from the tmux layout and update our tree
31        if let Some(ref mut root) = self.root {
32            Self::update_node_from_tmux_layout(root, &layout.root, pane_mappings);
33        }
34
35        log::debug!(
36            "Updated pane layout ratios from tmux layout ({} panes)",
37            pane_mappings.len()
38        );
39    }
40
41    /// Recursively update a pane node's ratios and directions from tmux layout
42    fn update_node_from_tmux_layout(
43        node: &mut PaneNode,
44        tmux_node: &LayoutNode,
45        pane_mappings: &HashMap<TmuxPaneId, PaneId>,
46    ) {
47        match (node, tmux_node) {
48            // Leaf nodes - nothing to update for ratios
49            (PaneNode::Leaf(_), LayoutNode::Pane { .. }) => {}
50
51            // Split node with VerticalSplit layout (panes side by side)
52            (
53                PaneNode::Split {
54                    direction,
55                    ratio,
56                    first,
57                    second,
58                },
59                LayoutNode::VerticalSplit {
60                    width, children, ..
61                },
62            ) if !children.is_empty() => {
63                // Update direction to match tmux layout
64                if *direction != SplitDirection::Vertical {
65                    log::debug!(
66                        "Updating split direction from {:?} to Vertical to match tmux layout",
67                        direction
68                    );
69                    *direction = SplitDirection::Vertical;
70                }
71
72                // Calculate ratio from first child's width vs total
73                let first_size = Self::get_node_size(&children[0], SplitDirection::Vertical);
74                let total_size = *width;
75                if total_size > 0 {
76                    *ratio = (first_size as f32) / (total_size as f32);
77                    log::debug!(
78                        "Updated vertical split ratio: {} / {} = {}",
79                        first_size,
80                        total_size,
81                        *ratio
82                    );
83                }
84
85                // Recursively update first child
86                Self::update_node_from_tmux_layout(first, &children[0], pane_mappings);
87
88                // For the second child, handle multi-child case
89                if children.len() == 2 {
90                    Self::update_node_from_tmux_layout(second, &children[1], pane_mappings);
91                } else if children.len() > 2 {
92                    // Our tree is binary but tmux has N children
93                    // The second child is a nested split containing children[1..]
94                    // Recursively update with remaining children treated as a nested split
95                    Self::update_nested_split(
96                        second,
97                        &children[1..],
98                        SplitDirection::Vertical,
99                        pane_mappings,
100                    );
101                }
102            }
103
104            // Split node with HorizontalSplit layout (panes stacked)
105            (
106                PaneNode::Split {
107                    direction,
108                    ratio,
109                    first,
110                    second,
111                },
112                LayoutNode::HorizontalSplit {
113                    height, children, ..
114                },
115            ) if !children.is_empty() => {
116                // Update direction to match tmux layout
117                if *direction != SplitDirection::Horizontal {
118                    log::debug!(
119                        "Updating split direction from {:?} to Horizontal to match tmux layout",
120                        direction
121                    );
122                    *direction = SplitDirection::Horizontal;
123                }
124
125                // Calculate ratio from first child's height vs total
126                let first_size = Self::get_node_size(&children[0], SplitDirection::Horizontal);
127                let total_size = *height;
128                if total_size > 0 {
129                    *ratio = (first_size as f32) / (total_size as f32);
130                    log::debug!(
131                        "Updated horizontal split ratio: {} / {} = {}",
132                        first_size,
133                        total_size,
134                        *ratio
135                    );
136                }
137
138                // Recursively update first child
139                Self::update_node_from_tmux_layout(first, &children[0], pane_mappings);
140
141                // For the second child, handle multi-child case
142                if children.len() == 2 {
143                    Self::update_node_from_tmux_layout(second, &children[1], pane_mappings);
144                } else if children.len() > 2 {
145                    // Our tree is binary but tmux has N children
146                    Self::update_nested_split(
147                        second,
148                        &children[1..],
149                        SplitDirection::Horizontal,
150                        pane_mappings,
151                    );
152                }
153            }
154
155            // Mismatched structure - log and skip
156            _ => {
157                log::debug!("Layout structure mismatch during update - skipping ratio update");
158            }
159        }
160    }
161
162    /// Update a nested binary split from a flat list of tmux children
163    fn update_nested_split(
164        node: &mut PaneNode,
165        children: &[LayoutNode],
166        direction: SplitDirection,
167        pane_mappings: &HashMap<TmuxPaneId, PaneId>,
168    ) {
169        if children.is_empty() {
170            return;
171        }
172
173        if children.len() == 1 {
174            // Single child - update directly
175            Self::update_node_from_tmux_layout(node, &children[0], pane_mappings);
176            return;
177        }
178
179        // Multiple children - node should be a split
180        if let PaneNode::Split {
181            ratio,
182            first,
183            second,
184            ..
185        } = node
186        {
187            // Calculate ratio: first child size vs remaining total
188            let first_size = Self::get_node_size(&children[0], direction);
189            let remaining_size: usize = children
190                .iter()
191                .map(|c| Self::get_node_size(c, direction))
192                .sum();
193
194            if remaining_size > 0 {
195                *ratio = (first_size as f32) / (remaining_size as f32);
196                log::debug!(
197                    "Updated nested split ratio: {} / {} = {}",
198                    first_size,
199                    remaining_size,
200                    *ratio
201                );
202            }
203
204            // Update first child
205            Self::update_node_from_tmux_layout(first, &children[0], pane_mappings);
206
207            // Recurse for remaining children
208            Self::update_nested_split(second, &children[1..], direction, pane_mappings);
209        } else {
210            // Node isn't a split but we expected one - update as single
211            Self::update_node_from_tmux_layout(node, &children[0], pane_mappings);
212        }
213    }
214
215    /// Update an existing pane tree to match a new tmux layout
216    ///
217    /// This tries to preserve existing panes where possible and only
218    /// creates/destroys panes as needed.
219    ///
220    /// Returns updated mappings (Some = new mappings, None = no changes needed)
221    pub fn update_from_tmux_layout(
222        &mut self,
223        layout: &TmuxLayout,
224        existing_mappings: &HashMap<TmuxPaneId, PaneId>,
225        config: &Config,
226        runtime: Arc<Runtime>,
227    ) -> Result<Option<HashMap<TmuxPaneId, PaneId>>> {
228        // Get the pane IDs from the new layout
229        let new_pane_ids: std::collections::HashSet<_> = layout.pane_ids().into_iter().collect();
230
231        // Check if the pane set has changed
232        let existing_tmux_ids: std::collections::HashSet<_> =
233            existing_mappings.keys().copied().collect();
234
235        if new_pane_ids == existing_tmux_ids {
236            // Same panes, just need to update the layout structure
237            // For now, we rebuild completely since layout changes are complex
238            // A future optimization could preserve terminals and just restructure
239            log::debug!("tmux layout changed but same panes - rebuilding structure");
240        }
241
242        // For now, always rebuild the tree completely
243        // A more sophisticated implementation would try to preserve terminals
244        let new_mappings = self.set_from_tmux_layout(layout, config, runtime)?;
245        Ok(Some(new_mappings))
246    }
247}