diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-21 20:19:54 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-21 20:22:57 +0300 |
| commit | 866aaf740e10fcfce169aaf81380ea0257fbe272 (patch) | |
| tree | f815392b7c6b24a7c0de14e655bd635656383813 /src/app/api/tabs.rs | |
| parent | ca579c402d5668560ca13059f82c03178be18b2a (diff) | |
refactor: separate server client boundaries
Diffstat (limited to 'src/app/api/tabs.rs')
| -rw-r--r-- | src/app/api/tabs.rs | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/app/api/tabs.rs b/src/app/api/tabs.rs new file mode 100644 index 00000000..7978a17f --- /dev/null +++ b/src/app/api/tabs.rs @@ -0,0 +1,233 @@ +use std::path::PathBuf; + +use crate::api::schema::{ + EventData, EventEnvelope, EventKind, ResponseResult, TabCreateParams, TabListParams, + TabRenameParams, TabTarget, +}; +use crate::app::{App, Mode}; + +use super::responses::{encode_error, encode_success}; + +impl App { + pub(super) fn handle_tab_list(&mut self, id: String, params: TabListParams) -> String { + let tabs = if let Some(workspace_id) = params.workspace_id { + let Some(ws_idx) = self.parse_workspace_id(&workspace_id) else { + return workspace_not_found(id, &workspace_id); + }; + let Some(ws) = self.state.workspaces.get(ws_idx) else { + return workspace_not_found(id, &workspace_id); + }; + (0..ws.tabs.len()) + .filter_map(|tab_idx| self.tab_info(ws_idx, tab_idx)) + .collect() + } else { + let mut tabs = Vec::new(); + for (ws_idx, ws) in self.state.workspaces.iter().enumerate() { + for tab_idx in 0..ws.tabs.len() { + if let Some(tab) = self.tab_info(ws_idx, tab_idx) { + tabs.push(tab); + } + } + } + tabs + }; + + encode_success(id, ResponseResult::TabList { tabs }) + } + + pub(super) fn handle_tab_get(&mut self, id: String, target: TabTarget) -> String { + let Some((ws_idx, tab_idx)) = self.parse_tab_id(&target.tab_id) else { + return tab_not_found(id, &target.tab_id); + }; + let Some(tab) = self.tab_info(ws_idx, tab_idx) else { + return tab_not_found(id, &target.tab_id); + }; + + encode_success(id, ResponseResult::TabInfo { tab }) + } + + pub(super) fn handle_tab_create(&mut self, id: String, params: TabCreateParams) -> String { + let TabCreateParams { + workspace_id, + cwd, + focus, + label, + } = params; + let ws_idx = if let Some(workspace_id) = workspace_id { + let Some(ws_idx) = self.parse_workspace_id(&workspace_id) else { + return workspace_not_found(id, &workspace_id); + }; + ws_idx + } else if let Some(active) = self.state.active { + active + } else { + return encode_error(id, "workspace_not_found", "no active workspace"); + }; + let cwd = cwd + .map(PathBuf::from) + .or_else(|| { + self.state + .focused_runtime_in_workspace(&self.terminal_runtimes, ws_idx) + .and_then(|rt| rt.cwd()) + }) + .or_else(|| std::env::current_dir().ok()) + .unwrap_or_else(|| PathBuf::from("/")); + let (rows, cols) = self.state.estimate_pane_size(); + let default_shell = self.state.default_shell.clone(); + let scrollback_limit_bytes = self.state.pane_scrollback_limit_bytes; + let host_terminal_theme = self.state.host_terminal_theme; + let result = self + .state + .workspaces + .get_mut(ws_idx) + .ok_or_else(|| std::io::Error::other("workspace disappeared")) + .and_then(|ws| { + ws.create_tab( + rows, + cols, + cwd, + scrollback_limit_bytes, + host_terminal_theme, + &default_shell, + ) + }); + match result { + Ok((tab_idx, terminal, runtime)) => { + self.terminal_runtimes.insert(terminal.id.clone(), runtime); + self.state.terminals.insert(terminal.id.clone(), terminal); + if let Some(label) = label { + let workspace_id = self.state.workspaces[ws_idx].id.clone(); + let tab_id = self + .public_tab_id(ws_idx, tab_idx) + .unwrap_or_else(|| format!("{}:{}", workspace_id, tab_idx + 1)); + if let Some(tab) = self + .state + .workspaces + .get_mut(ws_idx) + .and_then(|ws| ws.tabs.get_mut(tab_idx)) + { + tab.set_custom_name(label); + crate::logging::tab_renamed(&workspace_id, &tab_id); + } + } + if focus { + self.state.switch_workspace(ws_idx); + self.state.switch_tab(tab_idx); + self.state.mode = Mode::Terminal; + } + self.schedule_session_save(); + let tab = self.tab_info(ws_idx, tab_idx).unwrap(); + let root_pane = self + .root_pane_info(ws_idx, tab_idx) + .expect("new tab should have a root pane"); + 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.tab_created_result(ws_idx, tab_idx) + .expect("new tab should produce a complete create response"), + ) + } + Err(err) => encode_error(id, "tab_create_failed", err.to_string()), + } + } + + pub(super) fn handle_tab_focus(&mut self, id: String, target: TabTarget) -> String { + let Some((ws_idx, tab_idx)) = self.parse_tab_id(&target.tab_id) else { + return tab_not_found(id, &target.tab_id); + }; + self.state.switch_workspace(ws_idx); + self.state.switch_tab(tab_idx); + let tab = self.tab_info(ws_idx, tab_idx).unwrap(); + + encode_success(id, ResponseResult::TabInfo { tab }) + } + + pub(super) fn handle_tab_rename(&mut self, id: String, params: TabRenameParams) -> String { + let Some((ws_idx, tab_idx)) = self.parse_tab_id(¶ms.tab_id) else { + return tab_not_found(id, ¶ms.tab_id); + }; + let workspace_id = self.state.workspaces[ws_idx].id.clone(); + let tab_id = self + .public_tab_id(ws_idx, tab_idx) + .unwrap_or_else(|| format!("{}:{}", workspace_id, tab_idx + 1)); + let Some(tab) = self + .state + .workspaces + .get_mut(ws_idx) + .and_then(|ws| ws.tabs.get_mut(tab_idx)) + else { + return tab_not_found(id, ¶ms.tab_id); + }; + tab.set_custom_name(params.label.clone()); + crate::logging::tab_renamed(&workspace_id, &tab_id); + self.schedule_session_save(); + self.emit_event(EventEnvelope { + event: EventKind::TabRenamed, + data: EventData::TabRenamed { + tab_id: self.public_tab_id(ws_idx, tab_idx).unwrap(), + workspace_id: self.public_workspace_id(ws_idx), + label: params.label, + }, + }); + let tab = self.tab_info(ws_idx, tab_idx).unwrap(); + + encode_success(id, ResponseResult::TabInfo { tab }) + } + + pub(super) fn handle_tab_close(&mut self, id: String, target: TabTarget) -> String { + let Some((ws_idx, tab_idx)) = self.parse_tab_id(&target.tab_id) else { + return tab_not_found(id, &target.tab_id); + }; + let terminal_ids = self.state.terminal_ids_for_tab(ws_idx, tab_idx); + let Some(ws) = self.state.workspaces.get_mut(ws_idx) else { + return tab_not_found(id, &target.tab_id); + }; + if ws.tabs.len() <= 1 { + return encode_error( + id, + "tab_close_failed", + "cannot close the last tab in a workspace", + ); + } + if !ws.close_tab(tab_idx) { + return encode_error( + id, + "tab_close_failed", + format!("tab {} could not be closed", target.tab_id), + ); + } + self.state.remove_unattached_terminal_ids(terminal_ids); + self.shutdown_detached_terminal_runtimes(); + self.schedule_session_save(); + self.emit_event(EventEnvelope { + event: EventKind::TabClosed, + data: EventData::TabClosed { + tab_id: target.tab_id, + workspace_id: self.public_workspace_id(ws_idx), + }, + }); + + 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"), + ) +} + +fn tab_not_found(id: String, tab_id: &str) -> String { + encode_error(id, "tab_not_found", format!("tab {tab_id} not found")) +} |
