aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src/plugins
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2026-06-29 18:10:59 +0200
committerGitHub <noreply@github.com>2026-06-29 18:10:59 +0200
commit245f5141c9bb7eb91f003efac4defc94470243fd (patch)
tree52ae6d003366f97e41c8a1450602aef63c8fdca2 /zellij-server/src/plugins
parentfeat: reload config on theme change (#5135) (diff)
downloadsl0ppij-245f5141c9bb7eb91f003efac4defc94470243fd.tar.gz
sl0ppij-245f5141c9bb7eb91f003efac4defc94470243fd.tar.xz
sl0ppij-245f5141c9bb7eb91f003efac4defc94470243fd.zip
Title frames (#5318)
* initial implementation * improve single line title design * hint text plugin infra * add proto regen task * display alt text in status bar * don't display text with full frames * style alt hover text * fix + style control own pane hover * make multiplayer indications responsive * update snapshots * plugin api * single pane as tab * better scroll styling * fix tests * tab-bar plus hover * plugin surface tests * integration tests * adjust all integration tests * reduce hover noise * add pane id to in place * more integration tests * style: rustfmt * update plugin assets * update assets and snapshots * add pr
Diffstat (limited to 'zellij-server/src/plugins')
-rw-r--r--zellij-server/src/plugins/mod.rs5
-rw-r--r--zellij-server/src/plugins/unit/plugin_tests.rs232
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap29
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_plugin_command.snap21
-rw-r--r--zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_with_tab_id_plugin_command.snap21
-rw-r--r--zellij-server/src/plugins/wasm_bridge.rs2
-rw-r--r--zellij-server/src/plugins/zellij_exports.rs39
7 files changed, 347 insertions, 2 deletions
diff --git a/zellij-server/src/plugins/mod.rs b/zellij-server/src/plugins/mod.rs
index 5cc5835b..e92d7204 100644
--- a/zellij-server/src/plugins/mod.rs
+++ b/zellij-server/src/plugins/mod.rs
@@ -773,6 +773,11 @@ pub(crate) fn plugin_thread_main(
if events.contains(&EventType::InitialKeybinds) {
wasm_bridge.send_initial_keybinds_to_plugin(plugin_id, client_id);
}
+ if events.contains(&EventType::HintText) {
+ let _ = bus
+ .senders
+ .send_to_screen(ScreenInstruction::ClearHintTextCache);
+ }
},
PluginInstruction::PermissionRequestResult(
plugin_id,
diff --git a/zellij-server/src/plugins/unit/plugin_tests.rs b/zellij-server/src/plugins/unit/plugin_tests.rs
index 431d550e..a5dd5f9c 100644
--- a/zellij-server/src/plugins/unit/plugin_tests.rs
+++ b/zellij-server/src/plugins/unit/plugin_tests.rs
@@ -3351,6 +3351,238 @@ pub fn toggle_pane_embed_or_eject_plugin_command() {
#[test]
#[ignore]
+pub fn new_pane_plugin_command() {
+ let temp_folder = tempdir().unwrap();
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, pty_receiver, screen_receiver, teardown) =
+ create_plugin_thread_with_pty_receiver(Some(plugin_host_folder), None, None);
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ ..Default::default()
+ });
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_pty_instructions = Arc::new(Mutex::new(vec![]));
+ let pty_thread = log_actions_in_thread!(
+ received_pty_instructions,
+ PtyInstruction::SpawnTerminal,
+ pty_receiver,
+ 1
+ );
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let _screen_thread = grant_permissions_and_log_actions_in_thread_naked_variant!(
+ received_screen_instructions,
+ ScreenInstruction::Exit,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ false,
+ plugin_title,
+ run_plugin,
+ Some(tab_index),
+ None,
+ client_id,
+ size,
+ None,
+ None,
+ false,
+ None,
+ None,
+ None,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(KeyWithModifier::new(BareKey::Char('9'))),
+ )]));
+ pty_thread.join().unwrap();
+ teardown();
+ let new_pane_event = received_pty_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let PtyInstruction::SpawnTerminal(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", new_pane_event));
+}
+
+#[test]
+#[ignore]
+pub fn toggle_floating_panes_plugin_command() {
+ let temp_folder = tempdir().unwrap();
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, screen_receiver, teardown) =
+ create_plugin_thread(Some(plugin_host_folder), None);
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ ..Default::default()
+ });
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let screen_thread = grant_permissions_and_log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::ToggleFloatingPanes,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ false,
+ plugin_title,
+ run_plugin,
+ Some(tab_index),
+ None,
+ client_id,
+ size,
+ None,
+ None,
+ false,
+ None,
+ None,
+ None,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(KeyWithModifier::new(BareKey::Char('0'))),
+ )]));
+ screen_thread.join().unwrap();
+ teardown();
+ let toggle_floating_panes_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::ToggleFloatingPanes(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", toggle_floating_panes_event));
+}
+
+#[test]
+#[ignore]
+pub fn toggle_floating_panes_with_tab_id_plugin_command() {
+ let temp_folder = tempdir().unwrap();
+ let plugin_host_folder = PathBuf::from(temp_folder.path());
+ let cache_path = plugin_host_folder.join("permissions_test.kdl");
+ let (plugin_thread_sender, screen_receiver, teardown) =
+ create_plugin_thread(Some(plugin_host_folder), None);
+ let plugin_should_float = Some(false);
+ let plugin_title = Some("test_plugin".to_owned());
+ let run_plugin = RunPluginOrAlias::RunPlugin(RunPlugin {
+ _allow_exec_host_cmd: false,
+ location: RunPluginLocation::File(PathBuf::from(&*PLUGIN_FIXTURE)),
+ configuration: Default::default(),
+ ..Default::default()
+ });
+ let tab_index = 1;
+ let client_id = 1;
+ let size = Size {
+ cols: 121,
+ rows: 20,
+ };
+ let received_screen_instructions = Arc::new(Mutex::new(vec![]));
+ let screen_thread = grant_permissions_and_log_actions_in_thread!(
+ received_screen_instructions,
+ ScreenInstruction::ToggleFloatingPanesWithTabId,
+ screen_receiver,
+ 1,
+ &PermissionType::ChangeApplicationState,
+ cache_path,
+ plugin_thread_sender,
+ client_id
+ );
+
+ let _ = plugin_thread_sender.send(PluginInstruction::AddClient(client_id));
+ let _ = plugin_thread_sender.send(PluginInstruction::Load(
+ plugin_should_float,
+ false,
+ false,
+ plugin_title,
+ run_plugin,
+ Some(tab_index),
+ None,
+ client_id,
+ size,
+ None,
+ None,
+ false,
+ None,
+ None,
+ None,
+ ));
+ std::thread::sleep(std::time::Duration::from_millis(500));
+ let _ = plugin_thread_sender.send(PluginInstruction::Update(vec![(
+ None,
+ Some(client_id),
+ Event::Key(KeyWithModifier::new(BareKey::Char('i')).with_super_modifier()),
+ )]));
+ screen_thread.join().unwrap();
+ teardown();
+ let toggle_floating_panes_event = received_screen_instructions
+ .lock()
+ .unwrap()
+ .iter()
+ .find_map(|i| {
+ if let ScreenInstruction::ToggleFloatingPanesWithTabId(..) = i {
+ Some(i.clone())
+ } else {
+ None
+ }
+ })
+ .clone();
+ assert_snapshot!(format!("{:#?}", toggle_floating_panes_event));
+}
+
+#[test]
+#[ignore]
pub fn undo_rename_pane_plugin_command() {
let temp_folder = tempdir().unwrap(); // placed explicitly in the test scope because its
// destructor removes the directory
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap
new file mode 100644
index 00000000..9b2efb21
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__new_pane_plugin_command.snap
@@ -0,0 +1,29 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+expression: "format!(\"{:#?}\", new_pane_event)"
+---
+Some(
+ SpawnTerminal(
+ None,
+ None,
+ NoPreference {
+ borderless: None,
+ },
+ false,
+ ClientId(
+ 1,
+ ),
+ Some(
+ NotificationEnd {
+ channel: None,
+ exit_status: None,
+ unblock_condition: None,
+ affected_pane_id: None,
+ affected_tab_id: None,
+ error_message: None,
+ stdout_message: None,
+ },
+ ),
+ false,
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_plugin_command.snap
new file mode 100644
index 00000000..fac5fcd5
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_plugin_command.snap
@@ -0,0 +1,21 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+expression: "format!(\"{:#?}\", toggle_floating_panes_event)"
+---
+Some(
+ ToggleFloatingPanes(
+ 1,
+ None,
+ Some(
+ NotificationEnd {
+ channel: None,
+ exit_status: None,
+ unblock_condition: None,
+ affected_pane_id: None,
+ affected_tab_id: None,
+ error_message: None,
+ stdout_message: None,
+ },
+ ),
+ ),
+)
diff --git a/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_with_tab_id_plugin_command.snap b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_with_tab_id_plugin_command.snap
new file mode 100644
index 00000000..6668b899
--- /dev/null
+++ b/zellij-server/src/plugins/unit/snapshots/zellij_server__plugins__plugin_tests__toggle_floating_panes_with_tab_id_plugin_command.snap
@@ -0,0 +1,21 @@
+---
+source: zellij-server/src/plugins/./unit/plugin_tests.rs
+expression: "format!(\"{:#?}\", toggle_floating_panes_event)"
+---
+Some(
+ ToggleFloatingPanesWithTabId(
+ 2,
+ None,
+ Some(
+ NotificationEnd {
+ channel: None,
+ exit_status: None,
+ unblock_condition: None,
+ affected_pane_id: None,
+ affected_tab_id: None,
+ error_message: None,
+ stdout_message: None,
+ },
+ ),
+ ),
+)
diff --git a/zellij-server/src/plugins/wasm_bridge.rs b/zellij-server/src/plugins/wasm_bridge.rs
index 13e5ff82..4121684b 100644
--- a/zellij-server/src/plugins/wasm_bridge.rs
+++ b/zellij-server/src/plugins/wasm_bridge.rs
@@ -2170,6 +2170,8 @@ fn check_event_permission(
| Event::PluginConfigurationChanged(..)
| Event::HighlightClicked { .. }
| Event::SoftKeyboardVisibilityChanged(..)
+ | Event::HintText(..)
+ | Event::ActivePaneScroll(..)
| Event::InputReceived => PermissionType::ReadApplicationState,
Event::WebServerStatus(..) => PermissionType::StartWebServer,
Event::PaneRenderReport(..) | Event::PaneRenderReportWithAnsi(..) => {
diff --git a/zellij-server/src/plugins/zellij_exports.rs b/zellij-server/src/plugins/zellij_exports.rs
index c9c3515e..4dd559ed 100644
--- a/zellij-server/src/plugins/zellij_exports.rs
+++ b/zellij-server/src/plugins/zellij_exports.rs
@@ -37,8 +37,8 @@ use zellij_utils::data::{
OpenPluginPaneFloatingResponse, OpenTerminalFloatingNearPluginResponse,
OpenTerminalFloatingResponse, OpenTerminalInPlaceOfPluginResponse, OpenTerminalInPlaceResponse,
OpenTerminalNearPluginResponse, OpenTerminalPaneInPlaceOfPaneIdResponse, OpenTerminalResponse,
- OriginatingPlugin, PaneScrollbackResponse, PermissionStatus, PermissionType, PluginPermission,
- RegexHighlight, RenameLayoutResponse, SaveLayoutResponse, TabMetadata,
+ OriginatingPlugin, PaneFrameStyle, PaneScrollbackResponse, PermissionStatus, PermissionType,
+ PluginPermission, RegexHighlight, RenameLayoutResponse, SaveLayoutResponse, TabMetadata,
};
use zellij_utils::home::default_layout_dir;
use zellij_utils::input::permission::PermissionCache;
@@ -304,6 +304,10 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
PluginCommand::NewTiledPaneInTab { tab_position } => {
new_tiled_pane_in_tab(env, tab_position)
},
+ PluginCommand::ToggleFloatingPanes { tab_id } => {
+ toggle_floating_panes(env, tab_id)
+ },
+ PluginCommand::NewPane => new_pane(env),
PluginCommand::GoToNextTab => go_to_next_tab(env),
PluginCommand::GoToPreviousTab => go_to_previous_tab(env),
PluginCommand::Resize(resize_payload) => resize(env, resize_payload),
@@ -334,6 +338,9 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
PluginCommand::PageScrollDown => page_scroll_down(env),
PluginCommand::ToggleFocusFullscreen => toggle_focus_fullscreen(env),
PluginCommand::TogglePaneFrames => toggle_pane_frames(env),
+ PluginCommand::SetPaneFrameStyle(pane_frame_style) => {
+ set_pane_frame_style(env, pane_frame_style)
+ },
PluginCommand::TogglePaneEmbedOrEject => toggle_pane_embed_or_eject(env),
PluginCommand::UndoRenamePane => undo_rename_pane(env),
PluginCommand::CloseFocus => close_focus(env),
@@ -3146,6 +3153,12 @@ fn toggle_pane_frames(env: &PluginEnv) {
apply_action!(action, error_msg, env);
}
+fn set_pane_frame_style(env: &PluginEnv, pane_frame_style: PaneFrameStyle) {
+ let error_msg = || format!("failed to set pane frame style in plugin {}", env.name());
+ let action = Action::SetPaneFrameStyle(pane_frame_style);
+ apply_action!(action, error_msg, env);
+}
+
fn toggle_pane_embed_or_eject(env: &PluginEnv) {
let error_msg = || {
format!(
@@ -3157,6 +3170,25 @@ fn toggle_pane_embed_or_eject(env: &PluginEnv) {
apply_action!(action, error_msg, env);
}
+fn toggle_floating_panes(env: &PluginEnv, tab_id: Option<u64>) {
+ let error_msg = || format!("failed to toggle floating panes in plugin {}", env.name());
+ let action = match tab_id {
+ Some(id) => Action::ToggleFloatingPanesByTabId { id },
+ None => Action::ToggleFloatingPanes,
+ };
+ apply_action!(action, error_msg, env);
+}
+
+fn new_pane(env: &PluginEnv) {
+ let error_msg = || format!("failed to open new pane in plugin {}", env.name());
+ let action = Action::NewPane {
+ direction: None,
+ pane_name: None,
+ start_suppressed: false,
+ };
+ apply_action!(action, error_msg, env);
+}
+
fn undo_rename_pane(env: &PluginEnv) {
let error_msg = || format!("failed to undo rename pane in plugin {}", env.name());
let action = Action::UndoRenamePane;
@@ -5494,6 +5526,9 @@ fn check_command_permission(
| PluginCommand::ToggleFocusFullscreen
| PluginCommand::TogglePaneIdFullscreen(..)
| PluginCommand::TogglePaneFrames
+ | PluginCommand::SetPaneFrameStyle(..)
+ | PluginCommand::ToggleFloatingPanes { .. }
+ | PluginCommand::NewPane
| PluginCommand::TogglePaneEmbedOrEject
| PluginCommand::TogglePaneEmbedOrEjectForPaneId(..)
| PluginCommand::UndoRenamePane