diff options
Diffstat (limited to 'src/api')
| -rw-r--r-- | src/api/mod.rs | 7 | ||||
| -rw-r--r-- | src/api/schema.rs | 22 | ||||
| -rw-r--r-- | src/api/schema/commands.rs | 16 | ||||
| -rw-r--r-- | src/api/schema/common.rs | 19 | ||||
| -rw-r--r-- | src/api/schema/integrations.rs | 17 | ||||
| -rw-r--r-- | src/api/schema/panes.rs | 80 | ||||
| -rw-r--r-- | src/api/schema/response.rs | 29 | ||||
| -rw-r--r-- | src/api/schema/tests.rs | 75 | ||||
| -rw-r--r-- | src/api/schema/workspaces.rs | 3 | ||||
| -rw-r--r-- | src/api/server.rs | 18 |
10 files changed, 268 insertions, 18 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index b4a5d262..363d7702 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,7 +8,7 @@ mod wait; pub use event_hub::EventHub; pub(crate) use server::start_server_with_stop_control; -pub use server::{start_server_with_capabilities, ServerHandle}; +pub use server::ServerHandle; pub use status::{read_runtime_status_at, RuntimeStatus}; use std::path::PathBuf; @@ -25,6 +25,9 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool { Method::ServerReloadConfig(_) | Method::ServerReloadAgentManifests(_) | Method::NotificationShow(_) + | Method::ProductAnnouncementDismiss(_) + | Method::ReleaseNotesDismiss(_) + | Method::CommandInvoke(_) | Method::WorkspaceCreate(_) | Method::WorkspaceFocus(_) | Method::WorkspaceRename(_) @@ -55,6 +58,8 @@ pub(crate) fn request_changes_ui(request: &Request) -> bool { | Method::PaneZoom(_) | Method::PaneFocusDirection(_) | Method::PaneResize(_) + | Method::PaneScroll(_) + | Method::PaneEditScrollback(_) | Method::PaneFocus(_) | Method::PaneInputSet(_) | Method::PaneRename(_) diff --git a/src/api/schema.rs b/src/api/schema.rs index e06b701c..074cd4ed 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; pub mod agents; +pub mod commands; pub mod common; pub mod events; pub mod integrations; @@ -14,6 +15,7 @@ pub mod workspaces; pub mod worktrees; pub use agents::*; +pub use commands::*; pub use common::*; pub use events::*; pub use integrations::*; @@ -57,6 +59,12 @@ pub enum Method { ServerReloadAgentManifests(EmptyParams), #[serde(rename = "notification.show")] NotificationShow(NotificationShowParams), + #[serde(rename = "product_announcement.dismiss")] + ProductAnnouncementDismiss(ProductAnnouncementDismissParams), + #[serde(rename = "release_notes.dismiss")] + ReleaseNotesDismiss(ReleaseNotesDismissParams), + #[serde(rename = "command.invoke")] + CommandInvoke(CommandInvokeParams), #[serde(rename = "client.window_title.set")] ClientWindowTitleSet(ClientWindowTitleSetParams), #[serde(rename = "client.window_title.clear")] @@ -153,6 +161,16 @@ pub enum Method { PaneFocusDirection(PaneFocusDirectionParams), #[serde(rename = "pane.resize")] PaneResize(PaneResizeParams), + #[serde(rename = "pane.scroll")] + PaneScroll(PaneScrollParams), + #[serde(rename = "pane.edit_scrollback")] + PaneEditScrollback(PaneTarget), + #[serde(rename = "pane.selection.read")] + PaneSelectionRead(PaneSelectionReadParams), + #[serde(rename = "pane.copy_motion")] + PaneCopyMotion(PaneCopyMotionParams), + #[serde(rename = "pane.copy_search")] + PaneCopySearch(PaneCopySearchParams), #[serde(rename = "pane.list")] PaneList(PaneListParams), #[serde(rename = "pane.current")] @@ -163,6 +181,8 @@ pub enum Method { PaneFocus(PaneTarget), #[serde(rename = "pane.input.set")] PaneInputSet(PaneInputSetParams), + #[serde(rename = "pane.link.activate")] + PaneLinkActivate(PaneLinkActivateParams), #[serde(rename = "pane.rename")] PaneRename(PaneRenameParams), #[serde(rename = "pane.send_text")] @@ -214,6 +234,8 @@ pub enum Method { EventsWait(EventsWaitParams), #[serde(rename = "pane.wait_for_output")] PaneWaitForOutput(PaneWaitForOutputParams), + #[serde(rename = "integration.list")] + IntegrationList(EmptyParams), #[serde(rename = "integration.install")] IntegrationInstall(IntegrationInstallParams), #[serde(rename = "integration.uninstall")] diff --git a/src/api/schema/commands.rs b/src/api/schema/commands.rs new file mode 100644 index 00000000..c9ccd4bc --- /dev/null +++ b/src/api/schema/commands.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct CommandInvokeParams { + /// Opaque endpoint-issued command identifier from the client-shell projection. + pub command_id: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub tab_id: Option<String>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pane_id: Option<String>, + /// Client-owned selection coordinates, validated against the pane's content revision. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub selection: Option<super::PaneSelectionReadParams>, +} diff --git a/src/api/schema/common.rs b/src/api/schema/common.rs index 7313002c..15f56e6c 100644 --- a/src/api/schema/common.rs +++ b/src/api/schema/common.rs @@ -36,6 +36,17 @@ pub struct PaneTarget { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct ProductAnnouncementDismissParams { + pub version: String, + pub id: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct ReleaseNotesDismissParams { + pub version: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct TabTarget { pub tab_id: String, } @@ -109,14 +120,6 @@ impl NotificationShowSound { pub fn is_none(&self) -> bool { matches!(self, Self::None) } - - pub fn to_sound(self) -> Option<crate::sound::Sound> { - match self { - Self::None => None, - Self::Done => Some(crate::sound::Sound::Done), - Self::Request => Some(crate::sound::Sound::Request), - } - } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] diff --git a/src/api/schema/integrations.rs b/src/api/schema/integrations.rs index 6151dedb..0fce2913 100644 --- a/src/api/schema/integrations.rs +++ b/src/api/schema/integrations.rs @@ -1,5 +1,22 @@ use serde::{Deserialize, Serialize}; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum IntegrationState { + NotInstalled, + Current, + Outdated, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct IntegrationInfo { + pub target: IntegrationTarget, + pub label: String, + pub command: String, + pub available: bool, + pub state: IntegrationState, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct IntegrationInstallParams { pub target: IntegrationTarget, diff --git a/src/api/schema/panes.rs b/src/api/schema/panes.rs index 6435a422..517870f7 100644 --- a/src/api/schema/panes.rs +++ b/src/api/schema/panes.rs @@ -48,6 +48,17 @@ pub struct PaneInputSetParams { pub right_click: PaneRightClickTarget, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneLinkActivateParams { + pub pane_id: String, + pub viewport_row: u16, + pub col: u16, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content_revision: Option<u64>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub offset_from_bottom: Option<u64>, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] #[serde(rename_all = "snake_case")] pub enum PaneDirection { @@ -231,6 +242,75 @@ pub struct PaneResizeParams { pub amount: Option<f32>, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneScrollParams { + pub pane_id: String, + pub offset_from_bottom: u64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneTextPoint { + pub row: u32, + pub col: u16, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneSelectionReadParams { + pub pane_id: String, + pub anchor: PaneTextPoint, + pub cursor: PaneTextPoint, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content_revision: Option<u64>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PaneCopyMotion { + LineEnd, + FirstNonBlank, + NextWordStart, + PreviousWordStart, + NextWordEnd, + NextBigWordStart, + PreviousBigWordStart, + NextBigWordEnd, + PreviousParagraph, + NextParagraph, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneCopyMotionParams { + pub pane_id: String, + pub cursor: PaneTextPoint, + pub motion: PaneCopyMotion, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub content_revision: Option<u64>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum PaneCopySearchDirection { + Forward, + Backward, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneTextRange { + pub start: PaneTextPoint, + pub end: PaneTextPoint, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] +pub struct PaneCopySearchParams { + pub pane_id: String, + pub query: String, + pub direction: PaneCopySearchDirection, + pub cursor: PaneTextPoint, + pub content_revision: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub previous: Option<PaneTextRange>, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema, Default)] pub struct PaneListParams { #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/src/api/schema/response.rs b/src/api/schema/response.rs index da2813a6..180d6645 100644 --- a/src/api/schema/response.rs +++ b/src/api/schema/response.rs @@ -9,7 +9,7 @@ use super::integrations::{ use super::panes::{ LayoutDescription, PaneEdgesResult, PaneFocusDirectionResult, PaneInfo, PaneLayoutSnapshot, PaneMoveResult, PaneNeighborResult, PaneProcessInfo, PaneReadResult, PaneResizeResult, - PaneSwapResult, PaneZoomResult, + PaneSwapResult, PaneTextPoint, PaneTextRange, PaneZoomResult, }; use super::plugins::{ InstalledPluginInfo, PluginActionInfo, PluginCommandLogInfo, PluginInvocationContext, @@ -162,6 +162,25 @@ pub enum ResponseResult { PaneRead { read: PaneReadResult, }, + PaneSelection { + pane_id: String, + text: String, + }, + PaneCopyMotion { + pane_id: String, + cursor: PaneTextPoint, + content_revision: u64, + }, + PaneCopySearch { + pane_id: String, + content_revision: u64, + matches: Vec<PaneTextRange>, + total: u64, + #[serde(default, skip_serializing_if = "Option::is_none")] + current: Option<u32>, + #[serde(default, skip_serializing_if = "Option::is_none")] + current_global: Option<u64>, + }, PaneGraphicsFrameAck { sequence: u64, revision: u64, @@ -210,6 +229,9 @@ pub enum ResponseResult { changed: bool, reason: ClientWindowTitleReason, }, + IntegrationList { + integrations: Vec<super::integrations::IntegrationInfo>, + }, IntegrationInstall { target: IntegrationTarget, details: IntegrationInstallResult, @@ -252,6 +274,11 @@ pub enum ResponseResult { context: PluginInvocationContext, log: PluginCommandLogInfo, }, + PaneLinkActivated { + #[serde(default, skip_serializing_if = "Option::is_none")] + url: Option<String>, + handled: bool, + }, PluginLogList { logs: Vec<PluginCommandLogInfo>, }, diff --git a/src/api/schema/tests.rs b/src/api/schema/tests.rs index 663f3fc4..e4a7f2a3 100644 --- a/src/api/schema/tests.rs +++ b/src/api/schema/tests.rs @@ -50,6 +50,7 @@ fn request_uses_dot_method_names() { let request = Request { id: "req_1".into(), method: Method::WorkspaceCreate(WorkspaceCreateParams { + source_workspace_id: None, cwd: Some("/tmp".into()), focus: true, label: Some("api".into()), @@ -273,6 +274,54 @@ fn request_round_trips_for_agent_explain() { } #[test] +fn integration_list_request_and_response_round_trip() { + let request = Request { + id: "req_integrations".into(), + method: Method::IntegrationList(EmptyParams::default()), + }; + let json = serde_json::to_value(&request).unwrap(); + assert_eq!(json["method"], "integration.list"); + assert_eq!(serde_json::from_value::<Request>(json).unwrap(), request); + + let response = SuccessResponse { + id: "req_integrations".into(), + result: ResponseResult::IntegrationList { + integrations: vec![IntegrationInfo { + target: IntegrationTarget::Codex, + label: "codex".into(), + command: "codex".into(), + available: true, + state: IntegrationState::Outdated, + }], + }, + }; + let json = serde_json::to_value(&response).unwrap(); + assert_eq!(json["result"]["type"], "integration_list"); + assert_eq!(json["result"]["integrations"][0]["state"], "outdated"); + assert_eq!( + serde_json::from_value::<SuccessResponse>(json).unwrap(), + response + ); +} + +#[test] +fn command_invoke_request_round_trips_without_command_text() { + let request = Request { + id: "req_command".into(), + method: Method::CommandInvoke(CommandInvokeParams { + command_id: "cmd_0123456789abcdef0123456789abcdef".into(), + workspace_id: Some("w1".into()), + tab_id: Some("w1:t1".into()), + pane_id: Some("w1:p1".into()), + selection: None, + }), + }; + let json = serde_json::to_value(&request).unwrap(); + assert_eq!(json["method"], "command.invoke"); + assert_eq!(serde_json::from_value::<Request>(json).unwrap(), request); +} + +#[test] fn notification_show_request_parses() { let json = r#"{"id":"req_1","method":"notification.show","params":{"title":"build failed","body":"api workspace","position":"top-left","sound":"request"}}"#; let request: Request = serde_json::from_str(json).unwrap(); @@ -1264,6 +1313,32 @@ fn event_wait_parses_typed_match() { } #[test] +fn pane_link_activate_round_trips() { + let request = Request { + id: "req_pane_link".into(), + method: Method::PaneLinkActivate(PaneLinkActivateParams { + pane_id: "w1:p1".into(), + viewport_row: 3, + col: 7, + content_revision: Some(42), + offset_from_bottom: Some(5), + }), + }; + let json = serde_json::to_value(&request).unwrap(); + assert_eq!(json["method"], "pane.link.activate"); + let restored: Request = serde_json::from_value(json).unwrap(); + assert_eq!(restored, request); + + let response = ResponseResult::PaneLinkActivated { + url: Some("https://example.test".into()), + handled: false, + }; + let json = serde_json::to_string(&response).unwrap(); + let restored: ResponseResult = serde_json::from_str(&json).unwrap(); + assert_eq!(restored, response); +} + +#[test] fn plugin_action_list_and_invoke_round_trips() { let list = Request { id: "req_plugin_action_list".into(), diff --git a/src/api/schema/workspaces.rs b/src/api/schema/workspaces.rs index bfc63252..1b7132c4 100644 --- a/src/api/schema/workspaces.rs +++ b/src/api/schema/workspaces.rs @@ -6,6 +6,9 @@ use super::common::AgentStatus; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)] pub struct WorkspaceCreateParams { + /// Workspace whose focused pane supplies the `follow` cwd policy. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_workspace_id: Option<String>, #[serde(default, skip_serializing_if = "Option::is_none")] pub cwd: Option<String>, #[serde(default)] diff --git a/src/api/server.rs b/src/api/server.rs index 3c42d699..ff0b6fdf 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -64,14 +64,6 @@ pub(crate) fn start_server_with_stop_control( start_server_inner(api_tx, event_hub, default_capabilities(), Some(server_stop)) } -pub fn start_server_with_capabilities( - api_tx: ApiRequestSender, - event_hub: EventHub, - capabilities: Option<ServerCapabilities>, -) -> std::io::Result<ServerHandle> { - start_server_inner(api_tx, event_hub, capabilities, None) -} - fn default_capabilities() -> Option<ServerCapabilities> { Some(ServerCapabilities { live_handoff: crate::platform::capabilities().live_handoff, @@ -388,6 +380,9 @@ fn api_method_name(method: &Method) -> &'static str { Method::ServerAgentManifests(_) => "server.agent_manifests", Method::ServerReloadAgentManifests(_) => "server.reload_agent_manifests", Method::NotificationShow(_) => "notification.show", + Method::ProductAnnouncementDismiss(_) => "product_announcement.dismiss", + Method::ReleaseNotesDismiss(_) => "release_notes.dismiss", + Method::CommandInvoke(_) => "command.invoke", Method::ClientWindowTitleSet(_) => "client.window_title.set", Method::ClientWindowTitleClear(_) => "client.window_title.clear", Method::SessionSnapshot(_) => "session.snapshot", @@ -436,11 +431,17 @@ fn api_method_name(method: &Method) -> &'static str { Method::PaneEdges(_) => "pane.edges", Method::PaneFocusDirection(_) => "pane.focus_direction", Method::PaneResize(_) => "pane.resize", + Method::PaneScroll(_) => "pane.scroll", + Method::PaneEditScrollback(_) => "pane.edit_scrollback", + Method::PaneSelectionRead(_) => "pane.selection.read", + Method::PaneCopyMotion(_) => "pane.copy_motion", + Method::PaneCopySearch(_) => "pane.copy_search", Method::PaneList(_) => "pane.list", Method::PaneCurrent(_) => "pane.current", Method::PaneGet(_) => "pane.get", Method::PaneFocus(_) => "pane.focus", Method::PaneInputSet(_) => "pane.input.set", + Method::PaneLinkActivate(_) => "pane.link.activate", Method::PaneRename(_) => "pane.rename", Method::PaneSendText(_) => "pane.send_text", Method::PaneSendKeys(_) => "pane.send_keys", @@ -464,6 +465,7 @@ fn api_method_name(method: &Method) -> &'static str { Method::EventsSubscribe(_) => "events.subscribe", Method::EventsWait(_) => "events.wait", Method::PaneWaitForOutput(_) => "pane.wait_for_output", + Method::IntegrationList(_) => "integration.list", Method::IntegrationInstall(_) => "integration.install", Method::IntegrationUninstall(_) => "integration.uninstall", Method::PluginLink(_) => "plugin.link", |
