First round of merging against server-client and color stuff

This commit is contained in:
Kyle Sutherland-Cash 2021-05-08 04:32:31 -07:00
parent 7b5e728f9d
commit 9a3e8bcb84
17 changed files with 955 additions and 877 deletions

View file

@ -37,5 +37,5 @@ The Boundaries refer to those lines that are drawn between terminal panes. A few
* The Rect trait is here so that different panes can implement it, giving boundaries a generic way to calculate the size of the pane and draw boundaries around it. * The Rect trait is here so that different panes can implement it, giving boundaries a generic way to calculate the size of the pane and draw boundaries around it.
* Here we use the [unicode box drawing characters](https://en.wikipedia.org/wiki/Box-drawing_character) in order to draw the borders. There's some logic here about combining them together for all possible combinations of pane locations. * Here we use the [unicode box drawing characters](https://en.wikipedia.org/wiki/Box-drawing_character) in order to draw the borders. There's some logic here about combining them together for all possible combinations of pane locations.
## PTY Bus (src/pty_bus.rs) ## PTY Bus (src/pty.rs)
The PtyBus keeps track of several asynchronous streams that read from pty sockets (eg. /dev/pts/999), parse those bytes into ANSI/VT events and send them on to the Screen so that they can be received in the relevant TerminalPane. The PtyBus keeps track of several asynchronous streams that read from pty sockets (eg. /dev/pts/999), parse those bytes into ANSI/VT events and send them on to the Screen so that they can be received in the relevant TerminalPane.

View file

@ -16,7 +16,7 @@ use crate::common::{
input::config::Config, input::config::Config,
input::handler::input_loop, input::handler::input_loop,
os_input_output::ClientOsApi, os_input_output::ClientOsApi,
SenderType, SenderWithContext, SyncChannelWithContext, thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext},
}; };
use crate::server::ServerInstruction; use crate::server::ServerInstruction;

View file

@ -1,4 +1,6 @@
use crate::{common::SenderWithContext, pty_bus::VteBytes, tab::Pane, wasm_vm::PluginInstruction}; use crate::{
common::thread_bus::SenderWithContext, pty::VteBytes, tab::Pane, wasm_vm::PluginInstruction,
};
use crate::panes::{PaneId, PositionAndSize}; use crate::panes::{PaneId, PositionAndSize};

View file

@ -9,7 +9,7 @@ use crate::panes::grid::Grid;
use crate::panes::terminal_character::{ use crate::panes::terminal_character::{
CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER, CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
}; };
use crate::pty_bus::VteBytes; use crate::pty::VteBytes;
#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum PaneId { pub enum PaneId {

View file

@ -2,11 +2,12 @@
//! as well as how they should be resized //! as well as how they should be resized
use crate::client::pane_resizer::PaneResizer; use crate::client::pane_resizer::PaneResizer;
use crate::common::{input::handler::parse_keys, SenderWithContext}; use crate::common::input::handler::parse_keys;
use crate::common::thread_bus::ThreadSenders;
use crate::layout::Layout; use crate::layout::Layout;
use crate::os_input_output::ServerOsApi; use crate::os_input_output::ServerOsApi;
use crate::panes::{PaneId, PositionAndSize, TerminalPane}; use crate::panes::{PaneId, PositionAndSize, TerminalPane};
use crate::pty_bus::{PtyInstruction, VteBytes}; use crate::pty::{PtyInstruction, VteBytes};
use crate::server::ServerInstruction; use crate::server::ServerInstruction;
use crate::utils::shared::adjust_to_size; use crate::utils::shared::adjust_to_size;
use crate::wasm_vm::PluginInstruction; use crate::wasm_vm::PluginInstruction;
@ -69,9 +70,7 @@ pub struct Tab {
full_screen_ws: PositionAndSize, full_screen_ws: PositionAndSize,
fullscreen_is_active: bool, fullscreen_is_active: bool,
os_api: Box<dyn ServerOsApi>, os_api: Box<dyn ServerOsApi>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, pub senders: ThreadSenders,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_server_instructions: SenderWithContext<ServerInstruction>,
synchronize_is_active: bool, synchronize_is_active: bool,
should_clear_display_before_rendering: bool, should_clear_display_before_rendering: bool,
pub mode_info: ModeInfo, pub mode_info: ModeInfo,
@ -219,7 +218,7 @@ pub trait Pane {
} }
impl Tab { impl Tab {
// FIXME: Too many arguments here! Maybe bundle all of the senders for the whole program in a struct? // FIXME: Still too many arguments for clippy to be happy...
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
index: usize, index: usize,
@ -227,9 +226,7 @@ impl Tab {
name: String, name: String,
full_screen_ws: &PositionAndSize, full_screen_ws: &PositionAndSize,
mut os_api: Box<dyn ServerOsApi>, mut os_api: Box<dyn ServerOsApi>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, senders: ThreadSenders,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_server_instructions: SenderWithContext<ServerInstruction>,
max_panes: Option<usize>, max_panes: Option<usize>,
pane_id: Option<PaneId>, pane_id: Option<PaneId>,
mode_info: ModeInfo, mode_info: ModeInfo,
@ -261,9 +258,7 @@ impl Tab {
fullscreen_is_active: false, fullscreen_is_active: false,
synchronize_is_active: false, synchronize_is_active: false,
os_api, os_api,
send_plugin_instructions, senders,
send_pty_instructions,
send_server_instructions,
should_clear_display_before_rendering: false, should_clear_display_before_rendering: false,
mode_info, mode_info,
input_mode, input_mode,
@ -315,14 +310,14 @@ impl Tab {
// Just a regular terminal // Just a regular terminal
if let Some(plugin) = &layout.plugin { if let Some(plugin) = &layout.plugin {
let (pid_tx, pid_rx) = channel(); let (pid_tx, pid_rx) = channel();
self.send_plugin_instructions self.senders
.send(PluginInstruction::Load(pid_tx, plugin.clone())) .send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone()))
.unwrap(); .unwrap();
let pid = pid_rx.recv().unwrap(); let pid = pid_rx.recv().unwrap();
let mut new_plugin = PluginPane::new( let mut new_plugin = PluginPane::new(
pid, pid,
*position_and_size, *position_and_size,
self.send_plugin_instructions.clone(), self.senders.to_plugin.as_ref().unwrap().clone(),
); );
if let Some(max_rows) = position_and_size.max_rows { if let Some(max_rows) = position_and_size.max_rows {
new_plugin.set_max_height(max_rows); new_plugin.set_max_height(max_rows);
@ -332,8 +327,8 @@ impl Tab {
} }
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin)); self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
// Send an initial mode update to the newly loaded plugin only! // Send an initial mode update to the newly loaded plugin only!
self.send_plugin_instructions self.senders
.send(PluginInstruction::Update( .send_to_plugin(PluginInstruction::Update(
Some(pid), Some(pid),
Event::ModeUpdate(self.mode_info.clone()), Event::ModeUpdate(self.mode_info.clone()),
)) ))
@ -355,8 +350,8 @@ impl Tab {
// this is a bit of a hack and happens because we don't have any central location that // this is a bit of a hack and happens because we don't have any central location that
// can query the screen as to how many panes it needs to create a layout // can query the screen as to how many panes it needs to create a layout
// fixing this will require a bit of an architecture change // fixing this will require a bit of an architecture change
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) .send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
.unwrap(); .unwrap();
} }
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
@ -400,9 +395,9 @@ impl Tab {
}, },
); );
if terminal_id_to_split.is_none() { if terminal_id_to_split.is_none() {
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(pid)) .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
.unwrap(); // we can't open this pane, close the pty .unwrap();
return; // likely no terminal large enough to split return; // likely no terminal large enough to split
} }
let terminal_id_to_split = terminal_id_to_split.unwrap(); let terminal_id_to_split = terminal_id_to_split.unwrap();
@ -481,9 +476,9 @@ impl Tab {
let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane_id = &self.get_active_pane_id().unwrap();
let active_pane = self.panes.get_mut(active_pane_id).unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap();
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(pid)) .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
.unwrap(); // we can't open this pane, close the pty .unwrap();
return; return;
} }
let terminal_ws = PositionAndSize { let terminal_ws = PositionAndSize {
@ -538,9 +533,9 @@ impl Tab {
let active_pane_id = &self.get_active_pane_id().unwrap(); let active_pane_id = &self.get_active_pane_id().unwrap();
let active_pane = self.panes.get_mut(active_pane_id).unwrap(); let active_pane = self.panes.get_mut(active_pane_id).unwrap();
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(pid)) .send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
.unwrap(); // we can't open this pane, close the pty .unwrap();
return; return;
} }
let terminal_ws = PositionAndSize { let terminal_ws = PositionAndSize {
@ -597,7 +592,7 @@ impl Tab {
} }
pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) { pub fn handle_pty_bytes(&mut self, pid: RawFd, bytes: VteBytes) {
// if we don't have the terminal in self.terminals it's probably because // if we don't have the terminal in self.terminals it's probably because
// of a race condition where the terminal was created in pty_bus but has not // of a race condition where the terminal was created in pty but has not
// yet been created in Screen. These events are currently not buffered, so // yet been created in Screen. These events are currently not buffered, so
// if you're debugging seemingly randomly missing stdout data, this is // if you're debugging seemingly randomly missing stdout data, this is
// the reason // the reason
@ -628,8 +623,8 @@ impl Tab {
} }
PaneId::Plugin(pid) => { PaneId::Plugin(pid) => {
for key in parse_keys(&input_bytes) { for key in parse_keys(&input_bytes) {
self.send_plugin_instructions self.senders
.send(PluginInstruction::Update(Some(pid), Event::KeyPress(key))) .send_to_plugin(PluginInstruction::Update(Some(pid), Event::KeyPress(key)))
.unwrap() .unwrap()
} }
} }
@ -706,7 +701,7 @@ impl Tab {
pub fn is_sync_panes_active(&self) -> bool { pub fn is_sync_panes_active(&self) -> bool {
self.synchronize_is_active self.synchronize_is_active
} }
pub fn toggle_sync_tab_is_active(&mut self) { pub fn toggle_sync_panes_is_active(&mut self) {
self.synchronize_is_active = !self.synchronize_is_active; self.synchronize_is_active = !self.synchronize_is_active;
} }
pub fn panes_contain_widechar(&self) -> bool { pub fn panes_contain_widechar(&self) -> bool {
@ -781,8 +776,8 @@ impl Tab {
} }
} }
self.send_server_instructions self.senders
.send(ServerInstruction::Render(Some(output))) .send_to_server(ServerInstruction::Render(Some(output)))
.unwrap(); .unwrap();
} }
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> { fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
@ -2086,8 +2081,8 @@ impl Tab {
if let Some(max_panes) = self.max_panes { if let Some(max_panes) = self.max_panes {
let terminals = self.get_pane_ids(); let terminals = self.get_pane_ids();
for &pid in terminals.iter().skip(max_panes - 1) { for &pid in terminals.iter().skip(max_panes - 1) {
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(pid)) .send_to_pty(PtyInstruction::ClosePane(pid))
.unwrap(); .unwrap();
self.close_pane_without_rerender(pid); self.close_pane_without_rerender(pid);
} }
@ -2198,8 +2193,8 @@ impl Tab {
pub fn close_focused_pane(&mut self) { pub fn close_focused_pane(&mut self) {
if let Some(active_pane_id) = self.get_active_pane_id() { if let Some(active_pane_id) = self.get_active_pane_id() {
self.close_pane(active_pane_id); self.close_pane(active_pane_id);
self.send_pty_instructions self.senders
.send(PtyInstruction::ClosePane(active_pane_id)) .send_to_pty(PtyInstruction::ClosePane(active_pane_id))
.unwrap(); .unwrap();
} }
} }

View file

@ -1,9 +1,9 @@
//! Error context system based on a thread-local representation of the call stack, itself based on //! Error context system based on a thread-local representation of the call stack, itself based on
//! the instructions that are sent between threads. //! the instructions that are sent between threads.
use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS}; use super::{thread_bus::ASYNCOPENCALLS, thread_bus::OPENCALLS, ServerInstruction};
use crate::client::ClientInstruction; use crate::client::ClientInstruction;
use crate::pty_bus::PtyInstruction; use crate::pty::PtyInstruction;
use crate::screen::ScreenInstruction; use crate::screen::ScreenInstruction;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -14,7 +14,7 @@ use std::fmt::{Display, Error, Formatter};
const MAX_THREAD_CALL_STACK: usize = 6; const MAX_THREAD_CALL_STACK: usize = 6;
#[cfg(not(test))] #[cfg(not(test))]
use super::SenderWithContext; use super::thread_bus::SenderWithContext;
#[cfg(not(test))] #[cfg(not(test))]
use std::panic::PanicInfo; use std::panic::PanicInfo;
/// Custom panic handler/hook. Prints the [`ErrorContext`]. /// Custom panic handler/hook. Prints the [`ErrorContext`].
@ -200,7 +200,7 @@ pub enum ScreenContext {
PageScrollDown, PageScrollDown,
ClearScroll, ClearScroll,
CloseFocusedPane, CloseFocusedPane,
ToggleActiveSyncTab, ToggleActiveSyncPanes,
ToggleActiveTerminalFullscreen, ToggleActiveTerminalFullscreen,
SetSelectable, SetSelectable,
SetInvisibleBorders, SetInvisibleBorders,
@ -261,7 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName, ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize, ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize,
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode, ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab, ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes,
} }
} }
} }

