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}