tests!
This commit is contained in:
parent
68a8422457
commit
9e204e0dcc
15 changed files with 674 additions and 47 deletions
127
Cargo.lock
generated
127
Cargo.lock
generated
|
|
@ -118,6 +118,20 @@ dependencies = [
|
|||
"cache-padded",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c0994e656bba7b922d8dd1245db90672ffb701e684e45be58f20719d69abc5a"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"terminal_size",
|
||||
"termios",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
|
|
@ -129,6 +143,24 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difference"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.3.0"
|
||||
|
|
@ -283,6 +315,26 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "617e921abc813f96a3b00958c079e7bf1e2db998f8a04f1546dd967373a418ee"
|
||||
dependencies = [
|
||||
"console",
|
||||
"difference",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.44"
|
||||
|
|
@ -313,6 +365,12 @@ version = "0.2.71"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.11"
|
||||
|
|
@ -334,6 +392,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"async-std",
|
||||
"futures",
|
||||
"insta",
|
||||
"libc",
|
||||
"nix",
|
||||
"signal-hook",
|
||||
|
|
@ -446,6 +505,12 @@ version = "0.1.57"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
|
|
@ -458,6 +523,49 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"linked-hash-map",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.16"
|
||||
|
|
@ -528,6 +636,16 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a14cd9f8c72704232f0bfc8455c0e861f0ad4eb60cc9ec8a170e231414c1e13"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.3.2"
|
||||
|
|
@ -703,3 +821,12 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -19,3 +19,6 @@ futures = "0.3.5"
|
|||
[dependencies.async-std]
|
||||
version = "1.3.0"
|
||||
features = ["unstable"]
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "0.16.1"
|
||||
|
|
|
|||
108
src/main.rs
108
src/main.rs
|
|
@ -1,3 +1,6 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod os_input_output;
|
||||
|
||||
use ::std::fmt::{self, Display, Formatter};
|
||||
|
|
@ -14,7 +17,9 @@ use async_std::task::*;
|
|||
use ::std::pin::*;
|
||||
use std::sync::mpsc::{channel, Sender, Receiver};
|
||||
|
||||
use crate::os_input_output::{get_os_input, OsInputOutput, OsApi};
|
||||
use crate::os_input_output::{get_os_input, OsApi};
|
||||
|
||||
use vte::Perform;
|
||||
|
||||
struct ReadFromPid {
|
||||
pid: RawFd,
|
||||
|
|
@ -32,16 +37,19 @@ impl ReadFromPid {
|
|||
|
||||
impl Stream for ReadFromPid {
|
||||
type Item = Vec<u8>;
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let mut read_buffer = [0; 115200];
|
||||
let read_result = &self.os_input.read(self.pid, &mut read_buffer);
|
||||
let pid = self.pid;
|
||||
let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer);
|
||||
match read_result {
|
||||
Ok(res) => {
|
||||
// TODO: this might become an issue with multiple panes sending data simultaneously
|
||||
// ...consider returning None if res == 0 and handling it in the task (or sending
|
||||
// Poll::Pending?)
|
||||
let res = Some(read_buffer[..=*res].to_vec());
|
||||
return Poll::Ready(res)
|
||||
if *res == 0 {
|
||||
// indicates end of file
|
||||
return Poll::Ready(None);
|
||||
} else {
|
||||
let res = Some(read_buffer[..=*res].to_vec());
|
||||
return Poll::Ready(res);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
match e {
|
||||
|
|
@ -59,8 +67,8 @@ impl Stream for ReadFromPid {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TerminalCharacter {
|
||||
#[derive(Clone)]
|
||||
pub struct TerminalCharacter {
|
||||
pub character: char,
|
||||
pub ansi_code: Option<String>,
|
||||
}
|
||||
|
|
@ -106,7 +114,14 @@ impl Display for TerminalCharacter {
|
|||
}
|
||||
}
|
||||
|
||||
struct TerminalOutput {
|
||||
impl ::std::fmt::Debug for TerminalCharacter {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.character);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TerminalOutput {
|
||||
pub pid: RawFd,
|
||||
pub characters: Vec<TerminalCharacter>,
|
||||
pub display_rows: u16,
|
||||
|
|
@ -155,7 +170,8 @@ impl TerminalOutput {
|
|||
self.unhook();
|
||||
},
|
||||
VteEvent::OscDispatch(params, bell_terminated) => {
|
||||
self.osc_dispatch(params, bell_terminated);
|
||||
let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
|
||||
self.osc_dispatch(¶ms[..], bell_terminated);
|
||||
},
|
||||
VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
|
||||
self.csi_dispatch(¶ms, &intermediates, ignore, c);
|
||||
|
|
@ -337,6 +353,14 @@ impl TerminalOutput {
|
|||
(None, None) => 0
|
||||
}
|
||||
}
|
||||
fn index_of_beginning_of_last_canonical_line (&self) -> usize {
|
||||
if self.newline_indices.is_empty() {
|
||||
0
|
||||
} else {
|
||||
// return last
|
||||
*self.newline_indices.last().unwrap()
|
||||
}
|
||||
}
|
||||
fn index_of_beginning_of_line (&self, index_in_line: usize) -> usize {
|
||||
let last_newline_index = if self.newline_indices.is_empty() {
|
||||
None
|
||||
|
|
@ -404,8 +428,7 @@ impl TerminalOutput {
|
|||
|
||||
const DEBUGGING: bool = false;
|
||||
|
||||
// vte methods
|
||||
impl TerminalOutput {
|
||||
impl vte::Perform for TerminalOutput {
|
||||
fn print(&mut self, c: char) {
|
||||
if DEBUGGING {
|
||||
println!("\r[print] {:?}", c);
|
||||
|
|
@ -424,14 +447,15 @@ impl TerminalOutput {
|
|||
self.characters.push(space_character.clone());
|
||||
};
|
||||
self.characters.push(terminal_character);
|
||||
}
|
||||
|
||||
let start_of_last_line = self.index_of_beginning_of_line(self.cursor_position);
|
||||
let difference_from_last_newline = self.cursor_position - start_of_last_line;
|
||||
if difference_from_last_newline == self.display_cols as usize {
|
||||
self.linebreak_indices.push(self.cursor_position);
|
||||
let start_of_last_line = self.index_of_beginning_of_line(self.cursor_position);
|
||||
let difference_from_last_newline = self.cursor_position - start_of_last_line;
|
||||
if difference_from_last_newline == self.display_cols as usize {
|
||||
self.linebreak_indices.push(self.cursor_position);
|
||||
}
|
||||
}
|
||||
self.cursor_position += 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -478,9 +502,8 @@ impl TerminalOutput {
|
|||
}
|
||||
}
|
||||
|
||||
// fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
// TODO: normalize vec/slices for all of these methods and the enum
|
||||
fn osc_dispatch(&mut self, params: Vec<Vec<u8>>, bell_terminated: bool) {
|
||||
if DEBUGGING {
|
||||
println!("\r[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated);
|
||||
}
|
||||
|
|
@ -503,7 +526,7 @@ impl TerminalOutput {
|
|||
let param_string = params.iter().map(|p| p.to_string()).collect::<Vec<String>>().join(";");
|
||||
self.pending_ansi_code = Some(format!("\u{1b}[{}m", param_string));
|
||||
}
|
||||
} else if c == 'C' { // move cursor
|
||||
} else if c == 'C' { // move cursor forward
|
||||
self.cursor_position += params[0] as usize; // TODO: negative value?
|
||||
} else if c == 'K' { // clear line (0 => right, 1 => left, 2 => all)
|
||||
if params[0] == 0 {
|
||||
|
|
@ -527,6 +550,25 @@ impl TerminalOutput {
|
|||
self.characters.truncate(self.cursor_position + 1);
|
||||
}
|
||||
// TODO: implement 1, 2, and 3
|
||||
} else if c == 'H' { // goto row/col
|
||||
let row = params[0] as usize - 1; // we subtract 1 here because this csi is 1 indexed and we index from 0
|
||||
let col = params[1] as usize - 1;
|
||||
|
||||
match self.newline_indices.get(row as usize) {
|
||||
Some(index_of_next_row) => {
|
||||
let index_of_row = index_of_next_row - self.display_cols as usize;
|
||||
self.cursor_position = index_of_row + col as usize;
|
||||
}
|
||||
None => {
|
||||
let start_of_last_line = self.index_of_beginning_of_last_canonical_line();
|
||||
let num_of_lines_to_add = row - self.newline_indices.len();
|
||||
for i in 0..num_of_lines_to_add {
|
||||
self.newline_indices.push(start_of_last_line + ((i + 1) * self.display_cols as usize));
|
||||
}
|
||||
let index_of_row = self.newline_indices.last().unwrap_or(&0); // TODO: better
|
||||
self.cursor_position = index_of_row + col as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -542,7 +584,8 @@ impl TerminalOutput {
|
|||
}
|
||||
}
|
||||
|
||||
enum VteEvent { // TODO: try not to allocate Vecs
|
||||
#[derive(Debug)]
|
||||
pub enum VteEvent { // TODO: try not to allocate Vecs
|
||||
Print(char),
|
||||
Execute(u8), // byte
|
||||
Hook(Vec<i64>, Vec<u8>, bool, char), // params, intermediates, ignore, char
|
||||
|
|
@ -643,6 +686,7 @@ fn split_horizontally_with_gap (rect: &Winsize) -> (Winsize, Winsize) {
|
|||
(first_rect, second_rect)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ScreenInstruction {
|
||||
Pty(RawFd, VteEvent),
|
||||
Render,
|
||||
|
|
@ -732,10 +776,10 @@ impl Screen {
|
|||
let terminal_output = self.terminals.get_mut(&pid).unwrap();
|
||||
terminal_output.handle_event(event);
|
||||
}
|
||||
pub fn write_to_active_terminal(&self, byte: u8) {
|
||||
pub fn write_to_active_terminal(&mut self, byte: u8) {
|
||||
if let Some(active_terminal_id) = &self.get_active_terminal_id() {
|
||||
let mut buffer = [byte];
|
||||
self.os_api.write(*active_terminal_id, &mut buffer).expect("failed to write to terminal");
|
||||
self.os_api.write_to_tty_stdin(*active_terminal_id, &mut buffer).expect("failed to write to terminal");
|
||||
self.os_api.tcdrain(*active_terminal_id).expect("failed to drain terminal");
|
||||
}
|
||||
}
|
||||
|
|
@ -747,6 +791,7 @@ impl Screen {
|
|||
let mut stdout = self.os_api.get_stdout_writer();
|
||||
for (_pid, terminal) in self.terminals.iter_mut() {
|
||||
if let Some(vte_output) = terminal.buffer_as_vte_output() {
|
||||
// write boundaries
|
||||
if terminal.x_coords + terminal.display_cols < self.full_screen_ws.ws_col {
|
||||
let boundary_x_coords = terminal.x_coords + terminal.display_cols;
|
||||
let mut vte_output_boundaries = String::new();
|
||||
|
|
@ -990,19 +1035,19 @@ impl PtyBus {
|
|||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
pub fn main() {
|
||||
let os_input = get_os_input();
|
||||
start(os_input);
|
||||
start(Box::new(os_input));
|
||||
}
|
||||
|
||||
fn start(os_input: OsInputOutput) {
|
||||
pub fn start(mut os_input: Box<dyn OsApi>) {
|
||||
let mut active_threads = vec![];
|
||||
|
||||
let full_screen_ws = os_input.get_terminal_size_using_fd(0);
|
||||
os_input.into_raw_mode(0);
|
||||
let mut screen = Screen::new(&full_screen_ws, Box::new(os_input.clone()));
|
||||
let mut screen = Screen::new(&full_screen_ws, os_input.clone());
|
||||
let send_screen_instructions = screen.send_screen_instructions.clone();
|
||||
let mut pty_bus = PtyBus::new(send_screen_instructions.clone(), Box::new(os_input.clone()));
|
||||
let mut pty_bus = PtyBus::new(send_screen_instructions.clone(), os_input.clone());
|
||||
let send_pty_instructions = pty_bus.send_pty_instructions.clone();
|
||||
|
||||
active_threads.push(
|
||||
|
|
@ -1094,8 +1139,9 @@ fn start(os_input: OsInputOutput) {
|
|||
}
|
||||
// cleanup();
|
||||
let reset_style = "\u{1b}[m";
|
||||
let goodbye_message = format!("\r{}Bye from Mosaic!", reset_style);
|
||||
let goodbye_message = format!("\n\r{}Bye from Mosaic!", reset_style);
|
||||
|
||||
os_input.get_stdout_writer().write(goodbye_message.as_bytes()).unwrap();
|
||||
os_input.get_stdout_writer().flush().unwrap();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ fn spawn_terminal () -> (RawFd, RawFd) {
|
|||
let pid_primary = fork_pty_res.master;
|
||||
let pid_secondary = match fork_pty_res.fork_result {
|
||||
ForkResult::Parent { child } => {
|
||||
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
|
||||
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
|
||||
// fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::empty())).expect("could not fcntl");
|
||||
fcntl(pid_primary, FcntlArg::F_SETFL(OFlag::O_NONBLOCK)).expect("could not fcntl");
|
||||
child
|
||||
},
|
||||
ForkResult::Child => {
|
||||
|
|
@ -87,13 +87,13 @@ pub struct OsInputOutput {}
|
|||
|
||||
pub trait OsApi: Send + Sync {
|
||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize;
|
||||
fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16);
|
||||
fn into_raw_mode(&self, pid: RawFd);
|
||||
fn spawn_terminal(&self) -> (RawFd, RawFd);
|
||||
fn read(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn write(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
fn kill(&self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16);
|
||||
fn into_raw_mode(&mut self, pid: RawFd);
|
||||
fn spawn_terminal(&mut self) -> (RawFd, RawFd);
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>;
|
||||
fn get_stdin_reader(&self) -> Box<dyn Read>;
|
||||
fn get_stdout_writer(&self) -> Box<dyn Write>;
|
||||
fn box_clone(&self) -> Box<dyn OsApi>;
|
||||
|
|
@ -103,22 +103,22 @@ impl OsApi for OsInputOutput {
|
|||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize {
|
||||
get_terminal_size_using_fd(pid)
|
||||
}
|
||||
fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16) {
|
||||
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
|
||||
set_terminal_size_using_fd(pid, cols, rows);
|
||||
}
|
||||
fn into_raw_mode(&self, pid: RawFd) {
|
||||
fn into_raw_mode(&mut self, pid: RawFd) {
|
||||
into_raw_mode(pid);
|
||||
}
|
||||
fn spawn_terminal(&self) -> (RawFd, RawFd) {
|
||||
fn spawn_terminal(&mut self) -> (RawFd, RawFd) {
|
||||
spawn_terminal()
|
||||
}
|
||||
fn read(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
read(pid, buf)
|
||||
}
|
||||
fn write(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
|
||||
write(pid, buf)
|
||||
}
|
||||
fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
|
||||
tcdrain(pid)
|
||||
}
|
||||
fn box_clone(&self) -> Box<dyn OsApi> {
|
||||
|
|
@ -135,7 +135,7 @@ impl OsApi for OsInputOutput {
|
|||
let stdout = ::std::io::stdout();
|
||||
Box::new(stdout)
|
||||
}
|
||||
fn kill(&self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
|
||||
kill(Pid::from_raw(fd), None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3
src/tests/binary_inputs.rs
Normal file
3
src/tests/binary_inputs.rs
Normal file
File diff suppressed because one or more lines are too long
168
src/tests/fakes.rs
Normal file
168
src/tests/fakes.rs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
use ::std::time::Duration;
|
||||
use ::nix::pty::Winsize;
|
||||
use ::std::os::unix::io::RawFd;
|
||||
use ::std::io::{Read, Write};
|
||||
use ::std::collections::HashMap;
|
||||
use ::std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::os_input_output::OsApi;
|
||||
use crate::tests::possible_inputs::{Bytes, get_possible_inputs};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum IoEvent {
|
||||
Kill(RawFd),
|
||||
SetTerminalSizeUsingFd(RawFd, u16, u16),
|
||||
IntoRawMode(RawFd),
|
||||
TcDrain(RawFd),
|
||||
}
|
||||
|
||||
pub struct FakeStdinReader {
|
||||
pub input_chars: Bytes,
|
||||
}
|
||||
|
||||
impl FakeStdinReader {
|
||||
pub fn new(input_chars: Vec<u8>) -> Self {
|
||||
let bytes = Bytes::new().content(input_chars);
|
||||
FakeStdinReader {
|
||||
input_chars: bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FakeStdinReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
||||
::std::thread::sleep(Duration::from_millis(1000)); // TODO: adjust duration
|
||||
let read_position = self.input_chars.read_position;
|
||||
buf[0] = self.input_chars.content[read_position];
|
||||
self.input_chars.set_read_position(read_position + 1);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FakeStdoutWriter {
|
||||
output_buffer: Arc<Mutex<Vec<u8>>>,
|
||||
pub output_frames: Arc<Mutex<Vec<Vec<u8>>>>,
|
||||
}
|
||||
|
||||
impl Write for FakeStdoutWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||
let mut bytes_written = 0;
|
||||
let mut output_buffer = self.output_buffer.lock().unwrap();
|
||||
for byte in buf {
|
||||
bytes_written += 1;
|
||||
output_buffer.push(*byte);
|
||||
}
|
||||
Ok(bytes_written)
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||
let mut output_buffer = self.output_buffer.lock().unwrap();
|
||||
let mut output_frames = self.output_frames.lock().unwrap();
|
||||
let new_frame = output_buffer.drain(..).collect();
|
||||
output_frames.push(new_frame);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FakeInputOutput {
|
||||
read_buffers: Arc<Mutex<HashMap<RawFd, Bytes>>>,
|
||||
input_to_add: Arc<Mutex<Option<Vec<u8>>>>,
|
||||
stdin_writes: Arc<Mutex<HashMap<RawFd, Vec<u8>>>>,
|
||||
pub stdout_writer: FakeStdoutWriter, // stdout_writer.output is already an arc/mutex
|
||||
io_events: Arc<Mutex<Vec<IoEvent>>>,
|
||||
win_sizes: Arc<Mutex<HashMap<RawFd, Winsize>>>,
|
||||
possible_inputs: HashMap<u16, Bytes>,
|
||||
}
|
||||
|
||||
impl FakeInputOutput {
|
||||
pub fn new(winsize: Winsize) -> Self {
|
||||
let mut win_sizes = HashMap::new();
|
||||
win_sizes.insert(0 , winsize); // 0 is the current terminal
|
||||
FakeInputOutput {
|
||||
read_buffers: Arc::new(Mutex::new(HashMap::new())),
|
||||
stdin_writes: Arc::new(Mutex::new(HashMap::new())),
|
||||
input_to_add: Arc::new(Mutex::new(None)),
|
||||
stdout_writer: FakeStdoutWriter::default(),
|
||||
io_events: Arc::new(Mutex::new(vec![])),
|
||||
win_sizes: Arc::new(Mutex::new(win_sizes)),
|
||||
possible_inputs: get_possible_inputs(),
|
||||
}
|
||||
}
|
||||
pub fn add_terminal_input(&mut self, input: &[u8]) {
|
||||
self.input_to_add = Arc::new(Mutex::new(Some(input.to_vec())));
|
||||
}
|
||||
pub fn add_terminal(&mut self, fd: RawFd) {
|
||||
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
|
||||
}
|
||||
}
|
||||
|
||||
impl OsApi for FakeInputOutput {
|
||||
fn get_terminal_size_using_fd(&self, pid: RawFd) -> Winsize {
|
||||
let win_sizes = self.win_sizes.lock().unwrap();
|
||||
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_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 into_raw_mode(&mut self, pid: RawFd) {
|
||||
self.io_events.lock().unwrap().push(IoEvent::IntoRawMode(pid));
|
||||
}
|
||||
fn spawn_terminal(&mut self) -> (RawFd, RawFd) {
|
||||
let next_terminal_id = { self.read_buffers.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;
|
||||
if let Some(bytes) = read_buffers.get_mut(&pid) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
Ok(bytes_read)
|
||||
}
|
||||
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> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
fn get_stdin_reader(&self) -> Box<dyn Read> {
|
||||
let mut input_chars = vec![0, 1, 2];
|
||||
if let Some(input_to_add) = self.input_to_add.lock().unwrap().as_ref() {
|
||||
for byte in input_to_add {
|
||||
input_chars.push(*byte);
|
||||
}
|
||||
}
|
||||
input_chars.push(17); // ctrl-q (quit)
|
||||
let reader = FakeStdinReader::new(input_chars);
|
||||
Box::new(reader)
|
||||
}
|
||||
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(())
|
||||
}
|
||||
}
|
||||
99
src/tests/integration.rs
Normal file
99
src/tests/integration.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use ::nix::pty::Winsize;
|
||||
use ::insta::assert_snapshot;
|
||||
|
||||
use crate::{start, TerminalOutput};
|
||||
use crate::tests::fakes::{FakeInputOutput};
|
||||
|
||||
pub fn get_fake_os_input () -> FakeInputOutput {
|
||||
let fake_win_size = Winsize {
|
||||
ws_col: 121,
|
||||
ws_row: 20,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
FakeInputOutput::new(fake_win_size)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn starts_with_one_terminal () {
|
||||
let mut fake_input_output = get_fake_os_input();
|
||||
fake_input_output.add_terminal_input(&[17]); // quit (ctrl-q)
|
||||
start(Box::new(fake_input_output.clone()));
|
||||
|
||||
let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let main_pid = 0;
|
||||
let x = 0;
|
||||
let fake_win_size = Winsize { // TODO: combine with above
|
||||
ws_col: 121,
|
||||
ws_row: 20,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x);
|
||||
|
||||
for frame in output_frames.iter() {
|
||||
for byte in frame.iter() {
|
||||
vte_parser.advance(&mut terminal_output, *byte);
|
||||
}
|
||||
let output_lines = terminal_output.read_buffer_as_lines();
|
||||
let cursor_position_in_last_line = terminal_output.cursor_position_in_last_line();
|
||||
let mut snapshot = String::new();
|
||||
for (line_index, line) in output_lines.iter().enumerate() {
|
||||
for (character_index, terminal_character) in line.iter().enumerate() {
|
||||
if line_index == output_lines.len() - 1 && character_index == cursor_position_in_last_line {
|
||||
snapshot.push('█');
|
||||
} else {
|
||||
snapshot.push(terminal_character.character);
|
||||
}
|
||||
}
|
||||
if line_index != output_lines.len() - 1 {
|
||||
snapshot.push('\n');
|
||||
}
|
||||
}
|
||||
assert_snapshot!(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn split_terminals_vertically() {
|
||||
// TODO: this test is a little flaky - there's some sort of race condition that makes the
|
||||
// second terminal in the split appear blank sometimes - fix this
|
||||
let mut fake_input_output = get_fake_os_input();
|
||||
fake_input_output.add_terminal_input(&[14, 17]); // split-vertically and quit (ctrl-n + ctrl-q)
|
||||
start(Box::new(fake_input_output.clone()));
|
||||
|
||||
let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();
|
||||
let mut vte_parser = vte::Parser::new();
|
||||
let main_pid = 0;
|
||||
let x = 0;
|
||||
let fake_win_size = Winsize { // TODO: combine with above
|
||||
ws_col: 121,
|
||||
ws_row: 20,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
let mut terminal_output = TerminalOutput::new(main_pid, fake_win_size, x);
|
||||
|
||||
for frame in output_frames.iter() {
|
||||
for byte in frame.iter() {
|
||||
vte_parser.advance(&mut terminal_output, *byte);
|
||||
}
|
||||
let output_lines = terminal_output.read_buffer_as_lines();
|
||||
let cursor_position_in_last_line = terminal_output.cursor_position_in_last_line();
|
||||
let mut snapshot = String::new();
|
||||
for (line_index, line) in output_lines.iter().enumerate() {
|
||||
for (character_index, terminal_character) in line.iter().enumerate() {
|
||||
if line_index == output_lines.len() - 1 && character_index == cursor_position_in_last_line {
|
||||
snapshot.push('█');
|
||||
} else {
|
||||
snapshot.push(terminal_character.character);
|
||||
}
|
||||
}
|
||||
if line_index != output_lines.len() - 1 {
|
||||
snapshot.push('\n');
|
||||
}
|
||||
}
|
||||
assert_snapshot!(snapshot);
|
||||
}
|
||||
}
|
||||
4
src/tests/mod.rs
Normal file
4
src/tests/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod integration;
|
||||
pub mod possible_inputs;
|
||||
pub mod binary_inputs;
|
||||
pub mod fakes;
|
||||
33
src/tests/possible_inputs.rs
Normal file
33
src/tests/possible_inputs.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use std::collections::HashMap;
|
||||
use crate::tests::binary_inputs::{COL_121, COL_60};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes {
|
||||
pub content: Vec<u8>,
|
||||
pub read_position: usize,
|
||||
}
|
||||
|
||||
impl Bytes {
|
||||
pub fn new() -> Self {
|
||||
Bytes {
|
||||
content: vec![],
|
||||
read_position: 0
|
||||
}
|
||||
}
|
||||
pub fn content(mut self, content: Vec<u8>) -> Self {
|
||||
self.content = content;
|
||||
self
|
||||
}
|
||||
pub fn set_read_position(&mut self, read_position: usize) {
|
||||
self.read_position = read_position;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_possible_inputs () -> HashMap<u16, Bytes> { // the key is the column count for this terminal input
|
||||
let mut possible_inputs = HashMap::new();
|
||||
let col_60_bytes = Bytes::new().content(Vec::from(COL_60));
|
||||
let col_121_bytes = Bytes::new().content(Vec::from(COL_121));
|
||||
possible_inputs.insert(121, col_121_bytes);
|
||||
possible_inputs.insert(60, col_60_bytes);
|
||||
possible_inputs
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│
|
||||
⋊> ~/c/mosaic on main ⨯ │
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ │⋊> ~/c/mosaic on main ⨯ █ 19:40:00
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
│
|
||||
Welcome to fish, the friendly interactive shell │
|
||||
│
|
||||
│Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ │⋊> ~/c/mosaic on main ⨯ 19:40:00
|
||||
Bye from Mosaic!
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ █ 10:47:03
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ 10:47:03
|
||||
Bye from Mosaic!
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
source: src/tests/integration.rs
|
||||
expression: snapshot
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Welcome to fish, the friendly interactive shell
|
||||
⋊> ~/c/mosaic on main ⨯ █ 10:47:03
|
||||
Loading…
Add table
Reference in a new issue