mirror of
https://github.com/zellij-org/zellij.git
synced 2024-12-25 02:06:19 +03:00
First round of merging against server-client and color stuff
This commit is contained in:
parent
7b5e728f9d
commit
9a3e8bcb84
@ -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.
|
||||
* 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.
|
||||
|
@ -16,7 +16,7 @@ use crate::common::{
|
||||
input::config::Config,
|
||||
input::handler::input_loop,
|
||||
os_input_output::ClientOsApi,
|
||||
SenderType, SenderWithContext, SyncChannelWithContext,
|
||||
thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext},
|
||||
};
|
||||
use crate::server::ServerInstruction;
|
||||
|
||||
|
@ -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};
|
||||
|
||||
|
@ -9,7 +9,7 @@ use crate::panes::grid::Grid;
|
||||
use crate::panes::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)]
|
||||
pub enum PaneId {
|
||||
|
@ -2,11 +2,12 @@
|
||||
//! as well as how they should be resized
|
||||
|
||||
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::os_input_output::ServerOsApi;
|
||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||
use crate::pty::{PtyInstruction, VteBytes};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::utils::shared::adjust_to_size;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
@ -69,9 +70,7 @@ pub struct Tab {
|
||||
full_screen_ws: PositionAndSize,
|
||||
fullscreen_is_active: bool,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
pub senders: ThreadSenders,
|
||||
synchronize_is_active: bool,
|
||||
should_clear_display_before_rendering: bool,
|
||||
pub mode_info: ModeInfo,
|
||||
@ -219,7 +218,7 @@ pub trait Pane {
|
||||
}
|
||||
|
||||
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)]
|
||||
pub fn new(
|
||||
index: usize,
|
||||
@ -227,9 +226,7 @@ impl Tab {
|
||||
name: String,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
mut os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
senders: ThreadSenders,
|
||||
max_panes: Option<usize>,
|
||||
pane_id: Option<PaneId>,
|
||||
mode_info: ModeInfo,
|
||||
@ -261,9 +258,7 @@ impl Tab {
|
||||
fullscreen_is_active: false,
|
||||
synchronize_is_active: false,
|
||||
os_api,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
senders,
|
||||
should_clear_display_before_rendering: false,
|
||||
mode_info,
|
||||
input_mode,
|
||||
@ -315,14 +310,14 @@ impl Tab {
|
||||
// Just a regular terminal
|
||||
if let Some(plugin) = &layout.plugin {
|
||||
let (pid_tx, pid_rx) = channel();
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Load(pid_tx, plugin.clone()))
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Load(pid_tx, plugin.clone()))
|
||||
.unwrap();
|
||||
let pid = pid_rx.recv().unwrap();
|
||||
let mut new_plugin = PluginPane::new(
|
||||
pid,
|
||||
*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 {
|
||||
new_plugin.set_max_height(max_rows);
|
||||
@ -332,8 +327,8 @@ impl Tab {
|
||||
}
|
||||
self.panes.insert(PaneId::Plugin(pid), Box::new(new_plugin));
|
||||
// Send an initial mode update to the newly loaded plugin only!
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(
|
||||
Some(pid),
|
||||
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
|
||||
// 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
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid)))
|
||||
.unwrap();
|
||||
}
|
||||
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() {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return; // likely no terminal large enough to split
|
||||
}
|
||||
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 = self.panes.get_mut(active_pane_id).unwrap();
|
||||
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
@ -538,9 +533,9 @@ impl Tab {
|
||||
let active_pane_id = &self.get_active_pane_id().unwrap();
|
||||
let active_pane = self.panes.get_mut(active_pane_id).unwrap();
|
||||
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
|
||||
.unwrap();
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
@ -597,7 +592,7 @@ impl Tab {
|
||||
}
|
||||
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
|
||||
// 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
|
||||
// if you're debugging seemingly randomly missing stdout data, this is
|
||||
// the reason
|
||||
@ -628,8 +623,8 @@ impl Tab {
|
||||
}
|
||||
PaneId::Plugin(pid) => {
|
||||
for key in parse_keys(&input_bytes) {
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(Some(pid), Event::KeyPress(key)))
|
||||
self.senders
|
||||
.send_to_plugin(PluginInstruction::Update(Some(pid), Event::KeyPress(key)))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@ -706,7 +701,7 @@ impl Tab {
|
||||
pub fn is_sync_panes_active(&self) -> bool {
|
||||
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;
|
||||
}
|
||||
pub fn panes_contain_widechar(&self) -> bool {
|
||||
@ -781,8 +776,8 @@ impl Tab {
|
||||
}
|
||||
}
|
||||
|
||||
self.send_server_instructions
|
||||
.send(ServerInstruction::Render(Some(output)))
|
||||
self.senders
|
||||
.send_to_server(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
}
|
||||
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 {
|
||||
let terminals = self.get_pane_ids();
|
||||
for &pid in terminals.iter().skip(max_panes - 1) {
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(pid))
|
||||
.unwrap();
|
||||
self.close_pane_without_rerender(pid);
|
||||
}
|
||||
@ -2198,8 +2193,8 @@ impl Tab {
|
||||
pub fn close_focused_pane(&mut self) {
|
||||
if let Some(active_pane_id) = self.get_active_pane_id() {
|
||||
self.close_pane(active_pane_id);
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::ClosePane(active_pane_id))
|
||||
self.senders
|
||||
.send_to_pty(PtyInstruction::ClosePane(active_pane_id))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
//! 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 super::{thread_bus::ASYNCOPENCALLS, thread_bus::OPENCALLS, ServerInstruction};
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::pty::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -14,7 +14,7 @@ use std::fmt::{Display, Error, Formatter};
|
||||
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`].
|
||||
@ -200,7 +200,7 @@ pub enum ScreenContext {
|
||||
PageScrollDown,
|
||||
ClearScroll,
|
||||
CloseFocusedPane,
|
||||
ToggleActiveSyncTab,
|
||||
ToggleActiveSyncPanes,
|
||||
ToggleActiveTerminalFullscreen,
|
||||
SetSelectable,
|
||||
SetInvisibleBorders,
|
||||
@ -261,7 +261,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
||||
ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize,
|
||||
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
|
||||
ScreenInstruction::ToggleActiveSyncTab => ScreenContext::ToggleActiveSyncTab,
|
||||
ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ pub enum Action {
|
||||
/// Toggle between fullscreen focus pane and normal layout.
|
||||
ToggleFocusFullscreen,
|
||||
/// 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).
|
||||
/// If no direction is specified, will try to use the biggest available space.
|
||||
NewPane(Option<Direction>),
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -4,20 +4,19 @@ 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 std::path::PathBuf;
|
||||
|
||||
use super::SenderWithContext;
|
||||
use crate::layout::Layout;
|
||||
use super::{screen::ScreenInstruction, thread_bus::SenderWithContext};
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::utils::logging::debug_to_file;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use crate::{
|
||||
errors::{get_current_ctx, ContextType, ErrorContext},
|
||||
common::thread_bus::Bus,
|
||||
errors::{get_current_ctx, ContextType, PtyContext},
|
||||
panes::PaneId,
|
||||
screen::ScreenInstruction,
|
||||
wasm_vm::PluginInstruction,
|
||||
};
|
||||
use crate::{layout::Layout, server::ServerInstruction};
|
||||
|
||||
pub struct ReadFromPid {
|
||||
pid: RawFd,
|
||||
@ -79,16 +78,69 @@ 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>,
|
||||
@ -124,9 +176,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 _ = send_screen_instructions.send(ScreenInstruction::Render);
|
||||
last_byte_receive_time = Some(Instant::now());
|
||||
} else {
|
||||
pending_render = true;
|
||||
@ -140,9 +190,7 @@ fn stream_terminal_bytes(
|
||||
} else {
|
||||
if pending_render {
|
||||
pending_render = false;
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
let _ = send_screen_instructions.send(ScreenInstruction::Render);
|
||||
}
|
||||
last_byte_receive_time = None;
|
||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
||||
@ -162,31 +210,26 @@ fn stream_terminal_bytes(
|
||||
})
|
||||
}
|
||||
|
||||
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.to_screen.as_ref().unwrap().clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.debug_to_file,
|
||||
);
|
||||
self.task_handles.insert(pid_primary, task_handle);
|
||||
@ -197,12 +240,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 +255,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.to_screen.as_ref().unwrap().clone(),
|
||||
self.bus.os_input.as_ref().unwrap().clone(),
|
||||
self.debug_to_file,
|
||||
);
|
||||
self.task_handles.insert(id, task_handle);
|
||||
@ -222,15 +267,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 +286,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 {
|
@ -3,15 +3,14 @@
|
||||
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::errors::{ContextType, ScreenContext};
|
||||
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::wasm_vm::PluginInstruction;
|
||||
use crate::{layout::Layout, panes::PaneId};
|
||||
|
||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
|
||||
@ -52,7 +51,7 @@ pub enum ScreenInstruction {
|
||||
NewTab(RawFd),
|
||||
SwitchTabNext,
|
||||
SwitchTabPrev,
|
||||
ToggleActiveSyncTab,
|
||||
ToggleActiveSyncPanes,
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
UpdateTabName(Vec<u8>),
|
||||
@ -63,55 +62,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 +109,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 +194,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 +265,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 +290,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 +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
142
src/common/thread_bus.rs
Normal 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()
|
||||
}
|
||||
}
|
@ -1,17 +1,28 @@
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
path::PathBuf,
|
||||
process,
|
||||
str::FromStr,
|
||||
sync::{mpsc::Sender, Arc, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||
use wasmer_wasi::WasiEnv;
|
||||
use wasmer::{
|
||||
imports, ChainableNamedResolver, Function, ImportObject, Instance, Module, Store, Value,
|
||||
WasmerEnv,
|
||||
};
|
||||
use wasmer_wasi::{Pipe, WasiEnv, WasiState};
|
||||
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)]
|
||||
pub enum PluginInstruction {
|
||||
@ -25,14 +36,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 +168,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 +179,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 +190,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 +209,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 +224,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 +234,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),
|
||||
|
@ -6,9 +6,7 @@ mod server;
|
||||
mod tests;
|
||||
|
||||
use client::{boundaries, layout, panes, start_client, tab};
|
||||
use common::{
|
||||
command_is_executing, errors, os_input_output, pty_bus, screen, setup, utils, wasm_vm,
|
||||
};
|
||||
use common::{command_is_executing, errors, os_input_output, pty, screen, setup, utils, wasm_vm};
|
||||
use server::start_server;
|
||||
use structopt::StructOpt;
|
||||
|
||||
|
@ -1,35 +1,29 @@
|
||||
pub mod route;
|
||||
|
||||
use interprocess::local_socket::LocalSocketListener;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::{collections::HashMap, fs};
|
||||
use std::{
|
||||
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 std::{path::PathBuf, sync::mpsc::channel};
|
||||
use wasmer::Store;
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::thread_bus::{Bus, ThreadSenders};
|
||||
use crate::common::{
|
||||
errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext},
|
||||
input::actions::{Action, Direction},
|
||||
input::handler::get_mode_info,
|
||||
errors::{ContextType, ServerContext},
|
||||
input::actions::Action,
|
||||
os_input_output::{set_permissions, ServerOsApi},
|
||||
pty_bus::{PtyBus, PtyInstruction},
|
||||
screen::{Screen, ScreenInstruction},
|
||||
pty::{pty_thread_main, Pty, PtyInstruction},
|
||||
screen::{screen_thread_main, ScreenInstruction},
|
||||
setup::install::populate_data_dir,
|
||||
thread_bus::{ChannelWithContext, SenderType, SenderWithContext},
|
||||
utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR},
|
||||
wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction},
|
||||
ChannelWithContext, SenderType, SenderWithContext,
|
||||
wasm_vm::{wasm_thread_main, PluginInstruction},
|
||||
};
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::PaneId;
|
||||
use crate::panes::PositionAndSize;
|
||||
use route::route_thread_main;
|
||||
|
||||
/// Instructions related to server-side application including the
|
||||
/// ones sent by client to server
|
||||
@ -43,10 +37,8 @@ pub enum ServerInstruction {
|
||||
ClientExit,
|
||||
}
|
||||
|
||||
struct SessionMetaData {
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
pub struct SessionMetaData {
|
||||
pub senders: ThreadSenders,
|
||||
screen_thread: Option<thread::JoinHandle<()>>,
|
||||
pty_thread: Option<thread::JoinHandle<()>>,
|
||||
wasm_thread: Option<thread::JoinHandle<()>>,
|
||||
@ -54,9 +46,9 @@ struct SessionMetaData {
|
||||
|
||||
impl Drop for SessionMetaData {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.send_pty_instructions.send(PtyInstruction::Exit);
|
||||
let _ = self.send_screen_instructions.send(ScreenInstruction::Exit);
|
||||
let _ = self.send_plugin_instructions.send(PluginInstruction::Exit);
|
||||
let _ = self.senders.send_to_pty(PtyInstruction::Exit);
|
||||
let _ = self.senders.send_to_screen(ScreenInstruction::Exit);
|
||||
let _ = self.senders.send_to_plugin(PluginInstruction::Exit);
|
||||
let _ = self.screen_thread.take().unwrap().join();
|
||||
let _ = self.pty_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<()> {
|
||||
let (send_server_instructions, receive_server_instructions): ChannelWithContext<
|
||||
ServerInstruction,
|
||||
> = channel();
|
||||
let send_server_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_server_instructions));
|
||||
let (to_server, server_receiver): ChannelWithContext<ServerInstruction> = channel();
|
||||
let to_server = SenderWithContext::new(SenderType::Sender(to_server));
|
||||
let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
#[cfg(test)]
|
||||
handle_client(
|
||||
sessions.clone(),
|
||||
os_input.clone(),
|
||||
send_server_instructions.clone(),
|
||||
);
|
||||
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)
|
||||
});
|
||||
#[cfg(not(test))]
|
||||
let _ = thread::Builder::new()
|
||||
.name("server_listener".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let sessions = sessions.clone();
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
let to_server = to_server.clone();
|
||||
move || {
|
||||
drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
|
||||
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();
|
||||
os_input.update_receiver(stream);
|
||||
let sessions = sessions.clone();
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
handle_client(sessions, os_input, send_server_instructions);
|
||||
let to_server = to_server.clone();
|
||||
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) => {
|
||||
panic!("err {:?}", err);
|
||||
@ -109,24 +111,20 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
||||
.name("server_thread".to_string())
|
||||
.spawn({
|
||||
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)));
|
||||
match instruction {
|
||||
ServerInstruction::NewClient(full_screen_ws, opts) => {
|
||||
let session_data = init_session(
|
||||
os_input.clone(),
|
||||
opts,
|
||||
send_server_instructions.clone(),
|
||||
full_screen_ws,
|
||||
);
|
||||
let session_data =
|
||||
init_session(os_input.clone(), opts, to_server.clone(), full_screen_ws);
|
||||
*sessions.write().unwrap() = Some(session_data);
|
||||
sessions
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.senders
|
||||
.send_to_pty(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::UnblockInputThread => {
|
||||
@ -148,65 +146,19 @@ pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
||||
.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(
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
opts: CliArgs,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
to_server: SenderWithContext<ServerInstruction>,
|
||||
full_screen_ws: PositionAndSize,
|
||||
) -> SessionMetaData {
|
||||
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||
ScreenInstruction,
|
||||
> = channel();
|
||||
let send_screen_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
|
||||
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> = channel();
|
||||
let to_screen = SenderWithContext::new(SenderType::Sender(to_screen));
|
||||
|
||||
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
|
||||
PluginInstruction,
|
||||
> = channel();
|
||||
let send_plugin_instructions =
|
||||
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));
|
||||
let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> = channel();
|
||||
let to_plugin = SenderWithContext::new(SenderType::Sender(to_plugin));
|
||||
let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = channel();
|
||||
let to_pty = SenderWithContext::new(SenderType::Sender(to_pty));
|
||||
|
||||
// Determine and initialize the data directory
|
||||
let data_dir = opts
|
||||
@ -224,322 +176,40 @@ fn init_session(
|
||||
.map(|p| Layout::new(&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()
|
||||
.name("pty".to_string())
|
||||
.spawn({
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = pty_bus
|
||||
.receive_pty_instructions
|
||||
.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_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
let pty = Pty::new(
|
||||
Bus::new(
|
||||
pty_receiver,
|
||||
Some(&to_screen),
|
||||
None,
|
||||
Some(&to_plugin),
|
||||
None,
|
||||
Some(os_input.clone()),
|
||||
),
|
||||
opts.debug,
|
||||
);
|
||||
|
||||
move || pty_thread_main(pty, maybe_layout)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let screen_thread = thread::Builder::new()
|
||||
.name("screen".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_server_instructions = send_server_instructions;
|
||||
let screen_bus = Bus::new(
|
||||
screen_receiver,
|
||||
None,
|
||||
Some(&to_pty),
|
||||
Some(&to_plugin),
|
||||
Some(&to_server),
|
||||
Some(os_input.clone()),
|
||||
);
|
||||
let max_panes = opts.max_panes;
|
||||
let colors = os_input.load_palette();
|
||||
|
||||
move || {
|
||||
let mut screen = Screen::new(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
screen_thread_main(screen_bus, max_panes, full_screen_ws);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
@ -547,265 +217,28 @@ fn init_session(
|
||||
let wasm_thread = thread::Builder::new()
|
||||
.name("wasm".to_string())
|
||||
.spawn({
|
||||
let send_screen_instructions = send_screen_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
|
||||
let plugin_bus = Bus::new(
|
||||
plugin_receiver,
|
||||
Some(&to_screen),
|
||||
Some(&to_pty),
|
||||
Some(&to_plugin),
|
||||
Some(&to_server),
|
||||
None,
|
||||
);
|
||||
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
|
||||
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,
|
||||
}
|
||||
}
|
||||
move || wasm_thread_main(plugin_bus, store, data_dir)
|
||||
})
|
||||
.unwrap();
|
||||
SessionMetaData {
|
||||
send_plugin_instructions,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
senders: ThreadSenders {
|
||||
to_screen: Some(to_screen),
|
||||
to_pty: Some(to_pty),
|
||||
to_plugin: Some(to_plugin),
|
||||
to_server: None,
|
||||
},
|
||||
screen_thread: Some(screen_thread),
|
||||
pty_thread: Some(pty_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
205
src/server/route.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ use std::sync::{mpsc, Arc, Condvar, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::{ChannelWithContext, SenderType, SenderWithContext};
|
||||
use crate::common::thread_bus::{ChannelWithContext, SenderType, SenderWithContext};
|
||||
use crate::errors::ErrorContext;
|
||||
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
||||
use crate::server::ServerInstruction;
|
||||
|
Loading…
Reference in New Issue
Block a user