diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-06 19:24:33 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-06 19:24:33 +0300 |
| commit | cbe5c150baa147649912bb1bfad40522dc3c25f3 (patch) | |
| tree | 44622605ee8972bf8cf27f2ae784a122712b00f9 /src/app | |
| parent | 62b1f5e011c46403c3b78f661a3e5a385d2ec72c (diff) | |
feat: add all-workspaces scope to agent panel
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/input.rs | 109 | ||||
| -rw-r--r-- | src/app/mod.rs | 22 | ||||
| -rw-r--r-- | src/app/state.rs | 14 |
3 files changed, 117 insertions, 28 deletions
diff --git a/src/app/input.rs b/src/app/input.rs index 81089217..6af7cf0e 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -27,8 +27,8 @@ enum WheelRouting { const WORKSPACE_DRAG_THRESHOLD: u16 = 1; use super::state::{ - key_matches, AppState, ContextMenuKind, ContextMenuState, DragState, DragTarget, MenuListState, - Mode, WorkspacePressState, + key_matches, AgentPanelScope, AppState, ContextMenuKind, ContextMenuState, DragState, + DragTarget, MenuListState, Mode, WorkspacePressState, }; use super::App; @@ -2025,6 +2025,14 @@ impl AppState { return None; } + if self.on_agent_panel_scope_toggle(mouse.column, mouse.row) { + self.agent_panel_scope = match self.agent_panel_scope { + AgentPanelScope::CurrentWorkspace => AgentPanelScope::AllWorkspaces, + AgentPanelScope::AllWorkspaces => AgentPanelScope::CurrentWorkspace, + }; + return None; + } + if let Some((ws_idx, tab_idx, pane_id)) = self.agent_detail_target_at(mouse.row) { self.switch_workspace(ws_idx); @@ -2458,29 +2466,26 @@ impl AppState { best.map(|(insert_idx, _)| insert_idx) } - fn agent_detail_target_at(&self, row: u16) -> Option<(usize, usize, crate::layout::PaneId)> { + fn on_agent_panel_scope_toggle(&self, col: u16, row: u16) -> bool { if self.sidebar_collapsed { - return None; + return false; } - let content = Rect::new( - self.view.sidebar_rect.x, - self.view.sidebar_rect.y, - self.view.sidebar_rect.width.saturating_sub(1), - self.view.sidebar_rect.height, - ); - if content.width == 0 || content.height == 0 { + let (_, detail_area) = crate::ui::expanded_sidebar_sections(self.view.sidebar_rect); + let rect = crate::ui::agent_panel_toggle_rect(detail_area, self.agent_panel_scope); + rect.width > 0 + && col >= rect.x + && col < rect.x + rect.width + && row >= rect.y + && row < rect.y + rect.height + } + + fn agent_detail_target_at(&self, row: u16) -> Option<(usize, usize, crate::layout::PaneId)> { + if self.sidebar_collapsed { return None; } - let total_h = content.height as usize; - let ws_h = (total_h + 1) / 2; - let detail_area = Rect::new( - content.x, - content.y + ws_h as u16, - content.width, - total_h.saturating_sub(ws_h) as u16, - ); + let (_, detail_area) = crate::ui::expanded_sidebar_sections(self.view.sidebar_rect); if detail_area.height < 4 || row < detail_area.y + 3 || row >= detail_area.y + detail_area.height @@ -2488,17 +2493,15 @@ impl AppState { return None; } - let detail_ws_idx = self.collapsed_detail_workspace_idx()?; - let ws = self.workspaces.get(detail_ws_idx)?; let relative_row = row - (detail_area.y + 3); let entry_height = 3; if relative_row % entry_height == 2 { return None; } let detail_idx = (relative_row / entry_height) as usize; - let details = ws.pane_details(); + let details = crate::ui::agent_panel_entries(self); let detail = details.get(detail_idx)?; - Some((detail_ws_idx, detail.tab_idx, detail.pane_id)) + Some((detail.ws_idx, detail.tab_idx, detail.pane_id)) } fn screen_rect(&self) -> Rect { @@ -3513,6 +3516,66 @@ mod tests { } #[test] + fn clicking_agent_panel_toggle_switches_scope() { + let mut app = app_for_mouse_test(); + app.state.workspaces = vec![Workspace::test_new("test")]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + + let (_, detail_area) = crate::ui::expanded_sidebar_sections(app.state.view.sidebar_rect); + let toggle = crate::ui::agent_panel_toggle_rect(detail_area, app.state.agent_panel_scope); + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + toggle.x, + toggle.y, + )); + + assert_eq!(app.state.agent_panel_scope, AgentPanelScope::AllWorkspaces); + } + + #[test] + fn clicking_all_workspaces_agent_row_switches_to_correct_workspace() { + let mut app = app_for_mouse_test(); + let mut first = Workspace::test_new("one"); + let first_pane = first.tabs[0].root_pane; + first.tabs[0] + .panes + .get_mut(&first_pane) + .unwrap() + .detected_agent = Some(Agent::Pi); + + let mut second = Workspace::test_new("two"); + let second_pane = second.tabs[0].root_pane; + second.tabs[0] + .panes + .get_mut(&second_pane) + .unwrap() + .detected_agent = Some(Agent::Claude); + + app.state.workspaces = vec![first, second]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.agent_panel_scope = AgentPanelScope::AllWorkspaces; + + let (_, detail_area) = crate::ui::expanded_sidebar_sections(app.state.view.sidebar_rect); + app.handle_mouse(mouse( + MouseEventKind::Down(MouseButton::Left), + detail_area.x + 2, + detail_area.y + 6, + )); + + assert_eq!(app.state.active, Some(1)); + assert_eq!(app.state.selected, 1); + assert_eq!(app.state.workspaces[1].active_tab, 0); + assert_eq!( + app.state.workspaces[1].tabs[0].layout.focused(), + second_pane + ); + } + + #[test] fn clicking_collapsed_agent_row_switches_to_correct_tab_and_pane() { let mut app = app_for_mouse_test(); let mut ws = Workspace::test_new("test"); diff --git a/src/app/mod.rs b/src/app/mod.rs index 14274210..f06bbb19 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -157,8 +157,13 @@ impl App { let render_dirty = Arc::new(AtomicBool::new(false)); // Try to restore previous session - let (workspaces, active, selected) = if no_session { - (Vec::new(), None, 0) + let (workspaces, active, selected, agent_panel_scope) = if no_session { + ( + Vec::new(), + None, + 0, + state::AgentPanelScope::CurrentWorkspace, + ) } else if let Some(snap) = crate::persist::load() { let ws = crate::persist::restore( &snap, @@ -170,15 +175,20 @@ impl App { ); if ws.is_empty() { info!("session file found but no workspaces restored"); - (Vec::new(), None, 0) + (Vec::new(), None, 0, snap.agent_panel_scope) } else { info!(count = ws.len(), "session restored"); let active = snap.active.filter(|&i| i < ws.len()); let selected = snap.selected.min(ws.len().saturating_sub(1)); - (ws, active, selected) + (ws, active, selected, snap.agent_panel_scope) } } else { - (Vec::new(), None, 0) + ( + Vec::new(), + None, + 0, + state::AgentPanelScope::CurrentWorkspace, + ) }; let latest_release_notes_available = crate::release_notes::load_latest().is_some(); @@ -240,6 +250,7 @@ impl App { sidebar_width: config.ui.sidebar_width, sidebar_width_auto: true, sidebar_collapsed: false, + agent_panel_scope, confirm_close: config.ui.confirm_close, accent: crate::config::parse_color(&config.ui.accent), sound: config.ui.sound.clone(), @@ -422,6 +433,7 @@ impl App { &self.state.workspaces, self.state.active, self.state.selected, + self.state.agent_panel_scope, ); crate::persist::save(&snap); } diff --git a/src/app/state.rs b/src/app/state.rs index 74daeeb8..60cb0511 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -356,6 +356,18 @@ pub enum Mode { KeybindHelp, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub enum AgentPanelScope { + CurrentWorkspace, + AllWorkspaces, +} + +impl Default for AgentPanelScope { + fn default() -> Self { + Self::CurrentWorkspace + } +} + // --------------------------------------------------------------------------- // Settings UI state // --------------------------------------------------------------------------- @@ -586,6 +598,7 @@ pub struct AppState { pub sidebar_width: u16, pub sidebar_width_auto: bool, pub sidebar_collapsed: bool, + pub agent_panel_scope: AgentPanelScope, pub confirm_close: bool, #[allow(dead_code)] // kept for backward compat; palette.accent is the source of truth pub accent: Color, @@ -693,6 +706,7 @@ impl AppState { sidebar_width: 26, sidebar_width_auto: true, sidebar_collapsed: false, + agent_panel_scope: AgentPanelScope::CurrentWorkspace, confirm_close: true, accent: Color::Cyan, sound: SoundConfig { |
