isolate pty thread

This commit is contained in:
Kunal Mohan 2021-02-11 21:15:38 +05:30
parent 03f8e7220c
commit 588cdaa008
11 changed files with 557 additions and 349 deletions

View file

@ -2,7 +2,7 @@ use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV
use std::path::PathBuf; use std::path::PathBuf;
use structopt::StructOpt; use structopt::StructOpt;
#[derive(StructOpt, Default, Debug)] #[derive(StructOpt, Debug, Default, Clone)]
#[structopt(name = "zellij")] #[structopt(name = "zellij")]
pub struct CliArgs { pub struct CliArgs {
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session /// Send "split (direction h == horizontal / v == vertical)" to active zellij session

View file

@ -1,6 +1,8 @@
use crate::tab::Pane; use crate::tab::Pane;
use ::nix::pty::Winsize; use ::nix::pty::Winsize;
use ::std::os::unix::io::RawFd; use ::std::os::unix::io::RawFd;
use ::vte::Perform;
use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::time::Instant; use std::time::Instant;
@ -10,7 +12,7 @@ use crate::panes::terminal_character::{
}; };
use crate::pty_bus::VteBytes; 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 { pub enum PaneId {
Terminal(RawFd), Terminal(RawFd),
Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct? Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?

View file

@ -69,7 +69,6 @@ pub struct Tab {
fullscreen_is_active: bool, fullscreen_is_active: bool,
synchronize_is_active: bool, synchronize_is_active: bool,
os_api: Box<dyn OsApi>, os_api: Box<dyn OsApi>,
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>, pub send_app_instructions: SenderWithContext<AppInstruction>,
should_clear_display_before_rendering: bool, should_clear_display_before_rendering: bool,
@ -226,7 +225,6 @@ impl Tab {
name: String, name: String,
full_screen_ws: &PositionAndSize, full_screen_ws: &PositionAndSize,
mut os_api: Box<dyn OsApi>, mut os_api: Box<dyn OsApi>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>, send_app_instructions: SenderWithContext<AppInstruction>,
max_panes: Option<usize>, max_panes: Option<usize>,
@ -261,7 +259,6 @@ impl Tab {
synchronize_is_active: false, synchronize_is_active: false,
os_api, os_api,
send_app_instructions, send_app_instructions,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
should_clear_display_before_rendering: false, should_clear_display_before_rendering: false,
mode_info, 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 // this is a bit of a hack and happens because we don't have any central location that
// can query the screen as to how many panes it needs to create a layout // can query the screen as to how many panes it needs to create a layout
// fixing this will require a bit of an architecture change // fixing this will require a bit of an architecture change
self.send_pty_instructions self.send_app_instructions
.send(PtyInstruction::ClosePane(PaneId::Terminal(*unused_pid))) .send(AppInstruction::ToPty(PtyInstruction::ClosePane(
PaneId::Terminal(*unused_pid),
)))
.unwrap(); .unwrap();
} }
self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next(); self.active_terminal = self.panes.iter().map(|(id, _)| id.to_owned()).next();
@ -399,8 +398,8 @@ impl Tab {
}, },
); );
if terminal_id_to_split.is_none() { if terminal_id_to_split.is_none() {
self.send_pty_instructions self.send_app_instructions
.send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
.unwrap(); .unwrap();
return; // likely no terminal large enough to split return; // likely no terminal large enough to split
} }
@ -475,24 +474,25 @@ impl Tab {
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
self.active_terminal = Some(pid); self.active_terminal = Some(pid);
} }
} else if let PaneId::Terminal(term_pid) = pid { } else {
// TODO: check minimum size of active terminal // FIXME: This could use a second look
let active_pane_id = &self.get_active_pane_id().unwrap(); if let PaneId::Terminal(term_pid) = pid {
let active_pane = self.panes.get_mut(active_pane_id).unwrap(); // TODO: check minimum size of active terminal
if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 { let active_pane_id = &self.get_active_pane_id().unwrap();
self.send_pty_instructions let active_pane = self.panes.get_mut(active_pane_id).unwrap();
.send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty if active_pane.rows() < MIN_TERMINAL_HEIGHT * 2 + 1 {
.unwrap(); self.send_app_instructions
return; .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
} .unwrap();
let terminal_ws = PositionAndSize { return;
x: active_pane.x(), }
y: active_pane.y(), let terminal_ws = PositionAndSize {
rows: active_pane.rows(), x: active_pane.x(),
columns: active_pane.columns(), y: active_pane.y(),
..Default::default() rows: active_pane.rows(),
}; columns: active_pane.columns(),
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws); };
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&terminal_ws);
active_pane.change_pos_and_size(&top_winsize); active_pane.change_pos_and_size(&top_winsize);
@ -532,26 +532,25 @@ impl Tab {
self.panes.insert(pid, Box::new(new_terminal)); self.panes.insert(pid, Box::new(new_terminal));
self.active_terminal = Some(pid); self.active_terminal = Some(pid);
} }
} else if let PaneId::Terminal(term_pid) = pid { } else {
// TODO: check minimum size of active terminal // FIXME: This could use a second look
let active_pane_id = &self.get_active_pane_id().unwrap(); if let PaneId::Terminal(term_pid) = pid {
let active_pane = self.panes.get_mut(active_pane_id).unwrap(); // TODO: check minimum size of active terminal
if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 { let active_pane_id = &self.get_active_pane_id().unwrap();
self.send_pty_instructions let active_pane = self.panes.get_mut(active_pane_id).unwrap();
.send(PtyInstruction::ClosePane(pid)) // we can't open this pane, close the pty if active_pane.columns() < MIN_TERMINAL_WIDTH * 2 + 1 {
.unwrap(); self.send_app_instructions
return; .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid))) // we can't open this pane, close the pty
} .unwrap();
let terminal_ws = PositionAndSize { return;
x: active_pane.x(), }
y: active_pane.y(), let terminal_ws = PositionAndSize {
rows: active_pane.rows(), x: active_pane.x(),
columns: active_pane.columns(), y: active_pane.y(),
..Default::default() rows: active_pane.rows(),
}; columns: active_pane.columns(),
let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws); };
let (left_winsize, right_winsize) = split_vertically_with_gap(&terminal_ws);
active_pane.change_pos_and_size(&left_winsize);
let new_terminal = TerminalPane::new(term_pid, right_winsize); let new_terminal = TerminalPane::new(term_pid, right_winsize);
self.os_api.set_terminal_size_using_fd( self.os_api.set_terminal_size_using_fd(
@ -2105,8 +2104,8 @@ impl Tab {
if let Some(max_panes) = self.max_panes { if let Some(max_panes) = self.max_panes {
let terminals = self.get_pane_ids(); let terminals = self.get_pane_ids();
for &pid in terminals.iter().skip(max_panes - 1) { for &pid in terminals.iter().skip(max_panes - 1) {
self.send_pty_instructions self.send_app_instructions
.send(PtyInstruction::ClosePane(pid)) .send(AppInstruction::ToPty(PtyInstruction::ClosePane(pid)))
.unwrap(); .unwrap();
self.close_pane_without_rerender(pid); self.close_pane_without_rerender(pid);
} }
@ -2217,8 +2216,10 @@ impl Tab {
pub fn close_focused_pane(&mut self) { pub fn close_focused_pane(&mut self) {
if let Some(active_pane_id) = self.get_active_pane_id() { if let Some(active_pane_id) = self.get_active_pane_id() {
self.close_pane(active_pane_id); self.close_pane(active_pane_id);
self.send_pty_instructions self.send_app_instructions
.send(PtyInstruction::ClosePane(active_pane_id)) .send(AppInstruction::ToPty(PtyInstruction::ClosePane(
active_pane_id,
)))
.unwrap(); .unwrap();
} }
} }

View file

@ -4,6 +4,7 @@
use super::{AppInstruction, ASYNCOPENCALLS, OPENCALLS}; use super::{AppInstruction, ASYNCOPENCALLS, OPENCALLS};
use crate::pty_bus::PtyInstruction; use crate::pty_bus::PtyInstruction;
use crate::screen::ScreenInstruction; use crate::screen::ScreenInstruction;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Error, Formatter}; use std::fmt::{Display, Error, Formatter};
@ -79,7 +80,7 @@ pub fn get_current_ctx() -> ErrorContext {
} }
/// A representation of the call stack. /// A representation of the call stack.
#[derive(Clone, Copy)] #[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ErrorContext { pub struct ErrorContext {
calls: [ContextType; MAX_THREAD_CALL_STACK], 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 /// Complex variants store a variant of a related enum, whose variants can be built from
/// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`], /// the corresponding Zellij MSPC instruction enum variants ([`ScreenInstruction`],
/// [`PtyInstruction`], [`AppInstruction`], etc). /// [`PtyInstruction`], [`AppInstruction`], etc).
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum ContextType { pub enum ContextType {
/// A screen-related call. /// A screen-related call.
Screen(ScreenContext), Screen(ScreenContext),
@ -141,7 +142,7 @@ pub enum ContextType {
Plugin(PluginContext), Plugin(PluginContext),
/// An app-related call. /// An app-related call.
App(AppContext), App(AppContext),
IpcServer, IPCServer, // Fix: Create a separate ServerContext when sessions are introduced
StdinHandler, StdinHandler,
AsyncTask, AsyncTask,
/// An empty, placeholder call. This should be thought of as representing no call at all. /// 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!!! // 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. /// 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 { pub enum ScreenContext {
HandlePtyBytes, HandlePtyBytes,
Render, Render,
@ -267,7 +268,7 @@ impl From<&ScreenInstruction> for ScreenContext {
} }
/// Stack call representations corresponding to the different types of [`PtyInstruction`]s. /// 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 { pub enum PtyContext {
SpawnTerminal, SpawnTerminal,
SpawnTerminalVertically, SpawnTerminalVertically,
@ -297,7 +298,7 @@ impl From<&PtyInstruction> for PtyContext {
use crate::wasm_vm::PluginInstruction; use crate::wasm_vm::PluginInstruction;
/// Stack call representations corresponding to the different types of [`PluginInstruction`]s. /// 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 { pub enum PluginContext {
Load, Load,
Update, Update,
@ -319,10 +320,13 @@ impl From<&PluginInstruction> for PluginContext {
} }
/// Stack call representations corresponding to the different types of [`AppInstruction`]s. /// 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 { pub enum AppContext {
Exit, Exit,
Error, Error,
ToPty,
ToPlugin,
ToScreen,
} }
impl From<&AppInstruction> for AppContext { impl From<&AppInstruction> for AppContext {
@ -330,6 +334,9 @@ impl From<&AppInstruction> for AppContext {
match *app_instruction { match *app_instruction {
AppInstruction::Exit => AppContext::Exit, AppInstruction::Exit => AppContext::Exit,
AppInstruction::Error(_) => AppContext::Error, AppInstruction::Error(_) => AppContext::Error,
AppInstruction::ToPty(_) => AppContext::ToPty,
AppInstruction::ToPlugin(_) => AppContext::ToPlugin,
AppInstruction::ToScreen(_) => AppContext::ToScreen,
} }
} }
} }

View file

@ -23,7 +23,6 @@ struct InputHandler {
config: Config, config: Config,
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>, send_app_instructions: SenderWithContext<AppInstruction>,
should_exit: bool, should_exit: bool,
@ -36,7 +35,6 @@ impl InputHandler {
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
config: Config, config: Config,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>, send_app_instructions: SenderWithContext<AppInstruction>,
) -> Self { ) -> Self {
@ -46,7 +44,6 @@ impl InputHandler {
config, config,
command_is_executing, command_is_executing,
send_screen_instructions, send_screen_instructions,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
send_app_instructions, send_app_instructions,
should_exit: false, should_exit: false,
@ -58,25 +55,34 @@ impl InputHandler {
fn handle_input(&mut self) { fn handle_input(&mut self) {
let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow()); let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
err_ctx.add_call(ContextType::StdinHandler); err_ctx.add_call(ContextType::StdinHandler);
let alt_left_bracket = vec![27, 91]; self.send_app_instructions.update(err_ctx);
loop { self.send_screen_instructions.update(err_ctx);
if self.should_exit { if let Ok(keybinds) = get_default_keybinds() {
break; 'input_loop: loop {
} //@@@ I think this should actually just iterate over stdin directly
let stdin_buffer = self.os_input.read_from_stdin(); let stdin_buffer = self.os_input.read_from_stdin();
for key_result in stdin_buffer.events_and_raw() { for key_result in stdin_buffer.events_and_raw() {
match key_result { match key_result {
Ok((event, raw_bytes)) => match event { Ok((event, raw_bytes)) => match event {
termion::event::Event::Key(key) => { termion::event::Event::Key(key) => {
let key = cast_termion_key(key); let key = cast_termion_key(key);
self.handle_key(&key, raw_bytes); // FIXME this explicit break is needed because the current test
} // framework relies on it to not create dead threads that loop
termion::event::Event::Unsupported(unsupported_key) => { // and eat up CPUs. Do not remove until the test framework has
// we have to do this because of a bug in termion // been revised. Sorry about this (@categorille)
// this should be a key event and not an unsupported event let mut should_break = false;
if unsupported_key == alt_left_bracket { for action in key_to_actions(&key, raw_bytes, &self.mode, &keybinds)
let key = Key::Alt('['); {
self.handle_key(&key, raw_bytes); 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(_) => { termion::event::Event::Mouse(_) => {
@ -220,7 +226,9 @@ impl InputHandler {
None => PtyInstruction::SpawnTerminal(None), None => PtyInstruction::SpawnTerminal(None),
}; };
self.command_is_executing.opening_new_pane(); 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(); self.command_is_executing.wait_until_new_pane_is_opened();
} }
Action::CloseFocus => { Action::CloseFocus => {
@ -232,8 +240,8 @@ impl InputHandler {
} }
Action::NewTab => { Action::NewTab => {
self.command_is_executing.opening_new_pane(); self.command_is_executing.opening_new_pane();
self.send_pty_instructions self.send_app_instructions
.send(PtyInstruction::NewTab) .send(AppInstruction::ToPty(PtyInstruction::NewTab))
.unwrap(); .unwrap();
self.command_is_executing.wait_until_new_pane_is_opened(); self.command_is_executing.wait_until_new_pane_is_opened();
} }
@ -332,7 +340,6 @@ pub fn input_loop(
config: Config, config: Config,
command_is_executing: CommandIsExecuting, command_is_executing: CommandIsExecuting,
send_screen_instructions: SenderWithContext<ScreenInstruction>, send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>, send_app_instructions: SenderWithContext<AppInstruction>,
) { ) {
@ -341,7 +348,6 @@ pub fn input_loop(
command_is_executing, command_is_executing,
config, config,
send_screen_instructions, send_screen_instructions,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
send_app_instructions, send_app_instructions,
) )

View file

@ -9,8 +9,9 @@ pub mod setup;
pub mod utils; pub mod utils;
pub mod wasm_vm; pub mod wasm_vm;
use std::cell::RefCell; use std::io::Write;
use std::path::PathBuf; use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::sync::mpsc; use std::sync::mpsc;
use std::thread; use std::thread;
use std::{collections::HashMap, fs}; use std::{collections::HashMap, fs};
@ -23,19 +24,12 @@ use std::{
}; };
use crate::cli::CliArgs; use crate::cli::CliArgs;
use crate::common::input::config::Config; use crate::server::start_server;
use crate::layout::Layout;
use crate::panes::PaneId;
use async_std::task_local;
use command_is_executing::CommandIsExecuting; use command_is_executing::CommandIsExecuting;
use directories_next::ProjectDirs; use errors::{AppContext, ContextType, ErrorContext, PluginContext, ScreenContext};
use errors::{
get_current_ctx, AppContext, ContextType, ErrorContext, PluginContext, PtyContext,
ScreenContext,
};
use input::handler::input_loop; use input::handler::input_loop;
use os_input_output::OsApi; use os_input_output::OsApi;
use pty_bus::{PtyBus, PtyInstruction}; use pty_bus::PtyInstruction;
use screen::{Screen, ScreenInstruction}; use screen::{Screen, ScreenInstruction};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use setup::install; use setup::install;
@ -45,12 +39,36 @@ use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
use wasmer_wasi::{Pipe, WasiState}; use wasmer_wasi::{Pipe, WasiState};
use zellij_tile::data::{EventType, InputMode, ModeInfo}; use zellij_tile::data::{EventType, InputMode, ModeInfo};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ApiCommand { pub enum ApiCommand {
OpenFile(PathBuf), OpenFile(PathBuf),
SplitHorizontally, SplitHorizontally,
SplitVertically, SplitVertically,
MoveFocus, 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. /// 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`]. /// Wrappers around the two standard [MPSC](mpsc) sender types, [`mpsc::Sender`] and [`mpsc::SyncSender`], with an additional [`ErrorContext`].
#[derive(Clone)] #[derive(Clone)]
enum SenderType<T: Clone> { pub enum SenderType<T: Clone> {
/// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`]. /// A wrapper around an [`mpsc::Sender`], adding an [`ErrorContext`].
Sender(mpsc::Sender<(T, ErrorContext)>), Sender(mpsc::Sender<(T, ErrorContext)>),
/// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`]. /// A wrapper around an [`mpsc::SyncSender`], adding an [`ErrorContext`].
@ -81,8 +99,8 @@ pub struct SenderWithContext<T: Clone> {
} }
impl<T: Clone> SenderWithContext<T> { impl<T: Clone> SenderWithContext<T> {
fn new(sender: SenderType<T>) -> Self { pub fn new(err_ctx: ErrorContext, sender: SenderType<T>) -> Self {
Self { sender } Self { err_ctx, sender }
} }
/// Sends an event, along with the current [`ErrorContext`], on this /// Sends an event, along with the current [`ErrorContext`], on this
@ -116,6 +134,9 @@ task_local! {
pub enum AppInstruction { pub enum AppInstruction {
Exit, Exit,
Error(String), Error(String),
ToPty(PtyInstruction),
ToScreen(ScreenInstruction),
ToPlugin(PluginInstruction),
} }
/// Start Zellij with the specified [`OsApi`] and command-line arguments. /// 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 = let send_screen_instructions =
SenderWithContext::new(SenderType::Sender(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< let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
PluginInstruction, PluginInstruction,
> = mpsc::channel(); > = mpsc::channel();
@ -155,31 +172,13 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
let send_app_instructions = let send_app_instructions =
SenderWithContext::new(SenderType::SyncSender(send_app_instructions)); SenderWithContext::new(SenderType::SyncSender(send_app_instructions));
let mut pty_bus = PtyBus::new( let ipc_thread = start_server(
receive_pty_instructions,
send_screen_instructions.clone(),
send_plugin_instructions.clone(),
os_input.clone(), 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))] #[cfg(not(test))]
std::panic::set_hook({ std::panic::set_hook({
use crate::errors::handle_panic; 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() let screen_thread = thread::Builder::new()
.name("screen".to_string()) .name("screen".to_string())
.spawn({ .spawn({
let mut command_is_executing = command_is_executing.clone(); let mut command_is_executing = command_is_executing.clone();
let os_input = os_input.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_plugin_instructions = send_plugin_instructions.clone();
let send_app_instructions = send_app_instructions.clone(); let send_app_instructions = send_app_instructions.clone();
let max_panes = opts.max_panes; let max_panes = opts.max_panes;
@ -262,7 +200,6 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
move || { move || {
let mut screen = Screen::new( let mut screen = Screen::new(
receive_screen_instructions, receive_screen_instructions,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
send_app_instructions, send_app_instructions,
&full_screen_ws, &full_screen_ws,
@ -281,6 +218,7 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.recv() .recv()
.expect("failed to receive event on channel"); .expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event))); err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
screen.send_app_instructions.update(err_ctx);
match event { match event {
ScreenInstruction::PtyBytes(pid, vte_bytes) => { ScreenInstruction::PtyBytes(pid, vte_bytes) => {
let active_tab = screen.get_active_tab_mut().unwrap(); 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() let wasm_thread = thread::Builder::new()
.name("wasm".to_string()) .name("wasm".to_string())
.spawn({ .spawn({
let send_pty_instructions = send_pty_instructions.clone(); let mut send_screen_instructions = send_screen_instructions.clone();
let send_screen_instructions = send_screen_instructions.clone(); let mut send_app_instructions = send_app_instructions.clone();
let send_app_instructions = send_app_instructions.clone();
let send_plugin_instructions = send_plugin_instructions.clone();
let store = Store::default(); let store = Store::default();
let mut plugin_id = 0; let mut plugin_id = 0;
@ -473,6 +409,8 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.recv() .recv()
.expect("failed to receive event on channel"); .expect("failed to receive event on channel");
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event))); err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
send_screen_instructions.update(err_ctx);
send_app_instructions.update(err_ctx);
match event { match event {
PluginInstruction::Load(pid_tx, path) => { PluginInstruction::Load(pid_tx, path) => {
let plugin_dir = data_dir.join("plugins/"); 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 { let plugin_env = PluginEnv {
plugin_id, plugin_id,
send_pty_instructions: send_pty_instructions.clone(),
send_screen_instructions: send_screen_instructions.clone(), send_screen_instructions: send_screen_instructions.clone(),
send_app_instructions: send_app_instructions.clone(), send_app_instructions: send_app_instructions.clone(),
send_plugin_instructions: send_plugin_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(); .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() let _stdin_thread = thread::Builder::new()
.name("stdin_handler".to_string()) .name("stdin_handler".to_string())
.spawn({ .spawn({
let send_screen_instructions = send_screen_instructions.clone(); 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 send_plugin_instructions = send_plugin_instructions.clone();
let os_input = os_input.clone(); let os_input = os_input.clone();
let config = config; let config = config;
@ -642,13 +506,14 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
config, config,
command_is_executing, command_is_executing,
send_screen_instructions, send_screen_instructions,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
send_app_instructions, send_app_instructions,
) )
} }
}); });
let mut server_stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
#[warn(clippy::never_loop)] #[warn(clippy::never_loop)]
loop { loop {
let (app_instruction, mut err_ctx) = receive_app_instructions 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"); .expect("failed to receive app instruction on channel");
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction))); err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
send_screen_instructions.update(err_ctx);
match app_instruction { match app_instruction {
AppInstruction::Exit => { AppInstruction::Exit => {
break; break;
} }
AppInstruction::Error(backtrace) => { 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 _ = send_screen_instructions.send(ScreenInstruction::Quit);
let _ = screen_thread.join(); let _ = screen_thread.join();
let _ = send_pty_instructions.send(PtyInstruction::Quit);
let _ = pty_thread.join();
let _ = send_plugin_instructions.send(PluginInstruction::Quit); let _ = send_plugin_instructions.send(PluginInstruction::Quit);
let _ = wasm_thread.join(); let _ = wasm_thread.join();
os_input.unset_raw_mode(0); os_input.unset_raw_mode(0);
@ -680,11 +547,23 @@ pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
.unwrap(); .unwrap();
std::process::exit(1); 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); let api_command = bincode::serialize(&(err_ctx, ApiCommand::Quit)).unwrap();
pty_thread.join().unwrap(); server_stream.write_all(&api_command).unwrap();
let _ = ipc_thread.join().unwrap();
let _ = send_screen_instructions.send(ScreenInstruction::Quit); let _ = send_screen_instructions.send(ScreenInstruction::Quit);
screen_thread.join().unwrap(); screen_thread.join().unwrap();
let _ = send_plugin_instructions.send(PluginInstruction::Quit); let _ = send_plugin_instructions.send(PluginInstruction::Quit);

View file

@ -6,16 +6,21 @@ use ::std::os::unix::io::RawFd;
use ::std::pin::*; use ::std::pin::*;
use ::std::sync::mpsc::Receiver; use ::std::sync::mpsc::Receiver;
use ::std::time::{Duration, Instant}; 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 std::path::PathBuf;
use super::{ScreenInstruction, SenderWithContext}; use super::{ScreenInstruction, OPENCALLS};
use crate::layout::Layout;
use crate::os_input_output::OsApi; 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::{ use crate::{
errors::{get_current_ctx, ContextType, ErrorContext}, common::ApiCommand,
errors::{ContextType, ErrorContext},
panes::PaneId, panes::PaneId,
}; };
use crate::{layout::Layout, wasm_vm::PluginInstruction};
pub struct ReadFromPid { pub struct ReadFromPid {
pid: RawFd, 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). /// Instructions related to PTYs (pseudoterminals).
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub enum PtyInstruction { pub enum PtyInstruction {
SpawnTerminal(Option<PathBuf>), SpawnTerminal(Option<PathBuf>),
SpawnTerminalVertically(Option<PathBuf>), SpawnTerminalVertically(Option<PathBuf>),
@ -78,25 +187,22 @@ pub enum PtyInstruction {
} }
pub struct PtyBus { pub struct PtyBus {
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, pub receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
pub id_to_child_pid: HashMap<RawFd, RawFd>, pub id_to_child_pid: HashMap<RawFd, RawFd>,
os_input: Box<dyn OsApi>, os_input: Box<dyn OsApi>,
debug_to_file: bool, debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>, task_handles: HashMap<RawFd, JoinHandle<()>>,
pub server_stream: UnixStream,
} }
fn stream_terminal_bytes( fn stream_terminal_bytes(pid: RawFd, os_input: Box<dyn OsApi>, debug: bool) -> JoinHandle<()> {
pid: RawFd, let mut err_ctx = OPENCALLS.with(|ctx| *ctx.borrow());
send_screen_instructions: SenderWithContext<ScreenInstruction>,
os_input: Box<dyn OsApi>,
debug: bool,
) -> JoinHandle<()> {
let mut err_ctx = get_current_ctx();
task::spawn({ task::spawn({
async move { async move {
err_ctx.add_call(ContextType::AsyncTask); 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 terminal_bytes = ReadFromPid::new(&pid, os_input);
let mut last_byte_receive_time: Option<Instant> = None; let mut last_byte_receive_time: Option<Instant> = None;
@ -122,7 +228,12 @@ fn stream_terminal_bytes(
Some(receive_time) => { Some(receive_time) => {
if receive_time.elapsed() > max_render_pause { if receive_time.elapsed() > max_render_pause {
pending_render = false; pending_render = false;
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()); last_byte_receive_time = Some(Instant::now());
} else { } else {
pending_render = true; pending_render = true;
@ -136,22 +247,31 @@ fn stream_terminal_bytes(
} else { } else {
if pending_render { if pending_render {
pending_render = false; 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; last_byte_receive_time = None;
task::sleep(::std::time::Duration::from_millis(10)).await; task::sleep(::std::time::Duration::from_millis(10)).await;
} }
} }
send_screen_instructions let api_command =
.send(ScreenInstruction::Render) bincode::serialize(&(err_ctx, ApiCommand::ToScreen(ScreenInstruction::Render)))
.unwrap(); .unwrap();
server_stream.write_all(&api_command).unwrap();
#[cfg(not(test))] #[cfg(not(test))]
// this is a little hacky, and is because the tests end the file as soon as // 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 // 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 // a better solution would be to fix the test fakes, but this will do for now
send_screen_instructions let api_command = bincode::serialize(&(
.send(ScreenInstruction::ClosePane(PaneId::Terminal(pid))) err_ctx,
.unwrap(); 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 { impl PtyBus {
pub fn new( pub fn new(
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>, receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
send_screen_instructions: SenderWithContext<ScreenInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>,
os_input: Box<dyn OsApi>, os_input: Box<dyn OsApi>,
server_stream: UnixStream,
debug_to_file: bool, debug_to_file: bool,
) -> Self { ) -> Self {
PtyBus { PtyBus {
send_screen_instructions,
send_plugin_instructions,
receive_pty_instructions, receive_pty_instructions,
os_input, os_input,
id_to_child_pid: HashMap::new(), id_to_child_pid: HashMap::new(),
debug_to_file, debug_to_file,
task_handles: HashMap::new(), task_handles: HashMap::new(),
server_stream,
} }
} }
pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd { pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
let (pid_primary, pid_secondary): (RawFd, RawFd) = let (pid_primary, pid_secondary): (RawFd, RawFd) =
self.os_input.spawn_terminal(file_to_open); self.os_input.spawn_terminal(file_to_open);
let task_handle = stream_terminal_bytes( let task_handle =
pid_primary, stream_terminal_bytes(pid_primary, self.os_input.clone(), self.debug_to_file);
self.send_screen_instructions.clone(),
self.os_input.clone(),
self.debug_to_file,
);
self.task_handles.insert(pid_primary, task_handle); self.task_handles.insert(pid_primary, task_handle);
self.id_to_child_pid.insert(pid_primary, pid_secondary); self.id_to_child_pid.insert(pid_primary, pid_secondary);
pid_primary 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 total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![]; let mut new_pane_pids = vec![];
for _ in 0..total_panes { for _ in 0..total_panes {
@ -195,23 +309,21 @@ impl PtyBus {
self.id_to_child_pid.insert(pid_primary, pid_secondary); self.id_to_child_pid.insert(pid_primary, pid_secondary);
new_pane_pids.push(pid_primary); new_pane_pids.push(pid_primary);
} }
self.send_screen_instructions let api_command = bincode::serialize(&(
.send(ScreenInstruction::ApplyLayout(( err_ctx,
ApiCommand::ToScreen(ScreenInstruction::ApplyLayout((
layout, layout,
new_pane_pids.clone(), new_pane_pids.clone(),
))) ))),
.unwrap(); ))
.unwrap();
self.server_stream.write_all(&api_command).unwrap();
for id in new_pane_pids { for id in new_pane_pids {
let task_handle = stream_terminal_bytes( let task_handle = stream_terminal_bytes(id, self.os_input.clone(), self.debug_to_file);
id,
self.send_screen_instructions.clone(),
self.os_input.clone(),
self.debug_to_file,
);
self.task_handles.insert(id, task_handle); 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 { match id {
PaneId::Terminal(id) => { PaneId::Terminal(id) => {
let child_pid = self.id_to_child_pid.remove(&id).unwrap(); let child_pid = self.id_to_child_pid.remove(&id).unwrap();
@ -221,15 +333,16 @@ impl PtyBus {
handle.cancel().await; handle.cancel().await;
}); });
} }
PaneId::Plugin(pid) => drop( PaneId::Plugin(pid) => {
self.send_plugin_instructions let api_command =
.send(PluginInstruction::Unload(pid)), 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| { 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) { fn drop(&mut self) {
let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect(); let child_ids: Vec<RawFd> = self.id_to_child_pid.keys().copied().collect();
for id in child_ids { for id in child_ids {
self.close_pane(PaneId::Terminal(id)); self.close_pane(PaneId::Terminal(id), ErrorContext::new());
} }
} }
} }

View file

@ -1,5 +1,6 @@
//! Things related to [`Screen`]s. //! Things related to [`Screen`]s.
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::os::unix::io::RawFd; use std::os::unix::io::RawFd;
use std::str; use std::str;
@ -16,7 +17,7 @@ use crate::{layout::Layout, panes::PaneId};
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo}; use zellij_tile::data::{Event, InputMode, ModeInfo, Palette, TabInfo};
/// Instructions that can be sent to the [`Screen`]. /// Instructions that can be sent to the [`Screen`].
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScreenInstruction { pub enum ScreenInstruction {
PtyBytes(RawFd, VteBytes), PtyBytes(RawFd, VteBytes),
Render, Render,
@ -68,8 +69,6 @@ pub struct Screen {
max_panes: Option<usize>, max_panes: Option<usize>,
/// A map between this [`Screen`]'s tabs and their ID/key. /// A map between this [`Screen`]'s tabs and their ID/key.
tabs: BTreeMap<usize, Tab>, tabs: BTreeMap<usize, Tab>,
/// A [`PtyInstruction`] and [`ErrorContext`] sender.
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
/// A [`PluginInstruction`] and [`ErrorContext`] sender. /// A [`PluginInstruction`] and [`ErrorContext`] sender.
pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
/// An [`AppInstruction`] and [`ErrorContext`] sender. /// An [`AppInstruction`] and [`ErrorContext`] sender.
@ -91,7 +90,6 @@ impl Screen {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>, receive_screen_instructions: Receiver<(ScreenInstruction, ErrorContext)>,
send_pty_instructions: SenderWithContext<PtyInstruction>,
send_plugin_instructions: SenderWithContext<PluginInstruction>, send_plugin_instructions: SenderWithContext<PluginInstruction>,
send_app_instructions: SenderWithContext<AppInstruction>, send_app_instructions: SenderWithContext<AppInstruction>,
full_screen_ws: &PositionAndSize, full_screen_ws: &PositionAndSize,
@ -104,7 +102,6 @@ impl Screen {
Screen { Screen {
receiver: receive_screen_instructions, receiver: receive_screen_instructions,
max_panes, max_panes,
send_pty_instructions,
send_plugin_instructions, send_plugin_instructions,
send_app_instructions, send_app_instructions,
full_screen_ws: *full_screen_ws, full_screen_ws: *full_screen_ws,
@ -128,7 +125,6 @@ impl Screen {
String::new(), String::new(),
&self.full_screen_ws, &self.full_screen_ws,
self.os_api.clone(), self.os_api.clone(),
self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(), self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(), self.send_app_instructions.clone(),
self.max_panes, 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 // because this might be happening when the app is closing, at which point the pty thread
// has already closed and this would result in an error // has already closed and this would result in an error
let _ = self let _ = self
.send_pty_instructions .send_app_instructions
.send(PtyInstruction::CloseTab(pane_ids)); .send(AppInstruction::ToPty(PtyInstruction::CloseTab(pane_ids)));
if self.tabs.is_empty() { if self.tabs.is_empty() {
self.active_tab_index = None; self.active_tab_index = None;
self.send_app_instructions self.send_app_instructions
@ -285,7 +281,6 @@ impl Screen {
String::new(), String::new(),
&self.full_screen_ws, &self.full_screen_ws,
self.os_api.clone(), self.os_api.clone(),
self.send_pty_instructions.clone(),
self.send_plugin_instructions.clone(), self.send_plugin_instructions.clone(),
self.send_app_instructions.clone(), self.send_app_instructions.clone(),
self.max_panes, self.max_panes,

View file

@ -29,9 +29,7 @@ pub struct PluginEnv {
pub plugin_id: u32, pub plugin_id: u32,
// FIXME: This should be a big bundle of all of the channels // FIXME: This should be a big bundle of all of the channels
pub send_screen_instructions: SenderWithContext<ScreenInstruction>, pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
pub send_app_instructions: SenderWithContext<AppInstruction>, pub send_app_instructions: SenderWithContext<AppInstruction>, // FIXME: This should be a big bundle of all of the channels
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
pub wasi_env: WasiEnv, pub wasi_env: WasiEnv,
pub subscriptions: Arc<Mutex<HashSet<EventType>>>, pub subscriptions: Arc<Mutex<HashSet<EventType>>>,
} }
@ -74,6 +72,16 @@ fn host_unsubscribe(plugin_env: &PluginEnv) {
subscriptions.retain(|k| !old.contains(k)); 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) { fn host_set_selectable(plugin_env: &PluginEnv, selectable: i32) {
let selectable = selectable != 0; let selectable = selectable != 0;
plugin_env plugin_env

View file

@ -1,13 +1,11 @@
mod cli; mod cli;
mod common;
#[cfg(test)]
mod tests;
// TODO mod server;
mod client; mod client;
mod common;
mod server;
use crate::cli::CliArgs; use crate::cli::CliArgs;
use crate::command_is_executing::CommandIsExecuting; 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::os_input_output::get_os_input;
use crate::utils::{ use crate::utils::{
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR}, consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
@ -36,23 +34,29 @@ pub fn main() {
match split_dir { match split_dir {
'h' => { 'h' => {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); 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(); stream.write_all(&api_command).unwrap();
} }
'v' => { 'v' => {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); 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(); stream.write_all(&api_command).unwrap();
} }
_ => {} _ => {}
}; };
} else if opts.move_focus { } else if opts.move_focus {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); 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(); stream.write_all(&api_command).unwrap();
} else if let Some(file_to_open) = opts.open_file { } else if let Some(file_to_open) = opts.open_file {
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap(); 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(); stream.write_all(&api_command).unwrap();
} else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option { } else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
let shell = match shell.as_ref() { let shell = match shell.as_ref() {

View file

@ -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() { pub fn start_server(
// TODO 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()
}