From 56edcbcba83b8ff7f6f69ee4231f163e97df3346 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Mon, 6 Jul 2026 23:06:00 +0300 Subject: feat: expose pane scroll state --- src/api/schema/events.rs | 14 +++++++++++++- src/api/schema/panes.rs | 9 +++++++++ src/api/schema/tests.rs | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) (limited to 'src/api/schema') diff --git a/src/api/schema/events.rs b/src/api/schema/events.rs index aff3d2a2..41b9907d 100644 --- a/src/api/schema/events.rs +++ b/src/api/schema/events.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; use super::common::{AgentStatus, ReadSource}; -use super::panes::{PaneInfo, PaneReadResult}; +use super::panes::{PaneInfo, PaneReadResult, PaneScrollInfo}; use super::tabs::TabInfo; use super::workspaces::WorkspaceInfo; use super::worktrees::WorktreeInfo; @@ -72,6 +72,8 @@ pub enum Subscription { #[serde(default, skip_serializing_if = "Option::is_none")] agent_status: Option, }, + #[serde(rename = "pane.scroll_changed")] + PaneScrollChanged { pane_id: String }, #[serde(rename = "layout.updated")] LayoutUpdated {}, } @@ -350,6 +352,8 @@ pub enum SubscriptionEventKind { PaneOutputMatched, #[serde(rename = "pane.agent_status_changed")] PaneAgentStatusChanged, + #[serde(rename = "pane.scroll_changed")] + ScrollChanged, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] @@ -363,6 +367,7 @@ pub struct SubscriptionEventEnvelope { pub enum SubscriptionEventData { PaneOutputMatched(PaneOutputMatchedEvent), PaneAgentStatusChanged(PaneAgentStatusChangedEvent), + ScrollChanged(PaneScrollChangedEvent), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] @@ -389,6 +394,13 @@ pub struct PaneAgentStatusChangedEvent { pub state_labels: HashMap, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneScrollChangedEvent { + pub pane_id: String, + pub workspace_id: String, + pub scroll: PaneScrollInfo, +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum EventData { diff --git a/src/api/schema/panes.rs b/src/api/schema/panes.rs index ef45a7ca..42a640ef 100644 --- a/src/api/schema/panes.rs +++ b/src/api/schema/panes.rs @@ -364,9 +364,18 @@ pub struct PaneInfo { pub state_labels: HashMap, #[serde(default, skip_serializing_if = "Option::is_none")] pub agent_session: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scroll: Option, pub revision: u64, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneScrollInfo { + pub offset_from_bottom: u64, + pub max_offset_from_bottom: u64, + pub viewport_rows: u64, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct PaneProcessInfo { pub pane_id: String, diff --git a/src/api/schema/tests.rs b/src/api/schema/tests.rs index 8bb876d0..5f603a18 100644 --- a/src/api/schema/tests.rs +++ b/src/api/schema/tests.rs @@ -430,6 +430,10 @@ fn subscribe_request_parses_parameterized_subscriptions() { "type": "pane.agent_status_changed", "pane_id": "p_1_1", "agent_status": "done" + }, + { + "type": "pane.scroll_changed", + "pane_id": "p_1_1" } ] } @@ -440,7 +444,7 @@ fn subscribe_request_parses_parameterized_subscriptions() { let Method::EventsSubscribe(params) = request.method else { panic!("wrong method parsed"); }; - assert_eq!(params.subscriptions.len(), 2); + assert_eq!(params.subscriptions.len(), 3); assert!(matches!( ¶ms.subscriptions[0], Subscription::PaneOutputMatched { @@ -458,6 +462,10 @@ fn subscribe_request_parses_parameterized_subscriptions() { agent_status: Some(AgentStatus::Done), } if pane_id == "p_1_1" )); + assert!(matches!( + ¶ms.subscriptions[2], + Subscription::PaneScrollChanged { pane_id } if pane_id == "p_1_1" + )); } #[test] @@ -486,6 +494,27 @@ fn subscription_event_envelope_round_trips() { assert_eq!(restored, event); } +#[test] +fn scroll_changed_subscription_event_round_trips() { + let event = SubscriptionEventEnvelope { + event: SubscriptionEventKind::ScrollChanged, + data: SubscriptionEventData::ScrollChanged(PaneScrollChangedEvent { + pane_id: "p_1_1".into(), + workspace_id: "w_1".into(), + scroll: PaneScrollInfo { + offset_from_bottom: 12, + max_offset_from_bottom: 240, + viewport_rows: 30, + }, + }), + }; + + let json = serde_json::to_string(&event).unwrap(); + assert!(json.contains("\"event\":\"pane.scroll_changed\"")); + let restored: SubscriptionEventEnvelope = serde_json::from_str(&json).unwrap(); + assert_eq!(restored, event); +} + #[test] fn success_response_round_trips() { let response = SuccessResponse { @@ -600,6 +629,7 @@ fn worktree_request_and_response_round_trip() { custom_status: None, state_labels: HashMap::new(), agent_session: None, + scroll: None, revision: 0, }, worktree: WorktreeInfo { @@ -1007,6 +1037,7 @@ fn create_response_round_trips_with_root_pane() { custom_status: None, state_labels: HashMap::new(), agent_session: None, + scroll: None, revision: 0, }, }, -- cgit v1.2.3