From 592339bdcae593ff4de5bcb627cb0c643d9ee4c2 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 12 Sep 2021 13:47:32 +0300 Subject: [PATCH] embed: split EmbedGrid to EmbedTerminal and EmbedGrid An embedded pseudoterminal was enclosed in the EmbedGrid struct. This commit splits it into EmbedTerminal and EmbedGrid, with EmbedGrid containing only the CellBuffer grid logic. With this change we can reuse EmbedGrid to parse ANSI output from external programs into meli's CellBuffer's. --- src/components/mail/compose.rs | 20 +-- src/terminal/embed.rs | 15 ++- src/terminal/embed/grid.rs | 222 ++++++++++++++++++++++++--------- 3 files changed, 182 insertions(+), 75 deletions(-) diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index a64687c2..a163e879 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -26,7 +26,7 @@ use melib::Draft; use crate::conf::accounts::JobRequest; use crate::jobs::JoinHandle; -use crate::terminal::embed::EmbedGrid; +use crate::terminal::embed::EmbedTerminal; use indexmap::IndexSet; use nix::sys::wait::WaitStatus; use std::convert::TryInto; @@ -53,13 +53,13 @@ enum Cursor { #[derive(Debug)] enum EmbedStatus { - Stopped(Arc>, File), - Running(Arc>, File), + Stopped(Arc>, File), + Running(Arc>, File), } impl std::ops::Deref for EmbedStatus { - type Target = Arc>; - fn deref(&self) -> &Arc> { + type Target = Arc>; + fn deref(&self) -> &Arc> { use EmbedStatus::*; match self { Stopped(ref e, _) | Running(ref e, _) => e, @@ -68,7 +68,7 @@ impl std::ops::Deref for EmbedStatus { } impl std::ops::DerefMut for EmbedStatus { - fn deref_mut(&mut self) -> &mut Arc> { + fn deref_mut(&mut self) -> &mut Arc> { use EmbedStatus::*; match self { Stopped(ref mut e, _) | Running(ref mut e, _) => e, @@ -794,9 +794,9 @@ impl Component for Composer { clear_area(grid, embed_area, theme_default); copy_area( grid, - &guard.grid, + &guard.grid.buffer(), embed_area, - ((0, 0), pos_dec(guard.terminal_size, (1, 1))), + ((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))), ); guard.set_terminal_size((width!(embed_area), height!(embed_area))); context.dirty_areas.push_back(area); @@ -807,9 +807,9 @@ impl Component for Composer { let guard = embed_pty.lock().unwrap(); copy_area( grid, - &guard.grid, + &guard.grid.buffer(), embed_area, - ((0, 0), pos_dec(guard.terminal_size, (1, 1))), + ((0, 0), pos_dec(guard.grid.terminal_size, (1, 1))), ); change_colors(grid, embed_area, Color::Byte(8), theme_default.bg); const STOPPED_MESSAGE: &str = "process has stopped, press 'e' to re-activate"; diff --git a/src/terminal/embed.rs b/src/terminal/embed.rs index c35b9938..6cdfc671 100644 --- a/src/terminal/embed.rs +++ b/src/terminal/embed.rs @@ -37,7 +37,7 @@ use std::os::unix::{ mod grid; -pub use grid::EmbedGrid; +pub use grid::{EmbedGrid, EmbedTerminal}; // ioctl request code to "Make the given terminal the controlling terminal of the calling process" use libc::TIOCSCTTY; @@ -55,7 +55,11 @@ ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize); ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY); -pub fn create_pty(width: usize, height: usize, command: String) -> Result>> { +pub fn create_pty( + width: usize, + height: usize, + command: String, +) -> Result>> { // Open a new PTY master let master_fd = posix_openpt(OFlag::O_RDWR)?; @@ -149,7 +153,7 @@ pub fn create_pty(width: usize, height: usize, command: String) -> Result Result>) { +fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc>) { let mut bytes_iter = pty_fd.bytes(); //debug!("waiting for bytes"); while let Some(Ok(byte)) = bytes_iter.next() { @@ -391,6 +395,9 @@ impl std::fmt::Display for EscCode<'_> { EscCode(CsiQ(ref buf), c) => { write!(f, "ESC[?{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char) } + EscCode(Normal, c) => { + write!(f, "{} as char: {} Normal", c, *c as char) + } EscCode(unknown, c) => { write!(f, "{:?}{} [UNKNOWN]", unknown, c) } diff --git a/src/terminal/embed/grid.rs b/src/terminal/embed/grid.rs index cb922fd3..35837697 100644 --- a/src/terminal/embed/grid.rs +++ b/src/terminal/embed/grid.rs @@ -35,18 +35,22 @@ use nix::sys::wait::{waitpid, WaitPidFlag}; * The main process copies the grid whenever the actual terminal is redrawn. **/ +#[derive(Debug)] +enum ScreenBuffer { + Normal, + Alternate, +} + #[derive(Debug)] pub struct EmbedGrid { cursor: (usize, usize), /// [top;bottom] scroll_region: ScrollRegion, - pub grid: CellBuffer, + pub alternate_screen: CellBuffer, pub state: State, - pub stdin: std::fs::File, - /// Pid of the embed process - pub child_pid: nix::unistd::Pid, /// (width, height) pub terminal_size: (usize, usize), + initialized: bool, fg_color: Color, bg_color: Color, /// Store the fg/bg color when highlighting the cell where the cursor is so that it can be @@ -62,68 +66,35 @@ pub struct EmbedGrid { wrap_next: bool, /// Store state in case a multi-byte character is encountered codepoints: CodepointBuf, + pub normal_screen: CellBuffer, + screen_buffer: ScreenBuffer, } -#[derive(Debug, PartialEq)] -enum CodepointBuf { - None, - TwoCodepoints(u8), - ThreeCodepoints(u8, Option), - FourCodepoints(u8, Option, Option), +#[derive(Debug)] +pub struct EmbedTerminal { + pub grid: EmbedGrid, + pub stdin: std::fs::File, + /// Pid of the embed process + pub child_pid: nix::unistd::Pid, } -impl EmbedGrid { +impl EmbedTerminal { pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self { - EmbedGrid { - cursor: (0, 0), - scroll_region: ScrollRegion { - top: 0, - bottom: 0, - left: 0, - ..Default::default() - }, - terminal_size: (0, 0), - grid: CellBuffer::default(), - state: State::Normal, + EmbedTerminal { + grid: EmbedGrid::new(), stdin, child_pid, - fg_color: Color::Default, - bg_color: Color::Default, - prev_fg_color: None, - prev_bg_color: None, - show_cursor: true, - auto_wrap_mode: true, - wrap_next: false, - origin_mode: false, - codepoints: CodepointBuf::None, } } pub fn set_terminal_size(&mut self, new_val: (usize, usize)) { - if new_val == self.terminal_size { - return; - } - //debug!("resizing to {:?}", new_val); - self.scroll_region.top = 0; - self.scroll_region.bottom = new_val.1.saturating_sub(1); - - self.terminal_size = new_val; - if !self.grid.resize(new_val.0, new_val.1, None) { - panic!( - "Terminal size too big: ({} cols, {} rows)", - new_val.0, new_val.1 - ); - } - self.grid.clear(Some(Cell::default())); - self.cursor = (0, 0); - self.wrap_next = false; + self.grid.set_terminal_size(new_val); let winsize = Winsize { ws_row: ::try_from(new_val.1).unwrap(), ws_col: ::try_from(new_val.0).unwrap(), ws_xpixel: 0, ws_ypixel: 0, }; - let master_fd = self.stdin.as_raw_fd(); let _ = unsafe { set_window_size(master_fd, &winsize) }; let _ = nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH); @@ -144,13 +115,102 @@ impl EmbedGrid { } pub fn process_byte(&mut self, byte: u8) { + let Self { + ref mut grid, + ref mut stdin, + child_pid: _, + } = self; + grid.process_byte(stdin, byte); + } +} + +#[derive(Debug, PartialEq)] +enum CodepointBuf { + None, + TwoCodepoints(u8), + ThreeCodepoints(u8, Option), + FourCodepoints(u8, Option, Option), +} + +impl EmbedGrid { + pub fn new() -> Self { + let mut normal_screen = CellBuffer::default(); + normal_screen.set_growable(true); + EmbedGrid { + cursor: (0, 0), + scroll_region: ScrollRegion { + top: 0, + bottom: 0, + left: 0, + ..Default::default() + }, + terminal_size: (0, 0), + initialized: false, + alternate_screen: CellBuffer::default(), + state: State::Normal, + fg_color: Color::Default, + bg_color: Color::Default, + prev_fg_color: None, + prev_bg_color: None, + show_cursor: true, + auto_wrap_mode: true, + wrap_next: false, + origin_mode: false, + codepoints: CodepointBuf::None, + normal_screen, + screen_buffer: ScreenBuffer::Normal, + } + } + + pub fn buffer(&self) -> &CellBuffer { + match self.screen_buffer { + ScreenBuffer::Normal => &self.normal_screen, + ScreenBuffer::Alternate => &self.alternate_screen, + } + } + + pub fn buffer_mut(&mut self) -> &mut CellBuffer { + match self.screen_buffer { + ScreenBuffer::Normal => &mut self.normal_screen, + ScreenBuffer::Alternate => &mut self.alternate_screen, + } + } + + pub fn set_terminal_size(&mut self, new_val: (usize, usize)) { + if new_val == self.terminal_size && self.initialized { + return; + } + self.initialized = true; + //debug!("resizing to {:?}", new_val); + self.scroll_region.top = 0; + self.scroll_region.bottom = new_val.1.saturating_sub(1); + + self.terminal_size = new_val; + if !self.alternate_screen.resize(new_val.0, new_val.1, None) { + panic!( + "Terminal size too big: ({} cols, {} rows)", + new_val.0, new_val.1 + ); + } + self.alternate_screen.clear(Some(Cell::default())); + if !self.normal_screen.resize(new_val.0, new_val.1, None) { + panic!( + "Terminal size too big: ({} cols, {} rows)", + new_val.0, new_val.1 + ); + } + self.normal_screen.clear(Some(Cell::default())); + self.cursor = (0, 0); + self.wrap_next = false; + } + + pub fn process_byte(&mut self, stdin: &mut std::fs::File, byte: u8) { let EmbedGrid { ref mut cursor, ref mut scroll_region, - ref terminal_size, - ref mut grid, + ref mut terminal_size, + ref mut alternate_screen, ref mut state, - ref mut stdin, ref mut fg_color, ref mut bg_color, ref mut prev_fg_color, @@ -160,15 +220,45 @@ impl EmbedGrid { ref mut auto_wrap_mode, ref mut wrap_next, ref mut origin_mode, - child_pid: _, + ref mut screen_buffer, + ref mut normal_screen, + initialized: _, } = self; + let mut grid = normal_screen; + + let is_alternate = match *screen_buffer { + ScreenBuffer::Normal => false, + _ => { + grid = alternate_screen; + true + } + }; + + macro_rules! increase_cursor_y { + () => { + cursor.1 += 1; + if !is_alternate { + cursor.0 = 0; + if cursor.1 >= terminal_size.1 { + if !grid.resize(std::cmp::max(1, grid.cols()), grid.rows() + 2, None) { + return; + } + scroll_region.bottom += 1; + terminal_size.1 += 1; + } + } + }; + } macro_rules! increase_cursor_x { () => { if cursor.0 + 1 < terminal_size.0 { cursor.0 += 1; - } else if *auto_wrap_mode { + } else if is_alternate && *auto_wrap_mode { *wrap_next = true; + } else if !is_alternate { + cursor.0 = 0; + increase_cursor_y!(); } }; } @@ -183,10 +273,14 @@ impl EmbedGrid { } macro_rules! cursor_y { () => { - std::cmp::min( - cursor.1 + scroll_region.top, - terminal_size.1.saturating_sub(1), - ) + if is_alternate { + std::cmp::min( + cursor.1 + scroll_region.top, + terminal_size.1.saturating_sub(1), + ) + } else { + cursor.1 + } }; } macro_rules! cursor_val { @@ -274,11 +368,11 @@ impl EmbedGrid { //debug!("setting cell {:?} char '{}'", cursor, c as char); //debug!("newline y-> y+1, cursor was: {:?}", cursor); - if cursor.1 + 1 < terminal_size.1 { - if cursor.1 == scroll_region.bottom { + if cursor.1 + 1 < terminal_size.1 || !is_alternate { + if cursor.1 == scroll_region.bottom && is_alternate { grid.scroll_up(scroll_region, cursor.1, 1); } else { - cursor.1 += 1; + increase_cursor_y!(); } } *wrap_next = false; @@ -436,6 +530,9 @@ impl EmbedGrid { grid[cursor_val!()].set_fg(Color::Black); grid[cursor_val!()].set_bg(Color::White); } + b"1047" | b"1049" => { + *screen_buffer = ScreenBuffer::Alternate; + } _ => {} } @@ -463,6 +560,9 @@ impl EmbedGrid { grid[cursor_val!()].set_bg(*bg_color); } } + b"1047" | b"1049" => { + *screen_buffer = ScreenBuffer::Normal; + } _ => {} } //debug!("{}", EscCode::from((&(*state), byte)));