aboutsummaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
authora-kenji <aks.kenji@protonmail.com>2021-05-10 20:54:39 +0200
committera-kenji <aks.kenji@protonmail.com>2021-05-10 20:54:39 +0200
commit85a4e476f2011dc0491a15bcce3b0052ebb15adb (patch)
treeeb5d120250c5c3b0d74314a42129a857bf6b1afe /src/common
parentfixup! Add Option for Simplified Layout (diff)
parentMerge pull request #480 from a-kenji/dump-config-exit (diff)
downloadsl0ppij-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.rs18
-rw-r--r--src/common/input/actions.rs3
-rw-r--r--src/common/input/handler.rs5
-rw-r--r--src/common/mod.rs65
-rw-r--r--src/common/pty.rs (renamed from src/common/pty_bus.rs)165
-rw-r--r--src/common/screen.rs344
-rw-r--r--src/common/thread_bus.rs142
-rw-r--r--src/common/wasm_vm.rs138
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),