diff options
| author | user <user@clank> | 2026-08-12 13:16:00 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 13:16:00 +0200 |
| commit | 733ac78ca77cc61b52bb627e4c02eafa80282944 (patch) | |
| tree | f553706a88fa05f8f1ade8efb39b5475ded160d3 /zellij-utils/src/input | |
| parent | sl0p(pane-finder): document a single-chord binding (diff) | |
| download | sl0ppij-733ac78ca77cc61b52bb627e4c02eafa80282944.tar.gz sl0ppij-733ac78ca77cc61b52bb627e4c02eafa80282944.tar.xz sl0ppij-733ac78ca77cc61b52bb627e4c02eafa80282944.zip | |
sl0p: add close_if_focused to LaunchOrFocusPlugin
Upstream's action focuses an already-running plugin, so a chord bound to it can
only ever open one -- there is no way to dismiss with the same key.
This cannot be fixed in a plugin. Keybindings are resolved into actions in
route.rs before a key reaches a pane, so the launching chord never arrives at
the plugin. intercept_key_presses does not help either: the intercept lives in
screen.rs and only sees keys that already became Action::Write, i.e. keys
destined for a pane. Confirmed empirically -- an intercepting build received
Ctrl-u but never Ctrl-;.
Closes only when the plugin is the focused pane in the client's ACTIVE tab, so
the chord cannot close whatever pane the user happens to be on. A plugin that
exists but is hidden behind a floating-layer toggle, or is focused in a
different tab, is revealed or brought over as before.
Defaults to false, so every existing binding is unaffected. Config-only, with no
protobuf representation: defaulted at both boundaries and dropped when an action
crosses the plugin API, so no prost regeneration is needed.
The 24 snapshot lines are the new field appearing in serialised keybind dumps.
Diffstat (limited to 'zellij-utils/src/input')
| -rw-r--r-- | zellij-utils/src/input/actions.rs | 11 | ||||
| -rw-r--r-- | zellij-utils/src/input/config.rs | 61 |
2 files changed, 71 insertions, 1 deletions
diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index 95409609..27dde42e 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -387,6 +387,15 @@ pub enum Action { close_replaced_pane: bool, skip_cache: bool, tab_id: Option<usize>, + /// Close the plugin instead of focusing it when it is ALREADY the focused + /// pane, so one chord both opens and dismisses it. + /// + /// Needed because a plugin cannot do this itself: keybindings are + /// resolved into actions before a key can reach a pane, so the chord that + /// launched a plugin never arrives at it -- not even through + /// intercept_key_presses, which only sees keys already destined for a + /// pane. + close_if_focused: bool, }, /// Returns: Plugin pane ID (format: plugin_<id>) LaunchPlugin { @@ -1889,6 +1898,8 @@ impl Action { close_replaced_pane, skip_cache: skip_plugin_cache, tab_id, + // a one-shot CLI invocation has nothing to toggle + close_if_focused: false, }]) }, CliAction::LaunchPlugin { diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index 08bfa4b0..880b3fa9 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -680,7 +680,10 @@ pub async fn watch_layout_dir_changes<F, Fut>( #[cfg(test)] mod config_test { use super::*; - use crate::data::{InputMode, Palette, PaletteColor, StyleDeclaration, Styling}; + use crate::data::{ + BareKey, InputMode, KeyWithModifier, Palette, PaletteColor, StyleDeclaration, Styling, + }; + use crate::input::actions::Action; use crate::input::layout::RunPlugin; use crate::input::options::{Clipboard, OnForceClose}; use crate::input::theme::{FrameConfig, Theme, Themes, UiConfig}; @@ -1474,6 +1477,62 @@ mod config_test { } #[test] + fn launch_or_focus_plugin_close_if_focused_is_parsed() { + // A keybinding-only option: it lets one chord both open and dismiss a + // plugin, which the plugin cannot do itself because keybindings are + // resolved into actions before a key can reach a pane. + let config = r#" + keybinds { + normal { + bind "Ctrl a" { + LaunchOrFocusPlugin "zellij:about" { + floating true + close_if_focused true + }; + } + bind "Ctrl b" { + LaunchOrFocusPlugin "zellij:about" { + floating true + }; + } + } + } + "#; + let config = Config::from_kdl(config, None).unwrap(); + let actions_for = |key: KeyWithModifier| { + config + .keybinds + .get_actions_for_key_in_mode(&InputMode::Normal, &key) + .cloned() + .unwrap_or_default() + }; + let with_option = actions_for(KeyWithModifier::new(BareKey::Char('a')).with_ctrl_modifier()); + let without = actions_for(KeyWithModifier::new(BareKey::Char('b')).with_ctrl_modifier()); + assert!( + matches!( + with_option.first(), + Some(Action::LaunchOrFocusPlugin { + close_if_focused: true, + .. + }) + ), + "close_if_focused should be parsed as true, got {:?}", + with_option + ); + assert!( + matches!( + without.first(), + Some(Action::LaunchOrFocusPlugin { + close_if_focused: false, + .. + }) + ), + "omitting it must default to false, so existing bindings are unchanged, got {:?}", + without + ); + } + + #[test] fn can_define_ui_configuration_in_configfile() { let config_contents = r#" ui { |
