From f2401d0b2585c23c3358d0a2c1492c3a2981c70f Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Thu, 7 Oct 2021 15:22:20 +0200 Subject: [PATCH] refactor(clients): support multiple clients in tab/screen rendering infra (#770) --- zellij-server/src/lib.rs | 62 +++++++-- zellij-server/src/pty.rs | 12 +- zellij-server/src/route.rs | 2 +- zellij-server/src/screen.rs | 145 ++++++++++++++++++--- zellij-server/src/tab.rs | 172 +++++++++++++------------ zellij-server/src/unit/screen_tests.rs | 37 +++--- zellij-server/src/unit/tab_tests.rs | 32 ++--- zellij-utils/src/errors.rs | 3 +- 8 files changed, 314 insertions(+), 151 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 34b39fd8a..5ba049097 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -27,6 +27,7 @@ use crate::{ os_input_output::ServerOsApi, pty::{pty_thread_main, Pty, PtyInstruction}, screen::{screen_thread_main, ScreenInstruction}, + tab::Output, thread_bus::{Bus, ThreadSenders}, wasm_vm::{wasm_thread_main, PluginInstruction}, }; @@ -59,7 +60,7 @@ pub(crate) enum ServerInstruction { ClientId, Option, ), - Render(Option), + Render(Option), UnblockInputThread, ClientExit(ClientId), RemoveClient(ClientId), @@ -261,7 +262,6 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { os_input.clone(), to_server.clone(), client_attributes, - session_state.clone(), SessionOptions { opts, layout: layout.clone(), @@ -289,7 +289,11 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .as_ref() .unwrap() .senders - .send_to_pty(PtyInstruction::NewTab(default_shell.clone(), tab_layout)) + .send_to_pty(PtyInstruction::NewTab( + default_shell.clone(), + tab_layout, + client_id, + )) .unwrap() }; @@ -317,6 +321,10 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .senders .send_to_screen(ScreenInstruction::TerminalResize(min_size)) .unwrap(); + session_data + .senders + .send_to_screen(ScreenInstruction::AddClient(client_id)) + .unwrap(); let default_mode = options.default_mode.unwrap_or_default(); let mode_info = get_mode_info(default_mode, attrs.palette, session_data.capabilities); @@ -353,6 +361,16 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .senders .send_to_screen(ScreenInstruction::TerminalResize(min_size)) .unwrap(); + // we only do this inside this if because it means there are still connected + // clients + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)) + .unwrap(); } if session_state.read().unwrap().clients.is_empty() { *session_data.write().unwrap() = None; @@ -370,6 +388,16 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .senders .send_to_screen(ScreenInstruction::TerminalResize(min_size)) .unwrap(); + // we only do this inside this if because it means there are still connected + // clients + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)) + .unwrap(); } } ServerInstruction::DetachSession(client_id) => { @@ -384,6 +412,16 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { .senders .send_to_screen(ScreenInstruction::TerminalResize(min_size)) .unwrap(); + // we only do this inside this if because it means there are still connected + // clients + session_data + .write() + .unwrap() + .as_ref() + .unwrap() + .senders + .send_to_screen(ScreenInstruction::RemoveClient(client_id)) + .unwrap(); } } ServerInstruction::Render(mut output) => { @@ -392,8 +430,13 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { // If `Some(_)`- unwrap it and forward it to the clients to render. // If `None`- Send an exit instruction. This is the case when a user closes the last Tab/Pane. if let Some(op) = output.as_mut() { - for client_id in client_ids { - os_input.send_to_client(client_id, ServerToClientMsg::Render(op.clone())); + for (client_id, client_render_instruction) in + op.client_render_instructions.iter_mut() + { + os_input.send_to_client( + *client_id, + ServerToClientMsg::Render(client_render_instruction.clone()), + ); } } else { for client_id in client_ids { @@ -440,7 +483,6 @@ fn init_session( os_input: Box, to_server: SenderWithContext, client_attributes: ClientAttributes, - session_state: Arc>, options: SessionOptions, ) -> SessionMetaData { let SessionOptions { @@ -508,13 +550,7 @@ fn init_session( let max_panes = opts.max_panes; move || { - screen_thread_main( - screen_bus, - max_panes, - client_attributes, - config_options, - session_state, - ); + screen_thread_main(screen_bus, max_panes, client_attributes, config_options); } }) .unwrap(); diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 761b1702f..9a1f1423c 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -4,7 +4,7 @@ use crate::{ screen::ScreenInstruction, thread_bus::{Bus, ThreadSenders}, wasm_vm::PluginInstruction, - ServerInstruction, + ClientId, ServerInstruction, }; use async_std::{ future::timeout as async_timeout, @@ -36,7 +36,7 @@ pub(crate) enum PtyInstruction { SpawnTerminalVertically(Option), SpawnTerminalHorizontally(Option), UpdateActivePane(Option), - NewTab(Option, Option), + NewTab(Option, Option, ClientId), ClosePane(PaneId), CloseTab(Vec), Exit, @@ -96,7 +96,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { PtyInstruction::UpdateActivePane(pane_id) => { pty.set_active_pane(pane_id); } - PtyInstruction::NewTab(terminal_action, tab_layout) => { + PtyInstruction::NewTab(terminal_action, tab_layout, client_id) => { let tab_name = tab_layout.as_ref().and_then(|layout| { if layout.name.is_empty() { None @@ -109,7 +109,7 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { let layout: Layout = Layout::try_from(merged_layout).unwrap_or_else(|err| panic!("{}", err)); - pty.spawn_terminals_for_layout(layout, terminal_action.clone()); + pty.spawn_terminals_for_layout(layout, terminal_action.clone(), client_id); if let Some(tab_name) = tab_name { // clear current name at first @@ -284,6 +284,7 @@ impl Pty { &mut self, layout: Layout, default_shell: Option, + client_id: ClientId, ) { let default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); let extracted_run_instructions = layout.extract_run_instructions(); @@ -313,9 +314,10 @@ impl Pty { } self.bus .senders - .send_to_screen(ScreenInstruction::ApplyLayout( + .send_to_screen(ScreenInstruction::NewTab( layout, new_pane_pids.clone(), + client_id, )) .unwrap(); for id in new_pane_pids { diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 3d1a5960d..4a22b5c5c 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -204,7 +204,7 @@ fn route_action( let shell = session.default_shell.clone(); session .senders - .send_to_pty(PtyInstruction::NewTab(shell, tab_layout)) + .send_to_pty(PtyInstruction::NewTab(shell, tab_layout, client_id)) .unwrap(); } Action::GoToNextTab => { diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 275d65d5e..1c344f548 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -3,7 +3,6 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; -use std::sync::{Arc, RwLock}; use zellij_utils::pane_size::Size; use zellij_utils::{input::layout::Layout, position::Position, zellij_tile}; @@ -14,7 +13,7 @@ use crate::{ tab::Tab, thread_bus::Bus, wasm_vm::PluginInstruction, - ServerInstruction, SessionState, + ClientId, ServerInstruction, }; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, PluginCapabilities, TabInfo}; use zellij_utils::{ @@ -59,7 +58,7 @@ pub(crate) enum ScreenInstruction { TogglePaneFrames, SetSelectable(PaneId, bool, usize), ClosePane(PaneId), - ApplyLayout(Layout, Vec), + NewTab(Layout, Vec, ClientId), SwitchTabNext, SwitchTabPrev, ToggleActiveSyncTab, @@ -73,6 +72,8 @@ pub(crate) enum ScreenInstruction { MouseRelease(Position), MouseHold(Position), Copy, + AddClient(ClientId), + RemoveClient(ClientId), } impl From<&ScreenInstruction> for ScreenContext { @@ -113,7 +114,7 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, - ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout, + ScreenInstruction::NewTab(..) => ScreenContext::NewTab, ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, ScreenInstruction::CloseTab => ScreenContext::CloseTab, @@ -129,6 +130,8 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::MouseHold(_) => ScreenContext::MouseHold, ScreenInstruction::Copy => ScreenContext::Copy, ScreenInstruction::ToggleTab => ScreenContext::ToggleTab, + ScreenInstruction::AddClient(..) => ScreenContext::AddClient, + ScreenInstruction::RemoveClient(..) => ScreenContext::RemoveClient, } } } @@ -149,7 +152,6 @@ pub(crate) struct Screen { tab_history: Vec>, mode_info: ModeInfo, colors: Palette, - session_state: Arc>, draw_pane_frames: bool, } @@ -160,7 +162,6 @@ impl Screen { client_attributes: &ClientAttributes, max_panes: Option, mode_info: ModeInfo, - session_state: Arc>, draw_pane_frames: bool, ) -> Self { Screen { @@ -172,7 +173,6 @@ impl Screen { tabs: BTreeMap::new(), tab_history: Vec::with_capacity(32), mode_info, - session_state, draw_pane_frames, } } @@ -188,6 +188,14 @@ impl Screen { } } + fn move_clients(&mut self, source_index: usize, destination_index: usize) { + let connected_clients_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); + } /// A helper function to switch to a new tab at specified position. fn switch_active_tab(&mut self, new_tab_pos: usize) { if let Some(new_tab) = self.tabs.values().find(|t| t.position == new_tab_pos) { @@ -199,6 +207,7 @@ impl Screen { } 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(); @@ -214,6 +223,8 @@ impl Screen { self.tab_history.retain(|&e| e != Some(new_tab_pos)); self.tab_history.push(old_active_index); + self.move_clients(current_tab_index, new_tab_index); + self.update_tabs(); self.render(); } @@ -295,7 +306,12 @@ impl Screen { pub fn render(&mut self) { if let Some(active_tab) = self.get_active_tab_mut() { if active_tab.get_active_pane().is_some() { - active_tab.render(); + if let Some(output) = active_tab.render() { + self.bus + .senders + .send_to_server(ServerInstruction::Render(Some(output))) + .unwrap(); + } } else { self.close_tab(); } @@ -341,7 +357,7 @@ impl Screen { /// Creates a new [`Tab`] in this [`Screen`], applying the specified [`Layout`] /// and switching to it. - pub fn apply_layout(&mut self, layout: Layout, new_pids: Vec) { + pub fn new_tab(&mut self, layout: Layout, new_pids: Vec, client_id: ClientId) { let tab_index = self.get_new_tab_index(); let position = self.tabs.len(); let mut tab = Tab::new( @@ -354,19 +370,30 @@ impl Screen { self.max_panes, self.mode_info.clone(), self.colors, - self.session_state.clone(), self.draw_pane_frames, + client_id, ); tab.apply_layout(layout, new_pids, tab_index); if let Some(active_tab) = self.get_active_tab_mut() { active_tab.visible(false); active_tab.is_active = false; + let connected_clients = active_tab.drain_connected_clients(); + tab.add_multiple_clients(&connected_clients); } self.tab_history .push(self.active_tab_index.replace(tab_index)); tab.visible(true); self.tabs.insert(tab_index, tab); self.update_tabs(); + + self.render(); + } + + pub fn add_client(&mut self, client_id: ClientId) { + self.get_active_tab_mut().unwrap().add_client(client_id); + } + pub fn remove_client(&mut self, client_id: ClientId) { + self.get_active_tab_mut().unwrap().remove_client(client_id); } pub fn update_tabs(&self) { @@ -450,7 +477,6 @@ pub(crate) fn screen_thread_main( max_panes: Option, client_attributes: ClientAttributes, config_options: Box, - session_state: Arc>, ) { let capabilities = config_options.simplified_ui; let draw_pane_frames = !config_options.no_pane_frames; @@ -466,7 +492,6 @@ pub(crate) fn screen_thread_main( arrow_fonts: capabilities, }, ), - session_state, draw_pane_frames, ); loop { @@ -505,6 +530,8 @@ pub(crate) fn screen_thread_main( .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); screen.update_tabs(); + + screen.render(); } ScreenInstruction::HorizontalSplit(pid) => { screen.get_active_tab_mut().unwrap().horizontal_split(pid); @@ -514,6 +541,8 @@ pub(crate) fn screen_thread_main( .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); screen.update_tabs(); + + screen.render(); } ScreenInstruction::VerticalSplit(pid) => { screen.get_active_tab_mut().unwrap().vertical_split(pid); @@ -523,6 +552,8 @@ pub(crate) fn screen_thread_main( .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); screen.update_tabs(); + + screen.render(); } ScreenInstruction::WriteCharacter(bytes) => { let active_tab = screen.get_active_tab_mut().unwrap(); @@ -533,27 +564,43 @@ pub(crate) fn screen_thread_main( } ScreenInstruction::ResizeLeft => { screen.get_active_tab_mut().unwrap().resize_left(); + + screen.render(); } ScreenInstruction::ResizeRight => { screen.get_active_tab_mut().unwrap().resize_right(); + + screen.render(); } ScreenInstruction::ResizeDown => { screen.get_active_tab_mut().unwrap().resize_down(); + + screen.render(); } ScreenInstruction::ResizeUp => { screen.get_active_tab_mut().unwrap().resize_up(); + + screen.render(); } ScreenInstruction::SwitchFocus => { screen.get_active_tab_mut().unwrap().move_focus(); + + screen.render(); } ScreenInstruction::FocusNextPane => { screen.get_active_tab_mut().unwrap().focus_next_pane(); + + screen.render(); } ScreenInstruction::FocusPreviousPane => { screen.get_active_tab_mut().unwrap().focus_previous_pane(); + + screen.render(); } ScreenInstruction::MoveFocusLeft => { screen.get_active_tab_mut().unwrap().move_focus_left(); + + screen.render(); } ScreenInstruction::MoveFocusLeftOrPreviousTab => { screen.move_focus_left_or_previous_tab(); @@ -562,12 +609,18 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::MoveFocusDown => { screen.get_active_tab_mut().unwrap().move_focus_down(); + + screen.render(); } ScreenInstruction::MoveFocusRight => { screen.get_active_tab_mut().unwrap().move_focus_right(); + + screen.render(); } ScreenInstruction::MoveFocusRightOrNextTab => { screen.move_focus_right_or_next_tab(); @@ -576,61 +629,81 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::MoveFocusUp => { screen.get_active_tab_mut().unwrap().move_focus_up(); + + screen.render(); } ScreenInstruction::ScrollUp => { screen .get_active_tab_mut() .unwrap() .scroll_active_terminal_up(); + + screen.render(); } ScreenInstruction::ScrollUpAt(point) => { screen .get_active_tab_mut() .unwrap() .scroll_terminal_up(&point, 3); + + screen.render(); } ScreenInstruction::ScrollDown => { screen .get_active_tab_mut() .unwrap() .scroll_active_terminal_down(); + + screen.render(); } ScreenInstruction::ScrollDownAt(point) => { screen .get_active_tab_mut() .unwrap() .scroll_terminal_down(&point, 3); + + screen.render(); } ScreenInstruction::ScrollToBottom => { screen .get_active_tab_mut() .unwrap() .scroll_active_terminal_to_bottom(); + + screen.render(); } ScreenInstruction::PageScrollUp => { screen .get_active_tab_mut() .unwrap() .scroll_active_terminal_up_page(); + + screen.render(); } ScreenInstruction::PageScrollDown => { screen .get_active_tab_mut() .unwrap() .scroll_active_terminal_down_page(); + + screen.render(); } ScreenInstruction::ClearScroll => { screen .get_active_tab_mut() .unwrap() .clear_active_terminal_scroll(); + + screen.render(); } ScreenInstruction::CloseFocusedPane => { screen.get_active_tab_mut().unwrap().close_focused_pane(); - screen.update_tabs(); + screen.update_tabs(); // update_tabs eventually calls render through the plugin thread } ScreenInstruction::SetSelectable(id, selectable, tab_index) => { screen.get_indexed_tab_mut(tab_index).map_or_else( @@ -643,6 +716,8 @@ pub(crate) fn screen_thread_main( }, |tab| tab.set_pane_selectable(id, selectable), ); + + screen.render(); } ScreenInstruction::ClosePane(id) => { screen.get_active_tab_mut().unwrap().close_pane(id); @@ -654,6 +729,8 @@ pub(crate) fn screen_thread_main( .unwrap() .toggle_active_pane_fullscreen(); screen.update_tabs(); + + screen.render(); } ScreenInstruction::TogglePaneFrames => { screen.draw_pane_frames = !screen.draw_pane_frames; @@ -669,6 +746,8 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::SwitchTabPrev => { screen.switch_tab_prev(); @@ -677,6 +756,8 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::CloseTab => { screen.close_tab(); @@ -685,14 +766,18 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } - ScreenInstruction::ApplyLayout(layout, new_pane_pids) => { - screen.apply_layout(layout, new_pane_pids); + ScreenInstruction::NewTab(layout, new_pane_pids, client_id) => { + screen.new_tab(layout, new_pane_pids, client_id); screen .bus .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::GoToTab(tab_index) => { screen.go_to_tab(tab_index as usize); @@ -701,15 +786,23 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); } ScreenInstruction::UpdateTabName(c) => { screen.update_active_tab_name(c); + + screen.render(); } ScreenInstruction::TerminalResize(new_size) => { screen.resize_to_screen(new_size); + + screen.render(); } ScreenInstruction::ChangeMode(mode_info) => { screen.change_mode(mode_info); + + screen.render(); } ScreenInstruction::ToggleActiveSyncTab => { screen @@ -717,27 +810,37 @@ pub(crate) fn screen_thread_main( .unwrap() .toggle_sync_panes_is_active(); screen.update_tabs(); + + screen.render(); } ScreenInstruction::LeftClick(point) => { screen .get_active_tab_mut() .unwrap() .handle_left_click(&point); + + screen.render(); } ScreenInstruction::MouseRelease(point) => { screen .get_active_tab_mut() .unwrap() .handle_mouse_release(&point); + + screen.render(); } ScreenInstruction::MouseHold(point) => { screen .get_active_tab_mut() .unwrap() .handle_mouse_hold(&point); + + screen.render(); } ScreenInstruction::Copy => { screen.get_active_tab().unwrap().copy_selection(); + + screen.render(); } ScreenInstruction::Exit => { break; @@ -749,6 +852,18 @@ pub(crate) fn screen_thread_main( .senders .send_to_server(ServerInstruction::UnblockInputThread) .unwrap(); + + screen.render(); + } + ScreenInstruction::AddClient(client_id) => { + screen.add_client(client_id); + + screen.render(); + } + ScreenInstruction::RemoveClient(client_id) => { + screen.remove_client(client_id); + + screen.render(); } } } diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index 4f1a49395..ec570c5e6 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -8,11 +8,11 @@ use crate::{ thread_bus::ThreadSenders, ui::boundaries::Boundaries, wasm_vm::PluginInstruction, - ServerInstruction, SessionState, + ClientId, ServerInstruction, }; use serde::{Deserialize, Serialize}; use std::os::unix::io::RawFd; -use std::sync::{mpsc::channel, Arc, RwLock}; +use std::sync::mpsc::channel; use std::time::Instant; use std::{ cmp::Reverse, @@ -40,7 +40,6 @@ const MIN_TERMINAL_WIDTH: usize = 5; const RESIZE_PERCENT: f64 = 5.0; const MAX_PENDING_VTE_EVENTS: usize = 7000; -const PENDING_EVENTS_SET_SIZE: usize = 200; type BorderAndPaneIds = (usize, Vec); @@ -97,6 +96,28 @@ fn pane_content_offset(position_and_size: &PaneGeom, viewport: &Viewport) -> (us (columns_offset, rows_offset) } +#[derive(Clone, Debug)] +pub struct Output { + pub client_render_instructions: HashMap, +} + +impl Output { + pub fn new(client_ids: &HashSet) -> Self { + let mut client_render_instructions = HashMap::new(); + for client_id in client_ids { + client_render_instructions.insert(*client_id, String::new()); + } + Output { + client_render_instructions, + } + } + 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(crate) struct Tab { pub index: usize, pub position: usize, @@ -112,10 +133,10 @@ pub(crate) struct Tab { pub senders: ThreadSenders, synchronize_is_active: bool, should_clear_display_before_rendering: bool, - session_state: Arc>, pub mode_info: ModeInfo, pub colors: Palette, pub is_active: bool, + connected_clients: HashSet, draw_pane_frames: bool, pending_vte_events: HashMap>, } @@ -268,8 +289,8 @@ impl Tab { max_panes: Option, mode_info: ModeInfo, colors: Palette, - session_state: Arc>, draw_pane_frames: bool, + client_id: ClientId, ) -> Self { let panes = BTreeMap::new(); @@ -279,6 +300,9 @@ impl Tab { name }; + let mut connected_clients = HashSet::new(); + connected_clients.insert(client_id); + Tab { index, position, @@ -296,10 +320,10 @@ impl Tab { should_clear_display_before_rendering: false, mode_info, colors, - session_state, draw_pane_frames, pending_vte_events: HashMap::new(), is_active: true, + connected_clients, } } @@ -397,7 +421,23 @@ impl Tab { // This is the end of the nasty viewport hack... // FIXME: Active / new / current terminal, should be pane self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); - self.render(); + } + pub fn add_client(&mut self, client_id: ClientId) { + self.connected_clients.insert(client_id); + // 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(); + } + pub fn add_multiple_clients(&mut self, client_ids: &[ClientId]) { + for client_id in client_ids { + self.connected_clients.insert(*client_id); + } + } + pub fn remove_client(&mut self, client_id: ClientId) { + self.connected_clients.remove(&client_id); + } + pub fn drain_connected_clients(&mut self) -> Vec { + self.connected_clients.drain().collect() } pub fn new_pane(&mut self, pid: PaneId) { self.close_down_to_max_terminals(); @@ -470,7 +510,6 @@ impl Tab { } } self.active_terminal = Some(pid); - self.render(); } pub fn horizontal_split(&mut self, pid: PaneId) { self.close_down_to_max_terminals(); @@ -500,7 +539,6 @@ impl Tab { self.panes.insert(pid, Box::new(new_terminal)); self.active_terminal = Some(pid); self.relayout_tab(Direction::Vertical); - self.render(); } } } @@ -529,7 +567,6 @@ impl Tab { } self.active_terminal = Some(pid); self.relayout_tab(Direction::Horizontal); - self.render(); } } pub fn get_active_pane(&self) -> Option<&dyn Pane> { @@ -555,10 +592,10 @@ impl Tab { if terminal_output.is_scrolled() { self.pending_vte_events.entry(pid).or_default().push(bytes); if let Some(evs) = self.pending_vte_events.get(&pid) { - // Reset scroll and play the pending events if the buufer size exceeds the limit + // Reset scroll - and process all pending events for this pane if evs.len() >= MAX_PENDING_VTE_EVENTS { terminal_output.clear_scroll(); - self.play_pending_vte_events(pid); + self.process_pending_vte_events(pid); } } return; @@ -566,25 +603,12 @@ impl Tab { } self.process_pty_bytes(pid, bytes); } - fn play_pending_vte_events(&mut self, pid: RawFd) { - if self.pending_vte_events.get(&pid).is_some() { - if let Some(terminal_output) = self.panes.get_mut(&PaneId::Terminal(pid)) { - if terminal_output.is_scrolled() { - return; - } + pub fn process_pending_vte_events(&mut self, pid: RawFd) { + if let Some(pending_vte_events) = self.pending_vte_events.get_mut(&pid) { + let vte_events: Vec = pending_vte_events.drain(..).collect(); + for vte_event in vte_events { + self.process_pty_bytes(pid, vte_event); } - let mut events = self.pending_vte_events.remove(&pid).unwrap(); - while events.len() >= PENDING_EVENTS_SET_SIZE { - events - .drain(..PENDING_EVENTS_SET_SIZE) - .for_each(|bytes| self.process_pty_bytes(pid, bytes)); - // Render at regular intervals - self.render(); - } - events - .drain(..) - .for_each(|bytes| self.process_pty_bytes(pid, bytes)); - self.render(); } } fn process_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) { @@ -699,7 +723,6 @@ impl Tab { } self.set_force_render(); self.resize_whole_tab(self.display_area); - self.render(); self.toggle_fullscreen_is_active(); } } @@ -772,26 +795,20 @@ impl Tab { } } } - pub fn render(&mut self) { - if self.active_terminal.is_none() - || self.session_state.read().unwrap().clients.is_empty() - || !self.is_active - { - // we might not have an active terminal if we closed the last pane - // in that case, we should not render as the app is exiting - // or if there are no attached clients to this session - return; + pub fn render(&mut self) -> Option { + if self.connected_clients.is_empty() || self.active_terminal.is_none() { + return None; } self.senders .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) .unwrap(); - let mut output = String::new(); + let mut output = Output::new(&self.connected_clients); let mut boundaries = Boundaries::new(self.viewport); let hide_cursor = "\u{1b}[?25l"; - output.push_str(hide_cursor); + output.push_str_to_all_clients(hide_cursor); if self.should_clear_display_before_rendering { let clear_display = "\u{1b}[2J"; - output.push_str(clear_display); + output.push_str_to_all_clients(clear_display); self.should_clear_display_before_rendering = false; } for (_kind, pane) in self.panes.iter_mut() { @@ -824,7 +841,7 @@ impl Tab { } if let Some(vte_output) = pane.render() { // FIXME: Use Termion for cursor and style clearing? - output.push_str(&format!( + output.push_str_to_all_clients(&format!( "\u{1b}[{};{}H\u{1b}[m{}", pane.y() + 1, pane.x() + 1, @@ -835,7 +852,7 @@ impl Tab { } if !self.draw_pane_frames { - output.push_str(&boundaries.vte_output()); + output.push_str_to_all_clients(&boundaries.vte_output()); } match self.get_active_terminal_cursor_position() { @@ -848,18 +865,15 @@ impl Tab { cursor_position_x + 1, change_cursor_shape ); // goto row/col - output.push_str(show_cursor); - output.push_str(goto_cursor_position); + output.push_str_to_all_clients(show_cursor); + output.push_str_to_all_clients(goto_cursor_position); } None => { let hide_cursor = "\u{1b}[?25l"; - output.push_str(hide_cursor); + output.push_str_to_all_clients(hide_cursor); } } - - self.senders - .send_to_server(ServerInstruction::Render(Some(output))) - .unwrap(); + Some(output) } fn get_panes(&self) -> impl Iterator)> { self.panes.iter() @@ -1757,7 +1771,6 @@ impl Tab { } } self.relayout_tab(Direction::Horizontal); - self.render(); } pub fn resize_right(&mut self) { // TODO: find out by how much we actually reduced and only reduce by that much @@ -1769,7 +1782,6 @@ impl Tab { } } self.relayout_tab(Direction::Horizontal); - self.render(); } pub fn resize_down(&mut self) { // TODO: find out by how much we actually reduced and only reduce by that much @@ -1781,7 +1793,6 @@ impl Tab { } } self.relayout_tab(Direction::Vertical); - self.render(); } pub fn resize_up(&mut self) { // TODO: find out by how much we actually reduced and only reduce by that much @@ -1793,7 +1804,6 @@ impl Tab { } } self.relayout_tab(Direction::Vertical); - self.render(); } pub fn move_focus(&mut self) { if !self.has_selectable_panes() { @@ -1814,7 +1824,6 @@ impl Tab { .copied(); self.active_terminal = active_terminal; - self.render(); } pub fn focus_next_pane(&mut self) { if !self.has_selectable_panes() { @@ -1843,7 +1852,6 @@ impl Tab { .map(|p| *p.0); self.active_terminal = active_terminal; - self.render(); } pub fn focus_previous_pane(&mut self) { if !self.has_selectable_panes() { @@ -1873,7 +1881,6 @@ impl Tab { Some(*panes.get(active_pane_position - 1).unwrap().0) }; self.active_terminal = active_terminal; - self.render(); } // returns a boolean that indicates whether the focus moved pub fn move_focus_left(&mut self) -> bool { @@ -1904,7 +1911,6 @@ impl Tab { next_active_pane.set_should_render(true); self.active_terminal = Some(p); - self.render(); return true; } None => Some(active.pid()), @@ -1950,7 +1956,6 @@ impl Tab { Some(active_terminal.unwrap().pid()) }; self.active_terminal = updated_active_terminal; - self.render(); } pub fn move_focus_up(&mut self) { if !self.has_selectable_panes() { @@ -1987,7 +1992,6 @@ impl Tab { Some(active_terminal.unwrap().pid()) }; self.active_terminal = updated_active_terminal; - self.render(); } // returns a boolean that indicates whether the focus moved pub fn move_focus_right(&mut self) -> bool { @@ -2018,7 +2022,6 @@ impl Tab { next_active_pane.set_should_render(true); self.active_terminal = Some(p); - self.render(); return true; } None => Some(active.pid()), @@ -2175,7 +2178,6 @@ impl Tab { self.active_terminal = self.next_active_pane(&self.get_pane_ids()); } } - self.render(); } pub fn close_pane(&mut self, id: PaneId) { if self.fullscreen_is_active { @@ -2256,7 +2258,6 @@ impl Tab { .get_mut(&PaneId::Terminal(active_terminal_id)) .unwrap(); active_terminal.scroll_up(1); - self.render(); } } pub fn scroll_active_terminal_down(&mut self) { @@ -2266,8 +2267,9 @@ impl Tab { .get_mut(&PaneId::Terminal(active_terminal_id)) .unwrap(); active_terminal.scroll_down(1); - self.play_pending_vte_events(active_terminal_id); - self.render(); + if !active_terminal.is_scrolled() { + self.process_pending_vte_events(active_terminal_id); + } } } pub fn scroll_active_terminal_up_page(&mut self) { @@ -2279,7 +2281,6 @@ impl Tab { // prevent overflow when row == 0 let scroll_columns = active_terminal.rows().max(1) - 1; active_terminal.scroll_up(scroll_columns); - self.render(); } } pub fn scroll_active_terminal_down_page(&mut self) { @@ -2291,8 +2292,9 @@ impl Tab { // prevent overflow when row == 0 let scroll_columns = active_terminal.rows().max(1) - 1; active_terminal.scroll_down(scroll_columns); - self.play_pending_vte_events(active_terminal_id); - self.render(); + if !active_terminal.is_scrolled() { + self.process_pending_vte_events(active_terminal_id); + } } } pub fn scroll_active_terminal_to_bottom(&mut self) { @@ -2302,8 +2304,9 @@ impl Tab { .get_mut(&PaneId::Terminal(active_terminal_id)) .unwrap(); active_terminal.clear_scroll(); - self.play_pending_vte_events(active_terminal_id); - self.render(); + if !active_terminal.is_scrolled() { + self.process_pending_vte_events(active_terminal_id); + } } } pub fn clear_active_terminal_scroll(&mut self) { @@ -2313,22 +2316,24 @@ impl Tab { .get_mut(&PaneId::Terminal(active_terminal_id)) .unwrap(); active_terminal.clear_scroll(); - self.play_pending_vte_events(active_terminal_id); + if !active_terminal.is_scrolled() { + self.process_pending_vte_events(active_terminal_id); + } } } pub fn scroll_terminal_up(&mut self, point: &Position, lines: usize) { if let Some(pane) = self.get_pane_at(point) { pane.scroll_up(lines); - self.render(); } } pub fn scroll_terminal_down(&mut self, point: &Position, lines: usize) { if let Some(pane) = self.get_pane_at(point) { pane.scroll_down(lines); - if let PaneId::Terminal(id) = pane.pid() { - self.play_pending_vte_events(id); + if !pane.is_scrolled() { + if let PaneId::Terminal(pid) = pane.pid() { + self.process_pending_vte_events(pid); + } } - self.render(); } } fn get_pane_at(&mut self, point: &Position) -> Option<&mut Box> { @@ -2353,13 +2358,11 @@ impl Tab { if let Some(pane) = self.get_pane_at(position) { let relative_position = pane.relative_position(position); pane.start_selection(&relative_position); - self.render(); }; } fn focus_pane_at(&mut self, point: &Position) { if let Some(clicked_pane) = self.get_pane_id_at(point) { self.active_terminal = Some(clicked_pane); - self.render(); } } pub fn handle_mouse_release(&mut self, position: &Position) { @@ -2372,7 +2375,6 @@ impl Tab { active_pane.end_selection(None); selected_text = active_pane.get_selected_text(); active_pane.reset_selection(); - self.render(); } } } else if let Some(pane) = self.get_pane_at(position) { @@ -2380,7 +2382,6 @@ impl Tab { pane.end_selection(Some(&relative_position)); selected_text = pane.get_selected_text(); pane.reset_selection(); - self.render(); } if let Some(selected_text) = selected_text { @@ -2394,7 +2395,6 @@ impl Tab { active_pane.update_selection(&relative_position); } } - self.render(); } pub fn copy_selection(&self) { @@ -2408,7 +2408,13 @@ impl Tab { } fn write_selection_to_clipboard(&self, selection: &str) { - let output = format!("\u{1b}]52;c;{}\u{1b}\\", base64::encode(selection)); + let mut output = Output::new(&self.connected_clients); + output.push_str_to_all_clients(&format!( + "\u{1b}]52;c;{}\u{1b}\\", + base64::encode(selection) + )); + + // TODO: ideally we should be sending the Render instruction from the screen self.senders .send_to_server(ServerInstruction::Render(Some(output))) .unwrap(); diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 186bdf7f4..d1805b8af 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -3,11 +3,10 @@ use crate::zellij_tile::data::{ModeInfo, Palette}; use crate::{ os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, thread_bus::Bus, - ClientId, SessionState, + ClientId, }; use std::convert::TryInto; use std::path::PathBuf; -use std::sync::{Arc, RwLock}; use zellij_utils::input::command::TerminalAction; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::ipc::IpcReceiverWithContext; @@ -27,44 +26,44 @@ use zellij_utils::{ struct FakeInputOutput {} impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) { + fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } - fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { + fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { unimplemented!() } - fn async_file_reader(&self, fd: RawFd) -> Box { + fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result { + fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result { unimplemented!() } - fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> { + fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { unimplemented!() } - fn kill(&self, pid: Pid) -> Result<(), nix::Error> { + fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } - fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> { + fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } fn box_clone(&self) -> Box { Box::new((*self).clone()) } - fn send_to_client(&self, client_id: ClientId, msg: ServerToClientMsg) { + fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) { unimplemented!() } fn new_client( &mut self, - client_id: ClientId, - stream: LocalSocketStream, + _client_id: ClientId, + _stream: LocalSocketStream, ) -> IpcReceiverWithContext { unimplemented!() } - fn remove_client(&mut self, client_id: ClientId) { + fn remove_client(&mut self, _client_id: ClientId) { unimplemented!() } fn load_palette(&self) -> Palette { @@ -85,19 +84,23 @@ fn create_new_screen(size: Size) -> Screen { }; let max_panes = None; let mode_info = ModeInfo::default(); - let session_state = Arc::new(RwLock::new(SessionState::new())); + let draw_pane_frames = false; Screen::new( bus, &client_attributes, max_panes, mode_info, - session_state, - false, // draw_pane_frames + draw_pane_frames, ) } fn new_tab(screen: &mut Screen, pid: i32) { - screen.apply_layout(LayoutTemplate::default().try_into().unwrap(), vec![pid]); + let client_id = 1; + screen.new_tab( + LayoutTemplate::default().try_into().unwrap(), + vec![pid], + client_id, + ); } #[test] diff --git a/zellij-server/src/unit/tab_tests.rs b/zellij-server/src/unit/tab_tests.rs index 17bd98bee..8c26421c7 100644 --- a/zellij-server/src/unit/tab_tests.rs +++ b/zellij-server/src/unit/tab_tests.rs @@ -4,11 +4,10 @@ use crate::{ os_input_output::{AsyncReader, ChildId, Pid, ServerOsApi}, panes::PaneId, thread_bus::ThreadSenders, - ClientId, SessionState, + ClientId, }; use std::convert::TryInto; use std::path::PathBuf; -use std::sync::{Arc, RwLock}; use zellij_utils::input::layout::LayoutTemplate; use zellij_utils::ipc::IpcReceiverWithContext; use zellij_utils::pane_size::Size; @@ -27,44 +26,44 @@ use zellij_utils::{ struct FakeInputOutput {} impl ServerOsApi for FakeInputOutput { - fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) { + fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) { // noop } fn spawn_terminal(&self, _file_to_open: TerminalAction) -> (RawFd, ChildId) { unimplemented!() } - fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result { + fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result { unimplemented!() } - fn async_file_reader(&self, fd: RawFd) -> Box { + fn async_file_reader(&self, _fd: RawFd) -> Box { unimplemented!() } - fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result { + fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result { unimplemented!() } - fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> { + fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> { unimplemented!() } - fn kill(&self, pid: Pid) -> Result<(), nix::Error> { + fn kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } - fn force_kill(&self, pid: Pid) -> Result<(), nix::Error> { + fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> { unimplemented!() } fn box_clone(&self) -> Box { Box::new((*self).clone()) } - fn send_to_client(&self, client_id: ClientId, msg: ServerToClientMsg) { + fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) { unimplemented!() } fn new_client( &mut self, - client_id: ClientId, - stream: LocalSocketStream, + _client_id: ClientId, + _stream: LocalSocketStream, ) -> IpcReceiverWithContext { unimplemented!() } - fn remove_client(&mut self, client_id: ClientId) { + fn remove_client(&mut self, _client_id: ClientId) { unimplemented!() } fn load_palette(&self) -> Palette { @@ -84,7 +83,8 @@ fn create_new_tab(size: Size) -> Tab { let max_panes = None; let mode_info = ModeInfo::default(); let colors = Palette::default(); - let session_state = Arc::new(RwLock::new(SessionState::new())); + let draw_pane_frames = true; + let client_id = 1; let mut tab = Tab::new( index, position, @@ -95,8 +95,8 @@ fn create_new_tab(size: Size) -> Tab { max_panes, mode_info, colors, - session_state, - true, // draw pane frames + draw_pane_frames, + client_id, ); tab.apply_layout( LayoutTemplate::default().try_into().unwrap(), diff --git a/zellij-utils/src/errors.rs b/zellij-utils/src/errors.rs index cb4a4c264..348e6013a 100644 --- a/zellij-utils/src/errors.rs +++ b/zellij-utils/src/errors.rs @@ -244,7 +244,6 @@ pub enum ScreenContext { SetFixedHeight, SetFixedWidth, ClosePane, - ApplyLayout, NewTab, SwitchTabNext, SwitchTabPrev, @@ -258,6 +257,8 @@ pub enum ScreenContext { MouseHold, Copy, ToggleTab, + AddClient, + RemoveClient, } /// Stack call representations corresponding to the different types of [`PtyInstruction`]s.