mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
fix(screen): keep sending events to suppressed plugin panes (#5680)
* fix(screen): keep sending events to suppressed plugin panes A plugin that hides itself (hide_self, hide_pane_with_id or hide_slot) moves into the tab's suppressed panes. Event delivery only looked at the tiled and floating panes, so a hidden plugin stopped receiving ModeUpdate, TabUpdate, PaneUpdate and InputReceived, and could never learn that it should show itself again. Include suppressed plugin panes when updating input modes and when picking the plugins for a broadcast event. Render targeting and the client visible plugins report still use get_plugin_ids, so a suppressed plugin is not rendered. * docs(changelog): note suppressed plugins receiving events
This commit is contained in:
@@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
* fix: add websocket heartbeat to webserver to prevernt idle disconnect through proxies (https://github.com/zellij-org/zellij/pull/4633)
|
||||
* fix: memory-usage optimizations and multiple-pane-single-instance-plugins (https://github.com/zellij-org/zellij/pull/5662)
|
||||
* fix: erasing wide CJK character correctness (https://github.com/zellij-org/zellij/pull/5657)
|
||||
* fix: plugins hidden with `hide_self` keep receiving mode, tab and pane updates (https://github.com/zellij-org/zellij/pull/5680)
|
||||
|
||||
## [0.45.1] - 2026-08-28
|
||||
* fix: nested-session detection over SSH (https://github.com/zellij-org/zellij/pull/5522)
|
||||
|
||||
@@ -6847,10 +6847,10 @@ impl Screen {
|
||||
};
|
||||
}
|
||||
let mut plugin_ids = Vec::new();
|
||||
// Active-tab plugins
|
||||
// Active-tab plugins, including suppressed ones so they can decide to show themselves
|
||||
if let Some(active_tab_id) = self.active_tab_ids.get(&client_id) {
|
||||
if let Some(tab) = self.tabs.get(active_tab_id) {
|
||||
plugin_ids.extend(tab.get_plugin_ids());
|
||||
plugin_ids.extend(tab.get_plugin_ids_including_suppressed());
|
||||
}
|
||||
}
|
||||
// Background plugins subscribed to this event type
|
||||
|
||||
@@ -2109,7 +2109,7 @@ impl Tab {
|
||||
let mode_infos = self.mode_info.borrow();
|
||||
let mut plugin_updates = vec![];
|
||||
let currently_marking_pane_group = self.currently_marking_pane_group.borrow();
|
||||
let tab_plugin_ids = self.get_plugin_ids();
|
||||
let tab_plugin_ids = self.get_plugin_ids_including_suppressed();
|
||||
for client_id in self.connected_clients.borrow().iter() {
|
||||
let mut mode_info = mode_infos
|
||||
.get(client_id)
|
||||
@@ -5660,6 +5660,20 @@ impl Tab {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
/// Like `get_plugin_ids`, but also includes plugin panes that are suppressed (eg. hidden
|
||||
/// with `hide_self`). These are still running and should keep receiving events, otherwise
|
||||
/// a hidden plugin cannot know when to show itself again.
|
||||
pub fn get_plugin_ids_including_suppressed(&self) -> Vec<PluginId> {
|
||||
let mut plugin_ids = self.get_plugin_ids();
|
||||
for pane_id in self.suppressed_panes.keys() {
|
||||
if let PaneId::Plugin(plugin_id) = pane_id {
|
||||
if !plugin_ids.contains(plugin_id) {
|
||||
plugin_ids.push(*plugin_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin_ids
|
||||
}
|
||||
pub fn get_pane_info(&self, pane_id: PaneId) -> Option<PaneInfo> {
|
||||
let current_pane_group: HashMap<ClientId, Vec<PaneId>> =
|
||||
{ self.current_pane_group.borrow().clone_inner() };
|
||||
|
||||
@@ -18150,3 +18150,32 @@ fn floating_plugin_panes_are_not_shown_again_when_their_tab_returns_with_the_sur
|
||||
"a plugin whose floating surface is hidden should not be told it is visible when its tab returns"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suppressed_plugin_panes_get_mode_updates_but_are_not_visible() {
|
||||
let (mut tab, plugin_receiver) = tab_with_floating_plugin_pane(1);
|
||||
tab.suppress_pane(PaneId::Plugin(1), Some(1));
|
||||
while plugin_receiver.try_recv().is_ok() {}
|
||||
|
||||
tab.update_input_modes().unwrap();
|
||||
|
||||
let mut got_mode_update = false;
|
||||
while let Ok((instruction, _)) = plugin_receiver.try_recv() {
|
||||
if let PluginInstruction::Update(updates) = instruction {
|
||||
for (pid, _client_id, event) in updates {
|
||||
if pid == Some(1) && matches!(event, Event::ModeUpdate(..)) {
|
||||
got_mode_update = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
got_mode_update,
|
||||
"a suppressed plugin should still receive ModeUpdate so it can show itself again"
|
||||
);
|
||||
assert!(
|
||||
!tab.get_plugin_ids().contains(&1),
|
||||
"a suppressed plugin should not count as visible, since render targeting uses get_plugin_ids"
|
||||
);
|
||||
assert!(tab.get_plugin_ids_including_suppressed().contains(&1));
|
||||
}
|
||||
|
||||
@@ -8290,6 +8290,77 @@ pub fn closing_tab_updates_input_modes_of_destination_tab_plugins() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn suppressed_plugin_keeps_receiving_events() {
|
||||
// Tab 0: plugin panes 2 and 3, the client switches here and plugin 2 hides itself.
|
||||
// It should still hear about mode and tab changes so it can decide to show itself again.
|
||||
let size = Size { cols: 80, rows: 10 };
|
||||
let client_id = 10;
|
||||
|
||||
let mut mock_screen = MockScreen::new(size);
|
||||
mock_screen.new_tab_with_plugins(vec![2, 3]);
|
||||
let session_metadata = mock_screen.clone_session_metadata();
|
||||
let screen_thread = mock_screen.run(None, vec![]);
|
||||
|
||||
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
|
||||
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
|
||||
let plugin_thread = log_actions_in_thread!(
|
||||
received_plugin_instructions,
|
||||
PluginInstruction::Exit,
|
||||
plugin_receiver
|
||||
);
|
||||
|
||||
let goto_tab = CliAction::GoToTab { index: 1 };
|
||||
send_cli_action_to_server(&session_metadata, goto_tab, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let _ = mock_screen.to_screen.send(ScreenInstruction::SuppressPane(
|
||||
PaneId::Plugin(2),
|
||||
client_id,
|
||||
));
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
let instructions_before_mode_change = received_plugin_instructions.lock().unwrap().len();
|
||||
|
||||
let _ = mock_screen.to_screen.send(ScreenInstruction::ChangeMode(
|
||||
InputMode::Locked,
|
||||
None,
|
||||
client_id,
|
||||
None,
|
||||
));
|
||||
let rename_tab = CliAction::RenameTab {
|
||||
name: "renamed".to_owned(),
|
||||
tab_id: None,
|
||||
};
|
||||
send_cli_action_to_server(&session_metadata, rename_tab, client_id);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
mock_screen.teardown(vec![plugin_thread, screen_thread]);
|
||||
|
||||
let instructions = received_plugin_instructions.lock().unwrap();
|
||||
let mut mode_updates_received: Vec<u32> = vec![];
|
||||
let mut tab_updates_received: Vec<u32> = vec![];
|
||||
for instruction in instructions[instructions_before_mode_change..].iter() {
|
||||
if let PluginInstruction::Update(updates) = instruction {
|
||||
for (pid, _cid, event) in updates {
|
||||
match (pid, event) {
|
||||
(Some(pid), Event::ModeUpdate(..)) => mode_updates_received.push(*pid),
|
||||
(Some(pid), Event::TabUpdate(..)) => tab_updates_received.push(*pid),
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
mode_updates_received.contains(&2),
|
||||
"suppressed plugin 2 should receive a ModeUpdate, got: {:?}",
|
||||
mode_updates_received
|
||||
);
|
||||
assert!(
|
||||
tab_updates_received.contains(&2),
|
||||
"suppressed plugin 2 should receive a TabUpdate, got: {:?}",
|
||||
tab_updates_received
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn inactive_tab_plugins_get_fresh_state_on_activation() {
|
||||
// Tab 0: plugin pane 2 (from new_tab_with_plugins)
|
||||
|
||||
Reference in New Issue
Block a user