use std::path::PathBuf; use crate::api::schema::{ EventData, EventEnvelope, EventKind, ResponseResult, WorkspaceCreateParams, WorkspaceRenameParams, WorkspaceTarget, }; use crate::app::App; use super::responses::{encode_error, encode_success}; impl App { pub(super) fn handle_workspace_list(&mut self, id: String) -> String { encode_success( id, ResponseResult::WorkspaceList { workspaces: self .state .workspaces .iter() .enumerate() .map(|(idx, _)| self.workspace_info(idx)) .collect(), }, ) } pub(super) fn handle_workspace_get(&mut self, id: String, target: WorkspaceTarget) -> String { let Some(index) = self.parse_workspace_id(&target.workspace_id) else { return workspace_not_found(id, &target.workspace_id); }; let Some(_) = self.state.workspaces.get(index) else { return workspace_not_found(id, &target.workspace_id); }; encode_success( id, ResponseResult::WorkspaceInfo { workspace: self.workspace_info(index), }, ) } pub(super) fn handle_workspace_create( &mut self, id: String, params: WorkspaceCreateParams, ) -> String { let cwd = params.cwd.map(PathBuf::from).unwrap_or_else(|| { let follow_cwd = self .workspace_creation_source() .and_then(|ws_idx| self.seed_cwd_from_workspace(ws_idx)); self.resolve_new_terminal_cwd(follow_cwd) }); match self.create_workspace_with_options(cwd, params.focus) { Ok(index) => { if let Some(label) = params.label { if let Some(workspace) = self.state.workspaces.get_mut(index) { workspace.set_custom_name(label); crate::logging::workspace_renamed(&workspace.id); } } let workspace = self.workspace_info(index); let tab = self .tab_info(index, 0) .expect("new workspace should have an initial tab"); let root_pane = self .root_pane_info(index, 0) .expect("new workspace should have an initial root pane"); self.emit_event(EventEnvelope { event: EventKind::WorkspaceCreated, data: EventData::WorkspaceCreated { workspace: workspace.clone(), }, }); self.emit_event(EventEnvelope { event: EventKind::TabCreated, data: EventData::TabCreated { tab: tab.clone() }, }); self.emit_event(EventEnvelope { event: EventKind::PaneCreated, data: EventData::PaneCreated { pane: root_pane.clone(), }, }); encode_success( id, self.workspace_created_result(index) .expect("new workspace should produce a complete create response"), ) } Err(err) => encode_error(id, "workspace_create_failed", err.to_string()), } } pub(super) fn handle_workspace_focus(&mut self, id: String, target: WorkspaceTarget) -> String { let Some(index) = self.parse_workspace_id(&target.workspace_id) else { return workspace_not_found(id, &target.workspace_id); }; if self.state.workspaces.get(index).is_none() { return workspace_not_found(id, &target.workspace_id); } self.state.switch_workspace(index); encode_success( id, ResponseResult::WorkspaceInfo { workspace: self.workspace_info(index), }, ) } pub(super) fn handle_workspace_rename( &mut self, id: String, params: WorkspaceRenameParams, ) -> String { let Some(index) = self.parse_workspace_id(¶ms.workspace_id) else { return workspace_not_found(id, ¶ms.workspace_id); }; let Some(ws) = self.state.workspaces.get_mut(index) else { return workspace_not_found(id, ¶ms.workspace_id); }; ws.set_custom_name(params.label.clone()); crate::logging::workspace_renamed(&ws.id); self.schedule_session_save(); self.emit_event(EventEnvelope { event: EventKind::WorkspaceRenamed, data: EventData::WorkspaceRenamed { workspace_id: self.public_workspace_id(index), label: params.label, }, }); encode_success( id, ResponseResult::WorkspaceInfo { workspace: self.workspace_info(index), }, ) } pub(super) fn handle_workspace_close(&mut self, id: String, target: WorkspaceTarget) -> String { let Some(index) = self.parse_workspace_id(&target.workspace_id) else { return workspace_not_found(id, &target.workspace_id); }; if self.state.workspaces.get(index).is_none() { return workspace_not_found(id, &target.workspace_id); } self.state.selected = index; self.state.close_selected_workspace(); self.shutdown_detached_terminal_runtimes(); self.emit_event(EventEnvelope { event: EventKind::WorkspaceClosed, data: EventData::WorkspaceClosed { workspace_id: target.workspace_id, }, }); encode_success(id, ResponseResult::Ok {}) } } fn workspace_not_found(id: String, workspace_id: &str) -> String { encode_error( id, "workspace_not_found", format!("workspace {workspace_id} not found"), ) } #[cfg(test)] mod tests { use super::*; use crate::{api::schema::SuccessResponse, config::Config, workspace::Workspace}; fn app_with_linked_worktree() -> App { let (_api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel(); let mut app = App::new( &Config::default(), true, None, api_rx, crate::api::EventHub::default(), ); app.state.workspaces = vec![Workspace::test_new("issue")]; app.state.workspaces[0].worktree_space = Some(crate::workspace::WorktreeSpaceMembership { key: "repo-key".into(), label: "herdr".into(), repo_root: "/repo/herdr".into(), checkout_path: "/repo/herdr-issue".into(), is_linked_worktree: true, }); app } #[test] fn api_workspace_close_closes_linked_worktree_workspace_only() { let mut app = app_with_linked_worktree(); let response = app.handle_workspace_close( "req".into(), WorkspaceTarget { workspace_id: app.state.workspaces[0].id.clone(), }, ); let success: SuccessResponse = serde_json::from_str(&response).unwrap(); assert_eq!(success.id, "req"); assert_eq!(app.state.request_remove_linked_worktree, None); assert!(app.state.workspaces.is_empty()); } }