1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Flow control and miscellaneous tmux notification handlers.
//!
//! Covers pane focus changes, error reporting, pause/continue flow control,
//! and the (currently dead-code) sync-action dispatcher.
use crate::app::window_state::WindowState;
use crate::tmux::SyncAction;
impl WindowState {
/// Handle pane focus changed notification from external tmux
pub(super) fn handle_tmux_pane_focus_changed(&mut self, tmux_pane_id: crate::tmux::TmuxPaneId) {
crate::debug_info!("TMUX", "Pane focus changed to %{}", tmux_pane_id);
// Update the tmux session's focused pane
if let Some(session) = &mut self.tmux_state.tmux_session {
session.set_focused_pane(Some(tmux_pane_id));
}
// Update the native pane focus to match
if let Some(native_pane_id) = self.tmux_state.tmux_pane_to_native_pane.get(&tmux_pane_id) {
// Find the tab containing this pane and update its focus
if let Some(tab) = self.tab_manager.active_tab_mut()
&& let Some(pm) = tab.pane_manager_mut()
{
pm.focus_pane(*native_pane_id);
crate::debug_info!(
"TMUX",
"Updated native pane focus: tmux %{} -> native {}",
tmux_pane_id,
native_pane_id
);
}
}
}
/// Handle error notification
pub(super) fn handle_tmux_error(&mut self, msg: &str) {
crate::debug_error!("TMUX", "Error from tmux: {}", msg);
// Show notification to user
self.deliver_notification("tmux Error", msg);
}
/// Handle pause notification (for slow connections)
pub(super) fn handle_tmux_pause(&mut self) {
crate::debug_info!("TMUX", "Received pause notification - buffering output");
// Set paused state in sync manager
self.tmux_state.tmux_sync.pause();
// Show toast notification to user
self.show_toast("tmux: Output paused (slow connection)");
}
/// Handle continue notification (resume after pause)
pub(super) fn handle_tmux_continue(&mut self) {
crate::debug_info!("TMUX", "Received continue notification - resuming output");
// Get and flush buffered output
let buffered = self.tmux_state.tmux_sync.resume();
// Flush buffered data to each pane
for (tmux_pane_id, data) in buffered {
if !data.is_empty() {
crate::debug_info!(
"TMUX",
"Flushing {} buffered bytes to pane %{}",
data.len(),
tmux_pane_id
);
// Find the native pane and send the buffered data
if let Some(native_pane_id) =
self.tmux_state.tmux_sync.get_native_pane(tmux_pane_id)
{
// Find the pane across all tabs
for tab in self.tab_manager.tabs_mut() {
if let Some(pane_manager) = tab.pane_manager_mut()
&& let Some(pane) = pane_manager.get_pane_mut(native_pane_id)
{
// try_lock: intentional — flushing buffered output in the sync
// event loop. On miss: this buffered chunk is lost. Low risk:
// the tmux %continue state means output has resumed and fresh
// data will arrive shortly to fill in any gap.
if let Ok(term) = pane.terminal.try_write() {
term.process_data(&data);
}
break;
}
}
}
}
}
// Show toast notification to user
self.show_toast("tmux: Output resumed");
}
/// Process sync actions generated by `TmuxSync::process_notifications`.
///
/// Called from `check_tmux_notifications` (polling.rs) once per group of notifications
/// (session → layout → output → other) to drive UI state mutations with pre-translated
/// native IDs.
///
/// Returns `true` if any action requires a redraw.
pub(super) fn process_sync_actions(&mut self, actions: Vec<SyncAction>) -> bool {
let mut needs_redraw = false;
for action in actions {
match action {
SyncAction::CreateTab { window_id } => {
crate::debug_info!("TMUX", "Sync: Create tab for window @{}", window_id);
self.handle_tmux_window_add(window_id);
needs_redraw = true;
}
SyncAction::CloseTab { tab_id } => {
crate::debug_info!("TMUX", "Sync: Close tab {}", tab_id);
// Note: TmuxSync already called unmap_window when producing this action,
// so we only need to close the tab and handle the last-tab case.
let was_last = self.tab_manager.close_tab(tab_id);
if was_last {
self.handle_tmux_session_ended();
}
needs_redraw = true;
}
SyncAction::RenameTab { tab_id, name } => {
crate::debug_info!("TMUX", "Sync: Rename tab {} to '{}'", tab_id, name);
if let Some(tab) = self.tab_manager.get_tab_mut(tab_id) {
tab.set_title(&name);
needs_redraw = true;
}
}
SyncAction::UpdateLayout { tab_id, layout } => {
crate::debug_info!("TMUX", "Sync: Update layout for tab {}", tab_id);
// Reverse-lookup the tmux window ID so handle_tmux_layout_change can use
// it for mapping and pane-tree reconciliation.
if let Some(window_id) = self.tmux_state.tmux_sync.get_window(tab_id) {
self.handle_tmux_layout_change(window_id, &layout);
needs_redraw = true;
}
}
SyncAction::PaneOutput { pane_id, data } => {
crate::debug_trace!(
"TMUX",
"Sync: Route {} bytes to pane {}",
data.len(),
pane_id
);
// pane_id is already a native PaneId (translated by TmuxSync).
// Route directly to the pane's terminal across all tabs.
// try_lock: intentional — called from the sync event loop. On miss: this
// chunk of pane output is dropped. Acceptable; tmux backpressure via
// %pause/%continue handles flow control and recovery.
for tab in self.tab_manager.tabs_mut() {
if let Some(pane_manager) = tab.pane_manager_mut()
&& let Some(pane) = pane_manager.get_pane_mut(pane_id)
&& let Ok(term) = pane.terminal.try_write()
{
term.process_data(&data);
break;
}
}
}
SyncAction::SessionEnded => {
crate::debug_info!("TMUX", "Sync: Session ended");
self.handle_tmux_session_ended();
needs_redraw = true;
}
SyncAction::Pause => {
crate::debug_info!("TMUX", "Sync: Pause");
self.handle_tmux_pause();
}
SyncAction::Continue => {
crate::debug_info!("TMUX", "Sync: Continue");
self.handle_tmux_continue();
needs_redraw = true;
}
}
}
needs_redraw
}
}