diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-03-05 21:13:02 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-05 21:13:02 +0100 |
| commit | 903cdf5d32476a8d6cc208d2c418a1f2c67e5a53 (patch) | |
| tree | 9a4647d9dc3efcdea8149fd78fdfa956b3c8d8cb /zellij-utils/src/plugin_api | |
| parent | build: make test mirror build command for plugins (#4801) (diff) | |
| download | sl0ppij-903cdf5d32476a8d6cc208d2c418a1f2c67e5a53.tar.gz sl0ppij-903cdf5d32476a8d6cc208d2c418a1f2c67e5a53.tar.xz sl0ppij-903cdf5d32476a8d6cc208d2c418a1f2c67e5a53.zip | |
Click file paths to open in editor, allow plugins to change styles and hover of viewport (#4802)
* initial implementation
* implement hover
* add styles and foreground/background emphases, fix upsert hover collisions
* initial implementation of link plugin
* suppress highlighting + clicks on unfocused and mouse mode panes
* also make italic
* fix env var and ~ expansions
* fix regex issues and add defensive validations
* fix tests and add plugin assets
* add tests
* refactor all the things
* add pr
Diffstat (limited to 'zellij-utils/src/plugin_api')
| -rw-r--r-- | zellij-utils/src/plugin_api/event.proto | 9 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/event.rs | 41 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.proto | 53 | ||||
| -rw-r--r-- | zellij-utils/src/plugin_api/plugin_command.rs | 228 |
4 files changed, 310 insertions, 21 deletions
diff --git a/zellij-utils/src/plugin_api/event.proto b/zellij-utils/src/plugin_api/event.proto index bbee50a3..ce018a7f 100644 --- a/zellij-utils/src/plugin_api/event.proto +++ b/zellij-utils/src/plugin_api/event.proto @@ -64,6 +64,7 @@ enum EventType { CwdChanged = 39; AvailableLayoutInfo = 40; PluginConfigurationChanged = 41; + HighlightClicked = 42; } message EventNameList { @@ -107,6 +108,7 @@ message Event { CwdChangedPayload cwd_changed_payload = 33; AvailableLayoutInfoPayload available_layout_info_payload = 34; PluginConfigurationChangedPayload plugin_configuration_changed_payload = 35; + HighlightClickedPayload highlight_clicked_payload = 36; } } @@ -516,3 +518,10 @@ message PluginConfigurationChangedPayload { repeated ContextItem configuration = 1; } +message HighlightClickedPayload { + PaneId pane_id = 1; + string pattern = 2; + string matched_string = 3; + repeated ContextItem context = 4; +} + diff --git a/zellij-utils/src/plugin_api/event.rs b/zellij-utils/src/plugin_api/event.rs index 631d5069..267e1357 100644 --- a/zellij-utils/src/plugin_api/event.rs +++ b/zellij-utils/src/plugin_api/event.rs @@ -500,6 +500,26 @@ impl TryFrom<ProtobufEvent> for Event { }, _ => Err("Malformed payload for PluginConfigurationChanged Event"), }, + Some(ProtobufEventType::HighlightClicked) => match protobuf_event.payload { + Some(ProtobufEventPayload::HighlightClickedPayload(p)) => { + let pane_id = p + .pane_id + .ok_or("Missing pane_id in HighlightClicked")? + .try_into()?; + let context = p + .context + .into_iter() + .map(|item| (item.name, item.value)) + .collect(); + Ok(Event::HighlightClicked { + pane_id, + pattern: p.pattern, + matched_string: p.matched_string, + context, + }) + }, + _ => Err("Malformed payload for HighlightClicked Event"), + }, None => Err("Unknown Protobuf Event"), } } @@ -991,6 +1011,25 @@ impl TryFrom<Event> for ProtobufEvent { payload: Some(event::Payload::PluginConfigurationChangedPayload(payload)), }) }, + Event::HighlightClicked { + pane_id, + pattern, + matched_string, + context, + } => Ok(ProtobufEvent { + name: ProtobufEventType::HighlightClicked as i32, + payload: Some(event::Payload::HighlightClickedPayload( + HighlightClickedPayload { + pane_id: pane_id.try_into().ok(), + pattern, + matched_string, + context: context + .into_iter() + .map(|(name, value)| ProtobufContextItem { name, value }) + .collect(), + }, + )), + }), } } } @@ -1901,6 +1940,7 @@ impl TryFrom<ProtobufEventType> for EventType { ProtobufEventType::CwdChanged => EventType::CwdChanged, ProtobufEventType::AvailableLayoutInfo => EventType::AvailableLayoutInfo, ProtobufEventType::PluginConfigurationChanged => EventType::PluginConfigurationChanged, + ProtobufEventType::HighlightClicked => EventType::HighlightClicked, }) } } @@ -1950,6 +1990,7 @@ impl TryFrom<EventType> for ProtobufEventType { EventType::CwdChanged => ProtobufEventType::CwdChanged, EventType::AvailableLayoutInfo => ProtobufEventType::AvailableLayoutInfo, EventType::PluginConfigurationChanged => ProtobufEventType::PluginConfigurationChanged, + EventType::HighlightClicked => ProtobufEventType::HighlightClicked, }) } } diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index 53e9d729..c4fa949f 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -199,6 +199,8 @@ enum CommandName { HideFloatingPanes = 204; ShowFloatingPanes = 205; SetPaneColor = 206; + SetPaneRegexHighlights = 207; + ClearPaneHighlights = 208; } message PluginCommand { @@ -347,6 +349,8 @@ message PluginCommand { HideFloatingPanesPayload hide_floating_panes_payload = 155; ShowFloatingPanesPayload show_floating_panes_payload = 156; SetPaneColorPayload set_pane_color_payload = 157; + SetPaneRegexHighlightsPayload set_pane_regex_highlights_payload = 158; + ClearPaneHighlightsPayload clear_pane_highlights_payload = 159; } } @@ -1220,3 +1224,52 @@ message SetPaneColorPayload { optional string fg = 2; optional string bg = 3; } + +message HighlightStyle { + oneof style { + bool emphasis_0 = 1; + bool emphasis_1 = 2; + bool emphasis_2 = 3; + bool emphasis_3 = 4; + CustomRgbHighlight custom_rgb = 5; + CustomIndexHighlight custom_index = 6; + bool background_emphasis_0 = 7; + bool background_emphasis_1 = 8; + bool background_emphasis_2 = 9; + bool background_emphasis_3 = 10; + bool none = 11; + } +} + +message CustomRgbHighlight { + optional uint32 fg_r = 1; + optional uint32 fg_g = 2; + optional uint32 fg_b = 3; + optional uint32 bg_r = 4; + optional uint32 bg_g = 5; + optional uint32 bg_b = 6; +} + +message CustomIndexHighlight { + optional uint32 fg = 1; + optional uint32 bg = 2; +} + +message RegexHighlight { + string pattern = 1; + HighlightStyle style = 2; + repeated ContextItem context = 3; + bool on_hover = 4; + bool bold = 5; + bool italic = 6; + bool underline = 7; +} + +message SetPaneRegexHighlightsPayload { + PaneId pane_id = 1; + repeated RegexHighlight highlights = 2; +} + +message ClearPaneHighlightsPayload { + PaneId pane_id = 1; +} diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index abd9a994..b535e14a 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -7,7 +7,8 @@ pub use super::generated_api::api::{ break_panes_to_tab_with_index_response, delete_layout_response, dump_layout_response, dump_session_layout_response, edit_layout_response, focus_or_create_tab_response, get_focused_pane_info_response, get_pane_cwd_response, get_pane_pid_response, - get_pane_running_command_response, hide_floating_panes_response, new_tab_response, + get_pane_running_command_response, hide_floating_panes_response, + highlight_style::Style as ProtobufHighlightStyleVariant, new_tab_response, parse_layout_response, plugin_command::Payload, rename_layout_response, save_layout_response, save_session_response, show_floating_panes_response, BreakPanesToNewTabPayload, @@ -16,17 +17,19 @@ pub use super::generated_api::api::{ BreakPanesToTabWithIdResponse as ProtobufBreakPanesToTabWithIdResponse, BreakPanesToTabWithIndexPayload, BreakPanesToTabWithIndexResponse as ProtobufBreakPanesToTabWithIndexResponse, - ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload, + ChangeFloatingPanesCoordinatesPayload, ChangeHostFolderPayload, ClearPaneHighlightsPayload, ClearScreenForPaneIdPayload, CliPipeOutputPayload, CloseMultiplePanesPayload, CloseTabWithIdPayload, CloseTabWithIndexPayload, CommandName, ContextItem, CopyToClipboardPayload, CreateTokenResponse as ProtobufCreateTokenResponse, CreateTokenResponse, CurrentSessionLastSavedTimePayload, CurrentSessionLastSavedTimeResponse as ProtobufCurrentSessionLastSavedTimeResponse, - CursorPosition, DeleteLayoutPayload, DeleteLayoutResponse as ProtobufDeleteLayoutResponse, - DumpLayoutPayload, DumpLayoutResponse as ProtobufDumpLayoutResponse, - DumpSessionLayoutPayload, DumpSessionLayoutResponse as ProtobufDumpSessionLayoutResponse, - EditLayoutPayload, EditLayoutResponse as ProtobufEditLayoutResponse, - EditScrollbackForPaneWithIdPayload, EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload, + CursorPosition, CustomIndexHighlight as ProtobufCustomIndexHighlight, + CustomRgbHighlight as ProtobufCustomRgbHighlight, DeleteLayoutPayload, + DeleteLayoutResponse as ProtobufDeleteLayoutResponse, DumpLayoutPayload, + DumpLayoutResponse as ProtobufDumpLayoutResponse, DumpSessionLayoutPayload, + DumpSessionLayoutResponse as ProtobufDumpSessionLayoutResponse, EditLayoutPayload, + EditLayoutResponse as ProtobufEditLayoutResponse, EditScrollbackForPaneWithIdPayload, + EmbedMultiplePanesPayload, EnvVariable, ExecCmdPayload, FixedOrPercent as ProtobufFixedOrPercent, FixedOrPercentValue as ProtobufFixedOrPercentValue, FloatMultiplePanesPayload, FloatingPaneCoordinates as ProtobufFloatingPaneCoordinates, @@ -48,10 +51,11 @@ pub use super::generated_api::api::{ GetTabInfoPayload, GetTabInfoResponse as ProtobufGetTabInfoResponse, GoToTabWithIdPayload, GroupAndUngroupPanesPayload, HideFloatingPanesPayload as ProtobufHideFloatingPanesPayload, HideFloatingPanesResponse as ProtobufHideFloatingPanesResponse, HidePaneWithIdPayload, - HighlightAndUnhighlightPanesPayload, HttpVerb as ProtobufHttpVerb, IdAndNewName, - KeyToRebind, KeyToUnbind, KillSessionsPayload, ListTokensResponse, LoadNewPluginPayload, - MessageToPluginPayload, MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, - MovePayload, NewPluginArgs as ProtobufNewPluginArgs, NewTabPayload, + HighlightAndUnhighlightPanesPayload, HighlightStyle as ProtobufHighlightStyle, + HttpVerb as ProtobufHttpVerb, IdAndNewName, KeyToRebind, KeyToUnbind, KillSessionsPayload, + ListTokensResponse, LoadNewPluginPayload, MessageToPluginPayload, + MovePaneWithPaneIdInDirectionPayload, MovePaneWithPaneIdPayload, MovePayload, + NewPluginArgs as ProtobufNewPluginArgs, NewTabPayload, NewTabResponse as ProtobufNewTabResponse, NewTabsResponse as ProtobufNewTabsResponse, NewTabsWithLayoutInfoPayload, OpenCommandPaneBackgroundResponse as ProtobufOpenCommandPaneBackgroundResponse, @@ -92,19 +96,19 @@ pub use super::generated_api::api::{ PageScrollDownInPaneIdPayload, PageScrollUpInPaneIdPayload, PaneId as ProtobufPaneId, PaneIdAndFloatingPaneCoordinates, PaneType as ProtobufPaneType, ParseLayoutPayload, ParseLayoutResponse as ProtobufParseLayoutResponse, PluginCommand as ProtobufPluginCommand, - PluginMessagePayload, RebindKeysPayload, ReconfigurePayload, ReloadPluginPayload, - RenameLayoutPayload, RenameLayoutResponse as ProtobufRenameLayoutResponse, - RenameTabWithIdPayload, RenameWebLoginTokenPayload, RenameWebTokenResponse, - ReplacePaneWithExistingPanePayload, RequestPluginPermissionPayload, - RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, ResizePayload, - RevokeAllWebTokensResponse, RevokeTokenResponse, RevokeWebLoginTokenPayload, + PluginMessagePayload, RebindKeysPayload, ReconfigurePayload, + RegexHighlight as ProtobufRegexHighlight, ReloadPluginPayload, RenameLayoutPayload, + RenameLayoutResponse as ProtobufRenameLayoutResponse, RenameTabWithIdPayload, + RenameWebLoginTokenPayload, RenameWebTokenResponse, ReplacePaneWithExistingPanePayload, + RequestPluginPermissionPayload, RerunCommandPanePayload, ResizePaneIdWithDirectionPayload, + ResizePayload, RevokeAllWebTokensResponse, RevokeTokenResponse, RevokeWebLoginTokenPayload, RunActionPayload, RunCommandPayload, RunningCommand as ProtobufRunningCommand, SaveLayoutPayload, SaveLayoutResponse as ProtobufSaveLayoutResponse, SaveSessionPayload, SaveSessionResponse as ProtobufSaveSessionResponse, ScrollDownInPaneIdPayload, ScrollToBottomInPaneIdPayload, ScrollToTopInPaneIdPayload, ScrollUpInPaneIdPayload, SetFloatingPanePinnedPayload, SetPaneBorderlessPayload, SetPaneColorPayload, - SetSelfMouseSelectionSupportPayload, SetTimeoutPayload, ShowCursorPayload, - ShowFloatingPanesPayload as ProtobufShowFloatingPanesPayload, + SetPaneRegexHighlightsPayload, SetSelfMouseSelectionSupportPayload, SetTimeoutPayload, + ShowCursorPayload, ShowFloatingPanesPayload as ProtobufShowFloatingPanesPayload, ShowFloatingPanesResponse as ProtobufShowFloatingPanesResponse, ShowPaneWithIdPayload, StackPanesPayload, SubscribePayload, SwitchSessionPayload, SwitchTabToIdPayload, SwitchTabToPayload, TogglePaneBorderlessPayload, TogglePaneEmbedOrEjectForPaneIdPayload, @@ -118,8 +122,9 @@ pub use super::generated_api::api::{ use crate::data::{ ConnectToSession, DeleteLayoutResponse, EditLayoutResponse, FloatingPaneCoordinates, GetFocusedPaneInfoResponse, GetPaneCwdResponse, GetPanePidResponse, - GetPaneRunningCommandResponse, HttpVerb, InputMode, KeyWithModifier, MessageToPlugin, - NewPluginArgs, PaneId, PermissionType, PluginCommand, RenameLayoutResponse, SaveLayoutResponse, + GetPaneRunningCommandResponse, HighlightStyle, HttpVerb, InputMode, KeyWithModifier, + MessageToPlugin, NewPluginArgs, PaneId, PermissionType, PluginCommand, RegexHighlight, + RenameLayoutResponse, SaveLayoutResponse, }; use crate::input::actions::Action; use crate::input::layout::PercentOrFixed; @@ -2434,6 +2439,100 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { }, _ => Err("Mismatched payload for SetPaneColor"), }, + Some(CommandName::SetPaneRegexHighlights) => { + match protobuf_plugin_command.payload { + Some(Payload::SetPaneRegexHighlightsPayload(p)) => { + let pane_id: PaneId = p + .pane_id + .ok_or("Missing pane_id in SetPaneRegexHighlights")? + .try_into()?; + let highlights = p + .highlights + .into_iter() + .map(|h| { + let style = match h.style.and_then(|s| s.style) { + Some(ProtobufHighlightStyleVariant::Emphasis0(_)) => { + HighlightStyle::Emphasis0 + }, + Some(ProtobufHighlightStyleVariant::Emphasis1(_)) => { + HighlightStyle::Emphasis1 + }, + Some(ProtobufHighlightStyleVariant::Emphasis2(_)) => { + HighlightStyle::Emphasis2 + }, + Some(ProtobufHighlightStyleVariant::Emphasis3(_)) => { + HighlightStyle::Emphasis3 + }, + Some(ProtobufHighlightStyleVariant::CustomRgb(c)) => { + HighlightStyle::CustomRgb { + fg: match (c.fg_r, c.fg_g, c.fg_b) { + (Some(r), Some(g), Some(b)) => { + Some((r as u8, g as u8, b as u8)) + }, + _ => None, + }, + bg: match (c.bg_r, c.bg_g, c.bg_b) { + (Some(r), Some(g), Some(b)) => { + Some((r as u8, g as u8, b as u8)) + }, + _ => None, + }, + } + }, + Some(ProtobufHighlightStyleVariant::CustomIndex(c)) => { + HighlightStyle::CustomIndex { + fg: c.fg.map(|v| v as u8), + bg: c.bg.map(|v| v as u8), + } + }, + Some(ProtobufHighlightStyleVariant::BackgroundEmphasis0(_)) => { + HighlightStyle::BackgroundEmphasis0 + }, + Some(ProtobufHighlightStyleVariant::BackgroundEmphasis1(_)) => { + HighlightStyle::BackgroundEmphasis1 + }, + Some(ProtobufHighlightStyleVariant::BackgroundEmphasis2(_)) => { + HighlightStyle::BackgroundEmphasis2 + }, + Some(ProtobufHighlightStyleVariant::BackgroundEmphasis3(_)) => { + HighlightStyle::BackgroundEmphasis3 + }, + Some(ProtobufHighlightStyleVariant::None(_)) => { + HighlightStyle::None + }, + None => HighlightStyle::Emphasis0, // fallback + }; + let context = h + .context + .into_iter() + .map(|item| (item.name, item.value)) + .collect(); + RegexHighlight { + pattern: h.pattern, + style, + context, + on_hover: h.on_hover, + bold: h.bold, + italic: h.italic, + underline: h.underline, + } + }) + .collect(); + Ok(PluginCommand::SetPaneRegexHighlights(pane_id, highlights)) + }, + _ => Err("Mismatched payload for SetPaneRegexHighlights"), + } + }, + Some(CommandName::ClearPaneHighlights) => match protobuf_plugin_command.payload { + Some(Payload::ClearPaneHighlightsPayload(p)) => { + let pane_id: PaneId = p + .pane_id + .ok_or("Missing pane_id in ClearPaneHighlights")? + .try_into()?; + Ok(PluginCommand::ClearPaneHighlights(pane_id)) + }, + _ => Err("Mismatched payload for ClearPaneHighlights"), + }, None => Err("Unrecognized plugin command"), } } @@ -3994,6 +4093,93 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { bg, })), }), + PluginCommand::SetPaneRegexHighlights(pane_id, highlights) => { + Ok(ProtobufPluginCommand { + name: CommandName::SetPaneRegexHighlights as i32, + payload: Some(Payload::SetPaneRegexHighlightsPayload( + SetPaneRegexHighlightsPayload { + pane_id: pane_id.try_into().ok(), + highlights: highlights + .into_iter() + .map(|h| { + let style_variant = match h.style { + HighlightStyle::Emphasis0 => { + ProtobufHighlightStyleVariant::Emphasis0(true) + }, + HighlightStyle::Emphasis1 => { + ProtobufHighlightStyleVariant::Emphasis1(true) + }, + HighlightStyle::Emphasis2 => { + ProtobufHighlightStyleVariant::Emphasis2(true) + }, + HighlightStyle::Emphasis3 => { + ProtobufHighlightStyleVariant::Emphasis3(true) + }, + HighlightStyle::CustomRgb { fg, bg } => { + ProtobufHighlightStyleVariant::CustomRgb( + ProtobufCustomRgbHighlight { + fg_r: fg.map(|(r, _, _)| r as u32), + fg_g: fg.map(|(_, g, _)| g as u32), + fg_b: fg.map(|(_, _, b)| b as u32), + bg_r: bg.map(|(r, _, _)| r as u32), + bg_g: bg.map(|(_, g, _)| g as u32), + bg_b: bg.map(|(_, _, b)| b as u32), + }, + ) + }, + HighlightStyle::CustomIndex { fg, bg } => { + ProtobufHighlightStyleVariant::CustomIndex( + ProtobufCustomIndexHighlight { + fg: fg.map(|v| v as u32), + bg: bg.map(|v| v as u32), + }, + ) + }, + HighlightStyle::BackgroundEmphasis0 => { + ProtobufHighlightStyleVariant::BackgroundEmphasis0(true) + }, + HighlightStyle::BackgroundEmphasis1 => { + ProtobufHighlightStyleVariant::BackgroundEmphasis1(true) + }, + HighlightStyle::BackgroundEmphasis2 => { + ProtobufHighlightStyleVariant::BackgroundEmphasis2(true) + }, + HighlightStyle::BackgroundEmphasis3 => { + ProtobufHighlightStyleVariant::BackgroundEmphasis3(true) + }, + HighlightStyle::None => { + ProtobufHighlightStyleVariant::None(true) + }, + }; + ProtobufRegexHighlight { + pattern: h.pattern, + style: Some(ProtobufHighlightStyle { + style: Some(style_variant), + }), + context: h + .context + .into_iter() + .map(|(name, value)| ContextItem { name, value }) + .collect(), + on_hover: h.on_hover, + bold: h.bold, + italic: h.italic, + underline: h.underline, + } + }) + .collect(), + }, + )), + }) + }, + PluginCommand::ClearPaneHighlights(pane_id) => Ok(ProtobufPluginCommand { + name: CommandName::ClearPaneHighlights as i32, + payload: Some(Payload::ClearPaneHighlightsPayload( + ClearPaneHighlightsPayload { + pane_id: pane_id.try_into().ok(), + }, + )), + }), } } } |
