diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-10-09 11:29:55 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-09 11:29:55 +0200 |
| commit | 7e66ae3125bf5031b6c013dee8038c838b42bbc5 (patch) | |
| tree | 061c2311461cbe52b40b8ad433023085cfae67bf /zellij-utils/src/plugin_api | |
| parent | build(cargo): Update Rust toolchain to 1.90.0 (#4457) (diff) | |
| download | sl0ppij-7e66ae3125bf5031b6c013dee8038c838b42bbc5.tar.gz sl0ppij-7e66ae3125bf5031b6c013dee8038c838b42bbc5.tar.xz sl0ppij-7e66ae3125bf5031b6c013dee8038c838b42bbc5.zip | |
Give plugins the ability to read pane contents (#4465)
* initial implementation
* add Selection and better types
* event to plugins
* only send viewport diff
* simplify data structure sent over the wire
* cleanups
* rustfmt
* remove dead code and fix tests
* commit tests
* get pane scrollback plugin command without implementation
* add lines above and lines below to pane contents
* implement get scrollback plugin command
* selection => selected_text
* add convenience method to get the selected text
* move Selection back to zellij-server
* rustfmt
* bring back client id to get contents command
* rustfmt
* add pr
Diffstat (limited to 'zellij-utils/src/plugin_api')
| -rw-r--r-- | zellij-utils/src/plugin_api/event.proto | 30 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/event.rs | 175 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 7 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 40 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.proto | 1 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_permission.rs | 2 |
6 files changed, 234 insertions, 21 deletions
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index 52c1801c..f5059131 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -58,6 +58,7 @@ enum EventType { BeforeClose = 32; FailedToStartWebServer = 34; InterceptedKeyPress = 35; + PaneRenderReport = 36; } message EventNameList { @@ -95,6 +96,7 @@ message Event { WebServerStatusPayload web_server_status_payload = 27; FailedToStartWebServerPayload failed_to_start_web_server_payload = 28; key.Key intercepted_key_payload = 29; + PaneRenderReportPayload pane_render_report_payload = 30; } } @@ -388,3 +390,31 @@ message KeyBind { key.Key key = 1; repeated action.Action action = 2; } + +message PaneRenderReportPayload { + repeated PaneContentsEntry pane_contents = 1; +} + +message PaneContentsEntry { + PaneId pane_id = 1; + PaneContents pane_contents = 2; +} + +message PaneContents { + repeated string viewport = 1; + optional SelectedText selected_text = 2; + repeated string lines_above_viewport = 3; + repeated string lines_below_viewport = 4; +} + +message PaneScrollbackResponse { + oneof response { + PaneContents ok = 1; + string err = 2; + } +} + +message SelectedText { + action.Position start = 1; + action.Position end = 2; +} diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index fe8822e4..02ef1842 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -1,18 +1,21 @@ pub use super::generated_api::api::{ action::{Action as ProtobufAction, Position as ProtobufPosition}, event::{ - event::Payload as ProtobufEventPayload, ClientInfo as ProtobufClientInfo, - ClientTabHistory as ProtobufClientTabHistory, CopyDestination as ProtobufCopyDestination, - Event as ProtobufEvent, EventNameList as ProtobufEventNameList, - EventType as ProtobufEventType, FileMetadata as ProtobufFileMetadata, - InputModeKeybinds as ProtobufInputModeKeybinds, KeyBind as ProtobufKeyBind, - LayoutInfo as ProtobufLayoutInfo, ModeUpdatePayload as ProtobufModeUpdatePayload, - PaneId as ProtobufPaneId, PaneInfo as ProtobufPaneInfo, - PaneManifest as ProtobufPaneManifest, PaneType as ProtobufPaneType, + event::Payload as ProtobufEventPayload, pane_scrollback_response, + ClientInfo as ProtobufClientInfo, ClientTabHistory as ProtobufClientTabHistory, + CopyDestination as ProtobufCopyDestination, Event as ProtobufEvent, + EventNameList as ProtobufEventNameList, EventType as ProtobufEventType, + FileMetadata as ProtobufFileMetadata, InputModeKeybinds as ProtobufInputModeKeybinds, + KeyBind as ProtobufKeyBind, LayoutInfo as ProtobufLayoutInfo, + ModeUpdatePayload as ProtobufModeUpdatePayload, PaneContents as ProtobufPaneContents, + PaneContentsEntry as ProtobufPaneContentsEntry, PaneId as ProtobufPaneId, + PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest, + PaneRenderReportPayload as ProtobufPaneRenderReportPayload, + PaneScrollbackResponse as ProtobufPaneScrollbackResponse, PaneType as ProtobufPaneType, PluginInfo as ProtobufPluginInfo, ResurrectableSession as ProtobufResurrectableSession, - SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, - WebServerStatusPayload as ProtobufWebServerStatusPayload, WebSharing as ProtobufWebSharing, - *, + SelectedText as ProtobufSelectedText, SessionManifest as ProtobufSessionManifest, + TabInfo as ProtobufTabInfo, WebServerStatusPayload as ProtobufWebServerStatusPayload, + WebSharing as ProtobufWebSharing, *, }, input_mode::InputMode as ProtobufInputMode, key::Key as ProtobufKey, @@ -21,8 +24,9 @@ pub use super::generated_api::api::{ #[allow(hidden_glob_reexports)] use crate::data::{ ClientInfo, CopyDestination, Event, EventType, FileMetadata, InputMode, KeyWithModifier, - LayoutInfo, ModeInfo, Mouse, PaneId, PaneInfo, PaneManifest, PermissionStatus, - PluginCapabilities, PluginInfo, SessionInfo, Style, TabInfo, WebServerStatus, WebSharing, + LayoutInfo, ModeInfo, Mouse, PaneContents, PaneId, PaneInfo, PaneManifest, + PaneScrollbackResponse, PermissionStatus, PluginCapabilities, PluginInfo, SelectedText, + SessionInfo, Style, TabInfo, WebServerStatus, WebSharing, }; use crate::errors::prelude::*; @@ -387,6 +391,12 @@ impl TryFrom<ProtobufEvent> for Event { }, _ => Err("Malformed payload for the InterceptedKeyPress Event"), }, + Some(ProtobufEventType::PaneRenderReport) => match protobuf_event.payload { + Some(ProtobufEventPayload::PaneRenderReportPayload(protobuf_payload)) => { + Ok(Event::PaneRenderReport(protobuf_payload.try_into()?)) + }, + _ => Err("Malformed payload for the PaneRenderReport Event"), + }, None => Err("Unknown Protobuf Event"), } } @@ -781,6 +791,12 @@ impl TryFrom<Event> for ProtobufEvent { name: ProtobufEventType::InterceptedKeyPress as i32, payload: Some(event::Payload::KeyPayload(key.try_into()?)), }), + Event::PaneRenderReport(pane_contents_map) => Ok(ProtobufEvent { + name: ProtobufEventType::PaneRenderReport as i32, + payload: Some(event::Payload::PaneRenderReportPayload( + pane_contents_map.try_into()?, + )), + }), } } } @@ -1459,6 +1475,7 @@ impl TryFrom<ProtobufEventType> for EventType { ProtobufEventType::BeforeClose => EventType::BeforeClose, ProtobufEventType::FailedToStartWebServer => EventType::FailedToStartWebServer, ProtobufEventType::InterceptedKeyPress => EventType::InterceptedKeyPress, + ProtobufEventType::PaneRenderReport => EventType::PaneRenderReport, }) } } @@ -1502,6 +1519,7 @@ impl TryFrom<EventType> for ProtobufEventType { EventType::BeforeClose => ProtobufEventType::BeforeClose, EventType::FailedToStartWebServer => ProtobufEventType::FailedToStartWebServer, EventType::InterceptedKeyPress => ProtobufEventType::InterceptedKeyPress, + EventType::PaneRenderReport => ProtobufEventType::PaneRenderReport, }) } } @@ -2243,3 +2261,134 @@ impl TryFrom<ProtobufWebServerStatusPayload> for WebServerStatus { } } } + +impl TryFrom<ProtobufPaneRenderReportPayload> for HashMap<PaneId, PaneContents> { + type Error = &'static str; + fn try_from(protobuf_payload: ProtobufPaneRenderReportPayload) -> Result<Self, &'static str> { + let mut pane_contents_map = HashMap::new(); + + for entry in protobuf_payload.pane_contents { + let pane_id = entry + .pane_id + .ok_or("Missing pane_id in PaneContentsEntry")? + .try_into()?; + let pane_contents = entry + .pane_contents + .ok_or("Missing pane_contents in PaneContentsEntry")? + .try_into()?; + pane_contents_map.insert(pane_id, pane_contents); + } + + Ok(pane_contents_map) + } +} + +impl TryFrom<HashMap<PaneId, PaneContents>> for ProtobufPaneRenderReportPayload { + type Error = &'static str; + fn try_from(pane_contents_map: HashMap<PaneId, PaneContents>) -> Result<Self, &'static str> { + let mut pane_contents_vec = vec![]; + + for (pane_id, pane_contents) in pane_contents_map { + pane_contents_vec.push(ProtobufPaneContentsEntry { + pane_id: Some(pane_id.try_into()?), + pane_contents: Some(pane_contents.try_into()?), + }); + } + + Ok(ProtobufPaneRenderReportPayload { + pane_contents: pane_contents_vec, + }) + } +} + +impl TryFrom<ProtobufPaneContents> for PaneContents { + type Error = &'static str; + fn try_from(protobuf_contents: ProtobufPaneContents) -> Result<Self, &'static str> { + let selected_text = protobuf_contents + .selected_text + .map(|st| st.try_into()) + .transpose()?; + + Ok(PaneContents { + viewport: protobuf_contents.viewport, + selected_text, + lines_above_viewport: protobuf_contents.lines_above_viewport, + lines_below_viewport: protobuf_contents.lines_below_viewport, + }) + } +} + +impl TryFrom<PaneContents> for ProtobufPaneContents { + type Error = &'static str; + fn try_from(pane_contents: PaneContents) -> Result<Self, &'static str> { + let selected_text = pane_contents + .selected_text + .map(|st| st.try_into()) + .transpose()?; + + Ok(ProtobufPaneContents { + viewport: pane_contents.viewport, + selected_text, + lines_above_viewport: pane_contents.lines_above_viewport, + lines_below_viewport: pane_contents.lines_below_viewport, + }) + } +} + +impl TryFrom<ProtobufPaneScrollbackResponse> for PaneScrollbackResponse { + type Error = &'static str; + fn try_from(protobuf_response: ProtobufPaneScrollbackResponse) -> Result<Self, &'static str> { + match protobuf_response.response { + Some(pane_scrollback_response::Response::Ok(pane_contents)) => { + Ok(PaneScrollbackResponse::Ok(pane_contents.try_into()?)) + }, + Some(pane_scrollback_response::Response::Err(error_msg)) => { + Ok(PaneScrollbackResponse::Err(error_msg)) + }, + None => Err("PaneScrollbackResponse missing response field"), + } + } +} + +impl TryFrom<PaneScrollbackResponse> for ProtobufPaneScrollbackResponse { + type Error = &'static str; + fn try_from(response: PaneScrollbackResponse) -> Result<Self, &'static str> { + let response_field = match response { + PaneScrollbackResponse::Ok(pane_contents) => { + pane_scrollback_response::Response::Ok(pane_contents.try_into()?) + }, + PaneScrollbackResponse::Err(error_msg) => { + pane_scrollback_response::Response::Err(error_msg) + }, + }; + Ok(ProtobufPaneScrollbackResponse { + response: Some(response_field), + }) + } +} + +impl TryFrom<ProtobufSelectedText> for SelectedText { + type Error = &'static str; + fn try_from(protobuf_selected_text: ProtobufSelectedText) -> Result<Self, &'static str> { + Ok(SelectedText { + start: protobuf_selected_text + .start + .ok_or("Missing start in SelectedText")? + .try_into()?, + end: protobuf_selected_text + .end + .ok_or("Missing end in SelectedText")? + .try_into()?, + }) + } +} + +impl TryFrom<SelectedText> for ProtobufSelectedText { + type Error = &'static str; + fn try_from(selected_text: SelectedText) -> Result<Self, &'static str> { + Ok(ProtobufSelectedText { + start: Some(selected_text.start.try_into()?), + end: Some(selected_text.end.try_into()?), + }) + } +} diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 49ab98c6..7ffe2fc1 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -159,6 +159,7 @@ enum CommandName { InterceptKeyPresses = 143; ClearKeyPressesIntercepts = 144; ReplacePaneWithExistingPane = 155; + GetPaneScrollback = 163; } message PluginCommand { @@ -266,6 +267,7 @@ message PluginCommand { RenameWebLoginTokenPayload rename_web_login_token_payload = 110; ReplacePaneWithExistingPanePayload replace_pane_with_existing_pane_payload = 111; NewTabPayload new_tab_payload = 112; + GetPaneScrollbackPayload get_pane_scrollback_payload = 113; } } @@ -703,3 +705,8 @@ message RenameWebTokenResponse { bool successfully_renamed = 1; optional string error = 2; } + +message GetPaneScrollbackPayload { + PaneId pane_id = 1; + bool get_full_scrollback = 2; +} diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index 4fc9d939..c1ba877e 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -12,14 +12,14 @@ pub use super::generated_api::api::{ FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatMultiplePanesPayload, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, GenerateWebLoginTokenPayload, - GroupAndUngroupPanesPayload, HidePaneWithIdPayload, HighlightAndUnhighlightPanesPayload, - HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload, - ListTokensResponse, LoadNewPluginPayload, MessageToPluginPayload, - MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, MovePayload, - NewPluginArgs as ProtobufNewPluginArgs, NewTabPayload, NewTabsWithLayoutInfoPayload, - OpenCommandPaneFloatingNearPluginPayload, OpenCommandPaneInPlaceOfPluginPayload, - OpenCommandPaneNearPluginPayload, OpenCommandPanePayload, - OpenFileFloatingNearPluginPayload, OpenFileInPlaceOfPluginPayload, + GetPaneScrollbackPayload, GroupAndUngroupPanesPayload, HidePaneWithIdPayload, + HighlightAndUnhighlightPanesPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, + KeyToRebind, KeyToUnbind, KillSessionsPayload, ListTokensResponse, LoadNewPluginPayload, + MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, + MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabPayload, + NewTabsWithLayoutInfoPayload, OpenCommandPaneFloatingNearPluginPayload, + OpenCommandPaneInPlaceOfPluginPayload, OpenCommandPaneNearPluginPayload, + OpenCommandPanePayload, OpenFileFloatingNearPluginPayload, OpenFileInPlaceOfPluginPayload, OpenFileNearPluginPayload, OpenFilePayload, OpenTerminalFloatingNearPluginPayload, OpenTerminalInPlaceOfPluginPayload, OpenTerminalNearPluginPayload, PageScrollDownInPaneIdPayload, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, @@ -1109,6 +1109,18 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for EditScrollback"), }, + Some(CommandName::GetPaneScrollback) => match protobuf_plugin_command.payload { + Some(Payload::GetPaneScrollbackPayload(get_pane_scrollback_payload)) => { + match get_pane_scrollback_payload.pane_id { + Some(pane_id) => Ok(PluginCommand::GetPaneScrollback { + pane_id: pane_id.try_into()?, + get_full_scrollback: get_pane_scrollback_payload.get_full_scrollback, + }), + _ => Err("Malformed get_pane_scrollback_payload"), + } + }, + _ => Err("Mismatched payload for GetPaneScrollback"), + }, Some(CommandName::WriteToPaneId) => match protobuf_plugin_command.payload { Some(Payload::WriteToPaneIdPayload(write_to_pane_id_payload)) => { match write_to_pane_id_payload.pane_id { @@ -2373,6 +2385,18 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { }, )), }), + PluginCommand::GetPaneScrollback { + pane_id, + get_full_scrollback, + } => Ok(ProtobufPluginCommand { + name: CommandName::GetPaneScrollback as i32, + payload: Some(Payload::GetPaneScrollbackPayload( + GetPaneScrollbackPayload { + pane_id: Some(pane_id.try_into()?), + get_full_scrollback, + }, + )), + }), PluginCommand::WriteToPaneId(bytes_to_write, pane_id) => Ok(ProtobufPluginCommand { name: CommandName::WriteToPaneId as i32, payload: Some(Payload::WriteToPaneIdPayload(WriteToPaneIdPayload { diff --git a/zellij-utils/src/plugin_api/plugin_permission.proto b/zellij-utils/src/plugin_api/plugin_permission.proto index 0f53bb55..85126830 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.proto +++ b/zellij-utils/src/plugin_api/plugin_permission.proto @@ -16,4 +16,5 @@ enum PermissionType { FullHdAccess = 10; StartWebServer = 11; InterceptInput = 12; + ReadPaneContents = 13; } diff --git a/zellij-utils/src/plugin_api/plugin_permission.rs b/zellij-utils/src/plugin_api/plugin_permission.rs index 926f993f..307d3576 100644 --- a/zellij-utils/src/plugin_api/plugin_permission.rs +++ b/zellij-utils/src/plugin_api/plugin_permission.rs @@ -28,6 +28,7 @@ impl TryFrom<ProtobufPermissionType> for PermissionType { ProtobufPermissionType::FullHdAccess => Ok(PermissionType::FullHdAccess), ProtobufPermissionType::StartWebServer => Ok(PermissionType::StartWebServer), ProtobufPermissionType::InterceptInput => Ok(PermissionType::InterceptInput), + ProtobufPermissionType::ReadPaneContents => Ok(PermissionType::ReadPaneContents), } } } @@ -57,6 +58,7 @@ impl TryFrom<PermissionType> for ProtobufPermissionType { PermissionType::FullHdAccess => Ok(ProtobufPermissionType::FullHdAccess), PermissionType::StartWebServer => Ok(ProtobufPermissionType::StartWebServer), PermissionType::InterceptInput => Ok(ProtobufPermissionType::InterceptInput), + PermissionType::ReadPaneContents => Ok(ProtobufPermissionType::ReadPaneContents), } } } |
