1use super::PaneManager;
12use crate::config::Config;
13use crate::pane::tmux_helpers::{TmuxLayoutRebuildContext, extract_panes_from_node};
14use crate::pane::types::{Pane, PaneId, PaneNode, SplitDirection};
15use crate::tmux::{LayoutNode, TmuxLayout, TmuxPaneId};
16use anyhow::Result;
17use std::collections::HashMap;
18use std::sync::Arc;
19use tokio::runtime::Runtime;
20
21impl PaneManager {
22 pub fn set_from_tmux_layout(
36 &mut self,
37 layout: &TmuxLayout,
38 config: &Config,
39 runtime: Arc<Runtime>,
40 ) -> Result<HashMap<TmuxPaneId, PaneId>> {
41 let mut pane_mappings = HashMap::new();
42
43 let new_root =
45 self.convert_layout_node(&layout.root, config, runtime.clone(), &mut pane_mappings)?;
46
47 self.root = Some(new_root);
49
50 if let Some(first_native_id) = pane_mappings.values().next() {
52 self.focused_pane_id = Some(*first_native_id);
53 }
54
55 if let Some(max_id) = pane_mappings.values().max() {
57 self.next_pane_id = max_id + 1;
58 }
59
60 self.recalculate_bounds();
62
63 log::info!(
64 "Set pane tree from tmux layout: {} panes",
65 pane_mappings.len()
66 );
67
68 Ok(pane_mappings)
69 }
70
71 pub fn rebuild_from_tmux_layout(
87 &mut self,
88 layout: &TmuxLayout,
89 existing_mappings: &HashMap<TmuxPaneId, PaneId>,
90 new_tmux_panes: &[TmuxPaneId],
91 config: &Config,
92 runtime: Arc<Runtime>,
93 ) -> Result<HashMap<TmuxPaneId, PaneId>> {
94 let mut existing_panes: HashMap<PaneId, Pane> = HashMap::new();
96 if let Some(root) = self.root.take() {
97 extract_panes_from_node(root, &mut existing_panes);
98 }
99
100 log::debug!(
101 "Rebuilding layout: extracted {} existing panes, expecting {} new tmux panes",
102 existing_panes.len(),
103 new_tmux_panes.len()
104 );
105
106 let mut new_mappings = HashMap::new();
108 let mut rebuild_ctx = TmuxLayoutRebuildContext {
109 existing_mappings,
110 new_tmux_panes,
111 existing_panes: &mut existing_panes,
112 config,
113 runtime: runtime.clone(),
114 new_mappings: &mut new_mappings,
115 };
116 let new_root = self.rebuild_layout_node(&layout.root, &mut rebuild_ctx)?;
117
118 self.root = Some(new_root);
120
121 if self.focused_pane_id.is_none()
123 && let Some(first_native_id) = new_mappings.values().next()
124 {
125 self.focused_pane_id = Some(*first_native_id);
126 }
127
128 if let Some(max_id) = new_mappings.values().max()
130 && *max_id >= self.next_pane_id
131 {
132 self.next_pane_id = max_id + 1;
133 }
134
135 self.recalculate_bounds();
137
138 log::info!(
139 "Rebuilt pane tree from tmux layout: {} panes",
140 new_mappings.len()
141 );
142
143 Ok(new_mappings)
144 }
145
146 fn rebuild_layout_node(
148 &mut self,
149 node: &LayoutNode,
150 ctx: &mut TmuxLayoutRebuildContext<'_>,
151 ) -> Result<PaneNode> {
152 match node {
153 LayoutNode::Pane { id: tmux_id, .. } => {
154 if let Some(&native_id) = ctx.existing_mappings.get(tmux_id)
156 && let Some(pane) = ctx.existing_panes.remove(&native_id)
157 {
158 log::debug!(
159 "Reusing existing pane {} for tmux pane %{}",
160 native_id,
161 tmux_id
162 );
163 ctx.new_mappings.insert(*tmux_id, native_id);
164 return Ok(PaneNode::leaf(pane));
165 }
166
167 if ctx.new_tmux_panes.contains(tmux_id) {
169 let native_id = self.next_pane_id;
170 self.next_pane_id += 1;
171
172 let pane = Pane::new_for_tmux(native_id, ctx.config, ctx.runtime.clone())?;
173 log::debug!("Created new pane {} for tmux pane %{}", native_id, tmux_id);
174 ctx.new_mappings.insert(*tmux_id, native_id);
175 return Ok(PaneNode::leaf(pane));
176 }
177
178 log::warn!("Unexpected tmux pane %{} - creating new pane", tmux_id);
180 let native_id = self.next_pane_id;
181 self.next_pane_id += 1;
182 let pane = Pane::new_for_tmux(native_id, ctx.config, ctx.runtime.clone())?;
183 ctx.new_mappings.insert(*tmux_id, native_id);
184 Ok(PaneNode::leaf(pane))
185 }
186
187 LayoutNode::VerticalSplit {
188 width, children, ..
189 } => {
190 self.rebuild_multi_split_to_binary(children, SplitDirection::Vertical, *width, ctx)
192 }
193
194 LayoutNode::HorizontalSplit {
195 height, children, ..
196 } => {
197 self.rebuild_multi_split_to_binary(
199 children,
200 SplitDirection::Horizontal,
201 *height,
202 ctx,
203 )
204 }
205 }
206 }
207
208 fn rebuild_multi_split_to_binary(
210 &mut self,
211 children: &[LayoutNode],
212 direction: SplitDirection,
213 total_size: usize,
214 ctx: &mut TmuxLayoutRebuildContext<'_>,
215 ) -> Result<PaneNode> {
216 if children.is_empty() {
217 anyhow::bail!("Empty children list in tmux layout");
218 }
219
220 if children.len() == 1 {
221 return self.rebuild_layout_node(&children[0], ctx);
222 }
223
224 let first_size = Self::get_node_size(&children[0], direction);
226 let ratio = (first_size as f32) / (total_size as f32);
227
228 let first = self.rebuild_layout_node(&children[0], ctx)?;
230
231 let remaining_size = total_size.saturating_sub(first_size + 1);
233
234 let second = if children.len() == 2 {
236 self.rebuild_layout_node(&children[1], ctx)?
237 } else {
238 self.rebuild_remaining_children(&children[1..], direction, remaining_size, ctx)?
239 };
240
241 Ok(PaneNode::split(direction, ratio, first, second))
242 }
243
244 fn rebuild_remaining_children(
246 &mut self,
247 children: &[LayoutNode],
248 direction: SplitDirection,
249 total_size: usize,
250 ctx: &mut TmuxLayoutRebuildContext<'_>,
251 ) -> Result<PaneNode> {
252 if children.len() == 1 {
253 return self.rebuild_layout_node(&children[0], ctx);
254 }
255
256 let first_size = Self::get_node_size(&children[0], direction);
257 let ratio = (first_size as f32) / (total_size as f32);
258
259 let first = self.rebuild_layout_node(&children[0], ctx)?;
260
261 let remaining_size = total_size.saturating_sub(first_size + 1);
262 let second =
263 self.rebuild_remaining_children(&children[1..], direction, remaining_size, ctx)?;
264
265 Ok(PaneNode::split(direction, ratio, first, second))
266 }
267
268 pub(super) fn get_node_size(node: &LayoutNode, direction: SplitDirection) -> usize {
270 match node {
271 LayoutNode::Pane { width, height, .. } => match direction {
272 SplitDirection::Vertical => *width,
273 SplitDirection::Horizontal => *height,
274 },
275 LayoutNode::VerticalSplit { width, height, .. }
276 | LayoutNode::HorizontalSplit { width, height, .. } => match direction {
277 SplitDirection::Vertical => *width,
278 SplitDirection::Horizontal => *height,
279 },
280 }
281 }
282}