diff options
Diffstat (limited to 'zellij-server/src')
-rw-r--r-- | zellij-server/src/screen.rs | 177 | ||||
-rw-r--r-- | zellij-server/src/tab.rs | 119 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_boundaries_frame.rs | 18 | ||||
-rw-r--r-- | zellij-server/src/ui/pane_contents_and_ui.rs | 33 | ||||
-rw-r--r-- | zellij-server/src/unit/screen_tests.rs | 2 | ||||
-rw-r--r-- | zellij-server/src/unit/tab_tests.rs | 9 |
6 files changed, 231 insertions, 127 deletions
diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index f5beade59..7fa632752 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -1,7 +1,9 @@ //! Things related to [`Screen`]s. +use std::cell::RefCell; use std::collections::{BTreeMap, HashSet}; use std::os::unix::io::RawFd; +use std::rc::Rc; use std::str; use zellij_utils::pane_size::Size; @@ -182,6 +184,7 @@ pub(crate) struct Screen { size: Size, /// The overlay that is drawn on top of [`Pane`]'s', [`Tab`]'s and the [`Screen`] overlay: OverlayWindow, + connected_clients: Rc<RefCell<HashSet<ClientId>>>, /// The indices of this [`Screen`]'s active [`Tab`]s. active_tab_indices: BTreeMap<ClientId, usize>, tab_history: BTreeMap<ClientId, Vec<usize>>, @@ -189,6 +192,7 @@ pub(crate) struct Screen { default_mode_info: ModeInfo, // TODO: restructure ModeInfo to prevent this duplication colors: Palette, draw_pane_frames: bool, + session_is_mirrored: bool, } impl Screen { @@ -199,12 +203,14 @@ impl Screen { max_panes: Option<usize>, mode_info: ModeInfo, draw_pane_frames: bool, + session_is_mirrored: bool, ) -> Self { Screen { bus, max_panes, size: client_attributes.size, colors: client_attributes.palette, + connected_clients: Rc::new(RefCell::new(HashSet::new())), active_tab_indices: BTreeMap::new(), tabs: BTreeMap::new(), overlay: OverlayWindow::default(), @@ -212,6 +218,7 @@ impl Screen { mode_info: BTreeMap::new(), default_mode_info: mode_info, draw_pane_frames, + session_is_mirrored, } } @@ -226,34 +233,50 @@ impl Screen { } } - fn move_clients_from_closed_tab(&mut self, previous_tab_index: usize) { - let client_ids_in_closed_tab: Vec<ClientId> = self - .active_tab_indices - .iter() - .filter(|(_c_id, t_index)| **t_index == previous_tab_index) - .map(|(c_id, _t_index)| c_id) - .copied() - .collect(); - for client_id in client_ids_in_closed_tab { + fn move_clients_from_closed_tab( + &mut self, + client_ids_and_mode_infos: Vec<(ClientId, ModeInfo)>, + ) { + for (client_id, client_mode_info) in client_ids_and_mode_infos { let client_previous_tab = self.tab_history.get_mut(&client_id).unwrap().pop().unwrap(); self.active_tab_indices .insert(client_id, client_previous_tab); self.tabs .get_mut(&client_previous_tab) .unwrap() - .add_client(client_id); + .add_client(client_id, Some(client_mode_info)); } } - fn move_clients(&mut self, source_index: usize, destination_index: usize) { - let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) = { - let source_tab = self.tabs.get_mut(&source_index).unwrap(); - source_tab.drain_connected_clients() - }; - let destination_tab = self.tabs.get_mut(&destination_index).unwrap(); - destination_tab.add_multiple_clients( - connected_clients_in_source_tab, - client_mode_infos_in_source_tab, - ); + fn move_clients_between_tabs( + &mut self, + source_tab_index: usize, + destination_tab_index: usize, + clients_to_move: Option<Vec<ClientId>>, + ) { + // None ==> move all clients + let drained_clients = self + .get_indexed_tab_mut(source_tab_index) + .map(|t| t.drain_connected_clients(clients_to_move)); + if let Some(client_mode_info_in_source_tab) = drained_clients { + let destination_tab = self.get_indexed_tab_mut(destination_tab_index).unwrap(); + destination_tab.add_multiple_clients(client_mode_info_in_source_tab); + destination_tab.update_input_modes(); + destination_tab.set_force_render(); + destination_tab.visible(true); + } + } + fn update_client_tab_focus(&mut self, client_id: ClientId, new_tab_index: usize) { + match self.active_tab_indices.remove(&client_id) { + Some(old_active_index) => { + self.active_tab_indices.insert(client_id, new_tab_index); + let client_tab_history = self.tab_history.entry(client_id).or_insert_with(Vec::new); + client_tab_history.retain(|&e| e != new_tab_index); + client_tab_history.push(old_active_index); + } + None => { + self.active_tab_indices.insert(client_id, new_tab_index); + } + } } /// A helper function to switch to a new tab at specified position. fn switch_active_tab(&mut self, new_tab_pos: usize, client_id: ClientId) { @@ -265,24 +288,29 @@ impl Screen { return; } - current_tab.visible(false); let current_tab_index = current_tab.index; let new_tab_index = new_tab.index; - let new_tab = self.get_indexed_tab_mut(new_tab_index).unwrap(); - new_tab.set_force_render(); - new_tab.visible(true); - - // currently all clients are just mirrors, so we perform the action for every entry in - // tab_history - // TODO: receive a client_id and only do it for the client - for (client_id, tab_history) in &mut self.tab_history { - let old_active_index = self.active_tab_indices.remove(client_id).unwrap(); - self.active_tab_indices.insert(*client_id, new_tab_index); - tab_history.retain(|&e| e != new_tab_pos); - tab_history.push(old_active_index); + if self.session_is_mirrored { + self.move_clients_between_tabs(current_tab_index, new_tab_index, None); + let all_connected_clients: Vec<ClientId> = + self.connected_clients.borrow().iter().copied().collect(); + for client_id in all_connected_clients { + self.update_client_tab_focus(client_id, new_tab_index); + } + } else { + self.move_clients_between_tabs( + current_tab_index, + new_tab_index, + Some(vec![client_id]), + ); + self.update_client_tab_focus(client_id, new_tab_index); } - self.move_clients(current_tab_index, new_tab_index); + if let Some(current_tab) = self.get_indexed_tab_mut(current_tab_index) { + if current_tab.has_no_connected_clients() { + current_tab.visible(false); + } + } self.update_tabs(); self.render(); @@ -314,7 +342,7 @@ impl Screen { } fn close_tab_at_index(&mut self, tab_index: usize) { - let tab_to_close = self.tabs.remove(&tab_index).unwrap(); + let mut tab_to_close = self.tabs.remove(&tab_index).unwrap(); let pane_ids = tab_to_close.get_pane_ids(); // below we don't check the result of sending the CloseTab instruction to the pty thread // because this might be happening when the app is closing, at which point the pty thread @@ -330,7 +358,8 @@ impl Screen { .send_to_server(ServerInstruction::Render(None)) .unwrap(); } else { - self.move_clients_from_closed_tab(tab_index); + let client_mode_infos_in_closed_tab = tab_to_close.drain_connected_clients(None); + self.move_clients_from_closed_tab(client_mode_infos_in_closed_tab); let visible_tab_indices: HashSet<usize> = self.active_tab_indices.values().copied().collect(); for t in self.tabs.values_mut() { @@ -446,28 +475,38 @@ impl Screen { client_mode_info, self.colors, self.draw_pane_frames, + self.connected_clients.clone(), + self.session_is_mirrored, client_id, ); tab.apply_layout(layout, new_pids, tab_index, client_id); - if let Some(active_tab) = self.get_active_tab_mut(client_id) { - active_tab.visible(false); - let (connected_clients_in_source_tab, client_mode_infos_in_source_tab) = - active_tab.drain_connected_clients(); - tab.add_multiple_clients( - connected_clients_in_source_tab, - client_mode_infos_in_source_tab, - ); - } - for (client_id, tab_history) in &mut self.tab_history { - let old_active_index = self.active_tab_indices.remove(client_id).unwrap(); - self.active_tab_indices.insert(*client_id, tab_index); - tab_history.retain(|&e| e != tab_index); - tab_history.push(old_active_index); + if self.session_is_mirrored { + if let Some(active_tab) = self.get_active_tab_mut(client_id) { + let client_mode_infos_in_source_tab = active_tab.drain_connected_clients(None); + tab.add_multiple_clients(client_mode_infos_in_source_tab); + if active_tab.has_no_connected_clients() { + active_tab.visible(false); + } + } + let all_connected_clients: Vec<ClientId> = + self.connected_clients.borrow().iter().copied().collect(); + for client_id in all_connected_clients { + self.update_client_tab_focus(client_id, tab_index); + } + } else if let Some(active_tab) = self.get_active_tab_mut(client_id) { + let client_mode_info_in_source_tab = + active_tab.drain_connected_clients(Some(vec![client_id])); + tab.add_multiple_clients(client_mode_info_in_source_tab); + if active_tab.has_no_connected_clients() { + active_tab.visible(false); + } + self.update_client_tab_focus(client_id, tab_index); } tab.update_input_modes(); tab.visible(true); self.tabs.insert(tab_index, tab); if !self.active_tab_indices.contains_key(&client_id) { + // this means this is a new client and we need to add it to our state properly self.add_client(client_id); } self.update_tabs(); @@ -486,12 +525,19 @@ impl Screen { tab_history = first_tab_history.clone(); } self.active_tab_indices.insert(client_id, tab_index); + self.connected_clients.borrow_mut().insert(client_id); self.tab_history.insert(client_id, tab_history); - self.tabs.get_mut(&tab_index).unwrap().add_client(client_id); + self.tabs + .get_mut(&tab_index) + .unwrap() + .add_client(client_id, None); } pub fn remove_client(&mut self, client_id: ClientId) { if let Some(client_tab) = self.get_active_tab_mut(client_id) { client_tab.remove_client(client_id); + if client_tab.has_no_connected_clients() { + client_tab.visible(false); + } } if self.active_tab_indices.contains_key(&client_id) { self.active_tab_indices.remove(&client_id); @@ -499,30 +545,41 @@ impl Screen { if self.tab_history.contains_key(&client_id) { self.tab_history.remove(&client_id); } + self.connected_clients.borrow_mut().remove(&client_id); + self.update_tabs(); } pub fn update_tabs(&self) { - let mut tab_data = vec![]; - // TODO: right now all clients are synced, so we just take the first active_tab which is - // the same for everyone - when this is no longer the case, we need to update the TabInfo - // to account for this (or send multiple TabInfos) - if let Some((_first_client, first_active_tab_index)) = self.active_tab_indices.iter().next() - { + for (client_id, active_tab_index) in self.active_tab_indices.iter() { + let mut tab_data = vec![]; for tab in self.tabs.values() { + let other_focused_clients: Vec<ClientId> = if self.session_is_mirrored { + vec![] + } else { + self.active_tab_indices + .iter() + .filter(|(c_id, tab_position)| { + **tab_position == tab.index && *c_id != client_id + }) + .map(|(c_id, _)| c_id) + .copied() + .collect() + }; tab_data.push(TabInfo { position: tab.position, name: tab.name.clone(), - active: *first_active_tab_index == tab.index, + active: *active_tab_index == tab.index, panes_to_hide: tab.panes_to_hide.len(), is_fullscreen_active: tab.is_fullscreen_active(), is_sync_panes_active: tab.is_sync_panes_active(), + other_focused_clients, }); } self.bus .senders .send_to_plugin(PluginInstruction::Update( None, - None, + Some(*client_id), Event::TabUpdate(tab_data), )) .unwrap(); @@ -607,6 +664,7 @@ pub(crate) fn screen_thread_main( ) { let capabilities = config_options.simplified_ui; let draw_pane_frames = config_options.pane_frames.unwrap_or(true); + let session_is_mirrored = config_options.mirror_session.unwrap_or(false); let mut screen = Screen::new( bus, @@ -620,6 +678,7 @@ pub(crate) fn screen_thread_main( }, ), draw_pane_frames, + session_is_mirrored, ); loop { let (event, mut err_ctx) = screen diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index ca8153ecf..fc5b0718f 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -17,7 +17,9 @@ use crate::{ ClientId, ServerInstruction, }; use serde::{Deserialize, Serialize}; +use std::cell::RefCell; use std::os::unix::io::RawFd; +use std::rc::Rc; use std::sync::mpsc::channel; use std::time::Instant; use std::{ @@ -111,9 +113,16 @@ impl Output { .insert(*client_id, String::new()); } } - pub fn push_str_to_all_clients(&mut self, to_push: &str) { - for render_instruction in self.client_render_instructions.values_mut() { - render_instruction.push_str(to_push) + pub fn push_str_to_multiple_clients( + &mut self, + to_push: &str, + client_ids: impl Iterator<Item = ClientId>, + ) { + for client_id in client_ids { + self.client_render_instructions + .get_mut(&client_id) + .unwrap() + .push_str(to_push) } } pub fn push_to_client(&mut self, client_id: ClientId, to_push: &str) { @@ -141,6 +150,7 @@ pub(crate) struct Tab { mode_info: HashMap<ClientId, ModeInfo>, default_mode_info: ModeInfo, pub colors: Palette, + connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>, // TODO: combine this and connected_clients connected_clients: HashSet<ClientId>, draw_pane_frames: bool, session_is_mirrored: bool, @@ -322,6 +332,8 @@ impl Tab { mode_info: ModeInfo, colors: Palette, draw_pane_frames: bool, + connected_clients_in_app: Rc<RefCell<HashSet<ClientId>>>, + session_is_mirrored: bool, client_id: ClientId, ) -> Self { let panes = BTreeMap::new(); @@ -354,10 +366,9 @@ impl Tab { default_mode_info: mode_info, colors, draw_pane_frames, - // at the moment this is hard-coded while the feature is being developed - // the only effect this has is to make sure the UI is drawn without additional information about other connected clients - session_is_mirrored: true, + session_is_mirrored, pending_vte_events: HashMap::new(), + connected_clients_in_app, connected_clients, } } @@ -491,14 +502,16 @@ impl Tab { .unwrap(); } } - pub fn add_client(&mut self, client_id: ClientId) { + pub fn add_client(&mut self, client_id: ClientId, mode_info: Option<ModeInfo>) { match self.connected_clients.iter().next() { Some(first_client_id) => { let first_active_pane_id = *self.active_panes.get(first_client_id).unwrap(); self.connected_clients.insert(client_id); self.active_panes.insert(client_id, first_active_pane_id); - self.mode_info - .insert(client_id, self.default_mode_info.clone()); + self.mode_info.insert( + client_id, + mode_info.unwrap_or_else(|| self.default_mode_info.clone()), + ); } None => { let mut pane_ids: Vec<PaneId> = self.panes.keys().copied().collect(); @@ -511,40 +524,53 @@ impl Tab { let first_pane_id = pane_ids.get(0).unwrap(); self.connected_clients.insert(client_id); self.active_panes.insert(client_id, *first_pane_id); - self.mode_info - .insert(client_id, self.default_mode_info.clone()); + self.mode_info.insert( + client_id, + mode_info.unwrap_or_else(|| self.default_mode_info.clone()), + ); } } // TODO: we might be able to avoid this, we do this so that newly connected clients will // necessarily get a full render self.set_force_render(); + self.update_input_modes(); } pub fn change_mode_info(&mut self, mode_info: ModeInfo, client_id: ClientId) { self.mode_info.insert(client_id, mode_info); } - pub fn add_multiple_clients( - &mut self, - client_ids: Vec<ClientId>, - client_mode_infos: Vec<(ClientId, ModeInfo)>, - ) { - for client_id in client_ids { - self.add_client(client_id); - } - for (client_id, client_mode_info) in client_mode_infos { + pub fn add_multiple_clients(&mut self, client_ids_to_mode_infos: Vec<(ClientId, ModeInfo)>) { + for (client_id, client_mode_info) in client_ids_to_mode_infos { + self.add_client(client_id, None); self.mode_info.insert(client_id, client_mode_info); } } pub fn remove_client(&mut self, client_id: ClientId) { self.connected_clients.remove(&client_id); - self.active_panes.remove(&client_id); self.set_force_render(); } - pub fn drain_connected_clients(&mut self) -> (Vec<ClientId>, Vec<(ClientId, ModeInfo)>) { - let client_mode_info = self.mode_info.drain(); - ( - self.connected_clients.drain().collect(), - client_mode_info.collect(), - ) + pub fn drain_connected_clients( + &mut self, + clients_to_drain: Option<Vec<ClientId>>, + ) -> Vec<(ClientId, ModeInfo)> { + // None => all clients + let mut client_ids_to_mode_infos = vec![]; + let clients_to_drain = + clients_to_drain.unwrap_or_else(|| self.connected_clients.drain().collect()); + for client_id in clients_to_drain { + client_ids_to_mode_infos.push(self.drain_single_client(client_id)); + } + client_ids_to_mode_infos + } + pub fn drain_single_client(&mut self, client_id: ClientId) -> (ClientId, ModeInfo) { + let client_mode_info = self + .mode_info + .remove(&client_id) + .unwrap_or_else(|| self.default_mode_info.clone()); + self.connected_clients.remove(&client_id); + (client_id, client_mode_info) + } + pub fn has_no_connected_clients(&self) -> bool { + self.connected_clients.is_empty() } pub fn new_pane(&mut self, pid: PaneId, client_id: Option<ClientId>) { self.close_down_to_max_terminals(); @@ -786,7 +812,8 @@ impl Tab { }); } pub fn write_to_active_terminal(&mut self, input_bytes: Vec<u8>, client_id: ClientId) { - self.write_to_pane_id(input_bytes, self.get_active_pane_id(client_id).unwrap()); + let pane_id = self.get_active_pane_id(client_id).unwrap(); + self.write_to_pane_id(input_bytes, pane_id); } pub fn write_to_pane_id(&mut self, input_bytes: Vec<u8>, pane_id: PaneId) { match pane_id { @@ -981,10 +1008,21 @@ impl Tab { // render panes and their frames for (kind, pane) in self.panes.iter_mut() { if !self.panes_to_hide.contains(&pane.pid()) { - let mut pane_contents_and_ui = - PaneContentsAndUi::new(pane, output, self.colors, &self.active_panes); + let mut active_panes = self.active_panes.clone(); + let multiple_users_exist_in_session = + { self.connected_clients_in_app.borrow().len() > 1 }; + active_panes.retain(|c_id, _| self.connected_clients.contains(c_id)); + let mut pane_contents_and_ui = PaneContentsAndUi::new( + pane, + output, + self.colors, + &active_panes, + multiple_users_exist_in_session, + ); if let PaneId::Terminal(..) = kind { - pane_contents_and_ui.render_pane_contents_for_all_clients(); + pane_contents_and_ui.render_pane_contents_to_multiple_clients( + self.connected_clients.iter().copied(), + ); } for &client_id in &self.connected_clients { let client_mode = self @@ -1024,16 +1062,21 @@ impl Tab { } // FIXME: Once clients can be distinguished if let Some(overlay_vte) = &overlay { - output.push_str_to_all_clients(overlay_vte); + // output.push_str_to_all_clients(overlay_vte); + output + .push_str_to_multiple_clients(overlay_vte, self.connected_clients.iter().copied()); } self.render_cursor(output); } fn hide_cursor_and_clear_display_as_needed(&mut self, output: &mut Output) { let hide_cursor = "\u{1b}[?25l"; - output.push_str_to_all_clients(hide_cursor); + output.push_str_to_multiple_clients(hide_cursor, self.connected_clients.iter().copied()); if self.should_clear_display_before_rendering { let clear_display = "\u{1b}[2J"; - output.push_str_to_all_clients(clear_display); + output.push_str_to_multiple_clients( + clear_display, + self.connected_clients.iter().copied(), + ); self.should_clear_display_before_rendering = false; } } @@ -3400,10 +3443,10 @@ impl Tab { fn write_selection_to_clipboard(&self, selection: &str) { let mut output = Output::default(); output.add_clients(&self.connected_clients); - output.push_str_to_all_clients(&format!( - "\u{1b}]52;c;{}\u{1b}\\", - base64::encode(selection) - )); + output.push_str_to_multiple_clients( + &format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)), + self.connected_clients.iter().copied(), + ); // TODO: ideally we should be sending the Render instruction from the screen self.senders diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 1ed29d838..419dea79c 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -3,7 +3,7 @@ use crate::ClientId; use ansi_term::Colour::{Fixed, RGB}; use ansi_term::Style; use zellij_utils::pane_size::Viewport; -use zellij_utils::zellij_tile::prelude::{Palette, PaletteColor}; +use zellij_utils::zellij_tile::prelude::{client_id_to_colors, Palette, PaletteColor}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; @@ -29,22 +29,6 @@ fn background_color(character: &str, color: Option<PaletteColor>) -> String { } } -// TODO: move elsewhere -pub(crate) fn client_id_to_colors( - client_id: ClientId, - colors: Palette, -) -> Option<(PaletteColor, PaletteColor)> { - // (primary color, secondary color) - match client_id { - 1 => Some((colors.green, colors.black)), - 2 => Some((colors.blue, colors.black)), - 3 => Some((colors.cyan, colors.black)), - 4 => Some((colors.magenta, colors.black)), - 5 => Some((colors.yellow, colors.black)), - _ => None, - } -} - pub struct FrameParams { pub focused_client: Option<ClientId>, pub is_main_client: bool, diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs index 25550fe9f..d8967f6ea 100644 --- a/zellij-server/src/ui/pane_contents_and_ui.rs +++ b/zellij-server/src/ui/pane_contents_and_ui.rs @@ -1,11 +1,12 @@ use crate::panes::PaneId; use crate::tab::{Output, Pane}; use crate::ui::boundaries::Boundaries; -use crate::ui::pane_boundaries_frame::client_id_to_colors; use crate::ui::pane_boundaries_frame::FrameParams; use crate::ClientId; use std::collections::HashMap; -use zellij_tile::data::{InputMode, Palette, PaletteColor}; +use zellij_tile::data::{ + client_id_to_colors, single_client_color, InputMode, Palette, PaletteColor, +}; pub struct PaneContentsAndUi<'a> { pane: &'a mut Box<dyn Pane>, @@ -21,13 +22,13 @@ impl<'a> PaneContentsAndUi<'a> { output: &'a mut Output, colors: Palette, active_panes: &HashMap<ClientId, PaneId>, + multiple_users_exist_in_session: bool, ) -> Self { let focused_clients: Vec<ClientId> = active_panes .iter() .filter(|(_c_id, p_id)| **p_id == pane.pid()) .map(|(c_id, _p_id)| *c_id) .collect(); - let multiple_users_exist_in_session = active_panes.len() > 1; PaneContentsAndUi { pane, output, @@ -36,15 +37,21 @@ impl<'a> PaneContentsAndUi<'a> { multiple_users_exist_in_session, } } - pub fn render_pane_contents_for_all_clients(&mut self) { + pub fn render_pane_contents_to_multiple_clients( + &mut self, + clients: impl Iterator<Item = ClientId>, + ) { if let Some(vte_output) = self.pane.render(None) { // FIXME: Use Termion for cursor and style clearing? - self.output.push_str_to_all_clients(&format!( - "\u{1b}[{};{}H\u{1b}[m{}", - self.pane.y() + 1, - self.pane.x() + 1, - vte_ou |