diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-06-29 18:10:59 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-06-29 18:10:59 +0200 |
| commit | 245f5141c9bb7eb91f003efac4defc94470243fd (patch) | |
| tree | 52ae6d003366f97e41c8a1450602aef63c8fdca2 /zellij-server/src/plugins | |
| parent | feat: reload config on theme change (#5135) (diff) | |
| download | sl0ppij-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')
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 |
