aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/plugin_api
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-10-09 11:29:55 +0200
committerGitHub <noreply@github.com>2025-10-09 11:29:55 +0200
commit7e66ae3125bf5031b6c013dee8038c838b42bbc5 (patch)
tree061c2311461cbe52b40b8ad433023085cfae67bf /zellij-utils/src/plugin_api
parentbuild(cargo): Update Rust toolchain to 1.90.0 (#4457) (diff)
downloadsl0ppij-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.proto30
-rw-r--r--zellij-utils/src/plugin_api/event.rs175
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto7
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs40
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.proto1
-rw-r--r--zellij-utils/src/plugin_api/plugin_permission.rs2
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),
}
}
}