aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-08 14:53:45 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-08 14:53:45 +0300
commitc08e51e666f6df9a940393b568943b67940ceedc (patch)
tree6a6c4cd3c8ea5ddf9e97a4695c6b0d31eb0d0717 /src/app/input
parentb0aa7b2cddc57a55a1c0285a1ea82b59ac9783bd (diff)
feat: make agent toasts focus panes
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mouse.rs81
-rw-r--r--src/app/input/navigate.rs1
2 files changed, 82 insertions, 0 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index 185393f0..d0c33138 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -31,6 +31,19 @@ impl AppState {
return None;
}
+ if self.clickable_toast_at(mouse.column, mouse.row)
+ && matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left))
+ {
+ self.focus_toast_target();
+ return None;
+ }
+
+ if self.clickable_toast_at(mouse.column, mouse.row)
+ && matches!(mouse.kind, MouseEventKind::Up(MouseButton::Left))
+ {
+ return None;
+ }
+
if self.mode == Mode::Settings {
return self.handle_settings_mouse(mouse);
}
@@ -977,6 +990,34 @@ impl AppState {
}
}
+ fn clickable_toast_at(&self, col: u16, row: u16) -> bool {
+ self.toast
+ .as_ref()
+ .is_some_and(|toast| toast.target.is_some())
+ && rect_contains(self.view.toast_hit_area, col, row)
+ }
+
+ fn focus_toast_target(&mut self) {
+ let Some(target) = self.toast.as_ref().and_then(|toast| toast.target.clone()) else {
+ return;
+ };
+ let Some(ws_idx) = self
+ .workspaces
+ .iter()
+ .position(|workspace| workspace.id == target.workspace_id)
+ else {
+ return;
+ };
+ let Some(tab_idx) = self.workspaces[ws_idx].find_tab_index_for_pane(target.pane_id) else {
+ return;
+ };
+
+ self.switch_workspace(ws_idx);
+ self.switch_tab(tab_idx);
+ self.focus_pane(target.pane_id);
+ self.mode = Mode::Terminal;
+ }
+
pub(super) fn scroll_pane_up(&self, pane_id: crate::layout::PaneId, lines: usize) {
if let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) {
if let Some(rt) = ws.runtimes.get(&pane_id) {
@@ -1205,6 +1246,7 @@ mod tests {
use super::*;
use crate::{
app::state::{ContextMenuKind, ContextMenuState, MenuListState, Mode, ViewLayout},
+ detect::{Agent, AgentState},
workspace::Workspace,
};
@@ -1226,6 +1268,45 @@ mod tests {
}
#[test]
+ fn clicking_agent_toast_focuses_target_pane() {
+ let mut app = app_for_mouse_test();
+ let active = Workspace::test_new("active");
+ let mut background = Workspace::test_new("background");
+ let first_pane = background.tabs[0].root_pane;
+ let target_pane = background.test_split(Direction::Horizontal);
+ background.tabs[0].layout.focus_pane(first_pane);
+
+ app.state.workspaces = vec![active, background];
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ app.state.toast_config.delivery = crate::config::ToastDelivery::Herdr;
+ app.state.workspaces[1]
+ .panes
+ .get_mut(&target_pane)
+ .unwrap()
+ .state = AgentState::Working;
+
+ app.state
+ .handle_app_event(crate::events::AppEvent::StateChanged {
+ pane_id: target_pane,
+ agent: Some(Agent::Pi),
+ state: AgentState::Idle,
+ });
+ crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20));
+
+ let hit = app.state.view.toast_hit_area;
+ app.handle_mouse(mouse(
+ MouseEventKind::Down(MouseButton::Left),
+ hit.x + 1,
+ hit.y + 1,
+ ));
+
+ assert_eq!(app.state.active, Some(1));
+ assert_eq!(app.state.workspaces[1].focused_pane_id(), Some(target_pane));
+ assert_eq!(app.state.mode, Mode::Terminal);
+ }
+
+ #[test]
fn clicking_confirm_close_accepts_workspace_close() {
let mut app = app_for_mouse_test();
app.state.workspaces = vec![Workspace::test_new("a"), Workspace::test_new("b")];
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index 546228cc..3bc8da78 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -130,6 +130,7 @@ impl App {
kind: crate::app::state::ToastKind::NeedsAttention,
title: "custom command failed".to_string(),
context: err.to_string(),
+ target: None,
});
self.sync_toast_deadline(previous_toast);
}