mirror of
https://github.com/zellij-org/zellij.git
synced 2024-11-27 06:25:42 +03:00
Merge pull request #223 from zellij-org/isolate-pty
Psuedo Client-Server model
This commit is contained in:
commit
d5433f8f89
71
Cargo.lock
generated
71
Cargo.lock
generated
@ -598,6 +598,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
@ -1031,6 +1037,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238"
|
||||
|
||||
[[package]]
|
||||
name = "names"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef320dab323286b50fb5cdda23f61c796a72a89998ab565ca32525c5c556f2da"
|
||||
dependencies = [
|
||||
"rand 0.3.23",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
@ -1201,6 +1216,29 @@ version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand 0.4.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
dependencies = [
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.3"
|
||||
@ -1209,7 +1247,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.6.2",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
@ -1220,9 +1258,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.2"
|
||||
@ -1238,7 +1291,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
"rand_core 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1266,6 +1319,15 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.6"
|
||||
@ -1593,7 +1655,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand",
|
||||
"rand 0.8.3",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
@ -2205,6 +2267,7 @@ dependencies = [
|
||||
"interprocess",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"names",
|
||||
"nix",
|
||||
"nom",
|
||||
"serde",
|
||||
|
@ -35,7 +35,8 @@ strum = "0.20.0"
|
||||
lazy_static = "1.4.0"
|
||||
wasmer = "1.0.0"
|
||||
wasmer-wasi = "1.0.0"
|
||||
interprocess = "1.0.1"
|
||||
interprocess = "1.1.1"
|
||||
names = "0.11.0"
|
||||
colors-transform = "0.2.5"
|
||||
zellij-tile = { path = "zellij-tile/", version = "0.7.0" }
|
||||
|
||||
|
17
src/cli.rs
17
src/cli.rs
@ -1,22 +1,11 @@
|
||||
use super::common::utils::consts::{ZELLIJ_CONFIG_DIR_ENV, ZELLIJ_CONFIG_FILE_ENV};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt, Default, Debug)]
|
||||
#[derive(StructOpt, Default, Debug, Clone, Serialize, Deserialize)]
|
||||
#[structopt(name = "zellij")]
|
||||
pub struct CliArgs {
|
||||
/// Send "split (direction h == horizontal / v == vertical)" to active zellij session
|
||||
#[structopt(short, long)]
|
||||
pub split: Option<char>,
|
||||
|
||||
/// Send "move focused pane" to active zellij session
|
||||
#[structopt(short, long)]
|
||||
pub move_focus: bool,
|
||||
|
||||
/// Send "open file in new pane" to active zellij session
|
||||
#[structopt(short, long)]
|
||||
pub open_file: Option<PathBuf>,
|
||||
|
||||
/// Maximum panes on screen, caution: opening more panes will close old ones
|
||||
#[structopt(long)]
|
||||
pub max_panes: Option<usize>,
|
||||
@ -44,7 +33,7 @@ pub struct CliArgs {
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[derive(Debug, StructOpt, Clone, Serialize, Deserialize)]
|
||||
pub enum ConfigCli {
|
||||
/// Change the behaviour of zellij
|
||||
#[structopt(name = "option")]
|
||||
|
@ -4,4 +4,172 @@ pub mod pane_resizer;
|
||||
pub mod panes;
|
||||
pub mod tab;
|
||||
|
||||
pub fn _start_client() {}
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::common::{
|
||||
command_is_executing::CommandIsExecuting,
|
||||
errors::{ClientContext, ContextType},
|
||||
input::config::Config,
|
||||
input::handler::input_loop,
|
||||
os_input_output::ClientOsApi,
|
||||
SenderType, SenderWithContext, SyncChannelWithContext,
|
||||
};
|
||||
use crate::server::ServerInstruction;
|
||||
|
||||
/// Instructions related to the client-side application and sent from server to client
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum ClientInstruction {
|
||||
Error(String),
|
||||
Render(Option<String>),
|
||||
UnblockInputThread,
|
||||
Exit,
|
||||
}
|
||||
|
||||
pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) {
|
||||
let take_snapshot = "\u{1b}[?1049h";
|
||||
os_input.unset_raw_mode(0);
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(take_snapshot.as_bytes())
|
||||
.unwrap();
|
||||
std::env::set_var(&"ZELLIJ", "0");
|
||||
|
||||
let mut command_is_executing = CommandIsExecuting::new();
|
||||
|
||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||
os_input.connect_to_server();
|
||||
os_input.send_to_server(ServerInstruction::NewClient(full_screen_ws, opts));
|
||||
os_input.set_raw_mode(0);
|
||||
|
||||
let (send_client_instructions, receive_client_instructions): SyncChannelWithContext<
|
||||
ClientInstruction,
|
||||
> = mpsc::sync_channel(50);
|
||||
let send_client_instructions =
|
||||
SenderWithContext::new(SenderType::SyncSender(send_client_instructions));
|
||||
|
||||
#[cfg(not(test))]
|
||||
std::panic::set_hook({
|
||||
use crate::errors::handle_panic;
|
||||
let send_client_instructions = send_client_instructions.clone();
|
||||
Box::new(move |info| {
|
||||
handle_panic(info, &send_client_instructions);
|
||||
})
|
||||
});
|
||||
|
||||
let _stdin_thread = thread::Builder::new()
|
||||
.name("stdin_handler".to_string())
|
||||
.spawn({
|
||||
let send_client_instructions = send_client_instructions.clone();
|
||||
let command_is_executing = command_is_executing.clone();
|
||||
let os_input = os_input.clone();
|
||||
move || {
|
||||
input_loop(
|
||||
os_input,
|
||||
config,
|
||||
command_is_executing,
|
||||
send_client_instructions,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let _signal_thread = thread::Builder::new()
|
||||
.name("signal_listener".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
move || {
|
||||
os_input.receive_sigwinch(Box::new({
|
||||
let os_api = os_input.clone();
|
||||
move || {
|
||||
os_api.send_to_server(ServerInstruction::TerminalResize(
|
||||
os_api.get_terminal_size_using_fd(0),
|
||||
));
|
||||
}
|
||||
}));
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let router_thread = thread::Builder::new()
|
||||
.name("router".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
move || {
|
||||
loop {
|
||||
let (instruction, mut err_ctx) = os_input.recv_from_server();
|
||||
err_ctx.add_call(ContextType::Client(ClientContext::from(&instruction)));
|
||||
if let ClientInstruction::Exit = instruction {
|
||||
break;
|
||||
}
|
||||
send_client_instructions.send(instruction).unwrap();
|
||||
}
|
||||
send_client_instructions
|
||||
.send(ClientInstruction::Exit)
|
||||
.unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[warn(clippy::never_loop)]
|
||||
loop {
|
||||
let (client_instruction, mut err_ctx) = receive_client_instructions
|
||||
.recv()
|
||||
.expect("failed to receive app instruction on channel");
|
||||
|
||||
err_ctx.add_call(ContextType::Client(ClientContext::from(
|
||||
&client_instruction,
|
||||
)));
|
||||
match client_instruction {
|
||||
ClientInstruction::Exit => break,
|
||||
ClientInstruction::Error(backtrace) => {
|
||||
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
|
||||
os_input.unset_raw_mode(0);
|
||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||
let restore_snapshot = "\u{1b}[?1049l";
|
||||
let error = format!(
|
||||
"{}\n{}{}",
|
||||
goto_start_of_last_line, restore_snapshot, backtrace
|
||||
);
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(error.as_bytes())
|
||||
.unwrap();
|
||||
std::process::exit(1);
|
||||
}
|
||||
ClientInstruction::Render(output) => {
|
||||
if output.is_none() {
|
||||
break;
|
||||
}
|
||||
let mut stdout = os_input.get_stdout_writer();
|
||||
stdout
|
||||
.write_all(&output.unwrap().as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
stdout.flush().expect("could not flush");
|
||||
}
|
||||
ClientInstruction::UnblockInputThread => {
|
||||
command_is_executing.unblock_input_thread();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
|
||||
router_thread.join().unwrap();
|
||||
|
||||
// cleanup();
|
||||
let reset_style = "\u{1b}[m";
|
||||
let show_cursor = "\u{1b}[?25h";
|
||||
let restore_snapshot = "\u{1b}[?1049l";
|
||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||
let goodbye_message = format!(
|
||||
"{}\n{}{}{}Bye from Zellij!\n",
|
||||
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
|
||||
);
|
||||
|
||||
os_input.unset_raw_mode(0);
|
||||
let mut stdout = os_input.get_stdout_writer();
|
||||
let _ = stdout.write(goodbye_message.as_bytes()).unwrap();
|
||||
stdout.flush().unwrap();
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::panes::{PaneId, PositionAndSize};
|
||||
use crate::tab::Pane;
|
||||
use std::{
|
||||
@ -8,7 +8,7 @@ use std::{
|
||||
|
||||
pub struct PaneResizer<'a> {
|
||||
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
|
||||
os_api: &'a mut Box<dyn OsApi>,
|
||||
os_api: &'a mut Box<dyn ServerOsApi>,
|
||||
}
|
||||
|
||||
// TODO: currently there are some functions here duplicated with Tab
|
||||
@ -17,7 +17,7 @@ pub struct PaneResizer<'a> {
|
||||
impl<'a> PaneResizer<'a> {
|
||||
pub fn new(
|
||||
panes: &'a mut BTreeMap<PaneId, Box<dyn Pane>>,
|
||||
os_api: &'a mut Box<dyn OsApi>,
|
||||
os_api: &'a mut Box<dyn ServerOsApi>,
|
||||
) -> Self {
|
||||
PaneResizer { panes, os_api }
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::tab::Pane;
|
||||
use ::nix::pty::Winsize;
|
||||
use ::std::os::unix::io::RawFd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::time::Instant;
|
||||
|
||||
@ -10,7 +11,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?
|
||||
@ -18,7 +19,7 @@ pub enum PaneId {
|
||||
|
||||
/// Contains the position and size of a [`Pane`], or more generally of any terminal, measured
|
||||
/// in character rows and columns.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct PositionAndSize {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
|
@ -2,22 +2,23 @@
|
||||
//! as well as how they should be resized
|
||||
|
||||
use crate::client::pane_resizer::PaneResizer;
|
||||
use crate::common::{input::handler::parse_keys, AppInstruction, SenderWithContext};
|
||||
use crate::common::{input::handler::parse_keys, SenderWithContext};
|
||||
use crate::layout::Layout;
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::panes::{PaneId, PositionAndSize, TerminalPane};
|
||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::utils::shared::adjust_to_size;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use crate::{boundaries::Boundaries, panes::PluginPane};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
use std::{
|
||||
cmp::Reverse,
|
||||
collections::{BTreeMap, HashSet},
|
||||
};
|
||||
use std::{io::Write, sync::mpsc::channel};
|
||||
use zellij_tile::data::{Event, InputMode, ModeInfo, Palette};
|
||||
|
||||
const CURSOR_HEIGHT_WIDTH_RATIO: usize = 4; // this is not accurate and kind of a magic number, TODO: look into this
|
||||
@ -67,11 +68,11 @@ pub struct Tab {
|
||||
max_panes: Option<usize>,
|
||||
full_screen_ws: PositionAndSize,
|
||||
fullscreen_is_active: bool,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
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,
|
||||
pub mode_info: ModeInfo,
|
||||
pub input_mode: InputMode,
|
||||
@ -225,10 +226,10 @@ impl Tab {
|
||||
position: usize,
|
||||
name: String,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
mut os_api: Box<dyn OsApi>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
mut os_api: Box<dyn ServerOsApi>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
send_app_instructions: SenderWithContext<AppInstruction>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
max_panes: Option<usize>,
|
||||
pane_id: Option<PaneId>,
|
||||
mode_info: ModeInfo,
|
||||
@ -260,9 +261,9 @@ impl Tab {
|
||||
fullscreen_is_active: false,
|
||||
synchronize_is_active: false,
|
||||
os_api,
|
||||
send_app_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
should_clear_display_before_rendering: false,
|
||||
mode_info,
|
||||
input_mode,
|
||||
@ -400,8 +401,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
|
||||
.unwrap();
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
return; // likely no terminal large enough to split
|
||||
}
|
||||
let terminal_id_to_split = terminal_id_to_split.unwrap();
|
||||
@ -481,8 +482,8 @@ impl Tab {
|
||||
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();
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
@ -538,8 +539,8 @@ impl Tab {
|
||||
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();
|
||||
.send(PtyInstruction::ClosePane(pid))
|
||||
.unwrap(); // we can't open this pane, close the pty
|
||||
return;
|
||||
}
|
||||
let terminal_ws = PositionAndSize {
|
||||
@ -729,20 +730,16 @@ impl Tab {
|
||||
if self.panes_contain_widechar() {
|
||||
self.set_force_render()
|
||||
}
|
||||
let mut stdout = self.os_api.get_stdout_writer();
|
||||
let mut output = String::new();
|
||||
let mut boundaries = Boundaries::new(
|
||||
self.full_screen_ws.columns as u16,
|
||||
self.full_screen_ws.rows as u16,
|
||||
);
|
||||
let hide_cursor = "\u{1b}[?25l";
|
||||
stdout
|
||||
.write_all(&hide_cursor.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
output.push_str(hide_cursor);
|
||||
if self.should_clear_display_before_rendering {
|
||||
let clear_display = "\u{1b}[2J";
|
||||
stdout
|
||||
.write_all(&clear_display.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
output.push_str(clear_display);
|
||||
self.should_clear_display_before_rendering = false;
|
||||
}
|
||||
for (kind, pane) in self.panes.iter_mut() {
|
||||
@ -761,48 +758,39 @@ impl Tab {
|
||||
adjust_to_size(&vte_output, pane.rows(), pane.columns())
|
||||
};
|
||||
// FIXME: Use Termion for cursor and style clearing?
|
||||
write!(
|
||||
stdout,
|
||||
output.push_str(&format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m{}",
|
||||
pane.y() + 1,
|
||||
pane.x() + 1,
|
||||
vte_output
|
||||
)
|
||||
.expect("cannot write to stdout");
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: only render (and calculate) boundaries if there was a resize
|
||||
let vte_output = boundaries.vte_output();
|
||||
stdout
|
||||
.write_all(&vte_output.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
output.push_str(&boundaries.vte_output());
|
||||
|
||||
match self.get_active_terminal_cursor_position() {
|
||||
Some((cursor_position_x, cursor_position_y)) => {
|
||||
let show_cursor = "\u{1b}[?25h";
|
||||
let goto_cursor_position = format!(
|
||||
let goto_cursor_position = &format!(
|
||||
"\u{1b}[{};{}H\u{1b}[m",
|
||||
cursor_position_y + 1,
|
||||
cursor_position_x + 1
|
||||
); // goto row/col
|
||||
stdout
|
||||
.write_all(&show_cursor.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
stdout
|
||||
.write_all(&goto_cursor_position.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
stdout.flush().expect("could not flush");
|
||||
output.push_str(show_cursor);
|
||||
output.push_str(goto_cursor_position);
|
||||
}
|
||||
None => {
|
||||
let hide_cursor = "\u{1b}[?25l";
|
||||
stdout
|
||||
.write_all(&hide_cursor.as_bytes())
|
||||
.expect("cannot write to stdout");
|
||||
stdout.flush().expect("could not flush");
|
||||
output.push_str(hide_cursor);
|
||||
}
|
||||
}
|
||||
|
||||
self.send_server_instructions
|
||||
.send(ServerInstruction::Render(Some(output)))
|
||||
.unwrap();
|
||||
}
|
||||
fn get_panes(&self) -> impl Iterator<Item = (&PaneId, &Box<dyn Pane>)> {
|
||||
self.panes.iter()
|
||||
|
@ -3,51 +3,31 @@ use std::sync::{Arc, Condvar, Mutex};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CommandIsExecuting {
|
||||
opening_new_pane: Arc<(Mutex<bool>, Condvar)>,
|
||||
closing_pane: Arc<(Mutex<bool>, Condvar)>,
|
||||
input_thread: Arc<(Mutex<bool>, Condvar)>,
|
||||
}
|
||||
|
||||
impl CommandIsExecuting {
|
||||
pub fn new() -> Self {
|
||||
CommandIsExecuting {
|
||||
opening_new_pane: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
closing_pane: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
input_thread: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
}
|
||||
}
|
||||
pub fn closing_pane(&mut self) {
|
||||
let (lock, _cvar) = &*self.closing_pane;
|
||||
let mut closing_pane = lock.lock().unwrap();
|
||||
*closing_pane = true;
|
||||
pub fn blocking_input_thread(&mut self) {
|
||||
let (lock, _cvar) = &*self.input_thread;
|
||||
let mut input_thread = lock.lock().unwrap();
|
||||
*input_thread = true;
|
||||
}
|
||||
pub fn done_closing_pane(&mut self) {
|
||||
let (lock, cvar) = &*self.closing_pane;
|
||||
let mut closing_pane = lock.lock().unwrap();
|
||||
*closing_pane = false;
|
||||
pub fn unblock_input_thread(&mut self) {
|
||||
let (lock, cvar) = &*self.input_thread;
|
||||
let mut input_thread = lock.lock().unwrap();
|
||||
*input_thread = false;
|
||||
cvar.notify_all();
|
||||
}
|
||||
pub fn opening_new_pane(&mut self) {
|
||||
let (lock, _cvar) = &*self.opening_new_pane;
|
||||
let mut opening_new_pane = lock.lock().unwrap();
|
||||
*opening_new_pane = true;
|
||||
}
|
||||
pub fn done_opening_new_pane(&mut self) {
|
||||
let (lock, cvar) = &*self.opening_new_pane;
|
||||
let mut opening_new_pane = lock.lock().unwrap();
|
||||
*opening_new_pane = false;
|
||||
cvar.notify_all();
|
||||
}
|
||||
pub fn wait_until_pane_is_closed(&self) {
|
||||
let (lock, cvar) = &*self.closing_pane;
|
||||
let mut closing_pane = lock.lock().unwrap();
|
||||
while *closing_pane {
|
||||
closing_pane = cvar.wait(closing_pane).unwrap();
|
||||
}
|
||||
}
|
||||
pub fn wait_until_new_pane_is_opened(&self) {
|
||||
let (lock, cvar) = &*self.opening_new_pane;
|
||||
let mut opening_new_pane = lock.lock().unwrap();
|
||||
while *opening_new_pane {
|
||||
opening_new_pane = cvar.wait(opening_new_pane).unwrap();
|
||||
pub fn wait_until_input_thread_is_unblocked(&self) {
|
||||
let (lock, cvar) = &*self.input_thread;
|
||||
let mut input_thread = lock.lock().unwrap();
|
||||
while *input_thread {
|
||||
input_thread = cvar.wait(input_thread).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
//! Error context system based on a thread-local representation of the call stack, itself based on
|
||||
//! the instructions that are sent between threads.
|
||||
|
||||
use super::{AppInstruction, ASYNCOPENCALLS, OPENCALLS};
|
||||
use super::{ServerInstruction, ASYNCOPENCALLS, OPENCALLS};
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::{Display, Error, Formatter};
|
||||
|
||||
@ -19,7 +21,7 @@ use std::panic::PanicInfo;
|
||||
#[cfg(not(test))]
|
||||
pub fn handle_panic(
|
||||
info: &PanicInfo<'_>,
|
||||
send_app_instructions: &SenderWithContext<AppInstruction>,
|
||||
send_app_instructions: &SenderWithContext<ClientInstruction>,
|
||||
) {
|
||||
use backtrace::Backtrace;
|
||||
use std::{process, thread};
|
||||
@ -66,9 +68,7 @@ pub fn handle_panic(
|
||||
println!("{}", backtrace);
|
||||
process::exit(1);
|
||||
} else {
|
||||
send_app_instructions
|
||||
.send(AppInstruction::Error(backtrace))
|
||||
.unwrap();
|
||||
let _ = send_app_instructions.send(ClientInstruction::Error(backtrace));
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,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],
|
||||
}
|
||||
@ -130,8 +130,8 @@ 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)]
|
||||
/// [`PtyInstruction`], [`ClientInstruction`], etc).
|
||||
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ContextType {
|
||||
/// A screen-related call.
|
||||
Screen(ScreenContext),
|
||||
@ -140,8 +140,9 @@ pub enum ContextType {
|
||||
/// A plugin-related call.
|
||||
Plugin(PluginContext),
|
||||
/// An app-related call.
|
||||
App(AppContext),
|
||||
IpcServer,
|
||||
Client(ClientContext),
|
||||
/// A server-related call.
|
||||
IPCServer(ServerContext),
|
||||
StdinHandler,
|
||||
AsyncTask,
|
||||
/// An empty, placeholder call. This should be thought of as representing no call at all.
|
||||
@ -157,10 +158,9 @@ impl Display for ContextType {
|
||||
match *self {
|
||||
ContextType::Screen(c) => write!(f, "{}screen_thread: {}{:?}", purple, green, c),
|
||||
ContextType::Pty(c) => write!(f, "{}pty_thread: {}{:?}", purple, green, c),
|
||||
|
||||
ContextType::Plugin(c) => write!(f, "{}plugin_thread: {}{:?}", purple, green, c),
|
||||
ContextType::App(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
|
||||
ContextType::IpcServer => write!(f, "{}ipc_server: {}AcceptInput", purple, green),
|
||||
ContextType::Client(c) => write!(f, "{}main_thread: {}{:?}", purple, green, c),
|
||||
ContextType::IPCServer(c) => write!(f, "{}ipc_server: {}{:?}", purple, green, c),
|
||||
ContextType::StdinHandler => {
|
||||
write!(f, "{}stdin_handler_thread: {}AcceptInput", purple, green)
|
||||
}
|
||||
@ -174,7 +174,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,
|
||||
@ -193,7 +193,7 @@ pub enum ScreenContext {
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
MoveFocusRight,
|
||||
Quit,
|
||||
Exit,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
PageScrollUp,
|
||||
@ -238,7 +238,7 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::MoveFocusDown => ScreenContext::MoveFocusDown,
|
||||
ScreenInstruction::MoveFocusUp => ScreenContext::MoveFocusUp,
|
||||
ScreenInstruction::MoveFocusRight => ScreenContext::MoveFocusRight,
|
||||
ScreenInstruction::Quit => ScreenContext::Quit,
|
||||
ScreenInstruction::Exit => ScreenContext::Exit,
|
||||
ScreenInstruction::ScrollUp => ScreenContext::ScrollUp,
|
||||
ScreenInstruction::ScrollDown => ScreenContext::ScrollDown,
|
||||
ScreenInstruction::PageScrollUp => ScreenContext::PageScrollUp,
|
||||
@ -252,14 +252,14 @@ impl From<&ScreenInstruction> for ScreenContext {
|
||||
ScreenInstruction::SetInvisibleBorders(..) => ScreenContext::SetInvisibleBorders,
|
||||
ScreenInstruction::SetMaxHeight(..) => ScreenContext::SetMaxHeight,
|
||||
ScreenInstruction::ClosePane(_) => ScreenContext::ClosePane,
|
||||
ScreenInstruction::ApplyLayout(_) => ScreenContext::ApplyLayout,
|
||||
ScreenInstruction::ApplyLayout(..) => ScreenContext::ApplyLayout,
|
||||
ScreenInstruction::NewTab(_) => ScreenContext::NewTab,
|
||||
ScreenInstruction::SwitchTabNext => ScreenContext::SwitchTabNext,
|
||||
ScreenInstruction::SwitchTabPrev => ScreenContext::SwitchTabPrev,
|
||||
ScreenInstruction::CloseTab => ScreenContext::CloseTab,
|
||||
ScreenInstruction::GoToTab(_) => ScreenContext::GoToTab,
|
||||
ScreenInstruction::UpdateTabName(_) => ScreenContext::UpdateTabName,
|
||||
ScreenInstruction::TerminalResize => ScreenContext::TerminalResize,
|
||||
ScreenInstruction::TerminalResize(_) => ScreenContext::TerminalResize,
|
||||
ScreenInstruction::ChangeMode(_) => ScreenContext::ChangeMode,
|
||||
ScreenInstruction::ToggleActiveSyncPanes => ScreenContext::ToggleActiveSyncPanes,
|
||||
}
|
||||
@ -267,7 +267,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,
|
||||
@ -275,7 +275,7 @@ pub enum PtyContext {
|
||||
NewTab,
|
||||
ClosePane,
|
||||
CloseTab,
|
||||
Quit,
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl From<&PtyInstruction> for PtyContext {
|
||||
@ -287,7 +287,7 @@ impl From<&PtyInstruction> for PtyContext {
|
||||
PtyInstruction::ClosePane(_) => PtyContext::ClosePane,
|
||||
PtyInstruction::CloseTab(_) => PtyContext::CloseTab,
|
||||
PtyInstruction::NewTab => PtyContext::NewTab,
|
||||
PtyInstruction::Quit => PtyContext::Quit,
|
||||
PtyInstruction::Exit => PtyContext::Exit,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -297,13 +297,13 @@ 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,
|
||||
Render,
|
||||
Unload,
|
||||
Quit,
|
||||
Exit,
|
||||
}
|
||||
|
||||
impl From<&PluginInstruction> for PluginContext {
|
||||
@ -313,23 +313,51 @@ impl From<&PluginInstruction> for PluginContext {
|
||||
PluginInstruction::Update(..) => PluginContext::Update,
|
||||
PluginInstruction::Render(..) => PluginContext::Render,
|
||||
PluginInstruction::Unload(_) => PluginContext::Unload,
|
||||
PluginInstruction::Quit => PluginContext::Quit,
|
||||
PluginInstruction::Exit => PluginContext::Exit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`AppInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum AppContext {
|
||||
/// Stack call representations corresponding to the different types of [`ClientInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ClientContext {
|
||||
Exit,
|
||||
Error,
|
||||
UnblockInputThread,
|
||||
Render,
|
||||
}
|
||||
|
||||
impl From<&AppInstruction> for AppContext {
|
||||
fn from(app_instruction: &AppInstruction) -> Self {
|
||||
match *app_instruction {
|
||||
AppInstruction::Exit => AppContext::Exit,
|
||||
AppInstruction::Error(_) => AppContext::Error,
|
||||
impl From<&ClientInstruction> for ClientContext {
|
||||
fn from(client_instruction: &ClientInstruction) -> Self {
|
||||
match *client_instruction {
|
||||
ClientInstruction::Exit => ClientContext::Exit,
|
||||
ClientInstruction::Error(_) => ClientContext::Error,
|
||||
ClientInstruction::Render(_) => ClientContext::Render,
|
||||
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stack call representations corresponding to the different types of [`ServerInstruction`]s.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum ServerContext {
|
||||
NewClient,
|
||||
Action,
|
||||
Render,
|
||||
TerminalResize,
|
||||
UnblockInputThread,
|
||||
ClientExit,
|
||||
}
|
||||
|
||||
impl From<&ServerInstruction> for ServerContext {
|
||||
fn from(server_instruction: &ServerInstruction) -> Self {
|
||||
match *server_instruction {
|
||||
ServerInstruction::NewClient(..) => ServerContext::NewClient,
|
||||
ServerInstruction::Action(_) => ServerContext::Action,
|
||||
ServerInstruction::TerminalResize(_) => ServerContext::TerminalResize,
|
||||
ServerInstruction::Render(_) => ServerContext::Render,
|
||||
ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread,
|
||||
ServerInstruction::ClientExit => ServerContext::ClientExit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,53 +2,43 @@
|
||||
|
||||
use super::actions::Action;
|
||||
use super::keybinds::Keybinds;
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::common::{AppInstruction, SenderWithContext, OPENCALLS};
|
||||
use crate::common::{SenderWithContext, OPENCALLS};
|
||||
use crate::errors::ContextType;
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::pty_bus::PtyInstruction;
|
||||
use crate::screen::ScreenInstruction;
|
||||
use crate::wasm_vm::PluginInstruction;
|
||||
use crate::os_input_output::ClientOsApi;
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::CommandIsExecuting;
|
||||
|
||||
use termion::input::{TermRead, TermReadEventsAndRaw};
|
||||
use zellij_tile::data::{Event, InputMode, Key, ModeInfo, Palette};
|
||||
use zellij_tile::data::{InputMode, Key, ModeInfo, Palette};
|
||||
|
||||
/// Handles the dispatching of [`Action`]s according to the current
|
||||
/// [`InputMode`], and keep tracks of the current [`InputMode`].
|
||||
struct InputHandler {
|
||||
/// The current input mode
|
||||
mode: InputMode,
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ClientOsApi>,
|
||||
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>,
|
||||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
impl InputHandler {
|
||||
/// Returns a new [`InputHandler`] with the attributes specified as arguments.
|
||||
fn new(
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ClientOsApi>,
|
||||
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>,
|
||||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||
) -> Self {
|
||||
InputHandler {
|
||||
mode: InputMode::Normal,
|
||||
os_input,
|
||||
config,
|
||||
command_is_executing,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
send_client_instructions,
|
||||
should_exit: false,
|
||||
}
|
||||
}
|
||||
@ -114,162 +104,31 @@ impl InputHandler {
|
||||
let mut should_break = false;
|
||||
|
||||
match action {
|
||||
Action::Write(val) => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ClearScroll)
|
||||
.unwrap();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::WriteCharacter(val))
|
||||
.unwrap();
|
||||
}
|
||||
Action::Quit => {
|
||||
self.exit();
|
||||
should_break = true;
|
||||
}
|
||||
Action::SwitchToMode(mode) => {
|
||||
self.mode = mode;
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
None,
|
||||
Event::ModeUpdate(get_mode_info(mode, self.os_input.load_palette())),
|
||||
))
|
||||
.unwrap();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ChangeMode(get_mode_info(
|
||||
mode,
|
||||
self.os_input.load_palette(),
|
||||
)))
|
||||
.unwrap();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
self.os_input
|
||||
.send_to_server(ServerInstruction::Action(action));
|
||||
}
|
||||
Action::Resize(direction) => {
|
||||
let screen_instr = match direction {
|
||||
super::actions::Direction::Left => ScreenInstruction::ResizeLeft,
|
||||
super::actions::Direction::Right => ScreenInstruction::ResizeRight,
|
||||
super::actions::Direction::Up => ScreenInstruction::ResizeUp,
|
||||
super::actions::Direction::Down => ScreenInstruction::ResizeDown,
|
||||
};
|
||||
self.send_screen_instructions.send(screen_instr).unwrap();
|
||||
Action::CloseFocus
|
||||
| Action::NewPane(_)
|
||||
| Action::NewTab
|
||||
| Action::GoToNextTab
|
||||
| Action::GoToPreviousTab
|
||||
| Action::CloseTab
|
||||
| Action::GoToTab(_) => {
|
||||
self.command_is_executing.blocking_input_thread();
|
||||
self.os_input
|
||||
.send_to_server(ServerInstruction::Action(action));
|
||||
self.command_is_executing
|
||||
.wait_until_input_thread_is_unblocked();
|
||||
}
|
||||
Action::SwitchFocus => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchFocus)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusNextPane => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusNextPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusPreviousPane => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusPreviousPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::MoveFocus(direction) => {
|
||||
let screen_instr = match direction {
|
||||
super::actions::Direction::Left => ScreenInstruction::MoveFocusLeft,
|
||||
super::actions::Direction::Right => ScreenInstruction::MoveFocusRight,
|
||||
super::actions::Direction::Up => ScreenInstruction::MoveFocusUp,
|
||||
super::actions::Direction::Down => ScreenInstruction::MoveFocusDown,
|
||||
};
|
||||
self.send_screen_instructions.send(screen_instr).unwrap();
|
||||
}
|
||||
Action::ScrollUp => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ScrollDown => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollUp => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollDown => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleFocusFullscreen => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewPane(direction) => {
|
||||
let pty_instr = match direction {
|
||||
Some(super::actions::Direction::Left) => {
|
||||
PtyInstruction::SpawnTerminalVertically(None)
|
||||
}
|
||||
Some(super::actions::Direction::Right) => {
|
||||
PtyInstruction::SpawnTerminalVertically(None)
|
||||
}
|
||||
Some(super::actions::Direction::Up) => {
|
||||
PtyInstruction::SpawnTerminalHorizontally(None)
|
||||
}
|
||||
Some(super::actions::Direction::Down) => {
|
||||
PtyInstruction::SpawnTerminalHorizontally(None)
|
||||
}
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => PtyInstruction::SpawnTerminal(None),
|
||||
};
|
||||
self.command_is_executing.opening_new_pane();
|
||||
self.send_pty_instructions.send(pty_instr).unwrap();
|
||||
self.command_is_executing.wait_until_new_pane_is_opened();
|
||||
}
|
||||
Action::CloseFocus => {
|
||||
self.command_is_executing.closing_pane();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseFocusedPane)
|
||||
.unwrap();
|
||||
self.command_is_executing.wait_until_pane_is_closed();
|
||||
}
|
||||
Action::NewTab => {
|
||||
self.command_is_executing.opening_new_pane();
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
self.command_is_executing.wait_until_new_pane_is_opened();
|
||||
}
|
||||
Action::GoToNextTab => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabNext)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToPreviousTab => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabPrev)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleActiveSyncPanes => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveSyncPanes)
|
||||
.unwrap();
|
||||
}
|
||||
Action::CloseTab => {
|
||||
self.command_is_executing.closing_pane();
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseTab)
|
||||
.unwrap();
|
||||
self.command_is_executing.wait_until_pane_is_closed();
|
||||
}
|
||||
Action::GoToTab(i) => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::TabNameInput(c) => {
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::UpdateTabName(c))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
_ => self
|
||||
.os_input
|
||||
.send_to_server(ServerInstruction::Action(action)),
|
||||
}
|
||||
|
||||
should_break
|
||||
@ -278,8 +137,8 @@ impl InputHandler {
|
||||
/// Routine to be called when the input handler exits (at the moment this is the
|
||||
/// same as quitting Zellij).
|
||||
fn exit(&mut self) {
|
||||
self.send_app_instructions
|
||||
.send(AppInstruction::Exit)
|
||||
self.send_client_instructions
|
||||
.send(ClientInstruction::Exit)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@ -328,22 +187,16 @@ pub fn get_mode_info(mode: InputMode, palette: Palette) -> ModeInfo {
|
||||
/// Entry point to the module. Instantiates an [`InputHandler`] and starts
|
||||
/// its [`InputHandler::handle_input()`] loop.
|
||||
pub fn input_loop(
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ClientOsApi>,
|
||||
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>,
|
||||
send_client_instructions: SenderWithContext<ClientInstruction>,
|
||||
) {
|
||||
let _handler = InputHandler::new(
|
||||
os_input,
|
||||
command_is_executing,
|
||||
config,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
send_client_instructions,
|
||||
)
|
||||
.handle_input();
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
//! IPC stuff for starting to split things into a client and server model.
|
||||
|
||||
use crate::common::errors::{get_current_ctx, ErrorContext};
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
use nix::unistd::dup;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::io::{self, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::unix::io::{AsRawFd, FromRawFd};
|
||||
|
||||
type SessionId = u64;
|
||||
|
||||
@ -24,7 +30,7 @@ pub enum ClientType {
|
||||
|
||||
// Types of messages sent from the client to the server
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum ClientToServerMsg {
|
||||
pub enum _ClientToServerMsg {
|
||||
// List which sessions are available
|
||||
ListSessions,
|
||||
// Create a new session
|
||||
@ -45,3 +51,69 @@ pub enum _ServerToClientMsg {
|
||||
// A list of sessions
|
||||
SessionList(HashSet<Session>),
|
||||
}
|
||||
|
||||
/// Sends messages on a stream socket, along with an [`ErrorContext`].
|
||||
pub struct IpcSenderWithContext<T: Serialize> {
|
||||
sender: io::BufWriter<LocalSocketStream>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Serialize> IpcSenderWithContext<T> {
|
||||
/// Returns a sender to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream).
|
||||
pub fn new(sender: LocalSocketStream) -> Self {
|
||||
Self {
|
||||
sender: io::BufWriter::new(sender),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends an event, along with the current [`ErrorContext`], on this [`IpcSenderWithContext`]'s socket.
|
||||
pub fn send(&mut self, msg: T) {
|
||||
let err_ctx = get_current_ctx();
|
||||
bincode::serialize_into(&mut self.sender, &(msg, err_ctx)).unwrap();
|
||||
self.sender.flush().unwrap();
|
||||
}
|
||||
|
||||
/// Returns an [`IpcReceiverWithContext`] with the same socket as this sender.
|
||||
pub fn get_receiver<F>(&self) -> IpcReceiverWithContext<F>
|
||||
where
|
||||
F: for<'de> Deserialize<'de> + Serialize,
|
||||
{
|
||||
let sock_fd = self.sender.get_ref().as_raw_fd();
|
||||
let dup_sock = dup(sock_fd).unwrap();
|
||||
let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) };
|
||||
IpcReceiverWithContext::new(socket)
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives messages on a stream socket, along with an [`ErrorContext`].
|
||||
pub struct IpcReceiverWithContext<T> {
|
||||
receiver: io::BufReader<LocalSocketStream>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> IpcReceiverWithContext<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de> + Serialize,
|
||||
{
|
||||
/// Returns a receiver to the given [LocalSocketStream](interprocess::local_socket::LocalSocketStream).
|
||||
pub fn new(receiver: LocalSocketStream) -> Self {
|
||||
Self {
|
||||
receiver: io::BufReader::new(receiver),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives an event, along with the current [`ErrorContext`], on this [`IpcReceiverWithContext`]'s socket.
|
||||
pub fn recv(&mut self) -> (T, ErrorContext) {
|
||||
bincode::deserialize_from(&mut self.receiver).unwrap()
|
||||
}
|
||||
|
||||
/// Returns an [`IpcSenderWithContext`] with the same socket as this receiver.
|
||||
pub fn get_sender<F: Serialize>(&self) -> IpcSenderWithContext<F> {
|
||||
let sock_fd = self.receiver.get_ref().as_raw_fd();
|
||||
let dup_sock = dup(sock_fd).unwrap();
|
||||
let socket = unsafe { LocalSocketStream::from_raw_fd(dup_sock) };
|
||||
IpcSenderWithContext::new(socket)
|
||||
}
|
||||
}
|
||||
|
@ -9,49 +9,12 @@ pub mod setup;
|
||||
pub mod utils;
|
||||
pub mod wasm_vm;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::{collections::HashMap, fs};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env,
|
||||
io::Write,
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::PaneId;
|
||||
use crate::server::ServerInstruction;
|
||||
use async_std::task_local;
|
||||
use command_is_executing::CommandIsExecuting;
|
||||
use directories_next::ProjectDirs;
|
||||
use errors::{
|
||||
get_current_ctx, AppContext, ContextType, ErrorContext, PluginContext, PtyContext,
|
||||
ScreenContext,
|
||||
};
|
||||
use input::handler::input_loop;
|
||||
use os_input_output::OsApi;
|
||||
use pty_bus::{PtyBus, PtyInstruction};
|
||||
use screen::{Screen, ScreenInstruction};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use setup::install;
|
||||
use utils::consts::ZELLIJ_IPC_PIPE;
|
||||
use wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction};
|
||||
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
|
||||
use wasmer_wasi::{Pipe, WasiState};
|
||||
use zellij_tile::data::{EventType, InputMode, ModeInfo};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum ApiCommand {
|
||||
OpenFile(PathBuf),
|
||||
SplitHorizontally,
|
||||
SplitVertically,
|
||||
MoveFocus,
|
||||
}
|
||||
use errors::{get_current_ctx, ErrorContext};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::mpsc;
|
||||
|
||||
/// An [MPSC](mpsc) asynchronous channel with added error context.
|
||||
pub type ChannelWithContext<T> = (
|
||||
@ -66,7 +29,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,7 +44,7 @@ pub struct SenderWithContext<T: Clone> {
|
||||
}
|
||||
|
||||
impl<T: Clone> SenderWithContext<T> {
|
||||
fn new(sender: SenderType<T>) -> Self {
|
||||
pub fn new(sender: SenderType<T>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
@ -102,7 +65,7 @@ unsafe impl<T: Clone> Sync for SenderWithContext<T> {}
|
||||
thread_local!(
|
||||
/// A key to some thread local storage (TLS) that holds a representation of the thread's call
|
||||
/// stack in the form of an [`ErrorContext`].
|
||||
static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
|
||||
pub static OPENCALLS: RefCell<ErrorContext> = RefCell::default()
|
||||
);
|
||||
|
||||
task_local! {
|
||||
@ -110,600 +73,3 @@ task_local! {
|
||||
/// stack in the form of an [`ErrorContext`].
|
||||
static ASYNCOPENCALLS: RefCell<ErrorContext> = RefCell::default()
|
||||
}
|
||||
|
||||
/// Instructions related to the entire application.
|
||||
#[derive(Clone)]
|
||||
pub enum AppInstruction {
|
||||
Exit,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
/// Start Zellij with the specified [`OsApi`] and command-line arguments.
|
||||
// FIXME this should definitely be modularized and split into different functions.
|
||||
pub fn start(mut os_input: Box<dyn OsApi>, opts: CliArgs, config: Config) {
|
||||
let take_snapshot = "\u{1b}[?1049h";
|
||||
os_input.unset_raw_mode(0);
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(take_snapshot.as_bytes())
|
||||
.unwrap();
|
||||
|
||||
env::set_var(&"ZELLIJ", "0");
|
||||
|
||||
let command_is_executing = CommandIsExecuting::new();
|
||||
|
||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||
os_input.set_raw_mode(0);
|
||||
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||
ScreenInstruction,
|
||||
> = mpsc::channel();
|
||||
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();
|
||||
let send_plugin_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
|
||||
|
||||
let (send_app_instructions, receive_app_instructions): SyncChannelWithContext<AppInstruction> =
|
||||
mpsc::sync_channel(0);
|
||||
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(),
|
||||
os_input.clone(),
|
||||
opts.debug,
|
||||
);
|
||||
|
||||
// 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;
|
||||
let send_app_instructions = send_app_instructions.clone();
|
||||
Box::new(move |info| {
|
||||
handle_panic(info, &send_app_instructions);
|
||||
})
|
||||
});
|
||||
|
||||
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;
|
||||
let colors = os_input.load_palette();
|
||||
move || {
|
||||
let mut screen = Screen::new(
|
||||
receive_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
&full_screen_ws,
|
||||
os_input,
|
||||
max_panes,
|
||||
ModeInfo {
|
||||
palette: colors,
|
||||
..ModeInfo::default()
|
||||
},
|
||||
InputMode::Normal,
|
||||
colors,
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
.receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
|
||||
match event {
|
||||
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
if active_tab.has_terminal_pid(pid) {
|
||||
// it's most likely that this event is directed at the active tab
|
||||
// look there first
|
||||
active_tab.handle_pty_bytes(pid, vte_bytes);
|
||||
} else {
|
||||
// if this event wasn't directed at the active tab, start looking
|
||||
// in other tabs
|
||||
let all_tabs = screen.get_tabs_mut();
|
||||
for tab in all_tabs.values_mut() {
|
||||
if tab.has_terminal_pid(pid) {
|
||||
tab.handle_pty_bytes(pid, vte_bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScreenInstruction::Render => {
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::NewPane(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().new_pane(pid);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::HorizontalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::VerticalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().vertical_split(pid);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::WriteCharacter(bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
match active_tab.is_sync_panes_active() {
|
||||
true => active_tab.write_to_terminals_on_current_tab(bytes),
|
||||
false => active_tab.write_to_active_terminal(bytes),
|
||||
}
|
||||
}
|
||||
ScreenInstruction::ResizeLeft => {
|
||||
screen.get_active_tab_mut().unwrap().resize_left();
|
||||
}
|
||||
ScreenInstruction::ResizeRight => {
|
||||
screen.get_active_tab_mut().unwrap().resize_right();
|
||||
}
|
||||
ScreenInstruction::ResizeDown => {
|
||||
screen.get_active_tab_mut().unwrap().resize_down();
|
||||
}
|
||||
ScreenInstruction::ResizeUp => {
|
||||
screen.get_active_tab_mut().unwrap().resize_up();
|
||||
}
|
||||
ScreenInstruction::SwitchFocus => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus();
|
||||
}
|
||||
ScreenInstruction::FocusNextPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_next_pane();
|
||||
}
|
||||
ScreenInstruction::FocusPreviousPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_previous_pane();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeft => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||
}
|
||||
ScreenInstruction::MoveFocusDown => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_down();
|
||||
}
|
||||
ScreenInstruction::MoveFocusRight => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_right();
|
||||
}
|
||||
ScreenInstruction::MoveFocusUp => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_up();
|
||||
}
|
||||
ScreenInstruction::ScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up();
|
||||
}
|
||||
ScreenInstruction::ScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down();
|
||||
}
|
||||
ScreenInstruction::PageScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up_page();
|
||||
}
|
||||
ScreenInstruction::PageScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down_page();
|
||||
}
|
||||
ScreenInstruction::ClearScroll => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.clear_active_terminal_scroll();
|
||||
}
|
||||
ScreenInstruction::CloseFocusedPane => {
|
||||
screen.get_active_tab_mut().unwrap().close_focused_pane();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::SetSelectable(id, selectable) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_selectable(id, selectable);
|
||||
}
|
||||
ScreenInstruction::SetMaxHeight(id, max_height) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_max_height(id, max_height);
|
||||
}
|
||||
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_invisible_borders(id, invisible_borders);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ClosePane(id) => {
|
||||
screen.get_active_tab_mut().unwrap().close_pane(id);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ToggleActiveTerminalFullscreen => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_active_pane_fullscreen();
|
||||
}
|
||||
ScreenInstruction::NewTab(pane_id) => {
|
||||
screen.new_tab(pane_id);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::SwitchTabNext => screen.switch_tab_next(),
|
||||
ScreenInstruction::SwitchTabPrev => screen.switch_tab_prev(),
|
||||
ScreenInstruction::CloseTab => screen.close_tab(),
|
||||
ScreenInstruction::ApplyLayout((layout, new_pane_pids)) => {
|
||||
screen.apply_layout(layout, new_pane_pids);
|
||||
command_is_executing.done_opening_new_pane();
|
||||
}
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize)
|
||||
}
|
||||
ScreenInstruction::UpdateTabName(c) => {
|
||||
screen.update_active_tab_name(c);
|
||||
}
|
||||
ScreenInstruction::TerminalResize => {
|
||||
screen.resize_to_screen();
|
||||
}
|
||||
ScreenInstruction::ChangeMode(mode_info) => {
|
||||
screen.change_mode(mode_info);
|
||||
}
|
||||
ScreenInstruction::ToggleActiveSyncPanes => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_sync_panes_is_active();
|
||||
screen.update_tabs();
|
||||
}
|
||||
ScreenInstruction::Quit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
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 store = Store::default();
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = receive_plugin_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path) => {
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let wasm_bytes = fs::read(&path)
|
||||
.or_else(|_| fs::read(&path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
|
||||
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(&store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.preopen(|p| {
|
||||
p.directory(".") // FIXME: Change this to a more meaningful dir
|
||||
.alias(".")
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
})
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
send_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(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(&store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.load()` method
|
||||
start.call(&[]).unwrap();
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
plugin_id += 1;
|
||||
}
|
||||
PluginInstruction::Update(pid, event) => {
|
||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||
let event_type = EventType::from_str(&event.to_string()).unwrap();
|
||||
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
|
||||
let update = instance.exports.get_function("update").unwrap();
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
drop(send_screen_instructions.send(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
|
||||
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
|
||||
PluginInstruction::Quit => break,
|
||||
}
|
||||
}
|
||||
})
|
||||
.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;
|
||||
move || {
|
||||
input_loop(
|
||||
os_input,
|
||||
config,
|
||||
command_is_executing,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
#[warn(clippy::never_loop)]
|
||||
loop {
|
||||
let (app_instruction, mut err_ctx) = receive_app_instructions
|
||||
.recv()
|
||||
.expect("failed to receive app instruction on channel");
|
||||
|
||||
err_ctx.add_call(ContextType::App(AppContext::from(&app_instruction)));
|
||||
match app_instruction {
|
||||
AppInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
AppInstruction::Error(backtrace) => {
|
||||
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);
|
||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||
let restore_snapshot = "\u{1b}[?1049l";
|
||||
let error = format!(
|
||||
"{}\n{}{}",
|
||||
goto_start_of_last_line, restore_snapshot, backtrace
|
||||
);
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(error.as_bytes())
|
||||
.unwrap();
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = send_pty_instructions.send(PtyInstruction::Quit);
|
||||
pty_thread.join().unwrap();
|
||||
let _ = send_screen_instructions.send(ScreenInstruction::Quit);
|
||||
screen_thread.join().unwrap();
|
||||
let _ = send_plugin_instructions.send(PluginInstruction::Quit);
|
||||
wasm_thread.join().unwrap();
|
||||
|
||||
// cleanup();
|
||||
let reset_style = "\u{1b}[m";
|
||||
let show_cursor = "\u{1b}[?25h";
|
||||
let restore_snapshot = "\u{1b}[?1049l";
|
||||
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
|
||||
let goodbye_message = format!(
|
||||
"{}\n{}{}{}Bye from Zellij!\n",
|
||||
goto_start_of_last_line, restore_snapshot, reset_style, show_cursor
|
||||
);
|
||||
|
||||
os_input.unset_raw_mode(0);
|
||||
let _ = os_input
|
||||
.get_stdout_writer()
|
||||
.write(goodbye_message.as_bytes())
|
||||
.unwrap();
|
||||
os_input.get_stdout_writer().flush().unwrap();
|
||||
}
|
||||
|
@ -1,23 +1,35 @@
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::{
|
||||
ipc::{IpcReceiverWithContext, IpcSenderWithContext},
|
||||
utils::consts::ZELLIJ_IPC_PIPE,
|
||||
};
|
||||
use crate::errors::ErrorContext;
|
||||
use crate::panes::PositionAndSize;
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::utils::shared::default_palette;
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
use nix::fcntl::{fcntl, FcntlArg, OFlag};
|
||||
use nix::pty::{forkpty, Winsize};
|
||||
use nix::sys::signal::{kill, Signal};
|
||||
use nix::sys::termios;
|
||||
use nix::sys::wait::waitpid;
|
||||
use nix::unistd;
|
||||
use nix::unistd::{ForkResult, Pid};
|
||||
use std::io;
|
||||
use nix::unistd::{self, ForkResult, Pid};
|
||||
use signal_hook::{consts::signal::*, iterator::Signals};
|
||||
use std::io::prelude::*;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::os::unix::{fs::PermissionsExt, io::RawFd};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{env, fs, io};
|
||||
use zellij_tile::data::Palette;
|
||||
|
||||
use signal_hook::{consts::signal::*, iterator::Signals};
|
||||
const UNIX_PERMISSIONS: u32 = 0o700;
|
||||
|
||||
use std::env;
|
||||
pub fn set_permissions(path: &Path) {
|
||||
let mut permissions = fs::metadata(path).unwrap().permissions();
|
||||
permissions.set_mode(UNIX_PERMISSIONS);
|
||||
fs::set_permissions(path, permissions).unwrap();
|
||||
}
|
||||
|
||||
fn into_raw_mode(pid: RawFd) {
|
||||
let mut tio = termios::tcgetattr(pid).expect("could not get terminal attribute");
|
||||
@ -156,23 +168,17 @@ fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OsInputOutput {
|
||||
pub struct ServerOsInputOutput {
|
||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
||||
receive_instructions_from_client: Option<Arc<Mutex<IpcReceiverWithContext<ServerInstruction>>>>,
|
||||
send_instructions_to_client: Arc<Mutex<Option<IpcSenderWithContext<ClientInstruction>>>>,
|
||||
}
|
||||
|
||||
/// The `OsApi` trait represents an abstract interface to the features of an operating system that
|
||||
/// Zellij requires.
|
||||
pub trait OsApi: Send + Sync {
|
||||
/// Returns the size of the terminal associated to file descriptor `fd`.
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize;
|
||||
/// The `ServerOsApi` trait represents an abstract interface to the features of an operating system that
|
||||
/// Zellij server requires.
|
||||
pub trait ServerOsApi: Send + Sync {
|
||||
/// Sets the size of the terminal associated to file descriptor `fd`.
|
||||
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16);
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn set_raw_mode(&mut self, fd: RawFd);
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn unset_raw_mode(&mut self, fd: RawFd);
|
||||
/// Spawn a new terminal, with an optional file to open in a terminal program.
|
||||
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
|
||||
/// Read bytes from the standard output of the virtual terminal referred to by `fd`.
|
||||
@ -186,30 +192,23 @@ pub trait OsApi: Send + Sync {
|
||||
// or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument
|
||||
// called `pid` and of type `pid_t`, and not `fd`)
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
/// Returns the raw contents of standard input.
|
||||
fn read_from_stdin(&self) -> Vec<u8>;
|
||||
/// Returns the writer that allows writing to standard output.
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||
/// Returns a [`Box`] pointer to this [`OsApi`] struct.
|
||||
fn box_clone(&self) -> Box<dyn OsApi>;
|
||||
fn receive_sigwinch(&self, cb: Box<dyn Fn()>);
|
||||
/// Returns a [`Box`] pointer to this [`ServerOsApi`] struct.
|
||||
fn box_clone(&self) -> Box<dyn ServerOsApi>;
|
||||
/// Receives a message on server-side IPC channel
|
||||
fn recv_from_client(&self) -> (ServerInstruction, ErrorContext);
|
||||
/// Sends a message to client
|
||||
fn send_to_client(&self, msg: ClientInstruction);
|
||||
/// Adds a sender to client
|
||||
fn add_client_sender(&mut self);
|
||||
/// Update the receiver socket for the client
|
||||
fn update_receiver(&mut self, stream: LocalSocketStream);
|
||||
fn load_palette(&self) -> Palette;
|
||||
}
|
||||
|
||||
impl OsApi for OsInputOutput {
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize {
|
||||
get_terminal_size_using_fd(fd)
|
||||
}
|
||||
impl ServerOsApi for ServerOsInputOutput {
|
||||
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) {
|
||||
set_terminal_size_using_fd(fd, cols, rows);
|
||||
}
|
||||
fn set_raw_mode(&mut self, fd: RawFd) {
|
||||
into_raw_mode(fd);
|
||||
}
|
||||
fn unset_raw_mode(&mut self, fd: RawFd) {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
unset_raw_mode(fd, orig_termios.clone());
|
||||
}
|
||||
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
spawn_terminal(file_to_open, orig_termios.clone())
|
||||
@ -223,7 +222,118 @@ impl OsApi for OsInputOutput {
|
||||
fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
termios::tcdrain(fd)
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn OsApi> {
|
||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
// TODO:
|
||||
// Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
|
||||
// the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
|
||||
// that's why we're sending SIGKILL here
|
||||
// A better solution would be to send SIGINT here and not wait for it, and then have
|
||||
// a background thread do the waitpid stuff and send SIGKILL if the process is stuck
|
||||
kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap();
|
||||
waitpid(Pid::from_raw(pid), None).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
fn recv_from_client(&self) -> (ServerInstruction, ErrorContext) {
|
||||
self.receive_instructions_from_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.recv()
|
||||
}
|
||||
fn send_to_client(&self, msg: ClientInstruction) {
|
||||
self.send_instructions_to_client
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(msg);
|
||||
}
|
||||
fn add_client_sender(&mut self) {
|
||||
assert!(self.send_instructions_to_client.lock().unwrap().is_none());
|
||||
let sender = self
|
||||
.receive_instructions_from_client
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get_sender();
|
||||
*self.send_instructions_to_client.lock().unwrap() = Some(sender);
|
||||
}
|
||||
fn update_receiver(&mut self, stream: LocalSocketStream) {
|
||||
self.receive_instructions_from_client =
|
||||
Some(Arc::new(Mutex::new(IpcReceiverWithContext::new(stream))));
|
||||
}
|
||||
fn load_palette(&self) -> Palette {
|
||||
default_palette()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn ServerOsApi> {
|
||||
fn clone(&self) -> Box<dyn ServerOsApi> {
|
||||
self.box_clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_server_os_input() -> ServerOsInputOutput {
|
||||
let current_termios = termios::tcgetattr(0).unwrap();
|
||||
let orig_termios = Arc::new(Mutex::new(current_termios));
|
||||
ServerOsInputOutput {
|
||||
orig_termios,
|
||||
receive_instructions_from_client: None,
|
||||
send_instructions_to_client: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientOsInputOutput {
|
||||
orig_termios: Arc<Mutex<termios::Termios>>,
|
||||
send_instructions_to_server: Arc<Mutex<Option<IpcSenderWithContext<ServerInstruction>>>>,
|
||||
receive_instructions_from_server: Arc<Mutex<Option<IpcReceiverWithContext<ClientInstruction>>>>,
|
||||
}
|
||||
|
||||
/// The `ClientOsApi` trait represents an abstract interface to the features of an operating system that
|
||||
/// Zellij client requires.
|
||||
pub trait ClientOsApi: Send + Sync {
|
||||
/// Returns the size of the terminal associated to file descriptor `fd`.
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize;
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [raw mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn set_raw_mode(&mut self, fd: RawFd);
|
||||
/// Set the terminal associated to file descriptor `fd` to
|
||||
/// [cooked mode](https://en.wikipedia.org/wiki/Terminal_mode).
|
||||
fn unset_raw_mode(&mut self, fd: RawFd);
|
||||
/// Returns the writer that allows writing to standard output.
|
||||
fn get_stdout_writer(&self) -> Box<dyn io::Write>;
|
||||
/// Returns the raw contents of standard input.
|
||||
fn read_from_stdin(&self) -> Vec<u8>;
|
||||
/// Returns a [`Box`] pointer to this [`ClientOsApi`] struct.
|
||||
fn box_clone(&self) -> Box<dyn ClientOsApi>;
|
||||
/// Sends a message to the server.
|
||||
fn send_to_server(&self, msg: ServerInstruction);
|
||||
/// Receives a message on client-side IPC channel
|
||||
// This should be called from the client-side router thread only.
|
||||
fn recv_from_server(&self) -> (ClientInstruction, ErrorContext);
|
||||
fn receive_sigwinch(&self, cb: Box<dyn Fn()>);
|
||||
/// Establish a connection with the server socket.
|
||||
fn connect_to_server(&self);
|
||||
}
|
||||
|
||||
impl ClientOsApi for ClientOsInputOutput {
|
||||
fn get_terminal_size_using_fd(&self, fd: RawFd) -> PositionAndSize {
|
||||
get_terminal_size_using_fd(fd)
|
||||
}
|
||||
fn set_raw_mode(&mut self, fd: RawFd) {
|
||||
into_raw_mode(fd);
|
||||
}
|
||||
fn unset_raw_mode(&mut self, fd: RawFd) {
|
||||
let orig_termios = self.orig_termios.lock().unwrap();
|
||||
unset_raw_mode(fd, orig_termios.clone());
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn read_from_stdin(&self) -> Vec<u8> {
|
||||
@ -239,16 +349,21 @@ impl OsApi for OsInputOutput {
|
||||
let stdout = ::std::io::stdout();
|
||||
Box::new(stdout)
|
||||
}
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
// TODO:
|
||||
// Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
|
||||
// the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
|
||||
// that's why we're sending SIGKILL here
|
||||
// A better solution would be to send SIGINT here and not wait for it, and then have
|
||||
// a background thread do the waitpid stuff and send SIGKILL if the process is stuck
|
||||
kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap();
|
||||
waitpid(Pid::from_raw(pid), None).unwrap();
|
||||
Ok(())
|
||||
fn send_to_server(&self, msg: ServerInstruction) {
|
||||
self.send_instructions_to_server
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send(msg);
|
||||
}
|
||||
fn recv_from_server(&self) -> (ClientInstruction, ErrorContext) {
|
||||
self.receive_instructions_from_server
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.recv()
|
||||
}
|
||||
fn receive_sigwinch(&self, cb: Box<dyn Fn()>) {
|
||||
let mut signals = Signals::new(&[SIGWINCH, SIGTERM, SIGINT, SIGQUIT]).unwrap();
|
||||
@ -264,19 +379,33 @@ impl OsApi for OsInputOutput {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load_palette(&self) -> Palette {
|
||||
default_palette()
|
||||
fn connect_to_server(&self) {
|
||||
let socket = match LocalSocketStream::connect(&**ZELLIJ_IPC_PIPE) {
|
||||
Ok(sock) => sock,
|
||||
Err(_) => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
LocalSocketStream::connect(&**ZELLIJ_IPC_PIPE).unwrap()
|
||||
}
|
||||
};
|
||||
let sender = IpcSenderWithContext::new(socket);
|
||||
let receiver = sender.get_receiver();
|
||||
*self.send_instructions_to_server.lock().unwrap() = Some(sender);
|
||||
*self.receive_instructions_from_server.lock().unwrap() = Some(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn OsApi> {
|
||||
fn clone(&self) -> Box<dyn OsApi> {
|
||||
impl Clone for Box<dyn ClientOsApi> {
|
||||
fn clone(&self) -> Box<dyn ClientOsApi> {
|
||||
self.box_clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_os_input() -> OsInputOutput {
|
||||
pub fn get_client_os_input() -> ClientOsInputOutput {
|
||||
let current_termios = termios::tcgetattr(0).unwrap();
|
||||
let orig_termios = Arc::new(Mutex::new(current_termios));
|
||||
OsInputOutput { orig_termios }
|
||||
ClientOsInputOutput {
|
||||
orig_termios,
|
||||
send_instructions_to_server: Arc::new(Mutex::new(None)),
|
||||
receive_instructions_from_server: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
@ -8,22 +8,24 @@ use ::std::sync::mpsc::Receiver;
|
||||
use ::std::time::{Duration, Instant};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{ScreenInstruction, SenderWithContext};
|
||||
use crate::os_input_output::OsApi;
|
||||
use super::SenderWithContext;
|
||||
use crate::layout::Layout;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::utils::logging::debug_to_file;
|
||||
use crate::{
|
||||
errors::{get_current_ctx, ContextType, ErrorContext},
|
||||
panes::PaneId,
|
||||
screen::ScreenInstruction,
|
||||
wasm_vm::PluginInstruction,
|
||||
};
|
||||
use crate::{layout::Layout, wasm_vm::PluginInstruction};
|
||||
|
||||
pub struct ReadFromPid {
|
||||
pid: RawFd,
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
}
|
||||
|
||||
impl ReadFromPid {
|
||||
pub fn new(pid: &RawFd, os_input: Box<dyn OsApi>) -> ReadFromPid {
|
||||
pub fn new(pid: &RawFd, os_input: Box<dyn ServerOsApi>) -> ReadFromPid {
|
||||
ReadFromPid {
|
||||
pid: *pid,
|
||||
os_input,
|
||||
@ -74,15 +76,15 @@ pub enum PtyInstruction {
|
||||
NewTab,
|
||||
ClosePane(PaneId),
|
||||
CloseTab(Vec<PaneId>),
|
||||
Quit,
|
||||
Exit,
|
||||
}
|
||||
|
||||
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>,
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug_to_file: bool,
|
||||
task_handles: HashMap<RawFd, JoinHandle<()>>,
|
||||
}
|
||||
@ -90,7 +92,7 @@ pub struct PtyBus {
|
||||
fn stream_terminal_bytes(
|
||||
pid: RawFd,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug: bool,
|
||||
) -> JoinHandle<()> {
|
||||
let mut err_ctx = get_current_ctx();
|
||||
@ -122,7 +124,9 @@ fn stream_terminal_bytes(
|
||||
Some(receive_time) => {
|
||||
if receive_time.elapsed() > max_render_pause {
|
||||
pending_render = false;
|
||||
let _ = send_screen_instructions.send(ScreenInstruction::Render);
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
last_byte_receive_time = Some(Instant::now());
|
||||
} else {
|
||||
pending_render = true;
|
||||
@ -136,7 +140,9 @@ fn stream_terminal_bytes(
|
||||
} else {
|
||||
if pending_render {
|
||||
pending_render = false;
|
||||
let _ = send_screen_instructions.send(ScreenInstruction::Render);
|
||||
send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
}
|
||||
last_byte_receive_time = None;
|
||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
||||
@ -161,15 +167,15 @@ impl PtyBus {
|
||||
receive_pty_instructions: Receiver<(PtyInstruction, ErrorContext)>,
|
||||
send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
os_input: Box<dyn OsApi>,
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
debug_to_file: bool,
|
||||
) -> Self {
|
||||
PtyBus {
|
||||
send_screen_instructions,
|
||||
send_plugin_instructions,
|
||||
receive_pty_instructions,
|
||||
os_input,
|
||||
id_to_child_pid: HashMap::new(),
|
||||
send_screen_instructions,
|
||||
send_plugin_instructions,
|
||||
debug_to_file,
|
||||
task_handles: HashMap::new(),
|
||||
}
|
||||
@ -196,10 +202,10 @@ impl PtyBus {
|
||||
new_pane_pids.push(pid_primary);
|
||||
}
|
||||
self.send_screen_instructions
|
||||
.send(ScreenInstruction::ApplyLayout((
|
||||
.send(ScreenInstruction::ApplyLayout(
|
||||
layout,
|
||||
new_pane_pids.clone(),
|
||||
)))
|
||||
))
|
||||
.unwrap();
|
||||
for id in new_pane_pids {
|
||||
let task_handle = stream_terminal_bytes(
|
||||
@ -221,10 +227,10 @@ impl PtyBus {
|
||||
handle.cancel().await;
|
||||
});
|
||||
}
|
||||
PaneId::Plugin(pid) => drop(
|
||||
self.send_plugin_instructions
|
||||
.send(PluginInstruction::Unload(pid)),
|
||||
),
|
||||
PaneId::Plugin(pid) => self
|
||||
.send_plugin_instructions
|
||||
.send(PluginInstruction::Unload(pid))
|
||||
.unwrap(),
|
||||
}
|
||||
}
|
||||
pub fn close_tab(&mut self, ids: Vec<PaneId>) {
|
||||
|
@ -5,10 +5,11 @@ use std::os::unix::io::RawFd;
|
||||
use std::str;
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use super::{AppInstruction, SenderWithContext};
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::common::SenderWithContext;
|
||||
use crate::os_input_output::ServerOsApi;
|
||||
use crate::panes::PositionAndSize;
|
||||
use crate::pty_bus::{PtyInstruction, VteBytes};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::tab::Tab;
|
||||
use crate::{errors::ErrorContext, wasm_vm::PluginInstruction};
|
||||
use crate::{layout::Layout, panes::PaneId};
|
||||
@ -35,7 +36,7 @@ pub enum ScreenInstruction {
|
||||
MoveFocusDown,
|
||||
MoveFocusUp,
|
||||
MoveFocusRight,
|
||||
Quit,
|
||||
Exit,
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
PageScrollUp,
|
||||
@ -47,7 +48,7 @@ pub enum ScreenInstruction {
|
||||
SetMaxHeight(PaneId, usize),
|
||||
SetInvisibleBorders(PaneId, bool),
|
||||
ClosePane(PaneId),
|
||||
ApplyLayout((Layout, Vec<RawFd>)),
|
||||
ApplyLayout(Layout, Vec<RawFd>),
|
||||
NewTab(RawFd),
|
||||
SwitchTabNext,
|
||||
SwitchTabPrev,
|
||||
@ -55,7 +56,7 @@ pub enum ScreenInstruction {
|
||||
CloseTab,
|
||||
GoToTab(u32),
|
||||
UpdateTabName(Vec<u8>),
|
||||
TerminalResize,
|
||||
TerminalResize(PositionAndSize),
|
||||
ChangeMode(ModeInfo),
|
||||
}
|
||||
|
||||
@ -68,18 +69,18 @@ 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.
|
||||
pub send_app_instructions: SenderWithContext<AppInstruction>,
|
||||
/// An [`PtyInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
/// An [`ServerInstruction`] and [`ErrorContext`] sender.
|
||||
pub send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
/// The full size of this [`Screen`].
|
||||
full_screen_ws: PositionAndSize,
|
||||
/// The index of this [`Screen`]'s active [`Tab`].
|
||||
active_tab_index: Option<usize>,
|
||||
/// The [`OsApi`] this [`Screen`] uses.
|
||||
os_api: Box<dyn OsApi>,
|
||||
/// The [`ServerOsApi`] this [`Screen`] uses.
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
mode_info: ModeInfo,
|
||||
input_mode: InputMode,
|
||||
colors: Palette,
|
||||
@ -91,11 +92,11 @@ 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>,
|
||||
send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
full_screen_ws: &PositionAndSize,
|
||||
os_api: Box<dyn OsApi>,
|
||||
os_api: Box<dyn ServerOsApi>,
|
||||
max_panes: Option<usize>,
|
||||
mode_info: ModeInfo,
|
||||
input_mode: InputMode,
|
||||
@ -104,9 +105,9 @@ impl Screen {
|
||||
Screen {
|
||||
receiver: receive_screen_instructions,
|
||||
max_panes,
|
||||
send_pty_instructions,
|
||||
send_plugin_instructions,
|
||||
send_app_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
full_screen_ws: *full_screen_ws,
|
||||
active_tab_index: None,
|
||||
tabs: BTreeMap::new(),
|
||||
@ -128,9 +129,9 @@ 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.send_pty_instructions.clone(),
|
||||
self.send_server_instructions.clone(),
|
||||
self.max_panes,
|
||||
Some(PaneId::Terminal(pane_id)),
|
||||
self.mode_info.clone(),
|
||||
@ -214,13 +215,13 @@ impl Screen {
|
||||
// below we don't check the result of sending the CloseTab instruction to the pty thread
|
||||
// because this might be happening when the app is closing, at which point the pty thread
|
||||
// has already closed and this would result in an error
|
||||
let _ = self
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::CloseTab(pane_ids));
|
||||
self.send_pty_instructions
|
||||
.send(PtyInstruction::CloseTab(pane_ids))
|
||||
.unwrap();
|
||||
if self.tabs.is_empty() {
|
||||
self.active_tab_index = None;
|
||||
self.send_app_instructions
|
||||
.send(AppInstruction::Exit)
|
||||
self.send_server_instructions
|
||||
.send(ServerInstruction::Render(None))
|
||||
.unwrap();
|
||||
} else {
|
||||
for t in self.tabs.values_mut() {
|
||||
@ -232,8 +233,7 @@ impl Screen {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_to_screen(&mut self) {
|
||||
let new_screen_size = self.os_api.get_terminal_size_using_fd(0);
|
||||
pub fn resize_to_screen(&mut self, new_screen_size: PositionAndSize) {
|
||||
self.full_screen_ws = new_screen_size;
|
||||
for (_, tab) in self.tabs.iter_mut() {
|
||||
tab.resize_whole_tab(new_screen_size);
|
||||
@ -285,9 +285,9 @@ 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.send_pty_instructions.clone(),
|
||||
self.send_server_instructions.clone(),
|
||||
self.max_panes,
|
||||
None,
|
||||
self.mode_info.clone(),
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::common::utils::consts::SYSTEM_DEFAULT_CONFIG_DIR;
|
||||
use crate::common::utils::consts::{SYSTEM_DEFAULT_CONFIG_DIR, VERSION};
|
||||
use crate::os_input_output::set_permissions;
|
||||
use directories_next::{BaseDirs, ProjectDirs};
|
||||
use std::io::Write;
|
||||
use std::{fs, path::Path, path::PathBuf};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const CONFIG_LOCATION: &str = "/.config/zellij";
|
||||
|
||||
#[macro_export]
|
||||
@ -40,7 +40,9 @@ pub mod install {
|
||||
|
||||
for (path, bytes) in assets {
|
||||
let path = data_dir.join(path);
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
let parent_path = path.parent().unwrap();
|
||||
fs::create_dir_all(parent_path).unwrap();
|
||||
set_permissions(parent_path);
|
||||
if out_of_date || !path.exists() {
|
||||
fs::write(path, bytes).expect("Failed to install default assets!");
|
||||
}
|
||||
|
@ -1,12 +1,41 @@
|
||||
//! Zellij program-wide constants.
|
||||
|
||||
pub const ZELLIJ_TMP_DIR: &str = "/tmp/zellij";
|
||||
pub const ZELLIJ_TMP_LOG_DIR: &str = "/tmp/zellij/zellij-log";
|
||||
pub const ZELLIJ_TMP_LOG_FILE: &str = "/tmp/zellij/zellij-log/log.txt";
|
||||
pub const ZELLIJ_IPC_PIPE: &str = "/tmp/zellij/ipc";
|
||||
use crate::os_input_output::set_permissions;
|
||||
use directories_next::ProjectDirs;
|
||||
use lazy_static::lazy_static;
|
||||
use nix::unistd::Uid;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
pub const ZELLIJ_CONFIG_FILE_ENV: &str = "ZELLIJ_CONFIG_FILE";
|
||||
pub const ZELLIJ_CONFIG_DIR_ENV: &str = "ZELLIJ_CONFIG_DIR";
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
// TODO: ${PREFIX} argument in makefile
|
||||
pub const SYSTEM_DEFAULT_CONFIG_DIR: &str = "/etc/zellij";
|
||||
|
||||
lazy_static! {
|
||||
static ref UID: Uid = Uid::current();
|
||||
pub static ref SESSION_NAME: String = names::Generator::default().next().unwrap();
|
||||
pub static ref ZELLIJ_PROJ_DIR: ProjectDirs =
|
||||
ProjectDirs::from("org", "Zellij Contributors", "Zellij").unwrap();
|
||||
pub static ref ZELLIJ_IPC_PIPE: PathBuf = {
|
||||
let mut ipc_dir = env::var("ZELLIJ_SOCKET_DIR").map_or_else(
|
||||
|_| {
|
||||
ZELLIJ_PROJ_DIR
|
||||
.runtime_dir()
|
||||
.map_or_else(|| ZELLIJ_TMP_DIR.clone(), |p| p.to_owned())
|
||||
},
|
||||
PathBuf::from,
|
||||
);
|
||||
ipc_dir.push(VERSION);
|
||||
fs::create_dir_all(&ipc_dir).unwrap();
|
||||
set_permissions(&ipc_dir);
|
||||
ipc_dir.push(&*SESSION_NAME);
|
||||
ipc_dir
|
||||
};
|
||||
pub static ref ZELLIJ_TMP_DIR: PathBuf =
|
||||
PathBuf::from("/tmp/zellij-".to_string() + &format!("{}", *UID));
|
||||
pub static ref ZELLIJ_TMP_LOG_DIR: PathBuf = ZELLIJ_TMP_DIR.join("zellij-log");
|
||||
pub static ref ZELLIJ_TMP_LOG_FILE: PathBuf = ZELLIJ_TMP_LOG_DIR.join("log.txt");
|
||||
}
|
||||
|
@ -4,17 +4,20 @@ use std::{
|
||||
fs,
|
||||
io::{self, prelude::*},
|
||||
os::unix::io::RawFd,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::os_input_output::set_permissions;
|
||||
use crate::utils::consts::{ZELLIJ_TMP_LOG_DIR, ZELLIJ_TMP_LOG_FILE};
|
||||
|
||||
pub fn atomic_create_file(file_name: &str) {
|
||||
pub fn atomic_create_file(file_name: &Path) {
|
||||
let _ = fs::OpenOptions::new().create(true).open(file_name);
|
||||
#[cfg(not(test))]
|
||||
set_permissions(file_name);
|
||||
}
|
||||
|
||||
pub fn atomic_create_dir(dir_name: &str) -> io::Result<()> {
|
||||
if let Err(e) = fs::create_dir(dir_name) {
|
||||
pub fn atomic_create_dir(dir_name: &Path) -> io::Result<()> {
|
||||
let result = if let Err(e) = fs::create_dir(dir_name) {
|
||||
if e.kind() == std::io::ErrorKind::AlreadyExists {
|
||||
Ok(())
|
||||
} else {
|
||||
@ -22,7 +25,11 @@ pub fn atomic_create_dir(dir_name: &str) -> io::Result<()> {
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
if result.is_ok() {
|
||||
set_permissions(dir_name);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn debug_log_to_file(mut message: String) -> io::Result<()> {
|
||||
@ -31,11 +38,11 @@ pub fn debug_log_to_file(mut message: String) -> io::Result<()> {
|
||||
}
|
||||
|
||||
pub fn debug_log_to_file_without_newline(message: String) -> io::Result<()> {
|
||||
atomic_create_file(ZELLIJ_TMP_LOG_FILE);
|
||||
atomic_create_file(&*ZELLIJ_TMP_LOG_FILE);
|
||||
let mut file = fs::OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(ZELLIJ_TMP_LOG_FILE)?;
|
||||
.open(&*ZELLIJ_TMP_LOG_FILE)?;
|
||||
file.write_all(message.as_bytes())
|
||||
}
|
||||
|
||||
@ -48,16 +55,16 @@ pub fn _debug_log_to_file_pid_3(message: String, pid: RawFd) -> io::Result<()> {
|
||||
}
|
||||
|
||||
pub fn _delete_log_file() -> io::Result<()> {
|
||||
if fs::metadata(ZELLIJ_TMP_LOG_FILE).is_ok() {
|
||||
fs::remove_file(ZELLIJ_TMP_LOG_FILE)
|
||||
if fs::metadata(&*ZELLIJ_TMP_LOG_FILE).is_ok() {
|
||||
fs::remove_file(&*ZELLIJ_TMP_LOG_FILE)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn _delete_log_dir() -> io::Result<()> {
|
||||
if fs::metadata(ZELLIJ_TMP_LOG_DIR).is_ok() {
|
||||
fs::remove_dir_all(ZELLIJ_TMP_LOG_DIR)
|
||||
if fs::metadata(&*ZELLIJ_TMP_LOG_DIR).is_ok() {
|
||||
fs::remove_dir_all(&*ZELLIJ_TMP_LOG_DIR)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@ -65,7 +72,7 @@ pub fn _delete_log_dir() -> io::Result<()> {
|
||||
|
||||
pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
|
||||
let mut path = PathBuf::new();
|
||||
path.push(ZELLIJ_TMP_LOG_DIR);
|
||||
path.push(&*ZELLIJ_TMP_LOG_DIR);
|
||||
path.push(format!("zellij-{}.log", pid.to_string()));
|
||||
|
||||
let mut file = fs::OpenOptions::new()
|
||||
|
@ -11,9 +11,7 @@ use wasmer::{imports, Function, ImportObject, Store, WasmerEnv};
|
||||
use wasmer_wasi::WasiEnv;
|
||||
use zellij_tile::data::{Event, EventType, PluginIds};
|
||||
|
||||
use super::{
|
||||
pty_bus::PtyInstruction, screen::ScreenInstruction, AppInstruction, PaneId, SenderWithContext,
|
||||
};
|
||||
use super::{pty_bus::PtyInstruction, screen::ScreenInstruction, PaneId, SenderWithContext};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PluginInstruction {
|
||||
@ -21,7 +19,7 @@ pub enum PluginInstruction {
|
||||
Update(Option<u32>, Event), // Focused plugin / broadcast, event data
|
||||
Render(Sender<String>, u32, usize, usize), // String buffer, plugin id, rows, cols
|
||||
Unload(u32),
|
||||
Quit,
|
||||
Exit,
|
||||
}
|
||||
|
||||
#[derive(WasmerEnv, Clone)]
|
||||
@ -29,7 +27,6 @@ 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 wasi_env: WasiEnv,
|
||||
|
66
src/main.rs
66
src/main.rs
@ -1,27 +1,26 @@
|
||||
mod cli;
|
||||
mod client;
|
||||
mod common;
|
||||
mod server;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
// TODO mod server;
|
||||
mod client;
|
||||
|
||||
use client::{boundaries, layout, panes, start_client, tab};
|
||||
use common::{
|
||||
command_is_executing, errors, os_input_output, pty_bus, screen, setup, utils, wasm_vm,
|
||||
};
|
||||
use server::start_server;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::cli::CliArgs;
|
||||
use crate::command_is_executing::CommandIsExecuting;
|
||||
use crate::common::input::config::Config;
|
||||
use crate::os_input_output::get_os_input;
|
||||
use crate::os_input_output::{get_client_os_input, get_server_os_input, ClientOsApi, ServerOsApi};
|
||||
use crate::utils::{
|
||||
consts::{ZELLIJ_IPC_PIPE, ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
||||
consts::{ZELLIJ_TMP_DIR, ZELLIJ_TMP_LOG_DIR},
|
||||
logging::*,
|
||||
};
|
||||
use client::{boundaries, layout, panes, tab};
|
||||
use common::{
|
||||
command_is_executing, errors, os_input_output, pty_bus, screen, setup, start, utils, wasm_vm,
|
||||
ApiCommand,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
use std::io::Write;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use structopt::StructOpt;
|
||||
|
||||
pub fn main() {
|
||||
let opts = CliArgs::from_args();
|
||||
@ -32,29 +31,7 @@ pub fn main() {
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if let Some(split_dir) = opts.split {
|
||||
match split_dir {
|
||||
'h' => {
|
||||
let mut stream = UnixStream::connect(ZELLIJ_IPC_PIPE).unwrap();
|
||||
let api_command = bincode::serialize(&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();
|
||||
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();
|
||||
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();
|
||||
stream.write_all(&api_command).unwrap();
|
||||
} else if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
|
||||
if let Some(crate::cli::ConfigCli::GenerateCompletion { shell }) = opts.option {
|
||||
let shell = match shell.as_ref() {
|
||||
"bash" => structopt::clap::Shell::Bash,
|
||||
"fish" => structopt::clap::Shell::Fish,
|
||||
@ -72,9 +49,20 @@ pub fn main() {
|
||||
setup::dump_default_config().expect("Failed to print to stdout");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
let os_input = get_os_input();
|
||||
atomic_create_dir(ZELLIJ_TMP_DIR).unwrap();
|
||||
atomic_create_dir(ZELLIJ_TMP_LOG_DIR).unwrap();
|
||||
start(Box::new(os_input), opts, config);
|
||||
atomic_create_dir(&*ZELLIJ_TMP_DIR).unwrap();
|
||||
atomic_create_dir(&*ZELLIJ_TMP_LOG_DIR).unwrap();
|
||||
let server_os_input = get_server_os_input();
|
||||
let os_input = get_client_os_input();
|
||||
start(Box::new(os_input), opts, Box::new(server_os_input), config);
|
||||
}
|
||||
}
|
||||
pub fn start(
|
||||
client_os_input: Box<dyn ClientOsApi>,
|
||||
opts: CliArgs,
|
||||
server_os_input: Box<dyn ServerOsApi>,
|
||||
config: Config,
|
||||
) {
|
||||
let ipc_thread = start_server(server_os_input);
|
||||
start_client(client_os_input, opts, config);
|
||||
drop(ipc_thread.join());
|
||||
}
|
||||
|
@ -1,5 +1,811 @@
|
||||
use super::super::common::{screen};
|
||||
use interprocess::local_socket::LocalSocketListener;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::thread;
|
||||
use std::{collections::HashMap, fs};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
};
|
||||
use wasmer::{ChainableNamedResolver, Instance, Module, Store, Value};
|
||||
use wasmer_wasi::{Pipe, WasiState};
|
||||
use zellij_tile::data::{Event, EventType, InputMode, ModeInfo};
|
||||
|
||||
pub fn start_server() {
|
||||
// TODO
|
||||
}
|
||||
use crate::cli::CliArgs;
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::{
|
||||
errors::{ContextType, PluginContext, PtyContext, ScreenContext, ServerContext},
|
||||
input::actions::{Action, Direction},
|
||||
input::handler::get_mode_info,
|
||||
os_input_output::{set_permissions, ServerOsApi},
|
||||
pty_bus::{PtyBus, PtyInstruction},
|
||||
screen::{Screen, ScreenInstruction},
|
||||
setup::install::populate_data_dir,
|
||||
utils::consts::{ZELLIJ_IPC_PIPE, ZELLIJ_PROJ_DIR},
|
||||
wasm_vm::{wasi_read_string, wasi_write_object, zellij_exports, PluginEnv, PluginInstruction},
|
||||
ChannelWithContext, SenderType, SenderWithContext,
|
||||
};
|
||||
use crate::layout::Layout;
|
||||
use crate::panes::PaneId;
|
||||
use crate::panes::PositionAndSize;
|
||||
|
||||
/// Instructions related to server-side application including the
|
||||
/// ones sent by client to server
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum ServerInstruction {
|
||||
TerminalResize(PositionAndSize),
|
||||
NewClient(PositionAndSize, CliArgs),
|
||||
Action(Action),
|
||||
Render(Option<String>),
|
||||
UnblockInputThread,
|
||||
ClientExit,
|
||||
}
|
||||
|
||||
struct SessionMetaData {
|
||||
pub send_pty_instructions: SenderWithContext<PtyInstruction>,
|
||||
pub send_screen_instructions: SenderWithContext<ScreenInstruction>,
|
||||
pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
|
||||
screen_thread: Option<thread::JoinHandle<()>>,
|
||||
pty_thread: Option<thread::JoinHandle<()>>,
|
||||
wasm_thread: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Drop for SessionMetaData {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.send_pty_instructions.send(PtyInstruction::Exit);
|
||||
let _ = self.send_screen_instructions.send(ScreenInstruction::Exit);
|
||||
let _ = self.send_plugin_instructions.send(PluginInstruction::Exit);
|
||||
let _ = self.screen_thread.take().unwrap().join();
|
||||
let _ = self.pty_thread.take().unwrap().join();
|
||||
let _ = self.wasm_thread.take().unwrap().join();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_server(os_input: Box<dyn ServerOsApi>) -> thread::JoinHandle<()> {
|
||||
let (send_server_instructions, receive_server_instructions): ChannelWithContext<
|
||||
ServerInstruction,
|
||||
> = channel();
|
||||
let send_server_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_server_instructions));
|
||||
let sessions: Arc<RwLock<Option<SessionMetaData>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
#[cfg(test)]
|
||||
handle_client(
|
||||
sessions.clone(),
|
||||
os_input.clone(),
|
||||
send_server_instructions.clone(),
|
||||
);
|
||||
#[cfg(not(test))]
|
||||
let _ = thread::Builder::new()
|
||||
.name("server_listener".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let sessions = sessions.clone();
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
move || {
|
||||
drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
|
||||
let listener = LocalSocketListener::bind(&**ZELLIJ_IPC_PIPE).unwrap();
|
||||
set_permissions(&*ZELLIJ_IPC_PIPE);
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(stream) => {
|
||||
let mut os_input = os_input.clone();
|
||||
os_input.update_receiver(stream);
|
||||
let sessions = sessions.clone();
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
handle_client(sessions, os_input, send_server_instructions);
|
||||
}
|
||||
Err(err) => {
|
||||
panic!("err {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread::Builder::new()
|
||||
.name("server_thread".to_string())
|
||||
.spawn({
|
||||
move || loop {
|
||||
let (instruction, mut err_ctx) = receive_server_instructions.recv().unwrap();
|
||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
||||
match instruction {
|
||||
ServerInstruction::NewClient(full_screen_ws, opts) => {
|
||||
let session_data = init_session(
|
||||
os_input.clone(),
|
||||
opts,
|
||||
send_server_instructions.clone(),
|
||||
full_screen_ws,
|
||||
);
|
||||
*sessions.write().unwrap() = Some(session_data);
|
||||
sessions
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::UnblockInputThread => {
|
||||
os_input.send_to_client(ClientInstruction::UnblockInputThread);
|
||||
}
|
||||
ServerInstruction::ClientExit => {
|
||||
*sessions.write().unwrap() = None;
|
||||
os_input.send_to_client(ClientInstruction::Exit);
|
||||
drop(std::fs::remove_file(&*ZELLIJ_IPC_PIPE));
|
||||
break;
|
||||
}
|
||||
ServerInstruction::Render(output) => {
|
||||
os_input.send_to_client(ClientInstruction::Render(output))
|
||||
}
|
||||
_ => panic!("Received unexpected instruction."),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn handle_client(
|
||||
sessions: Arc<RwLock<Option<SessionMetaData>>>,
|
||||
mut os_input: Box<dyn ServerOsApi>,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
) {
|
||||
thread::Builder::new()
|
||||
.name("server_router".to_string())
|
||||
.spawn(move || loop {
|
||||
let (instruction, mut err_ctx) = os_input.recv_from_client();
|
||||
err_ctx.add_call(ContextType::IPCServer(ServerContext::from(&instruction)));
|
||||
let rlocked_sessions = sessions.read().unwrap();
|
||||
match instruction {
|
||||
ServerInstruction::ClientExit => {
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
break;
|
||||
}
|
||||
ServerInstruction::Action(action) => {
|
||||
route_action(action, rlocked_sessions.as_ref().unwrap(), &*os_input);
|
||||
}
|
||||
ServerInstruction::TerminalResize(new_size) => {
|
||||
rlocked_sessions
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::TerminalResize(new_size))
|
||||
.unwrap();
|
||||
}
|
||||
ServerInstruction::NewClient(..) => {
|
||||
os_input.add_client_sender();
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
}
|
||||
_ => {
|
||||
send_server_instructions.send(instruction).unwrap();
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn init_session(
|
||||
os_input: Box<dyn ServerOsApi>,
|
||||
opts: CliArgs,
|
||||
send_server_instructions: SenderWithContext<ServerInstruction>,
|
||||
full_screen_ws: PositionAndSize,
|
||||
) -> SessionMetaData {
|
||||
let (send_screen_instructions, receive_screen_instructions): ChannelWithContext<
|
||||
ScreenInstruction,
|
||||
> = channel();
|
||||
let send_screen_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_screen_instructions));
|
||||
|
||||
let (send_plugin_instructions, receive_plugin_instructions): ChannelWithContext<
|
||||
PluginInstruction,
|
||||
> = channel();
|
||||
let send_plugin_instructions =
|
||||
SenderWithContext::new(SenderType::Sender(send_plugin_instructions));
|
||||
let (send_pty_instructions, receive_pty_instructions): ChannelWithContext<PtyInstruction> =
|
||||
channel();
|
||||
let send_pty_instructions = SenderWithContext::new(SenderType::Sender(send_pty_instructions));
|
||||
|
||||
// Determine and initialize the data directory
|
||||
let data_dir = opts
|
||||
.data_dir
|
||||
.unwrap_or_else(|| ZELLIJ_PROJ_DIR.data_dir().to_path_buf());
|
||||
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)));
|
||||
|
||||
let mut pty_bus = PtyBus::new(
|
||||
receive_pty_instructions,
|
||||
send_screen_instructions.clone(),
|
||||
send_plugin_instructions.clone(),
|
||||
os_input.clone(),
|
||||
opts.debug,
|
||||
);
|
||||
|
||||
let pty_thread = thread::Builder::new()
|
||||
.name("pty".to_string())
|
||||
.spawn({
|
||||
let send_server_instructions = send_server_instructions.clone();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = pty_bus
|
||||
.receive_pty_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Pty(PtyContext::from(&event)));
|
||||
match event {
|
||||
PtyInstruction::SpawnTerminal(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::NewPane(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalVertically(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::VerticalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::SpawnTerminalHorizontally(file_to_open) => {
|
||||
let pid = pty_bus.spawn_terminal(file_to_open);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::HorizontalSplit(PaneId::Terminal(pid)))
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::NewTab => {
|
||||
if let Some(layout) = maybe_layout.clone() {
|
||||
pty_bus.spawn_terminals_for_layout(layout);
|
||||
} else {
|
||||
let pid = pty_bus.spawn_terminal(None);
|
||||
pty_bus
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::NewTab(pid))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
PtyInstruction::ClosePane(id) => {
|
||||
pty_bus.close_pane(id);
|
||||
send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::CloseTab(ids) => {
|
||||
pty_bus.close_tab(ids);
|
||||
send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
PtyInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let screen_thread = thread::Builder::new()
|
||||
.name("screen".to_string())
|
||||
.spawn({
|
||||
let os_input = os_input.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_server_instructions = send_server_instructions;
|
||||
let max_panes = opts.max_panes;
|
||||
let colors = os_input.load_palette();
|
||||
|
||||
move || {
|
||||
let mut screen = Screen::new(
|
||||
receive_screen_instructions,
|
||||
send_plugin_instructions,
|
||||
send_pty_instructions,
|
||||
send_server_instructions,
|
||||
&full_screen_ws,
|
||||
os_input,
|
||||
max_panes,
|
||||
ModeInfo {
|
||||
palette: colors,
|
||||
..ModeInfo::default()
|
||||
},
|
||||
InputMode::Normal,
|
||||
colors,
|
||||
);
|
||||
loop {
|
||||
let (event, mut err_ctx) = screen
|
||||
.receiver
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Screen(ScreenContext::from(&event)));
|
||||
match event {
|
||||
ScreenInstruction::PtyBytes(pid, vte_bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
if active_tab.has_terminal_pid(pid) {
|
||||
// it's most likely that this event is directed at the active tab
|
||||
// look there first
|
||||
active_tab.handle_pty_bytes(pid, vte_bytes);
|
||||
} else {
|
||||
// if this event wasn't directed at the active tab, start looking
|
||||
// in other tabs
|
||||
let all_tabs = screen.get_tabs_mut();
|
||||
for tab in all_tabs.values_mut() {
|
||||
if tab.has_terminal_pid(pid) {
|
||||
tab.handle_pty_bytes(pid, vte_bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScreenInstruction::Render => {
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::NewPane(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().new_pane(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::HorizontalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().horizontal_split(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::VerticalSplit(pid) => {
|
||||
screen.get_active_tab_mut().unwrap().vertical_split(pid);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::WriteCharacter(bytes) => {
|
||||
let active_tab = screen.get_active_tab_mut().unwrap();
|
||||
match active_tab.is_sync_panes_active() {
|
||||
true => active_tab.write_to_terminals_on_current_tab(bytes),
|
||||
false => active_tab.write_to_active_terminal(bytes),
|
||||
}
|
||||
}
|
||||
ScreenInstruction::ResizeLeft => {
|
||||
screen.get_active_tab_mut().unwrap().resize_left();
|
||||
}
|
||||
ScreenInstruction::ResizeRight => {
|
||||
screen.get_active_tab_mut().unwrap().resize_right();
|
||||
}
|
||||
ScreenInstruction::ResizeDown => {
|
||||
screen.get_active_tab_mut().unwrap().resize_down();
|
||||
}
|
||||
ScreenInstruction::ResizeUp => {
|
||||
screen.get_active_tab_mut().unwrap().resize_up();
|
||||
}
|
||||
ScreenInstruction::SwitchFocus => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus();
|
||||
}
|
||||
ScreenInstruction::FocusNextPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_next_pane();
|
||||
}
|
||||
ScreenInstruction::FocusPreviousPane => {
|
||||
screen.get_active_tab_mut().unwrap().focus_previous_pane();
|
||||
}
|
||||
ScreenInstruction::MoveFocusLeft => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_left();
|
||||
}
|
||||
ScreenInstruction::MoveFocusDown => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_down();
|
||||
}
|
||||
ScreenInstruction::MoveFocusRight => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_right();
|
||||
}
|
||||
ScreenInstruction::MoveFocusUp => {
|
||||
screen.get_active_tab_mut().unwrap().move_focus_up();
|
||||
}
|
||||
ScreenInstruction::ScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up();
|
||||
}
|
||||
ScreenInstruction::ScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down();
|
||||
}
|
||||
ScreenInstruction::PageScrollUp => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_up_page();
|
||||
}
|
||||
ScreenInstruction::PageScrollDown => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.scroll_active_terminal_down_page();
|
||||
}
|
||||
ScreenInstruction::ClearScroll => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.clear_active_terminal_scroll();
|
||||
}
|
||||
ScreenInstruction::CloseFocusedPane => {
|
||||
screen.get_active_tab_mut().unwrap().close_focused_pane();
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::SetSelectable(id, selectable) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_selectable(id, selectable);
|
||||
}
|
||||
ScreenInstruction::SetMaxHeight(id, max_height) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_max_height(id, max_height);
|
||||
}
|
||||
ScreenInstruction::SetInvisibleBorders(id, invisible_borders) => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.set_pane_invisible_borders(id, invisible_borders);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ClosePane(id) => {
|
||||
screen.get_active_tab_mut().unwrap().close_pane(id);
|
||||
screen.render();
|
||||
}
|
||||
ScreenInstruction::ToggleActiveTerminalFullscreen => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_active_pane_fullscreen();
|
||||
}
|
||||
ScreenInstruction::NewTab(pane_id) => {
|
||||
screen.new_tab(pane_id);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabNext => {
|
||||
screen.switch_tab_next();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::SwitchTabPrev => {
|
||||
screen.switch_tab_prev();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::CloseTab => {
|
||||
screen.close_tab();
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::ApplyLayout(layout, new_pane_pids) => {
|
||||
screen.apply_layout(layout, new_pane_pids);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::GoToTab(tab_index) => {
|
||||
screen.go_to_tab(tab_index as usize);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::UpdateTabName(c) => {
|
||||
screen.update_active_tab_name(c);
|
||||
screen
|
||||
.send_server_instructions
|
||||
.send(ServerInstruction::UnblockInputThread)
|
||||
.unwrap();
|
||||
}
|
||||
ScreenInstruction::TerminalResize(new_size) => {
|
||||
screen.resize_to_screen(new_size);
|
||||
}
|
||||
ScreenInstruction::ChangeMode(mode_info) => {
|
||||
screen.change_mode(mode_info);
|
||||
}
|
||||
ScreenInstruction::ToggleActiveSyncPanes => {
|
||||
screen
|
||||
.get_active_tab_mut()
|
||||
.unwrap()
|
||||
.toggle_sync_panes_is_active();
|
||||
screen.update_tabs();
|
||||
}
|
||||
ScreenInstruction::Exit => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let wasm_thread = thread::Builder::new()
|
||||
.name("wasm".to_string())
|
||||
.spawn({
|
||||
let send_screen_instructions = send_screen_instructions.clone();
|
||||
let send_pty_instructions = send_pty_instructions.clone();
|
||||
let send_plugin_instructions = send_plugin_instructions.clone();
|
||||
|
||||
let store = Store::default();
|
||||
let mut plugin_id = 0;
|
||||
let mut plugin_map = HashMap::new();
|
||||
move || loop {
|
||||
let (event, mut err_ctx) = receive_plugin_instructions
|
||||
.recv()
|
||||
.expect("failed to receive event on channel");
|
||||
err_ctx.add_call(ContextType::Plugin(PluginContext::from(&event)));
|
||||
match event {
|
||||
PluginInstruction::Load(pid_tx, path) => {
|
||||
let plugin_dir = data_dir.join("plugins/");
|
||||
let wasm_bytes = fs::read(&path)
|
||||
.or_else(|_| fs::read(&path.with_extension("wasm")))
|
||||
.or_else(|_| fs::read(&plugin_dir.join(&path).with_extension("wasm")))
|
||||
.unwrap_or_else(|_| panic!("cannot find plugin {}", &path.display()));
|
||||
|
||||
// FIXME: Cache this compiled module on disk. I could use `(de)serialize_to_file()` for that
|
||||
let module = Module::new(&store, &wasm_bytes).unwrap();
|
||||
|
||||
let output = Pipe::new();
|
||||
let input = Pipe::new();
|
||||
let mut wasi_env = WasiState::new("Zellij")
|
||||
.env("CLICOLOR_FORCE", "1")
|
||||
.preopen(|p| {
|
||||
p.directory(".") // FIXME: Change this to a more meaningful dir
|
||||
.alias(".")
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
})
|
||||
.unwrap()
|
||||
.stdin(Box::new(input))
|
||||
.stdout(Box::new(output))
|
||||
.finalize()
|
||||
.unwrap();
|
||||
|
||||
let wasi = wasi_env.import_object(&module).unwrap();
|
||||
|
||||
let plugin_env = PluginEnv {
|
||||
plugin_id,
|
||||
send_screen_instructions: send_screen_instructions.clone(),
|
||||
send_pty_instructions: send_pty_instructions.clone(),
|
||||
send_plugin_instructions: send_plugin_instructions.clone(),
|
||||
wasi_env,
|
||||
subscriptions: Arc::new(Mutex::new(HashSet::new())),
|
||||
};
|
||||
|
||||
let zellij = zellij_exports(&store, &plugin_env);
|
||||
let instance = Instance::new(&module, &zellij.chain_back(wasi)).unwrap();
|
||||
|
||||
let start = instance.exports.get_function("_start").unwrap();
|
||||
|
||||
// This eventually calls the `.init()` method
|
||||
start.call(&[]).unwrap();
|
||||
|
||||
plugin_map.insert(plugin_id, (instance, plugin_env));
|
||||
pid_tx.send(plugin_id).unwrap();
|
||||
plugin_id += 1;
|
||||
}
|
||||
PluginInstruction::Update(pid, event) => {
|
||||
for (&i, (instance, plugin_env)) in &plugin_map {
|
||||
let subs = plugin_env.subscriptions.lock().unwrap();
|
||||
// FIXME: This is very janky... Maybe I should write my own macro for Event -> EventType?
|
||||
let event_type = EventType::from_str(&event.to_string()).unwrap();
|
||||
if (pid.is_none() || pid == Some(i)) && subs.contains(&event_type) {
|
||||
let update = instance.exports.get_function("update").unwrap();
|
||||
wasi_write_object(&plugin_env.wasi_env, &event);
|
||||
update.call(&[]).unwrap();
|
||||
}
|
||||
}
|
||||
drop(send_screen_instructions.send(ScreenInstruction::Render));
|
||||
}
|
||||
PluginInstruction::Render(buf_tx, pid, rows, cols) => {
|
||||
let (instance, plugin_env) = plugin_map.get(&pid).unwrap();
|
||||
|
||||
let render = instance.exports.get_function("render").unwrap();
|
||||
|
||||
render
|
||||
.call(&[Value::I32(rows as i32), Value::I32(cols as i32)])
|
||||
.unwrap();
|
||||
|
||||
buf_tx.send(wasi_read_string(&plugin_env.wasi_env)).unwrap();
|
||||
}
|
||||
PluginInstruction::Unload(pid) => drop(plugin_map.remove(&pid)),
|
||||
PluginInstruction::Exit => break,
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
SessionMetaData {
|
||||
send_plugin_instructions,
|
||||
send_screen_instructions,
|
||||
send_pty_instructions,
|
||||
screen_thread: Some(screen_thread),
|
||||
pty_thread: Some(pty_thread),
|
||||
wasm_thread: Some(wasm_thread),
|
||||
}
|
||||
}
|
||||
|
||||
fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn ServerOsApi) {
|
||||
match action {
|
||||
Action::Write(val) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ClearScroll)
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::WriteCharacter(val))
|
||||
.unwrap();
|
||||
}
|
||||
Action::SwitchToMode(mode) => {
|
||||
let palette = os_input.load_palette();
|
||||
session
|
||||
.send_plugin_instructions
|
||||
.send(PluginInstruction::Update(
|
||||
None,
|
||||
Event::ModeUpdate(get_mode_info(mode, palette)),
|
||||
))
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ChangeMode(get_mode_info(mode, palette)))
|
||||
.unwrap();
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::Render)
|
||||
.unwrap();
|
||||
}
|
||||
Action::Resize(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::ResizeLeft,
|
||||
Direction::Right => ScreenInstruction::ResizeRight,
|
||||
Direction::Up => ScreenInstruction::ResizeUp,
|
||||
Direction::Down => ScreenInstruction::ResizeDown,
|
||||
};
|
||||
session.send_screen_instructions.send(screen_instr).unwrap();
|
||||
}
|
||||
Action::SwitchFocus => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchFocus)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusNextPane => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusNextPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::FocusPreviousPane => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::FocusPreviousPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::MoveFocus(direction) => {
|
||||
let screen_instr = match direction {
|
||||
Direction::Left => ScreenInstruction::MoveFocusLeft,
|
||||
Direction::Right => ScreenInstruction::MoveFocusRight,
|
||||
Direction::Up => ScreenInstruction::MoveFocusUp,
|
||||
Direction::Down => ScreenInstruction::MoveFocusDown,
|
||||
};
|
||||
session.send_screen_instructions.send(screen_instr).unwrap();
|
||||
}
|
||||
Action::ScrollUp => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ScrollDown => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollUp => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollUp)
|
||||
.unwrap();
|
||||
}
|
||||
Action::PageScrollDown => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::PageScrollDown)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleFocusFullscreen => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveTerminalFullscreen)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewPane(direction) => {
|
||||
let pty_instr = match direction {
|
||||
Some(Direction::Left) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Right) => PtyInstruction::SpawnTerminalVertically(None),
|
||||
Some(Direction::Up) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
Some(Direction::Down) => PtyInstruction::SpawnTerminalHorizontally(None),
|
||||
// No direction specified - try to put it in the biggest available spot
|
||||
None => PtyInstruction::SpawnTerminal(None),
|
||||
};
|
||||
session.send_pty_instructions.send(pty_instr).unwrap();
|
||||
}
|
||||
Action::CloseFocus => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseFocusedPane)
|
||||
.unwrap();
|
||||
}
|
||||
Action::NewTab => {
|
||||
session
|
||||
.send_pty_instructions
|
||||
.send(PtyInstruction::NewTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToNextTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabNext)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToPreviousTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::SwitchTabPrev)
|
||||
.unwrap();
|
||||
}
|
||||
Action::ToggleActiveSyncPanes => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::ToggleActiveSyncPanes)
|
||||
.unwrap();
|
||||
}
|
||||
Action::CloseTab => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::CloseTab)
|
||||
.unwrap();
|
||||
}
|
||||
Action::GoToTab(i) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::GoToTab(i))
|
||||
.unwrap();
|
||||
}
|
||||
Action::TabNameInput(c) => {
|
||||
session
|
||||
.send_screen_instructions
|
||||
.send(ScreenInstruction::UpdateTabName(c))
|
||||
.unwrap();
|
||||
}
|
||||
Action::NoOp => {}
|
||||
Action::Quit => panic!("Received unexpected action"),
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,23 @@
|
||||
use crate::panes::PositionAndSize;
|
||||
use interprocess::local_socket::LocalSocketStream;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::io::Write;
|
||||
use std::os::unix::io::RawFd;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Condvar, Mutex};
|
||||
use std::sync::{mpsc, Arc, Condvar, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::client::ClientInstruction;
|
||||
use crate::common::{ChannelWithContext, SenderType, SenderWithContext};
|
||||
use crate::errors::ErrorContext;
|
||||
use crate::os_input_output::{ClientOsApi, ServerOsApi};
|
||||
use crate::server::ServerInstruction;
|
||||
use crate::tests::possible_tty_inputs::{get_possible_tty_inputs, Bytes};
|
||||
use crate::tests::utils::commands::{QUIT, SLEEP};
|
||||
use crate::utils::shared::default_palette;
|
||||
use zellij_tile::data::Palette;
|
||||
|
||||
use crate::tests::utils::commands::{QUIT, SLEEP};
|
||||
|
||||
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(50);
|
||||
const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(150);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum IoEvent {
|
||||
@ -73,6 +77,10 @@ pub struct FakeInputOutput {
|
||||
win_sizes: Arc<Mutex<HashMap<RawFd, PositionAndSize>>>,
|
||||
possible_tty_inputs: HashMap<u16, Bytes>,
|
||||
last_snapshot_time: Arc<Mutex<Instant>>,
|
||||
send_instructions_to_client: SenderWithContext<ClientInstruction>,
|
||||
receive_instructions_from_server: Arc<Mutex<mpsc::Receiver<(ClientInstruction, ErrorContext)>>>,
|
||||
send_instructions_to_server: SenderWithContext<ServerInstruction>,
|
||||
receive_instructions_from_client: Arc<Mutex<mpsc::Receiver<(ServerInstruction, ErrorContext)>>>,
|
||||
should_trigger_sigwinch: Arc<(Mutex<bool>, Condvar)>,
|
||||
sigwinch_event: Option<PositionAndSize>,
|
||||
}
|
||||
@ -82,6 +90,12 @@ impl FakeInputOutput {
|
||||
let mut win_sizes = HashMap::new();
|
||||
let last_snapshot_time = Arc::new(Mutex::new(Instant::now()));
|
||||
let stdout_writer = FakeStdoutWriter::new(last_snapshot_time.clone());
|
||||
let (client_sender, client_receiver): ChannelWithContext<ClientInstruction> =
|
||||
mpsc::channel();
|
||||
let send_instructions_to_client = SenderWithContext::new(SenderType::Sender(client_sender));
|
||||
let (server_sender, server_receiver): ChannelWithContext<ServerInstruction> =
|
||||
mpsc::channel();
|
||||
let send_instructions_to_server = SenderWithContext::new(SenderType::Sender(server_sender));
|
||||
win_sizes.insert(0, winsize); // 0 is the current terminal
|
||||
FakeInputOutput {
|
||||
read_buffers: Arc::new(Mutex::new(HashMap::new())),
|
||||
@ -93,6 +107,10 @@ impl FakeInputOutput {
|
||||
io_events: Arc::new(Mutex::new(vec![])),
|
||||
win_sizes: Arc::new(Mutex::new(win_sizes)),
|
||||
possible_tty_inputs: get_possible_tty_inputs(),
|
||||
receive_instructions_from_client: Arc::new(Mutex::new(server_receiver)),
|
||||
send_instructions_to_server,
|
||||
receive_instructions_from_server: Arc::new(Mutex::new(client_receiver)),
|
||||
send_instructions_to_client,
|
||||
should_trigger_sigwinch: Arc::new((Mutex::new(false), Condvar::new())),
|
||||
sigwinch_event: None,
|
||||
}
|
||||
@ -116,7 +134,7 @@ impl FakeInputOutput {
|
||||
}
|
||||
}
|
||||
|
||||
impl OsApi for FakeInputOutput {
|
||||
impl ClientOsApi for FakeInputOutput {
|
||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> PositionAndSize {
|
||||
if let Some(new_position_and_size) = self.sigwinch_event {
|
||||
let (lock, _cvar) = &*self.should_trigger_sigwinch;
|
||||
@ -129,20 +147,6 @@ impl OsApi for FakeInputOutput {
|
||||
let winsize = win_sizes.get(&pid).unwrap();
|
||||
*winsize
|
||||
}
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
|
||||
let terminal_input = self
|
||||
.possible_tty_inputs
|
||||
.get(&cols)
|
||||
.expect(&format!("could not find input for size {:?}", cols));
|
||||
self.read_buffers
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(pid, terminal_input.clone());
|
||||
self.io_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
|
||||
}
|
||||
fn set_raw_mode(&mut self, pid: RawFd) {
|
||||
self.io_events
|
||||
.lock()
|
||||
@ -155,43 +159,7 @@ impl OsApi for FakeInputOutput {
|
||||
.unwrap()
|
||||
.push(IoEvent::UnsetRawMode(pid));
|
||||
}
|
||||
fn spawn_terminal(&mut self, _file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
|
||||
let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1;
|
||||
self.add_terminal(next_terminal_id);
|
||||
(next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here
|
||||
}
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
let mut read_buffers = self.read_buffers.lock().unwrap();
|
||||
let mut bytes_read = 0;
|
||||
match read_buffers.get_mut(&pid) {
|
||||
Some(bytes) => {
|
||||
for i in bytes.read_position..bytes.content.len() {
|
||||
bytes_read += 1;
|
||||
buf[i] = bytes.content[i];
|
||||
}
|
||||
if bytes_read > bytes.read_position {
|
||||
bytes.set_read_position(bytes_read);
|
||||
}
|
||||
return Ok(bytes_read);
|
||||
}
|
||||
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
|
||||
}
|
||||
}
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
let mut stdin_writes = self.stdin_writes.lock().unwrap();
|
||||
let write_buffer = stdin_writes.get_mut(&pid).unwrap();
|
||||
let mut bytes_written = 0;
|
||||
for byte in buf {
|
||||
bytes_written += 1;
|
||||
write_buffer.push(*byte);
|
||||
}
|
||||
Ok(bytes_written)
|
||||
}
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid));
|
||||
Ok(())
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn OsApi> {
|
||||
fn box_clone(&self) -> Box<dyn ClientOsApi> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn read_from_stdin(&self) -> Vec<u8> {
|
||||
@ -213,30 +181,115 @@ impl OsApi for FakeInputOutput {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
} else if command == QUIT && self.sigwinch_event.is_some() {
|
||||
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
||||
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||
*should_trigger_sigwinch = true;
|
||||
{
|
||||
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||
*should_trigger_sigwinch = true;
|
||||
}
|
||||
cvar.notify_one();
|
||||
::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS); // give some time for the app to resize before quitting
|
||||
} else if command == QUIT {
|
||||
::std::thread::sleep(MIN_TIME_BETWEEN_SNAPSHOTS);
|
||||
}
|
||||
command
|
||||
}
|
||||
fn get_stdout_writer(&self) -> Box<dyn Write> {
|
||||
Box::new(self.stdout_writer.clone())
|
||||
}
|
||||
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
|
||||
Ok(())
|
||||
fn send_to_server(&self, msg: ServerInstruction) {
|
||||
self.send_instructions_to_server.send(msg).unwrap();
|
||||
}
|
||||
fn recv_from_server(&self) -> (ClientInstruction, ErrorContext) {
|
||||
self.receive_instructions_from_server
|
||||
.lock()
|
||||
.unwrap()
|
||||
.recv()
|
||||
.unwrap()
|
||||
}
|
||||
fn receive_sigwinch(&self, cb: Box<dyn Fn()>) {
|
||||
if self.sigwinch_event.is_some() {
|
||||
let (lock, cvar) = &*self.should_trigger_sigwinch;
|
||||
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||
while !*should_trigger_sigwinch {
|
||||
should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap();
|
||||
{
|
||||
let mut should_trigger_sigwinch = lock.lock().unwrap();
|
||||
while !*should_trigger_sigwinch {
|
||||
should_trigger_sigwinch = cvar.wait(should_trigger_sigwinch).unwrap();
|
||||
}
|
||||
}
|
||||
cb();
|
||||
}
|
||||
}
|
||||
fn connect_to_server(&self) {}
|
||||
}
|
||||
|
||||
impl ServerOsApi for FakeInputOutput {
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
|
||||
let terminal_input = self
|
||||
.possible_tty_inputs
|
||||
.get(&cols)
|
||||
.expect(&format!("could not find input for size {:?}", cols));
|
||||
self.read_buffers
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(pid, terminal_input.clone());
|
||||
self.io_events
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
|
||||
}
|
||||
fn spawn_terminal(&mut self, _file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
|
||||
let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1;
|
||||
self.add_terminal(next_terminal_id);
|
||||
(next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here
|
||||
}
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
let mut stdin_writes = self.stdin_writes.lock().unwrap();
|
||||
let write_buffer = stdin_writes.get_mut(&pid).unwrap();
|
||||
let mut bytes_written = 0;
|
||||
for byte in buf {
|
||||
bytes_written += 1;
|
||||
write_buffer.push(*byte);
|
||||
}
|
||||
Ok(bytes_written)
|
||||
}
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
let mut read_buffers = self.read_buffers.lock().unwrap();
|
||||
let mut bytes_read = 0;
|
||||
match read_buffers.get_mut(&pid) {
|
||||
Some(bytes) => {
|
||||
for i in bytes.read_position..bytes.content.len() {
|
||||
bytes_read += 1;
|
||||
buf[i] = bytes.content[i];
|
||||
}
|
||||
if bytes_read > bytes.read_position {
|
||||
bytes.set_read_position(bytes_read);
|
||||
}
|
||||
return Ok(bytes_read);
|
||||
}
|
||||
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
|
||||
}
|
||||
}
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid));
|
||||
Ok(())
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn ServerOsApi> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
|
||||
Ok(())
|
||||
}
|
||||
fn recv_from_client(&self) -> (ServerInstruction, ErrorContext) {
|
||||
self.receive_instructions_from_client
|
||||
.lock()
|
||||
.unwrap()
|
||||
.recv()
|
||||
.unwrap()
|
||||
}
|
||||
fn send_to_client(&self, msg: ClientInstruction) {
|
||||
self.send_instructions_to_client.send(msg).unwrap();
|
||||
}
|
||||
fn add_client_sender(&mut self) {}
|
||||
fn update_receiver(&mut self, _stream: LocalSocketStream) {}
|
||||
fn load_palette(&self) -> Palette {
|
||||
default_palette()
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ pub fn starts_with_one_terminal() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -57,6 +58,7 @@ pub fn split_terminals_vertically() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -84,6 +86,7 @@ pub fn split_terminals_horizontally() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -118,6 +121,7 @@ pub fn split_largest_terminal() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -145,6 +149,7 @@ pub fn cannot_split_terminals_vertically_when_active_terminal_is_too_small() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -172,6 +177,7 @@ pub fn cannot_split_terminals_horizontally_when_active_terminal_is_too_small() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -199,6 +205,7 @@ pub fn cannot_split_largest_terminal_when_there_is_no_room() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -234,6 +241,7 @@ pub fn scrolling_up_inside_a_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -271,6 +279,7 @@ pub fn scrolling_down_inside_a_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -305,6 +314,7 @@ pub fn scrolling_page_up_inside_a_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -342,6 +352,7 @@ pub fn scrolling_page_down_inside_a_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -377,7 +388,12 @@ pub fn max_panes() {
|
||||
]);
|
||||
let mut opts = CliArgs::default();
|
||||
opts.max_panes = Some(4);
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
@ -409,7 +425,12 @@ pub fn toggle_focused_pane_fullscreen() {
|
||||
]);
|
||||
let mut opts = CliArgs::default();
|
||||
opts.max_panes = Some(4);
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
|
@ -43,6 +43,7 @@ pub fn close_pane_with_another_pane_above_it() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -85,6 +86,7 @@ pub fn close_pane_with_another_pane_below_it() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -124,6 +126,7 @@ pub fn close_pane_with_another_pane_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -164,6 +167,7 @@ pub fn close_pane_with_another_pane_to_the_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -209,6 +213,7 @@ pub fn close_pane_with_multiple_panes_above_it() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -252,6 +257,7 @@ pub fn close_pane_with_multiple_panes_below_it() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -297,6 +303,7 @@ pub fn close_pane_with_multiple_panes_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -340,6 +347,7 @@ pub fn close_pane_with_multiple_panes_to_the_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -405,6 +413,7 @@ pub fn close_pane_with_multiple_panes_above_it_away_from_screen_edges() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -466,6 +475,7 @@ pub fn close_pane_with_multiple_panes_below_it_away_from_screen_edges() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -529,6 +539,7 @@ pub fn close_pane_with_multiple_panes_to_the_left_away_from_screen_edges() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -592,6 +603,7 @@ pub fn close_pane_with_multiple_panes_to_the_right_away_from_screen_edges() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -628,6 +640,7 @@ pub fn closing_last_pane_exits_app() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -41,10 +41,12 @@ pub fn run_bandwhich_from_fish_shell() {
|
||||
};
|
||||
let fixture_name = "fish_and_bandwhich";
|
||||
let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
|
||||
|
||||
fake_input_output.add_terminal_input(&[&QUIT]);
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -73,6 +75,7 @@ pub fn fish_tab_completion_options() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -106,6 +109,7 @@ pub fn fish_select_tab_completion_options() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -143,6 +147,7 @@ pub fn vim_scroll_region_down() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -177,6 +182,7 @@ pub fn vim_ctrl_d() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -210,6 +216,7 @@ pub fn vim_ctrl_u() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -238,6 +245,7 @@ pub fn htop() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -266,6 +274,7 @@ pub fn htop_scrolling() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -294,6 +303,7 @@ pub fn htop_right_scrolling() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -330,6 +340,7 @@ pub fn vim_overwrite() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -361,6 +372,7 @@ pub fn clear_scroll_region() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -389,6 +401,7 @@ pub fn display_tab_characters_properly() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -417,6 +430,7 @@ pub fn neovim_insert_mode() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -447,6 +461,7 @@ pub fn bash_cursor_linewrap() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -477,6 +492,7 @@ pub fn fish_paste_multiline() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -505,6 +521,7 @@ pub fn git_log() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -535,6 +552,7 @@ pub fn git_diff_scrollup() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -563,6 +581,7 @@ pub fn emacs_longbuf() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -591,6 +610,7 @@ pub fn top_and_quit() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
@ -625,6 +645,7 @@ pub fn exa_plus_omf_theme() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
|
@ -28,7 +28,12 @@ pub fn accepts_basic_layout() {
|
||||
"src/tests/fixtures/layouts/three-panes-with-nesting.yaml",
|
||||
));
|
||||
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
|
@ -35,6 +35,7 @@ pub fn move_focus_down() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn move_focus_down_to_the_most_recently_used_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -34,6 +34,7 @@ pub fn move_focus_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn move_focus_left_to_the_most_recently_used_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -35,6 +35,7 @@ pub fn move_focus_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn move_focus_right_to_the_most_recently_used_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -34,6 +34,7 @@ pub fn move_focus_up() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn move_focus_up_to_the_most_recently_used_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -46,6 +46,7 @@ pub fn resize_down_with_pane_above() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -90,6 +91,7 @@ pub fn resize_down_with_pane_below() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -140,6 +142,7 @@ pub fn resize_down_with_panes_above_and_below() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -189,6 +192,7 @@ pub fn resize_down_with_multiple_panes_above() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -240,6 +244,7 @@ pub fn resize_down_with_panes_above_aligned_left_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -290,6 +295,7 @@ pub fn resize_down_with_panes_below_aligned_left_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -338,6 +344,7 @@ pub fn resize_down_with_panes_above_aligned_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -387,6 +394,7 @@ pub fn resize_down_with_panes_below_aligned_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -439,6 +447,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -493,6 +502,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -564,6 +574,7 @@ pub fn resize_down_with_panes_above_aligned_left_and_right_with_panes_to_the_lef
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -637,6 +648,7 @@ pub fn resize_down_with_panes_below_aligned_left_and_right_with_to_the_left_and_
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -678,6 +690,7 @@ pub fn cannot_resize_down_when_pane_below_is_at_minimum_height() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -42,6 +42,7 @@ pub fn resize_left_with_pane_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -84,6 +85,7 @@ pub fn resize_left_with_pane_to_the_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -128,6 +130,7 @@ pub fn resize_left_with_panes_to_the_left_and_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -175,6 +178,7 @@ pub fn resize_left_with_multiple_panes_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -224,6 +228,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -270,6 +275,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -318,6 +324,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_bottom_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -365,6 +372,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_bottom_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -417,6 +425,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_current_pa
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -471,6 +480,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_current_p
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -542,6 +552,7 @@ pub fn resize_left_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abov
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -616,6 +627,7 @@ pub fn resize_left_with_panes_to_the_right_aligned_top_and_bottom_with_panes_abo
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -657,6 +669,7 @@ pub fn cannot_resize_left_when_pane_to_the_left_is_at_minimum_width() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -42,6 +42,7 @@ pub fn resize_right_with_pane_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -84,6 +85,7 @@ pub fn resize_right_with_pane_to_the_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -128,6 +130,7 @@ pub fn resize_right_with_panes_to_the_left_and_right() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -175,6 +178,7 @@ pub fn resize_right_with_multiple_panes_to_the_left() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -224,6 +228,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -270,6 +275,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -318,6 +324,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_bottom_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -365,6 +372,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_bottom_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -417,6 +425,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_current_p
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -471,6 +480,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_current_
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -542,6 +552,7 @@ pub fn resize_right_with_panes_to_the_left_aligned_top_and_bottom_with_panes_abo
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -615,6 +626,7 @@ pub fn resize_right_with_panes_to_the_right_aligned_top_and_bottom_with_panes_ab
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -656,6 +668,7 @@ pub fn cannot_resize_right_when_pane_to_the_left_is_at_minimum_width() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -44,6 +44,7 @@ pub fn resize_up_with_pane_above() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -88,6 +89,7 @@ pub fn resize_up_with_pane_below() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -137,6 +139,7 @@ pub fn resize_up_with_panes_above_and_below() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -185,6 +188,7 @@ pub fn resize_up_with_multiple_panes_above() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -234,6 +238,7 @@ pub fn resize_up_with_panes_above_aligned_left_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -284,6 +289,7 @@ pub fn resize_up_with_panes_below_aligned_left_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -332,6 +338,7 @@ pub fn resize_up_with_panes_above_aligned_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -381,6 +388,7 @@ pub fn resize_up_with_panes_below_aligned_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -433,6 +441,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -487,6 +496,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_current_pane() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -558,6 +568,7 @@ pub fn resize_up_with_panes_above_aligned_left_and_right_with_panes_to_the_left_
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -631,6 +642,7 @@ pub fn resize_up_with_panes_below_aligned_left_and_right_with_to_the_left_and_ri
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -672,6 +684,7 @@ pub fn cannot_resize_up_when_pane_above_is_at_minimum_height() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -36,6 +36,7 @@ pub fn open_new_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -71,6 +72,7 @@ pub fn switch_to_prev_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -106,6 +108,7 @@ pub fn switch_to_next_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -141,6 +144,7 @@ pub fn close_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -177,6 +181,7 @@ pub fn close_last_pane_in_a_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -215,6 +220,7 @@ pub fn close_the_middle_tab() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -258,6 +264,7 @@ pub fn close_the_tab_that_has_a_pane_in_fullscreen() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -293,6 +300,7 @@ pub fn closing_last_tab_exits_the_app() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
@ -30,7 +30,12 @@ pub fn window_width_decrease_with_one_pane() {
|
||||
..Default::default()
|
||||
});
|
||||
let opts = CliArgs::default();
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
@ -61,7 +66,12 @@ pub fn window_width_increase_with_one_pane() {
|
||||
..Default::default()
|
||||
});
|
||||
let opts = CliArgs::default();
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
@ -92,7 +102,12 @@ pub fn window_height_increase_with_one_pane() {
|
||||
..Default::default()
|
||||
});
|
||||
let opts = CliArgs::default();
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
@ -123,7 +138,12 @@ pub fn window_width_and_height_decrease_with_one_pane() {
|
||||
..Default::default()
|
||||
});
|
||||
let opts = CliArgs::default();
|
||||
start(Box::new(fake_input_output.clone()), opts, Config::default());
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
opts,
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
let output_frames = fake_input_output
|
||||
.stdout_writer
|
||||
.output_frames
|
||||
|
@ -35,6 +35,7 @@ pub fn adding_new_terminal_in_fullscreen() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
@ -69,6 +70,7 @@ pub fn move_focus_is_disabled_in_fullscreen() {
|
||||
start(
|
||||
Box::new(fake_input_output.clone()),
|
||||
CliArgs::default(),
|
||||
Box::new(fake_input_output.clone()),
|
||||
Config::default(),
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user