aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-20 00:54:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-20 00:55:36 +0300
commitd30ab1b54f6b6be95a0cb47ddc38da1d5e088700 (patch)
treeeb2002b408c856d9fcd7a573afe73b917f22cd97 /src/app/input
parent3f80947678a9723434aa7fddfe23750b5393e24c (diff)
feat: add plugin-driven agent views and startup hooks
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/navigate.rs48
-rw-r--r--src/app/input/sidebar.rs49
-rw-r--r--src/app/input/terminal.rs1
3 files changed, 88 insertions, 10 deletions
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index e1ffeb5d..d151b94d 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -724,14 +724,13 @@ impl App {
.and_then(crate::workspace::Workspace::focused_pane_id);
let current_idx = entries
.iter()
- .position(|entry| Some(entry.pane_id) == focused)
- .unwrap_or(0);
- let next_idx = if forward {
- (current_idx + 1) % entries.len()
- } else if current_idx == 0 {
- entries.len() - 1
- } else {
- current_idx - 1
+ .position(|entry| Some(entry.pane_id) == focused);
+ let next_idx = match (current_idx, forward) {
+ (Some(idx), true) => (idx + 1) % entries.len(),
+ (Some(0), false) => entries.len() - 1,
+ (Some(idx), false) => idx - 1,
+ (None, true) => 0,
+ (None, false) => entries.len() - 1,
};
let target = entries.get(next_idx)?;
Some((next_idx, target.ws_idx, target.pane_id))
@@ -1877,6 +1876,39 @@ mod tests {
}
#[test]
+ fn next_agent_starts_at_first_visible_entry_when_focused_agent_is_filtered_out() {
+ let mut app = app_with_test_workspaces(&["hidden", "first", "second"]);
+ for ws_idx in 0..app.state.workspaces.len() {
+ let pane_id = app.state.workspaces[ws_idx].tabs[0].root_pane;
+ let terminal_id = app.state.workspaces[ws_idx].tabs[0].panes[&pane_id]
+ .attached_terminal_id
+ .clone();
+ let terminal = app.state.terminals.get_mut(&terminal_id).unwrap();
+ terminal.detected_agent = Some(crate::detect::Agent::Claude);
+ terminal.state = if ws_idx == 0 {
+ crate::detect::AgentState::Idle
+ } else {
+ crate::detect::AgentState::Working
+ };
+ }
+ app.state.agent_view_override = Some(crate::api::schema::AgentViewSetParams {
+ source: "example.views".to_string(),
+ label: None,
+ filter: Some(crate::api::schema::AgentViewFilter::Eq {
+ field: crate::api::schema::AgentViewField::Builtin(
+ crate::api::schema::AgentViewBuiltinField::Status,
+ ),
+ value: crate::api::schema::AgentViewValue::String("working".to_string()),
+ }),
+ sort: Vec::new(),
+ });
+
+ app.execute_tui_navigate_action(NavigateAction::NextAgent, ActionContext::Prefix);
+
+ assert_eq!(app.state.active, Some(1));
+ }
+
+ #[test]
fn default_goto_key_opens_navigator() {
let mut state = state_with_workspaces(&["test"]);
diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs
index 9ae30763..85de8aac 100644
--- a/src/app/input/sidebar.rs
+++ b/src/app/input/sidebar.rs
@@ -413,7 +413,7 @@ impl AppState {
}
pub(super) fn on_agent_panel_sort_toggle(&self, col: u16, row: u16) -> bool {
- if self.sidebar_collapsed {
+ if self.sidebar_collapsed || self.agent_view_override.is_some() {
return false;
}
@@ -450,7 +450,8 @@ impl AppState {
let mut row_y = body.y;
let body_bottom = body.y + body.height;
let entries = crate::ui::agent_panel_entries(self);
- for (index, detail) in entries.iter().enumerate().skip(self.agent_panel_scroll) {
+ let scroll = self.agent_panel_scroll.min(metrics.max_offset_from_bottom);
+ for (index, detail) in entries.iter().enumerate().skip(scroll) {
let height = crate::ui::agent_entry_height_in_body(self, detail, body.height);
if row_y.saturating_add(height) > body_bottom {
break;
@@ -746,6 +747,50 @@ mod tests {
}
#[test]
+ fn agent_hit_testing_clamps_scroll_after_dynamic_filter_shrink() {
+ let mut app = app_for_mouse_test();
+ let first = Workspace::test_new("one");
+ let first_pane = first.tabs[0].root_pane;
+ let second = Workspace::test_new("two");
+ let second_pane = second.tabs[0].root_pane;
+ app.state.workspaces = vec![first, second];
+ app.state.ensure_test_terminals();
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ for (ws_idx, pane_id) in [(0, first_pane), (1, second_pane)] {
+ let terminal_id = app.state.workspaces[ws_idx].tabs[0].panes[&pane_id]
+ .attached_terminal_id
+ .clone();
+ app.state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .detected_agent = Some(Agent::Claude);
+ }
+ app.state.agent_view_override = Some(crate::api::schema::AgentViewSetParams {
+ source: "example.views".to_string(),
+ label: None,
+ filter: Some(crate::api::schema::AgentViewFilter::Eq {
+ field: crate::api::schema::AgentViewField::Builtin(
+ crate::api::schema::AgentViewBuiltinField::WorkspaceId,
+ ),
+ value: crate::api::schema::AgentViewValue::Context {
+ context: crate::api::schema::AgentViewContext::CurrentWorkspaceId,
+ },
+ }),
+ sort: Vec::new(),
+ });
+ app.state.agent_panel_scroll = 10;
+ let detail_area = app.state.agent_panel_rect();
+ let body = crate::ui::agent_panel_body_rect(detail_area, false);
+
+ assert_eq!(
+ app.state.agent_detail_target_at(body.y),
+ Some((0, 0, first_pane))
+ );
+ }
+
+ #[test]
fn clicking_agent_panel_toggle_switches_sort() {
let mut app = app_for_mouse_test();
app.state.workspaces = vec![Workspace::test_new("test")];
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs
index 587c0e22..c1bfac87 100644
--- a/src/app/input/terminal.rs
+++ b/src/app/input/terminal.rs
@@ -376,6 +376,7 @@ mod tests {
enabled: true,
platforms: None,
build: Vec::new(),
+ startup: Vec::new(),
actions: vec![crate::api::schema::PluginManifestAction {
id: "open".into(),
title: "Open link".into(),