diff options
| author | a-kenji <aks.kenji@protonmail.com> | 2021-05-10 20:54:39 +0200 |
|---|---|---|
| committer | a-kenji <aks.kenji@protonmail.com> | 2021-05-10 20:54:39 +0200 |
| commit | 85a4e476f2011dc0491a15bcce3b0052ebb15adb (patch) | |
| tree | eb5d120250c5c3b0d74314a42129a857bf6b1afe /src/common | |
| parent | fixup! Add Option for Simplified Layout (diff) | |
| parent | Merge pull request #480 from a-kenji/dump-config-exit (diff) | |
| download | sl0ppij-85a4e476f2011dc0491a15bcce3b0052ebb15adb.tar.gz sl0ppij-85a4e476f2011dc0491a15bcce3b0052ebb15adb.tar.xz sl0ppij-85a4e476f2011dc0491a15bcce3b0052ebb15adb.zip | |
fixup! Merge branch 'main' of https://github.com/zellij-org/zellij into simple-font-ui
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/errors.rs | 18 | ||||
| -rw-r--r-- | src/common/input/actions.rs | 3 | ||||
| -rw-r--r-- | src/common/input/handler.rs | 5 | ||||
| -rw-r--r-- | src/common/mod.rs | 65 | ||||
| -rw-r--r-- | src/common/pty.rs (renamed from src/common/pty_bus.rs) | 165 | ||||
| -rw-r--r-- | src/common/screen.rs | 344 | ||||
| -rw-r--r-- | src/common/thread_bus.rs | 142 | ||||
| -rw-r--r-- | src/common/wasm_vm.rs | 138 |
8 files changed, 683 insertions, 197 deletions
diff --git a/src/common/errors.rs b/src/common/errors.rs index d8327462..c2caf19d 100644 --- a/src/common/errors.rs +++ b/src/common/errors.rs @@ -1,20 +1,22 @@ //! Error context system based on a thread-local representation of the call stack, itself based on //! the instructions that are sent between threads. -use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS}; -use crate::client::ClientInstruction; -use crate::pty_bus::PtyInstruction; -use crate::screen::ScreenInstruction; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Error, Formatter}; +use crate::client::ClientInstruction; +use crate::common::thread_bus::{ASYNCOPENCALLS, OPENCALLS}; +use crate::pty::PtyInstruction; +use crate::screen::ScreenInstruction; +use crate::server::ServerInstruction; + /// The maximum amount of calls an [`ErrorContext`] will keep track /// of in its stack representation. This is a per-thread maximum. const MAX_THREAD_CALL_STACK: usize = 6; #[cfg(not(test))] -use super::SenderWithContext; +use super::thread_bus::SenderWithContext; #[cfg(not(test))] use std::panic::PanicInfo; /// Custom panic handler/hook. Prints the [`ErrorContext`]. @@ -190,9 +192,11 @@ pub enum ScreenContext { FocusNextPane, FocusPreviousPane, MoveFocusLeft, + MoveFocusLeftOrPreviousTab, MoveFocusDown, MoveFocusUp, MoveFocusRight, + MoveFocusRightOrNextTab, Exit, ScrollUp, ScrollDown, @@ -235,9 +239,13 @@ impl From<&ScreenInstruction> for ScreenContext { ScreenInstruction::FocusNextPane => ScreenContext::FocusNextPane, ScreenInstruction::FocusPreviousPane => ScreenContext::FocusPreviousPane, ScreenInstruction::MoveFocusLeft => ScreenContext::MoveFocusLeft, + ScreenInstruction::MoveFocusLeftOrPreviousTab => { + ScreenContext::MoveFocusLeftOrPreviousTab + } ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown, ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp, ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight, + ScreenInstruction::MoveFocusRightOrNextTab => ScreenContext::MoveFocusRightOrNextTab, ScreenInstruction::Exit => ScreenContext::Exit, ScreenInstruction::ScrollUp => ScreenContext::ScrollUp, ScreenInstruction::ScrollDown => ScreenContext::ScrollDown, diff --git a/src/common/input/actions.rs b/src/common/input/actions.rs index 54cbbcdc..b806aa3f 100644 --- a/src/common/input/actions.rs +++ b/src/common/input/actions.rs @@ -29,6 +29,9 @@ pub enum Action { /// Move the focus pane in specified direction. SwitchFocus, MoveFocus(Direction), + /// Tries to move the focus pane in specified direction. + /// If there is no pane in the direction, move to previous/next Tab. + MoveFocusOrTab(Direction), /// Scroll up in focus pane. ScrollUp, /// Scroll down in focus pane. diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs index 6238a3ee..68a75448 100644 --- a/src/common/input/handler.rs +++ b/src/common/input/handler.rs @@ -4,7 +4,7 @@ use super::actions::Action; use super::keybinds::Keybinds; use crate::client::ClientInstruction; use crate::common::input::config::Config; -use crate::common::{SenderWithContext, OPENCALLS}; +use crate::common::thread_bus::{SenderWithContext, OPENCALLS}; use crate::errors::ContextType; use crate::os_input_output::ClientOsApi; use crate::server::ServerInstruction; @@ -119,7 +119,8 @@ impl InputHandler { | Action::GoToNextTab | Action::GoToPreviousTab | Action::CloseTab - | Action::GoToTab(_) => { + | Action::GoToTab(_) + | Action::MoveFocusOrTab(_) => { self.command_is_executing.blocking_input_thread(); self.os_input .send_to_server(ServerInstruction::Action(action)); diff --git a/src/common/mod.rs b/src/common/mod.rs index 6061f1a6..c91e1e05 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,73 +3,12 @@ pub mod errors; pub mod input; pub mod ipc; pub mod os_input_output; -pub mod pty_bus; +pub mod pty; pub mod screen; pub mod setup; +pub mod thread_bus; pub mod utils; pub mod wasm_vm; use crate::panes::PaneId; 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() -} diff --git a/src/common/pty_bus.rs b/src/common/pty.rs index 31696186..3c471d6a 100644 --- a/src/common/pty_bus.rs +++ b/src/common/pty.rs @@ -1,23 +1,21 @@ -use ::async_std::stream::*; -use ::async_std::task; -use ::async_std::task::*; -use ::std::collections::HashMap; -use ::std::os::unix::io::RawFd; -use ::std::pin::*; -use ::std::sync::mpsc::Receiver; -use ::std::time::{Duration, Instant}; +use async_std::stream::*; +use async_std::task; +use async_std::task::*; +use std::collections::HashMap; +use std::os::unix::io::RawFd; use std::path::PathBuf; +use std::pin::*; +use std::time::{Duration, Instant}; -use super::SenderWithContext; +use crate::client::panes::PaneId; +use crate::common::errors::{get_current_ctx, ContextType, PtyContext}; +use crate::common::screen::ScreenInstruction; +use crate::common::thread_bus::{Bus, ThreadSenders}; use crate::layout::Layout; use crate::os_input_output::ServerOsApi; +use crate::server::ServerInstruction; use crate::utils::logging::debug_to_file; -use crate::{ - errors::{get_current_ctx, ContextType, ErrorContext}, - panes::PaneId, - screen::ScreenInstruction, - wasm_vm::PluginInstruction, -}; +use crate::wasm_vm::PluginInstruction; pub struct ReadFromPid { pid: RawFd, @@ -79,19 +77,72 @@ pub enum PtyInstruction { Exit, } -pub struct PtyBus { - pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, +pub struct Pty { + pub bus: Bus<PtyInstruction>, 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, 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( pid: RawFd, - send_screen_instructions: SenderWithContext<ScreenInstruction>, + senders: ThreadSenders, os_input: Box<dyn ServerOsApi>, debug: bool, ) -> JoinHandle<()> { @@ -113,7 +164,7 @@ fn stream_terminal_bytes( } } if !bytes_is_empty { - let _ = send_screen_instructions.send(ScreenInstruction::PtyBytes(pid, bytes)); + let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes)); // for UX reasons, if we got something on the wire, we only send the render notice if: // 1. there aren't any more bytes on the wire afterwards // 2. a certain period (currently 30ms) has elapsed since the last render @@ -124,9 +175,7 @@ fn stream_terminal_bytes( Some(receive_time) => { if receive_time.elapsed() > max_render_pause { pending_render = false; - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + let _ = senders.send_to_screen(ScreenInstruction::Render); last_byte_receive_time = Some(Instant::now()); } else { pending_render = true; @@ -140,53 +189,44 @@ fn stream_terminal_bytes( } else { if pending_render { pending_render = false; - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + let _ = senders.send_to_screen(ScreenInstruction::Render); } last_byte_receive_time = None; task::sleep(::std::time::Duration::from_millis(10)).await; } } - send_screen_instructions - .send(ScreenInstruction::Render) - .unwrap(); + senders.send_to_screen(ScreenInstruction::Render).unwrap(); #[cfg(not(test))] // this is a little hacky, and is because the tests end the file as soon as // we read everything, rather than hanging until there is new data // a better solution would be to fix the test fakes, but this will do for now - send_screen_instructions - .send(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) + senders + .send_to_screen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) .unwrap(); } }) } -impl PtyBus { - pub fn new( - receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, - send_screen_instructions: SenderWithContext<ScreenInstruction>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - os_input: Box<dyn ServerOsApi>, - debug_to_file: bool, - ) -> Self { - PtyBus { - receive_pty_instructions, - os_input, +impl Pty { + pub fn new(bus: Bus<PtyInstruction>, debug_to_file: bool) -> Self { + Pty { + bus, id_to_child_pid: HashMap::new(), - send_screen_instructions, - send_plugin_instructions, debug_to_file, task_handles: HashMap::new(), } } pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd { - let (pid_primary, pid_secondary): (RawFd, RawFd) = - self.os_input.spawn_terminal(file_to_open); + let (pid_primary, pid_secondary): (RawFd, RawFd) = self + .bus + .os_input + .as_mut() + .unwrap() + .spawn_terminal(file_to_open); let task_handle = stream_terminal_bytes( pid_primary, - self.send_screen_instructions.clone(), - self.os_input.clone(), + self.bus.senders.clone(), + self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); self.task_handles.insert(pid_primary, task_handle); @@ -197,12 +237,14 @@ impl PtyBus { let total_panes = layout.total_terminal_panes(); let mut new_pane_pids = vec![]; 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); new_pane_pids.push(pid_primary); } - self.send_screen_instructions - .send(ScreenInstruction::ApplyLayout( + self.bus + .senders + .send_to_screen(ScreenInstruction::ApplyLayout( layout, new_pane_pids.clone(), )) @@ -210,8 +252,8 @@ impl PtyBus { for id in new_pane_pids { let task_handle = stream_terminal_bytes( id, - self.send_screen_instructions.clone(), - self.os_input.clone(), + self.bus.senders.clone(), + self.bus.os_input.as_ref().unwrap().clone(), self.debug_to_file, ); self.task_handles.insert(id, task_handle); @@ -222,15 +264,16 @@ impl PtyBus { PaneId::Terminal(id) => { let child_pid = self.id_to_child_pid.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 { handle.cancel().await; }); } - PaneId::Plugin(pid) => self - .send_plugin_instructions - .send(PluginInstruction::Unload(pid)) - .unwrap(), + PaneId::Plugin(pid) => drop( + self.bus + .senders + .send_to_plugin(PluginInstruction::Unload(pid)), + ), } } pub fn close_tab(&mut self, ids: Vec<PaneId>) { @@ -240,7 +283,7 @@ impl PtyBus { } } -impl Drop for PtyBus { +impl Drop for Pty { fn drop(&mut self) { let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect(); for id in child_ids { diff --git a/src/common/screen.rs b/src/common/screen.rs index 62d87b51..82f63537 100644 --- a/src/common/screen.rs +++ b/src/common/screen.rs @@ -3,18 +3,20 @@ use std::collections::BTreeMap; use std::os::unix::io::RawFd; use std::str; -use std::sync::mpsc::Receiver; -use crate::common::SenderWithContext; -use crate::os_input_output::ServerOsApi; +use crate::common::pty::{PtyInstruction, VteBytes}; +use crate::common::thread_bus::Bus; +use crate::common::input::options::Options; +use crate::cli::ConfigCli; +use crate::errors::{ContextType, ScreenContext}; +use crate::layout::Layout; +use crate::panes::PaneId; use crate::panes::PositionAndSize; -use crate::pty_bus::{PtyInstruction, VteBytes}; use crate::server::ServerInstruction; use crate::tab::Tab; -use crate::{errors::ErrorContext, wasm_vm::PluginInstruction}; -use crate::{layout::Layout, panes::PaneId}; +use crate::wasm_vm::PluginInstruction; -use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; +use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo, PluginCapabilities}; /// Instructions that can be sent to the [`Screen`]. #[derive(Debug, Clone)] @@ -33,9 +35,11 @@ pub enum ScreenInstruction { FocusNextPane, FocusPreviousPane, MoveFocusLeft, + MoveFocusLeftOrPreviousTab, MoveFocusDown, MoveFocusUp, MoveFocusRight, + MoveFocusRightOrNextTab, Exit, ScrollUp, ScrollDown, @@ -63,55 +67,37 @@ pub enum ScreenInstruction { /// 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`. pub struct Screen { - /// A [`ScreenInstruction`] and [`ErrorContext`] receiver. - pub receiver: Receiver<(ScreenInstruction, ErrorContext)>, + /// A Bus for sending and receiving messages with the other threads. + pub bus: Bus<ScreenInstruction>, /// An optional maximal amount of panes allowed per [`Tab`] in this [`Screen`] instance. max_panes: Option<usize>, /// A map between this [`Screen`]'s tabs and their ID/key. 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`]. full_screen_ws: PositionAndSize, /// The index of this [`Screen`]'s active [`Tab`]. active_tab_index: Option<usize>, - /// The [`ServerOsApi`] this [`Screen`] uses. - os_api: Box<dyn ServerOsApi>, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, } impl Screen { - // FIXME: This lint needs actual fixing! Maybe by bundling the Senders /// Creates and returns a new [`Screen`]. - #[allow(clippy::too_many_arguments)] pub fn new( - receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, - send_plugin_instructions: SenderWithContext<PluginInstruction>, - send_pty_instructions: SenderWithContext<PtyInstruction>, - send_server_instructions: SenderWithContext<ServerInstruction>, + bus: Bus<ScreenInstruction>, full_screen_ws: &PositionAndSize, - os_api: Box<dyn ServerOsApi>, max_panes: Option<usize>, mode_info: ModeInfo, input_mode: InputMode, colors: Palette, ) -> Self { Screen { - receiver: receive_screen_instructions, + bus, max_panes, - send_plugin_instructions, - send_pty_instructions, - send_server_instructions, full_screen_ws: *full_screen_ws, active_tab_index: None, tabs: BTreeMap::new(), - os_api, mode_info, input_mode, colors, @@ -128,10 +114,8 @@ impl Screen { position, String::new(), &self.full_screen_ws, - self.os_api.clone(), - self.send_plugin_instructions.clone(), - self.send_pty_instructions.clone(), - self.send_server_instructions.clone(), + self.bus.os_input.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.max_panes, Some(PaneId::Terminal(pane_id)), self.mode_info.clone(), @@ -215,13 +199,15 @@ impl Screen { // below we don't check the result of sending the CloseTab instruction to the pty thread // because this might be happening when the app is closing, at which point the pty thread // has already closed and this would result in an error - self.send_pty_instructions - .send(PtyInstruction::CloseTab(pane_ids)) + self.bus + .senders + .send_to_pty(PtyInstruction::CloseTab(pane_ids)) .unwrap(); if self.tabs.is_empty() { self.active_tab_index = None; - self.send_server_instructions - .send(ServerInstruction::Render(None)) + self.bus + .senders + .send_to_server(ServerInstruction::Render(None)) .unwrap(); } else { for t in self.tabs.values_mut() { @@ -284,10 +270,8 @@ impl Screen { position, String::new(), &self.full_screen_ws, - self.os_api.clone(), - self.send_plugin_instructions.clone(), - self.send_pty_instructions.clone(), - self.send_server_instructions.clone(), + self.bus.os_input.as_ref().unwrap().clone(), + self.bus.senders.clone(), self.max_panes, None, self.mode_info.clone(), @@ -311,8 +295,9 @@ impl Screen { is_sync_panes_active: tab.is_sync_panes_active(), }); } - self.send_plugin_instructions - .send(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) + self.bus + .senders + .send_to_plugin(PluginInstruction::Update(None, Event::TabUpdate(tab_data))) .unwrap(); } @@ -340,3 +325,276 @@ impl Screen { } } } + +pub fn screen_thread_main( + bus: Bus<ScreenInstruction>, + max_panes: Option<usize>, + full_screen_ws: PositionAndSize, + options: Option<ConfigCli>, + config_options: Options, +) { + let colors = bus.os_input.as_ref().unwrap().load_palette(); + let capabilities = if let Some(ConfigCli::Options(options)) = options { + options.simplified_ui + } else { + config_options.simplified_ui + }; + let mut screen = Screen::new( + bus, + &full_screen_ws, + max_panes, + ModeInfo { + palette: colors, + capabilities: PluginCapabilities { + arrow_fonts: capabilities, + }, + ..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::MoveFocusLeftOrPreviousTab => { + if !screen.get_active_tab_mut().unwrap().move_focus_left() { + screen.switch_tab_prev(); + } + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + ScreenInstruction::MoveFocusDown => { + screen.get_active_tab_mut().unwrap().move_focus_down(); + } + ScreenInstruction::MoveFocusRight => { + screen.get_active_tab_mut().unwrap().move_focus_right(); + } + ScreenInstruction::MoveFocusRightOrNextTab => { + if !screen.get_active_tab_mut().unwrap().move_focus_right() { + screen.switch_tab_next(); + } + screen + .bus + .senders + .send_to_server(ServerInstruction::UnblockInputThread) + .unwrap(); + } + 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::ToggleActiveSyncTab => { + screen + .get_active_tab_mut() + .unwrap() + .toggle_sync_panes_is_active(); + screen.update_tabs(); + } + ScreenInstruction::Exit => { + break; + } + } + } +} diff --git a/src/common/thread_bus.rs b/src/common/thread_bus.rs new file mode 100644 index 00000000..8827d068 --- /dev/null +++ b/src/common/thread_bus.rs @@ -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() + } +} diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs index db52c2bb..e5567285 100644 --- a/src/common/wasm_vm.rs +++ b/src/common/wasm_vm.rs @@ -1,17 +1,25 @@ +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::PathBuf; +use std::process; +use std::str::FromStr; +use std::sync::{mpsc::Sender, Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + use serde::{de::DeserializeOwned, Serialize}; -use std::{ - collections::HashSet, - path::PathBuf, - process, - sync::{mpsc::Sender, Arc, Mutex}, - thread, - time::{Duration, Instant}, +use wasmer::{ + imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value, + WasmerEnv, }; -use wasmer::{imports, Function, ImportObject, Store, WasmerEnv}; -use wasmer_wasi::WasiEnv; +use wasmer_wasi::{Pipe, WasiEnv, WasiState}; use zellij_tile::data::{Event, EventType, PluginIds}; -use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext}; +use crate::common::errors::{ContextType, PluginContext}; +use crate::common::pty::PtyInstruction; +use crate::common::screen::ScreenInstruction; +use crate::common::thread_bus::{Bus, ThreadSenders}; +use crate::common::PaneId; #[derive(Clone, Debug)] pub enum PluginInstruction { @@ -25,14 +33,97 @@ pub enum PluginInstruction { #[derive(WasmerEnv, Clone)] pub struct PluginEnv { pub plugin_id: u32, - // FIXME: This should be a big bundle of all of the channels - pub send_screen_instructions: SenderWithContext<ScreenInstruction>, - pub send_pty_instructions: SenderWithContext<PtyInstruction>, - pub send_plugin_instructions: SenderWithContext<PluginInstruction>, + pub senders: ThreadSenders, pub wasi_env: WasiEnv, 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 --------------------------------------------------------------------------------------------------------- pub fn zellij_exports(store: &Store, plugin_env: &PluginEnv) -> ImportObject { @@ -74,8 +165,8 @@ fn host_unsubscribe(plugin_env: &PluginEnv) { fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { let selectable = selectable != 0; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetSelectable( + .senders + .send_to_screen(ScreenInstruction::SetSelectable( PaneId::Plugin(plugin_env.plugin_id), selectable, )) @@ -85,8 +176,8 @@ fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) { fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { let max_height = max_height as usize; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetMaxHeight( + .senders + .send_to_screen(ScreenInstruction::SetMaxHeight( PaneId::Plugin(plugin_env.plugin_id), max_height, )) @@ -96,8 +187,8 @@ fn host_set_max_height(plugin_env: &PluginEnv, max_height: i32) { fn host_set_invisible_borders(plugin_env: &PluginEnv, invisible_borders: i32) { let invisible_borders = invisible_borders != 0; plugin_env - .send_screen_instructions - .send(ScreenInstruction::SetInvisibleBorders( + .senders + .send_to_screen(ScreenInstruction::SetInvisibleBorders( PaneId::Plugin(plugin_env.plugin_id), invisible_borders, )) @@ -115,8 +206,8 @@ fn host_get_plugin_ids(plugin_env: &PluginEnv) { fn host_open_file(plugin_env: &PluginEnv) { let path: PathBuf = wasi_read_object(&plugin_env.wasi_env); plugin_env - .send_pty_instructions - .send(PtyInstruction::SpawnTerminal(Some(path))) + .senders + .send_to_pty(PtyInstruction::SpawnTerminal(Some(path))) .unwrap(); } @@ -130,7 +221,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { // timers as we'd like. // // 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); thread::spawn(move || { let start_time = Instant::now(); @@ -140,6 +231,7 @@ fn host_set_timeout(plugin_env: &PluginEnv, secs: f64) { let elapsed_time = Instant::now().duration_since(start_time).as_secs_f64(); send_plugin_instructions + .unwrap() .send(PluginInstruction::Update( update_target, Event::Timer(elapsed_time), |