View file

@ -40,7 +40,7 @@ pub enum Action {
/// Toggle between fullscreen focus pane and normal layout. /// Toggle between fullscreen focus pane and normal layout.
ToggleFocusFullscreen, ToggleFocusFullscreen,
/// Toggle between sending text commands to all panes on the current tab and normal mode. /// Toggle between sending text commands to all panes on the current tab and normal mode.
ToggleActiveSyncTab, ToggleActiveSyncPanes,
/// Open a new pane in the specified direction (relative to focus). /// Open a new pane in the specified direction (relative to focus).
/// If no direction is specified, will try to use the biggest available space. /// If no direction is specified, will try to use the biggest available space.
NewPane(Option<Direction>), NewPane(Option<Direction>),

View file

@ -4,7 +4,7 @@ use super::actions::Action;
use super::keybinds::Keybinds; use super::keybinds::Keybinds;
use crate::client::ClientInstruction; use crate::client::ClientInstruction;
use crate::common::input::config::Config; use crate::common::input::config::Config;
use crate::common::{SenderWithContext, OPENCALLS}; use crate::common::thread_bus::{SenderWithContext, OPENCALLS};
use crate::errors::ContextType; use crate::errors::ContextType;
use crate::os_input_output::ClientOsApi; use crate::os_input_output::ClientOsApi;
use crate::server::ServerInstruction; use crate::server::ServerInstruction;

View file

@ -3,73 +3,12 @@ pub mod errors;
pub mod input; pub mod input;
pub mod ipc; pub mod ipc;
pub mod os_input_output; pub mod os_input_output;
pub mod pty_bus; pub mod pty;
pub mod screen; pub mod screen;
pub mod setup; pub mod setup;
pub mod thread_bus;
pub mod utils; pub mod utils;
pub mod wasm_vm; pub mod wasm_vm;
use crate::panes::PaneId; use crate::panes::PaneId;
use crate::server::ServerInstruction; use crate::server::ServerInstruction;
use async_std::task_local;
use errors::{get_current_ctx, ErrorContext};
use std::cell::RefCell;
use std::sync::mpsc;
/// An [MPSC](mpsc) asynchronous channel with added error context.
pub type ChannelWithContext<T> = (
mpsc::Sender<(T, ErrorContext)>,
mpsc::Receiver<(T, ErrorContext)>,
);
/// An [MPSC](mpsc) synchronous channel with added error context.
pub type SyncChannelWithContext<T> = (
mpsc::SyncSender<(T, ErrorContext)>,
mpsc::Receiver<(T, ErrorContext)>,
);
/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
#[derive(Clone)]
pub enum SenderType<T: Clone> {
/// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
Sender(mpsc::Sender<(T, ErrorContext)>),
/// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
SyncSender(mpsc::SyncSender<(T, ErrorContext)>),
}
/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`],
/// synchronously or asynchronously depending on the underlying [`SenderType`].
#[derive(Clone)]
pub struct SenderWithContext<T: Clone> {
sender: SenderType<T>,
}
impl<T: Clone> SenderWithContext<T> {
pub fn new(sender: SenderType<T>) -> Self {
Self { sender }
}
/// Sends an event, along with the current [`ErrorContext`], on this
/// [`SenderWithContext`]'s channel.
pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> {
let err_ctx = get_current_ctx();
match self.sender {
SenderType::Sender(ref s) => s.send((event, err_ctx)),
SenderType::SyncSender(ref s) => s.send((event, err_ctx)),
}
}
}
unsafe impl<T: Clone> Send for SenderWithContext<T> {}
unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
thread_local!(
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
/// stack in the form of an [`ErrorContext`].
pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
);
task_local! {
/// A key to some task local storage that holds a representation of the task's call
/// stack in the form of an [`ErrorContext`].
static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
}

View file

@ -4,20 +4,19 @@ use ::async_std::task::*;
use ::std::collections::HashMap; use ::std::collections::HashMap;
use ::std::os::unix::io::RawFd; use ::std::os::unix::io::RawFd;
use ::std::pin::*; use ::std::pin::*;
use ::std::sync::mpsc::Receiver;
use ::std::time::{Duration, Instant}; use ::std::time::{Duration, Instant};
use std::path::PathBuf; use std::path::PathBuf;
use super::SenderWithContext; use super::{screen::ScreenInstruction, thread_bus::SenderWithContext};
use crate::layout::Layout;
use crate::os_input_output::ServerOsApi; use crate::os_input_output::ServerOsApi;
use crate::utils::logging::debug_to_file; use crate::utils::logging::debug_to_file;
use crate::wasm_vm::PluginInstruction;
use crate::{ use crate::{
errors::{get_current_ctx, ContextType, ErrorContext}, common::thread_bus::Bus,
errors::{get_current_ctx, ContextType, PtyContext},
panes::PaneId, panes::PaneId,
screen::ScreenInstruction,
wasm_vm::PluginInstruction,
}; };
use crate::{layout::Layout, server::ServerInstruction};
pub struct ReadFromPid { pub struct ReadFromPid {
pid: RawFd, pid: RawFd,
@ -79,16 +78,69 @@ pub enum PtyInstruction {
Exit, Exit,
} }
pub struct PtyBus { pub struct Pty {
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub bus: Bus<PtyInstruction>,
pub id_to_child_pid: HashMap<RawFd, RawFd>, pub id_to_child_pid: HashMap<RawFd, RawFd>,
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
os_input: Box<dyn ServerOsApi>,
debug_to_file: bool, debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>, task_handles: HashMap<RawFd, JoinHandle<()>>,
} }
pub fn pty_thread_main(mut pty: Pty, maybe_layout: Option<Layout>) {
loop {
let (event, mut err_ctx) = pty.bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
match event {
PtyInstruction::SpawnTerminal(file_to_open) => {
let pid = pty.spawn_terminal(file_to_open);
pty.bus
.senders
.send_to_screen(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
let pid = pty.spawn_terminal(file_to_open);
pty.bus
.senders
.send_to_screen(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
let pid = pty.spawn_terminal(file_to_open);
pty.bus
.senders
.send_to_screen(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::NewTab => {
if let Some(layout) = maybe_layout.clone() {
pty.spawn_terminals_for_layout(layout);
} else {
let pid = pty.spawn_terminal(None);
pty.bus
.senders
.send_to_screen(ScreenInstruction::NewTab(pid))
.unwrap();
}
}
PtyInstruction::ClosePane(id) => {
pty.close_pane(id);
pty.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
PtyInstruction::CloseTab(ids) => {
pty.close_tab(ids);
pty.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
PtyInstruction::Exit => break,
}
}
}
fn stream_terminal_bytes( fn stream_terminal_bytes(
pid: RawFd, pid: RawFd,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
@ -124,9 +176,7 @@ fn stream_terminal_bytes(
Some(receive_time) => { Some(receive_time) => {
if receive_time.elapsed() > max_render_pause { if receive_time.elapsed() > max_render_pause {
pending_render = false; pending_render = false;
send_screen_instructions let _ = send_screen_instructions.send(ScreenInstruction::Render);
.send(ScreenInstruction::Render)
.unwrap();
last_byte_receive_time = Some(Instant::now()); last_byte_receive_time = Some(Instant::now());
} else { } else {
pending_render = true; pending_render = true;
@ -140,9 +190,7 @@ fn stream_terminal_bytes(
} else { } else {
if pending_render { if pending_render {
pending_render = false; pending_render = false;
send_screen_instructions let _ = send_screen_instructions.send(ScreenInstruction::Render);
.send(ScreenInstruction::Render)
.unwrap();
} }
last_byte_receive_time = None; last_byte_receive_time = None;
task::sleep(::std::time::Duration::from_millis(10)).await; task::sleep(::std::time::Duration::from_millis(10)).await;
@ -162,31 +210,26 @@ fn stream_terminal_bytes(
}) })
} }
impl PtyBus { impl Pty {
pub fn new( pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self {
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, Pty {
send_screen_instructions: SenderWithContext<ScreenInstruction>, bus,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
os_input: Box<dyn ServerOsApi>,
debug_to_file: bool,
) -> Self {
PtyBus {
receive_pty_instructions,
os_input,
id_to_child_pid: HashMap::new(), id_to_child_pid: HashMap::new(),
send_screen_instructions,
send_plugin_instructions,
debug_to_file, debug_to_file,
task_handles: HashMap::new(), task_handles: HashMap::new(),
} }
} }
pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd { pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
let (pid_primary, pid_secondary): (RawFd, RawFd) = let (pid_primary, pid_secondary): (RawFd, RawFd) = self
self.os_input.spawn_terminal(file_to_open); .bus
.os_input
.as_mut()
.unwrap()
.spawn_terminal(file_to_open);
let task_handle = stream_terminal_bytes( let task_handle = stream_terminal_bytes(
pid_primary, pid_primary,
self.send_screen_instructions.clone(), self.bus.senders.to_screen.as_ref().unwrap().clone(),
self.os_input.clone(), self.bus.os_input.as_ref().unwrap().clone(),
self.debug_to_file, self.debug_to_file,
); );
self.task_handles.insert(pid_primary, task_handle); self.task_handles.insert(pid_primary, task_handle);
@ -197,12 +240,14 @@ impl PtyBus {
let total_panes = layout.total_terminal_panes(); let total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![]; let mut new_pane_pids = vec![];
for _ in 0..total_panes { for _ in 0..total_panes {
let (pid_primary, pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal(None); let (pid_primary, pid_secondary): (RawFd, RawFd) =
self.bus.os_input.as_mut().unwrap().spawn_terminal(None);
self.id_to_child_pid.insert(pid_primary, pid_secondary); self.id_to_child_pid.insert(pid_primary, pid_secondary);
new_pane_pids.push(pid_primary); new_pane_pids.push(pid_primary);
} }
self.send_screen_instructions self.bus
.send(ScreenInstruction::ApplyLayout( .senders
.send_to_screen(ScreenInstruction::ApplyLayout(
layout, layout,
new_pane_pids.clone(), new_pane_pids.clone(),
)) ))
@ -210,8 +255,8 @@ impl PtyBus {
for id in new_pane_pids { for id in new_pane_pids {
let task_handle = stream_terminal_bytes( let task_handle = stream_terminal_bytes(
id, id,
self.send_screen_instructions.clone(), self.bus.senders.to_screen.as_ref().unwrap().clone(),
self.os_input.clone(), self.bus.os_input.as_ref().unwrap().clone(),
self.debug_to_file, self.debug_to_file,
); );
self.task_handles.insert(id, task_handle); self.task_handles.insert(id, task_handle);
@ -222,15 +267,16 @@ impl PtyBus {
PaneId::Terminal(id) => { PaneId::Terminal(id) => {
let child_pid = self.id_to_child_pid.remove(&id).unwrap(); let child_pid = self.id_to_child_pid.remove(&id).unwrap();
let handle = self.task_handles.remove(&id).unwrap(); let handle = self.task_handles.remove(&id).unwrap();
self.os_input.kill(child_pid).unwrap(); self.bus.os_input.as_mut().unwrap().kill(child_pid).unwrap();
task::block_on(async { task::block_on(async {
handle.cancel().await; handle.cancel().await;
}); });
} }
PaneId::Plugin(pid) => self PaneId::Plugin(pid) => drop(
.send_plugin_instructions self.bus
.send(PluginInstruction::Unload(pid)) .senders
.unwrap(), .send_to_plugin(PluginInstruction::Unload(pid)),
),
} }
} }
pub fn close_tab(&mut self, ids: Vec<PaneId>) { pub fn close_tab(&mut self, ids: Vec<PaneId>) {
@ -240,7 +286,7 @@ impl PtyBus {
} }
} }
impl Drop for PtyBus { impl Drop for Pty {
fn drop(&mut self) { fn drop(&mut self) {
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect(); let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
for id in child_ids { for id in child_ids {

View file

@ -3,15 +3,14 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::str; use std::str;
use std::sync::mpsc::Receiver;
use crate::common::SenderWithContext; use crate::common::pty::{PtyInstruction, VteBytes};
use crate::os_input_output::ServerOsApi; use crate::common::thread_bus::Bus;
use crate::errors::{ContextType, ScreenContext};
use crate::panes::PositionAndSize; use crate::panes::PositionAndSize;
use crate::pty_bus::{PtyInstruction, VteBytes};
use crate::server::ServerInstruction; use crate::server::ServerInstruction;
use crate::tab::Tab; use crate::tab::Tab;
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction}; use crate::wasm_vm::PluginInstruction;
use crate::{layout::Layout, panes::PaneId}; use crate::{layout::Layout, panes::PaneId};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
@ -52,7 +51,7 @@ pub enum ScreenInstruction {
NewTab(RawFd), NewTab(RawFd),
SwitchTabNext, SwitchTabNext,
SwitchTabPrev, SwitchTabPrev,
ToggleActiveSyncTab, ToggleActiveSyncPanes,
CloseTab, CloseTab,
GoToTab(u32), GoToTab(u32),
UpdateTabName(Vec<u8>), UpdateTabName(Vec<u8>),
@ -63,55 +62,37 @@ pub enum ScreenInstruction {
/// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes). /// A [`Screen`] holds multiple [`Tab`]s, each one holding multiple [`panes`](crate::client::panes).
/// It only directly controls which tab is active, delegating the rest to the individual `Tab`. /// It only directly controls which tab is active, delegating the rest to the individual `Tab`.
pub struct Screen { pub struct Screen {
/// A [`ScreenInstruction`] and [`ErrorContext`] receiver. /// A Bus for sending and receiving messages with the other threads.
pub receiver: Receiver<(ScreenInstruction, ErrorContext)>, pub bus: Bus<ScreenInstruction>,
/// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance. /// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance.
max_panes: Option<usize>, max_panes: Option<usize>,
/// A map between this [`Screen`]'s tabs and their ID/key. /// A map between this [`Screen`]'s tabs and their ID/key.
tabs: BTreeMap<usize, Tab>, tabs: BTreeMap<usize, Tab>,
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
/// An [`PtyInstruction`] and [`ErrorContext`] sender.
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
/// An [`ServerInstruction`] and [`ErrorContext`] sender.
pub send_server_instructions: SenderWithContext<ServerInstruction>,
/// The full size of this [`Screen`]. /// The full size of this [`Screen`].
full_screen_ws: PositionAndSize, full_screen_ws: PositionAndSize,
/// The index of this [`Screen`]'s active [`Tab`]. /// The index of this [`Screen`]'s active [`Tab`].
active_tab_index: Option<usize>, active_tab_index: Option<usize>,
/// The [`ServerOsApi`] this [`Screen`] uses.
os_api: Box<dyn ServerOsApi>,
mode_info: ModeInfo, mode_info: ModeInfo,
input_mode: InputMode, input_mode: InputMode,
colors: Palette, colors: Palette,
} }
impl Screen { impl Screen {
// FIXME: This lint needs actual fixing! Maybe by bundling the Senders
/// Creates and returns a new [`Screen`]. /// Creates and returns a new [`Screen`].
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, bus: Bus<ScreenInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_server_instructions: SenderWithContext<ServerInstruction>,
full_screen_ws: &PositionAndSize, full_screen_ws: &PositionAndSize,
os_api: Box<dyn ServerOsApi>,
max_panes: Option<usize>, max_panes: Option<usize>,
mode_info: ModeInfo, mode_info: ModeInfo,
input_mode: InputMode, input_mode: InputMode,
colors: Palette, colors: Palette,
) -> Self { ) -> Self {
Screen { Screen {
receiver: receive_screen_instructions, bus,
max_panes, max_panes,
send_plugin_instructions,
send_pty_instructions,
send_server_instructions,
full_screen_ws: *full_screen_ws, full_screen_ws: *full_screen_ws,
active_tab_index: None, active_tab_index: None,
tabs: BTreeMap::new(), tabs: BTreeMap::new(),
os_api,
mode_info, mode_info,
input_mode, input_mode,
colors, colors,
@ -128,10 +109,8 @@ impl Screen {
position, position,
String::new(), String::new(),
&self.full_screen_ws, &self.full_screen_ws,
self.os_api.clone(), self.bus.os_input.as_ref().unwrap().clone(),
self.send_plugin_instructions.clone(), self.bus.senders.clone(),
self.send_pty_instructions.clone(),
self.send_server_instructions.clone(),
self.max_panes, self.max_panes,
Some(PaneId::Terminal(pane_id)), Some(PaneId::Terminal(pane_id)),
self.mode_info.clone(), self.mode_info.clone(),
@ -215,13 +194,15 @@ impl Screen {
// below we don't check the result of sending the CloseTab instruction to the pty thread // 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 // 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 // has already closed and this would result in an error
self.send_pty_instructions self.bus
.send(PtyInstruction::CloseTab(pane_ids)) .senders
.send_to_pty(PtyInstruction::CloseTab(pane_ids))
.unwrap(); .unwrap();
if self.tabs.is_empty() { if self.tabs.is_empty() {
self.active_tab_index = None; self.active_tab_index = None;
self.send_server_instructions self.bus
.send(ServerInstruction::Render(None)) .senders
.send_to_server(ServerInstruction::Render(None))
.unwrap(); .unwrap();
} else { } else {
for t in self.tabs.values_mut() { for t in self.tabs.values_mut() {
@ -284,10 +265,8 @@ impl Screen {
position, position,
String::new(), String::new(),
&self.full_screen_ws, &self.full_screen_ws,
self.os_api.clone(), self.bus.os_input.as_ref().unwrap().clone(),
self.send_plugin_instructions.clone(), self.bus.senders.clone(),
self.send_pty_instructions.clone(),
self.send_server_instructions.clone(),
self.max_panes, self.max_panes,
None, None,
self.mode_info.clone(), self.mode_info.clone(),
@ -311,8 +290,9 @@ impl Screen {
is_sync_panes_active: tab.is_sync_panes_active(), is_sync_panes_active: tab.is_sync_panes_active(),
}); });
} }
self.send_plugin_instructions self.bus
.send(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) .senders
.send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data)))
.unwrap(); .unwrap();
} }
@ -340,3 +320,246 @@ impl Screen {
} }
} }
} }
pub fn screen_thread_main(
bus: Bus<ScreenInstruction>,
max_panes: Option<usize>,
full_screen_ws: PositionAndSize,
) {
let colors = bus.os_input.as_ref().unwrap().load_palette();
let mut screen = Screen::new(
bus,
&full_screen_ws,
max_panes,
ModeInfo {
palette: colors,
..ModeInfo::default()
},
InputMode::Normal,
colors,
);
loop {
let (event, mut err_ctx) = screen
.bus
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
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;
}
}
}
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::WriteCharacter(bytes) => {
let active_tab = screen.get_active_tab_mut().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::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::SwitchFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::FocusNextPane => {
screen.get_active_tab_mut().unwrap().focus_next_pane();
}
ScreenInstruction::FocusPreviousPane => {
screen.get_active_tab_mut().unwrap().focus_previous_pane();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::PageScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up_page();
}
ScreenInstruction::PageScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down_page();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_invisible_borders(id, invisible_borders);
screen.render();
}
ScreenInstruction::ClosePane(id) => {
screen.get_active_tab_mut().unwrap().close_pane(id);
screen.render();
}
ScreenInstruction::ToggleActiveTerminalFullscreen => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_active_pane_fullscreen();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::SwitchTabNext => {
screen.switch_tab_next();
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::SwitchTabPrev => {
screen.switch_tab_prev();
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::CloseTab => {
screen.close_tab();
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
screen.apply_layout(layout, new_pane_pids);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::GoToTab(tab_index) => {
screen.go_to_tab(tab_index as usize);
screen
.bus
.senders
.send_to_server(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::UpdateTabName(c) => {
screen.update_active_tab_name(c);
}
ScreenInstruction::TerminalResize(new_size) => {
screen.resize_to_screen(new_size);
}
ScreenInstruction::ChangeMode(mode_info) => {
screen.change_mode(mode_info);
}
ScreenInstruction::ToggleActiveSyncPanes => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_sync_panes_is_active();
screen.update_tabs();
}
ScreenInstruction::Exit => {
break;
}
}
}
}

142
src/common/thread_bus.rs Normal file
View file

@ -0,0 +1,142 @@
//! Definitions and helpers for sending and receiving messages between threads.
use async_std::task_local;
use std::cell::RefCell;
use std::sync::mpsc;
use crate::common::pty::PtyInstruction;
use crate::common::ServerInstruction;
use crate::errors::{get_current_ctx, ErrorContext};
use crate::os_input_output::ServerOsApi;
use crate::screen::ScreenInstruction;
use crate::wasm_vm::PluginInstruction;
/// An [MPSC](mpsc) asynchronous channel with added error context.
pub type ChannelWithContext<T> = (
mpsc::Sender<(T, ErrorContext)>,
mpsc::Receiver<(T, ErrorContext)>,
);
/// An [MPSC](mpsc) synchronous channel with added error context.
pub type SyncChannelWithContext<T> = (
mpsc::SyncSender<(T, ErrorContext)>,
mpsc::Receiver<(T, ErrorContext)>,
);
/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
#[derive(Clone)]
pub enum SenderType<T: Clone> {
/// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
Sender(mpsc::Sender<(T, ErrorContext)>),
/// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
SyncSender(mpsc::SyncSender<(T, ErrorContext)>),
}
/// Sends messages on an [MPSC](std::sync::mpsc) channel, along with an [`ErrorContext`],
/// synchronously or asynchronously depending on the underlying [`SenderType`].
#[derive(Clone)]
pub struct SenderWithContext<T: Clone> {
sender: SenderType<T>,
}
impl<T: Clone> SenderWithContext<T> {
pub fn new(sender: SenderType<T>) -> Self {
Self { sender }
}
/// Sends an event, along with the current [`ErrorContext`], on this
/// [`SenderWithContext`]'s channel.
pub fn send(&self, event: T) -> Result<(), mpsc::SendError<(T, ErrorContext)>> {
let err_ctx = get_current_ctx();
match self.sender {
SenderType::Sender(ref s) => s.send((event, err_ctx)),
SenderType::SyncSender(ref s) => s.send((event, err_ctx)),
}
}
}
unsafe impl<T: Clone> Send for SenderWithContext<T> {}
unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
thread_local!(
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
/// stack in the form of an [`ErrorContext`].
pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
);
task_local! {
/// A key to some task local storage that holds a representation of the task's call
/// stack in the form of an [`ErrorContext`].
pub static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
}
/// A container for senders to the different threads in zellij on the server side
#[derive(Clone)]
pub struct ThreadSenders {
pub to_screen: Option<SenderWithContext<ScreenInstruction>>,
pub to_pty: Option<SenderWithContext<PtyInstruction>>,
pub to_plugin: Option<SenderWithContext<PluginInstruction>>,
pub to_server: Option<SenderWithContext<ServerInstruction>>,
}
impl ThreadSenders {
pub fn send_to_screen(
&self,
instruction: ScreenInstruction,
) -> Result<(), mpsc::SendError<(ScreenInstruction, ErrorContext)>> {
self.to_screen.as_ref().unwrap().send(instruction)
}
pub fn send_to_pty(
&self,
instruction: PtyInstruction,
) -> Result<(), mpsc::SendError<(PtyInstruction, ErrorContext)>> {
self.to_pty.as_ref().unwrap().send(instruction)
}
pub fn send_to_plugin(
&self,
instruction: PluginInstruction,
) -> Result<(), mpsc::SendError<(PluginInstruction, ErrorContext)>> {
self.to_plugin.as_ref().unwrap().send(instruction)
}
pub fn send_to_server(
&self,
instruction: ServerInstruction,
) -> Result<(), mpsc::SendError<(ServerInstruction, ErrorContext)>> {
self.to_server.as_ref().unwrap().send(instruction)
}
}
/// A container for a receiver, OS input and the senders to a given thread
pub struct Bus<T> {
pub receiver: mpsc::Receiver<(T, ErrorContext)>,
pub senders: ThreadSenders,
pub os_input: Option<Box<dyn ServerOsApi>>,
}
impl<T> Bus<T> {
pub fn new(
receiver: mpsc::Receiver<(T, ErrorContext)>,
to_screen: Option<&SenderWithContext<ScreenInstruction>>,
to_pty: Option<&SenderWithContext<PtyInstruction>>,
to_plugin: Option<&SenderWithContext<PluginInstruction>>,
to_server: Option<&SenderWithContext<ServerInstruction>>,
os_input: Option<Box<dyn ServerOsApi>>,
) -> Self {
Bus {
receiver,
senders: ThreadSenders {
to_screen: to_screen.cloned(),
to_pty: to_pty.cloned(),
to_plugin: to_plugin.cloned(),
to_server: to_server.cloned(),
},
os_input: os_input.clone(),
}
}
pub fn recv(&self) -> Result<(T, ErrorContext), mpsc::RecvError> {
self.receiver.recv()
}
}

View file

@ -1,17 +1,28 @@
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
use std::{ use std::{
collections::HashSet, collections::{HashMap, HashSet},
fs,
path::PathBuf, path::PathBuf,
process, process,
str::FromStr,
sync::{mpsc::Sender, Arc, Mutex}, sync::{mpsc::Sender, Arc, Mutex},
thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; use wasmer::{
use wasmer_wasi::WasiEnv; imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
WasmerEnv,
};
use wasmer_wasi::{Pipe, WasiEnv, WasiState};
use zellij_tile::data::{Event, EventType, PluginIds}; use zellij_tile::data::{Event, EventType, PluginIds};
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext}; use super::{
errors::{ContextType, PluginContext},
pty::PtyInstruction,
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
PaneId,
};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum PluginInstruction { pub enum PluginInstruction {
@ -25,14 +36,97 @@ pub enum PluginInstruction {
#[derive(WasmerEnv, Clone)] #[derive(WasmerEnv, Clone)]
pub struct PluginEnv { pub struct PluginEnv {
pub plugin_id: u32, pub plugin_id: u32,
// FIXME: This should be a big bundle of all of the channels pub senders: ThreadSenders,
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub wasi_env: WasiEnv, pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>, pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
} }
// Thread main --------------------------------------------------------------------------------------------------------
pub fn wasm_thread_main(bus: Bus<PluginInstruction>, store: Store, data_dir: PathBuf) {
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
loop {
let (event, mut err_ctx) = bus.recv().expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
match event {
PluginInstruction::Load(pid_tx, path) => {
let plugin_dir = data_dir.join("plugins/");
let wasm_bytes = fs::read(&path)
.or_else(|_| fs::read(&path.with_extension("wasm")))
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
let module = Module::new(&store, &wasm_bytes).unwrap();
let output = Pipe::new();
let input = Pipe::new();
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
.preopen(|p| {
p.directory(".") // FIXME: Change this to a more meaningful dir
.alias(".")
.read(true)
.write(true)
.create(true)
})
.unwrap()
.stdin(Box::new(input))
.stdout(Box::new(output))
.finalize()
.unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
let plugin_env = PluginEnv {
plugin_id,
senders: bus.senders.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
};
let zellij = zellij_exports(&store, &plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start").unwrap();
// This eventually calls the `.load()` method
start.call(&[]).unwrap();
plugin_map.insert(plugin_id, (instance, plugin_env));
pid_tx.send(plugin_id).unwrap();
plugin_id += 1;
}
PluginInstruction::Update(pid, event) => {
for (&i, (instance, plugin_env)) in &plugin_map {
let subs = plugin_env.subscriptions.lock().unwrap();
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
let event_type = EventType::from_str(&event.to_string()).unwrap();
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
let update = instance.exports.get_function("update").unwrap();
wasi_write_object(&plugin_env.wasi_env, &event);
update.call(&[]).unwrap();
}
}
drop(bus.senders.send_to_screen(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").unwrap();
render
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break,
}
}
}
// Plugin API --------------------------------------------------------------------------------------------------------- // Plugin API ---------------------------------------------------------------------------------------------------------
pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject {
@ -74,8 +168,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
let selectable = selectable != 0; let selectable = selectable != 0;
plugin_env plugin_env
.send_screen_instructions .senders
.send(ScreenInstruction::SetSelectable( .send_to_screen(ScreenInstruction::SetSelectable(
PaneId::Plugin(plugin_env.plugin_id), PaneId::Plugin(plugin_env.plugin_id),
selectable, selectable,
)) ))
@ -85,8 +179,8 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
let max_height = max_height as usize; let max_height = max_height as usize;
plugin_env plugin_env
.send_screen_instructions .senders
.send(ScreenInstruction::SetMaxHeight( .send_to_screen(ScreenInstruction::SetMaxHeight(
PaneId::Plugin(plugin_env.plugin_id), PaneId::Plugin(plugin_env.plugin_id),
max_height, max_height,
)) ))
@ -96,8 +190,8 @@ fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) {
fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) {
let invisible_borders = invisible_borders != 0; let invisible_borders = invisible_borders != 0;
plugin_env plugin_env
.send_screen_instructions .senders
.send(ScreenInstruction::SetInvisibleBorders( .send_to_screen(ScreenInstruction::SetInvisibleBorders(
PaneId::Plugin(plugin_env.plugin_id), PaneId::Plugin(plugin_env.plugin_id),
invisible_borders, invisible_borders,
)) ))
@ -115,8 +209,8 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) {
fn host_open_file(plugin_env: &PluginEnv) { fn host_open_file(plugin_env: &PluginEnv) {
let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); let path: PathBuf = wasi_read_object(&plugin_env.wasi_env);
plugin_env plugin_env
.send_pty_instructions .senders
.send(PtyInstruction::SpawnTerminal(Some(path))) .send_to_pty(PtyInstruction::SpawnTerminal(Some(path)))
.unwrap(); .unwrap();
} }
@ -130,7 +224,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
// timers as we'd like. // timers as we'd like.
// //
// But that's a lot of code, and this is a few lines: // But that's a lot of code, and this is a few lines:
let send_plugin_instructions = plugin_env.send_plugin_instructions.clone(); let send_plugin_instructions = plugin_env.senders.to_plugin.clone();
let update_target = Some(plugin_env.plugin_id); let update_target = Some(plugin_env.plugin_id);
thread::spawn(move || { thread::spawn(move || {
let start_time = Instant::now(); let start_time = Instant::now();
@ -140,6 +234,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) {
let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64();
send_plugin_instructions send_plugin_instructions
.unwrap()
.send(PluginInstruction::Update( .send(PluginInstruction::Update(
update_target, update_target,
Event::Timer(elapsed_time), Event::Timer(elapsed_time),

View file

@ -6,9 +6,7 @@ mod server;
mod tests; mod tests;
use client::{boundaries, layout, panes, start_client, tab}; use client::{boundaries, layout, panes, start_client, tab};
use common::{ use common::{command_is_executing, errors, os_input_output, pty, screen, setup, utils, wasm_vm};
command_is_executing, errors, os_input_output, pty_bus, screen, setup, utils, wasm_vm,
};
use server::start_server; use server::start_server;
use structopt::StructOpt; use structopt::StructOpt;

View file

@ -1,35 +1,29 @@
pub mod route;
use interprocess::local_socket::LocalSocketListener; use interprocess::local_socket::LocalSocketListener;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::sync::{Arc, RwLock};
use std::sync::mpsc::channel;
use std::thread; use std::thread;
use std::{collections::HashMap, fs}; use std::{path::PathBuf, sync::mpsc::channel};
use std::{ use wasmer::Store;
collections::HashSet,
str::FromStr,
sync::{Arc, Mutex, RwLock},
};
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{Event, EventType, InputMode, ModeInfo};
use crate::cli::CliArgs; use crate::cli::CliArgs;
use crate::client::ClientInstruction; use crate::client::ClientInstruction;
use crate::common::thread_bus::{Bus, ThreadSenders};
use crate::common::{ use crate::common::{
errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext}, errors::{ContextType, ServerContext},
input::actions::{Action, Direction}, input::actions::Action,
input::handler::get_mode_info,
os_input_output::{set_permissions, ServerOsApi}, os_input_output::{set_permissions, ServerOsApi},
pty_bus::{PtyBus, PtyInstruction}, pty::{pty_thread_main, Pty, PtyInstruction},
screen::{Screen, ScreenInstruction}, screen::{screen_thread_main, ScreenInstruction},
setup::install::populate_data_dir, setup::install::populate_data_dir,
thread_bus::{ChannelWithContext, SenderType, SenderWithContext},
utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR}, utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR},
wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction}, wasm_vm::{wasm_thread_main, PluginInstruction},
ChannelWithContext, SenderType, SenderWithContext,
}; };
use crate::layout::Layout; use crate::layout::Layout;
use crate::panes::PaneId;
use crate::panes::PositionAndSize; use crate::panes::PositionAndSize;
use route::route_thread_main;
/// Instructions related to server-side application including the /// Instructions related to server-side application including the
/// ones sent by client to server /// ones sent by client to server
@ -43,10 +37,8 @@ pub enum ServerInstruction {
ClientExit, ClientExit,
} }
struct SessionMetaData { pub struct SessionMetaData {
pub send_pty_instructions: SenderWithContext<PtyInstruction>, pub senders: ThreadSenders,
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
screen_thread: Option<thread::JoinHandle<()>>, screen_thread: Option<thread::JoinHandle<()>>,
pty_thread: Option<thread::JoinHandle<()>>, pty_thread: Option<thread::JoinHandle<()>>,
wasm_thread: Option<thread::JoinHandle<()>>, wasm_thread: Option<thread::JoinHandle<()>>,
@ -54,9 +46,9 @@ struct SessionMetaData {
impl Drop for SessionMetaData { impl Drop for SessionMetaData {
fn drop(&mut self) { fn drop(&mut self) {
let _ = self.send_pty_instructions.send(PtyInstruction::Exit); let _ = self.senders.send_to_pty(PtyInstruction::Exit);
let _ = self.send_screen_instructions.send(ScreenInstruction::Exit); let _ = self.senders.send_to_screen(ScreenInstruction::Exit);
let _ = self.send_plugin_instructions.send(PluginInstruction::Exit); let _ = self.senders.send_to_plugin(PluginInstruction::Exit);
let _ = self.screen_thread.take().unwrap().join(); let _ = self.screen_thread.take().unwrap().join();
let _ = self.pty_thread.take().unwrap().join(); let _ = self.pty_thread.take().unwrap().join();
let _ = self.wasm_thread.take().unwrap().join(); let _ = self.wasm_thread.take().unwrap().join();
@ -64,26 +56,27 @@ impl Drop for SessionMetaData {
} }
pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> { pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
let (send_server_instructions, receive_server_instructions): ChannelWithContext< let (to_server, server_receiver): ChannelWithContext<ServerInstruction> = channel();
ServerInstruction, let to_server = SenderWithContext::new(SenderType::Sender(to_server));
> = channel();
let send_server_instructions =
SenderWithContext::new(SenderType::Sender(send_server_instructions));
let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None)); let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
#[cfg(test)] #[cfg(test)]
handle_client( thread::Builder::new()
sessions.clone(), .name("server_router".to_string())
os_input.clone(), .spawn({
send_server_instructions.clone(), let sessions = sessions.clone();
); let os_input = os_input.clone();
let to_server = to_server.clone();
move || route_thread_main(sessions, os_input, to_server)
});
#[cfg(not(test))] #[cfg(not(test))]
let _ = thread::Builder::new() let _ = thread::Builder::new()
.name("server_listener".to_string()) .name("server_listener".to_string())
.spawn({ .spawn({
let os_input = os_input.clone(); let os_input = os_input.clone();
let sessions = sessions.clone(); let sessions = sessions.clone();
let send_server_instructions = send_server_instructions.clone(); let to_server = to_server.clone();
move || { move || {
drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE)); drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap(); let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap();
@ -94,8 +87,17 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
let mut os_input = os_input.clone(); let mut os_input = os_input.clone();
os_input.update_receiver(stream); os_input.update_receiver(stream);
let sessions = sessions.clone(); let sessions = sessions.clone();
let send_server_instructions = send_server_instructions.clone(); let to_server = to_server.clone();
handle_client(sessions, os_input, send_server_instructions); thread::Builder::new()
.name("server_router".to_string())
.spawn({
let sessions = sessions.clone();
let os_input = os_input.clone();
let to_server = to_server.clone();
move || route_thread_main(sessions, os_input, to_server)
})
.unwrap();
} }
Err(err) => { Err(err) => {
panic!("err {:?}", err); panic!("err {:?}", err);
@ -109,24 +111,20 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
.name("server_thread".to_string()) .name("server_thread".to_string())
.spawn({ .spawn({
move || loop { move || loop {
let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap(); let (instruction, mut err_ctx) = server_receiver.recv().unwrap();
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction))); err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
match instruction { match instruction {
ServerInstruction::NewClient(full_screen_ws, opts) => { ServerInstruction::NewClient(full_screen_ws, opts) => {
let session_data = init_session( let session_data =
os_input.clone(), init_session(os_input.clone(), opts, to_server.clone(), full_screen_ws);
opts,
send_server_instructions.clone(),
full_screen_ws,
);
*sessions.write().unwrap() = Some(session_data); *sessions.write().unwrap() = Some(session_data);
sessions sessions
.read() .read()
.unwrap() .unwrap()
.as_ref() .as_ref()
.unwrap() .unwrap()
.send_pty_instructions .senders
.send(PtyInstruction::NewTab) .send_to_pty(PtyInstruction::NewTab)
.unwrap(); .unwrap();
} }
ServerInstruction::UnblockInputThread => { ServerInstruction::UnblockInputThread => {
@ -148,65 +146,19 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
.unwrap() .unwrap()
} }
fn handle_client(
sessions: Arc<RwLock<Option<SessionMetaData>>>,
mut os_input: Box<dyn ServerOsApi>,
send_server_instructions: SenderWithContext<ServerInstruction>,
) {
thread::Builder::new()
.name("server_router".to_string())
.spawn(move || loop {
let (instruction, mut err_ctx) = os_input.recv_from_client();
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
let rlocked_sessions = sessions.read().unwrap();
match instruction {
ServerInstruction::ClientExit => {
send_server_instructions.send(instruction).unwrap();
break;
}
ServerInstruction::Action(action) => {
route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input);
}
ServerInstruction::TerminalResize(new_size) => {
rlocked_sessions
.as_ref()
.unwrap()
.send_screen_instructions
.send(ScreenInstruction::TerminalResize(new_size))
.unwrap();
}
ServerInstruction::NewClient(..) => {
os_input.add_client_sender();
send_server_instructions.send(instruction).unwrap();
}
_ => {
send_server_instructions.send(instruction).unwrap();
}
}
})
.unwrap();
}
fn init_session( fn init_session(
os_input: Box<dyn ServerOsApi>, os_input: Box<dyn ServerOsApi>,
opts: CliArgs, opts: CliArgs,
send_server_instructions: SenderWithContext<ServerInstruction>, to_server: SenderWithContext<ServerInstruction>,
full_screen_ws: PositionAndSize, full_screen_ws: PositionAndSize,
) -> SessionMetaData { ) -> SessionMetaData {
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext< let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channel();
ScreenInstruction, let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
> = channel();
let send_screen_instructions =
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext< let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = channel();
PluginInstruction, let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin));
> = channel(); let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = channel();
let send_plugin_instructions = let to_pty = SenderWithContext::new(SenderType::Sender(to_pty));
SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
channel();
let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions));
// Determine and initialize the data directory // Determine and initialize the data directory
let data_dir = opts let data_dir = opts
@ -224,322 +176,40 @@ fn init_session(
.map(|p| Layout::new(&p, &data_dir)) .map(|p| Layout::new(&p, &data_dir))
.or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir))); .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir)));
let mut pty_bus = PtyBus::new(
receive_pty_instructions,
send_screen_instructions.clone(),
send_plugin_instructions.clone(),
os_input.clone(),
opts.debug,
);
let pty_thread = thread::Builder::new() let pty_thread = thread::Builder::new()
.name("pty".to_string()) .name("pty".to_string())
.spawn({ .spawn({
let send_server_instructions = send_server_instructions.clone(); let pty = Pty::new(
move || loop { Bus::new(
let (event, mut err_ctx) = pty_bus pty_receiver,
.receive_pty_instructions Some(&to_screen),
.recv() None,
.expect("failed to receive event on channel"); Some(&to_plugin),
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event))); None,
match event { Some(os_input.clone()),
PtyInstruction::SpawnTerminal(file_to_open) => { ),
let pid = pty_bus.spawn_terminal(file_to_open); opts.debug,
pty_bus );
.send_screen_instructions
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid))) move || pty_thread_main(pty, maybe_layout)
.unwrap();
}
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
let pid = pty_bus.spawn_terminal(file_to_open);
pty_bus
.send_screen_instructions
.send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
let pid = pty_bus.spawn_terminal(file_to_open);
pty_bus
.send_screen_instructions
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
.unwrap();
}
PtyInstruction::NewTab => {
if let Some(layout) = maybe_layout.clone() {
pty_bus.spawn_terminals_for_layout(layout);
} else {
let pid = pty_bus.spawn_terminal(None);
pty_bus
.send_screen_instructions
.send(ScreenInstruction::NewTab(pid))
.unwrap();
}
}
PtyInstruction::ClosePane(id) => {
pty_bus.close_pane(id);
send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
PtyInstruction::CloseTab(ids) => {
pty_bus.close_tab(ids);
send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
PtyInstruction::Exit => {
break;
}
}
}
}) })
.unwrap(); .unwrap();
let screen_thread = thread::Builder::new() let screen_thread = thread::Builder::new()
.name("screen".to_string()) .name("screen".to_string())
.spawn({ .spawn({
let os_input = os_input.clone(); let screen_bus = Bus::new(
let send_plugin_instructions = send_plugin_instructions.clone(); screen_receiver,
let send_pty_instructions = send_pty_instructions.clone(); None,
let send_server_instructions = send_server_instructions; Some(&to_pty),
Some(&to_plugin),
Some(&to_server),
Some(os_input.clone()),
);
let max_panes = opts.max_panes; let max_panes = opts.max_panes;
let colors = os_input.load_palette();
move || { move || {
let mut screen = Screen::new( screen_thread_main(screen_bus, max_panes, full_screen_ws);
receive_screen_instructions,
send_plugin_instructions,
send_pty_instructions,
send_server_instructions,
&full_screen_ws,
os_input,
max_panes,
ModeInfo {
palette: colors,
..ModeInfo::default()
},
InputMode::Normal,
colors,
);
loop {
let (event, mut err_ctx) = screen
.receiver
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
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;
}
}
}
}
ScreenInstruction::Render => {
screen.render();
}
ScreenInstruction::NewPane(pid) => {
screen.get_active_tab_mut().unwrap().new_pane(pid);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::HorizontalSplit(pid) => {
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::VerticalSplit(pid) => {
screen.get_active_tab_mut().unwrap().vertical_split(pid);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::WriteCharacter(bytes) => {
let active_tab = screen.get_active_tab_mut().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::ResizeRight => {
screen.get_active_tab_mut().unwrap().resize_right();
}
ScreenInstruction::ResizeDown => {
screen.get_active_tab_mut().unwrap().resize_down();
}
ScreenInstruction::ResizeUp => {
screen.get_active_tab_mut().unwrap().resize_up();
}
ScreenInstruction::SwitchFocus => {
screen.get_active_tab_mut().unwrap().move_focus();
}
ScreenInstruction::FocusNextPane => {
screen.get_active_tab_mut().unwrap().focus_next_pane();
}
ScreenInstruction::FocusPreviousPane => {
screen.get_active_tab_mut().unwrap().focus_previous_pane();
}
ScreenInstruction::MoveFocusLeft => {
screen.get_active_tab_mut().unwrap().move_focus_left();
}
ScreenInstruction::MoveFocusDown => {
screen.get_active_tab_mut().unwrap().move_focus_down();
}
ScreenInstruction::MoveFocusRight => {
screen.get_active_tab_mut().unwrap().move_focus_right();
}
ScreenInstruction::MoveFocusUp => {
screen.get_active_tab_mut().unwrap().move_focus_up();
}
ScreenInstruction::ScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up();
}
ScreenInstruction::ScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down();
}
ScreenInstruction::PageScrollUp => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_up_page();
}
ScreenInstruction::PageScrollDown => {
screen
.get_active_tab_mut()
.unwrap()
.scroll_active_terminal_down_page();
}
ScreenInstruction::ClearScroll => {
screen
.get_active_tab_mut()
.unwrap()
.clear_active_terminal_scroll();
}
ScreenInstruction::CloseFocusedPane => {
screen.get_active_tab_mut().unwrap().close_focused_pane();
screen.render();
}
ScreenInstruction::SetSelectable(id, selectable) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_selectable(id, selectable);
}
ScreenInstruction::SetMaxHeight(id, max_height) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_max_height(id, max_height);
}
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
screen
.get_active_tab_mut()
.unwrap()
.set_pane_invisible_borders(id, invisible_borders);
screen.render();
}
ScreenInstruction::ClosePane(id) => {
screen.get_active_tab_mut().unwrap().close_pane(id);
screen.render();
}
ScreenInstruction::ToggleActiveTerminalFullscreen => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_active_pane_fullscreen();
}
ScreenInstruction::NewTab(pane_id) => {
screen.new_tab(pane_id);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::SwitchTabNext => {
screen.switch_tab_next();
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::SwitchTabPrev => {
screen.switch_tab_prev();
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::CloseTab => {
screen.close_tab();
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
screen.apply_layout(layout, new_pane_pids);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::GoToTab(tab_index) => {
screen.go_to_tab(tab_index as usize);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::UpdateTabName(c) => {
screen.update_active_tab_name(c);
screen
.send_server_instructions
.send(ServerInstruction::UnblockInputThread)
.unwrap();
}
ScreenInstruction::TerminalResize(new_size) => {
screen.resize_to_screen(new_size);
}
ScreenInstruction::ChangeMode(mode_info) => {
screen.change_mode(mode_info);
}
ScreenInstruction::ToggleActiveSyncTab => {
screen
.get_active_tab_mut()
.unwrap()
.toggle_sync_tab_is_active();
screen.update_tabs();
}
ScreenInstruction::Exit => {
break;
}
}
}
} }
}) })
.unwrap(); .unwrap();
@ -547,265 +217,28 @@ fn init_session(
let wasm_thread = thread::Builder::new() let wasm_thread = thread::Builder::new()
.name("wasm".to_string()) .name("wasm".to_string())
.spawn({ .spawn({
let send_screen_instructions = send_screen_instructions.clone(); let plugin_bus = Bus::new(
let send_pty_instructions = send_pty_instructions.clone(); plugin_receiver,
let send_plugin_instructions = send_plugin_instructions.clone(); Some(&to_screen),
Some(&to_pty),
Some(&to_plugin),
Some(&to_server),
None,
);
let store = Store::default(); let store = Store::default();
let mut plugin_id = 0;
let mut plugin_map = HashMap::new();
move || loop {
let (event, mut err_ctx) = receive_plugin_instructions
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
match event {
PluginInstruction::Load(pid_tx, path) => {
let plugin_dir = data_dir.join("plugins/");
let wasm_bytes = fs::read(&path)
.or_else(|_| fs::read(&path.with_extension("wasm")))
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that move || wasm_thread_main(plugin_bus, store, data_dir)
let module = Module::new(&store, &wasm_bytes).unwrap();
let output = Pipe::new();
let input = Pipe::new();
let mut wasi_env = WasiState::new("Zellij")
.env("CLICOLOR_FORCE", "1")
.preopen(|p| {
p.directory(".") // FIXME: Change this to a more meaningful dir
.alias(".")
.read(true)
.write(true)
.create(true)
})
.unwrap()
.stdin(Box::new(input))
.stdout(Box::new(output))
.finalize()
.unwrap();
let wasi = wasi_env.import_object(&module).unwrap();
let plugin_env = PluginEnv {
plugin_id,
send_screen_instructions: send_screen_instructions.clone(),
send_pty_instructions: send_pty_instructions.clone(),
send_plugin_instructions: send_plugin_instructions.clone(),
wasi_env,
subscriptions: Arc::new(Mutex::new(HashSet::new())),
};
let zellij = zellij_exports(&store, &plugin_env);
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
let start = instance.exports.get_function("_start").unwrap();
// This eventually calls the `.init()` method
start.call(&[]).unwrap();
plugin_map.insert(plugin_id, (instance, plugin_env));
pid_tx.send(plugin_id).unwrap();
plugin_id += 1;
}
PluginInstruction::Update(pid, event) => {
for (&i, (instance, plugin_env)) in &plugin_map {
let subs = plugin_env.subscriptions.lock().unwrap();
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
let event_type = EventType::from_str(&event.to_string()).unwrap();
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
let update = instance.exports.get_function("update").unwrap();
wasi_write_object(&plugin_env.wasi_env, &event);
update.call(&[]).unwrap();
}
}
drop(send_screen_instructions.send(ScreenInstruction::Render));
}
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
let render = instance.exports.get_function("render").unwrap();
render
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
.unwrap();
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
}
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
PluginInstruction::Exit => break,
}
}
}) })
.unwrap(); .unwrap();
SessionMetaData { SessionMetaData {
send_plugin_instructions, senders: ThreadSenders {
send_screen_instructions, to_screen: Some(to_screen),
send_pty_instructions, to_pty: Some(to_pty),
to_plugin: Some(to_plugin),
to_server: None,
},
screen_thread: Some(screen_thread), screen_thread: Some(screen_thread),
pty_thread: Some(pty_thread), pty_thread: Some(pty_thread),
wasm_thread: Some(wasm_thread), wasm_thread: Some(wasm_thread),
} }
} }
fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) {
match action {
Action::Write(val) => {
session
.send_screen_instructions
.send(ScreenInstruction::ClearScroll)
.unwrap();
session
.send_screen_instructions
.send(ScreenInstruction::WriteCharacter(val))
.unwrap();
}
Action::SwitchToMode(mode) => {
let palette = os_input.load_palette();
session
.send_plugin_instructions
.send(PluginInstruction::Update(
None,
Event::ModeUpdate(get_mode_info(mode, palette)),
))
.unwrap();
session
.send_screen_instructions
.send(ScreenInstruction::ChangeMode(get_mode_info(mode, palette)))
.unwrap();
session
.send_screen_instructions
.send(ScreenInstruction::Render)
.unwrap();
}
Action::Resize(direction) => {
let screen_instr = match direction {
Direction::Left => ScreenInstruction::ResizeLeft,
Direction::Right => ScreenInstruction::ResizeRight,
Direction::Up => ScreenInstruction::ResizeUp,
Direction::Down => ScreenInstruction::ResizeDown,
};
session.send_screen_instructions.send(screen_instr).unwrap();
}
Action::SwitchFocus => {
session
.send_screen_instructions
.send(ScreenInstruction::SwitchFocus)
.unwrap();
}
Action::FocusNextPane => {
session
.send_screen_instructions
.send(ScreenInstruction::FocusNextPane)
.unwrap();
}
Action::FocusPreviousPane => {
session
.send_screen_instructions
.send(ScreenInstruction::FocusPreviousPane)
.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,
};
session.send_screen_instructions.send(screen_instr).unwrap();
}
Action::ScrollUp => {
session
.send_screen_instructions
.send(ScreenInstruction::ScrollUp)
.unwrap();
}
Action::ScrollDown => {
session
.send_screen_instructions
.send(ScreenInstruction::ScrollDown)
.unwrap();
}
Action::PageScrollUp => {
session
.send_screen_instructions
.send(ScreenInstruction::PageScrollUp)
.unwrap();
}
Action::PageScrollDown => {
session
.send_screen_instructions
.send(ScreenInstruction::PageScrollDown)
.unwrap();
}
Action::ToggleFocusFullscreen => {
session
.send_screen_instructions
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
.unwrap();
}
Action::NewPane(direction) => {
let pty_instr = match direction {
Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
// No direction specified - try to put it in the biggest available spot
None => PtyInstruction::SpawnTerminal(None),
};
session.send_pty_instructions.send(pty_instr).unwrap();
}
Action::CloseFocus => {
session
.send_screen_instructions
.send(ScreenInstruction::CloseFocusedPane)
.unwrap();
}
Action::NewTab => {
session
.send_pty_instructions
.send(PtyInstruction::NewTab)
.unwrap();
}
Action::GoToNextTab => {
session
.send_screen_instructions
.send(ScreenInstruction::SwitchTabNext)
.unwrap();
}
Action::GoToPreviousTab => {
session
.send_screen_instructions
.send(ScreenInstruction::SwitchTabPrev)
.unwrap();
}
Action::ToggleActiveSyncTab => {
session
.send_screen_instructions
.send(ScreenInstruction::ToggleActiveSyncTab)
.unwrap();
}
Action::CloseTab => {
session
.send_screen_instructions
.send(ScreenInstruction::CloseTab)
.unwrap();
}
Action::GoToTab(i) => {
session
.send_screen_instructions
.send(ScreenInstruction::GoToTab(i))
.unwrap();
}
Action::TabNameInput(c) => {
session
.send_screen_instructions
.send(ScreenInstruction::UpdateTabName(c))
.unwrap();
}
Action::NoOp => {}
Action::Quit => panic!("Received unexpected action"),
}
}

205
src/server/route.rs Normal file
View file

@ -0,0 +1,205 @@
use std::sync::{Arc, RwLock};
use zellij_tile::data::Event;
use crate::common::errors::{ContextType, ServerContext};
use crate::common::input::actions::Action;
use crate::common::input::{actions::Direction, handler::get_mode_info};
use crate::common::os_input_output::ServerOsApi;
use crate::common::pty::PtyInstruction;
use crate::common::screen::ScreenInstruction;
use crate::common::thread_bus::SenderWithContext;
use crate::common::wasm_vm::PluginInstruction;
use crate::server::{ServerInstruction, SessionMetaData};
fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) {
match action {
Action::Write(val) => {
session
.senders
.send_to_screen(ScreenInstruction::ClearScroll)
.unwrap();
session
.senders
.send_to_screen(ScreenInstruction::WriteCharacter(val))
.unwrap();
}
Action::SwitchToMode(mode) => {
let palette = os_input.load_palette();
session
.senders
.send_to_plugin(PluginInstruction::Update(
None,
Event::ModeUpdate(get_mode_info(mode, palette)),
))
.unwrap();
session
.senders
.send_to_screen(ScreenInstruction::ChangeMode(get_mode_info(mode, palette)))
.unwrap();
session
.senders
.send_to_screen(ScreenInstruction::Render)
.unwrap();
}
Action::Resize(direction) => {
let screen_instr = match direction {
Direction::Left => ScreenInstruction::ResizeLeft,
Direction::Right => ScreenInstruction::ResizeRight,
Direction::Up => ScreenInstruction::ResizeUp,
Direction::Down => ScreenInstruction::ResizeDown,
};
session.senders.send_to_screen(screen_instr).unwrap();
}
Action::SwitchFocus => {
session
.senders
.send_to_screen(ScreenInstruction::SwitchFocus)
.unwrap();
}
Action::FocusNextPane => {
session
.senders
.send_to_screen(ScreenInstruction::FocusNextPane)
.unwrap();
}
Action::FocusPreviousPane => {
session
.senders
.send_to_screen(ScreenInstruction::FocusPreviousPane)
.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,
};
session.senders.send_to_screen(screen_instr).unwrap();
}
Action::ScrollUp => {
session
.senders
.send_to_screen(ScreenInstruction::ScrollUp)
.unwrap();
}
Action::ScrollDown => {
session
.senders
.send_to_screen(ScreenInstruction::ScrollDown)
.unwrap();
}
Action::PageScrollUp => {
session
.senders
.send_to_screen(ScreenInstruction::PageScrollUp)
.unwrap();
}
Action::PageScrollDown => {
session
.senders
.send_to_screen(ScreenInstruction::PageScrollDown)
.unwrap();
}
Action::ToggleFocusFullscreen => {
session
.senders
.send_to_screen(ScreenInstruction::ToggleActiveTerminalFullscreen)
.unwrap();
}
Action::NewPane(direction) => {
let pty_instr = match direction {
Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
// No direction specified - try to put it in the biggest available spot
None => PtyInstruction::SpawnTerminal(None),
};
session.senders.send_to_pty(pty_instr).unwrap();
}
Action::CloseFocus => {
session
.senders
.send_to_screen(ScreenInstruction::CloseFocusedPane)
.unwrap();
}
Action::NewTab => {
session.senders.send_to_pty(PtyInstruction::NewTab).unwrap();
}
Action::GoToNextTab => {
session
.senders
.send_to_screen(ScreenInstruction::SwitchTabNext)
.unwrap();
}
Action::GoToPreviousTab => {
session
.senders
.send_to_screen(ScreenInstruction::SwitchTabPrev)
.unwrap();
}
Action::ToggleActiveSyncPanes => {
session
.senders
.send_to_screen(ScreenInstruction::ToggleActiveSyncPanes)
.unwrap();
}
Action::CloseTab => {
session
.senders
.send_to_screen(ScreenInstruction::CloseTab)
.unwrap();
}
Action::GoToTab(i) => {
session
.senders
.send_to_screen(ScreenInstruction::GoToTab(i))
.unwrap();
}
Action::TabNameInput(c) => {
session
.senders
.send_to_screen(ScreenInstruction::UpdateTabName(c))
.unwrap();
}
Action::NoOp => {}
Action::Quit => panic!("Received unexpected action"),
}
}
pub fn route_thread_main(
sessions: Arc<RwLock<Option<SessionMetaData>>>,
mut os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
) {
loop {
let (instruction, mut err_ctx) = os_input.recv_from_client();
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
let rlocked_sessions = sessions.read().unwrap();
match instruction {
ServerInstruction::ClientExit => {
to_server.send(instruction).unwrap();
break;
}
ServerInstruction::Action(action) => {
route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input);
}
ServerInstruction::TerminalResize(new_size) => {
rlocked_sessions
.as_ref()
.unwrap()
.senders
.send_to_screen(ScreenInstruction::TerminalResize(new_size))
.unwrap();
}
ServerInstruction::NewClient(..) => {
os_input.add_client_sender();
to_server.send(instruction).unwrap();
}
_ => {
to_server.send(instruction).unwrap();
}
}
}
}

View file

@ -8,7 +8,7 @@ use std::sync::{mpsc, Arc, Condvar, Mutex};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crate::client::ClientInstruction; use crate::client::ClientInstruction;
use crate::common::{ChannelWithContext, SenderType, SenderWithContext}; use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext};
use crate::errors::ErrorContext; use crate::errors::ErrorContext;
use crate::os_input_output::{ClientOsApi, ServerOsApi}; use crate::os_input_output::{ClientOsApi, ServerOsApi};
use crate::server::ServerInstruction; use crate::server::ServerInstruction;