1mod block;
35mod layout;
36mod lifecycle;
37mod snapshot;
38mod tab;
39#[cfg(test)]
40mod test_support;
41mod window;
42mod workspace;
43
44use agentmux_common::ipc::{Command, ErrorCode, Event};
45use crate::state::State;
46
47#[derive(Debug, Clone)]
49pub struct Ctx {
50 pub registered_pid: Option<u32>,
51}
52
53pub fn update(state: &mut State, cmd: Command, ctx: &Ctx) -> Vec<Event> {
54 match cmd {
55 Command::Register { kind, pid, version } => lifecycle::handle_register(state, kind, pid, version),
56 Command::Goodbye => lifecycle::handle_goodbye(state, ctx),
57 Command::Ping { nonce } => {
58 let v = state.bump_version();
59 vec![Event::Pong { nonce, version: v }]
60 }
61 Command::GetSrvSnapshot => snapshot::handle_get_srv_snapshot(state),
62 Command::GetEvents { .. } => Vec::new(), Command::CreateWorkspace { name } => workspace::handle_create_workspace(state, name),
64 Command::DeleteWorkspace { workspace_id, force } => {
65 workspace::handle_delete_workspace(state, workspace_id, force)
66 }
67 Command::CreateTab { workspace_id, name } => tab::handle_create_tab(state, workspace_id, name),
68 Command::DeleteTab { workspace_id, tab_id, force } => tab::handle_delete_tab(state, workspace_id, tab_id, force),
69 Command::SetActiveTab { workspace_id, tab_id } => {
70 tab::handle_set_active_tab(state, workspace_id, tab_id)
71 }
72 Command::ReorderTab {
73 workspace_id,
74 tab_id,
75 new_index,
76 } => tab::handle_reorder_tab(state, workspace_id, tab_id, new_index),
77 Command::CreateBlock { tab_id, meta } => block::handle_create_block(state, tab_id, meta),
78 Command::DeleteBlock { tab_id, block_id } => block::handle_delete_block(state, tab_id, block_id),
79 Command::SetFocusedNode { tab_id, node_id } => {
80 layout::handle_set_focused_node(state, tab_id, node_id)
81 }
82 Command::SetMagnifiedNode { tab_id, node_id } => {
83 layout::handle_set_magnified_node(state, tab_id, node_id)
84 }
85 Command::LayoutClear {
90 tab_id,
91 correlation_id,
92 } => layout::handle_layout_clear(state, tab_id, correlation_id),
93 Command::LayoutSetTree {
94 tab_id,
95 new_tree,
96 correlation_id,
97 slices,
98 } => layout::handle_layout_set_tree(state, tab_id, new_tree, correlation_id, slices),
99 Command::LayoutInsertNode {
100 tab_id,
101 node,
102 parent_id,
103 index,
104 focus_after,
105 magnify_after,
106 correlation_id,
107 } => layout::handle_layout_insert_node(
108 state,
109 tab_id,
110 node,
111 parent_id,
112 index,
113 focus_after,
114 magnify_after,
115 correlation_id,
116 ),
117 Command::LayoutDeleteNode {
118 tab_id,
119 node_id,
120 correlation_id,
121 } => layout::handle_layout_delete_node(state, tab_id, node_id, correlation_id),
122 Command::LayoutDeleteNodeByBlock {
125 tab_id,
126 block_id,
127 correlation_id,
128 } => layout::handle_layout_delete_node_by_block(state, tab_id, block_id, correlation_id),
129 Command::LayoutStackPush {
131 tab_id,
132 target_block_id,
133 block_id,
134 activate,
135 correlation_id,
136 } => layout::handle_layout_stack_push(state, tab_id, target_block_id, block_id, activate, correlation_id),
137 Command::LayoutStackActivate {
138 tab_id,
139 block_id,
140 correlation_id,
141 } => layout::handle_layout_stack_activate(state, tab_id, block_id, correlation_id),
142 Command::CreateBlockInStack {
143 tab_id,
144 target_block_id,
145 meta,
146 activate,
147 } => layout::handle_create_block_in_stack(state, tab_id, target_block_id, meta, activate),
148 Command::LayoutStackMove {
151 tab_id,
152 block_id,
153 target_block_id,
154 position,
155 activate,
156 correlation_id,
157 } => layout::handle_layout_stack_move(state, tab_id, block_id, target_block_id, position, activate, correlation_id),
158 Command::LayoutQueueBackendActions {
162 tab_id,
163 actions,
164 correlation_id,
165 } => layout::handle_layout_queue_backend_actions(state, tab_id, actions, correlation_id),
166 Command::LayoutMoveNode {
171 tab_id,
172 node_id,
173 new_parent_id,
174 index,
175 correlation_id,
176 } => layout::handle_layout_move_node(
177 state,
178 tab_id,
179 node_id,
180 new_parent_id,
181 index,
182 correlation_id,
183 ),
184 Command::LayoutSwapNodes {
185 tab_id,
186 node1_id,
187 node2_id,
188 correlation_id,
189 } => layout::handle_layout_swap_nodes(state, tab_id, node1_id, node2_id, correlation_id),
190 Command::LayoutResizeNodes {
191 tab_id,
192 ops,
193 correlation_id,
194 } => layout::handle_layout_resize_nodes(state, tab_id, ops, correlation_id),
195 Command::LayoutReplaceNode {
196 tab_id,
197 target_id,
198 new_node,
199 focus_after,
200 correlation_id,
201 } => layout::handle_layout_replace_node(
202 state,
203 tab_id,
204 target_id,
205 new_node,
206 focus_after,
207 correlation_id,
208 ),
209 Command::LayoutSplitHorizontal {
210 tab_id,
211 target_id,
212 new_node,
213 position,
214 focus_after,
215 correlation_id,
216 } => layout::handle_layout_split_horizontal(
217 state,
218 tab_id,
219 target_id,
220 new_node,
221 position,
222 focus_after,
223 correlation_id,
224 ),
225 Command::LayoutSplitVertical {
226 tab_id,
227 target_id,
228 new_node,
229 position,
230 focus_after,
231 correlation_id,
232 } => layout::handle_layout_split_vertical(
233 state,
234 tab_id,
235 target_id,
236 new_node,
237 position,
238 focus_after,
239 correlation_id,
240 ),
241 Command::LayoutInsertNodeAtIndex {
242 tab_id,
243 node,
244 index_arr,
245 focus_after,
246 magnify_after,
247 correlation_id,
248 } => layout::handle_layout_insert_node_at_index(
249 state,
250 tab_id,
251 node,
252 index_arr,
253 focus_after,
254 magnify_after,
255 correlation_id,
256 ),
257 Command::CreateWindow {
258 window_id,
259 workspace_id,
260 } => window::handle_create_window(state, window_id, workspace_id),
261 Command::CloseWindowInternal { window_id } => {
262 window::handle_close_window_internal(state, window_id)
263 }
264 Command::SwitchWorkspace {
265 window_id,
266 workspace_id,
267 } => window::handle_switch_workspace(state, window_id, workspace_id),
268 Command::ReorderTabsBulk {
269 workspace_id,
270 tab_ids,
271 } => tab::handle_reorder_tabs_bulk(state, workspace_id, tab_ids),
272 Command::RenameWorkspace { workspace_id, name } => {
273 workspace::handle_rename_workspace(state, workspace_id, name)
274 }
275 Command::RenameTab { tab_id, name } => tab::handle_rename_tab(state, tab_id, name),
276 Command::UpdateWorkspaceMeta {
277 workspace_id,
278 meta_patch,
279 } => workspace::handle_update_workspace_meta(state, workspace_id, meta_patch),
280 Command::UpdateTabMeta {
281 tab_id,
282 meta_patch,
283 } => tab::handle_update_tab_meta(state, tab_id, meta_patch),
284 Command::UpdateBlockMeta {
285 block_id,
286 meta_patch,
287 } => block::handle_update_block_meta(state, block_id, meta_patch),
288 Command::UpdateWindowMeta {
289 window_id,
290 meta_patch,
291 } => window::handle_update_window_meta(state, window_id, meta_patch),
292 Command::MoveTab {
293 tab_id,
294 src_workspace_id,
295 dst_workspace_id,
296 dst_index,
297 } => tab::handle_move_tab(state, tab_id, src_workspace_id, dst_workspace_id, dst_index),
298 Command::MoveBlock {
299 block_id,
300 src_tab_id,
301 dst_tab_id,
302 dst_index,
303 } => block::handle_move_block(state, block_id, src_tab_id, dst_tab_id, dst_index),
304 other => {
308 let v = state.bump_version();
309 vec![Event::Error {
310 code: ErrorCode::InvalidCommand,
311 message: format!("srv reducer does not accept: {:?}", other),
312 fatal: false,
313 version: v,
314 }]
315 }
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use crate::reducer::test_support::*;
323
324 #[test]
329 fn unaccepted_command_returns_invalid_command_error() {
330 let mut state = State::default();
331 let events = update(
332 &mut state,
333 Command::ReportWindowOpened {
334 label: "main".into(),
335 kind: agentmux_common::ipc::WindowKind::FullInstance,
336 parent_label: None,
337 },
338 &ctx(1),
339 );
340 assert!(matches!(
341 &events[0],
342 Event::Error {
343 code: ErrorCode::InvalidCommand,
344 fatal: false,
345 ..
346 }
347 ));
348 }
349}