diff options
author | Aram Drevekenin <aram@poor.dev> | 2022-11-04 17:29:41 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-04 17:29:41 +0100 |
commit | 8d1a497d10b16ede047aa905e4315451f13fa0bd (patch) | |
tree | 3513e741b248baaac8cf424005bb62d84ab46fc0 | |
parent | c34853adacd1d2bd7f356a84a0bd8cdd4d4082d1 (diff) |
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
14 files changed, 480 insertions, 55 deletions
diff --git a/zellij-server/src/panes/active_panes.rs b/zellij-server/src/panes/active_panes.rs new file mode 100644 index 000000000..3fce0a9ff --- /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<ClientId, PaneId>, + os_api: Box<dyn ServerOsApi>, +} + +impl ActivePanes { + pub fn new(os_api: &Box<dyn ServerOsApi>) -> 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<PaneId, Box<dyn Pane>>, + ) { + 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<PaneId, Box<dyn Pane>>) { + 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<Item = (&ClientId, &PaneId)> { + self.active_panes.iter() + } + pub fn values(&self) -> impl Iterator<Item = &PaneId> { + self.active_panes.values() + } + pub fn remove( + &mut self, + client_id: &ClientId, + panes: &mut BTreeMap<PaneId, Box<dyn Pane>>, + ) -> Option<PaneId> { + 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<PaneId, Box<dyn Pane>>) { + for (_client_id, pane_id) in &self.active_panes { + self.unfocus_pane(*pane_id, panes); + } + } + pub fn focus_all_panes(&self, panes: &mut BTreeMap<PaneId, Box<dyn Pane>>) { + for (_client_id, pane_id) in &self.active_panes { + self.focus_pane(*pane_id, panes); + } + } + pub fn clone_active_panes(&self) -> HashMap<ClientId, PaneId> { + 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<PaneId, Box<dyn Pane>>, + ) { + 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<PaneId, Box<dyn Pane>>) { + 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<PaneId, Box<dyn Pane>>) { + 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 48d4d700f..c8a606812 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<PaneId, PaneGeom>, // this represents the positions of panes the user moved with intention, rather than by resizing the terminal window z_indices: Vec<PaneId>, - active_panes: HashMap<ClientId, PaneId>, + 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<dyn ServerOsApi>, ) -> 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<ClientId> = 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<dyn Pane>> { @@ -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 d9d60fefe..f570a4460 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<String>, } @@ -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<String> { + if self.focus_event_tracking { + Some("\u{1b}[I".into()) + } else { + None + } + } + pub fn unfocus_event(&self) -> Option<String> { + 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 17e7e9d91..5055d0d30 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 5726ef44b..0cb3601a0 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<String> { self.grid.mouse_scroll_down_signal(position) } + fn focus_event(&self) -> Option<String> { + self.grid.focus_event() + } + fn unfocus_event(&self) -> Option<String> { + self.grid.unfocus_event() + } fn get_line_number(&self) -> Option<usize> { // + 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 86b21efde..3f4faa53a 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<ClientId, PaneId>, + active_panes: ActivePanes, draw_pane_frames: bool, panes_to_hide: HashSet<PaneId>, 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<ClientId> = 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<PaneId> { 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<Box<dyn Pane>> { @@ -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<ClientId> = 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 b38d6b428..fca2386f9 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<String> { None } + fn focus_event(&self) -> Option<String> { + None + } + fn unfocus_event(&self) -> Option<String> { + None + } fn get_line_number(&self) -> Option<usize> { 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<TerminalAction>, ) -> 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 + // |