aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKunal Mohan <kunalmohan99@gmail.com>2021-02-11 21:15:38 +0530
committerKunal Mohan <kunalmohan99@gmail.com>2021-05-04 20:47:48 +0530
commit588cdaa00894a9ae46e6a293f73ba62d5b1df18f (patch)
treea75863550610cbea67d4804ef15c26712603674c /src
parentdocs(changelog): update change (diff)
downloadsl0ppij-588cdaa00894a9ae46e6a293f73ba62d5b1df18f.tar.gz
sl0ppij-588cdaa00894a9ae46e6a293f73ba62d5b1df18f.tar.xz
sl0ppij-588cdaa00894a9ae46e6a293f73ba62d5b1df18f.zip
isolate pty thread
Diffstat (limited to 'src')
-rw-r--r--src/cli.rs2
-rw-r--r--src/client/panes/terminal_pane.rs4
-rw-r--r--src/client/tab.rs99
-rw-r--r--src/common/errors.rs21
-rw-r--r--src/common/input/handler.rs60
-rw-r--r--src/common/mod.rs253
-rw-r--r--src/common/pty_bus.rs217
-rw-r--r--src/common/screen.rs13
-rw-r--r--src/common/wasm_vm.rs14
-rw-r--r--src/main.rs22
-rw-r--r--src/server/mod.rs201
11 files changed, 557 insertions, 349 deletions
diff --git a/src/cli.rs b/src/cli.rs
index bc4e4a9e..d4c1c704 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,7 +2,7 @@ use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV
use std::path::PathBuf;
use structopt::StructOpt;
-#[derive(StructOpt, Default, Debug)]
+#[derive(StructOpt, Debug, Default, Clone)]
#[structopt(name = "zellij")]
pub struct CliArgs {
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
diff --git a/src/client/panes/terminal_pane.rs b/src/client/panes/terminal_pane.rs
index a5a957e6..1834e722 100644
--- a/src/client/panes/terminal_pane.rs
+++ b/src/client/panes/terminal_pane.rs
@@ -1,6 +1,8 @@
use crate::tab::Pane;
use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd;
+use ::vte::Perform;
+use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::time::Instant;
@@ -10,7 +12,7 @@ use crate::panes::terminal_character::{
};
use crate::pty_bus::VteBytes;
-#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
+#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum PaneId {
Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
diff --git a/src/client/tab.rs b/src/client/tab.rs
index b01e04e5..b87926b7 100644
--- a/src/client/tab.rs
+++ b/src/client/tab.rs
@@ -69,7 +69,6 @@ pub struct Tab {
fullscreen_is_active: bool,
synchronize_is_active: bool,
os_api: Box<dyn OsApi>,
- pub send_pty_instructions: SenderWithContext<PtyInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>,
should_clear_display_before_rendering: bool,
@@ -226,7 +225,6 @@ impl Tab {
name: String,
full_screen_ws: &PositionAndSize,
mut os_api: Box<dyn OsApi>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
max_panes: Option<usize>,
@@ -261,7 +259,6 @@ impl Tab {
synchronize_is_active: false,
os_api,
send_app_instructions,
- send_pty_instructions,
send_plugin_instructions,
should_clear_display_before_rendering: false,
mode_info,
@@ -354,8 +351,10 @@ 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.send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(
+ PaneId::Terminal(*unused_pid),
+ )))
.unwrap();
}
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
@@ -399,8 +398,8 @@ impl Tab {
},
);
if terminal_id_to_split.is_none() {
- self.send_pty_instructions
- .send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty
+ self.send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
.unwrap();
return; // likely no terminal large enough to split
}
@@ -475,24 +474,25 @@ impl Tab {
self.panes.insert(pid, Box::new(new_terminal));
self.active_terminal = Some(pid);
}
- } else if let PaneId::Terminal(term_pid) = pid {
- // TODO: check minimum size of active terminal
- 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)) // we can't open this pane, close the pty
- .unwrap();
- return;
- }
- let terminal_ws = PositionAndSize {
- x: active_pane.x(),
- y: active_pane.y(),
- rows: active_pane.rows(),
- columns: active_pane.columns(),
- ..Default::default()
- };
- let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
+ } else {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ // TODO: check minimum size of active terminal
+ 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_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
+ .unwrap();
+ return;
+ }
+ let terminal_ws = PositionAndSize {
+ x: active_pane.x(),
+ y: active_pane.y(),
+ rows: active_pane.rows(),
+ columns: active_pane.columns(),
+ };
+ let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
active_pane.change_pos_and_size(&top_winsize);
@@ -532,26 +532,25 @@ impl Tab {
self.panes.insert(pid, Box::new(new_terminal));
self.active_terminal = Some(pid);
}
- } else if let PaneId::Terminal(term_pid) = pid {
- // TODO: check minimum size of active terminal
- 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)) // we can't open this pane, close the pty
- .unwrap();
- return;
- }
- let terminal_ws = PositionAndSize {
- x: active_pane.x(),
- y: active_pane.y(),
- rows: active_pane.rows(),
- columns: active_pane.columns(),
- ..Default::default()
- };
- let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
-
- active_pane.change_pos_and_size(&left_winsize);
+ } else {
+ // FIXME: This could use a second look
+ if let PaneId::Terminal(term_pid) = pid {
+ // TODO: check minimum size of active terminal
+ 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_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
+ .unwrap();
+ return;
+ }
+ let terminal_ws = PositionAndSize {
+ x: active_pane.x(),
+ y: active_pane.y(),
+ rows: active_pane.rows(),
+ columns: active_pane.columns(),
+ };
+ let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
let new_terminal = TerminalPane::new(term_pid, right_winsize);
self.os_api.set_terminal_size_using_fd(
@@ -2105,8 +2104,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.send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid)))
.unwrap();
self.close_pane_without_rerender(pid);
}
@@ -2217,8 +2216,10 @@ 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.send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::ClosePane(
+ active_pane_id,
+ )))
.unwrap();
}
}
diff --git a/src/common/errors.rs b/src/common/errors.rs
index c1c6753f..a9156ae6 100644
--- a/src/common/errors.rs
+++ b/src/common/errors.rs
@@ -4,6 +4,7 @@
use super::{AppInstruction, ASYNCOPENCALLS, OPENCALLS};
use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction;
+use serde::{Deserialize, Serialize};
use std::fmt::{Display, Error, Formatter};
@@ -79,7 +80,7 @@ pub fn get_current_ctx() -> ErrorContext {
}
/// A representation of the call stack.
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ErrorContext {
calls: [ContextType; MAX_THREAD_CALL_STACK],
}
@@ -131,7 +132,7 @@ impl Display for ErrorContext {
/// Complex variants store a variant of a related enum, whose variants can be built from
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
/// [`PtyInstruction`], [`AppInstruction`], etc).
-#[derive(Copy, Clone, PartialEq)]
+#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum ContextType {
/// A screen-related call.
Screen(ScreenContext),
@@ -141,7 +142,7 @@ pub enum ContextType {
Plugin(PluginContext),
/// An app-related call.
App(AppContext),
- IpcServer,
+ IPCServer, // Fix: Create a separate ServerContext when sessions are introduced
StdinHandler,
AsyncTask,
/// An empty, placeholder call. This should be thought of as representing no call at all.
@@ -174,7 +175,7 @@ impl Display for ContextType {
// FIXME: Just deriving EnumDiscriminants from strum will remove the need for any of this!!!
/// Stack call representations corresponding to the different types of [`ScreenInstruction`]s.
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ScreenContext {
HandlePtyBytes,
Render,
@@ -267,7 +268,7 @@ impl From<&ScreenInstruction> for ScreenContext {
}
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s.
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PtyContext {
SpawnTerminal,
SpawnTerminalVertically,
@@ -297,7 +298,7 @@ impl From<&PtyInstruction> for PtyContext {
use crate::wasm_vm::PluginInstruction;
/// Stack call representations corresponding to the different types of [`PluginInstruction`]s.
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PluginContext {
Load,
Update,
@@ -319,10 +320,13 @@ impl From<&PluginInstruction> for PluginContext {
}
/// Stack call representations corresponding to the different types of [`AppInstruction`]s.
-#[derive(Debug, Clone, Copy, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum AppContext {
Exit,
Error,
+ ToPty,
+ ToPlugin,
+ ToScreen,
}
impl From<&AppInstruction> for AppContext {
@@ -330,6 +334,9 @@ impl From<&AppInstruction> for AppContext {
match *app_instruction {
AppInstruction::Exit => AppContext::Exit,
AppInstruction::Error(_) => AppContext::Error,
+ AppInstruction::ToPty(_) => AppContext::ToPty,
+ AppInstruction::ToPlugin(_) => AppContext::ToPlugin,
+ AppInstruction::ToScreen(_) => AppContext::ToScreen,
}
}
}
diff --git a/src/common/input/handler.rs b/src/common/input/handler.rs
index 7304225c..f8f07f53 100644
--- a/src/common/input/handler.rs
+++ b/src/common/input/handler.rs
@@ -23,7 +23,6 @@ struct InputHandler {
config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
should_exit: bool,
@@ -36,7 +35,6 @@ impl InputHandler {
command_is_executing: CommandIsExecuting,
config: Config,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) -> Self {
@@ -46,7 +44,6 @@ impl InputHandler {
config,
command_is_executing,
send_screen_instructions,
- send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
should_exit: false,
@@ -58,25 +55,34 @@ impl InputHandler {
fn handle_input(&mut self) {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler);
- let alt_left_bracket = vec![27, 91];
- loop {
- if self.should_exit {
- break;
- }
- let stdin_buffer = self.os_input.read_from_stdin();
- for key_result in stdin_buffer.events_and_raw() {
- match key_result {
- Ok((event, raw_bytes)) => match event {
- termion::event::Event::Key(key) => {
- let key = cast_termion_key(key);
- self.handle_key(&key, raw_bytes);
- }
- termion::event::Event::Unsupported(unsupported_key) => {
- // we have to do this because of a bug in termion
- // this should be a key event and not an unsupported event
- if unsupported_key == alt_left_bracket {
- let key = Key::Alt('[');
- self.handle_key(&key, raw_bytes);
+ self.send_app_instructions.update(err_ctx);
+ self.send_screen_instructions.update(err_ctx);
+ if let Ok(keybinds) = get_default_keybinds() {
+ 'input_loop: loop {
+ //@@@ I think this should actually just iterate over stdin directly
+ let stdin_buffer = self.os_input.read_from_stdin();
+ for key_result in stdin_buffer.events_and_raw() {
+ match key_result {
+ Ok((event, raw_bytes)) => match event {
+ termion::event::Event::Key(key) => {
+ let key = cast_termion_key(key);
+ // FIXME this explicit break is needed because the current test
+ // framework relies on it to not create dead threads that loop
+ // and eat up CPUs. Do not remove until the test framework has
+ // been revised. Sorry about this (@categorille)
+ let mut should_break = false;
+ for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
+ {
+ should_break |= self.dispatch_action(action);
+ }
+ if should_break {
+ break 'input_loop;
+ }
+ }
+ termion::event::Event::Mouse(_)
+ | termion::event::Event::Unsupported(_) => {
+ // Mouse and unsupported events aren't implemented yet,
+ // use a NoOp untill then.
}
}
termion::event::Event::Mouse(_) => {
@@ -220,7 +226,9 @@ impl InputHandler {
None => PtyInstruction::SpawnTerminal(None),
};
self.command_is_executing.opening_new_pane();
- self.send_pty_instructions.send(pty_instr).unwrap();
+ self.send_app_instructions
+ .send(AppInstruction::ToPty(pty_instr))
+ .unwrap();
self.command_is_executing.wait_until_new_pane_is_opened();
}
Action::CloseFocus => {
@@ -232,8 +240,8 @@ impl InputHandler {
}
Action::NewTab => {
self.command_is_executing.opening_new_pane();
- self.send_pty_instructions
- .send(PtyInstruction::NewTab)
+ self.send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::NewTab))
.unwrap();
self.command_is_executing.wait_until_new_pane_is_opened();
}
@@ -332,7 +340,6 @@ pub fn input_loop(
config: Config,
command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
) {
@@ -341,7 +348,6 @@ pub fn input_loop(
command_is_executing,
config,
send_screen_instructions,
- send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
)
diff --git a/src/common/mod.rs b/src/common/mod.rs
index 17c1f4cb..61897df0 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -9,8 +9,9 @@ pub mod setup;
pub mod utils;
pub mod wasm_vm;
-use std::cell::RefCell;
-use std::path::PathBuf;
+use std::io::Write;
+use std::os::unix::net::UnixStream;
+use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::thread;
use std::{collections::HashMap, fs};
@@ -23,19 +24,12 @@ use std::{
};
use crate::cli::CliArgs;
-use crate::common::input::config::Config;
-use crate::layout::Layout;
-use crate::panes::PaneId;
-use async_std::task_local;
+use crate::server::start_server;
use command_is_executing::CommandIsExecuting;
-use directories_next::ProjectDirs;
-use errors::{
- get_current_ctx, AppContext, ContextType, ErrorContext, PluginContext, PtyContext,
- ScreenContext,
-};
+use errors::{AppContext, ContextType, ErrorContext, PluginContext, ScreenContext};
use input::handler::input_loop;
use os_input_output::OsApi;
-use pty_bus::{PtyBus, PtyInstruction};
+use pty_bus::PtyInstruction;
use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize};
use setup::install;
@@ -45,12 +39,36 @@ use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{EventType, InputMode, ModeInfo};
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ApiCommand {
OpenFile(PathBuf),
SplitHorizontally,
SplitVertically,
MoveFocus,
+ ToPty(PtyInstruction),
+ ToScreen(ScreenInstruction),
+ ClosePluginPane(u32),
+ Quit,
+}
+
+// FIXME: It would be good to add some more things to this over time
+#[derive(Debug, Clone, Default)]
+pub struct AppState {
+ pub input_mode: InputMode,
+}
+
+// FIXME: Make this a method on the big `Communication` struct, so that app_tx can be extracted
+// from self instead of being explicitly passed here
+pub fn update_state(
+ app_tx: &SenderWithContext<AppInstruction>,
+ update_fn: impl FnOnce(AppState) -> AppState,
+) {
+ let (state_tx, state_rx) = mpsc::channel();
+
+ drop(app_tx.send(AppInstruction::GetState(state_tx)));
+ let state = state_rx.recv().unwrap();
+
+ drop(app_tx.send(AppInstruction::SetState(update_fn(state))))
}
/// An [MPSC](mpsc) asynchronous channel with added error context.
@@ -66,7 +84,7 @@ pub type SyncChannelWithContext<T> = (
/// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
#[derive(Clone)]
-enum SenderType<T: 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`].
@@ -81,8 +99,8 @@ pub struct SenderWithContext<T: Clone> {
}
impl<T: Clone> SenderWithContext<T> {
- fn new(sender: SenderType<T>) -> Self {
- Self { sender }
+ pub fn new(err_ctx: ErrorContext, sender: SenderType<T>) -> Self {
+ Self { err_ctx, sender }
}
/// Sends an event, along with the current [`ErrorContext`], on this
@@ -116,6 +134,9 @@ task_local! {
pub enum AppInstruction {
Exit,
Error(String),
+ ToPty(PtyInstruction),
+ ToScreen(ScreenInstruction),
+ ToPlugin(PluginInstruction),
}
/// Start Zellij with the specified [`OsApi`] and command-line arguments.
@@ -140,10 +161,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let send_screen_instructions =
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
- let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
- mpsc::channel();
- let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions));
-
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
PluginInstruction,
> = mpsc::channel();
@@ -155,31 +172,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let send_app_instructions =
SenderWithContext::new(SenderType::SyncSender(send_app_instructions));
- let mut pty_bus = PtyBus::new(
- receive_pty_instructions,
- send_screen_instructions.clone(),
- send_plugin_instructions.clone(),
+ let ipc_thread = start_server(
os_input.clone(),
- opts.debug,
+ opts.clone(),
+ command_is_executing.clone(),
+ send_app_instructions.clone(),
);
- // Determine and initialize the data directory
- let project_dirs = ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
- let data_dir = opts
- .data_dir
- .unwrap_or_else(|| project_dirs.data_dir().to_path_buf());
- install::populate_data_dir(&data_dir);
-
- // Don't use default layouts in tests, but do everywhere else
- #[cfg(not(test))]
- let default_layout = Some(PathBuf::from("default"));
- #[cfg(test)]
- let default_layout = None;
- let maybe_layout = opts
- .layout
- .map(|p| Layout::new(&p, &data_dir))
- .or_else(|| default_layout.map(|p| Layout::from_defaults(&p, &data_dir)));
-
#[cfg(not(test))]
std::panic::set_hook({
use crate::errors::handle_panic;
@@ -189,72 +188,11 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
})
});
- let pty_thread = thread::Builder::new()
- .name("pty".to_string())
- .spawn({
- let mut command_is_executing = command_is_executing.clone();
- send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
- 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);
- command_is_executing.done_closing_pane();
- }
- PtyInstruction::CloseTab(ids) => {
- pty_bus.close_tab(ids);
- command_is_executing.done_closing_pane();
- }
- PtyInstruction::Quit => {
- break;
- }
- }
- }
- })
- .unwrap();
-
let screen_thread = thread::Builder::new()
.name("screen".to_string())
.spawn({
let mut command_is_executing = command_is_executing.clone();
let os_input = os_input.clone();
- let send_pty_instructions = send_pty_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let max_panes = opts.max_panes;
@@ -262,7 +200,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
move || {
let mut screen = Screen::new(
receive_screen_instructions,
- send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
&full_screen_ws,
@@ -281,6 +218,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
+ screen.send_app_instructions.update(err_ctx);
match event {
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
let active_tab = screen.get_active_tab_mut().unwrap();
@@ -460,10 +398,8 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let wasm_thread = thread::Builder::new()
.name("wasm".to_string())
.spawn({
- let send_pty_instructions = send_pty_instructions.clone();
- let send_screen_instructions = send_screen_instructions.clone();
- let send_app_instructions = send_app_instructions.clone();
- let send_plugin_instructions = send_plugin_instructions.clone();
+ let mut send_screen_instructions = send_screen_instructions.clone();
+ let mut send_app_instructions = send_app_instructions.clone();
let store = Store::default();
let mut plugin_id = 0;
@@ -473,6 +409,8 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.recv()
.expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
+ send_screen_instructions.update(err_ctx);
+ send_app_instructions.update(err_ctx);
match event {
PluginInstruction::Load(pid_tx, path) => {
let plugin_dir = data_dir.join("plugins/");
@@ -505,7 +443,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let plugin_env = PluginEnv {
plugin_id,
- send_pty_instructions: send_pty_instructions.clone(),
send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(),
send_plugin_instructions: send_plugin_instructions.clone(),
@@ -556,83 +493,10 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
})
.unwrap();
- let _signal_thread = thread::Builder::new()
- .name("signal_listener".to_string())
- .spawn({
- let os_input = os_input.clone();
- let send_screen_instructions = send_screen_instructions.clone();
- move || {
- os_input.receive_sigwinch(Box::new(move || {
- let _ = send_screen_instructions.send(ScreenInstruction::TerminalResize);
- }));
- }
- })
- .unwrap();
-
- // TODO: currently we don't wait for this to quit
- // because otherwise the app will hang. Need to fix this so it both
- // listens to the ipc-bus and is able to quit cleanly
- #[cfg(not(test))]
- let _ipc_thread = thread::Builder::new()
- .name("ipc_server".to_string())
- .spawn({
- use std::io::Read;
- let send_pty_instructions = send_pty_instructions.clone();
- let send_screen_instructions = send_screen_instructions.clone();
- move || {
- std::fs::remove_file(ZELLIJ_IPC_PIPE).ok();
- let listener = std::os::unix::net::UnixListener::bind(ZELLIJ_IPC_PIPE)
- .expect("could not listen on ipc socket");
- let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
- err_ctx.add_call(ContextType::IpcServer);
-
- for stream in listener.incoming() {
- match stream {
- Ok(mut stream) => {
- let mut buffer = [0; 65535]; // TODO: more accurate
- let _ = stream
- .read(&mut buffer)
- .expect("failed to parse ipc message");
- let decoded: ApiCommand = bincode::deserialize(&buffer)
- .expect("failed to deserialize ipc message");
- match &decoded {
- ApiCommand::OpenFile(file_name) => {
- let path = PathBuf::from(file_name);
- send_pty_instructions
- .send(PtyInstruction::SpawnTerminal(Some(path)))
- .unwrap();
- }
- ApiCommand::SplitHorizontally => {
- send_pty_instructions
- .send(PtyInstruction::SpawnTerminalHorizontally(None))
- .unwrap();
- }
- ApiCommand::SplitVertically => {
- send_pty_instructions
- .send(PtyInstruction::SpawnTerminalVertically(None))
- .unwrap();
- }
- ApiCommand::MoveFocus => {
- send_screen_instructions
- .send(ScreenInstruction::FocusNextPane)
- .unwrap();
- }
- }
- }
- Err(err) => {
- panic!("err {:?}", err);
- }
- }
- }
- }
- })
- .unwrap();
-
let _stdin_thread = thread::Builder::new()
.name("stdin_handler".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 os_input = os_input.clone();
let config = config;
@@ -642,13 +506,14 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
config,
command_is_executing,
send_screen_instructions,
- send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
)
}
});
+ let mut server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
+
#[warn(clippy::never_loop)]
loop {
let (app_instruction, mut err_ctx) = receive_app_instructions
@@ -656,15 +521,17 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
+ send_screen_instructions.update(err_ctx);
match app_instruction {
AppInstruction::Exit => {
break;
}
AppInstruction::Error(backtrace) => {
+ let api_command = bincode::serialize(&(err_ctx, ApiCommand::Quit)).unwrap();
+ server_stream.write_all(&api_command).unwrap();
+ let _ = ipc_thread.join();
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = screen_thread.join();
- let _ = send_pty_instructions.send(PtyInstruction::Quit);
- let _ = pty_thread.join();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
let _ = wasm_thread.join();
os_input.unset_raw_mode(0);
@@ -680,11 +547,23 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.unwrap();
std::process::exit(1);
}
+ AppInstruction::ToScreen(instruction) => {
+ send_screen_instructions.send(instruction).unwrap();
+ }
+ AppInstruction::ToPlugin(instruction) => {
+ send_plugin_instructions.send(instruction).unwrap();
+ }
+ AppInstruction::ToPty(instruction) => {
+ let api_command =
+ bincode::serialize(&(err_ctx, ApiCommand::ToPty(instruction))).unwrap();
+ server_stream.write_all(&api_command).unwrap();
+ }
}
}
- let _ = send_pty_instructions.send(PtyInstruction::Quit);
- pty_thread.join().unwrap();
+ let api_command = bincode::serialize(&(err_ctx, ApiCommand::Quit)).unwrap();
+ server_stream.write_all(&api_command).unwrap();
+ let _ = ipc_thread.join().unwrap();
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
screen_thread.join().unwrap();
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
diff --git a/src/common/pty_bus.rs b/src/common/pty_bus.rs
index 2c132228..108c04fd 100644
--- a/src/common/pty_bus.rs
+++ b/src/common/pty_bus.rs
@@ -6,16 +6,21 @@ use ::std::os::unix::io::RawFd;
use ::std::pin::*;
use ::std::sync::mpsc::Receiver;
use ::std::time::{Duration, Instant};
+use ::vte;
+use serde::{Deserialize, Serialize};
+use std::io::Write;
+use std::os::unix::net::UnixStream;
use std::path::PathBuf;
-use super::{ScreenInstruction, SenderWithContext};
+use super::{ScreenInstruction, OPENCALLS};
+use crate::layout::Layout;
use crate::os_input_output::OsApi;
-use crate::utils::logging::debug_to_file;
+use crate::utils::{consts::ZELLIJ_IPC_PIPE, logging::debug_to_file};
use crate::{
- errors::{get_current_ctx, ContextType, ErrorContext},
+ common::ApiCommand,
+ errors::{ContextType, ErrorContext},
panes::PaneId,
};
-use crate::{layout::Layout, wasm_vm::PluginInstruction};
pub struct ReadFromPid {
pid: RawFd,
@@ -63,10 +68,114 @@ impl Stream for ReadFromPid {
}
}
-pub type VteBytes = Vec<u8>;
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum VteEvent {
+ // TODO: try not to allocate Vecs
+ Print(char),
+ Execute(u8), // byte
+ Hook(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
+ Put(u8), // byte
+ Unhook,
+ OscDispatch(Vec<Vec<u8>>, bool), // params, bell_terminated
+ CsiDispatch(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
+ EscDispatch(Vec<u8>, bool, u8), // intermediates, ignore, byte
+}
+
+struct VteEventSender {
+ id: RawFd,
+ err_ctx: ErrorContext,
+ server_stream: UnixStream,
+}
+
+impl VteEventSender {
+ pub fn new(id: RawFd, err_ctx: ErrorContext) -> Self {
+ VteEventSender {
+ id,
+ err_ctx,
+ server_stream: UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(),
+ }
+ }
+}
+
+impl vte::Perform for VteEventSender {
+ fn print(&mut self, c: char) {
+ let api_command = bincode::serialize(&(
+ self.err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Print(c))),
+ ))
+ .unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+ fn execute(&mut self, byte: u8) {
+ let api_command = bincode::serialize(&(
+ self.err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Execute(byte))),
+ ))
+ .unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
+ let params = params.iter().copied().collect();
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::Hook(params, intermediates, ignore, c));
+ let api_command =
+ bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn put(&mut self, byte: u8) {
+ let api_command = bincode::serialize(&(
+ self.err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Put(byte))),
+ ))
+ .unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn unhook(&mut self) {
+ let api_command = bincode::serialize(&(
+ self.err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Pty(self.id, VteEvent::Unhook)),
+ ))
+ .unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
+ let params = params.iter().map(|p| p.to_vec()).collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::OscDispatch(params, bell_terminated));
+ let api_command =
+ bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, c: char) {
+ let params = params.iter().copied().collect();
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction = ScreenInstruction::Pty(
+ self.id,
+ VteEvent::CsiDispatch(params, intermediates, ignore, c),
+ );
+ let api_command =
+ bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
+ let intermediates = intermediates.iter().copied().collect();
+ let instruction =
+ ScreenInstruction::Pty(self.id, VteEvent::EscDispatch(intermediates, ignore, byte));
+ let api_command =
+ bincode::serialize(&(self.err_ctx, ApiCommand::ToScreen(instruction))).unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
+}
/// Instructions related to PTYs (pseudoterminals).
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PtyInstruction {
SpawnTerminal(Option<PathBuf>),
SpawnTerminalVertically(Option<PathBuf>),
@@ -78,25 +187,22 @@ pub enum PtyInstruction {
}
pub struct PtyBus {
- pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
- pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
pub id_to_child_pid: HashMap<RawFd, RawFd>,
os_input: Box<dyn OsApi>,
debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>,
+ pub server_stream: UnixStream,
}
-fn stream_terminal_bytes(
- pid: RawFd,
- send_screen_instructions: SenderWithContext<ScreenInstruction>,
- os_input: Box<dyn OsApi>,
- debug: bool,
-) -> JoinHandle<()> {
- let mut err_ctx = get_current_ctx();
+fn stream_terminal_bytes(pid: RawFd, os_input: Box<dyn OsApi>, debug: bool) -> JoinHandle<()> {
+ let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
task::spawn({
async move {
err_ctx.add_call(ContextType::AsyncTask);
+ let mut server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
+ let mut vte_parser = vte::Parser::new();
+ let mut vte_event_sender = VteEventSender::new(pid, err_ctx);
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
let mut last_byte_receive_time: Option<Instant> = None;
@@ -122,7 +228,12 @@ fn stream_terminal_bytes(
Some(receive_time) => {
if receive_time.elapsed() > max_render_pause {
pending_render = false;
- let _ = send_screen_instructions.send(ScreenInstruction::Render);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Render),
+ ))
+ .unwrap();
+ server_stream.write_all(&api_command).unwrap();
last_byte_receive_time = Some(Instant::now());
} else {
pending_render = true;
@@ -136,22 +247,31 @@ fn stream_terminal_bytes(
} else {
if pending_render {
pending_render = false;
- let _ = send_screen_instructions.send(ScreenInstruction::Render);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::Render),
+ ))
+ .unwrap();
+ server_stream.write_all(&api_command).unwrap();
}
last_byte_receive_time = None;
task::sleep(::std::time::Duration::from_millis(10)).await;
}
}
- send_screen_instructions
- .send(ScreenInstruction::Render)
- .unwrap();
+ let api_command =
+ bincode::serialize(&(err_ctx, ApiCommand::ToScreen(ScreenInstruction::Render)))
+ .unwrap();
+ server_stream.write_all(&api_command).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)))
- .unwrap();
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::ClosePane(PaneId::Terminal(pid))),
+ ))
+ .unwrap();
+ server_stream.write_all(&api_command).unwrap();
}
})
}
@@ -159,35 +279,29 @@ 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 OsApi>,
+ server_stream: UnixStream,
debug_to_file: bool,
) -> Self {
PtyBus {
- send_screen_instructions,
- send_plugin_instructions,
receive_pty_instructions,
os_input,
id_to_child_pid: HashMap::new(),
debug_to_file,
task_handles: HashMap::new(),
+ server_stream,
}
}
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 task_handle = stream_terminal_bytes(
- pid_primary,
- self.send_screen_instructions.clone(),
- self.os_input.clone(),
- self.debug_to_file,
- );
+ let task_handle =
+ stream_terminal_bytes(pid_primary, self.os_input.clone(), self.debug_to_file);
self.task_handles.insert(pid_primary, task_handle);
self.id_to_child_pid.insert(pid_primary, pid_secondary);
pid_primary
}
- pub fn spawn_terminals_for_layout(&mut self, layout: Layout) {
+ pub fn spawn_terminals_for_layout(&mut self, layout: Layout, err_ctx: ErrorContext) {
let total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![];
for _ in 0..total_panes {
@@ -195,23 +309,21 @@ impl PtyBus {
self.id_to_child_pid.insert(pid_primary, pid_secondary);
new_pane_pids.push(pid_primary);
}
- self.send_screen_instructions
- .send(ScreenInstruction::ApplyLayout((
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::ApplyLayout((
layout,
new_pane_pids.clone(),
- )))
- .unwrap();
+ ))),
+ ))
+ .unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
for id in new_pane_pids {
- let task_handle = stream_terminal_bytes(
- id,
- self.send_screen_instructions.clone(),
- self.os_input.clone(),
- self.debug_to_file,
- );
+ let task_handle = stream_terminal_bytes(id, self.os_input.clone(), self.debug_to_file);
self.task_handles.insert(id, task_handle);
}
}
- pub fn close_pane(&mut self, id: PaneId) {
+ pub fn close_pane(&mut self, id: PaneId, err_ctx: ErrorContext) {
match id {
PaneId::Terminal(id) => {
let child_pid = self.id_to_child_pid.remove(&id).unwrap();
@@ -221,15 +333,16 @@ impl PtyBus {
handle.cancel().await;
});
}
- PaneId::Plugin(pid) => drop(
- self.send_plugin_instructions
- .send(PluginInstruction::Unload(pid)),
- ),
+ PaneId::Plugin(pid) => {
+ let api_command =
+ bincode::serialize(&(err_ctx, ApiCommand::ClosePluginPane(pid))).unwrap();
+ self.server_stream.write_all(&api_command).unwrap();
+ }
}
}
- pub fn close_tab(&mut self, ids: Vec<PaneId>) {
+ pub fn close_tab(&mut self, ids: Vec<PaneId>, err_ctx: ErrorContext) {
ids.iter().for_each(|&id| {
- self.close_pane(id);
+ self.close_pane(id, err_ctx);
});
}
}
@@ -238,7 +351,7 @@ impl Drop for PtyBus {
fn drop(&mut self) {
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
for id in child_ids {
- self.close_pane(PaneId::Terminal(id));
+ self.close_pane(PaneId::Terminal(id), ErrorContext::new());
}
}
}
diff --git a/src/common/screen.rs b/src/common/screen.rs
index e6cf73f5..b6b8e7f2 100644
--- a/src/common/screen.rs
+++ b/src/common/screen.rs
@@ -1,5 +1,6 @@
//! Things related to [`Screen`]s.
+use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::os::unix::io::RawFd;
use std::str;
@@ -16,7 +17,7 @@ use crate::{layout::Layout, panes::PaneId};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
/// Instructions that can be sent to the [`Screen`].
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScreenInstruction {
PtyBytes(RawFd, VteBytes),
Render,
@@ -68,8 +69,6 @@ pub struct Screen {
max_panes: Option<usize>,
/// A map between this [`Screen`]'s tabs and their ID/key.
tabs: BTreeMap<usize, Tab>,
- /// A [`PtyInstruction`] and [`ErrorContext`] sender.
- pub send_pty_instructions: SenderWithContext<PtyInstruction>,
/// A [`PluginInstruction`] and [`ErrorContext`] sender.
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
/// An [`AppInstruction`] and [`ErrorContext`] sender.
@@ -91,7 +90,6 @@ impl Screen {
#[allow(clippy::too_many_arguments)]
pub fn new(
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
- send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>,
full_screen_ws: &PositionAndSize,
@@ -104,7 +102,6 @@ impl Screen {
Screen {
receiver: receive_screen_instructions,
max_panes,
- send_pty_instructions,
send_plugin_instructions,
send_app_instructions,
full_screen_ws: *full_screen_ws,
@@ -128,7 +125,6 @@ impl Screen {
String::new(),
&self.full_screen_ws,
self.os_api.clone(),
- self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(),
self.max_panes,
@@ -215,8 +211,8 @@ impl Screen {
// 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
let _ = self
- .send_pty_instructions
- .send(PtyInstruction::CloseTab(pane_ids));
+ .send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::CloseTab(pane_ids)));
if self.tabs.is_empty() {
self.active_tab_index = None;
self.send_app_instructions
@@ -285,7 +281,6 @@ impl Screen {
String::new(),
&self.full_screen_ws,
self.os_api.clone(),
- self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(),
self.max_panes,
diff --git a/src/common/wasm_vm.rs b/src/common/wasm_vm.rs
index bd98a85b..ddb7deaf 100644
--- a/src/common/wasm_vm.rs
+++ b/src/common/wasm_vm.rs
@@ -29,9 +29,7 @@ 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_app_instructions: SenderWithContext<AppInstruction>,
- pub send_pty_instructions: SenderWithContext<PtyInstruction>,
- pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ pub send_app_instructions: SenderWithContext<AppInstruction>, // FIXME: This should be a big bundle of all of the channels
pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
}
@@ -74,6 +72,16 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
subscriptions.retain(|k| !old.contains(k));
}
+fn host_open_file(plugin_env: &PluginEnv) {
+ let path = PathBuf::from(wasi_stdout(&plugin_env.wasi_env).lines().next().unwrap());
+ plugin_env
+ .send_app_instructions
+ .send(AppInstruction::ToPty(PtyInstruction::SpawnTerminal(Some(
+ path,
+ ))))
+ .unwrap();
+}
+
fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
let selectable = selectable != 0;
plugin_env
diff --git a/src/main.rs b/src/main.rs
index ece55fa2..5fa9317e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,11 @@
mod cli;
-mod common;
-#[cfg(test)]
-mod tests;
-// TODO mod server;
mod client;
+mod common;
+mod server;
use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting;
-use crate::common::input::config::Config;
+use crate::errors::ErrorContext;
use crate::os_input_output::get_os_input;
use crate::utils::{
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
@@ -36,23 +34,29 @@ pub fn main() {
match split_dir {
'h' => {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
- let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap();
+ let api_command =
+ bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitHorizontally))
+ .unwrap();
stream.write_all(&api_command).unwrap();
}
'v' => {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
- let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap();
+ let api_command =
+ bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitVertically))
+ .unwrap();
stream.write_all(&api_command).unwrap();
}
_ => {}
};
} else if opts.move_focus {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
- let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap();
+ let api_command =
+ bincode::serialize(&(ErrorContext::new(), ApiCommand::MoveFocus)).unwrap();
stream.write_all(&api_command).unwrap();
} else if let Some(file_to_open) = opts.open_file {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
- let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap();
+ let api_command =
+ bincode::serialize(&(ErrorContext::new(), ApiCommand::OpenFile(file_to_open))).unwrap();
stream.write_all(&api_command).unwrap();
} else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
let shell = match shell.as_ref() {
diff --git a/src/server/mod.rs b/src/server/mod.rs
index 626b4e6b..3db2ab52 100644
--- a/src/server/mod.rs
+++ b/src/server/mod.rs
@@ -1,5 +1,198 @@
-use super::super::common::{screen};
+use crate::cli::CliArgs;
+use crate::command_is_executing::CommandIsExecuting;
+use crate::common::{
+ ApiCommand, AppInstruction, ChannelWithContext, SenderType, SenderWithContext,
+};
+use crate::errors::{ContextType, ErrorContext, PtyContext};
+use crate::layout::Layout;
+use crate::os_input_output::OsApi;
+use crate::panes::PaneId;
+use crate::pty_bus::{PtyBus, PtyInstruction};
+use crate::screen::ScreenInstruction;
+use crate::utils::consts::ZELLIJ_IPC_PIPE;
+use crate::wasm_vm::PluginInstruction;
+use std::io::{Read, Write};
+use std::os::unix::net::UnixStream;
+use std::path::PathBuf;
+use std::sync::mpsc::channel;
+use std::thread;
-pub fn start_server() {
- // TODO
-} \ No newline at end of file
+pub fn start_server(
+ os_input: Box<dyn OsApi>,
+ opts: CliArgs,
+ command_is_executing: CommandIsExecuting,
+ mut send_app_instructions: SenderWithContext<AppInstruction>,
+) -> thread::JoinHandle<()> {
+ let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
+ channel();
+ let mut send_pty_instructions = SenderWithContext::new(
+ ErrorContext::new(),
+ SenderType::Sender(send_pty_instructions),
+ );
+
+ std::fs::remove_file(ZELLIJ_IPC_PIPE).ok();
+ let listener = std::os::unix::net::UnixListener::bind(ZELLIJ_IPC_PIPE)
+ .expect("could not listen on ipc socket");
+
+ // Don't use default layouts in tests, but do everywhere else
+ #[cfg(not(test))]
+ let default_layout = Some(PathBuf::from("default"));
+ #[cfg(test)]
+ let default_layout = None;
+ let maybe_layout = opts.layout.or(default_layout).map(Layout::new);
+
+ let server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
+ let mut pty_bus = PtyBus::new(
+ receive_pty_instructions,
+ os_input.clone(),
+ server_stream,
+ opts.debug,
+ );
+
+ let pty_thread = thread::Builder::new()
+ .name("pty".to_string())
+ .spawn({
+ let mut command_is_executing = command_is_executing.clone();
+ send_pty_instructions.send(PtyInstruction::NewTab).unwrap();
+ 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);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::NewPane(PaneId::Terminal(pid))),
+ ))
+ .unwrap();
+ pty_bus.server_stream.write_all(&api_command).unwrap();
+ }
+ PtyInstruction::SpawnTerminalVertically(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::VerticalSplit(
+ PaneId::Terminal(pid),
+ )),
+ ))
+ .unwrap();
+ pty_bus.server_stream.write_all(&api_command).unwrap();
+ }
+ PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
+ let pid = pty_bus.spawn_terminal(file_to_open);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::HorizontalSplit(
+ PaneId::Terminal(pid),
+ )),
+ ))
+ .unwrap();
+ pty_bus.server_stream.write_all(&api_command).unwrap();
+ }
+ PtyInstruction::NewTab => {
+ if let Some(layout) = maybe_layout.clone() {
+ pty_bus.spawn_terminals_for_layout(layout, err_ctx);
+ } else {
+ let pid = pty_bus.spawn_terminal(None);
+ let api_command = bincode::serialize(&(
+ err_ctx,
+ ApiCommand::ToScreen(ScreenInstruction::NewTab(pid)),
+ ))
+ .unwrap();
+ pty_bus.server_stream.write_all(&api_command).unwrap();
+ }
+ }
+ PtyInstruction::ClosePane(id) => {
+ pty_bus.close_pane(id, err_ctx);
+ command_is_executing.done_closing_pane();
+ }
+ PtyInstruction::CloseTab(ids) => {
+ pty_bus.close_tab(ids, err_ctx);
+ command_is_executing.done_closing_pane();
+ }
+ PtyInstruction::Quit => {
+ break;
+ }
+ }
+ }
+ })
+ .unwrap();
+
+ thread::Builder::new()
+ .name("ipc_server".to_string())
+ .spawn({
+ move || {
+ for stream in listener.incoming() {
+ match stream {
+ Ok(mut stream) => {
+ let mut buffer = [0; 65535]; // TODO: more accurate
+ let bytes = stream
+ .read(&mut buffer)
+ .expect("failed to parse ipc message");
+ println!("{}\n\n", bytes);
+ let (mut err_ctx, decoded): (ErrorContext, ApiCommand) =
+ bincode::deserialize(&buffer[0..bytes])
+ .expect("failed to deserialize ipc message");
+ err_ctx.add_call(ContextType::IPCServer);
+ send_pty_instructions.update(err_ctx);
+ send_app_instructions.update(err_ctx);
+
+ match decoded {
+ ApiCommand::OpenFile(file_name) => {
+ let path = PathBuf::from(file_name);
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminal(Some(path)))
+ .unwrap();
+ }
+ ApiCommand::SplitHorizontally => {
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminalHorizontally(None))
+ .unwrap();
+ }
+ ApiCommand::SplitVertically => {
+ send_pty_instructions
+ .send(PtyInstruction::SpawnTerminalVertically(None))
+ .unwrap();
+ }
+ ApiCommand::MoveFocus => {
+ send_app_instructions
+ .send(AppInstruction::ToScreen(
+ ScreenInstruction::MoveFocus,
+ ))
+ .unwrap();
+ }
+ ApiCommand::ToPty(instruction) => {
+ send_pty_instructions.send(instruction).unwrap();
+ }
+ ApiCommand::ToScreen(instruction) => {
+ send_app_instructions
+ .send(AppInstruction::ToScreen(instruction))
+ .unwrap();
+ }
+ ApiCommand::ClosePluginPane(pid) => {
+ send_app_instructions
+ .send(AppInstruction::ToPlugin(PluginInstruction::Unload(
+ pid,
+ )))
+ .unwrap();
+ }
+ ApiCommand::Quit => {
+ send_pty_instructions.send(PtyInstruction::Quit).unwrap();
+ break;
+ }
+ }
+ }
+ Err(err) => {
+ panic!("err {:?}", err);
+ }
+ }
+ }
+
+ let _ = pty_thread.join();
+ }
+ })
+ .unwrap()
+}