diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-12-11 14:25:42 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-11 14:25:42 +0100 |
| commit | 693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch) | |
| tree | b3f651462bbc1cd2fa5b2d79207343413ef6b83c /zellij-utils/src/plugin_api/plugin_command.rs | |
| parent | fix(zellij-server): unblock building on 32bit targets (#4484) (diff) | |
| download | sl0ppij-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.rs | 259 |
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, + })), + }), } } } |
