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 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

View file

@ -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?

View file

@ -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();
}
}

View file

@ -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,
}
}
}

View file

@ -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,
)

View file

@ -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);

View file

@ -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());
}
}
}

View file

@ -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,

View file

@ -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

View file

@ -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() {

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() {
// TODO
}
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()
}