diff --git a/default-plugins/status-bar/src/second_line.rs b/default-plugins/status-bar/src/second_line.rs index 4376616a38..e505e66aad 100644 --- a/default-plugins/status-bar/src/second_line.rs +++ b/default-plugins/status-bar/src/second_line.rs @@ -240,7 +240,7 @@ fn get_keys_and_hints(mi: &ModeInfo) -> Vec<(String, String, Vec)> { action_key(&km, &[A::SearchToggleOption(SOpt::WholeWord)])), ]} else if mi.mode == IM::Session { vec![ (s("Detach"), s("Detach"), action_key(&km, &[Action::Detach])), - (s("Session Manager"), s("Manager"), action_key(&km, &[A::LaunchOrFocusPlugin(Default::default(), true), TO_NORMAL])), // not entirely accurate + (s("Session Manager"), s("Manager"), action_key(&km, &[A::LaunchOrFocusPlugin(Default::default(), true, true), TO_NORMAL])), // not entirely accurate (s("Select pane"), s("Select"), to_normal_key), ]} else if mi.mode == IM::Tmux { vec![ (s("Move focus"), s("Move"), action_key_group(&km, &[ diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 26b551866b..b29dd934fa 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -621,11 +621,12 @@ pub(crate) fn route_action( .send_to_screen(ScreenInstruction::StartOrReloadPluginPane(run_plugin, None)) .with_context(err_context)?; }, - Action::LaunchOrFocusPlugin(run_plugin, should_float) => { + Action::LaunchOrFocusPlugin(run_plugin, should_float, move_to_focused_tab) => { senders .send_to_screen(ScreenInstruction::LaunchOrFocusPlugin( run_plugin, should_float, + move_to_focused_tab, client_id, )) .with_context(err_context)?; diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 2c018dcf3b..8015075fcb 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -281,9 +281,9 @@ pub enum ScreenInstruction { StartPluginLoadingIndication(u32, LoadingIndication), // u32 - plugin_id ProgressPluginLoadingOffset(u32), // u32 - plugin id RequestStateUpdateForPlugins, - LaunchOrFocusPlugin(RunPlugin, bool, ClientId), // bool is should_float - SuppressPane(PaneId, ClientId), // bool is should_float - FocusPaneWithId(PaneId, bool, ClientId), // bool is should_float + LaunchOrFocusPlugin(RunPlugin, bool, bool, ClientId), // bools are: should_float, move_to_focused_tab + SuppressPane(PaneId, ClientId), // bool is should_float + FocusPaneWithId(PaneId, bool, ClientId), // bool is should_float RenamePane(PaneId, Vec), RenameTab(usize, Vec), RequestPluginPermissions( @@ -1618,18 +1618,47 @@ impl Screen { &mut self, run_plugin: &RunPlugin, should_float: bool, + move_to_focused_tab: bool, client_id: ClientId, ) -> Result { // true => found and focused, false => not let err_context = || format!("failed to focus_plugin_pane"); let mut tab_index_and_plugin_pane_id = None; + let mut plugin_pane_to_move_to_active_tab = None; + let focused_tab_index = *self.active_tab_indices.get(&client_id).unwrap_or(&0); let all_tabs = self.get_tabs_mut(); for (tab_index, tab) in all_tabs.iter_mut() { if let Some(plugin_pane_id) = tab.find_plugin(&run_plugin) { tab_index_and_plugin_pane_id = Some((*tab_index, plugin_pane_id)); + if move_to_focused_tab && focused_tab_index != *tab_index { + plugin_pane_to_move_to_active_tab = + tab.extract_pane(plugin_pane_id, Some(client_id)); + } + break; } } + if let Some(plugin_pane_to_move_to_active_tab) = plugin_pane_to_move_to_active_tab.take() { + let pane_id = plugin_pane_to_move_to_active_tab.pid(); + let new_active_tab = self.get_active_tab_mut(client_id)?; + + if should_float { + new_active_tab.show_floating_panes(); + new_active_tab.add_floating_pane( + plugin_pane_to_move_to_active_tab, + pane_id, + Some(client_id), + )?; + } else { + new_active_tab.hide_floating_panes(); + new_active_tab.add_tiled_pane( + plugin_pane_to_move_to_active_tab, + pane_id, + Some(client_id), + )?; + } + return Ok(true); + } match tab_index_and_plugin_pane_id { Some((tab_index, plugin_pane_id)) => { self.go_to_tab(tab_index + 1, client_id)?; @@ -2969,7 +2998,12 @@ pub(crate) fn screen_thread_main( screen.log_and_report_session_state()?; screen.render()?; }, - ScreenInstruction::LaunchOrFocusPlugin(run_plugin, should_float, client_id) => { + ScreenInstruction::LaunchOrFocusPlugin( + run_plugin, + should_float, + move_to_focused_tab, + client_id, + ) => { let client_id = if screen.active_tab_indices.contains_key(&client_id) { Some(client_id) } else { @@ -2983,7 +3017,12 @@ pub(crate) fn screen_thread_main( }); match client_id_and_focused_tab { Some((tab_index, client_id)) => { - if screen.focus_plugin_pane(&run_plugin, should_float, client_id)? { + if screen.focus_plugin_pane( + &run_plugin, + should_float, + move_to_focused_tab, + client_id, + )? { screen.render()?; screen.log_and_report_session_state()?; } else { diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 2da5306a9d..799cebcb25 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -2245,6 +2245,49 @@ impl Tab { closed_pane } } + pub fn extract_pane( + &mut self, + id: PaneId, + client_id: Option, + ) -> Option> { + if self.floating_panes.panes_contain(&id) { + let closed_pane = self.floating_panes.remove_pane(id); + self.floating_panes.move_clients_out_of_pane(id); + if !self.floating_panes.has_panes() { + self.hide_floating_panes(); + } + self.set_force_render(); + self.floating_panes.set_force_render(); + if self.auto_layout + && !self.swap_layouts.is_floating_damaged() + && self.floating_panes.visible_panes_count() > 0 + { + self.swap_layouts.set_is_floating_damaged(); + // only relayout if the user is already "in" a layout, otherwise this might be + // confusing + let _ = self.next_swap_layout(client_id, false); + } + closed_pane + } else if self.tiled_panes.panes_contain(&id) { + if self.tiled_panes.fullscreen_is_active() { + self.tiled_panes.unset_fullscreen(); + } + let closed_pane = self.tiled_panes.remove_pane(id); + self.set_force_render(); + self.tiled_panes.set_force_render(); + if self.auto_layout && !self.swap_layouts.is_tiled_damaged() { + self.swap_layouts.set_is_tiled_damaged(); + // only relayout if the user is already "in" a layout, otherwise this might be + // confusing + let _ = self.next_swap_layout(client_id, false); + } + closed_pane + } else if self.suppressed_panes.contains_key(&id) { + self.suppressed_panes.remove(&id) + } else { + None + } + } pub fn hold_pane( &mut self, id: PaneId, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 8d1debd353..2f123ddccd 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -2564,6 +2564,7 @@ pub fn send_cli_launch_or_focus_plugin_action() { ); let cli_action = CliAction::LaunchOrFocusPlugin { floating: true, + move_to_focused_tab: true, url: url::Url::parse("file:/path/to/fake/plugin").unwrap(), configuration: Default::default(), }; @@ -2621,6 +2622,7 @@ pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() { ); let cli_action = CliAction::LaunchOrFocusPlugin { floating: true, + move_to_focused_tab: true, url: url::Url::parse("file:/path/to/fake/plugin").unwrap(), configuration: Default::default(), }; diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index aea41f2741..bf54c86208 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -116,6 +116,7 @@ keybinds { bind "w" { LaunchOrFocusPlugin "zellij:session-manager" { floating true + move_to_focused_tab true }; SwitchToMode "Normal" } diff --git a/zellij-utils/src/cli.rs b/zellij-utils/src/cli.rs index fd2bdcda75..957a13c1a0 100644 --- a/zellij-utils/src/cli.rs +++ b/zellij-utils/src/cli.rs @@ -384,6 +384,8 @@ pub enum CliAction { LaunchOrFocusPlugin { #[clap(short, long, value_parser)] floating: bool, + #[clap(short, long, value_parser)] + move_to_focused_tab: bool, url: Url, #[clap(short, long, value_parser)] configuration: Option, diff --git a/zellij-utils/src/input/actions.rs b/zellij-utils/src/input/actions.rs index faeb8a4e7e..054273a0a3 100644 --- a/zellij-utils/src/input/actions.rs +++ b/zellij-utils/src/input/actions.rs @@ -204,7 +204,7 @@ pub enum Action { LeftClick(Position), RightClick(Position), MiddleClick(Position), - LaunchOrFocusPlugin(RunPlugin, bool), // bool => should float + LaunchOrFocusPlugin(RunPlugin, bool, bool), // bools => should float, move_to_focused_tab LeftMouseRelease(Position), RightMouseRelease(Position), MiddleMouseRelease(Position), @@ -501,6 +501,7 @@ impl Action { CliAction::LaunchOrFocusPlugin { url, floating, + move_to_focused_tab, configuration, } => { let current_dir = get_current_dir(); @@ -511,7 +512,11 @@ impl Action { _allow_exec_host_cmd: false, configuration: configuration.unwrap_or_default(), }; - Ok(vec![Action::LaunchOrFocusPlugin(run_plugin, floating)]) + Ok(vec![Action::LaunchOrFocusPlugin( + run_plugin, + floating, + move_to_focused_tab, + )]) }, } } diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index af49dbb309..1d09bdac52 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -906,6 +906,9 @@ impl TryFrom<(&KdlNode, &Options)> for Action { let should_float = command_metadata .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "floating")) .unwrap_or(false); + let move_to_focused_tab = command_metadata + .and_then(|c_m| kdl_child_bool_value_for_entry(c_m, "move_to_focused_tab")) + .unwrap_or(false); let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let location = RunPluginLocation::parse(&plugin_path, Some(current_dir))?; let configuration = KdlLayoutParser::parse_plugin_user_configuration(&kdl_action)?; @@ -914,7 +917,11 @@ impl TryFrom<(&KdlNode, &Options)> for Action { _allow_exec_host_cmd: false, configuration, }; - Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float)) + Ok(Action::LaunchOrFocusPlugin( + run_plugin, + should_float, + move_to_focused_tab, + )) }, "PreviousSwapLayout" => Ok(Action::PreviousSwapLayout), "NextSwapLayout" => Ok(Action::NextSwapLayout), diff --git a/zellij-utils/src/plugin_api/action.proto b/zellij-utils/src/plugin_api/action.proto index 05fa5c6ed0..429c8432e3 100644 --- a/zellij-utils/src/plugin_api/action.proto +++ b/zellij-utils/src/plugin_api/action.proto @@ -84,6 +84,7 @@ message LaunchOrFocusPluginPayload { string plugin_url = 1; bool should_float = 2; optional PluginConfiguration plugin_configuration = 3; + bool move_to_focused_tab = 4; } message GoToTabNamePayload { diff --git a/zellij-utils/src/plugin_api/action.rs b/zellij-utils/src/plugin_api/action.rs index 1a964fa9bb..b3bff6f849 100644 --- a/zellij-utils/src/plugin_api/action.rs +++ b/zellij-utils/src/plugin_api/action.rs @@ -399,7 +399,12 @@ impl TryFrom for Action { configuration, }; let should_float = payload.should_float; - Ok(Action::LaunchOrFocusPlugin(run_plugin, should_float)) + let move_to_focused_tab = payload.move_to_focused_tab; + Ok(Action::LaunchOrFocusPlugin( + run_plugin, + should_float, + move_to_focused_tab, + )) }, _ => Err("Wrong payload for Action::LaunchOrFocusPlugin"), } @@ -954,7 +959,7 @@ impl TryFrom for ProtobufAction { optional_payload: Some(OptionalPayload::MiddleClickPayload(position)), }) }, - Action::LaunchOrFocusPlugin(run_plugin, should_float) => { + Action::LaunchOrFocusPlugin(run_plugin, should_float, move_to_focused_tab) => { let url: Url = Url::from(&run_plugin.location); Ok(ProtobufAction { name: ProtobufActionName::LaunchOrFocusPlugin as i32, @@ -962,6 +967,7 @@ impl TryFrom for ProtobufAction { LaunchOrFocusPluginPayload { plugin_url: url.into(), should_float, + move_to_focused_tab, plugin_configuration: Some(run_plugin.configuration.try_into()?), }, )), diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index 367866e090..06c90a6891 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -2503,10 +2503,12 @@ Config { configuration: PluginUserConfiguration( { "floating": "true", + "move_to_focused_tab": "true", }, ), }, true, + true, ), SwitchToMode( Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index 5af23a0a5b..b2c4305cb7 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -2503,10 +2503,12 @@ Config { configuration: PluginUserConfiguration( { "floating": "true", + "move_to_focused_tab": "true", }, ), }, true, + true, ), SwitchToMode( Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap index 55173c3ab5..42cd12c25b 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_plugins_override_config_plugins.snap @@ -2503,10 +2503,12 @@ Config { configuration: PluginUserConfiguration( { "floating": "true", + "move_to_focused_tab": "true", }, ), }, true, + true, ), SwitchToMode( Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index a7613eee18..9f5749f556 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -2503,10 +2503,12 @@ Config { configuration: PluginUserConfiguration( { "floating": "true", + "move_to_focused_tab": "true", }, ), }, true, + true, ), SwitchToMode( Normal, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index dae918163b..8da67cf042 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -2503,10 +2503,12 @@ Config { configuration: PluginUserConfiguration( { "floating": "true", + "move_to_focused_tab": "true", }, ), }, true, + true, ), SwitchToMode( Normal,