diff options
| author | Aram Drevekenin <aram@poor.dev> | 2025-12-11 14:25:42 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-11 14:25:42 +0100 |
| commit | 693fa082def5ef0fe030cd7fdc3b1df051b35dc6 (patch) | |
| tree | b3f651462bbc1cd2fa5b2d79207343413ef6b83c /zellij-server/src/tab | |
| parent | fix(zellij-server): unblock building on 32bit targets (#4484) (diff) | |
| download | sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.gz sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.tar.xz sl0ppij-693fa082def5ef0fe030cd7fdc3b1df051b35dc6.zip | |
Command Sequences, Blocking CLI Commands, New Plugin APIs (#4546)
* fix deadlocks
* initial logging to plugin implementation
* only log actions, include keys as Action::Write
* send actions to plugins as a new Event::UserAction
* add Action::Write metadata to plugin API
* run_action plugin command and permission
* block-until-exit/success/failure in the cli
* fix tests
* add NewBlockingPane to the plugin API
* run actions in a different thread so as not to block plugin execution
* add custom context to run_action flow
* fixes for floating pane z_index sorting and unknown protobuf events not crashing plugins
* always render the frame of pinned panes
* macros module
* add macros to config
* KDL macro parsing
* thread macros to the plugin thread
* get/update/delete macros from plugins
* add macros updated event
* add ability to rename macros
* fix deadlock
* fix z_indices with pinned unselectable panes bug
* allow plugins to opt-in to show_cursor
* change show_cursor(bool) to show_cursor(Option<coordinates>)
* test
* upgrade strum to allow proper enum iteration and case insensitive fromstr implementation for Actions
* better cwd tracking for exited command panes
* add initial panes to NewTab Action (no-op for now)
* add initial panes to cli newtab action
* make initial panes work
* add close_on_exit and start_suspended to the cli
* properly convert newtab action (potentially breaking for plugins)
* allow first pane in tab to block
* make distinction between opening panes with the last active client and the current pane in the cli
* improve some apis
* fix fixed size closing pane bug
* fix plugin cursor coordinates
* fix issues with launch-or-focus-plugin having to do with in-place and suppressed panes
* fix: set affected pane in replace pane
* allow setting a pane in place when it is suppressed and being focused (tests failing)
* fix tests
* some fixes for unsuppression of panes
* some fixes for completion_tx in new tab action
* allow replace/suppressed rather than replace/close only
* fixes for inserting suppressed panes without conflicts
* pane focus history and explicit unsuppress plugin api
* fix: prefer explicitly provided pane id in placement in actions that provide it
* fix floating pane coordinate change and z_index
* fix plugin cache dir to persist between sessions
* fix some stack focusing issues when adding panes without a client id
* make sure pinned panes are always above non-pinned floating panes
* allow plugins to copy to clipboard
* initial text styling additions implementations - buggy
* fix dimming issues
* remove extras, stay with just dim and unbold
* unbold and dim as indices
* allow ui components to render success/error colors
* allow plugins to send sigint/sigkill to processes, as well as receive their pid
* ui component fixes and send originating plugin context with run_action
* originating plugin for new tab too
* make plugn show pane command also expand stacked panes
* add sequence plugin
* remove macros
* remove snapshots
* add shortcut for sequence plugin
* fix plugin tests
* plugin tests for new apis
* update snapshots for sequence shortcut
* cleanups and formatting
* Event::CwdChanged
* moar cleanups
* remove some warnings
* remove mroe warnings
* remove dead code
* remove more dead code
* fix minor bugs in sequence plugin
* update sequence help and format codebase
* fix conditional blocking cli commands
* fix initial-command new-tab cli flag
* add client_ids to CwdChanged Event
* last fixes
* add pr
Diffstat (limited to 'zellij-server/src/tab')
| -rw-r--r-- | zellij-server/src/tab/layout_applier.rs | 19 | ||||
| -rw-r--r-- | zellij-server/src/tab/mod.rs | 262 | ||||
| -rw-r--r-- | zellij-server/src/tab/unit/tab_integration_tests.rs | 18 | ||||
| -rw-r--r-- | zellij-server/src/tab/unit/tab_tests.rs | 6 |
4 files changed, 252 insertions, 53 deletions
diff --git a/zellij-server/src/tab/layout_applier.rs b/zellij-server/src/tab/layout_applier.rs index 5e52888a..559dc5db 100644 --- a/zellij-server/src/tab/layout_applier.rs +++ b/zellij-server/src/tab/layout_applier.rs @@ -11,7 +11,7 @@ use crate::{ plugins::PluginInstruction, pty::PtyInstruction, thread_bus::ThreadSenders, - ClientId, + ClientId, NotificationEnd, }; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; @@ -42,6 +42,7 @@ pub struct LayoutApplier<'a> { arrow_fonts: bool, styled_underlines: bool, explicitly_disable_kitty_keyboard_protocol: bool, + blocking_terminal: Option<(u32, NotificationEnd)>, } impl<'a> LayoutApplier<'a> { @@ -65,6 +66,7 @@ impl<'a> LayoutApplier<'a> { arrow_fonts: bool, styled_underlines: bool, explicitly_disable_kitty_keyboard_protocol: bool, + blocking_terminal: Option<(u32, NotificationEnd)>, ) -> Self { let viewport = viewport.clone(); let senders = senders.clone(); @@ -97,6 +99,7 @@ impl<'a> LayoutApplier<'a> { arrow_fonts, styled_underlines, explicitly_disable_kitty_keyboard_protocol, + blocking_terminal, } } pub fn apply_layout( @@ -496,6 +499,18 @@ impl<'a> LayoutApplier<'a> { Some(Run::Command(run_command)) => Some(run_command.to_string()), _ => None, }; + + // Check if this terminal should receive the blocking completion_tx + let notification_end = if let Some((blocking_pid, _)) = &self.blocking_terminal { + if *blocking_pid == pid { + self.blocking_terminal.take().map(|(_, tx)| tx) + } else { + None + } + } else { + None + }; + let mut new_pane = TerminalPane::new( pid, *position_and_size, @@ -513,7 +528,7 @@ impl<'a> LayoutApplier<'a> { self.arrow_fonts, self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, - None, + notification_end, ); if let Some(pane_initial_contents) = &layout.pane_initial_contents { new_pane.handle_pty_bytes(pane_initial_contents.as_bytes().into()); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index cd87b817..7e441a0e 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -7,7 +7,6 @@ mod layout_applier; mod swap_layouts; use copy_command::CopyCommand; -use serde; use std::env::temp_dir; use std::net::IpAddr; use std::path::PathBuf; @@ -46,7 +45,6 @@ use crate::{ thread_bus::ThreadSenders, ClientId, ServerInstruction, }; -use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::rc::Rc; use std::time::Instant; @@ -279,16 +277,6 @@ pub(crate) struct Tab { web_server_port: u16, } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(crate = "self::serde")] -pub(crate) struct TabData { - pub position: usize, - pub name: String, - pub active: bool, - pub mode_info: ModeInfo, - pub colors: Styling, -} - // FIXME: Use a struct that has a pane_type enum, to reduce all of the duplication pub trait Pane { fn x(&self) -> usize; @@ -304,7 +292,8 @@ pub trait Pane { fn set_geom_override(&mut self, pane_geom: PaneGeom); fn handle_pty_bytes(&mut self, _bytes: VteBytes) {} fn handle_plugin_bytes(&mut self, _client_id: ClientId, _bytes: VteBytes) {} - fn cursor_coordinates(&self) -> Option<(usize, usize)>; + fn show_cursor(&mut self, _client_id: ClientId, _cursor_position: Option<(usize, usize)>) {} + fn cursor_coordinates(&self, _client_id: Option<ClientId>) -> Option<(usize, usize)>; fn is_mid_frame(&self) -> bool { false } @@ -818,6 +807,7 @@ impl Tab { new_floating_terminal_ids: Vec<(u32, HoldForCommand)>, new_plugin_ids: HashMap<RunPluginOrAlias, Vec<u32>>, client_id: ClientId, + blocking_terminal: Option<(u32, NotificationEnd)>, ) -> Result<()> { self.swap_layouts .set_base_layout((layout.clone(), floating_panes_layout.clone())); @@ -841,6 +831,7 @@ impl Tab { self.arrow_fonts, self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, + blocking_terminal, ) .apply_layout( layout, @@ -911,6 +902,7 @@ impl Tab { self.arrow_fonts, self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, + None, ) .apply_floating_panes_layout_to_existing_panes(&layout_candidate) .non_fatal(); @@ -949,6 +941,7 @@ impl Tab { self.arrow_fonts, self.styled_underlines, self.explicitly_disable_kitty_keyboard_protocol, + None, ) .apply_tiled_panes_layout_to_existing_panes(&layout_candidate); if application_res.is_err() { @@ -2080,6 +2073,26 @@ impl Tab { drop(replaced_pane); } } + pub fn suppress_pane_and_replace_with_other_pane( + &mut self, + pane_id_to_replace: PaneId, + pane_to_replace_with: Box<dyn Pane>, + _completion_tx: Option<NotificationEnd>, + ) { + let mut replaced_pane = if self.floating_panes.panes_contain(&pane_id_to_replace) { + self.floating_panes + .replace_pane(pane_id_to_replace, pane_to_replace_with) + .ok() + } else { + self.tiled_panes + .replace_pane(pane_id_to_replace, pane_to_replace_with) + }; + + if let Some(replaced_pane) = replaced_pane.take() { + let is_scrollback_editor = false; + self.insert_suppressed_pane(replaced_pane.pid(), (is_scrollback_editor, replaced_pane)); + } + } pub fn horizontal_split( &mut self, pid: PaneId, @@ -2683,7 +2696,7 @@ impl Tab { .get(&active_pane_id) .or_else(|| self.tiled_panes.get_pane(active_pane_id))?; active_terminal - .cursor_coordinates() + .cursor_coordinates(Some(client_id)) .map(|(x_in_terminal, y_in_terminal)| { let x = active_terminal.x() + x_in_terminal; let y = active_terminal.y() + y_in_terminal; @@ -3381,21 +3394,16 @@ impl Tab { if let Some(pane) = self.tiled_panes.get_pane_mut(id) { pane.set_selectable(selectable); if !selectable { - // there are some edge cases in which this causes a hard crash when there are no - // other selectable panes - ideally this should never happen unless it's a - // configuration error - but this *does* sometimes happen with the default - // configuration as well since we set this at run time. I left this here because - // this should very rarely happen and I hope in my heart that we will stop setting - // this at runtime in the default configuration at some point - // - // If however this is not the case and we find this does cause crashes, we can - // solve it by adding a "dangling_clients" struct to Tab which we would fill with - // the relevant client ids in this case and drain as soon as a new selectable pane - // is opened self.tiled_panes.move_clients_out_of_pane(id); } } else if let Some(pane) = self.floating_panes.get_pane_mut(id) { pane.set_selectable(selectable); + if !selectable { + self.floating_panes.move_clients_out_of_pane(id); + if !self.floating_panes.has_selectable_panes() { + self.hide_floating_panes(); + } + } } // we do this here because if there is a non-selectable pane on the edge, we consider it // outside the viewport (a ui-pane, eg. the status-bar and tab-bar) and need to adjust for it @@ -3539,8 +3547,15 @@ impl Tab { closed_pane.reset_logical_position(); } closed_pane - } else if self.suppressed_panes.contains_key(&id) { - self.suppressed_panes.remove(&id).map(|s_p| s_p.1) + } else if let Some(suppressed_key_of_pane) = self + .suppressed_panes + .iter() + .find_map(|(key, (_, pane))| if &pane.pid() == &id { Some(*key) } else { None }) + { + // TODO: test this (from the path in screen.rs focus_plugin_pane ~line 2519 + self.suppressed_panes + .remove(&suppressed_key_of_pane) + .map(|s_p| s_p.1) } else { None } @@ -4674,6 +4689,19 @@ impl Tab { } Ok(()) } + pub fn copy_text_to_clipboard(&self, text: &str) -> Result<()> { + self.write_selection_to_clipboard(text) + .with_context(|| format!("failed to write text to clipboard"))?; + self.senders + .send_to_plugin(PluginInstruction::Update(vec![( + None, + None, + Event::CopyToClipboard(self.clipboard_provider.as_copy_destination()), + )])) + .with_context(|| "failed to inform plugins about clipboard copy") + .non_fatal(); + Ok(()) + } fn write_selection_to_clipboard(&self, selection: &str) -> Result<()> { let err_context = || format!("failed to write selection to clipboard: '{}'", selection); @@ -4950,6 +4978,26 @@ impl Tab { plugin_pane.update_loading_indication(loading_indication); } } + pub fn show_plugin_cursor( + &mut self, + pid: u32, + client_id: ClientId, + cursor_position: Option<(usize, usize)>, + ) { + if let Some(plugin_pane) = self + .tiled_panes + .get_pane_mut(PaneId::Plugin(pid)) + .or_else(|| self.floating_panes.get_pane_mut(PaneId::Plugin(pid))) + .or_else(|| { + self.suppressed_panes + .values_mut() + .find(|s_p| s_p.1.pid() == PaneId::Plugin(pid)) + .map(|s_p| &mut s_p.1) + }) + { + plugin_pane.show_cursor(client_id, cursor_position); + } + } pub fn start_plugin_loading_indication( &mut self, pid: u32, @@ -5009,7 +5057,7 @@ impl Tab { .find(|(_id, (_, pane))| { run_plugin_or_alias.is_equivalent_to_run(pane.invoked_with()) }) - .map(|(id, _)| *id) + .map(|(_, (_, pane))| pane.pid()) // TODO: does this break things???? }) } @@ -5017,6 +5065,7 @@ impl Tab { &mut self, pane_id: PaneId, should_float: bool, + should_be_in_place: bool, client_id: ClientId, ) -> Result<()> { // TODO: should error if pane is not selectable @@ -5031,18 +5080,45 @@ impl Tab { }; focused_floating_pane }) - .or_else(|_| match self.suppressed_panes.remove(&pane_id) { - Some(mut pane) => { - pane.1.set_selectable(true); - if should_float { - self.show_floating_panes(); - self.add_floating_pane(pane.1, pane_id, None, true) - } else { - self.hide_floating_panes(); - self.add_tiled_pane(pane.1, pane_id, Some(client_id)) - } - }, - None => Ok(()), + // TODO: change suppressed_panes to be a proper struct with methods that make sense rather + // than doing this dance every time + .or_else(|_| { + match self + .suppressed_panes + .extract_if(|_key, (_, pane)| pane.pid() == pane_id) + .next() + .map(|(_key, (_, pane))| pane) + { + Some(mut pane) => { + pane.set_selectable(true); + if should_float { + self.show_floating_panes(); + self.add_floating_pane(pane, pane_id, None, true) + } else if should_be_in_place { + let replaced_pane = if self.are_floating_panes_visible() { + self.floating_panes + .replace_active_pane(pane, client_id) + .ok() + } else { + self.tiled_panes.replace_active_pane(pane, client_id) + }; + if let Some(replaced_pane) = replaced_pane { + let is_scrollback_editor = false; + self.insert_suppressed_pane( + pane_id, + (is_scrollback_editor, replaced_pane), + ); + } else { + log::error!("Could not find pane to replace, aborting."); + } + Ok(()) + } else { + self.hide_floating_panes(); + self.add_tiled_pane(pane, pane_id, Some(client_id)) + } + }, + None => Ok(()), + } }) } pub fn focus_suppressed_pane_for_all_clients(&mut self, pane_id: PaneId) { @@ -5064,11 +5140,101 @@ impl Tab { // scrollback editor), but it has to take itself out on its own (eg. a plugin using the // show_self() method) if let Some(pane) = self.extract_pane(pane_id, true) { - let is_scrollback_editor = false; - self.suppressed_panes - .insert(pane_id, (is_scrollback_editor, pane)); + self.insert_suppressed_pane(pane_id, (false, pane)); } } + pub fn unsuppress_pane(&mut self, pane_id: PaneId, should_float_if_hidden: bool) { + // removes a pane from being suppressed (hidden) but does not focus it + match self + .suppressed_panes + .extract_if(|_key, (_, pane)| pane.pid() == pane_id) + .next() + .map(|(_key, (_, pane))| pane) + { + Some(pane) => { + if should_float_if_hidden { + self.add_floating_pane(pane, pane_id, None, true) + .non_fatal(); + } else { + self.add_tiled_pane(pane, pane_id, None).non_fatal(); + } + }, + None => { + log::error!("Could not find suppressed pane with id: {:?}", pane_id); + }, + } + } + pub fn unsuppress_or_expand_pane(&mut self, pane_id: PaneId, should_float_if_hidden: bool) { + // removes a pane from being suppressed (hidden) but does not focus it + match self + .suppressed_panes + .extract_if(|_key, (_, pane)| pane.pid() == pane_id) + .next() + .map(|(_key, (_, pane))| pane) + { + Some(pane) => { + if should_float_if_hidden { + self.add_floating_pane(pane, pane_id, None, true) + .non_fatal(); + } else { + self.add_tiled_pane(pane, pane_id, None).non_fatal(); + } + }, + None => { + let expand_panes_success = self.tiled_panes.expand_pane_in_stack(pane_id).len() > 0; + if !expand_panes_success { + log::error!( + "Could not find suppressed or stacked pane with id: {:?}", + pane_id + ); + } + }, + } + } + fn insert_suppressed_pane( + &mut self, + suppressing_pane_id: PaneId, + pane_to_suppress: (bool, Box<dyn Pane>), + ) { + // bool -> is_scrollback_editor + // this method is intended to insert an existing provided pane into suppressed_panes, while + // making sure any existing panes already in suppressed_panes still remain there + // if there's a key collision (eg. the suppressing_pane_id already suppresses another + // pane), we fix the colliding pane so that it now becomes suppressed by its own id (i.e. + // needs to explicitly be removed from suppressed_panes by eg. a plugin action rather than + // being conditionally removed when its suppressing pane is closed or itself suppressed) + // + // - suppressing_pane_id: the id of the pane suppressing this pane - the pane that if + // closed, should trigger this pane being unsuppressed + // - pane_to_suppress: the pane itself that we want to suppress + + // this closure removes and returns an existing pane from the map if it exists under this key + // and inserts the given pane into the map under this key + let mut insert_and_return_existing_value = + |key: PaneId, value: (bool, Box<dyn Pane>)| -> Option<(bool, Box<dyn Pane>)> { + let existing = self.suppressed_panes.remove(&key); + self.suppressed_panes.insert(key, value); + existing + }; + + // here we try to insert a pane into the suppressed panes map while making sure not to drop + // (close) any existing panes in the map. We repeat the action of remapping existing panes + // under their own keys until all keys either reference the suppressing_pane_id (only one + // can do this) or themselves + let mut key_to_insert = suppressing_pane_id; + let mut pane_to_insert = Some(pane_to_suppress); + loop { + pane_to_insert = pane_to_insert + .take() + .and_then(|p| insert_and_return_existing_value(key_to_insert, p)); + if let Some(pane_to_insert) = pane_to_insert.as_ref() { + key_to_insert = pane_to_insert.1.pid(); + } else { + break; + } + } + } + pub fn pane_infos(&self) -> Vec<PaneInfo> { let mut pane_info = vec![]; let current_pane_group = { self.current_pane_group.borrow().clone_inner() }; @@ -5076,9 +5242,11 @@ impl Tab { let mut floating_pane_info = self.floating_panes.pane_info(¤t_pane_group); pane_info.append(&mut tiled_pane_info); pane_info.append(&mut floating_pane_info); - for (pane_id, (_is_scrollback_editor, pane)) in self.suppressed_panes.iter() { + for (_pane_id_of_suppressing_pane, (_is_scrollback_editor, pane)) in + self.suppressed_panes.iter() + { let mut pane_info_for_suppressed_pane = - pane_info_for_pane(pane_id, pane, ¤t_pane_group); + pane_info_for_pane(&pane.pid(), pane, ¤t_pane_group); pane_info_for_suppressed_pane.is_floating = false; pane_info_for_suppressed_pane.is_suppressed = true; pane_info_for_suppressed_pane.is_focused = false; @@ -5364,7 +5532,7 @@ impl Tab { } pub fn add_suppressed_panes(&mut self, mut suppressed_panes: SuppressedPanes) { for (pane_id, suppressed_pane_entry) in suppressed_panes.drain() { - self.suppressed_panes.insert(pane_id, suppressed_pane_entry); + self.insert_suppressed_pane(pane_id, suppressed_pane_entry); } } pub fn toggle_pane_pinned(&mut self, client_id: ClientId) { @@ -5531,7 +5699,7 @@ pub fn pane_info_for_pane( pane_info.pane_content_rows = pane.get_content_rows(); pane_info.pane_columns = pane.cols(); pane_info.pane_content_columns = pane.get_content_columns(); - pane_info.cursor_coordinates_in_pane = pane.cursor_coordinates(); + pane_info.cursor_coordinates_in_pane = pane.cursor_coordinates(None); pane_info.is_selectable = pane.selectable(); pane_info.title = pane.current_title(); pane_info.exited = pane.exited(); diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index 029df49d..6eb39eae 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -138,6 +138,9 @@ impl ServerOsApi for FakeInputOutput { fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> { unimplemented!() } + fn send_sigint(&self, pid: Pid) -> Result<()> { + unimplemented!() + } } struct MockPtyInstructionBus { @@ -280,6 +283,7 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -361,6 +365,7 @@ fn create_new_tab_without_pane_frames(size: Size, default_mode: ModeInfo) -> Tab vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -469,6 +474,7 @@ fn create_new_tab_with_swap_layouts( new_floating_terminal_ids, new_plugin_ids, client_id, + None, ) .unwrap(); tab @@ -554,6 +560,7 @@ fn create_new_tab_with_os_api( vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -648,6 +655,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) floating_pane_ids, HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -734,6 +742,7 @@ fn create_new_tab_with_mock_pty_writer( vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -822,6 +831,7 @@ fn create_new_tab_with_sixel_support( vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -1191,7 +1201,7 @@ fn render_stacks_without_pane_frames() { ) .unwrap(); } - tab.focus_pane_with_id(PaneId::Terminal(1), false, client_id); + tab.focus_pane_with_id(PaneId::Terminal(1), false, false, client_id); for i in 7..9 { let new_pane_id_1 = PaneId::Terminal(i); tab.new_pane( @@ -1206,7 +1216,7 @@ fn render_stacks_without_pane_frames() { ) .unwrap(); } - let _ = tab.focus_pane_with_id(PaneId::Terminal(1), false, client_id); + let _ = tab.focus_pane_with_id(PaneId::Terminal(1), false, false, client_id); for i in 9..11 { let new_pane_id_1 = PaneId::Terminal(i); tab.new_pane( @@ -1226,8 +1236,8 @@ fn render_stacks_without_pane_frames() { ResizeStrategy::new(Resize::Increase, Some(Direction::Right)), ) .unwrap(); - let _ = tab.focus_pane_with_id(PaneId::Terminal(7), false, client_id); - let _ = tab.focus_pane_with_id(PaneId::Terminal(5), false, client_id); + let _ = tab.focus_pane_with_id(PaneId::Terminal(7), false, false, client_id); + let _ = tab.focus_pane_with_id(PaneId::Terminal(5), false, false, client_id); tab.render(&mut output, None).unwrap(); let snapshot = take_snapshot( diff --git a/zellij-server/src/tab/unit/tab_tests.rs b/zellij-server/src/tab/unit/tab_tests.rs index f5284b88..085f8211 100644 --- a/zellij-server/src/tab/unit/tab_tests.rs +++ b/zellij-server/src/tab/unit/tab_tests.rs @@ -106,6 +106,9 @@ impl ServerOsApi for FakeInputOutput { fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> { unimplemented!() } + fn send_sigint(&self, pid: Pid) -> Result<()> { + unimplemented!() + } } fn tab_resize_increase(tab: &mut Tab, id: ClientId) { @@ -220,6 +223,7 @@ fn create_new_tab(size: Size, stacked_resize: bool) -> Tab { vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -304,6 +308,7 @@ fn create_new_tab_with_layout(size: Size, layout: TiledPaneLayout) -> Tab { vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab @@ -386,6 +391,7 @@ fn create_new_tab_with_cell_size( vec![], HashMap::new(), client_id, + None, ) .unwrap(); tab |
