From a99354a155348a233c446126f305b6d8b2fb7494 Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 20 Oct 2021 10:37:38 +0200 Subject: [PATCH] refactor(screen): more multiple-users groundwork (#788) * refactor(screen): support multiple mirrored clients * style(fmt): make rustfmt happy * style(clippy): make clippy happy * whitespace * github, y u no update CI?! * is this a cache issue? * is it the checkout cache? * no cache at all? * Debug * fix gototab * decoment * gototab none in wasm_vm * gototab none in wasm_vm * the fun never ends * style(fmt): make rustfmt happy --- zellij-server/src/lib.rs | 2 +- zellij-server/src/pty.rs | 111 ++-- zellij-server/src/route.rs | 126 ++-- zellij-server/src/screen.rs | 781 ++++++++++++++----------- zellij-server/src/tab.rs | 36 +- zellij-server/src/unit/screen_tests.rs | 124 ++-- zellij-server/src/wasm_vm.rs | 13 +- 7 files changed, 680 insertions(+), 513 deletions(-) diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 2c92ec49..570645e9 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -338,7 +338,7 @@ pub fn start_server(mut os_input: Box, socket_path: PathBuf) { let mode = mode_info.mode; session_data .senders - .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone())) + .send_to_screen(ScreenInstruction::ChangeMode(mode_info.clone(), client_id)) .unwrap(); session_data .senders diff --git a/zellij-server/src/pty.rs b/zellij-server/src/pty.rs index 9a1f1423..76ff7978 100644 --- a/zellij-server/src/pty.rs +++ b/zellij-server/src/pty.rs @@ -29,13 +29,19 @@ use zellij_utils::{ pub type VteBytes = Vec; +#[derive(Clone, Copy, Debug)] +pub enum ClientOrTabIndex { + ClientId(ClientId), + TabIndex(usize), +} + /// Instructions related to PTYs (pseudoterminals). #[derive(Clone, Debug)] pub(crate) enum PtyInstruction { - SpawnTerminal(Option), - SpawnTerminalVertically(Option), - SpawnTerminalHorizontally(Option), - UpdateActivePane(Option), + SpawnTerminal(Option, ClientOrTabIndex), + SpawnTerminalVertically(Option, ClientId), + SpawnTerminalHorizontally(Option, ClientId), + UpdateActivePane(Option, ClientId), NewTab(Option, Option, ClientId), ClosePane(PaneId), CloseTab(Vec), @@ -45,10 +51,10 @@ pub(crate) enum PtyInstruction { impl From<&PtyInstruction> for PtyContext { fn from(pty_instruction: &PtyInstruction) -> Self { match *pty_instruction { - PtyInstruction::SpawnTerminal(_) => PtyContext::SpawnTerminal, - PtyInstruction::SpawnTerminalVertically(_) => PtyContext::SpawnTerminalVertically, - PtyInstruction::SpawnTerminalHorizontally(_) => PtyContext::SpawnTerminalHorizontally, - PtyInstruction::UpdateActivePane(_) => PtyContext::UpdateActivePane, + PtyInstruction::SpawnTerminal(..) => PtyContext::SpawnTerminal, + PtyInstruction::SpawnTerminalVertically(..) => PtyContext::SpawnTerminalVertically, + PtyInstruction::SpawnTerminalHorizontally(..) => PtyContext::SpawnTerminalHorizontally, + PtyInstruction::UpdateActivePane(..) => PtyContext::UpdateActivePane, PtyInstruction::ClosePane(_) => PtyContext::ClosePane, PtyInstruction::CloseTab(_) => PtyContext::CloseTab, PtyInstruction::NewTab(..) => PtyContext::NewTab, @@ -58,7 +64,7 @@ impl From<&PtyInstruction> for PtyContext { } pub(crate) struct Pty { - pub active_pane: Option, + pub active_panes: HashMap, pub bus: Bus, pub id_to_child_pid: HashMap, debug_to_file: bool, @@ -72,29 +78,40 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel"); err_ctx.add_call(ContextType::Pty((&event).into())); match event { - PtyInstruction::SpawnTerminal(terminal_action) => { - let pid = pty.spawn_terminal(terminal_action); + PtyInstruction::SpawnTerminal(terminal_action, client_or_tab_index) => { + let pid = pty.spawn_terminal(terminal_action, client_or_tab_index); pty.bus .senders - .send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid))) + .send_to_screen(ScreenInstruction::NewPane( + PaneId::Terminal(pid), + client_or_tab_index, + )) .unwrap(); } - PtyInstruction::SpawnTerminalVertically(terminal_action) => { - let pid = pty.spawn_terminal(terminal_action); + PtyInstruction::SpawnTerminalVertically(terminal_action, client_id) => { + let pid = + pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)); pty.bus .senders - .send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid))) + .send_to_screen(ScreenInstruction::VerticalSplit( + PaneId::Terminal(pid), + client_id, + )) .unwrap(); } - PtyInstruction::SpawnTerminalHorizontally(terminal_action) => { - let pid = pty.spawn_terminal(terminal_action); + PtyInstruction::SpawnTerminalHorizontally(terminal_action, client_id) => { + let pid = + pty.spawn_terminal(terminal_action, ClientOrTabIndex::ClientId(client_id)); pty.bus .senders - .send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid))) + .send_to_screen(ScreenInstruction::HorizontalSplit( + PaneId::Terminal(pid), + client_id, + )) .unwrap(); } - PtyInstruction::UpdateActivePane(pane_id) => { - pty.set_active_pane(pane_id); + PtyInstruction::UpdateActivePane(pane_id, client_id) => { + pty.set_active_pane(pane_id, client_id); } PtyInstruction::NewTab(terminal_action, tab_layout, client_id) => { let tab_name = tab_layout.as_ref().and_then(|layout| { @@ -115,11 +132,14 @@ pub(crate) fn pty_thread_main(mut pty: Pty, layout: LayoutFromYaml) { // clear current name at first pty.bus .senders - .send_to_screen(ScreenInstruction::UpdateTabName(vec![0])) + .send_to_screen(ScreenInstruction::UpdateTabName(vec![0], client_id)) .unwrap(); pty.bus .senders - .send_to_screen(ScreenInstruction::UpdateTabName(tab_name.into_bytes())) + .send_to_screen(ScreenInstruction::UpdateTabName( + tab_name.into_bytes(), + client_id, + )) .unwrap(); } } @@ -229,11 +249,14 @@ fn stream_terminal_bytes( } async_send_to_screen(senders.clone(), ScreenInstruction::Render).await; - // this is a little hacky, and is because the tests end the file as soon as - // we read everything, rather than hanging until there is new data - // a better solution would be to fix the test fakes, but this will do for now - async_send_to_screen(senders, ScreenInstruction::ClosePane(PaneId::Terminal(pid))) - .await; + // we send ClosePane here so that the screen knows to close this tab if the process + // inside the terminal exited on its own (eg. the user typed "exit" inside a + // bash shell) + async_send_to_screen( + senders, + ScreenInstruction::ClosePane(PaneId::Terminal(pid), None), + ) + .await; } }) } @@ -241,29 +264,40 @@ fn stream_terminal_bytes( impl Pty { pub fn new(bus: Bus, debug_to_file: bool) -> Self { Pty { - active_pane: None, + active_panes: HashMap::new(), bus, id_to_child_pid: HashMap::new(), debug_to_file, task_handles: HashMap::new(), } } - pub fn get_default_terminal(&self) -> TerminalAction { + pub fn get_default_terminal(&self, client_id: Option) -> TerminalAction { TerminalAction::RunCommand(RunCommand { args: vec![], command: PathBuf::from(env::var("SHELL").expect("Could not find the SHELL variable")), - cwd: self - .active_pane + cwd: client_id + .and_then(|client_id| self.active_panes.get(&client_id)) .and_then(|pane| match pane { PaneId::Plugin(..) => None, - PaneId::Terminal(id) => self.id_to_child_pid.get(&id).and_then(|id| id.shell), + PaneId::Terminal(id) => self.id_to_child_pid.get(id).and_then(|id| id.shell), }) .and_then(|id| self.bus.os_input.as_ref().map(|input| input.get_cwd(id))) .flatten(), }) } - pub fn spawn_terminal(&mut self, terminal_action: Option) -> RawFd { - let terminal_action = terminal_action.unwrap_or_else(|| self.get_default_terminal()); + pub fn spawn_terminal( + &mut self, + terminal_action: Option, + client_or_tab_index: ClientOrTabIndex, + ) -> RawFd { + let terminal_action = match client_or_tab_index { + ClientOrTabIndex::ClientId(client_id) => { + terminal_action.unwrap_or_else(|| self.get_default_terminal(Some(client_id))) + } + ClientOrTabIndex::TabIndex(_) => { + terminal_action.unwrap_or_else(|| self.get_default_terminal(None)) + } + }; let (pid_primary, child_id): (RawFd, ChildId) = self .bus .os_input @@ -286,7 +320,8 @@ impl Pty { default_shell: Option, client_id: ClientId, ) { - let default_shell = default_shell.unwrap_or_else(|| self.get_default_terminal()); + let default_shell = + default_shell.unwrap_or_else(|| self.get_default_terminal(Some(client_id))); let extracted_run_instructions = layout.extract_run_instructions(); let mut new_pane_pids = vec![]; for run_instruction in extracted_run_instructions { @@ -368,8 +403,10 @@ impl Pty { self.close_pane(id); }); } - pub fn set_active_pane(&mut self, pane_id: Option) { - self.active_pane = pane_id; + pub fn set_active_pane(&mut self, pane_id: Option, client_id: ClientId) { + if let Some(pane_id) = pane_id { + self.active_panes.insert(client_id, pane_id); + } } } diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 4206cf78..bfdf18ca 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -3,8 +3,11 @@ use std::sync::{Arc, RwLock}; use zellij_utils::zellij_tile::data::Event; use crate::{ - os_input_output::ServerOsApi, pty::PtyInstruction, screen::ScreenInstruction, - wasm_vm::PluginInstruction, ServerInstruction, SessionMetaData, SessionState, + os_input_output::ServerOsApi, + pty::{ClientOrTabIndex, PtyInstruction}, + screen::ScreenInstruction, + wasm_vm::PluginInstruction, + ServerInstruction, SessionMetaData, SessionState, }; use zellij_utils::{ channels::SenderWithContext, @@ -34,17 +37,17 @@ fn route_action( Action::ToggleTab => { session .senders - .send_to_screen(ScreenInstruction::ToggleTab) + .send_to_screen(ScreenInstruction::ToggleTab(client_id)) .unwrap(); } Action::Write(val) => { session .senders - .send_to_screen(ScreenInstruction::ClearScroll) + .send_to_screen(ScreenInstruction::ClearScroll(client_id)) .unwrap(); session .senders - .send_to_screen(ScreenInstruction::WriteCharacter(val)) + .send_to_screen(ScreenInstruction::WriteCharacter(val, client_id)) .unwrap(); } Action::SwitchToMode(mode) => { @@ -61,11 +64,10 @@ fn route_action( .unwrap(); session .senders - .send_to_screen(ScreenInstruction::ChangeMode(get_mode_info( - mode, - palette, - session.capabilities, - ))) + .send_to_screen(ScreenInstruction::ChangeMode( + get_mode_info(mode, palette, session.capabilities), + client_id, + )) .unwrap(); session .senders @@ -74,103 +76,103 @@ fn route_action( } Action::Resize(direction) => { let screen_instr = match direction { - Direction::Left => ScreenInstruction::ResizeLeft, - Direction::Right => ScreenInstruction::ResizeRight, - Direction::Up => ScreenInstruction::ResizeUp, - Direction::Down => ScreenInstruction::ResizeDown, + Direction::Left => ScreenInstruction::ResizeLeft(client_id), + Direction::Right => ScreenInstruction::ResizeRight(client_id), + Direction::Up => ScreenInstruction::ResizeUp(client_id), + Direction::Down => ScreenInstruction::ResizeDown(client_id), }; session.senders.send_to_screen(screen_instr).unwrap(); } Action::SwitchFocus => { session .senders - .send_to_screen(ScreenInstruction::SwitchFocus) + .send_to_screen(ScreenInstruction::SwitchFocus(client_id)) .unwrap(); } Action::FocusNextPane => { session .senders - .send_to_screen(ScreenInstruction::FocusNextPane) + .send_to_screen(ScreenInstruction::FocusNextPane(client_id)) .unwrap(); } Action::FocusPreviousPane => { session .senders - .send_to_screen(ScreenInstruction::FocusPreviousPane) + .send_to_screen(ScreenInstruction::FocusPreviousPane(client_id)) .unwrap(); } Action::MoveFocus(direction) => { let screen_instr = match direction { - Direction::Left => ScreenInstruction::MoveFocusLeft, - Direction::Right => ScreenInstruction::MoveFocusRight, - Direction::Up => ScreenInstruction::MoveFocusUp, - Direction::Down => ScreenInstruction::MoveFocusDown, + Direction::Left => ScreenInstruction::MoveFocusLeft(client_id), + Direction::Right => ScreenInstruction::MoveFocusRight(client_id), + Direction::Up => ScreenInstruction::MoveFocusUp(client_id), + Direction::Down => ScreenInstruction::MoveFocusDown(client_id), }; session.senders.send_to_screen(screen_instr).unwrap(); } Action::MoveFocusOrTab(direction) => { let screen_instr = match direction { - Direction::Left => ScreenInstruction::MoveFocusLeftOrPreviousTab, - Direction::Right => ScreenInstruction::MoveFocusRightOrNextTab, + Direction::Left => ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id), + Direction::Right => ScreenInstruction::MoveFocusRightOrNextTab(client_id), _ => unreachable!(), }; session.senders.send_to_screen(screen_instr).unwrap(); } Action::MovePane(direction) => { let screen_instr = match direction { - Direction::Left => ScreenInstruction::MovePaneLeft, - Direction::Right => ScreenInstruction::MovePaneRight, - Direction::Up => ScreenInstruction::MovePaneUp, - Direction::Down => ScreenInstruction::MovePaneDown, + Direction::Left => ScreenInstruction::MovePaneLeft(client_id), + Direction::Right => ScreenInstruction::MovePaneRight(client_id), + Direction::Up => ScreenInstruction::MovePaneUp(client_id), + Direction::Down => ScreenInstruction::MovePaneDown(client_id), }; session.senders.send_to_screen(screen_instr).unwrap(); } Action::ScrollUp => { session .senders - .send_to_screen(ScreenInstruction::ScrollUp) + .send_to_screen(ScreenInstruction::ScrollUp(client_id)) .unwrap(); } Action::ScrollUpAt(point) => { session .senders - .send_to_screen(ScreenInstruction::ScrollUpAt(point)) + .send_to_screen(ScreenInstruction::ScrollUpAt(point, client_id)) .unwrap(); } Action::ScrollDown => { session .senders - .send_to_screen(ScreenInstruction::ScrollDown) + .send_to_screen(ScreenInstruction::ScrollDown(client_id)) .unwrap(); } Action::ScrollDownAt(point) => { session .senders - .send_to_screen(ScreenInstruction::ScrollDownAt(point)) + .send_to_screen(ScreenInstruction::ScrollDownAt(point, client_id)) .unwrap(); } Action::ScrollToBottom => { session .senders - .send_to_screen(ScreenInstruction::ScrollToBottom) + .send_to_screen(ScreenInstruction::ScrollToBottom(client_id)) .unwrap(); } Action::PageScrollUp => { session .senders - .send_to_screen(ScreenInstruction::PageScrollUp) + .send_to_screen(ScreenInstruction::PageScrollUp(client_id)) .unwrap(); } Action::PageScrollDown => { session .senders - .send_to_screen(ScreenInstruction::PageScrollDown) + .send_to_screen(ScreenInstruction::PageScrollDown(client_id)) .unwrap(); } Action::ToggleFocusFullscreen => { session .senders - .send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen) + .send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen(client_id)) .unwrap(); } Action::TogglePaneFrames => { @@ -182,31 +184,43 @@ fn route_action( Action::NewPane(direction) => { let shell = session.default_shell.clone(); let pty_instr = match direction { - Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(shell), - Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(shell), - Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(shell), - Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(shell), + Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(shell, client_id), + Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(shell, client_id), + Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(shell, client_id), + Some(Direction::Down) => { + PtyInstruction::SpawnTerminalHorizontally(shell, client_id) + } // No direction specified - try to put it in the biggest available spot - None => PtyInstruction::SpawnTerminal(shell), + None => PtyInstruction::SpawnTerminal(shell, ClientOrTabIndex::ClientId(client_id)), }; session.senders.send_to_pty(pty_instr).unwrap(); } Action::Run(command) => { let run_cmd = Some(TerminalAction::RunCommand(command.clone().into())); let pty_instr = match command.direction { - Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(run_cmd), - Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(run_cmd), - Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(run_cmd), - Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(run_cmd), + Some(Direction::Left) => { + PtyInstruction::SpawnTerminalVertically(run_cmd, client_id) + } + Some(Direction::Right) => { + PtyInstruction::SpawnTerminalVertically(run_cmd, client_id) + } + Some(Direction::Up) => { + PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id) + } + Some(Direction::Down) => { + PtyInstruction::SpawnTerminalHorizontally(run_cmd, client_id) + } // No direction specified - try to put it in the biggest available spot - None => PtyInstruction::SpawnTerminal(run_cmd), + None => { + PtyInstruction::SpawnTerminal(run_cmd, ClientOrTabIndex::ClientId(client_id)) + } }; session.senders.send_to_pty(pty_instr).unwrap(); } Action::CloseFocus => { session .senders - .send_to_screen(ScreenInstruction::CloseFocusedPane) + .send_to_screen(ScreenInstruction::CloseFocusedPane(client_id)) .unwrap(); } Action::NewTab(tab_layout) => { @@ -219,37 +233,37 @@ fn route_action( Action::GoToNextTab => { session .senders - .send_to_screen(ScreenInstruction::SwitchTabNext) + .send_to_screen(ScreenInstruction::SwitchTabNext(client_id)) .unwrap(); } Action::GoToPreviousTab => { session .senders - .send_to_screen(ScreenInstruction::SwitchTabPrev) + .send_to_screen(ScreenInstruction::SwitchTabPrev(client_id)) .unwrap(); } Action::ToggleActiveSyncTab => { session .senders - .send_to_screen(ScreenInstruction::ToggleActiveSyncTab) + .send_to_screen(ScreenInstruction::ToggleActiveSyncTab(client_id)) .unwrap(); } Action::CloseTab => { session .senders - .send_to_screen(ScreenInstruction::CloseTab) + .send_to_screen(ScreenInstruction::CloseTab(client_id)) .unwrap(); } Action::GoToTab(i) => { session .senders - .send_to_screen(ScreenInstruction::GoToTab(i)) + .send_to_screen(ScreenInstruction::GoToTab(i, Some(client_id))) .unwrap(); } Action::TabNameInput(c) => { session .senders - .send_to_screen(ScreenInstruction::UpdateTabName(c)) + .send_to_screen(ScreenInstruction::UpdateTabName(c, client_id)) .unwrap(); } Action::Quit => { @@ -267,25 +281,25 @@ fn route_action( Action::LeftClick(point) => { session .senders - .send_to_screen(ScreenInstruction::LeftClick(point)) + .send_to_screen(ScreenInstruction::LeftClick(point, client_id)) .unwrap(); } Action::MouseRelease(point) => { session .senders - .send_to_screen(ScreenInstruction::MouseRelease(point)) + .send_to_screen(ScreenInstruction::MouseRelease(point, client_id)) .unwrap(); } Action::MouseHold(point) => { session .senders - .send_to_screen(ScreenInstruction::MouseHold(point)) + .send_to_screen(ScreenInstruction::MouseHold(point, client_id)) .unwrap(); } Action::Copy => { session .senders - .send_to_screen(ScreenInstruction::Copy) + .send_to_screen(ScreenInstruction::Copy(client_id)) .unwrap(); } Action::NoOp => {} diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 580626f2..28ef40a2 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -1,6 +1,6 @@ //! Things related to [`Screen`]s. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet}; use std::os::unix::io::RawFd; use std::str; @@ -9,8 +9,8 @@ use zellij_utils::{input::layout::Layout, position::Position, zellij_tile}; use crate::{ panes::PaneId, - pty::{PtyInstruction, VteBytes}, - tab::Tab, + pty::{ClientOrTabIndex, PtyInstruction, VteBytes}, + tab::{Output, Tab}, thread_bus::Bus, wasm_vm::PluginInstruction, ClientId, ServerInstruction, @@ -27,55 +27,55 @@ use zellij_utils::{ pub(crate) enum ScreenInstruction { PtyBytes(RawFd, VteBytes), Render, - NewPane(PaneId), - HorizontalSplit(PaneId), - VerticalSplit(PaneId), - WriteCharacter(Vec), - ResizeLeft, - ResizeRight, - ResizeDown, - ResizeUp, - SwitchFocus, - FocusNextPane, - FocusPreviousPane, - MoveFocusLeft, - MoveFocusLeftOrPreviousTab, - MoveFocusDown, - MoveFocusUp, - MoveFocusRight, - MoveFocusRightOrNextTab, - MovePaneUp, - MovePaneDown, - MovePaneRight, - MovePaneLeft, + NewPane(PaneId, ClientOrTabIndex), + HorizontalSplit(PaneId, ClientId), + VerticalSplit(PaneId, ClientId), + WriteCharacter(Vec, ClientId), + ResizeLeft(ClientId), + ResizeRight(ClientId), + ResizeDown(ClientId), + ResizeUp(ClientId), + SwitchFocus(ClientId), + FocusNextPane(ClientId), + FocusPreviousPane(ClientId), + MoveFocusLeft(ClientId), + MoveFocusLeftOrPreviousTab(ClientId), + MoveFocusDown(ClientId), + MoveFocusUp(ClientId), + MoveFocusRight(ClientId), + MoveFocusRightOrNextTab(ClientId), + MovePaneUp(ClientId), + MovePaneDown(ClientId), + MovePaneRight(ClientId), + MovePaneLeft(ClientId), Exit, - ScrollUp, - ScrollUpAt(Position), - ScrollDown, - ScrollDownAt(Position), - ScrollToBottom, - PageScrollUp, - PageScrollDown, - ClearScroll, - CloseFocusedPane, - ToggleActiveTerminalFullscreen, + ScrollUp(ClientId), + ScrollUpAt(Position, ClientId), + ScrollDown(ClientId), + ScrollDownAt(Position, ClientId), + ScrollToBottom(ClientId), + PageScrollUp(ClientId), + PageScrollDown(ClientId), + ClearScroll(ClientId), + CloseFocusedPane(ClientId), + ToggleActiveTerminalFullscreen(ClientId), TogglePaneFrames, SetSelectable(PaneId, bool, usize), - ClosePane(PaneId), + ClosePane(PaneId, Option), NewTab(Layout, Vec, ClientId), - SwitchTabNext, - SwitchTabPrev, - ToggleActiveSyncTab, - CloseTab, - GoToTab(u32), - ToggleTab, - UpdateTabName(Vec), + SwitchTabNext(ClientId), + SwitchTabPrev(ClientId), + ToggleActiveSyncTab(ClientId), + CloseTab(ClientId), + GoToTab(u32, Option), // this Option is a hacky workaround, please do not copy thie behaviour + ToggleTab(ClientId), + UpdateTabName(Vec, ClientId), TerminalResize(Size), - ChangeMode(ModeInfo), - LeftClick(Position), - MouseRelease(Position), - MouseHold(Position), - Copy, + ChangeMode(ModeInfo, ClientId), + LeftClick(Position, ClientId), + MouseRelease(Position, ClientId), + MouseHold(Position, ClientId), + Copy(ClientId), AddClient(ClientId), RemoveClient(ClientId), } @@ -85,59 +85,61 @@ impl From<&ScreenInstruction> for ScreenContext { match *screen_instruction { ScreenInstruction::PtyBytes(..) => ScreenContext::HandlePtyBytes, ScreenInstruction::Render => ScreenContext::Render, - ScreenInstruction::NewPane(_) => ScreenContext::NewPane, - ScreenInstruction::HorizontalSplit(_) => ScreenContext::HorizontalSplit, - ScreenInstruction::VerticalSplit(_) => ScreenContext::VerticalSplit, - ScreenInstruction::WriteCharacter(_) => ScreenContext::WriteCharacter, - ScreenInstruction::ResizeLeft => ScreenContext::ResizeLeft, - ScreenInstruction::ResizeRight => ScreenContext::ResizeRight, - ScreenInstruction::ResizeDown => ScreenContext::ResizeDown, - ScreenInstruction::ResizeUp => ScreenContext::ResizeUp, - ScreenInstruction::SwitchFocus => ScreenContext::SwitchFocus, - ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, - ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, - ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, - ScreenInstruction::MoveFocusLeftOrPreviousTab => { + ScreenInstruction::NewPane(..) => ScreenContext::NewPane, + ScreenInstruction::HorizontalSplit(..) => ScreenContext::HorizontalSplit, + ScreenInstruction::VerticalSplit(..) => ScreenContext::VerticalSplit, + ScreenInstruction::WriteCharacter(..) => ScreenContext::WriteCharacter, + ScreenInstruction::ResizeLeft(..) => ScreenContext::ResizeLeft, + ScreenInstruction::ResizeRight(..) => ScreenContext::ResizeRight, + ScreenInstruction::ResizeDown(..) => ScreenContext::ResizeDown, + ScreenInstruction::ResizeUp(..) => ScreenContext::ResizeUp, + ScreenInstruction::SwitchFocus(..) => ScreenContext::SwitchFocus, + ScreenInstruction::FocusNextPane(..) => ScreenContext::FocusNextPane, + ScreenInstruction::FocusPreviousPane(..) => ScreenContext::FocusPreviousPane, + ScreenInstruction::MoveFocusLeft(..) => ScreenContext::MoveFocusLeft, + ScreenInstruction::MoveFocusLeftOrPreviousTab(..) => { ScreenContext::MoveFocusLeftOrPreviousTab } - ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, - ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, - ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, - ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, - ScreenInstruction::MovePaneDown => ScreenContext::MovePaneDown, - ScreenInstruction::MovePaneUp => ScreenContext::MovePaneUp, - ScreenInstruction::MovePaneRight => ScreenContext::MovePaneRight, - ScreenInstruction::MovePaneLeft => ScreenContext::MovePaneLeft, + ScreenInstruction::MoveFocusDown(..) => ScreenContext::MoveFocusDown, + ScreenInstruction::MoveFocusUp(..) => ScreenContext::MoveFocusUp, + ScreenInstruction::MoveFocusRight(..) => ScreenContext::MoveFocusRight, + ScreenInstruction::MoveFocusRightOrNextTab(..) => { + ScreenContext::MoveFocusRightOrNextTab + } + ScreenInstruction::MovePaneDown(..) => ScreenContext::MovePaneDown, + ScreenInstruction::MovePaneUp(..) => ScreenContext::MovePaneUp, + ScreenInstruction::MovePaneRight(..) => ScreenContext::MovePaneRight, + ScreenInstruction::MovePaneLeft(..) => ScreenContext::MovePaneLeft, ScreenInstruction::Exit => ScreenContext::Exit, - ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, - ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, - ScreenInstruction::ScrollToBottom => ScreenContext::ScrollToBottom, - ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp, - ScreenInstruction::PageScrollDown => ScreenContext::PageScrollDown, - ScreenInstruction::ClearScroll => ScreenContext::ClearScroll, - ScreenInstruction::CloseFocusedPane => ScreenContext::CloseFocusedPane, - ScreenInstruction::ToggleActiveTerminalFullscreen => { + ScreenInstruction::ScrollUp(..) => ScreenContext::ScrollUp, + ScreenInstruction::ScrollDown(..) => ScreenContext::ScrollDown, + ScreenInstruction::ScrollToBottom(..) => ScreenContext::ScrollToBottom, + ScreenInstruction::PageScrollUp(..) => ScreenContext::PageScrollUp, + ScreenInstruction::PageScrollDown(..) => ScreenContext::PageScrollDown, + ScreenInstruction::ClearScroll(..) => ScreenContext::ClearScroll, + ScreenInstruction::CloseFocusedPane(..) => ScreenContext::CloseFocusedPane, + ScreenInstruction::ToggleActiveTerminalFullscreen(..) => { ScreenContext::ToggleActiveTerminalFullscreen } ScreenInstruction::TogglePaneFrames => ScreenContext::TogglePaneFrames, ScreenInstruction::SetSelectable(..) => ScreenContext::SetSelectable, - ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane, + ScreenInstruction::ClosePane(..) => ScreenContext::ClosePane, ScreenInstruction::NewTab(..) => ScreenContext::NewTab, - ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext, - ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev, - ScreenInstruction::CloseTab => ScreenContext::CloseTab, - ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab, - ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, - ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, - ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, - ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, - ScreenInstruction::ScrollUpAt(_) => ScreenContext::ScrollUpAt, - ScreenInstruction::ScrollDownAt(_) => ScreenContext::ScrollDownAt, - ScreenInstruction::LeftClick(_) => ScreenContext::LeftClick, - ScreenInstruction::MouseRelease(_) => ScreenContext::MouseRelease, - ScreenInstruction::MouseHold(_) => ScreenContext::MouseHold, - ScreenInstruction::Copy => ScreenContext::Copy, - ScreenInstruction::ToggleTab => ScreenContext::ToggleTab, + ScreenInstruction::SwitchTabNext(..) => ScreenContext::SwitchTabNext, + ScreenInstruction::SwitchTabPrev(..) => ScreenContext::SwitchTabPrev, + ScreenInstruction::CloseTab(..) => ScreenContext::CloseTab, + ScreenInstruction::GoToTab(..) => ScreenContext::GoToTab, + ScreenInstruction::UpdateTabName(..) => ScreenContext::UpdateTabName, + ScreenInstruction::TerminalResize(..) => ScreenContext::TerminalResize, + ScreenInstruction::ChangeMode(..) => ScreenContext::ChangeMode, + ScreenInstruction::ToggleActiveSyncTab(..) => ScreenContext::ToggleActiveSyncTab, + ScreenInstruction::ScrollUpAt(..) => ScreenContext::ScrollUpAt, + ScreenInstruction::ScrollDownAt(..) => ScreenContext::ScrollDownAt, + ScreenInstruction::LeftClick(..) => ScreenContext::LeftClick, + ScreenInstruction::MouseRelease(..) => ScreenContext::MouseRelease, + ScreenInstruction::MouseHold(..) => ScreenContext::MouseHold, + ScreenInstruction::Copy(..) => ScreenContext::Copy, + ScreenInstruction::ToggleTab(..) => ScreenContext::ToggleTab, ScreenInstruction::AddClient(..) => ScreenContext::AddClient, ScreenInstruction::RemoveClient(..) => ScreenContext::RemoveClient, } @@ -155,9 +157,9 @@ pub(crate) struct Screen { tabs: BTreeMap, /// The full size of this [`Screen`]. size: Size, - /// The index of this [`Screen`]'s active [`Tab`]. - active_tab_index: Option, - tab_history: Vec>, + /// The indices of this [`Screen`]'s active [`Tab`]s. + active_tab_indices: BTreeMap, + tab_history: BTreeMap>, mode_info: ModeInfo, colors: Palette, draw_pane_frames: bool, @@ -177,9 +179,9 @@ impl Screen { max_panes, size: client_attributes.size, colors: client_attributes.palette, - active_tab_index: None, + active_tab_indices: BTreeMap::new(), tabs: BTreeMap::new(), - tab_history: Vec::with_capacity(32), + tab_history: BTreeMap::new(), mode_info, draw_pane_frames, } @@ -196,6 +198,24 @@ impl Screen { } } + fn move_clients_from_closed_tab(&mut self, previous_tab_index: usize) { + let client_ids_in_closed_tab: Vec = 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 { + 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); + } + } 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(); @@ -205,9 +225,9 @@ impl Screen { 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) { + fn switch_active_tab(&mut self, new_tab_pos: usize, client_id: ClientId) { if let Some(new_tab) = self.tabs.values().find(|t| t.position == new_tab_pos) { - let current_tab = self.get_active_tab().unwrap(); + let current_tab = self.get_active_tab(client_id).unwrap(); // If new active tab is same as the current one, do nothing. if current_tab.position == new_tab_pos { @@ -221,15 +241,15 @@ impl Screen { new_tab.set_force_render(); new_tab.visible(true); - let old_active_index = self.active_tab_index.replace(new_tab_index); - if let Some(ref i) = old_active_index { - if let Some(t) = self.tabs.get_mut(i) { - t.is_active = false; - } + // 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 self.tab_history.iter_mut() { + 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); } - self.tabs.get_mut(&new_tab_index).unwrap().is_active = true; - 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); @@ -239,35 +259,32 @@ impl Screen { } /// Sets this [`Screen`]'s active [`Tab`] to the next tab. - pub fn switch_tab_next(&mut self) { - let active_tab_pos = self.get_active_tab().unwrap().position; + pub fn switch_tab_next(&mut self, client_id: ClientId) { + let active_tab_pos = self.get_active_tab(client_id).unwrap().position; let new_tab_pos = (active_tab_pos + 1) % self.tabs.len(); - self.switch_active_tab(new_tab_pos); + self.switch_active_tab(new_tab_pos, client_id); } /// Sets this [`Screen`]'s active [`Tab`] to the previous tab. - pub fn switch_tab_prev(&mut self) { - let active_tab_pos = self.get_active_tab().unwrap().position; + pub fn switch_tab_prev(&mut self, client_id: ClientId) { + let active_tab_pos = self.get_active_tab(client_id).unwrap().position; let new_tab_pos = if active_tab_pos == 0 { self.tabs.len() - 1 } else { active_tab_pos - 1 }; - self.switch_active_tab(new_tab_pos); + self.switch_active_tab(new_tab_pos, client_id); } - pub fn go_to_tab(&mut self, tab_index: usize) { - self.switch_active_tab(tab_index - 1); + pub fn go_to_tab(&mut self, tab_index: usize, client_id: ClientId) { + self.switch_active_tab(tab_index - 1, client_id); } - /// Closes this [`Screen`]'s active [`Tab`], exiting the application if it happens - /// to be the last tab. - pub fn close_tab(&mut self) { - let active_tab_index = self.active_tab_index.unwrap(); - let active_tab = self.tabs.remove(&active_tab_index).unwrap(); - let pane_ids = active_tab.get_pane_ids(); + fn close_tab_at_index(&mut self, tab_index: usize) { + let 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 // has already closed and this would result in an error @@ -276,23 +293,21 @@ impl Screen { .send_to_pty(PtyInstruction::CloseTab(pane_ids)) .unwrap(); if self.tabs.is_empty() { - self.active_tab_index = None; + self.active_tab_indices.clear(); self.bus .senders .send_to_server(ServerInstruction::Render(None)) .unwrap(); } else { - if let Some(tab) = self.get_active_tab() { - tab.visible(false); - } - self.active_tab_index = self.tab_history.pop().unwrap(); + self.move_clients_from_closed_tab(tab_index); + let visible_tab_indices: HashSet = + self.active_tab_indices.values().copied().collect(); for t in self.tabs.values_mut() { - if t.index == self.active_tab_index.unwrap() { + if visible_tab_indices.contains(&t.index) { t.set_force_render(); - t.is_active = true; t.visible(true); } - if t.position > active_tab.position { + if t.position > tab_to_close.position { t.position -= 1; } } @@ -301,29 +316,39 @@ impl Screen { } } + // Closes the client_id's focused tab + pub fn close_tab(&mut self, client_id: ClientId) { + let active_tab_index = *self.active_tab_indices.get(&client_id).unwrap(); + self.close_tab_at_index(active_tab_index); + } + pub fn resize_to_screen(&mut self, new_screen_size: Size) { self.size = new_screen_size; for (_, tab) in self.tabs.iter_mut() { tab.resize_whole_tab(new_screen_size); + tab.set_force_render(); } - let _ = self.get_active_tab_mut().map(|t| t.set_force_render()); self.render(); } /// Renders this [`Screen`], which amounts to rendering its active [`Tab`]. pub fn render(&mut self) { - if let Some(active_tab) = self.get_active_tab_mut() { - if active_tab.get_active_pane().is_some() { - if let Some(output) = active_tab.render() { - self.bus - .senders - .send_to_server(ServerInstruction::Render(Some(output))) - .unwrap(); - } + let mut output = Output::default(); + let mut tabs_to_close = vec![]; + for (tab_index, tab) in self.tabs.iter_mut() { + if tab.get_active_pane().is_some() { + tab.render(&mut output); } else { - self.close_tab(); + tabs_to_close.push(*tab_index); } - }; + } + for tab_index in tabs_to_close { + self.close_tab_at_index(tab_index); + } + self.bus + .senders + .send_to_server(ServerInstruction::Render(Some(output))) + .unwrap(); } /// Returns a mutable reference to this [`Screen`]'s tabs. @@ -332,28 +357,26 @@ impl Screen { } /// Returns an immutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab(&self) -> Option<&Tab> { - match self.active_tab_index { - Some(tab) => self.tabs.get(&tab), + pub fn get_active_tab(&self, client_id: ClientId) -> Option<&Tab> { + match self.active_tab_indices.get(&client_id) { + Some(tab) => self.tabs.get(tab), None => None, } } /// Returns an immutable reference to this [`Screen`]'s previous active [`Tab`]. /// Consumes the last entry in tab history. - pub fn get_previous_tab(&mut self) -> Option<&Tab> { - let last = self.tab_history.pop(); - last?; - match last.unwrap() { + pub fn get_previous_tab(&mut self, client_id: ClientId) -> Option<&Tab> { + match self.tab_history.get_mut(&client_id).unwrap().pop() { Some(tab) => self.tabs.get(&tab), None => None, } } /// Returns a mutable reference to this [`Screen`]'s active [`Tab`]. - pub fn get_active_tab_mut(&mut self) -> Option<&mut Tab> { - match self.active_tab_index { - Some(tab) => self.get_tabs_mut().get_mut(&tab), + pub fn get_active_tab_mut(&mut self, client_id: ClientId) -> Option<&mut Tab> { + match self.active_tab_indices.get(&client_id) { + Some(tab) => self.tabs.get_mut(tab), None => None, } } @@ -382,50 +405,80 @@ impl Screen { client_id, ); tab.apply_layout(layout, new_pids, tab_index); - if let Some(active_tab) = self.get_active_tab_mut() { + if let Some(active_tab) = self.get_active_tab_mut(client_id) { 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)); + for (client_id, tab_history) in self.tab_history.iter_mut() { + 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); + } tab.visible(true); self.tabs.insert(tab_index, tab); + if !self.active_tab_indices.contains_key(&client_id) { + self.add_client(client_id); + } self.update_tabs(); self.render(); } pub fn add_client(&mut self, client_id: ClientId) { - self.get_active_tab_mut().unwrap().add_client(client_id); + let mut tab_index = 0; + let mut tab_history = vec![]; + if let Some((_first_client, first_active_tab_index)) = self.active_tab_indices.iter().next() + { + tab_index = *first_active_tab_index; + } + if let Some((_first_client, first_tab_history)) = self.tab_history.iter().next() { + tab_history = first_tab_history.clone(); + } + self.active_tab_indices.insert(client_id, tab_index); + self.tab_history.insert(client_id, tab_history); + self.tabs.get_mut(&tab_index).unwrap().add_client(client_id); } pub fn remove_client(&mut self, client_id: ClientId) { - self.get_active_tab_mut().unwrap().remove_client(client_id); + if let Some(client_tab) = self.get_active_tab_mut(client_id) { + client_tab.remove_client(client_id); + } + if self.active_tab_indices.contains_key(&client_id) { + self.active_tab_indices.remove(&client_id); + } + if self.tab_history.contains_key(&client_id) { + self.tab_history.remove(&client_id); + } } pub fn update_tabs(&self) { let mut tab_data = vec![]; - let active_tab_index = self.active_tab_index.unwrap(); - for tab in self.tabs.values() { - tab_data.push(TabInfo { - position: tab.position, - name: tab.name.clone(), - 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(), - }); + // 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 tab in self.tabs.values() { + tab_data.push(TabInfo { + position: tab.position, + name: tab.name.clone(), + active: *first_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(), + }); + } + self.bus + .senders + .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) + .unwrap(); } - self.bus - .senders - .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) - .unwrap(); } - pub fn update_active_tab_name(&mut self, buf: Vec) { + pub fn update_active_tab_name(&mut self, buf: Vec, client_id: ClientId) { let s = str::from_utf8(&buf).unwrap(); - let active_tab = self.get_active_tab_mut().unwrap(); + let active_tab = self.get_active_tab_mut(client_id).unwrap(); match s { "\0" => { active_tab.name = String::new(); @@ -440,11 +493,11 @@ impl Screen { } self.update_tabs(); } - pub fn change_mode(&mut self, mode_info: ModeInfo) { + pub fn change_mode(&mut self, mode_info: ModeInfo, client_id: ClientId) { if self.mode_info.mode == InputMode::Scroll && (mode_info.mode == InputMode::Normal || mode_info.mode == InputMode::Locked) { - self.get_active_tab_mut() + self.get_active_tab_mut(client_id) .unwrap() .clear_active_terminal_scroll(); } @@ -455,21 +508,29 @@ impl Screen { tab.mark_active_pane_for_rerender(); } } - pub fn move_focus_left_or_previous_tab(&mut self) { - if !self.get_active_tab_mut().unwrap().move_focus_left() { - self.switch_tab_prev(); + pub fn move_focus_left_or_previous_tab(&mut self, client_id: ClientId) { + if !self + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_left() + { + self.switch_tab_prev(client_id); } } - pub fn move_focus_right_or_next_tab(&mut self) { - if !self.get_active_tab_mut().unwrap().move_focus_right() { - self.switch_tab_next(); + pub fn move_focus_right_or_next_tab(&mut self, client_id: ClientId) { + if !self + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_right() + { + self.switch_tab_next(client_id); } } - pub fn toggle_tab(&mut self) { - let tab = self.get_previous_tab(); + pub fn toggle_tab(&mut self, client_id: ClientId) { + let tab = self.get_previous_tab(client_id); if let Some(t) = tab { let position = t.position; - self.go_to_tab(position + 1); + self.go_to_tab(position + 1, client_id); }; self.update_tabs(); @@ -510,28 +571,26 @@ pub(crate) fn screen_thread_main( err_ctx.add_call(ContextType::Screen((&event).into())); match event { ScreenInstruction::PtyBytes(pid, vte_bytes) => { - let active_tab = screen.get_active_tab_mut().unwrap(); - if active_tab.has_terminal_pid(pid) { - // it's most likely that this event is directed at the active tab - // look there first - active_tab.handle_pty_bytes(pid, vte_bytes); - } else { - // if this event wasn't directed at the active tab, start looking - // in other tabs - let all_tabs = screen.get_tabs_mut(); - for tab in all_tabs.values_mut() { - if tab.has_terminal_pid(pid) { - tab.handle_pty_bytes(pid, vte_bytes); - break; - } + let all_tabs = screen.get_tabs_mut(); + for tab in all_tabs.values_mut() { + if tab.has_terminal_pid(pid) { + tab.handle_pty_bytes(pid, vte_bytes); + break; } } } ScreenInstruction::Render => { screen.render(); } - ScreenInstruction::NewPane(pid) => { - screen.get_active_tab_mut().unwrap().new_pane(pid); + ScreenInstruction::NewPane(pid, client_or_tab_index) => { + match client_or_tab_index { + ClientOrTabIndex::ClientId(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().new_pane(pid); + } + ClientOrTabIndex::TabIndex(tab_index) => { + screen.tabs.get_mut(&tab_index).unwrap().new_pane(pid); + } + }; screen .bus .senders @@ -541,8 +600,11 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::HorizontalSplit(pid) => { - screen.get_active_tab_mut().unwrap().horizontal_split(pid); + ScreenInstruction::HorizontalSplit(pid, client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .horizontal_split(pid); screen .bus .senders @@ -552,8 +614,11 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::VerticalSplit(pid) => { - screen.get_active_tab_mut().unwrap().vertical_split(pid); + ScreenInstruction::VerticalSplit(pid, client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .vertical_split(pid); screen .bus .senders @@ -563,177 +628,207 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::WriteCharacter(bytes) => { - let active_tab = screen.get_active_tab_mut().unwrap(); + ScreenInstruction::WriteCharacter(bytes, client_id) => { + let active_tab = screen.get_active_tab_mut(client_id).unwrap(); match active_tab.is_sync_panes_active() { true => active_tab.write_to_terminals_on_current_tab(bytes), false => active_tab.write_to_active_terminal(bytes), } } - ScreenInstruction::ResizeLeft => { - screen.get_active_tab_mut().unwrap().resize_left(); + ScreenInstruction::ResizeLeft(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().resize_left(); screen.render(); } - ScreenInstruction::ResizeRight => { - screen.get_active_tab_mut().unwrap().resize_right(); + ScreenInstruction::ResizeRight(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().resize_right(); screen.render(); } - ScreenInstruction::ResizeDown => { - screen.get_active_tab_mut().unwrap().resize_down(); + ScreenInstruction::ResizeDown(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().resize_down(); screen.render(); } - ScreenInstruction::ResizeUp => { - screen.get_active_tab_mut().unwrap().resize_up(); + ScreenInstruction::ResizeUp(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().resize_up(); screen.render(); } - ScreenInstruction::SwitchFocus => { - screen.get_active_tab_mut().unwrap().move_focus(); + ScreenInstruction::SwitchFocus(client_id) => { + screen.get_active_tab_mut(client_id).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(); + ScreenInstruction::FocusNextPane(client_id) => { screen - .bus - .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(); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); - - screen.render(); - } - ScreenInstruction::MoveFocusUp => { - screen.get_active_tab_mut().unwrap().move_focus_up(); - - screen.render(); - } - ScreenInstruction::MovePaneDown => { - screen.get_active_tab_mut().unwrap().move_active_pane_down(); - - screen.render(); - } - ScreenInstruction::MovePaneUp => { - screen.get_active_tab_mut().unwrap().move_active_pane_up(); - - screen.render(); - } - ScreenInstruction::MovePaneRight => { - screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() - .move_active_pane_right(); + .focus_next_pane(); screen.render(); } - ScreenInstruction::MovePaneLeft => { - screen.get_active_tab_mut().unwrap().move_active_pane_left(); - - screen.render(); - } - ScreenInstruction::ScrollUp => { + ScreenInstruction::FocusPreviousPane(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) + .unwrap() + .focus_previous_pane(); + + screen.render(); + } + ScreenInstruction::MoveFocusLeft(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_left(); + + screen.render(); + } + ScreenInstruction::MoveFocusLeftOrPreviousTab(client_id) => { + screen.move_focus_left_or_previous_tab(client_id); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + + screen.render(); + } + ScreenInstruction::MoveFocusDown(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_down(); + + screen.render(); + } + ScreenInstruction::MoveFocusRight(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_right(); + + screen.render(); + } + ScreenInstruction::MoveFocusRightOrNextTab(client_id) => { + screen.move_focus_right_or_next_tab(client_id); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + + screen.render(); + } + ScreenInstruction::MoveFocusUp(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_focus_up(); + + screen.render(); + } + ScreenInstruction::ScrollUp(client_id) => { + screen + .get_active_tab_mut(client_id) .unwrap() .scroll_active_terminal_up(); screen.render(); } - ScreenInstruction::ScrollUpAt(point) => { + ScreenInstruction::MovePaneDown(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) + .unwrap() + .move_active_pane_down(); + + screen.render(); + } + ScreenInstruction::MovePaneUp(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_active_pane_up(); + + screen.render(); + } + ScreenInstruction::MovePaneRight(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_active_pane_right(); + + screen.render(); + } + ScreenInstruction::MovePaneLeft(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .move_active_pane_left(); + + screen.render(); + } + ScreenInstruction::ScrollUpAt(point, client_id) => { + screen + .get_active_tab_mut(client_id) .unwrap() .scroll_terminal_up(&point, 3); screen.render(); } - ScreenInstruction::ScrollDown => { + ScreenInstruction::ScrollDown(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .scroll_active_terminal_down(); screen.render(); } - ScreenInstruction::ScrollDownAt(point) => { + ScreenInstruction::ScrollDownAt(point, client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .scroll_terminal_down(&point, 3); screen.render(); } - ScreenInstruction::ScrollToBottom => { + ScreenInstruction::ScrollToBottom(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .scroll_active_terminal_to_bottom(); screen.render(); } - ScreenInstruction::PageScrollUp => { + ScreenInstruction::PageScrollUp(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .scroll_active_terminal_up_page(); screen.render(); } - ScreenInstruction::PageScrollDown => { + ScreenInstruction::PageScrollDown(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .scroll_active_terminal_down_page(); screen.render(); } - ScreenInstruction::ClearScroll => { + ScreenInstruction::ClearScroll(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .clear_active_terminal_scroll(); screen.render(); } - ScreenInstruction::CloseFocusedPane => { - screen.get_active_tab_mut().unwrap().close_focused_pane(); + ScreenInstruction::CloseFocusedPane(client_id) => { + screen + .get_active_tab_mut(client_id) + .unwrap() + .close_focused_pane(); screen.update_tabs(); // update_tabs eventually calls render through the plugin thread } ScreenInstruction::SetSelectable(id, selectable, tab_index) => { @@ -750,13 +845,25 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::ClosePane(id) => { - screen.get_active_tab_mut().unwrap().close_pane(id); + ScreenInstruction::ClosePane(id, client_id) => { + match client_id { + Some(client_id) => { + screen.get_active_tab_mut(client_id).unwrap().close_pane(id); + } + None => { + for (_index, tab) in screen.tabs.iter_mut() { + if tab.get_pane_ids().iter().any(|pid| *pid == id) { + tab.close_pane(id); + break; + } + } + } + } screen.update_tabs(); } - ScreenInstruction::ToggleActiveTerminalFullscreen => { + ScreenInstruction::ToggleActiveTerminalFullscreen(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .toggle_active_pane_fullscreen(); screen.update_tabs(); @@ -770,8 +877,8 @@ pub(crate) fn screen_thread_main( } screen.render(); } - ScreenInstruction::SwitchTabNext => { - screen.switch_tab_next(); + ScreenInstruction::SwitchTabNext(client_id) => { + screen.switch_tab_next(client_id); screen .bus .senders @@ -780,8 +887,8 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::SwitchTabPrev => { - screen.switch_tab_prev(); + ScreenInstruction::SwitchTabPrev(client_id) => { + screen.switch_tab_prev(client_id); screen .bus .senders @@ -790,8 +897,8 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::CloseTab => { - screen.close_tab(); + ScreenInstruction::CloseTab(client_id) => { + screen.close_tab(client_id); screen .bus .senders @@ -810,18 +917,22 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::GoToTab(tab_index) => { - screen.go_to_tab(tab_index as usize); - screen - .bus - .senders - .send_to_server(ServerInstruction::UnblockInputThread) - .unwrap(); + ScreenInstruction::GoToTab(tab_index, client_id) => { + if let Some(client_id) = + client_id.or_else(|| screen.active_tab_indices.keys().next().copied()) + { + screen.go_to_tab(tab_index as usize, client_id); + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); - screen.render(); + screen.render(); + } } - ScreenInstruction::UpdateTabName(c) => { - screen.update_active_tab_name(c); + ScreenInstruction::UpdateTabName(c, client_id) => { + screen.update_active_tab_name(c, client_id); screen.render(); } @@ -830,54 +941,54 @@ pub(crate) fn screen_thread_main( screen.render(); } - ScreenInstruction::ChangeMode(mode_info) => { - screen.change_mode(mode_info); + ScreenInstruction::ChangeMode(mode_info, client_id) => { + screen.change_mode(mode_info, client_id); screen.render(); } - ScreenInstruction::ToggleActiveSyncTab => { + ScreenInstruction::ToggleActiveSyncTab(client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .toggle_sync_panes_is_active(); screen.update_tabs(); screen.render(); } - ScreenInstruction::LeftClick(point) => { + ScreenInstruction::LeftClick(point, client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .handle_left_click(&point); screen.render(); } - ScreenInstruction::MouseRelease(point) => { + ScreenInstruction::MouseRelease(point, client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .handle_mouse_release(&point); screen.render(); } - ScreenInstruction::MouseHold(point) => { + ScreenInstruction::MouseHold(point, client_id) => { screen - .get_active_tab_mut() + .get_active_tab_mut(client_id) .unwrap() .handle_mouse_hold(&point); screen.render(); } - ScreenInstruction::Copy => { - screen.get_active_tab().unwrap().copy_selection(); + ScreenInstruction::Copy(client_id) => { + screen.get_active_tab(client_id).unwrap().copy_selection(); screen.render(); } ScreenInstruction::Exit => { break; } - ScreenInstruction::ToggleTab => { - screen.toggle_tab(); + ScreenInstruction::ToggleTab(client_id) => { + screen.toggle_tab(client_id); screen .bus .senders diff --git a/zellij-server/src/tab.rs b/zellij-server/src/tab.rs index ec181023..84a77862 100644 --- a/zellij-server/src/tab.rs +++ b/zellij-server/src/tab.rs @@ -95,19 +95,16 @@ fn pane_content_offset(position_and_size: &PaneGeom, viewport: &Viewport) -> (us (columns_offset, rows_offset) } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Output { pub client_render_instructions: HashMap, } impl Output { - pub fn new(client_ids: &HashSet) -> Self { - let mut client_render_instructions = HashMap::new(); + pub fn add_clients(&mut self, client_ids: &HashSet) { for client_id in client_ids { - client_render_instructions.insert(*client_id, String::new()); - } - Output { - client_render_instructions, + self.client_render_instructions + .insert(*client_id, String::new()); } } pub fn push_str_to_all_clients(&mut self, to_push: &str) { @@ -134,7 +131,6 @@ pub(crate) struct Tab { should_clear_display_before_rendering: bool, pub mode_info: ModeInfo, pub colors: Palette, - pub is_active: bool, connected_clients: HashSet, draw_pane_frames: bool, pending_vte_events: HashMap>, @@ -335,7 +331,6 @@ impl Tab { colors, draw_pane_frames, pending_vte_events: HashMap::new(), - is_active: true, connected_clients, } } @@ -800,14 +795,20 @@ impl Tab { resize_pty!(pane, self.os_api); } } - pub fn render(&mut self) -> Option { + pub fn render(&mut self, output: &mut Output) { if self.connected_clients.is_empty() || self.active_terminal.is_none() { - return None; + return; } - self.senders - .send_to_pty(PtyInstruction::UpdateActivePane(self.active_terminal)) - .unwrap(); - let mut output = Output::new(&self.connected_clients); + for connected_client in self.connected_clients.iter() { + // TODO: move this out of the render function + self.senders + .send_to_pty(PtyInstruction::UpdateActivePane( + self.active_terminal, + *connected_client, + )) + .unwrap(); + } + output.add_clients(&self.connected_clients); let mut boundaries = Boundaries::new(self.viewport); let hide_cursor = "\u{1b}[?25l"; output.push_str_to_all_clients(hide_cursor); @@ -878,7 +879,6 @@ impl Tab { output.push_str_to_all_clients(hide_cursor); } } - Some(output) } fn get_panes(&self) -> impl Iterator)> { self.panes.iter() @@ -2409,6 +2409,7 @@ impl Tab { // if we reached here, this is either the last pane or there's some sort of // configuration error (eg. we're trying to close a pane surrounded by fixed panes) self.panes.remove(&id); + self.active_terminal = None; self.resize_whole_tab(self.display_area); } } @@ -2587,7 +2588,8 @@ impl Tab { } fn write_selection_to_clipboard(&self, selection: &str) { - let mut output = Output::new(&self.connected_clients); + 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) diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index d1805b8a..b4d367e4 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -116,7 +116,7 @@ fn open_new_tab() { assert_eq!(screen.tabs.len(), 2, "Screen now has two tabs"); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to new tab" ); @@ -132,10 +132,10 @@ pub fn switch_to_prev_tab() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); - screen.switch_tab_prev(); + screen.switch_tab_prev(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous tab" ); @@ -151,11 +151,11 @@ pub fn switch_to_next_tab() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); - screen.switch_tab_prev(); - screen.switch_tab_next(); + screen.switch_tab_prev(1); + screen.switch_tab_next(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to next tab" ); @@ -171,11 +171,11 @@ pub fn close_tab() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); - screen.close_tab(); + screen.close_tab(1); assert_eq!(screen.tabs.len(), 1, "Only one tab left"); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous tab" ); @@ -197,13 +197,13 @@ pub fn close_the_middle_tab() { .values() .map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids())) .collect::>()); - screen.switch_tab_prev(); + screen.switch_tab_prev(1); dbg!(screen .tabs .values() .map(|t| (t.index, t.position, t.name.clone(), t.get_pane_ids())) .collect::>()); - screen.close_tab(); + screen.close_tab(1); dbg!(screen .tabs .values() @@ -212,7 +212,7 @@ pub fn close_the_middle_tab() { assert_eq!(screen.tabs.len(), 2, "Two tabs left"); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab switched to previous tab" ); @@ -229,11 +229,11 @@ fn move_focus_left_at_left_screen_edge_changes_tab() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); - screen.switch_tab_prev(); - screen.move_focus_left_or_previous_tab(); + screen.switch_tab_prev(1); + screen.move_focus_left_or_previous_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 0, "Active tab switched to previous" ); @@ -250,11 +250,11 @@ fn move_focus_right_at_right_screen_edge_changes_tab() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); new_tab(&mut screen, 3); - screen.switch_tab_prev(); - screen.move_focus_right_or_next_tab(); + screen.switch_tab_prev(1); + screen.move_focus_right_or_next_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 2, "Active tab switched to next" ); @@ -270,19 +270,19 @@ pub fn toggle_to_previous_tab_simple() { new_tab(&mut screen, 1); new_tab(&mut screen, 2); - screen.go_to_tab(1); - screen.go_to_tab(2); + screen.go_to_tab(1, 1); + screen.go_to_tab(2, 1); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 0, "Active tab toggler to previous tab" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); @@ -301,38 +301,38 @@ pub fn toggle_to_previous_tab_create_tabs_only() { new_tab(&mut screen, 3); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1], "Tab history is invalid" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(2)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 2], "Tab history is invalid" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1], "Tab history is invalid" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); @@ -352,84 +352,84 @@ pub fn toggle_to_previous_tab_delete() { new_tab(&mut screen, 4); // 3 assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1), Some(2)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1, 2], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 3, "Active tab toggler to previous tab" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1), Some(3)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1, 3], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1), Some(2)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1, 2], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 3, "Active tab toggler to previous tab" ); - screen.switch_tab_prev(); + screen.switch_tab_prev(1); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(1), Some(3)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 1, 3], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); - screen.switch_tab_prev(); + screen.switch_tab_prev(1); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(3), Some(2)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 3, 2], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); - screen.close_tab(); + screen.close_tab(1); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(3)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 3], "Tab history is invalid" ); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 1, "Active tab toggler to previous tab" ); - screen.toggle_tab(); + screen.toggle_tab(1); assert_eq!( - screen.get_active_tab().unwrap().position, + screen.get_active_tab(1).unwrap().position, 2, "Active tab toggler to previous tab" ); assert_eq!( - screen.tab_history, - vec![None, Some(0), Some(2)], + screen.tab_history.get(&1).unwrap(), + &vec![0, 2], "Tab history is invalid" ); } diff --git a/zellij-server/src/wasm_vm.rs b/zellij-server/src/wasm_vm.rs index 8f851513..de42b611 100644 --- a/zellij-server/src/wasm_vm.rs +++ b/zellij-server/src/wasm_vm.rs @@ -20,7 +20,7 @@ use zellij_tile::data::{Event, EventType, PluginIds}; use crate::{ logging_pipe::LoggingPipe, panes::PaneId, - pty::PtyInstruction, + pty::{ClientOrTabIndex, PtyInstruction}, screen::ScreenInstruction, thread_bus::{Bus, ThreadSenders}, }; @@ -60,6 +60,7 @@ pub(crate) struct PluginEnv { pub senders: ThreadSenders, pub wasi_env: WasiEnv, pub subscriptions: Arc>>, + pub tab_index: usize, plugin_own_data_dir: PathBuf, } @@ -208,6 +209,7 @@ fn start_plugin( wasi_env, subscriptions: Arc::new(Mutex::new(HashSet::new())), plugin_own_data_dir, + tab_index, }; let zellij = zellij_exports(store, &plugin_env); @@ -293,16 +295,17 @@ fn host_open_file(plugin_env: &PluginEnv) { let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); plugin_env .senders - .send_to_pty(PtyInstruction::SpawnTerminal(Some( - TerminalAction::OpenFile(path), - ))) + .send_to_pty(PtyInstruction::SpawnTerminal( + Some(TerminalAction::OpenFile(path)), + ClientOrTabIndex::TabIndex(plugin_env.tab_index), + )) .unwrap(); } fn host_switch_tab_to(plugin_env: &PluginEnv, tab_idx: u32) { plugin_env .senders - .send_to_screen(ScreenInstruction::GoToTab(tab_idx)) + .send_to_screen(ScreenInstruction::GoToTab(tab_idx, None)) // this is a hack, we should be able to return the client id here .unwrap(); }