isolate pty thread
This commit is contained in:
parent
03f8e7220c
commit
588cdaa008
11 changed files with 557 additions and 349 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
22
src/main.rs
22
src/main.rs
|
|
@ -1,13 +1,11 @@
|
|||
mod cli;
|
||||
mod common;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
// TODO mod server;
|
||||
mod client;
|
||||
mod common;
|
||||
mod server;
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::command_is_executing::CommandIsExecuting;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::errors::ErrorContext;
|
||||
use crate::os_input_output::get_os_input;
|
||||
use crate::utils::{
|
||||
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
||||
|
|
@ -36,23 +34,29 @@ pub fn main() {
|
|||
match split_dir {
|
||||
'h' => {
|
||||
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
|
||||
let api_command = bincode::serialize(&ApiCommand::SplitHorizontally).unwrap();
|
||||
let api_command =
|
||||
bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitHorizontally))
|
||||
.unwrap();
|
||||
stream.write_all(&api_command).unwrap();
|
||||
}
|
||||
'v' => {
|
||||
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
|
||||
let api_command = bincode::serialize(&ApiCommand::SplitVertically).unwrap();
|
||||
let api_command =
|
||||
bincode::serialize(&(ErrorContext::new(), ApiCommand::SplitVertically))
|
||||
.unwrap();
|
||||
stream.write_all(&api_command).unwrap();
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
} else if opts.move_focus {
|
||||
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
|
||||
let api_command = bincode::serialize(&ApiCommand::MoveFocus).unwrap();
|
||||
let api_command =
|
||||
bincode::serialize(&(ErrorContext::new(), ApiCommand::MoveFocus)).unwrap();
|
||||
stream.write_all(&api_command).unwrap();
|
||||
} else if let Some(file_to_open) = opts.open_file {
|
||||
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
|
||||
let api_command = bincode::serialize(&ApiCommand::OpenFile(file_to_open)).unwrap();
|
||||
let api_command =
|
||||
bincode::serialize(&(ErrorContext::new(), ApiCommand::OpenFile(file_to_open))).unwrap();
|
||||
stream.write_all(&api_command).unwrap();
|
||||
} else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
|
||||
let shell = match shell.as_ref() {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue