From ec35b9fa0aea3bd650638f4695a1ea9685b82d1e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 20 Oct 2019 10:52:02 +0300 Subject: [PATCH] embed test #2 --- Cargo.lock | 15 +- src/bin.rs | 5 +- ui/Cargo.toml | 2 +- ui/src/components/mail/compose.rs | 43 +++- ui/src/terminal/embed.rs | 414 ++++++------------------------ ui/src/terminal/embed/grid.rs | 376 +++++++++++++++++++++++++++ ui/src/types.rs | 9 +- 7 files changed, 526 insertions(+), 338 deletions(-) create mode 100644 ui/src/terminal/embed/grid.rs diff --git a/Cargo.lock b/Cargo.lock index 55f61a1c9..e73e33c8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,6 +645,18 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -1181,7 +1193,7 @@ dependencies = [ "linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "melib 0.3.2", "mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)", - "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)", "notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1359,6 +1371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" +"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943" diff --git a/src/bin.rs b/src/bin.rs index d74036523..7d71d7912 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -303,10 +303,13 @@ fn main() -> std::result::Result<(), std::io::Error> { }, } }, + UIMode::Embed => { + state.rcv_event(UIEvent::EmbedInput(k)); + state.redraw(); + }, UIMode::Fork => { break 'inner; // `goto` 'reap loop, and wait on child. }, - UIMode::Embed => {} } }, ThreadEvent::RefreshMailbox(event) => { diff --git a/ui/Cargo.toml b/ui/Cargo.toml index 5e61d0641..307df1bc3 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -25,7 +25,7 @@ uuid = { version = "0.7.4", features = ["serde", "v4"] } unicode-segmentation = "1.2.1" # >:c text_processing = { path = "../text_processing", version = "*" } libc = {version = "0.2.59", features = ["extra_traits",]} -nix = "*" +nix = "0.15.0" [features] default = [] diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index cd02ed43c..547a274e5 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -21,6 +21,7 @@ use super::*; +use crate::terminal::embed::EmbedPty; use melib::Draft; use mime_apps::query_mime_info; use std::str::FromStr; @@ -46,6 +47,7 @@ pub struct Composer { mode: ViewMode, body_area: Area, // Cache body_area in case we need to replace it with a pseudoterminal + embed: Option, sign_mail: ToggleFlag, dirty: bool, has_changes: bool, @@ -70,6 +72,7 @@ impl Default for Composer { dirty: true, has_changes: false, body_area: ((0, 0), (0, 0)), + embed: None, initialized: false, id: ComponentId::new_v4(), } @@ -530,6 +533,25 @@ impl Component for Composer { Color::Default, ); } + self.body_area = body_area; + if let Some(ref mut embed_pty) = self.embed { + let lock = embed_pty.grid.lock().unwrap(); + copy_area( + grid, + &lock, + area, + ((0, 0), pos_dec(embed_pty.terminal_size, (1, 1))), + ); + for y in 0..embed_pty.terminal_size.1 { + for x in 0..embed_pty.terminal_size.0 { + if lock[(x, y)].ch() != ' ' { + debug!("coors {:?} char = {}", (x, y), lock[(x, y)].ch()); + } + } + } + context.dirty_areas.push_back(area); + debug!("copied grid"); + } match self.mode { ViewMode::ThreadView | ViewMode::Edit => {} @@ -747,12 +769,27 @@ impl Component for Composer { } return true; } + UIEvent::EmbedInput(Key::Char(c)) => { + debug!("got embed input {:?}", event); + let mut buf: [u8; 4] = [0; 4]; + let s = c.encode_utf8(&mut buf); + + use std::io::Write; + self.embed + .as_mut() + .unwrap() + .stdin + .write_all(s.as_bytes()) + .unwrap(); + self.dirty = true; + return true; + } UIEvent::Input(Key::Char('e')) => { /* Edit draft in $EDITOR */ use std::process::{Command, Stdio}; - context.input_kill(); - crate::terminal::embed::create_pty(self.body_area).unwrap(); - + self.embed = Some(crate::terminal::embed::create_pty(self.body_area).unwrap()); + self.dirty = true; + debug!("returned"); context .replies .push_back(UIEvent::ChangeMode(UIMode::Embed)); diff --git a/ui/src/terminal/embed.rs b/ui/src/terminal/embed.rs index 338a0a0fe..dddbf6cb6 100644 --- a/ui/src/terminal/embed.rs +++ b/ui/src/terminal/embed.rs @@ -1,8 +1,17 @@ use crate::terminal::position::Area; use nix::fcntl::{open, OFlag}; use nix::ioctl_write_ptr_bad; +use nix::libc::{STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO}; use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize}; use nix::sys::stat::Mode; +use nix::sys::{stat, wait}; +use nix::unistd::{dup2, fork, setsid, ForkResult}; +use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; + +mod grid; + +use crate::terminal::cells::{Cell, CellBuffer}; +pub use grid::*; // ioctl command to set window size of pty: use libc::TIOCSWINSZ; @@ -11,13 +20,20 @@ use std::process::{Command, Stdio}; use std::io::Read; use std::io::Write; -use std::os::unix::io::{FromRawFd, IntoRawFd}; +use std::sync::{Arc, Mutex}; ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize); static SWITCHALTERNATIVE_1049: &'static [u8] = &[b'1', b'0', b'4', b'9']; -pub fn create_pty(area: Area) -> nix::Result<()> { +#[derive(Debug)] +pub struct EmbedPty { + pub grid: Arc>, + pub stdin: std::fs::File, + pub terminal_size: (usize, usize), +} + +pub fn create_pty(area: Area) -> nix::Result { // Open a new PTY master let master_fd = posix_openpt(OFlag::O_RDWR)?; @@ -29,34 +45,55 @@ pub fn create_pty(area: Area) -> nix::Result<()> { let slave_name = unsafe { ptsname(&master_fd) }?; // Try to open the slave - let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; + //let _slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty())?; + { + let winsize = Winsize { + ws_row: 40, + ws_col: 80, + ws_xpixel: 0, + ws_ypixel: 0, + }; - Command::new("vim") - .stdin(Stdio::inherit()) - .stdout(unsafe { Stdio::from_raw_fd(_slave_fd) }) - .stderr(unsafe { Stdio::from_raw_fd(_slave_fd) }) - .spawn(); + let master_fd = master_fd.clone().into_raw_fd(); + unsafe { set_window_size(master_fd, &winsize).unwrap() }; + } + match fork() { + Ok(ForkResult::Child) => { + setsid().unwrap(); // create new session with child as session leader + let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?; + + // assign stdin, stdout, stderr to the tty, just like a terminal does + dup2(slave_fd, STDIN_FILENO).unwrap(); + dup2(slave_fd, STDOUT_FILENO).unwrap(); + dup2(slave_fd, STDERR_FILENO).unwrap(); + std::process::Command::new("vim").status().unwrap(); + } + Ok(ForkResult::Parent { child: _ }) => {} + Err(e) => panic!(e), + }; + + let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) }; + let stdin_ = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) }; + let grid = Arc::new(Mutex::new(CellBuffer::new(80, 40, Cell::default()))); + let grid_ = grid.clone(); + let terminal_size = (80, 40); std::thread::Builder::new() .spawn(move || { - let winsize = Winsize { - ws_row: 20, - ws_col: 80, - ws_xpixel: 0, - ws_ypixel: 0, - }; - //lock.write(b"\x1b3g").unwrap(); //clear all let master_fd = master_fd.into_raw_fd(); - unsafe { set_window_size(master_fd, &winsize).unwrap() }; let master_file = unsafe { std::fs::File::from_raw_fd(master_fd) }; - forward_pty_translate_escape_codes(master_file, area); + forward_pty_translate_escape_codes(master_file, area, grid_, stdin_); }) .unwrap(); - Ok(()) + Ok(EmbedPty { + grid, + stdin, + terminal_size, + }) } #[derive(Debug)] -enum State { +pub enum State { ExpectingControlChar, G0, // Designate G0 Character Set Osc1(Vec), //ESC ] Operating System Command (OSC is 0x9d). @@ -71,6 +108,13 @@ enum State { struct EscCode<'a>(&'a State, u8); +impl<'a> From<(&'a mut State, u8)> for EscCode<'a> { + fn from(val: (&mut State, u8)) -> EscCode { + let (s, b) = val; + EscCode(s, b) + } +} + impl<'a> From<(&'a State, u8)> for EscCode<'a> { fn from(val: (&State, u8)) -> EscCode { let (s, b) = val; @@ -122,6 +166,15 @@ impl std::fmt::Display for EscCode<'_> { "ESC[{}n\t\tCSI Device Status Report (DSR)| Report Cursor Position", unsafestr!(buf) ), + EscCode(Csi1(ref buf), b't') if buf == b"18" => write!( + f, + "ESC[18t\t\tReport the size of the text area in characters", + ), + EscCode(Csi1(ref buf), b't') => write!( + f, + "ESC[{buf}t\t\tWindow manipulation, skipped", + buf = unsafestr!(buf) + ), EscCode(Csi1(ref buf), b'B') => write!( f, "ESC[{buf}B\t\tCSI Cursor Down {buf} Times", @@ -208,7 +261,12 @@ impl std::fmt::Display for EscCode<'_> { } } -fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, area: Area) { +fn forward_pty_translate_escape_codes( + pty_fd: std::fs::File, + area: Area, + grid: Arc>, + stdin: std::fs::File, +) { let (upper_left, bottom_right) = area; let (upper_x, upper_y) = upper_left; let (bottom_x, bottom_y) = bottom_right; @@ -222,322 +280,18 @@ fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, area: Area) { debug!(&upper_y_str); debug!(&bottom_x_str); debug!(&bottom_y_str); - let stdout = std::io::stdout(); - let mut buf1: Vec = Vec::with_capacity(8); - let mut buf2: Vec = Vec::with_capacity(8); - let mut buf3: Vec = Vec::with_capacity(8); - let mut lock = stdout.lock(); - - let mut state = State::Normal; + let mut embed_grid = EmbedGrid::new(grid, stdin); + embed_grid.set_terminal_size((79, 39)); let mut bytes_iter = pty_fd.bytes(); - macro_rules! cleanup { - (CSIQ) => { - if let State::CsiQ(ref mut buf1_p) = state { - std::mem::swap(buf1_p, &mut buf1); - } - }; - (CSI1) => { - if let State::Csi1(ref mut buf1_p) = state { - std::mem::swap(buf1_p, &mut buf1); - } - }; - (CSI2) => { - if let State::Csi2(ref mut buf1_p, ref mut buf2_p) = state { - std::mem::swap(buf1_p, &mut buf1); - std::mem::swap(buf2_p, &mut buf2); - } - }; - (CSI3) => { - if let State::Csi3(ref mut buf1_p, ref mut buf2_p, ref mut buf3_p) = state { - std::mem::swap(buf1_p, &mut buf1); - std::mem::swap(buf2_p, &mut buf2); - std::mem::swap(buf3_p, &mut buf3); - } - }; - (OSC1) => { - if let State::Osc1(ref mut buf1_p) = state { - std::mem::swap(buf1_p, &mut buf1); - } - }; - (OSC2) => { - if let State::Osc2(ref mut buf1_p, ref mut buf2_p) = state { - std::mem::swap(buf1_p, &mut buf1); - std::mem::swap(buf2_p, &mut buf2); - } - }; - } - - macro_rules! restore_global_buf { - ($b:ident) => { - let mut $b = std::mem::replace(&mut $b, Vec::new()); - $b.clear(); - }; - } - let mut prev_char = b'\0'; + debug!("waiting for bytes"); while let Some(Ok(byte)) = bytes_iter.next() { + debug!("got byte {}", byte as char); debug!( "{}{} byte is {} and state is {:?}", - prev_char as char, byte as char, byte as char, &state + prev_char as char, byte as char, byte as char, &embed_grid.state ); prev_char = byte; - match (byte, &mut state) { - (b'\x1b', State::Normal) => { - state = State::ExpectingControlChar; - } - (b']', State::ExpectingControlChar) => { - restore_global_buf!(buf1); - state = State::Osc1(buf1); - } - (b'[', State::ExpectingControlChar) => { - state = State::Csi; - } - (b'(', State::ExpectingControlChar) => { - state = State::G0; - } - (c, State::ExpectingControlChar) => { - debug!( - "unrecognised: byte is {} and state is {:?}", - byte as char, &state - ); - state = State::Normal; - } - (b'?', State::Csi) => { - restore_global_buf!(buf1); - state = State::CsiQ(buf1); - } - /* ********** */ - /* ********** */ - /* ********** */ - /* OSC stuff */ - (c, State::Osc1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => { - buf.push(c); - } - (b';', State::Osc1(ref mut buf1_p)) => { - let buf1 = std::mem::replace(buf1_p, Vec::new()); - let mut buf2 = std::mem::replace(&mut buf2, Vec::new()); - buf2.clear(); - state = State::Osc2(buf1, buf2); - } - (c, State::Osc2(_, ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => { - buf.push(c); - } - (c, State::Osc1(ref buf1)) => { - lock.write_all(&[b'\x1b', b']']).unwrap(); - lock.write_all(buf1).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - cleanup!(OSC1); - state = State::Normal; - } - (c, State::Osc2(ref buf1, ref buf2)) => { - lock.write_all(&[b'\x1b', b']']).unwrap(); - lock.write_all(buf1).unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all(buf2).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - cleanup!(OSC2); - state = State::Normal; - } - /* END OF OSC */ - /* ********** */ - /* ********** */ - /* ********** */ - /* ********** */ - (c, State::Normal) => { - lock.write(&[byte]).unwrap(); - lock.flush().unwrap(); - } - (b'u', State::Csi) => { - /* restore cursor */ - lock.write_all(&[b'\x1b', b'[', b'u']).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - lock.flush().unwrap(); - state = State::Normal; - } - (b'm', State::Csi) => { - /* Character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */ - lock.write_all(&[b'\x1b', b'[', b'm']).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - lock.flush().unwrap(); - state = State::Normal; - } - (b'H', State::Csi) => { - /* move cursor to (1,1) */ - lock.write_all(&[b'\x1b', b'[']).unwrap(); - lock.write_all(upper_x_str.as_bytes()).unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all(upper_y_str.as_bytes()).unwrap(); - lock.write_all(&[b'H']).unwrap(); - debug!( - "sending translating {} to ESC[{};{}H", - EscCode::from((&state, byte)), - upper_x_str, - upper_y_str, - ); - lock.flush().unwrap(); - state = State::Normal; - } - /* CSI ? stuff */ - (c, State::CsiQ(ref mut buf)) if c >= b'0' && c <= b'9' => { - buf.push(c); - } - (c, State::CsiQ(ref mut buf)) => { - // we are already in AlternativeScreen so do not forward this - if &buf.as_slice() != &SWITCHALTERNATIVE_1049 { - lock.write_all(&[b'\x1b', b'[', b'?']).unwrap(); - lock.write_all(buf).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - } - cleanup!(CSIQ); - state = State::Normal; - } - /* END OF CSI ? stuff */ - /* ******************* */ - /* ******************* */ - /* ******************* */ - (c, State::Csi) if c >= b'0' && c <= b'9' => { - let mut buf1 = std::mem::replace(&mut buf1, Vec::new()); - buf1.clear(); - buf1.push(c); - state = State::Csi1(buf1); - } - (c, State::Csi) => { - lock.write_all(&[b'\x1b', b'[', c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - lock.flush().unwrap(); - state = State::Normal; - } - (b'K', State::Csi1(_)) => { - /* Erase in Display (ED), VT100.*/ - cleanup!(CSI1); - state = State::Normal; - } - (b'J', State::Csi1(_)) => { - /* Erase in Display (ED), VT100.*/ - cleanup!(CSI1); - state = State::Normal; - } - (b't', State::Csi1(_)) => { - /* Window manipulation, skip it */ - cleanup!(CSI1); - state = State::Normal; - } - (b';', State::Csi1(ref mut buf1_p)) => { - let buf1 = std::mem::replace(buf1_p, Vec::new()); - let mut buf2 = std::mem::replace(&mut buf2, Vec::new()); - buf2.clear(); - state = State::Csi2(buf1, buf2); - } - (c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => { - buf.push(c); - } - (c, State::Csi1(ref buf)) => { - lock.write_all(&[b'\x1b', b'[']).unwrap(); - lock.write_all(buf).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - cleanup!(CSI1); - state = State::Normal; - } - (b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => { - let buf1 = std::mem::replace(buf1_p, Vec::new()); - let buf2 = std::mem::replace(buf2_p, Vec::new()); - let mut buf3 = std::mem::replace(&mut buf3, Vec::new()); - buf3.clear(); - state = State::Csi3(buf1, buf2, buf3); - } - (b'n', State::Csi2(_, _)) => { - // Report Cursor Position, skip it - cleanup!(CSI2); - state = State::Normal; - } - (b't', State::Csi2(_, _)) => { - // Window manipulation, skip it - cleanup!(CSI2); - state = State::Normal; - } - (b'H', State::Csi2(ref x, ref y)) => { - //Cursor Position [row;column] (default = [1,1]) (CUP). - let orig_x = unsafe { std::str::from_utf8_unchecked(x) } - .parse::() - .unwrap(); - let orig_y = unsafe { std::str::from_utf8_unchecked(y) } - .parse::() - .unwrap(); - if orig_x + upper_x + 1 > bottom_x || orig_y + upper_y + 1 > bottom_y { - debug!(orig_x); - debug!(orig_y); - debug!(area); - } else { - debug!("orig_x + upper_x = {}", orig_x + upper_x); - debug!("orig_y + upper_y = {}", orig_y + upper_y); - lock.write_all(&[b'\x1b', b'[']).unwrap(); - lock.write_all((orig_x + upper_x).to_string().as_bytes()) - .unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all((orig_y + upper_y).to_string().as_bytes()) - .unwrap(); - lock.write_all(&[b'H']).unwrap(); - debug!( - "sending translating {} to ESC[{};{}H ", - EscCode::from((&state, byte)), - orig_x + upper_x, - orig_y + upper_y - ); - } - cleanup!(CSI2); - state = State::Normal; - } - (c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => { - buf.push(c); - } - (c, State::Csi2(ref buf1, ref buf2)) => { - lock.write_all(&[b'\x1b', b'[']).unwrap(); - lock.write_all(buf1).unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all(buf2).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - cleanup!(CSI2); - state = State::Normal; - } - (b't', State::Csi3(_, _, _)) => { - // Window manipulation, skip it - cleanup!(CSI3); - state = State::Normal; - } - - (c, State::Csi3(_, _, ref mut buf)) if c >= b'0' && c <= b'9' => { - buf.push(c); - } - (c, State::Csi3(ref buf1, ref buf2, ref buf3)) => { - lock.write_all(&[b'\x1b', b'[']).unwrap(); - lock.write_all(buf1).unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all(buf2).unwrap(); - lock.write_all(&[b';']).unwrap(); - lock.write_all(buf3).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - cleanup!(CSI3); - state = State::Normal; - } - /* other stuff */ - /* ******************* */ - /* ******************* */ - /* ******************* */ - (c, State::G0) => { - lock.write_all(&[b'\x1b', b'(']).unwrap(); - lock.write_all(&[c]).unwrap(); - debug!("sending {}", EscCode::from((&state, byte))); - state = State::Normal; - } - (b, s) => { - debug!("unrecognised: byte is {} and state is {:?}", b as char, s); - } - } + embed_grid.process_byte(byte); } } diff --git a/ui/src/terminal/embed/grid.rs b/ui/src/terminal/embed/grid.rs new file mode 100644 index 000000000..f425273ba --- /dev/null +++ b/ui/src/terminal/embed/grid.rs @@ -0,0 +1,376 @@ +use super::*; +use crate::terminal::cells::{Cell, CellBuffer}; +use std::sync::{Arc, Mutex}; + +pub struct EmbedGrid { + cursor: (usize, usize), + terminal_size: (usize, usize), + grid: Arc>, + pub state: State, + stdin: std::fs::File, +} + +impl EmbedGrid { + pub fn new(grid: Arc>, stdin: std::fs::File) -> Self { + EmbedGrid { + cursor: (1, 1), + terminal_size: (0, 0), + grid, + state: State::Normal, + stdin, + } + } + + pub fn set_terminal_size(&mut self, new_val: (usize, usize)) { + self.terminal_size = new_val; + } + + pub fn process_byte(&mut self, byte: u8) { + let EmbedGrid { + ref mut cursor, + ref terminal_size, + ref mut grid, + ref mut state, + ref mut stdin, + } = self; + + macro_rules! increase_cursor_x { + () => { + if *cursor == *terminal_size { + /* do nothing */ + } else if cursor.0 == terminal_size.0 { + cursor.0 = 0; + cursor.1 += 1; + } else { + cursor.0 += 1; + } + }; + } + + let mut state = state; + match (byte, &mut state) { + (b'\x1b', State::Normal) => { + *state = State::ExpectingControlChar; + } + (b']', State::ExpectingControlChar) => { + let buf1 = Vec::new(); + *state = State::Osc1(buf1); + } + (b'[', State::ExpectingControlChar) => { + *state = State::Csi; + } + (b'(', State::ExpectingControlChar) => { + *state = State::G0; + } + (c, State::ExpectingControlChar) => { + debug!( + "unrecognised: byte is {} and state is {:?}", + byte as char, state + ); + *state = State::Normal; + } + (b'?', State::Csi) => { + let buf1 = Vec::new(); + *state = State::CsiQ(buf1); + } + /* ********** */ + /* ********** */ + /* ********** */ + /* OSC stuff */ + (c, State::Osc1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => { + buf.push(c); + } + (b';', State::Osc1(ref mut buf1_p)) => { + let buf1 = std::mem::replace(buf1_p, Vec::new()); + let buf2 = Vec::new(); + *state = State::Osc2(buf1, buf2); + } + (c, State::Osc2(_, ref mut buf)) if (c >= b'0' && c <= b'9') || c == b'?' => { + buf.push(c); + } + (c, State::Osc1(_)) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (c, State::Osc2(_, _)) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + /* END OF OSC */ + /* ********** */ + /* ********** */ + /* ********** */ + /* ********** */ + (c, State::Normal) => { + grid.lock().unwrap()[*cursor].set_ch(c as char); + debug!("setting cell {:?} char '{}'", cursor, c as char); + increase_cursor_x!(); + } + (b'u', State::Csi) => { + /* restore cursor */ + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b'm', State::Csi) => { + /* Character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */ + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b'H', State::Csi) => { + /* move cursor to (1,1) */ + + debug!("sending {}", EscCode::from((&(*state), byte)),); + debug!("move cursor to (1,1) cursor before: {:?}", *cursor); + *cursor = (0, 0); + debug!("cursor after: {:?}", *cursor); + *state = State::Normal; + } + /* CSI ? stuff */ + (c, State::CsiQ(ref mut buf)) if c >= b'0' && c <= b'9' => { + buf.push(c); + } + (c, State::CsiQ(ref mut buf)) => { + // we are already in AlternativeScreen so do not forward this + if &buf.as_slice() != &SWITCHALTERNATIVE_1049 { + debug!("sending {}", EscCode::from((&(*state), byte))); + } + *state = State::Normal; + } + /* END OF CSI ? stuff */ + /* ******************* */ + /* ******************* */ + /* ******************* */ + (c, State::Csi) if c >= b'0' && c <= b'9' => { + let mut buf1 = Vec::new(); + buf1.push(c); + *state = State::Csi1(buf1); + } + (b'J', State::Csi) => { + // "ESC[J\t\tCSI Erase from the cursor to the end of the screen [BAD]" + debug!("sending {}", EscCode::from((&(*state), byte))); + let mut grid = grid.lock().unwrap(); + debug!("erasing from {:?} to {:?}", cursor, terminal_size); + for y in cursor.1..terminal_size.1 { + for x in cursor.0..terminal_size.0 { + cursor.0 = x; + grid[(x, y)] = Cell::default(); + } + cursor.1 = y; + } + *state = State::Normal; + } + (b'K', State::Csi) => { + // "ESC[K\t\tCSI Erase from the cursor to the end of the line [BAD]" + debug!("sending {}", EscCode::from((&(*state), byte))); + let mut grid = grid.lock().unwrap(); + for x in cursor.0..terminal_size.0 { + grid[(x, terminal_size.1)] = Cell::default(); + } + *state = State::Normal; + } + (c, State::Csi) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b'K', State::Csi1(_)) => { + /* Erase in Display (ED), VT100.*/ + debug!("not sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b'J', State::Csi1(_)) => { + /* Erase in Display (ED), VT100.*/ + debug!("not sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b't', State::Csi1(buf)) => { + /* Window manipulation, skip it */ + if buf == b"18" { + // P s = 1 8 → Report the size of the text area in characters as CSI 8 ; height ; width t + stdin.write_all(&[b'\x1b', b'[', b'8', b';']).unwrap(); + stdin + .write_all((terminal_size.0 + 1).to_string().as_bytes()) + .unwrap(); + stdin.write_all(&[b';']).unwrap(); + stdin + .write_all((terminal_size.1 + 1).to_string().as_bytes()) + .unwrap(); + stdin.write_all(&[b't']).unwrap(); + } + debug!("not sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b'n', State::Csi1(_)) => { + /* report cursor position */ + debug!("got {}", EscCode::from((&(*state), byte))); + stdin.write_all(&[b'\x1b', b'[']).unwrap(); + // Ps = 6 ⇒ Report Cursor Position (CPR) [row;column]. + //Result is CSI r ; c R + stdin + .write_all((cursor.0 + 1).to_string().as_bytes()) + .unwrap(); + stdin.write_all(&[b';']).unwrap(); + stdin + .write_all((cursor.1 + 1).to_string().as_bytes()) + .unwrap(); + stdin.write_all(&[b'R']).unwrap(); + *state = State::Normal; + } + (b'B', State::Csi1(buf)) => { + //"ESC[{buf}B\t\tCSI Cursor Down {buf} Times", + let offset = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!("cursor down {} times, cursor was: {:?}", offset, cursor); + if offset + cursor.1 < terminal_size.1 { + cursor.1 += offset; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b'C', State::Csi1(buf)) => { + // "ESC[{buf}C\t\tCSI Cursor Forward {buf} Times", + let offset = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!("cursor forward {} times, cursor was: {:?}", offset, cursor); + if offset + cursor.0 < terminal_size.0 { + cursor.0 += offset; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b'D', State::Csi1(buf)) => { + // "ESC[{buf}D\t\tCSI Cursor Backward {buf} Times", + let offset = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!("cursor backward {} times, cursor was: {:?}", offset, cursor); + if offset + cursor.0 < terminal_size.0 { + cursor.0 += offset; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b'E', State::Csi1(buf)) => { + //"ESC[{buf}E\t\tCSI Cursor Next Line {buf} Times", + let offset = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!( + "cursor next line {} times, cursor was: {:?}", + offset, cursor + ); + if offset + cursor.1 < terminal_size.1 { + cursor.1 += offset; + cursor.0 = 0; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b'G', State::Csi1(buf)) => { + // "ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])", + let new_col = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor); + if new_col < terminal_size.0 { + cursor.0 = new_col; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b'C', State::Csi1(buf)) => { + // "ESC[{buf}F\t\tCSI Cursor Preceding Line {buf} Times", + let offset = unsafe { std::str::from_utf8_unchecked(buf) } + .parse::() + .unwrap(); + debug!( + "cursor preceding {} times, cursor was: {:?}", + offset, cursor + ); + if cursor.1 < offset + terminal_size.1 { + cursor.1 -= offset; + cursor.0 = 0; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (b';', State::Csi1(ref mut buf1_p)) => { + let buf1 = std::mem::replace(buf1_p, Vec::new()); + let buf2 = Vec::new(); + *state = State::Csi2(buf1, buf2); + } + (c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => { + buf.push(c); + } + (c, State::Csi1(ref buf)) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b';', State::Csi2(ref mut buf1_p, ref mut buf2_p)) => { + let buf1 = std::mem::replace(buf1_p, Vec::new()); + let buf2 = std::mem::replace(buf2_p, Vec::new()); + let buf3 = Vec::new(); + *state = State::Csi3(buf1, buf2, buf3); + } + (b'n', State::Csi2(_, _)) => { + // Report Cursor Position, skip it + *state = State::Normal; + } + (b't', State::Csi2(_, _)) => { + // Window manipulation, skip it + *state = State::Normal; + } + (b'H', State::Csi2(ref x, ref y)) => { + //Cursor Position [row;column] (default = [1,1]) (CUP). + let orig_x = unsafe { std::str::from_utf8_unchecked(x) } + .parse::() + .unwrap(); + let orig_y = unsafe { std::str::from_utf8_unchecked(y) } + .parse::() + .unwrap(); + debug!("sending {}", EscCode::from((&(*state), byte)),); + debug!( + "cursor set to ({},{}), cursor was: {:?}", + orig_x, orig_y, cursor + ); + if orig_x - 1 <= terminal_size.0 && orig_y - 1 <= terminal_size.1 { + cursor.0 = orig_x - 1; + cursor.1 = orig_y - 1; + } + debug!("cursor became: {:?}", cursor); + *state = State::Normal; + } + (c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => { + buf.push(c); + } + (c, State::Csi2(ref buf1, ref buf2)) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b't', State::Csi3(_, _, _)) => { + // Window manipulation, skip it + *state = State::Normal; + } + + (c, State::Csi3(_, _, ref mut buf)) if c >= b'0' && c <= b'9' => { + buf.push(c); + } + (c, State::Csi3(ref buf1, ref buf2, ref buf3)) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + /* other stuff */ + /* ******************* */ + /* ******************* */ + /* ******************* */ + (c, State::G0) => { + debug!("sending {}", EscCode::from((&(*state), byte))); + *state = State::Normal; + } + (b, s) => { + debug!("unrecognised: byte is {} and state is {:?}", b as char, s); + } + } + } +} diff --git a/ui/src/types.rs b/ui/src/types.rs index 3ff9e39b7..57af6f729 100644 --- a/ui/src/types.rs +++ b/ui/src/types.rs @@ -64,7 +64,10 @@ impl From for ThreadEvent { #[derive(Debug)] pub enum ForkType { - Finished, // Already finished fork, we only want to restore input/output + /// Already finished fork, we only want to restore input/output + Finished, + /// Embed pty + Embed, Generic(std::process::Child), NewDraft(File, std::process::Child), } @@ -81,6 +84,7 @@ pub enum UIEvent { Input(Key), ExInput(Key), InsertInput(Key), + EmbedInput(Key), RefreshMailbox((usize, FolderHash)), //view has changed to FolderHash mailbox //Quit? Resize, @@ -111,9 +115,10 @@ impl From for UIEvent { pub enum UIMode { Normal, Insert, + /// Forward input to an embed pseudoterminal. + Embed, Execute, Fork, - Embed, } impl fmt::Display for UIMode {