Skip to main content

taskers_control/
controller.rs

1use std::sync::{Arc, Mutex};
2
3use taskers_domain::{AppModel, DomainError, WindowId, WorkspaceId};
4
5use crate::protocol::{ControlCommand, ControlQuery, ControlResponse};
6
7#[derive(Debug, Clone)]
8pub struct InMemoryController {
9    state: Arc<Mutex<AppModel>>,
10}
11
12#[derive(Debug, Clone)]
13pub struct ControllerSnapshot {
14    pub model: AppModel,
15}
16
17impl InMemoryController {
18    pub fn new(state: AppModel) -> Self {
19        Self {
20            state: Arc::new(Mutex::new(state)),
21        }
22    }
23
24    pub fn snapshot(&self) -> ControllerSnapshot {
25        let state = self.state.lock().expect("state mutex poisoned").clone();
26        ControllerSnapshot { model: state }
27    }
28
29    pub fn handle(&self, command: ControlCommand) -> Result<ControlResponse, DomainError> {
30        let mut model = self.state.lock().expect("state mutex poisoned");
31
32        match command {
33            ControlCommand::CreateWorkspace { label } => {
34                let workspace_id = model.create_workspace(label);
35                Ok(ControlResponse::WorkspaceCreated { workspace_id })
36            }
37            ControlCommand::RenameWorkspace {
38                workspace_id,
39                label,
40            } => {
41                model.rename_workspace(workspace_id, label)?;
42                Ok(ControlResponse::Ack {
43                    message: "workspace renamed".into(),
44                })
45            }
46            ControlCommand::SwitchWorkspace {
47                window_id,
48                workspace_id,
49            } => {
50                let target_window = window_id.unwrap_or(model.active_window);
51                model.switch_workspace(target_window, workspace_id)?;
52                Ok(ControlResponse::Ack {
53                    message: "workspace switched".into(),
54                })
55            }
56            ControlCommand::SplitPane {
57                workspace_id,
58                pane_id,
59                axis,
60            } => {
61                let new_pane_id = model.split_pane(workspace_id, pane_id, axis)?;
62                Ok(ControlResponse::PaneSplit {
63                    pane_id: new_pane_id,
64                })
65            }
66            ControlCommand::CreateWorkspaceWindow {
67                workspace_id,
68                direction,
69            } => {
70                let new_pane_id = model.create_workspace_window(workspace_id, direction)?;
71                Ok(ControlResponse::WorkspaceWindowCreated {
72                    pane_id: new_pane_id,
73                })
74            }
75            ControlCommand::FocusWorkspaceWindow {
76                workspace_id,
77                workspace_window_id,
78            } => {
79                model.focus_workspace_window(workspace_id, workspace_window_id)?;
80                Ok(ControlResponse::Ack {
81                    message: "workspace window focused".into(),
82                })
83            }
84            ControlCommand::FocusPane {
85                workspace_id,
86                pane_id,
87            } => {
88                model.focus_pane(workspace_id, pane_id)?;
89                Ok(ControlResponse::Ack {
90                    message: "pane focused".into(),
91                })
92            }
93            ControlCommand::FocusPaneDirection {
94                workspace_id,
95                direction,
96            } => {
97                model.focus_pane_direction(workspace_id, direction)?;
98                Ok(ControlResponse::Ack {
99                    message: "pane focus moved".into(),
100                })
101            }
102            ControlCommand::ResizeActiveWindow {
103                workspace_id,
104                direction,
105                amount,
106            } => {
107                model.resize_active_window(workspace_id, direction, amount)?;
108                Ok(ControlResponse::Ack {
109                    message: "workspace window resized".into(),
110                })
111            }
112            ControlCommand::ResizeActivePaneSplit {
113                workspace_id,
114                direction,
115                amount,
116            } => {
117                model.resize_active_pane_split(workspace_id, direction, amount)?;
118                Ok(ControlResponse::Ack {
119                    message: "pane split resized".into(),
120                })
121            }
122            ControlCommand::SetWorkspaceWindowFrame {
123                workspace_id,
124                workspace_window_id,
125                frame,
126            } => {
127                model.set_workspace_window_frame(workspace_id, workspace_window_id, frame)?;
128                Ok(ControlResponse::Ack {
129                    message: "workspace window frame updated".into(),
130                })
131            }
132            ControlCommand::SetWindowSplitRatio {
133                workspace_id,
134                workspace_window_id,
135                path,
136                ratio,
137            } => {
138                model.set_window_split_ratio(workspace_id, workspace_window_id, &path, ratio)?;
139                Ok(ControlResponse::Ack {
140                    message: "window split ratio updated".into(),
141                })
142            }
143            ControlCommand::UpdatePaneMetadata { pane_id, patch } => {
144                model.update_pane_metadata(pane_id, patch)?;
145                Ok(ControlResponse::Ack {
146                    message: "pane metadata updated".into(),
147                })
148            }
149            ControlCommand::SetWorkspaceViewport {
150                workspace_id,
151                viewport,
152            } => {
153                model.set_workspace_viewport(workspace_id, viewport)?;
154                Ok(ControlResponse::Ack {
155                    message: "workspace viewport updated".into(),
156                })
157            }
158            ControlCommand::ClosePane {
159                workspace_id,
160                pane_id,
161            } => {
162                model.close_pane(workspace_id, pane_id)?;
163                Ok(ControlResponse::Ack {
164                    message: "pane closed".into(),
165                })
166            }
167            ControlCommand::CloseWorkspace { workspace_id } => {
168                model.close_workspace(workspace_id)?;
169                Ok(ControlResponse::Ack {
170                    message: "workspace closed".into(),
171                })
172            }
173            ControlCommand::EmitSignal {
174                workspace_id,
175                pane_id,
176                event,
177            } => {
178                model.apply_signal(workspace_id, pane_id, event)?;
179                Ok(ControlResponse::Ack {
180                    message: "signal applied".into(),
181                })
182            }
183            ControlCommand::QueryStatus { query } => match query {
184                ControlQuery::ActiveWindow | ControlQuery::All => Ok(ControlResponse::Status {
185                    session: model.snapshot(),
186                }),
187                ControlQuery::Window { window_id } => window_snapshot(&model, window_id),
188                ControlQuery::Workspace { workspace_id } => {
189                    workspace_snapshot(&model, workspace_id)
190                }
191            },
192        }
193    }
194}
195
196fn window_snapshot(model: &AppModel, window_id: WindowId) -> Result<ControlResponse, DomainError> {
197    let _ = model
198        .windows
199        .get(&window_id)
200        .ok_or(DomainError::MissingWindow(window_id))?;
201    Ok(ControlResponse::Status {
202        session: model.snapshot(),
203    })
204}
205
206fn workspace_snapshot(
207    model: &AppModel,
208    workspace_id: WorkspaceId,
209) -> Result<ControlResponse, DomainError> {
210    let _ = model
211        .workspaces
212        .get(&workspace_id)
213        .ok_or(DomainError::MissingWorkspace(workspace_id))?;
214    Ok(ControlResponse::WorkspaceState {
215        workspace_id,
216        session: model.snapshot(),
217    })
218}