1use agentmux_common::ipc::{ErrorCode, Event};
5
6use crate::state::State;
7
8
9pub(super) fn handle_set_focused_node(state: &mut State, tab_id: String, node_id: String) -> Vec<Event> {
15 let Some(tab) = state.tabs.get_mut(&tab_id) else {
16 let v = state.bump_version();
17 return vec![Event::Error {
18 code: ErrorCode::InvalidCommand,
19 message: format!("SetFocusedNode: unknown tab {}", tab_id),
20 fatal: false,
21 version: v,
22 }];
23 };
24 if tab.focused_node_id == node_id {
25 return Vec::new();
26 }
27 tab.focused_node_id = node_id.clone();
28 let v = state.bump_version();
29 vec![Event::FocusedNodeChanged {
30 tab_id,
31 node_id,
32 version: v,
33 }]
34}
35
36pub(super) fn handle_set_magnified_node(state: &mut State, tab_id: String, node_id: String) -> Vec<Event> {
40 let Some(tab) = state.tabs.get_mut(&tab_id) else {
41 let v = state.bump_version();
42 return vec![Event::Error {
43 code: ErrorCode::InvalidCommand,
44 message: format!("SetMagnifiedNode: unknown tab {}", tab_id),
45 fatal: false,
46 version: v,
47 }];
48 };
49 if tab.magnified_node_id == node_id {
50 return Vec::new();
51 }
52 tab.magnified_node_id = node_id.clone();
53 let v = state.bump_version();
54 vec![Event::MagnifiedNodeChanged {
55 tab_id,
56 node_id,
57 version: v,
58 }]
59}
60
61pub(super) fn handle_layout_clear(
62 state: &mut State,
63 tab_id: String,
64 correlation_id: String,
65) -> Vec<Event> {
66 let Some(tab) = state.tabs.get_mut(&tab_id) else {
67 let v = state.bump_version();
68 return vec![Event::Error {
69 code: ErrorCode::InvalidCommand,
70 message: format!("LayoutClear: unknown tab {}", tab_id),
71 fatal: false,
72 version: v,
73 }];
74 };
75 tab.rootnode = None;
76 tab.focused_node_id = String::new();
77 tab.magnified_node_id = String::new();
78 let v = state.bump_version();
79 vec![Event::LayoutCleared {
80 tab_id,
81 correlation_id,
82 version: v,
83 }]
84}
85
86pub(super) fn handle_layout_set_tree(
87 state: &mut State,
88 tab_id: String,
89 new_tree: Option<agentmux_common::LayoutNode>,
90 correlation_id: String,
91) -> Vec<Event> {
92 let Some(tab) = state.tabs.get_mut(&tab_id) else {
93 let v = state.bump_version();
94 return vec![Event::Error {
95 code: ErrorCode::InvalidCommand,
96 message: format!("LayoutSetTree: unknown tab {}", tab_id),
97 fatal: false,
98 version: v,
99 }];
100 };
101 tab.rootnode = new_tree.clone();
102 if new_tree.is_none() {
106 tab.focused_node_id = String::new();
107 tab.magnified_node_id = String::new();
108 }
109 let v = state.bump_version();
110 vec![Event::LayoutTreeReplaced {
111 tab_id,
112 new_tree,
113 correlation_id,
114 version: v,
115 }]
116}
117
118pub(super) fn handle_layout_insert_node(
119 state: &mut State,
120 tab_id: String,
121 node: agentmux_common::LayoutNode,
122 parent_id: Option<String>,
123 index: Option<usize>,
124 focus_after: bool,
125 magnify_after: bool,
126 correlation_id: String,
127) -> Vec<Event> {
128 let Some(tab) = state.tabs.get_mut(&tab_id) else {
129 let v = state.bump_version();
130 return vec![Event::Error {
131 code: ErrorCode::InvalidCommand,
132 message: format!("LayoutInsertNode: unknown tab {}", tab_id),
133 fatal: false,
134 version: v,
135 }];
136 };
137 let empty_tree = tab.rootnode.is_none();
150 if empty_tree {
151 if parent_id.is_some() || index.is_some() {
159 let v = state.bump_version();
160 return vec![Event::Error {
161 code: ErrorCode::InvalidCommand,
162 message: format!(
163 "LayoutInsertNode: empty tree cannot honour explicit parent_id={:?} / index={:?} (tab {})",
164 parent_id, index, tab_id
165 ),
166 fatal: false,
167 version: v,
168 }];
169 }
170 tab.rootnode = Some(node.clone());
171 } else if let Some(pid) = parent_id.as_deref() {
172 let root = tab.rootnode.as_mut().expect("non-empty checked above");
173 match crate::backend::layout::find_node_by_id_mut(root, pid) {
174 Some(parent_node) if parent_node.data.is_none() => {
175 let len = parent_node.children.len();
176 let target = index.map(|i| i.min(len)).unwrap_or(len);
177 parent_node.children.insert(target, node.clone());
178 }
179 Some(_) => {
180 let v = state.bump_version();
182 return vec![Event::Error {
183 code: ErrorCode::InvalidCommand,
184 message: format!(
185 "LayoutInsertNode: parent_id {:?} is a leaf node, cannot host children (tab {})",
186 pid, tab_id
187 ),
188 fatal: false,
189 version: v,
190 }];
191 }
192 None => {
193 let v = state.bump_version();
194 return vec![Event::Error {
195 code: ErrorCode::InvalidCommand,
196 message: format!(
197 "LayoutInsertNode: parent_id {:?} not found in tree (tab {})",
198 pid, tab_id
199 ),
200 fatal: false,
201 version: v,
202 }];
203 }
204 }
205 } else {
206 let root = tab.rootnode.as_mut().expect("non-empty checked above");
207 crate::backend::layout::insert_node(root, node.clone());
208 }
209 if focus_after || magnify_after {
221 tab.focused_node_id = node.id.clone();
222 }
223 if magnify_after {
224 tab.magnified_node_id = node.id.clone();
225 }
226 let v = state.bump_version();
227 vec![Event::LayoutNodeInserted {
228 tab_id,
229 node,
230 parent_id,
238 index,
239 correlation_id,
240 version: v,
241 }]
242}
243
244pub(super) fn handle_layout_delete_node(
245 state: &mut State,
246 tab_id: String,
247 node_id: String,
248 correlation_id: String,
249) -> Vec<Event> {
250 let Some(tab) = state.tabs.get_mut(&tab_id) else {
251 let v = state.bump_version();
252 return vec![Event::Error {
253 code: ErrorCode::InvalidCommand,
254 message: format!("LayoutDeleteNode: unknown tab {}", tab_id),
255 fatal: false,
256 version: v,
257 }];
258 };
259 let pre_focused = tab.focused_node_id.clone();
262 let pre_magnified = tab.magnified_node_id.clone();
263 let Some(root) = tab.rootnode.as_mut() else {
264 return Vec::new();
266 };
267 if root.id == node_id {
273 tab.rootnode = None;
274 } else if let Err(e) = crate::backend::layout::delete_node(root, &node_id) {
275 let v = state.bump_version();
276 return vec![Event::Error {
277 code: ErrorCode::InvalidCommand,
278 message: format!("LayoutDeleteNode: {} (tab {})", e, tab_id),
279 fatal: false,
280 version: v,
281 }];
282 }
283
284 let id_resolves = |id: &str| -> bool {
297 if id.is_empty() {
298 return true;
299 }
300 match tab.rootnode.as_ref() {
301 None => false,
302 Some(root) => crate::backend::layout::find_node_by_id(root, id).is_some(),
303 }
304 };
305 let was_focused = !pre_focused.is_empty() && !id_resolves(&pre_focused);
306 let was_magnified = !pre_magnified.is_empty() && !id_resolves(&pre_magnified);
307 if was_focused {
308 tab.focused_node_id = String::new();
309 }
310 if was_magnified {
311 tab.magnified_node_id = String::new();
312 }
313
314 let v = state.bump_version();
315 vec![Event::LayoutNodeDeleted {
316 tab_id,
317 node_id,
318 was_focused,
319 was_magnified,
320 correlation_id,
321 version: v,
322 }]
323}