use crate::app::window_state::WindowState;
use crate::tmux::{TmuxLayout, TmuxWindowId};
pub(super) type BoundsInfo = Option<(winit::dpi::PhysicalSize<u32>, f32, f32, f32, f32, f32, f32)>;
impl WindowState {
pub(super) fn request_pane_refresh(&self, pane_ids: &[crate::tmux::TmuxPaneId]) {
let refresh_cmd = "refresh-client\n";
if self.write_to_gateway(refresh_cmd) {
crate::debug_info!(
"TMUX",
"Requested client refresh for {} panes",
pane_ids.len()
);
}
}
pub(super) fn handle_tmux_layout_change(&mut self, window_id: TmuxWindowId, layout_str: &str) {
crate::debug_info!(
"TMUX",
"Layout changed for window @{}: {}",
window_id,
layout_str
);
let parsed_layout = match TmuxLayout::parse(layout_str) {
Some(layout) => layout,
None => {
crate::debug_error!(
"TMUX",
"Failed to parse layout string for window @{}: {}",
window_id,
layout_str
);
return;
}
};
let pane_ids = parsed_layout.pane_ids();
crate::debug_info!(
"TMUX",
"Parsed layout for window @{}: {} panes (IDs: {:?})",
window_id,
pane_ids.len(),
pane_ids
);
Self::log_layout_node(&parsed_layout.root, 0);
if !pane_ids.is_empty()
&& let Some(session) = &mut self.tmux_state.tmux_session
{
if session.focused_pane().is_none() {
session.set_focused_pane(Some(pane_ids[0]));
}
}
let tab_id = if let Some(id) = self.tmux_state.tmux_sync.get_tab(window_id) {
Some(id)
} else {
let mut found_tab_id = None;
for pane_id in &pane_ids {
for tab in self.tab_manager.tabs() {
if tab.tmux.tmux_pane_id == Some(*pane_id) {
found_tab_id = Some(tab.id);
crate::debug_info!(
"TMUX",
"Found existing tab {} with pane %{} for window @{}",
tab.id,
pane_id,
window_id
);
break;
}
}
if found_tab_id.is_some() {
break;
}
}
if let Some(tid) = found_tab_id {
self.tmux_state.tmux_sync.map_window(window_id, tid);
crate::debug_info!(
"TMUX",
"Created window mapping: @{} -> tab {}",
window_id,
tid
);
}
found_tab_id
};
let is_tmux_connected = self.is_tmux_connected();
let status_bar_height = crate::tmux_status_bar_ui::TmuxStatusBarUI::height(
&self.config.load(),
is_tmux_connected,
);
let custom_status_bar_height = self
.status_bar_ui
.height(&self.config.load(), self.is_fullscreen);
let bounds_info = self.renderer.as_ref().map(|r| {
let size = r.size();
let padding = r.window_padding();
let content_offset_y = r.content_offset_y();
let content_inset_right = r.content_inset_right();
let cell_width = r.cell_width();
let cell_height = r.cell_height();
let physical_status_bar_height =
(status_bar_height + custom_status_bar_height) * r.scale_factor();
(
size,
padding,
content_offset_y,
content_inset_right,
cell_width,
cell_height,
physical_status_bar_height,
)
});
if let Some(tab_id) = tab_id {
self.apply_layout_to_existing_tab(
tab_id,
window_id,
&parsed_layout,
&pane_ids,
bounds_info,
);
} else {
self.create_tab_for_layout(window_id, &parsed_layout, &pane_ids, bounds_info);
}
}
fn apply_layout_to_existing_tab(
&mut self,
tab_id: crate::tab::TabId,
window_id: TmuxWindowId,
parsed_layout: &TmuxLayout,
pane_ids: &[crate::tmux::TmuxPaneId],
bounds_info: BoundsInfo,
) {
crate::debug_info!(
"TMUX",
"Layout change for window @{} on tab {} - {} panes: {:?}",
window_id,
tab_id,
pane_ids.len(),
pane_ids
);
let Some(tab) = self.tab_manager.get_tab_mut(tab_id) else {
return;
};
tab.init_pane_manager();
if let Some((
size,
padding,
content_offset_y,
content_inset_right,
_cell_width,
_cell_height,
status_bar_height,
)) = bounds_info
&& let Some(pm) = tab.pane_manager_mut()
{
let effective_padding = if self.config.load().window.hide_window_padding_on_split {
0.0
} else {
padding
};
let content_width = size.width as f32 - effective_padding * 2.0 - content_inset_right;
let content_height =
size.height as f32 - content_offset_y - effective_padding - status_bar_height;
let bounds = crate::pane::PaneBounds::new(
effective_padding,
content_offset_y,
content_width,
content_height,
);
pm.set_bounds(bounds);
crate::debug_info!(
"TMUX",
"Set pane manager bounds: {}x{} at ({}, {})",
content_width,
content_height,
effective_padding,
content_offset_y
);
}
let existing_tmux_ids: std::collections::HashSet<_> = self
.tmux_state
.tmux_pane_to_native_pane
.keys()
.copied()
.collect();
let new_tmux_ids: std::collections::HashSet<_> = pane_ids.iter().copied().collect();
if existing_tmux_ids == new_tmux_ids && !existing_tmux_ids.is_empty() {
self.handle_same_pane_layout_update(tab_id, parsed_layout, bounds_info);
return;
}
let panes_to_keep: std::collections::HashSet<_> = existing_tmux_ids
.intersection(&new_tmux_ids)
.copied()
.collect();
let panes_to_remove: Vec<_> = existing_tmux_ids
.difference(&new_tmux_ids)
.copied()
.collect();
let panes_to_add: Vec<_> = new_tmux_ids
.difference(&existing_tmux_ids)
.copied()
.collect();
if !panes_to_keep.is_empty() && !panes_to_remove.is_empty() && panes_to_add.is_empty() {
self.handle_pane_removal(
tab_id,
parsed_layout,
&panes_to_keep,
&panes_to_remove,
bounds_info,
);
return;
}
if !panes_to_keep.is_empty() && !panes_to_add.is_empty() && panes_to_remove.is_empty() {
self.handle_pane_addition(
tab_id,
parsed_layout,
&panes_to_keep,
&panes_to_add,
bounds_info,
);
return;
}
self.handle_full_layout_recreation(tab_id, window_id, parsed_layout, pane_ids, bounds_info);
}
pub(super) fn log_layout_node(node: &crate::tmux::LayoutNode, depth: usize) {
let indent = " ".repeat(depth);
match node {
crate::tmux::LayoutNode::Pane {
id,
width,
height,
x,
y,
} => {
crate::debug_trace!(
"TMUX",
"{}Pane %{}: {}x{} at ({}, {})",
indent,
id,
width,
height,
x,
y
);
}
crate::tmux::LayoutNode::VerticalSplit {
width,
height,
x,
y,
children,
} => {
crate::debug_trace!(
"TMUX",
"{}VerticalSplit: {}x{} at ({}, {}) with {} children",
indent,
width,
height,
x,
y,
children.len()
);
for child in children {
Self::log_layout_node(child, depth + 1);
}
}
crate::tmux::LayoutNode::HorizontalSplit {
width,
height,
x,
y,
children,
} => {
crate::debug_trace!(
"TMUX",
"{}HorizontalSplit: {}x{} at ({}, {}) with {} children",
indent,
width,
height,
x,
y,
children.len()
);
for child in children {
Self::log_layout_node(child, depth + 1);
}
}
}
}
}