diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
| commit | 86adf88351e17c6a11cac253714b169eac0db233 (patch) | |
| tree | 883d32b0126e0b69449e0e9c0a7017d279c23935 /src/app/terminal_targets.rs | |
| parent | d2aa8865911ded97a67054fb2392a1c12fae018c (diff) | |
feat: add terminal identity and agent cli facade
Diffstat (limited to 'src/app/terminal_targets.rs')
| -rw-r--r-- | src/app/terminal_targets.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/app/terminal_targets.rs b/src/app/terminal_targets.rs new file mode 100644 index 00000000..cf90717d --- /dev/null +++ b/src/app/terminal_targets.rs @@ -0,0 +1,165 @@ +use super::{api_helpers::pane_agent_status, App}; + +// Staged for #00f: the agent CLI will use this resolver once #00e provides terminal ids. +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct TerminalTarget { + pub ws_idx: usize, + pub tab_idx: usize, + pub pane_id: crate::layout::PaneId, + pub terminal_id: String, +} + +// Staged for #00f: ambiguity details are produced before the agent facade consumes them. +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct TerminalTargetCandidate { + pub terminal_id: String, + pub pane_id: String, + pub workspace_id: String, + pub tab_id: String, + pub cwd: Option<String>, + pub agent_status: crate::api::schema::AgentStatus, +} + +// Staged for #00f: target errors are specified now and surfaced by agent commands next. +#[allow(dead_code)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum TerminalTargetError { + NotFound { + target: String, + }, + Ambiguous { + target: String, + candidates: Vec<TerminalTargetCandidate>, + }, +} + +impl App { + // Staged for #00f: current pane APIs stay pane-targeted while agent APIs will call this. + #[allow(dead_code)] + pub(crate) fn resolve_terminal_target( + &self, + target: &str, + ) -> Result<TerminalTarget, TerminalTargetError> { + let terminal_matches: Vec<_> = self + .terminal_targets() + .into_iter() + .filter(|candidate| candidate.terminal_id == target) + .collect(); + if let Some(resolved) = self.single_terminal_match(target, terminal_matches)? { + return Ok(resolved); + } + + if let Some((ws_idx, pane_id)) = self.parse_pane_id(target) { + if let Some(resolved) = self.terminal_target_for_pane(ws_idx, pane_id) { + return Ok(resolved); + } + } + + let name_matches: Vec<_> = self + .terminal_targets() + .into_iter() + .filter(|candidate| { + self.state.workspaces[candidate.ws_idx] + .pane_state(candidate.pane_id) + .and_then(|pane| pane.manual_label.as_deref()) + .is_some_and(|label| label == target) + }) + .collect(); + if let Some(resolved) = self.single_terminal_match(target, name_matches)? { + return Ok(resolved); + } + + Err(TerminalTargetError::NotFound { + target: target.to_string(), + }) + } + + // Staged for #00f with resolve_terminal_target. + #[allow(dead_code)] + fn single_terminal_match( + &self, + target: &str, + matches: Vec<TerminalTarget>, + ) -> Result<Option<TerminalTarget>, TerminalTargetError> { + match matches.len() { + 0 => Ok(None), + 1 => Ok(matches.into_iter().next()), + _ => Err(TerminalTargetError::Ambiguous { + target: target.to_string(), + candidates: matches + .into_iter() + .filter_map(|candidate| { + self.terminal_target_candidate(candidate.ws_idx, candidate.pane_id) + }) + .collect(), + }), + } + } + + // Staged for #00f with resolve_terminal_target. + #[allow(dead_code)] + fn terminal_targets(&self) -> Vec<TerminalTarget> { + self.state + .workspaces + .iter() + .enumerate() + .flat_map(|(ws_idx, ws)| { + ws.tabs.iter().enumerate().flat_map(move |(tab_idx, tab)| { + tab.layout + .pane_ids() + .into_iter() + .filter_map(move |pane_id| { + tab.terminal_id(pane_id).map(|terminal_id| TerminalTarget { + ws_idx, + tab_idx, + pane_id, + terminal_id: terminal_id.to_string(), + }) + }) + }) + }) + .collect() + } + + // Staged for #00f with resolve_terminal_target. + #[allow(dead_code)] + fn terminal_target_for_pane( + &self, + ws_idx: usize, + pane_id: crate::layout::PaneId, + ) -> Option<TerminalTarget> { + let ws = self.state.workspaces.get(ws_idx)?; + let tab_idx = ws.find_tab_index_for_pane(pane_id)?; + let terminal_id = ws.terminal_id(pane_id)?.to_string(); + Some(TerminalTarget { + ws_idx, + tab_idx, + pane_id, + terminal_id, + }) + } + + // Staged for #00f with resolve_terminal_target. + #[allow(dead_code)] + fn terminal_target_candidate( + &self, + ws_idx: usize, + pane_id: crate::layout::PaneId, + ) -> Option<TerminalTargetCandidate> { + let ws = self.state.workspaces.get(ws_idx)?; + let tab_idx = ws.find_tab_index_for_pane(pane_id)?; + let pane = ws.pane_state(pane_id)?; + Some(TerminalTargetCandidate { + terminal_id: ws.terminal_id(pane_id)?.to_string(), + pane_id: self.public_pane_id(ws_idx, pane_id)?, + workspace_id: self.public_workspace_id(ws_idx), + tab_id: self.public_tab_id(ws_idx, tab_idx)?, + cwd: ws.tabs[tab_idx] + .cwd_for_pane(pane_id) + .map(|cwd| cwd.display().to_string()), + agent_status: pane_agent_status(pane.state, pane.seen), + }) + } +} |
