aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/plugin_api/plugin_command.rs
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2025-12-11 14:25:42 +0100
committerGitHub <noreply@github.com>2025-12-11 14:25:42 +0100
commit693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch)
treeb3f651462bbc1cd2fa5b2d79207343413ef6b83c /zellij-utils/src/plugin_api/plugin_command.rs
parentfix(zellij-server): unblock building on 32bit targets (#4484) (diff)
downloadsl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.gz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.xz
sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.zip
Command Sequences, Blocking CLI Commands, New Plugin APIs (#4546)
* fix deadlocks * initial logging to plugin implementation * only log actions, include keys as Action::Write * send actions to plugins as a new Event::UserAction * add Action::Write metadata to plugin API * run_action plugin command and permission * block-until-exit/success/failure in the cli * fix tests * add NewBlockingPane to the plugin API * run actions in a different thread so as not to block plugin execution * add custom context to run_action flow * fixes for floating pane z_index sorting and unknown protobuf events not crashing plugins * always render the frame of pinned panes * macros module * add macros to config * KDL macro parsing * thread macros to the plugin thread * get/update/delete macros from plugins * add macros updated event * add ability to rename macros * fix deadlock * fix z_indices with pinned unselectable panes bug * allow plugins to opt-in to show_cursor * change show_cursor(bool) to show_cursor(Option<coordinates>) * test * upgrade strum to allow proper enum iteration and case insensitive fromstr implementation for Actions * better cwd tracking for exited command panes * add initial panes to NewTab Action (no-op for now) * add initial panes to cli newtab action * make initial panes work * add close_on_exit and start_suspended to the cli * properly convert newtab action (potentially breaking for plugins) * allow first pane in tab to block * make distinction between opening panes with the last active client and the current pane in the cli * improve some apis * fix fixed size closing pane bug * fix plugin cursor coordinates * fix issues with launch-or-focus-plugin having to do with in-place and suppressed panes * fix: set affected pane in replace pane * allow setting a pane in place when it is suppressed and being focused (tests failing) * fix tests * some fixes for unsuppression of panes * some fixes for completion_tx in new tab action * allow replace/suppressed rather than replace/close only * fixes for inserting suppressed panes without conflicts * pane focus history and explicit unsuppress plugin api * fix: prefer explicitly provided pane id in placement in actions that provide it * fix floating pane coordinate change and z_index * fix plugin cache dir to persist between sessions * fix some stack focusing issues when adding panes without a client id * make sure pinned panes are always above non-pinned floating panes * allow plugins to copy to clipboard * initial text styling additions implementations - buggy * fix dimming issues * remove extras, stay with just dim and unbold * unbold and dim as indices * allow ui components to render success/error colors * allow plugins to send sigint/sigkill to processes, as well as receive their pid * ui component fixes and send originating plugin context with run_action * originating plugin for new tab too * make plugn show pane command also expand stacked panes * add sequence plugin * remove macros * remove snapshots * add shortcut for sequence plugin * fix plugin tests * plugin tests for new apis * update snapshots for sequence shortcut * cleanups and formatting * Event::CwdChanged * moar cleanups * remove some warnings * remove mroe warnings * remove dead code * remove more dead code * fix minor bugs in sequence plugin * update sequence help and format codebase * fix conditional blocking cli commands * fix initial-command new-tab cli flag * add client_ids to CwdChanged Event * last fixes * add pr
Diffstat (limited to 'zellij-utils/src/plugin_api/plugin_command.rs')
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs259
1 files changed, 207 insertions, 52 deletions
diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs
index c1ba877e..771bb9ec 100644
--- a/zellij-utils/src/plugin_api/plugin_command.rs
+++ b/zellij-utils/src/plugin_api/plugin_command.rs
@@ -1,17 +1,19 @@
pub use super::generated_api::api::{
- action::{PaneIdAndShouldFloat, SwitchToModePayload},
+ action::{Action as ProtobufAction, PaneIdAndShouldFloat, SwitchToModePayload},
event::{EventNameList as ProtobufEventNameList, Header},
input_mode::InputMode as ProtobufInputMode,
plugin_command::{
- plugin_command::Payload, BreakPanesToNewTabPayload, BreakPanesToTabWithIndexPayload,
- ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload,
- ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseMultiplePanesPayload,
- CloseTabWithIndexPayload, CommandName, ContextItem,
- CreateTokenResponse as ProtobufCreateTokenResponse, CreateTokenResponse,
- EditScrollbackForPaneWithIdPayload, EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload,
+ get_pane_pid_response, plugin_command::Payload, BreakPanesToNewTabPayload,
+ BreakPanesToTabWithIndexPayload, ChangeFloatingPanesCoordinatesPayload,
+ ChangeHostFolderPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload,
+ CloseMultiplePanesPayload, CloseTabWithIndexPayload, CommandName, ContextItem,
+ CopyToClipboardPayload, CreateTokenResponse as ProtobufCreateTokenResponse,
+ CreateTokenResponse, CursorPosition, EditScrollbackForPaneWithIdPayload,
+ EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload,
FixedOrPercent as ProtobufFixedOrPercent,
FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatMultiplePanesPayload,
FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, GenerateWebLoginTokenPayload,
+ GetPanePidPayload, GetPanePidResponse as ProtobufGetPanePidResponse,
GetPaneScrollbackPayload, GroupAndUngroupPanesPayload, HidePaneWithIdPayload,
HighlightAndUnhighlightPanesPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName,
KeyToRebind, KeyToUnbind, KillSessionsPayload, ListTokensResponse, LoadNewPluginPayload,
@@ -29,20 +31,21 @@ pub use super::generated_api::api::{
RenameWebTokenResponse, ReplacePaneWithExistingPanePayload, RequestPluginPermissionPayload,
RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload,
RevokeAllWebTokensResponse, RevokeTokenResponse, RevokeWebLoginTokenPayload,
- RunCommandPayload, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload,
- ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetFloatingPanePinnedPayload,
- SetSelfMouseSelectionSupportPayload, SetTimeoutPayload, ShowPaneWithIdPayload,
- StackPanesPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToPayload,
- TogglePaneEmbedOrEjectForPaneIdPayload, TogglePaneIdFullscreenPayload, UnsubscribePayload,
- WebRequestPayload, WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
+ RunActionPayload, RunCommandPayload, ScrollDownInPaneIdPayload,
+ ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload,
+ SetFloatingPanePinnedPayload, SetSelfMouseSelectionSupportPayload, SetTimeoutPayload,
+ ShowCursorPayload, ShowPaneWithIdPayload, StackPanesPayload, SubscribePayload,
+ SwitchSessionPayload, SwitchTabToPayload, TogglePaneEmbedOrEjectForPaneIdPayload,
+ TogglePaneIdFullscreenPayload, UnsubscribePayload, WebRequestPayload,
+ WriteCharsToPaneIdPayload, WriteToPaneIdPayload,
},
plugin_permission::PermissionType as ProtobufPermissionType,
resize::ResizeAction as ProtobufResizeAction,
};
use crate::data::{
- ConnectToSession, FloatingPaneCoordinates, HttpVerb, InputMode, KeyWithModifier,
- MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand,
+ ConnectToSession, FloatingPaneCoordinates, GetPanePidResponse, HttpVerb, InputMode,
+ KeyWithModifier, MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand,
};
use crate::input::actions::Action;
use crate::input::layout::SplitSize;
@@ -200,6 +203,30 @@ impl TryFrom<PaneId> for ProtobufPaneId {
}
}
+impl TryFrom<ProtobufGetPanePidResponse> for GetPanePidResponse {
+ type Error = &'static str;
+ fn try_from(protobuf_response: ProtobufGetPanePidResponse) -> Result<Self, &'static str> {
+ match protobuf_response.result {
+ Some(get_pane_pid_response::Result::Pid(pid)) => Ok(GetPanePidResponse::Ok(pid)),
+ Some(get_pane_pid_response::Result::Error(error)) => Ok(GetPanePidResponse::Err(error)),
+ None => Err("Empty GetPanePidResponse"),
+ }
+ }
+}
+
+impl From<GetPanePidResponse> for ProtobufGetPanePidResponse {
+ fn from(response: GetPanePidResponse) -> Self {
+ match response {
+ GetPanePidResponse::Ok(pid) => ProtobufGetPanePidResponse {
+ result: Some(get_pane_pid_response::Result::Pid(pid)),
+ },
+ GetPanePidResponse::Err(error) => ProtobufGetPanePidResponse {
+ result: Some(get_pane_pid_response::Result::Error(error)),
+ },
+ }
+ }
+}
+
impl TryFrom<(InputMode, KeyWithModifier, Vec<Action>)> for KeyToRebind {
type Error = &'static str;
fn try_from(
@@ -288,6 +315,14 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for SetSelectable"),
},
+ Some(CommandName::ShowCursor) => match protobuf_plugin_command.payload {
+ Some(Payload::ShowCursorPayload(payload)) => {
+ let cursor_position =
+ payload.position.map(|pos| (pos.x as usize, pos.y as usize));
+ Ok(PluginCommand::ShowCursor(cursor_position))
+ },
+ _ => Err("Mismatched payload for ShowCursor"),
+ },
Some(CommandName::GetPluginIds) => {
if protobuf_plugin_command.payload.is_some() {
Err("GetPluginIds should not have a payload")
@@ -734,7 +769,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(Payload::FocusTerminalPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
- Ok(PluginCommand::FocusTerminalPane(pane_id, should_float))
+ let should_be_in_place = payload.should_be_in_place;
+ Ok(PluginCommand::FocusTerminalPane(
+ pane_id,
+ should_float,
+ should_be_in_place,
+ ))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
@@ -742,7 +782,12 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
Some(Payload::FocusPluginPanePayload(payload)) => {
let pane_id = payload.pane_id as u32;
let should_float = payload.should_float;
- Ok(PluginCommand::FocusPluginPane(pane_id, should_float))
+ let should_be_in_place = payload.should_be_in_place;
+ Ok(PluginCommand::FocusPluginPane(
+ pane_id,
+ should_float,
+ should_be_in_place,
+ ))
},
_ => Err("Mismatched payload for ClosePluginPane"),
},
@@ -1048,9 +1093,11 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
.and_then(|p_id| PaneId::try_from(p_id).ok())
.ok_or("Failed to parse ShowPaneWithId command")?;
let should_float_if_hidden = show_pane_with_id_payload.should_float_if_hidden;
+ let should_focus_pane = show_pane_with_id_payload.should_focus_pane;
Ok(PluginCommand::ShowPaneWithId(
pane_id,
should_float_if_hidden,
+ should_focus_pane,
))
},
_ => Err("Mismatched payload for ShowPaneWithId"),
@@ -1145,6 +1192,29 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
},
_ => Err("Mismatched payload for WriteCharsCharsToPaneId"),
},
+ Some(CommandName::SendSigintToPaneId) => match protobuf_plugin_command.payload {
+ Some(Payload::SendSigintToPaneIdPayload(pane_id)) => {
+ Ok(PluginCommand::SendSigintToPaneId(pane_id.try_into()?))
+ },
+ _ => Err("Mismatched payload for SendSigintToPaneId"),
+ },
+ Some(CommandName::SendSigkillToPaneId) => match protobuf_plugin_command.payload {
+ Some(Payload::SendSigkillToPaneIdPayload(pane_id)) => {
+ Ok(PluginCommand::SendSigkillToPaneId(pane_id.try_into()?))
+ },
+ _ => Err("Mismatched payload for SendSigkillToPaneId"),
+ },
+ Some(CommandName::GetPanePid) => match protobuf_plugin_command.payload {
+ Some(Payload::GetPanePidPayload(get_pane_pid_payload)) => {
+ match get_pane_pid_payload.pane_id {
+ Some(pane_id) => Ok(PluginCommand::GetPanePid {
+ pane_id: pane_id.try_into()?,
+ }),
+ _ => Err("Malformed get_pane_pid payload"),
+ }
+ },
+ _ => Err("Mismatched payload for GetPanePid"),
+ },
Some(CommandName::MovePaneWithPaneId) => match protobuf_plugin_command.payload {
Some(Payload::MovePaneWithPaneIdPayload(move_pane_with_pane_id_payload)) => {
match move_pane_with_pane_id_payload.pane_id {
@@ -1745,9 +1815,35 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
.existing_pane_id
.and_then(|p_id| PaneId::try_from(p_id).ok())
.ok_or("Failed to parse ReplacePaneWithExistingPanePayload")?,
+ replace_pane_with_other_pane_payload.suppress_replaced_pane,
)),
_ => Err("Mismatched payload for ReplacePaneWithExistingPane"),
},
+ Some(CommandName::RunAction) => match protobuf_plugin_command.payload {
+ Some(Payload::RunActionPayload(protobuf_payload)) => {
+ let action = Action::try_from(
+ protobuf_payload
+ .action
+ .ok_or("Missing action in RunAction payload")?,
+ )
+ .map_err(|_| "Failed to convert protobuf action")?;
+
+ let context: BTreeMap<String, String> = protobuf_payload
+ .context
+ .into_iter()
+ .map(|item| (item.name, item.value))
+ .collect();
+
+ Ok(PluginCommand::RunAction(action, context))
+ },
+ _ => Err("Mismatched payload for RunAction"),
+ },
+ Some(CommandName::CopyToClipboard) => match protobuf_plugin_command.payload {
+ Some(Payload::CopyToClipboardPayload(payload)) => {
+ Ok(PluginCommand::CopyToClipboard(payload.text))
+ },
+ _ => Err("Mismatched payload for CopyToClipboard"),
+ },
None => Err("Unrecognized plugin command"),
}
}
@@ -1779,6 +1875,16 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::SetSelectable as i32,
payload: Some(Payload::SetSelectablePayload(should_be_selectable)),
}),
+ PluginCommand::ShowCursor(cursor_position) => {
+ let position = cursor_position.map(|(x, y)| CursorPosition {
+ x: x as u32,
+ y: y as u32,
+ });
+ Ok(ProtobufPluginCommand {
+ name: CommandName::ShowCursor as i32,
+ payload: Some(Payload::ShowCursorPayload(ShowCursorPayload { position })),
+ })
+ },
PluginCommand::GetPluginIds => Ok(ProtobufPluginCommand {
name: CommandName::GetPluginIds as i32,
payload: None,
@@ -2077,24 +2183,30 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::ClosePluginPane as i32,
payload: Some(Payload::ClosePluginPanePayload(pane_id)),
}),
- PluginCommand::FocusTerminalPane(pane_id, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::FocusTerminalPane as i32,
- payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat {
- pane_id: pane_id,
- should_float: should_float_if_hidden,
- })),
- })
- },
- PluginCommand::FocusPluginPane(pane_id, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::FocusPluginPane as i32,
- payload: Some(Payload::FocusPluginPanePayload(PaneIdAndShouldFloat {
- pane_id: pane_id,
- should_float: should_float_if_hidden,
- })),
- })
- },
+ PluginCommand::FocusTerminalPane(
+ pane_id,
+ should_float_if_hidden,
+ should_be_in_place_if_hidden,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::FocusTerminalPane as i32,
+ payload: Some(Payload::FocusTerminalPanePayload(PaneIdAndShouldFloat {
+ pane_id: pane_id,
+ should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
+ })),
+ }),
+ PluginCommand::FocusPluginPane(
+ pane_id,
+ should_float_if_hidden,
+ should_be_in_place_if_hidden,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::FocusPluginPane as i32,
+ payload: Some(Payload::FocusPluginPanePayload(PaneIdAndShouldFloat {
+ pane_id: pane_id,
+ should_float: should_float_if_hidden,
+ should_be_in_place: should_be_in_place_if_hidden,
+ })),
+ }),
PluginCommand::RenameTerminalPane(pane_id, new_name) => Ok(ProtobufPluginCommand {
name: CommandName::RenameTerminalPane as i32,
payload: Some(Payload::RenameTerminalPanePayload(IdAndNewName {
@@ -2335,15 +2447,18 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
pane_id: ProtobufPaneId::try_from(pane_id_to_hide).ok(),
})),
}),
- PluginCommand::ShowPaneWithId(pane_id_to_show, should_float_if_hidden) => {
- Ok(ProtobufPluginCommand {
- name: CommandName::ShowPaneWithId as i32,
- payload: Some(Payload::ShowPaneWithIdPayload(ShowPaneWithIdPayload {
- pane_id: ProtobufPaneId::try_from(pane_id_to_show).ok(),
- should_float_if_hidden,
- })),
- })
- },
+ PluginCommand::ShowPaneWithId(
+ pane_id_to_show,
+ should_float_if_hidden,
+ should_focus_pane,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::ShowPaneWithId as i32,
+ payload: Some(Payload::ShowPaneWithIdPayload(ShowPaneWithIdPayload {
+ pane_id: ProtobufPaneId::try_from(pane_id_to_show).ok(),
+ should_float_if_hidden,
+ should_focus_pane,
+ })),
+ }),
PluginCommand::OpenCommandPaneBackground(command_to_run, context) => {
let context: Vec<_> = context
.into_iter()
@@ -2415,6 +2530,20 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
)),
})
},
+ PluginCommand::SendSigintToPaneId(pane_id) => Ok(ProtobufPluginCommand {
+ name: CommandName::SendSigintToPaneId as i32,
+ payload: Some(Payload::SendSigintToPaneIdPayload(pane_id.try_into()?)),
+ }),
+ PluginCommand::SendSigkillToPaneId(pane_id) => Ok(ProtobufPluginCommand {
+ name: CommandName::SendSigkillToPaneId as i32,
+ payload: Some(Payload::SendSigkillToPaneIdPayload(pane_id.try_into()?)),
+ }),
+ PluginCommand::GetPanePid { pane_id } => Ok(ProtobufPluginCommand {
+ name: CommandName::GetPanePid as i32,
+ payload: Some(Payload::GetPanePidPayload(GetPanePidPayload {
+ pane_id: Some(pane_id.try_into()?),
+ })),
+ }),
PluginCommand::MovePaneWithPaneId(pane_id) => Ok(ProtobufPluginCommand {
name: CommandName::MovePaneWithPaneId as i32,
payload: Some(Payload::MovePaneWithPaneIdPayload(
@@ -2901,17 +3030,43 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
name: CommandName::ClearKeyPressesIntercepts as i32,
payload: None,
}),
- PluginCommand::ReplacePaneWithExistingPane(pane_id_to_replace, existing_pane_id) => {
+ PluginCommand::ReplacePaneWithExistingPane(
+ pane_id_to_replace,
+ existing_pane_id,
+ suppress_replaced_pane,
+ ) => Ok(ProtobufPluginCommand {
+ name: CommandName::ReplacePaneWithExistingPane as i32,
+ payload: Some(Payload::ReplacePaneWithExistingPanePayload(
+ ReplacePaneWithExistingPanePayload {
+ pane_id_to_replace: ProtobufPaneId::try_from(pane_id_to_replace).ok(),
+ existing_pane_id: ProtobufPaneId::try_from(existing_pane_id).ok(),
+ suppress_replaced_pane,
+ },
+ )),
+ }),
+ PluginCommand::RunAction(action, context) => {
+ let protobuf_action = ProtobufAction::try_from(action)
+ .map_err(|_| "Failed to convert action to protobuf")?;
+
+ let context_items: Vec<ContextItem> = context
+ .into_iter()
+ .map(|(name, value)| ContextItem { name, value })
+ .collect();
+
Ok(ProtobufPluginCommand {
- name: CommandName::ReplacePaneWithExistingPane as i32,
- payload: Some(Payload::ReplacePaneWithExistingPanePayload(
- ReplacePaneWithExistingPanePayload {
- pane_id_to_replace: ProtobufPaneId::try_from(pane_id_to_replace).ok(),
- existing_pane_id: ProtobufPaneId::try_from(existing_pane_id).ok(),
- },
- )),
+ name: CommandName::RunAction as i32,
+ payload: Some(Payload::RunActionPayload(RunActionPayload {
+ action: Some(protobuf_action),
+ context: context_items,
+ })),
})
},
+ PluginCommand::CopyToClipboard(text) => Ok(ProtobufPluginCommand {
+ name: CommandName::CopyToClipboard as i32,
+ payload: Some(Payload::CopyToClipboardPayload(CopyToClipboardPayload {
+ text,
+ })),
+ }),
}
}
}