aboutsummaryrefslogtreecommitdiff
path: root/src/app/terminal_targets.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:16 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-17 01:41:16 +0300
commit86adf88351e17c6a11cac253714b169eac0db233 (patch)
tree883d32b0126e0b69449e0e9c0a7017d279c23935 /src/app/terminal_targets.rs
parentd2aa8865911ded97a67054fb2392a1c12fae018c (diff)
feat: add terminal identity and agent cli facade
Diffstat (limited to 'src/app/terminal_targets.rs')
-rw-r--r--src/app/terminal_targets.rs165
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),
+ })
+ }
+}