meli/ui/src/terminal/embed/grid.rs

581 lines
23 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

use super::*;
use crate::terminal::cells::*;
use melib::error::{MeliError, Result};
use nix::sys::wait::WaitStatus;
use nix::sys::wait::{waitpid, WaitPidFlag};
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct EmbedGrid {
cursor: (usize, usize),
pub grid: CellBuffer,
pub state: State,
pub stdin: std::fs::File,
pub child_pid: nix::unistd::Pid,
pub terminal_size: (usize, usize),
resized: bool,
}
impl EmbedGrid {
pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self {
EmbedGrid {
cursor: (0, 0),
terminal_size: (0, 0),
grid: CellBuffer::default(),
state: State::Normal,
stdin,
child_pid,
resized: false,
}
}
pub fn set_terminal_size(&mut self, new_val: (usize, usize)) {
self.terminal_size = new_val;
self.grid.resize(new_val.0, new_val.1, Cell::default());
self.cursor = (0, 0);
nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH).unwrap();
self.resized = true;
}
pub fn wake_up(&self) {
nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGCONT).unwrap();
}
pub fn stop(&self) {
debug!("stopping");
nix::sys::signal::kill(debug!(self.child_pid), nix::sys::signal::SIGSTOP).unwrap();
}
pub fn is_active(&self) -> Result<WaitStatus> {
debug!(waitpid(self.child_pid, Some(WaitPidFlag::WNOHANG),))
.map_err(|e| MeliError::new(e.to_string()))
}
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,
ref mut resized,
child_pid: _,
} = self;
macro_rules! increase_cursor_x {
() => {
if *cursor == *terminal_size {
/* do nothing */
} else if cursor.0 + 1 >= 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;
}
(b'J', State::ExpectingControlChar) => {
// "ESCJ Erase from the cursor to the end of the screen"
debug!("sending {}", EscCode::from((&(*state), byte)));
debug!("erasing from {:?} to {:?}", cursor, terminal_size);
for y in cursor.1..terminal_size.1 {
for x in cursor.0..terminal_size.0 {
grid[(x, y)] = Cell::default();
}
}
*state = State::Normal;
}
(b'K', State::ExpectingControlChar) => {
// "ESCK Erase from the cursor to the end of the line"
debug!("sending {}", EscCode::from((&(*state), byte)));
for x in cursor.0..terminal_size.0 {
grid[(x, cursor.1)] = Cell::default();
}
*state = State::Normal;
}
(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!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(c, State::Osc2(_, _)) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
/* END OF OSC */
/* ********** */
/* ********** */
/* ********** */
/* ********** */
(b'\r', State::Normal) => {
//debug!("setting cell {:?} char '{}'", cursor, c as char);
debug!("carriage return x-> 0, cursor was: {:?}", cursor);
cursor.0 = 0;
debug!("cursor became: {:?}", cursor);
}
(b'\n', State::Normal) => {
//debug!("setting cell {:?} char '{}'", cursor, c as char);
debug!("newline y-> y+1, cursor was: {:?}", cursor);
if cursor.1 + 1 < terminal_size.1 {
cursor.1 += 1;
}
debug!("cursor became: {:?}", cursor);
}
(b'', State::Normal) => {
debug!("Visual bell ^G, ignoring {:?}", cursor);
}
/* Backspace */
(0x08, State::Normal) => {
//debug!("setting cell {:?} char '{}'", cursor, c as char);
debug!("backspace x-> x-1, cursor was: {:?}", cursor);
if cursor.0 > 0 {
cursor.0 -= 1;
}
//grid[*cursor].set_ch(' ');
debug!("cursor became: {:?}", cursor);
}
(c, State::Normal) => {
grid[*cursor].set_ch(c as char);
debug!("setting cell {:?} char '{}'", cursor, c as char);
increase_cursor_x!();
}
(b'u', State::Csi) => {
/* restore cursor */
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'm', State::Csi) => {
/* Character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */
debug!("ignoring {}", 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;
}
(b'P', State::Csi) => {
/* delete one character */
debug!("sending {}", EscCode::from((&(*state), byte)),);
grid[*cursor].set_ch(' ');
*state = State::Normal;
}
(b'C', State::Csi) => {
// "ESC[C\t\tCSI Cursor Forward one Time",
debug!("cursor forward one time, cursor was: {:?}", cursor);
cursor.0 = std::cmp::min(cursor.0 + 1, terminal_size.0.saturating_sub(1));
debug!("cursor became: {:?}", 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!("ignoring {}", 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) => {
/* Erase in Display (ED), VT100.*/
/* Erase Below (default). */
clear_area(
grid,
(
(
0,
std::cmp::min(cursor.1 + 1, terminal_size.1.saturating_sub(1)),
),
(
terminal_size.0.saturating_sub(1),
terminal_size.1.saturating_sub(1),
),
),
);
debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'K', State::Csi) => {
/* Erase in Line (ED), VT100.*/
/* Erase to right (Default) */
debug!("{}", EscCode::from((&(*state), byte)));
for x in cursor.0..terminal_size.0 {
grid[(x, cursor.1)] = Cell::default();
}
*state = State::Normal;
}
(c, State::Csi) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'K', State::Csi1(buf)) if buf == b"0" => {
/* Erase in Line (ED), VT100.*/
/* Erase to right (Default) */
debug!("{}", EscCode::from((&(*state), byte)));
for x in cursor.0..terminal_size.0 {
grid[(x, cursor.1)] = Cell::default();
}
*state = State::Normal;
}
(b'K', State::Csi1(buf)) if buf == b"1" => {
/* Erase in Line (ED), VT100.*/
/* Erase to left (Default) */
for x in cursor.0..=0 {
grid[(x, cursor.1)] = Cell::default();
}
debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'K', State::Csi1(buf)) if buf == b"2" => {
/* Erase in Line (ED), VT100.*/
/* Erase all */
for y in 0..terminal_size.1 {
for x in 0..terminal_size.0 {
grid[(x, y)] = Cell::default();
}
}
debug!("{}", EscCode::from((&(*state), byte)));
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
*state = State::Normal;
}
(b'J', State::Csi1(ref buf)) if buf == b"0" => {
/* Erase in Display (ED), VT100.*/
/* Erase Below (default). */
clear_area(
grid,
(
(
0,
std::cmp::min(cursor.1 + 1, terminal_size.1.saturating_sub(1)),
),
(
terminal_size.0.saturating_sub(1),
terminal_size.1.saturating_sub(1),
),
),
);
debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'J', State::Csi1(ref buf)) if buf == b"1" => {
/* Erase in Display (ED), VT100.*/
/* Erase Above */
clear_area(
grid,
(
(0, 0),
(
terminal_size.0.saturating_sub(1),
cursor.1.saturating_sub(1),
),
),
);
debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'J', State::Csi1(ref buf)) if buf == b"2" => {
/* Erase in Display (ED), VT100.*/
/* Erase All */
clear_area(grid, ((0, 0), pos_dec(*terminal_size, (1, 1))));
debug!("{}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b'J', State::Csi1(ref buf)) if buf == b"3" => {
/* Erase in Display (ED), VT100.*/
/* Erase saved lines (What?) */
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b't', State::Csi1(buf)) => {
/* Window manipulation */
if buf == b"18" {
debug!("report size of the text area");
debug!("got {}", EscCode::from((&(*state), byte)));
// P s = 1 8 → Report the size of the text area in characters as CSI 8 ; height ; width t
stdin.write_all(b"\x1b[8;").unwrap();
stdin
.write_all((terminal_size.1).to_string().as_bytes())
.unwrap();
stdin.write_all(&[b';']).unwrap();
stdin
.write_all((terminal_size.0).to_string().as_bytes())
.unwrap();
stdin.write_all(&[b't']).unwrap();
} else {
debug!("not sending {}", EscCode::from((&(*state), byte)));
}
*state = State::Normal;
}
(b'n', State::Csi1(_)) => {
/* report cursor position */
debug!("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.1 + 1).to_string().as_bytes())
.unwrap();
stdin.write_all(&[b';']).unwrap();
stdin
.write_all((cursor.0 + 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::<usize>()
.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::<usize>()
.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::<usize>()
.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::<usize>()
.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::<usize>()
.unwrap();
debug!("cursor absolute {}, cursor was: {:?}", new_col, cursor);
if new_col < terminal_size.0 + 1 {
cursor.0 = new_col.saturating_sub(1);
} else {
debug!(
"error: new_cal = {} > terminal.size.0 = {}\nterminal_size = {:?}",
new_col, terminal_size.0, terminal_size
);
}
debug!("cursor became: {:?}", cursor);
*state = State::Normal;
}
(b'C', State::Csi1(buf)) => {
// "ESC[{buf}C\t\tCSI Cursor Preceding Line {buf} Times",
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
.parse::<usize>()
.unwrap();
debug!(
"cursor preceding {} times, cursor was: {:?}",
offset, cursor
);
if cursor.1 >= offset {
cursor.1 -= offset;
//cursor.0 = 0;
}
debug!("cursor became: {:?}", cursor);
*state = State::Normal;
}
(b'P', State::Csi1(buf)) => {
// "ESC[{buf}P\t\tCSI Delete {buf} characters, default = 1",
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
.parse::<usize>()
.unwrap();
debug!(
"Delete {} Character(s) with cursor at {:?} ",
offset, cursor
);
for x in (cursor.0 - std::cmp::min(offset, cursor.0))..cursor.0 {
grid[(x, cursor.1)].set_ch(' ');
}
debug!("cursor became: {:?}", cursor);
*state = State::Normal;
}
/* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
(b'd', State::Csi1(buf)) => {
let row = unsafe { std::str::from_utf8_unchecked(buf) }
.parse::<usize>()
.unwrap();
debug!(
"Line position absolute row {} with cursor at {:?}",
row, cursor
);
cursor.1 = std::cmp::min(row.saturating_sub(1), terminal_size.1.saturating_sub(1));
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!("ignoring {}", 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(_, _)) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
// Report Cursor Position, skip it
*state = State::Normal;
}
(b't', State::Csi2(_, _)) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
// Window manipulation, skip it
*state = State::Normal;
}
(b'H', State::Csi2(ref y, ref x)) => {
//Cursor Position [row;column] (default = [1,1]) (CUP).
let orig_x = unsafe { std::str::from_utf8_unchecked(x) }
.parse::<usize>()
.unwrap_or(1);
let orig_y = unsafe { std::str::from_utf8_unchecked(y) }
.parse::<usize>()
.unwrap_or(1);
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;
} else {
debug!(
"[error] terminal_size = {:?}, cursor = {:?} but given [{},{}]",
terminal_size, cursor, orig_x, orig_y
);
}
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!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b't', State::Csi3(_, _, _)) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
// 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(_, _, _)) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
/* other stuff */
/* ******************* */
/* ******************* */
/* ******************* */
(c, State::G0) => {
debug!("ignoring {}", EscCode::from((&(*state), byte)));
*state = State::Normal;
}
(b, s) => {
debug!("unrecognised: byte is {} and state is {:?}", b as char, s);
}
}
}
}