refactor(clients): support multiple clients in tab/screen rendering infra (#770)

This commit is contained in:
Aram Drevekenin 2021-10-07 15:22:20 +02:00 committed by GitHub
parent 24154e40e0
commit f2401d0b25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 314 additions and 151 deletions

View File

@ -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<PluginsConfig>,
),
Render(Option<String>),
Render(Option<Output>),
UnblockInputThread,
ClientExit(ClientId),
RemoveClient(ClientId),
@ -261,7 +262,6 @@ pub fn start_server(mut os_input: Box<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>, 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<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
client_attributes: ClientAttributes,
session_state: Arc<RwLock<SessionState>>,
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();

View File

@ -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<TerminalAction>),
SpawnTerminalHorizontally(Option<TerminalAction>),
UpdateActivePane(Option<PaneId>),
NewTab(Option<TerminalAction>, Option<TabLayout>),
NewTab(Option<TerminalAction>, Option<TabLayout>, ClientId),
ClosePane(PaneId),
CloseTab(Vec<PaneId>),
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<TerminalAction>,
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 {

View File

@ -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 => {

View File

@ -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<RawFd>),
NewTab(Layout, Vec<RawFd>, 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<Option<usize>>,
mode_info: ModeInfo,
colors: Palette,
session_state: Arc<RwLock<SessionState>>,
draw_pane_frames: bool,
}
@ -160,7 +162,6 @@ impl Screen {
client_attributes: &ClientAttributes,
max_panes: Option<usize>,
mode_info: ModeInfo,
session_state: Arc<RwLock<SessionState>>,
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<RawFd>) {
pub fn new_tab(&mut self, layout: Layout, new_pids: Vec<RawFd>, 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<usize>,
client_attributes: ClientAttributes,
config_options: Box<Options>,
session_state: Arc<RwLock<SessionState>>,
) {
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();
}
}
}

View File

@ -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<PaneId>);
@ -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<ClientId, String>,
}
impl Output {
pub fn new(client_ids: &HashSet<ClientId>) -> 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<RwLock<SessionState>>,
pub mode_info: ModeInfo,
pub colors: Palette,
pub is_active: bool,
connected_clients: HashSet<ClientId>,
draw_pane_frames: bool,
pending_vte_events: HashMap<RawFd, Vec<VteBytes>>,
}
@ -268,8 +289,8 @@ impl Tab {
max_panes: Option<usize>,
mode_info: ModeInfo,
colors: Palette,
session_state: Arc<RwLock<SessionState>>,
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<ClientId> {
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<VteBytes> = 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<Output> {
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<Item = (&PaneId, &Box<dyn Pane>)> {
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<dyn Pane>> {
@ -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();

View File

@ -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<usize, nix::Error> {
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
unimplemented!()
}
fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader> {
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
unimplemented!()
}
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
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<dyn ServerOsApi> {
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<ClientToServerMsg> {
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]

View File

@ -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<usize, nix::Error> {
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
unimplemented!()
}
fn async_file_reader(&self, fd: RawFd) -> Box<dyn AsyncReader> {
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
unimplemented!()
}
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
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<dyn ServerOsApi> {
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<ClientToServerMsg> {
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(),

View File

@ -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.