From 8d1a497d10b16ede047aa905e4315451f13fa0bd Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Fri, 4 Nov 2022 17:29:41 +0100 Subject: [PATCH] feat(terminals): send focus in/out events to terminal panes (#1908) * feat(terminals): send focus in/out events to terminal panes * style(fmt): rustfmt * style(fmt): rustfmt --- zellij-server/src/panes/active_panes.rs | 100 +++++++ zellij-server/src/panes/floating_panes/mod.rs | 38 ++- zellij-server/src/panes/grid.rs | 23 ++ zellij-server/src/panes/mod.rs | 2 + zellij-server/src/panes/terminal_pane.rs | 6 + zellij-server/src/panes/tiled_panes/mod.rs | 50 ++-- zellij-server/src/tab/mod.rs | 36 ++- ...oating_pane_focus_sends_tty_csi_event.snap | 6 + ...__move_pane_focus_sends_tty_csi_event.snap | 6 + ...e_pane_focus_with_sends_tty_csi_event.snap | 6 + ...loating_panes_off_sends_tty_csi_event.snap | 6 + ...floating_panes_on_sends_tty_csi_event.snap | 6 + ...le_floating_panes_sends_tty_csi_event.snap | 6 + .../src/tab/unit/tab_integration_tests.rs | 244 ++++++++++++++++-- 14 files changed, 480 insertions(+), 55 deletions(-) create mode 100644 zellij-server/src/panes/active_panes.rs create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_focus_sends_tty_csi_event.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_sends_tty_csi_event.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_with_sends_tty_csi_event.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_off_sends_tty_csi_event.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_on_sends_tty_csi_event.snap create mode 100644 zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_sends_tty_csi_event.snap diff --git a/zellij-server/src/panes/active_panes.rs b/zellij-server/src/panes/active_panes.rs new file mode 100644 index 00000000..3fce0a9f --- /dev/null +++ b/zellij-server/src/panes/active_panes.rs @@ -0,0 +1,100 @@ +use crate::tab::Pane; + +use crate::{os_input_output::ServerOsApi, panes::PaneId, ClientId}; +use std::collections::{BTreeMap, HashMap}; + +pub struct ActivePanes { + active_panes: HashMap, + os_api: Box, +} + +impl ActivePanes { + pub fn new(os_api: &Box) -> Self { + let os_api = os_api.clone(); + ActivePanes { + active_panes: HashMap::new(), + os_api, + } + } + pub fn get(&self, client_id: &ClientId) -> Option<&PaneId> { + self.active_panes.get(client_id) + } + pub fn insert( + &mut self, + client_id: ClientId, + pane_id: PaneId, + panes: &mut BTreeMap>, + ) { + self.unfocus_pane_for_client(client_id, panes); + self.active_panes.insert(client_id, pane_id); + self.focus_pane(pane_id, panes); + } + pub fn clear(&mut self, panes: &mut BTreeMap>) { + for pane_id in self.active_panes.values() { + self.unfocus_pane(*pane_id, panes); + } + self.active_panes.clear(); + } + pub fn is_empty(&self) -> bool { + self.active_panes.is_empty() + } + pub fn iter(&self) -> impl Iterator { + self.active_panes.iter() + } + pub fn values(&self) -> impl Iterator { + self.active_panes.values() + } + pub fn remove( + &mut self, + client_id: &ClientId, + panes: &mut BTreeMap>, + ) -> Option { + if let Some(pane_id_to_unfocus) = self.active_panes.get(&client_id) { + self.unfocus_pane(*pane_id_to_unfocus, panes); + } + self.active_panes.remove(client_id) + } + pub fn unfocus_all_panes(&self, panes: &mut BTreeMap>) { + for (_client_id, pane_id) in &self.active_panes { + self.unfocus_pane(*pane_id, panes); + } + } + pub fn focus_all_panes(&self, panes: &mut BTreeMap>) { + for (_client_id, pane_id) in &self.active_panes { + self.focus_pane(*pane_id, panes); + } + } + pub fn clone_active_panes(&self) -> HashMap { + self.active_panes.clone() + } + pub fn contains_key(&self, client_id: &ClientId) -> bool { + self.active_panes.contains_key(client_id) + } + fn unfocus_pane_for_client( + &self, + client_id: ClientId, + panes: &mut BTreeMap>, + ) { + if let Some(pane_id_to_unfocus) = self.active_panes.get(&client_id) { + self.unfocus_pane(*pane_id_to_unfocus, panes); + } + } + fn unfocus_pane(&self, pane_id: PaneId, panes: &mut BTreeMap>) { + if let PaneId::Terminal(terminal_id) = pane_id { + if let Some(focus_event) = panes.get(&pane_id).and_then(|p| p.unfocus_event()) { + let _ = self + .os_api + .write_to_tty_stdin(terminal_id, focus_event.as_bytes()); + } + } + } + fn focus_pane(&self, pane_id: PaneId, panes: &mut BTreeMap>) { + if let PaneId::Terminal(terminal_id) = pane_id { + if let Some(focus_event) = panes.get(&pane_id).and_then(|p| p.focus_event()) { + let _ = self + .os_api + .write_to_tty_stdin(terminal_id, focus_event.as_bytes()); + } + } + } +} diff --git a/zellij-server/src/panes/floating_panes/mod.rs b/zellij-server/src/panes/floating_panes/mod.rs index 48d4d700..c8a60681 100644 --- a/zellij-server/src/panes/floating_panes/mod.rs +++ b/zellij-server/src/panes/floating_panes/mod.rs @@ -7,7 +7,7 @@ use floating_pane_grid::FloatingPaneGrid; use crate::{ os_input_output::ServerOsApi, output::{FloatingPanesStack, Output}, - panes::PaneId, + panes::{ActivePanes, PaneId}, ui::pane_contents_and_ui::PaneContentsAndUi, ClientId, }; @@ -50,7 +50,7 @@ pub struct FloatingPanes { session_is_mirrored: bool, desired_pane_positions: HashMap, // this represents the positions of panes the user moved with intention, rather than by resizing the terminal window z_indices: Vec, - active_panes: HashMap, + active_panes: ActivePanes, show_panes: bool, pane_being_moved_with_mouse: Option<(PaneId, Position)>, } @@ -67,6 +67,7 @@ impl FloatingPanes { session_is_mirrored: bool, default_mode_info: ModeInfo, style: Style, + os_input: Box, ) -> Self { FloatingPanes { panes: BTreeMap::new(), @@ -81,7 +82,7 @@ impl FloatingPanes { desired_pane_positions: HashMap::new(), z_indices: vec![], show_panes: false, - active_panes: HashMap::new(), + active_panes: ActivePanes::new(&os_input), pane_being_moved_with_mouse: None, } } @@ -195,6 +196,11 @@ impl FloatingPanes { } pub fn toggle_show_panes(&mut self, should_show_floating_panes: bool) { self.show_panes = should_show_floating_panes; + if should_show_floating_panes { + self.active_panes.focus_all_panes(&mut self.panes); + } else { + self.active_panes.unfocus_all_panes(&mut self.panes); + } } pub fn active_panes_contain(&self, client_id: &ClientId) -> bool { self.active_panes.contains_key(client_id) @@ -260,7 +266,7 @@ impl FloatingPanes { }); for (z_index, (kind, pane)) in floating_panes.iter_mut().enumerate() { - let mut active_panes = self.active_panes.clone(); + let mut active_panes = self.active_panes.clone_active_panes(); let multiple_users_exist_in_session = { self.connected_clients_in_app.borrow().len() > 1 }; active_panes.retain(|c_id, _| self.connected_clients.borrow().contains(c_id)); @@ -532,7 +538,7 @@ impl FloatingPanes { }, None => { // TODO: can this happen? - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); self.z_indices.clear(); }, } @@ -603,7 +609,7 @@ impl FloatingPanes { }, None => { // TODO: can this happen? - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); self.z_indices.clear(); }, } @@ -673,7 +679,7 @@ impl FloatingPanes { }, None => { // TODO: can this happen? - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); self.z_indices.clear(); }, } @@ -743,7 +749,7 @@ impl FloatingPanes { }, None => { // TODO: can this happen? - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); self.z_indices.clear(); }, } @@ -816,7 +822,8 @@ impl FloatingPanes { if active_pane_id == pane_id { match next_active_pane { Some(next_active_pane) => { - self.active_panes.insert(client_id, next_active_pane); + self.active_panes + .insert(client_id, next_active_pane, &mut self.panes); self.focus_pane(next_active_pane, client_id); }, None => { @@ -830,7 +837,8 @@ impl FloatingPanes { let connected_clients: Vec = self.connected_clients.borrow().iter().copied().collect(); for client_id in connected_clients { - self.active_panes.insert(client_id, pane_id); + self.active_panes + .insert(client_id, pane_id, &mut self.panes); } self.z_indices.retain(|p_id| *p_id != pane_id); self.z_indices.push(pane_id); @@ -838,12 +846,13 @@ impl FloatingPanes { self.set_force_render(); } pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { - self.active_panes.insert(client_id, pane_id); + self.active_panes + .insert(client_id, pane_id, &mut self.panes); self.focus_pane_for_all_clients(pane_id); } pub fn defocus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { self.z_indices.retain(|p_id| *p_id != pane_id); - self.active_panes.remove(&client_id); + self.active_panes.remove(&client_id, &mut self.panes); self.set_force_render(); } pub fn get_pane(&self, pane_id: PaneId) -> Option<&Box> { @@ -960,8 +969,9 @@ impl FloatingPanes { .map(|(cid, _pid)| *cid) .collect(); for client_id in clients_in_pane { - self.active_panes.remove(&client_id); - self.active_panes.insert(client_id, to_pane_id); + self.active_panes.remove(&client_id, &mut self.panes); + self.active_panes + .insert(client_id, to_pane_id, &mut self.panes); } } } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index d9d60fef..f570a446 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -365,6 +365,7 @@ pub struct Grid { scrollback_buffer_lines: usize, pub mouse_mode: MouseMode, pub mouse_tracking: MouseTracking, + pub focus_event_tracking: bool, pub search_results: SearchResult, pub pending_clipboard_update: Option, } @@ -497,6 +498,7 @@ impl Grid { scrollback_buffer_lines: 0, mouse_mode: MouseMode::default(), mouse_tracking: MouseTracking::default(), + focus_event_tracking: false, character_cell_size, search_results: Default::default(), sixel_grid, @@ -1533,6 +1535,7 @@ impl Grid { self.sixel_scrolling = false; self.mouse_mode = MouseMode::NoEncoding; self.mouse_tracking = MouseTracking::Off; + self.focus_event_tracking = false; self.cursor_is_hidden = false; if let Some(images_to_reap) = self.sixel_grid.clear() { self.sixel_grid.reap_images(images_to_reap); @@ -1942,6 +1945,20 @@ impl Grid { pub fn is_alternate_mode_active(&self) -> bool { self.alternate_screen_state.is_some() } + pub fn focus_event(&self) -> Option { + if self.focus_event_tracking { + Some("\u{1b}[I".into()) + } else { + None + } + } + pub fn unfocus_event(&self) -> Option { + if self.focus_event_tracking { + Some("\u{1b}[O".into()) + } else { + None + } + } } impl Perform for Grid { @@ -2356,6 +2373,9 @@ impl Perform for Grid { 1003 => { // TBD: any-even mouse tracking }, + 1004 => { + self.focus_event_tracking = false; + }, 1005 => { self.mouse_mode = MouseMode::NoEncoding; }, @@ -2450,6 +2470,9 @@ impl Perform for Grid { 1003 => { // TBD: any-even mouse tracking }, + 1004 => { + self.focus_event_tracking = true; + }, 1005 => { self.mouse_mode = MouseMode::Utf8; }, diff --git a/zellij-server/src/panes/mod.rs b/zellij-server/src/panes/mod.rs index 17e7e9d9..5055d0d3 100644 --- a/zellij-server/src/panes/mod.rs +++ b/zellij-server/src/panes/mod.rs @@ -5,12 +5,14 @@ pub mod selection; pub mod sixel; pub mod terminal_character; +mod active_panes; mod floating_panes; mod plugin_pane; mod search; mod terminal_pane; mod tiled_panes; +pub use active_panes::*; pub use alacritty_functions::*; pub use floating_panes::*; pub use grid::*; diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 5726ef44..0cb3601a 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -655,6 +655,12 @@ impl Pane for TerminalPane { fn mouse_scroll_down(&self, position: &Position) -> Option { self.grid.mouse_scroll_down_signal(position) } + fn focus_event(&self) -> Option { + self.grid.focus_event() + } + fn unfocus_event(&self) -> Option { + self.grid.unfocus_event() + } fn get_line_number(&self) -> Option { // + 1 because the absolute position in the scrollback is 0 indexed and this should be 1 indexed Some(self.grid.absolute_position_in_scrollback() + 1) diff --git a/zellij-server/src/panes/tiled_panes/mod.rs b/zellij-server/src/panes/tiled_panes/mod.rs index 86b21efd..3f4faa53 100644 --- a/zellij-server/src/panes/tiled_panes/mod.rs +++ b/zellij-server/src/panes/tiled_panes/mod.rs @@ -5,8 +5,12 @@ use crate::tab::{Pane, MIN_TERMINAL_HEIGHT, MIN_TERMINAL_WIDTH}; use tiled_pane_grid::{split, TiledPaneGrid}; use crate::{ - os_input_output::ServerOsApi, output::Output, panes::PaneId, ui::boundaries::Boundaries, - ui::pane_contents_and_ui::PaneContentsAndUi, ClientId, + os_input_output::ServerOsApi, + output::Output, + panes::{ActivePanes, PaneId}, + ui::boundaries::Boundaries, + ui::pane_contents_and_ui::PaneContentsAndUi, + ClientId, }; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet}; @@ -66,7 +70,7 @@ pub struct TiledPanes { default_mode_info: ModeInfo, style: Style, session_is_mirrored: bool, - active_panes: HashMap, + active_panes: ActivePanes, draw_pane_frames: bool, panes_to_hide: HashSet, fullscreen_is_active: bool, @@ -99,7 +103,7 @@ impl TiledPanes { default_mode_info, style, session_is_mirrored, - active_panes: HashMap::new(), + active_panes: ActivePanes::new(&os_api), draw_pane_frames, panes_to_hide: HashSet::new(), fullscreen_is_active: false, @@ -330,18 +334,20 @@ impl TiledPanes { } } pub fn focus_pane(&mut self, pane_id: PaneId, client_id: ClientId) { - self.active_panes.insert(client_id, pane_id); + self.active_panes + .insert(client_id, pane_id, &mut self.panes); if self.session_is_mirrored { // move all clients let connected_clients: Vec = self.connected_clients.borrow().iter().copied().collect(); for client_id in connected_clients { - self.active_panes.insert(client_id, pane_id); + self.active_panes + .insert(client_id, pane_id, &mut self.panes); } } } pub fn clear_active_panes(&mut self) { - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); } pub fn first_active_pane_id(&self) -> Option { self.connected_clients @@ -595,7 +601,8 @@ impl TiledPanes { ); let next_active_pane_id = pane_grid.next_selectable_pane_id(&active_pane_id); for client_id in connected_clients { - self.active_panes.insert(client_id, next_active_pane_id); + self.active_panes + .insert(client_id, next_active_pane_id, &mut self.panes); } self.set_pane_active_at(next_active_pane_id); } @@ -611,7 +618,8 @@ impl TiledPanes { ); let next_active_pane_id = pane_grid.previous_selectable_pane_id(&active_pane_id); for client_id in connected_clients { - self.active_panes.insert(client_id, next_active_pane_id); + self.active_panes + .insert(client_id, next_active_pane_id, &mut self.panes); } self.set_pane_active_at(next_active_pane_id); } @@ -967,21 +975,22 @@ impl TiledPanes { .iter() .map(|(cid, pid)| (*cid, *pid)) .collect(); - match self + let next_active_pane_id = self .panes .iter() .filter(|(p_id, _)| !self.panes_to_hide.contains(p_id)) .find(|(p_id, p)| **p_id != pane_id && p.selectable()) - .map(|(p_id, _p)| p_id) - { + .map(|(p_id, _p)| *p_id); + match next_active_pane_id { Some(next_active_pane) => { for (client_id, active_pane_id) in active_panes { if active_pane_id == pane_id { - self.active_panes.insert(client_id, *next_active_pane); + self.active_panes + .insert(client_id, next_active_pane, &mut self.panes); } } }, - None => self.active_panes.clear(), + None => self.active_panes.clear(&mut self.panes), } } pub fn extract_pane(&mut self, pane_id: PaneId) -> Option> { @@ -1004,7 +1013,7 @@ impl TiledPanes { self.panes.remove(&pane_id); // this is a bit of a roundabout way to say: this is the last pane and so the tab // should be destroyed - self.active_panes.clear(); + self.active_panes.clear(&mut self.panes); None } } @@ -1141,6 +1150,12 @@ impl TiledPanes { pub fn remove_from_hidden_panels(&mut self, pid: PaneId) { self.panes_to_hide.remove(&pid); } + pub fn unfocus_all_panes(&mut self) { + self.active_panes.unfocus_all_panes(&mut self.panes); + } + pub fn focus_all_panes(&mut self) { + self.active_panes.focus_all_panes(&mut self.panes); + } fn move_clients_between_panes(&mut self, from_pane_id: PaneId, to_pane_id: PaneId) { let clients_in_pane: Vec = self .active_panes @@ -1149,8 +1164,9 @@ impl TiledPanes { .map(|(cid, _pid)| *cid) .collect(); for client_id in clients_in_pane { - self.active_panes.remove(&client_id); - self.active_panes.insert(client_id, to_pane_id); + self.active_panes.remove(&client_id, &mut self.panes); + self.active_panes + .insert(client_id, to_pane_id, &mut self.panes); } } } diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index b38d6b42..fca2386f 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -330,6 +330,12 @@ pub trait Pane { fn mouse_scroll_down(&self, _position: &Position) -> Option { None } + fn focus_event(&self) -> Option { + None + } + fn unfocus_event(&self) -> Option { + None + } fn get_line_number(&self) -> Option { None } @@ -429,6 +435,7 @@ impl Tab { session_is_mirrored, default_mode_info.clone(), style, + os_api.clone(), ); let clipboard_provider = match copy_options.command { @@ -751,7 +758,7 @@ impl Tab { self.should_clear_display_before_rendering = true; self.tiled_panes .focus_pane(focused_floating_pane_id, client_id); - self.floating_panes.toggle_show_panes(false); + self.hide_floating_panes(); } } } else if let Some(focused_pane_id) = self.tiled_panes.focused_pane_id(client_id) { @@ -767,7 +774,7 @@ impl Tab { self.floating_panes .add_pane(focused_pane_id, embedded_pane_to_float); self.floating_panes.focus_pane(focused_pane_id, client_id); - self.floating_panes.toggle_show_panes(true); + self.show_floating_panes(); } } } @@ -779,10 +786,10 @@ impl Tab { default_shell: Option, ) -> Result<()> { if self.floating_panes.panes_are_visible() { - self.floating_panes.toggle_show_panes(false); + self.hide_floating_panes(); self.set_force_render(); } else { - self.floating_panes.toggle_show_panes(true); + self.show_floating_panes(); match self.floating_panes.first_floating_pane_id() { Some(first_floating_pane_id) => { if !self.floating_panes.active_panes_contain(&client_id) { @@ -819,8 +826,8 @@ impl Tab { let err_context = || format!("failed to create new pane with id {pid:?}"); match should_float { - Some(true) => self.floating_panes.toggle_show_panes(true), - Some(false) => self.floating_panes.toggle_show_panes(false), + Some(true) => self.show_floating_panes(), + Some(false) => self.hide_floating_panes(), None => {}, }; self.close_down_to_max_terminals() @@ -1759,7 +1766,7 @@ impl Tab { 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.floating_panes.toggle_show_panes(false); + self.hide_floating_panes(); } self.set_force_render(); self.floating_panes.set_force_render(); @@ -2206,7 +2213,7 @@ impl Tab { self.tiled_panes.focus_pane(clicked_pane, client_id); self.set_pane_active_at(clicked_pane); if self.floating_panes.panes_are_visible() { - self.floating_panes.toggle_show_panes(false); + self.hide_floating_panes(); self.set_force_render(); } } @@ -2711,6 +2718,19 @@ impl Tab { active_pane.clear_search(); } } + + fn show_floating_panes(&mut self) { + // this function is to be preferred to directly invoking floating_panes.toggle_show_panes(true) + self.floating_panes.toggle_show_panes(true); + self.tiled_panes.unfocus_all_panes(); + } + + fn hide_floating_panes(&mut self) { + // this function is to be preferred to directly invoking + // floating_panes.toggle_show_panes(false) + self.floating_panes.toggle_show_panes(false); + self.tiled_panes.focus_all_panes(); + } } #[cfg(test)] diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_focus_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_focus_sends_tty_csi_event.snap new file mode 100644 index 00000000..71504aba --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_floating_pane_focus_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2657 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{2: [27, 91, 73, 27, 91, 79, 27, 91, 73], 3: [27, 91, 79]} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_sends_tty_csi_event.snap new file mode 100644 index 00000000..5824e7a8 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2614 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{1: [27, 91, 73, 27, 91, 79, 27, 91, 73], 2: [27, 91, 79]} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_with_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_with_sends_tty_csi_event.snap new file mode 100644 index 00000000..3ed4c528 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__move_pane_focus_with_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2615 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{2: [27, 91, 79], 1: [27, 91, 73, 27, 91, 79, 27, 91, 73]} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_off_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_off_sends_tty_csi_event.snap new file mode 100644 index 00000000..81d612e3 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_off_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2744 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{1: [27, 91, 73], 3: [27, 91, 79]} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_on_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_on_sends_tty_csi_event.snap new file mode 100644 index 00000000..29bab74b --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_on_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2701 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{1: [27, 91, 79], 3: [27, 91, 73]} diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_sends_tty_csi_event.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_sends_tty_csi_event.snap new file mode 100644 index 00000000..29bab74b --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__toggle_floating_panes_sends_tty_csi_event.snap @@ -0,0 +1,6 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +assertion_line: 2701 +expression: "format!(\"{:?}\", * tty_stdin_bytes.lock().unwrap())" +--- +{1: [27, 91, 79], 3: [27, 91, 73]} diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index fd4dcbed..281da814 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -22,7 +22,7 @@ use crate::pty_writer::PtyWriteInstruction; use zellij_utils::channels::{self, ChannelWithContext, SenderWithContext}; use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::os::unix::io::RawFd; use std::rc::Rc; @@ -33,9 +33,10 @@ use zellij_utils::{ ipc::{ClientToServerMsg, ServerToClientMsg}, }; -#[derive(Clone)] +#[derive(Clone, Default)] struct FakeInputOutput { file_dumps: Arc>>, + pub tty_stdin_bytes: Arc>>>, } impl ServerOsApi for FakeInputOutput { @@ -57,8 +58,14 @@ impl ServerOsApi for FakeInputOutput { fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result { - unimplemented!() + fn write_to_tty_stdin(&self, id: u32, buf: &[u8]) -> Result { + self.tty_stdin_bytes + .lock() + .unwrap() + .entry(id) + .or_insert_with(|| vec![]) + .extend_from_slice(buf); + Ok(buf.len()) } fn tcdrain(&self, _id: u32) -> Result<()> { unimplemented!() @@ -181,9 +188,57 @@ fn create_new_tab(size: Size, default_mode: ModeInfo) -> Tab { let index = 0; let position = 0; let name = String::new(); - let os_api = Box::new(FakeInputOutput { - file_dumps: Arc::new(Mutex::new(HashMap::new())), - }); + let os_api = Box::new(FakeInputOutput::default()); + let senders = ThreadSenders::default().silently_fail_on_send(); + let max_panes = None; + let mode_info = default_mode; + let style = Style::default(); + let draw_pane_frames = true; + let client_id = 1; + let session_is_mirrored = true; + let mut connected_clients = HashSet::new(); + connected_clients.insert(client_id); + let connected_clients = Rc::new(RefCell::new(connected_clients)); + let character_cell_info = Rc::new(RefCell::new(None)); + let terminal_emulator_colors = Rc::new(RefCell::new(Palette::default())); + let copy_options = CopyOptions::default(); + let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new())); + let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default())); + let mut tab = Tab::new( + index, + position, + name, + size, + character_cell_info, + sixel_image_store, + os_api, + senders, + max_panes, + style, + mode_info, + draw_pane_frames, + connected_clients, + session_is_mirrored, + client_id, + copy_options, + terminal_emulator_colors, + terminal_emulator_color_codes, + ); + tab.apply_layout(PaneLayout::default(), vec![(1, None)], index, client_id) + .unwrap(); + tab +} + +fn create_new_tab_with_os_api( + size: Size, + default_mode: ModeInfo, + os_api: &Box, +) -> Tab { + set_session_name("test".into()); + let index = 0; + let position = 0; + let name = String::new(); + let os_api = os_api.clone(); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = default_mode; @@ -229,9 +284,7 @@ fn create_new_tab_with_layout(size: Size, default_mode: ModeInfo, layout: &str) let index = 0; let position = 0; let name = String::new(); - let os_api = Box::new(FakeInputOutput { - file_dumps: Arc::new(Mutex::new(HashMap::new())), - }); + let os_api = Box::new(FakeInputOutput::default()); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = default_mode; @@ -289,9 +342,7 @@ fn create_new_tab_with_mock_pty_writer( let index = 0; let position = 0; let name = String::new(); - let os_api = Box::new(FakeInputOutput { - file_dumps: Arc::new(Mutex::new(HashMap::new())), - }); + let os_api = Box::new(FakeInputOutput::default()); let mut senders = ThreadSenders::default().silently_fail_on_send(); senders.replace_to_pty_writer(mock_pty_writer); let max_panes = None; @@ -343,9 +394,7 @@ fn create_new_tab_with_sixel_support( let index = 0; let position = 0; let name = String::new(); - let os_api = Box::new(FakeInputOutput { - file_dumps: Arc::new(Mutex::new(HashMap::new())), - }); + let os_api = Box::new(FakeInputOutput::default()); let senders = ThreadSenders::default().silently_fail_on_send(); let max_panes = None; let mode_info = ModeInfo::default(); @@ -490,6 +539,7 @@ fn dump_screen() { let map = Arc::new(Mutex::new(HashMap::new())); tab.os_api = Box::new(FakeInputOutput { file_dumps: map.clone(), + ..Default::default() }); let new_pane_id = PaneId::Terminal(2); tab.new_pane(new_pane_id, None, None, Some(client_id)) @@ -2522,3 +2572,165 @@ fn pane_faux_scrolling_in_alternate_mode() { assert_eq!(pty_instruction_bus.clone_output(), expected); } + +#[test] +fn move_pane_focus_sends_tty_csi_event() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); + let os_api = Box::new(FakeInputOutput { + tty_stdin_bytes: tty_stdin_bytes.clone(), + ..Default::default() + }); + let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); + let new_pane_id_1 = PaneId::Terminal(2); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.handle_pty_bytes( + 1, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 2, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.move_focus_left(client_id); + assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); +} + +#[test] +fn move_floating_pane_focus_sends_tty_csi_event() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); + let os_api = Box::new(FakeInputOutput { + tty_stdin_bytes: tty_stdin_bytes.clone(), + ..Default::default() + }); + let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); + let new_pane_id_1 = PaneId::Terminal(2); + let new_pane_id_2 = PaneId::Terminal(3); + + tab.toggle_floating_panes(client_id, None).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.handle_pty_bytes( + 1, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 2, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 3, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.move_focus_left(client_id); + assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); +} + +#[test] +fn toggle_floating_panes_on_sends_tty_csi_event() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); + let os_api = Box::new(FakeInputOutput { + tty_stdin_bytes: tty_stdin_bytes.clone(), + ..Default::default() + }); + let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); + let new_pane_id_1 = PaneId::Terminal(2); + let new_pane_id_2 = PaneId::Terminal(3); + + tab.toggle_floating_panes(client_id, None).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.toggle_floating_panes(client_id, None).unwrap(); + tab.handle_pty_bytes( + 1, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 2, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 3, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.toggle_floating_panes(client_id, None).unwrap(); + assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); +} + +#[test] +fn toggle_floating_panes_off_sends_tty_csi_event() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let tty_stdin_bytes = Arc::new(Mutex::new(BTreeMap::new())); + let os_api = Box::new(FakeInputOutput { + tty_stdin_bytes: tty_stdin_bytes.clone(), + ..Default::default() + }); + let mut tab = create_new_tab_with_os_api(size, ModeInfo::default(), &os_api); + let new_pane_id_1 = PaneId::Terminal(2); + let new_pane_id_2 = PaneId::Terminal(3); + + tab.toggle_floating_panes(client_id, None).unwrap(); + tab.new_pane(new_pane_id_1, None, None, Some(client_id)) + .unwrap(); + tab.new_pane(new_pane_id_2, None, None, Some(client_id)) + .unwrap(); + tab.handle_pty_bytes( + 1, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 2, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.handle_pty_bytes( + 3, + // subscribe to focus events + Vec::from("\u{1b}[?1004h".as_bytes()), + ) + .unwrap(); + tab.toggle_floating_panes(client_id, None).unwrap(); + assert_snapshot!(format!("{:?}", *tty_stdin_bytes.lock().unwrap())); +}