aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-utils/src/plugin_api
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-03-05 21:13:02 +0100
committerGitHub <noreply@github.com>2026-03-05 21:13:02 +0100
commit903cdf5d32476a8d6cc208d2c418a1f2c67e5a53 (patch)
tree9a4647d9dc3efcdea8149fd78fdfa956b3c8d8cb /zellij-utils/src/plugin_api
parentbuild: make test mirror build command for plugins (#4801) (diff)
downloadsl0ppij-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.proto9
-rw-r--r--zellij-utils/src/plugin_api/event.rs41
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.proto53
-rw-r--r--zellij-utils/src/plugin_api/plugin_command.rs228
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(),
+ },
+ )),
+ }),
}
}
}