Add embed pty support
Emulate a terminal within meli. In the next commit it will be used to embed an editor in the composing tab. This is a non-complete xterm emulation that has some bugs.jmap
parent
0566937a76
commit
99da9a35b6
|
@ -645,6 +645,18 @@ dependencies = [
|
||||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]]
|
[[package]]
|
||||||
name = "nodrop"
|
name = "nodrop"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
@ -1181,6 +1193,7 @@ dependencies = [
|
||||||
"linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"melib 0.3.2",
|
"melib 0.3.2",
|
||||||
"mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)",
|
"mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)",
|
||||||
|
"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)",
|
"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 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)",
|
"notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -1358,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 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 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.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 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 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"
|
"checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943"
|
||||||
|
|
|
@ -197,6 +197,8 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
let signals = &[
|
let signals = &[
|
||||||
/* Catch SIGWINCH to handle terminal resizing */
|
/* Catch SIGWINCH to handle terminal resizing */
|
||||||
signal_hook::SIGWINCH,
|
signal_hook::SIGWINCH,
|
||||||
|
/* Catch SIGCHLD to handle embed applications status change */
|
||||||
|
signal_hook::SIGCHLD,
|
||||||
];
|
];
|
||||||
|
|
||||||
let signal_recvr = notify(signals, sender)?;
|
let signal_recvr = notify(signals, sender)?;
|
||||||
|
@ -303,11 +305,17 @@ fn main() -> std::result::Result<(), std::io::Error> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
UIMode::Embed => state.redraw(),
|
||||||
|
|
||||||
UIMode::Fork => {
|
UIMode::Fork => {
|
||||||
break 'inner; // `goto` 'reap loop, and wait on child.
|
break 'inner; // `goto` 'reap loop, and wait on child.
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ThreadEvent::InputRaw(raw_input) => {
|
||||||
|
state.rcv_event(UIEvent::EmbedInput(raw_input));
|
||||||
|
state.redraw();
|
||||||
|
},
|
||||||
ThreadEvent::RefreshMailbox(event) => {
|
ThreadEvent::RefreshMailbox(event) => {
|
||||||
state.refresh_event(*event);
|
state.refresh_event(*event);
|
||||||
state.redraw();
|
state.redraw();
|
||||||
|
|
|
@ -25,6 +25,7 @@ uuid = { version = "0.7.4", features = ["serde", "v4"] }
|
||||||
unicode-segmentation = "1.2.1" # >:c
|
unicode-segmentation = "1.2.1" # >:c
|
||||||
text_processing = { path = "../text_processing", version = "*" }
|
text_processing = { path = "../text_processing", version = "*" }
|
||||||
libc = {version = "0.2.59", features = ["extra_traits",]}
|
libc = {version = "0.2.59", features = ["extra_traits",]}
|
||||||
|
nix = "0.15.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -44,33 +44,39 @@ use termion::{clear, cursor};
|
||||||
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
||||||
|
|
||||||
struct InputHandler {
|
struct InputHandler {
|
||||||
rx: Receiver<bool>,
|
rx: Receiver<InputCommand>,
|
||||||
tx: Sender<bool>,
|
tx: Sender<InputCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputHandler {
|
impl InputHandler {
|
||||||
fn restore(&self, tx: Sender<ThreadEvent>) {
|
fn restore(&self, tx: Sender<ThreadEvent>) {
|
||||||
let stdin = std::io::stdin();
|
|
||||||
let rx = self.rx.clone();
|
let rx = self.rx.clone();
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("input-thread".to_string())
|
.name("input-thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
get_events(
|
get_events(
|
||||||
stdin,
|
|
||||||
|k| {
|
|k| {
|
||||||
tx.send(ThreadEvent::Input(k)).unwrap();
|
tx.send(ThreadEvent::Input(k)).unwrap();
|
||||||
},
|
},
|
||||||
|| {
|
|i| {
|
||||||
tx.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(UIMode::Fork)))
|
tx.send(ThreadEvent::InputRaw(i)).unwrap();
|
||||||
.unwrap();
|
|
||||||
},
|
},
|
||||||
&rx,
|
&rx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kill(&self) {
|
fn kill(&self) {
|
||||||
self.tx.send(false).unwrap();
|
self.tx.send(InputCommand::Kill).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_to_raw(&self) {
|
||||||
|
self.tx.send(InputCommand::Raw).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch_from_raw(&self) {
|
||||||
|
self.tx.send(InputCommand::NoRaw).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,9 +104,19 @@ impl Context {
|
||||||
pub fn replies(&mut self) -> Vec<UIEvent> {
|
pub fn replies(&mut self) -> Vec<UIEvent> {
|
||||||
self.replies.drain(0..).collect()
|
self.replies.drain(0..).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn input_kill(&self) {
|
pub fn input_kill(&self) {
|
||||||
self.input.kill();
|
self.input.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn input_from_raw(&self) {
|
||||||
|
self.input.switch_from_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_to_raw(&self) {
|
||||||
|
self.input.switch_to_raw();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn restore_input(&self) {
|
pub fn restore_input(&self) {
|
||||||
self.input.restore(self.sender.clone());
|
self.input.restore(self.sender.clone());
|
||||||
}
|
}
|
||||||
|
@ -569,25 +585,35 @@ impl State {
|
||||||
self.parse_command(&cmd);
|
self.parse_command(&cmd);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UIEvent::Fork(child) => {
|
UIEvent::Fork(ForkType::Finished) => {
|
||||||
self.mode = UIMode::Fork;
|
|
||||||
self.child = Some(child);
|
|
||||||
if let Some(ForkType::Finished) = self.child {
|
|
||||||
/*
|
/*
|
||||||
* Fork has finished in the past.
|
* Fork has finished in the past.
|
||||||
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
|
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
|
||||||
* it.
|
* it.
|
||||||
*/
|
|
||||||
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
|
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
|
||||||
self.flush();
|
self.flush();
|
||||||
|
*/
|
||||||
|
self.switch_to_main_screen();
|
||||||
|
self.switch_to_alternate_screen();
|
||||||
|
self.context.restore_input();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
UIEvent::Fork(child) => {
|
||||||
|
self.mode = UIMode::Fork;
|
||||||
|
self.child = Some(child);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UIEvent::ChangeMode(m) => {
|
UIEvent::ChangeMode(m) => {
|
||||||
|
if self.mode == UIMode::Embed {
|
||||||
|
self.context.input_from_raw();
|
||||||
|
}
|
||||||
self.context
|
self.context
|
||||||
.sender
|
.sender
|
||||||
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
.send(ThreadEvent::UIEvent(UIEvent::ChangeMode(m)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
if m == UIMode::Embed {
|
||||||
|
self.context.input_to_raw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ mod position;
|
||||||
mod cells;
|
mod cells;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod keys;
|
mod keys;
|
||||||
|
pub mod embed;
|
||||||
mod text_editing;
|
mod text_editing;
|
||||||
pub use self::cells::*;
|
pub use self::cells::*;
|
||||||
pub use self::keys::*;
|
pub use self::keys::*;
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
use crate::split_command;
|
||||||
|
use crate::terminal::position::Area;
|
||||||
|
use crate::terminal::position::*;
|
||||||
|
use melib::log;
|
||||||
|
use melib::ERROR;
|
||||||
|
|
||||||
|
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, wait::waitpid};
|
||||||
|
use nix::unistd::{dup2, fork, ForkResult};
|
||||||
|
use std::ffi::CString;
|
||||||
|
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
|
||||||
|
|
||||||
|
mod grid;
|
||||||
|
|
||||||
|
pub use grid::EmbedGrid;
|
||||||
|
|
||||||
|
// ioctl command to set window size of pty:
|
||||||
|
use libc::TIOCSWINSZ;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
// Macro generated function that calls ioctl to set window size of slave pty end
|
||||||
|
ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize);
|
||||||
|
|
||||||
|
pub fn create_pty(area: Area, command: String) -> nix::Result<Arc<Mutex<EmbedGrid>>> {
|
||||||
|
// Open a new PTY master
|
||||||
|
let master_fd = posix_openpt(OFlag::O_RDWR)?;
|
||||||
|
|
||||||
|
// Allow a slave to be generated for it
|
||||||
|
grantpt(&master_fd)?;
|
||||||
|
unlockpt(&master_fd)?;
|
||||||
|
|
||||||
|
// Get the name of the slave
|
||||||
|
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 winsize = Winsize {
|
||||||
|
ws_row: <u16>::try_from(height!(area)).unwrap(),
|
||||||
|
ws_col: <u16>::try_from(width!(area)).unwrap(),
|
||||||
|
ws_xpixel: 0,
|
||||||
|
ws_ypixel: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let master_fd = master_fd.clone().into_raw_fd();
|
||||||
|
unsafe { set_window_size(master_fd, &winsize).unwrap() };
|
||||||
|
}
|
||||||
|
|
||||||
|
let child_pid = match fork() {
|
||||||
|
Ok(ForkResult::Child) => {
|
||||||
|
/* Open slave end for pseudoterminal */
|
||||||
|
let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, stat::Mode::empty())?;
|
||||||
|
|
||||||
|
let child_pid = match fork() {
|
||||||
|
Ok(ForkResult::Child) => {
|
||||||
|
// assign stdin, stdout, stderr to the tty
|
||||||
|
dup2(slave_fd, STDIN_FILENO).unwrap();
|
||||||
|
dup2(slave_fd, STDOUT_FILENO).unwrap();
|
||||||
|
dup2(slave_fd, STDERR_FILENO).unwrap();
|
||||||
|
let parts = split_command!(command);
|
||||||
|
let (cmd, _) = (parts[0], &parts[1..]);
|
||||||
|
if let Err(e) = nix::unistd::execv(
|
||||||
|
&CString::new(cmd).unwrap(),
|
||||||
|
&parts
|
||||||
|
.iter()
|
||||||
|
.map(|&a| CString::new(a).unwrap())
|
||||||
|
.collect::<Vec<CString>>(),
|
||||||
|
) {
|
||||||
|
log(format!("Could not execute `{}`: {}", command, e,), ERROR);
|
||||||
|
std::process::exit(-1);
|
||||||
|
}
|
||||||
|
/* This path shouldn't be executed. */
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Ok(ForkResult::Parent { child }) => child,
|
||||||
|
Err(e) => panic!(e),
|
||||||
|
};
|
||||||
|
waitpid(child_pid, None).unwrap();
|
||||||
|
std::process::exit(0);
|
||||||
|
}
|
||||||
|
Ok(ForkResult::Parent { child }) => child,
|
||||||
|
Err(e) => panic!(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdin = unsafe { std::fs::File::from_raw_fd(master_fd.clone().into_raw_fd()) };
|
||||||
|
let mut embed_grid = EmbedGrid::new(stdin, child_pid);
|
||||||
|
embed_grid.set_terminal_size((width!(area), height!(area)));
|
||||||
|
let grid = Arc::new(Mutex::new(embed_grid));
|
||||||
|
let grid_ = grid.clone();
|
||||||
|
|
||||||
|
std::thread::Builder::new()
|
||||||
|
.spawn(move || {
|
||||||
|
let master_fd = master_fd.into_raw_fd();
|
||||||
|
let master_file = unsafe { std::fs::File::from_raw_fd(master_fd) };
|
||||||
|
forward_pty_translate_escape_codes(master_file, grid_);
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
Ok(grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_pty_translate_escape_codes(pty_fd: std::fs::File, grid: Arc<Mutex<EmbedGrid>>) {
|
||||||
|
let mut bytes_iter = pty_fd.bytes();
|
||||||
|
debug!("waiting for bytes");
|
||||||
|
while let Some(Ok(byte)) = bytes_iter.next() {
|
||||||
|
debug!("got byte {}", byte as char);
|
||||||
|
grid.lock().unwrap().process_byte(byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum State {
|
||||||
|
ExpectingControlChar,
|
||||||
|
G0, // Designate G0 Character Set
|
||||||
|
Osc1(Vec<u8>), //ESC ] Operating System Command (OSC is 0x9d).
|
||||||
|
Osc2(Vec<u8>, Vec<u8>),
|
||||||
|
Csi, // ESC [ Control Sequence Introducer (CSI is 0x9b).
|
||||||
|
Csi1(Vec<u8>),
|
||||||
|
Csi2(Vec<u8>, Vec<u8>),
|
||||||
|
Csi3(Vec<u8>, Vec<u8>, Vec<u8>),
|
||||||
|
CsiQ(Vec<u8>),
|
||||||
|
Normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used for debugging */
|
||||||
|
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;
|
||||||
|
EscCode(s, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EscCode<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
use State::*;
|
||||||
|
macro_rules! unsafestr {
|
||||||
|
($buf:ident) => {
|
||||||
|
unsafe { std::str::from_utf8_unchecked($buf) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
EscCode(G0, b'B') => write!(f, "ESC(B\t\tG0 USASCII charset set"),
|
||||||
|
EscCode(G0, c) => write!(f, "ESC({}\t\tG0 charset set", *c as char),
|
||||||
|
EscCode(Osc1(ref buf), ref c) => {
|
||||||
|
write!(f, "ESC]{}{}\t\tOSC", unsafestr!(buf), *c as char)
|
||||||
|
}
|
||||||
|
EscCode(Osc2(ref buf1, ref buf2), c) => write!(
|
||||||
|
f,
|
||||||
|
"ESC]{};{}{}\t\tOSC [UNKNOWN]",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
*c as char
|
||||||
|
),
|
||||||
|
EscCode(Csi, b'm') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[m\t\tCSI Character Attributes | Set Attr and Color to Normal (default)"
|
||||||
|
),
|
||||||
|
EscCode(Csi, b'K') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[K\t\tCSI Erase from the cursor to the end of the line"
|
||||||
|
),
|
||||||
|
EscCode(Csi, b'J') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[J\t\tCSI Erase from the cursor to the end of the screen"
|
||||||
|
),
|
||||||
|
EscCode(Csi, b'H') => write!(f, "ESC[H\t\tCSI Move the cursor to home position."),
|
||||||
|
EscCode(Csi, c) => write!(f, "ESC[{}\t\tCSI [UNKNOWN]", *c as char),
|
||||||
|
EscCode(Csi1(ref buf), b'm') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{}m\t\tCSI Character Attributes | Set fg, bg color",
|
||||||
|
unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'n') => write!(
|
||||||
|
f,
|
||||||
|
"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",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'C') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}C\t\tCSI Cursor Forward {buf} Times",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'D') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}D\t\tCSI Cursor Backward {buf} Times",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'E') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}E\t\tCSI Cursor Next Line {buf} Times",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'F') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}F\t\tCSI Cursor Preceding Line {buf} Times",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'G') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}G\t\tCursor Character Absolute [column={buf}] (default = [row,1])",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
|
||||||
|
EscCode(Csi1(ref buf), b'P') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}P\t\tDelete P s Character(s) (default = 1) (DCH). ",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'S') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{buf}S\t\tCSI P s S Scroll up P s lines (default = 1) (SU), VT420, EC",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), b'J') => write!(
|
||||||
|
f,
|
||||||
|
"Erase in display {buf}",
|
||||||
|
buf = unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(Csi1(ref buf), c) => {
|
||||||
|
write!(f, "ESC[{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
|
||||||
|
}
|
||||||
|
EscCode(Csi2(ref buf1, ref buf2), b'r') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{};{}r\t\tCSI Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100.",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
),
|
||||||
|
EscCode(Csi2(ref buf1, ref buf2), c) => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{};{}{}\t\tCSI",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
*c as char
|
||||||
|
),
|
||||||
|
EscCode(Csi3(ref buf1, ref buf2, ref buf3), b'm') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{};{};{}m\t\tCSI Character Attributes | Set fg, bg color",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
unsafestr!(buf3),
|
||||||
|
),
|
||||||
|
EscCode(Csi3(ref buf1, ref buf2, ref buf3), c) => write!(
|
||||||
|
f,
|
||||||
|
"ESC[{};{};{}{}\t\tCSI [UNKNOWN]",
|
||||||
|
unsafestr!(buf1),
|
||||||
|
unsafestr!(buf2),
|
||||||
|
unsafestr!(buf3),
|
||||||
|
*c as char
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b's') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?{}r\t\tCSI Save DEC Private Mode Values",
|
||||||
|
unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'r') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?{}r\t\tCSI Restore DEC Private Mode Values",
|
||||||
|
unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'h') if buf == b"25" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?25h\t\tCSI DEC Private Mode Set (DECSET) show cursor",
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'h') if buf == b"12" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?12h\t\tCSI DEC Private Mode Set (DECSET) Start Blinking Cursor.",
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'h') => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?{}h\t\tCSI DEC Private Mode Set (DECSET). [UNKNOWN]",
|
||||||
|
unsafestr!(buf)
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'l') if buf == b"12" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?12l\t\tCSI DEC Private Mode Set (DECSET) Stop Blinking Cursor",
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), b'l') if buf == b"25" => write!(
|
||||||
|
f,
|
||||||
|
"ESC[?25l\t\tCSI DEC Private Mode Set (DECSET) hide cursor",
|
||||||
|
),
|
||||||
|
EscCode(CsiQ(ref buf), c) => {
|
||||||
|
write!(f, "ESC[?{}{}\t\tCSI [UNKNOWN]", unsafestr!(buf), *c as char)
|
||||||
|
}
|
||||||
|
EscCode(unknown, c) => {
|
||||||
|
write!(f, "{:?}{} [UNKNOWN]", unknown, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,889 @@
|
||||||
|
use super::*;
|
||||||
|
use crate::terminal::cells::*;
|
||||||
|
use melib::error::{MeliError, Result};
|
||||||
|
use nix::sys::wait::WaitStatus;
|
||||||
|
use nix::sys::wait::{waitpid, WaitPidFlag};
|
||||||
|
/**
|
||||||
|
* `EmbedGrid` manages the terminal grid state of the embed process.
|
||||||
|
*
|
||||||
|
* The embed process sends bytes to the master end (see super mod) and interprets them in a state
|
||||||
|
* machine stored in `State`. Escape codes are translated as changes to the grid, eg changes in a
|
||||||
|
* cell's colors.
|
||||||
|
*
|
||||||
|
* The main process copies the grid whenever the actual terminal is redrawn.
|
||||||
|
**/
|
||||||
|
|
||||||
|
/// In a scroll region up and down cursor movements shift the region vertically. The new lines are
|
||||||
|
/// empty.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ScrollRegion {
|
||||||
|
top: usize,
|
||||||
|
bottom: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EmbedGrid {
|
||||||
|
cursor: (usize, usize),
|
||||||
|
/// [top;bottom]
|
||||||
|
scroll_region: ScrollRegion,
|
||||||
|
pub grid: 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),
|
||||||
|
fg_color: Color,
|
||||||
|
bg_color: Color,
|
||||||
|
/// Store the fg/bg color when highlighting the cell where the cursor is so that it can be
|
||||||
|
/// restored afterwards
|
||||||
|
prev_fg_color: Option<Color>,
|
||||||
|
prev_bg_color: Option<Color>,
|
||||||
|
|
||||||
|
show_cursor: bool,
|
||||||
|
/// Store state in case a multi-byte character is encountered
|
||||||
|
codepoints: CodepointBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum CodepointBuf {
|
||||||
|
None,
|
||||||
|
TwoCodepoints(Vec<u8>),
|
||||||
|
ThreeCodepoints(Vec<u8>),
|
||||||
|
FourCodepoints(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EmbedGrid {
|
||||||
|
pub fn new(stdin: std::fs::File, child_pid: nix::unistd::Pid) -> Self {
|
||||||
|
EmbedGrid {
|
||||||
|
cursor: (0, 0),
|
||||||
|
scroll_region: ScrollRegion { top: 0, bottom: 0 },
|
||||||
|
terminal_size: (0, 0),
|
||||||
|
grid: CellBuffer::default(),
|
||||||
|
state: State::Normal,
|
||||||
|
stdin,
|
||||||
|
child_pid,
|
||||||
|
fg_color: Color::Default,
|
||||||
|
bg_color: Color::Default,
|
||||||
|
prev_fg_color: None,
|
||||||
|
prev_bg_color: None,
|
||||||
|
show_cursor: true,
|
||||||
|
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);
|
||||||
|
if self.scroll_region.top == 0 && self.scroll_region.bottom == self.terminal_size.1 {
|
||||||
|
self.scroll_region.bottom = new_val.1;
|
||||||
|
}
|
||||||
|
self.terminal_size = new_val;
|
||||||
|
self.grid.resize(new_val.0, new_val.1, Cell::default());
|
||||||
|
self.grid.clear(Cell::default());
|
||||||
|
self.cursor = (0, 0);
|
||||||
|
let winsize = Winsize {
|
||||||
|
ws_row: <u16>::try_from(new_val.1).unwrap(),
|
||||||
|
ws_col: <u16>::try_from(new_val.0).unwrap(),
|
||||||
|
ws_xpixel: 0,
|
||||||
|
ws_ypixel: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let master_fd = self.stdin.as_raw_fd();
|
||||||
|
unsafe { set_window_size(master_fd, &winsize).unwrap() };
|
||||||
|
nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mut scroll_region,
|
||||||
|
ref terminal_size,
|
||||||
|
ref mut grid,
|
||||||
|
ref mut state,
|
||||||
|
ref mut stdin,
|
||||||
|
ref mut fg_color,
|
||||||
|
ref mut bg_color,
|
||||||
|
ref mut prev_fg_color,
|
||||||
|
ref mut prev_bg_color,
|
||||||
|
ref mut codepoints,
|
||||||
|
ref mut show_cursor,
|
||||||
|
child_pid: _,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
macro_rules! increase_cursor_x {
|
||||||
|
() => {
|
||||||
|
if cursor.0 + 1 < terminal_size.0 {
|
||||||
|
cursor.0 += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! cursor_x {
|
||||||
|
() => {{
|
||||||
|
if cursor.0 >= terminal_size.0 {
|
||||||
|
cursor.0 = terminal_size.0.saturating_sub(1);
|
||||||
|
}
|
||||||
|
cursor.0
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! cursor_y {
|
||||||
|
() => {
|
||||||
|
std::cmp::min(
|
||||||
|
cursor.1 + scroll_region.top,
|
||||||
|
terminal_size.1.saturating_sub(1),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! cursor_val {
|
||||||
|
() => {
|
||||||
|
(cursor_x!(), cursor_y!())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
(_, 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);
|
||||||
|
}
|
||||||
|
(_, State::Osc1(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Osc2(_, _)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
/* Normal */
|
||||||
|
(b'\r', State::Normal) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
(0x08, State::Normal) => {
|
||||||
|
/* Backspace */
|
||||||
|
debug!("backspace x-> x-1, cursor was: {:?}", cursor);
|
||||||
|
if cursor.0 > 0 {
|
||||||
|
cursor.0 -= 1;
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
}
|
||||||
|
(c, State::Normal) => {
|
||||||
|
/* Character to be printed. */
|
||||||
|
if *codepoints == CodepointBuf::None && c & 0x80 == 0 {
|
||||||
|
/* This is a one byte char */
|
||||||
|
grid[cursor_val!()].set_ch(c as char);
|
||||||
|
} else {
|
||||||
|
match codepoints {
|
||||||
|
CodepointBuf::None if c & 0b1110_0000 == 0b1100_0000 => {
|
||||||
|
*codepoints = CodepointBuf::TwoCodepoints(vec![c]);
|
||||||
|
}
|
||||||
|
CodepointBuf::None if c & 0b1111_0000 == 0b1110_0000 => {
|
||||||
|
*codepoints = CodepointBuf::ThreeCodepoints(vec![c]);
|
||||||
|
}
|
||||||
|
CodepointBuf::None if c & 0b1111_1000 == 0b1111_0000 => {
|
||||||
|
*codepoints = CodepointBuf::FourCodepoints(vec![c]);
|
||||||
|
}
|
||||||
|
CodepointBuf::TwoCodepoints(buf) => {
|
||||||
|
grid[cursor_val!()].set_ch(
|
||||||
|
unsafe { std::str::from_utf8_unchecked(&[buf[0], c]) }
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
*codepoints = CodepointBuf::None;
|
||||||
|
}
|
||||||
|
CodepointBuf::ThreeCodepoints(buf) if buf.len() == 2 => {
|
||||||
|
grid[cursor_val!()].set_ch(
|
||||||
|
unsafe { std::str::from_utf8_unchecked(&[buf[0], buf[1], c]) }
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
*codepoints = CodepointBuf::None;
|
||||||
|
}
|
||||||
|
CodepointBuf::ThreeCodepoints(buf) => {
|
||||||
|
buf.push(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CodepointBuf::FourCodepoints(buf) if buf.len() == 3 => {
|
||||||
|
grid[cursor_val!()].set_ch(
|
||||||
|
unsafe {
|
||||||
|
std::str::from_utf8_unchecked(&[buf[0], buf[1], buf[2], c])
|
||||||
|
}
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
*codepoints = CodepointBuf::None;
|
||||||
|
}
|
||||||
|
CodepointBuf::FourCodepoints(buf) => {
|
||||||
|
buf.push(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"invalid utf8 sequence: codepoints = {:?} and c={}",
|
||||||
|
codepoints, c
|
||||||
|
);
|
||||||
|
*codepoints = CodepointBuf::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
increase_cursor_x!();
|
||||||
|
}
|
||||||
|
(b'u', State::Csi) => {
|
||||||
|
/* restore cursor */
|
||||||
|
debug!("restore cursor {}", EscCode::from((&(*state), byte)));
|
||||||
|
*show_cursor = true;
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'm', State::Csi) => {
|
||||||
|
/* Reset character Attributes (SGR). Ps = 0 -> Normal (default), VT100 */
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*fg_color = Color::Default;
|
||||||
|
*bg_color = Color::Default;
|
||||||
|
grid[cursor_val!()].set_fg(Color::Default);
|
||||||
|
grid[cursor_val!()].set_bg(Color::Default);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'H', State::Csi) => {
|
||||||
|
/* move cursor to (1,1) */
|
||||||
|
debug!("{}", 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!("{}", EscCode::from((&(*state), byte)),);
|
||||||
|
grid[cursor_val!()].set_ch(' ');
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'C', State::Csi) => {
|
||||||
|
// ESC[C CSI 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);
|
||||||
|
}
|
||||||
|
(b'h', State::CsiQ(ref buf)) => {
|
||||||
|
match buf.as_slice() {
|
||||||
|
b"25" => {
|
||||||
|
*show_cursor = true;
|
||||||
|
*prev_fg_color = Some(grid[cursor_val!()].fg());
|
||||||
|
*prev_bg_color = Some(grid[cursor_val!()].bg());
|
||||||
|
grid[cursor_val!()].set_fg(Color::Black);
|
||||||
|
grid[cursor_val!()].set_bg(Color::White);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'l', State::CsiQ(ref mut buf)) => {
|
||||||
|
match buf.as_slice() {
|
||||||
|
b"25" => {
|
||||||
|
*show_cursor = false;
|
||||||
|
if let Some(fg_color) = prev_fg_color.take() {
|
||||||
|
grid[cursor_val!()].set_fg(fg_color);
|
||||||
|
} else {
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
}
|
||||||
|
if let Some(bg_color) = prev_bg_color.take() {
|
||||||
|
grid[cursor_val!()].set_bg(bg_color);
|
||||||
|
} else {
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::CsiQ(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", 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 + scroll_region.top,
|
||||||
|
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 + scroll_region.top)] = Cell::default();
|
||||||
|
}
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'M', State::Csi) => {
|
||||||
|
/* Delete line */
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
for x in 0..terminal_size.0 {
|
||||||
|
grid[(x, cursor.1 + scroll_region.top)] = Cell::default();
|
||||||
|
}
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'A', State::Csi) => {
|
||||||
|
// Move cursor up 1 line
|
||||||
|
debug!("cursor up 1 times, cursor was: {:?}", cursor);
|
||||||
|
if cursor.1 > 0 {
|
||||||
|
cursor.1 -= 1;
|
||||||
|
} else {
|
||||||
|
debug!("cursor.1 == 0");
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'r', State::Csi) => {
|
||||||
|
// Set scrolling region to default (size of window)
|
||||||
|
scroll_region.top = 0;
|
||||||
|
scroll_region.bottom = terminal_size.1.saturating_sub(1);
|
||||||
|
*cursor = (0, 0);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Csi) => {
|
||||||
|
debug!("ignoring unknown code {}", 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 + scroll_region.top)] = 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 + scroll_region.top)] = 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 + scroll_region.top,
|
||||||
|
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) + scroll_region.top,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
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 unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'X', State::Csi1(ref buf)) => {
|
||||||
|
/* Erase Ps Character(s) (default = 1) (ECH)..*/
|
||||||
|
let ps = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut ctr = 0;
|
||||||
|
let (mut cur_x, mut cur_y) = cursor_val!();
|
||||||
|
while ctr < ps {
|
||||||
|
if cur_x >= terminal_size.0 {
|
||||||
|
cur_y += 1;
|
||||||
|
cur_x = 0;
|
||||||
|
if cur_y >= terminal_size.1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid[(cur_x, cur_y)] = Cell::default();
|
||||||
|
cur_x += 1;
|
||||||
|
ctr += 1;
|
||||||
|
}
|
||||||
|
debug!("Erased {} Character(s)", ps);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b't', State::Csi1(buf)) => {
|
||||||
|
/* Window manipulation */
|
||||||
|
if buf == b"18" {
|
||||||
|
// Ps = 18 → Report the size of the text area in characters as CSI 8 ; height ; width t
|
||||||
|
debug!("report size of the text area");
|
||||||
|
debug!("got {}", EscCode::from((&(*state), byte)));
|
||||||
|
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!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
}
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'n', State::Csi1(_)) => {
|
||||||
|
// Ps = 6 ⇒ Report Cursor Position (CPR) [row;column].
|
||||||
|
// Result is CSI r ; c R
|
||||||
|
debug!("report cursor position");
|
||||||
|
debug!("got {}", EscCode::from((&(*state), byte)));
|
||||||
|
stdin.write_all(&[b'\x1b', b'[']).unwrap();
|
||||||
|
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'A', State::Csi1(buf)) => {
|
||||||
|
// Move cursor up n lines
|
||||||
|
let offset = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap();
|
||||||
|
debug!("cursor up {} times, cursor was: {:?}", offset, cursor);
|
||||||
|
if cursor.1 == scroll_region.top {
|
||||||
|
for y in scroll_region.top..scroll_region.bottom {
|
||||||
|
for x in 0..terminal_size.1 {
|
||||||
|
grid[(x, y)] = grid[(x, y + 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for x in 0..terminal_size.1 {
|
||||||
|
grid[(x, scroll_region.bottom)] = Cell::default();
|
||||||
|
}
|
||||||
|
} else if cursor.1 >= offset {
|
||||||
|
cursor.1 -= offset;
|
||||||
|
} else {
|
||||||
|
debug!("offset > cursor.1");
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'B', State::Csi1(buf)) => {
|
||||||
|
// ESC[{buf}B CSI 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;
|
||||||
|
}
|
||||||
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'D', State::Csi1(buf)) => {
|
||||||
|
// ESC[{buf}D CSI 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 CSI 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;
|
||||||
|
}
|
||||||
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'G', State::Csi1(buf)) => {
|
||||||
|
// ESC[{buf}G Cursor 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 {
|
||||||
|
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 CSI 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;
|
||||||
|
}
|
||||||
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'P', State::Csi1(buf)) => {
|
||||||
|
// ESC[{buf}P CSI 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 + scroll_region.top)].set_ch(' ');
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'd', State::Csi1(buf)) => {
|
||||||
|
/* CSI Pm d Line Position Absolute [row] (default = [1,column]) (VPA). */
|
||||||
|
let row = unsafe { std::str::from_utf8_unchecked(buf) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap();
|
||||||
|
debug!(
|
||||||
|
"Line position absolute row {} with cursor at {:?}",
|
||||||
|
row, cursor
|
||||||
|
);
|
||||||
|
cursor.1 = row.saturating_sub(1);
|
||||||
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
|
cursor.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);
|
||||||
|
}
|
||||||
|
(b'm', State::Csi1(ref buf1)) => {
|
||||||
|
// Character Attributes.
|
||||||
|
match buf1.as_slice() {
|
||||||
|
b"30" => *fg_color = Color::Black,
|
||||||
|
b"31" => *fg_color = Color::Red,
|
||||||
|
b"32" => *fg_color = Color::Green,
|
||||||
|
b"33" => *fg_color = Color::Yellow,
|
||||||
|
b"34" => *fg_color = Color::Blue,
|
||||||
|
b"35" => *fg_color = Color::Magenta,
|
||||||
|
b"36" => *fg_color = Color::Cyan,
|
||||||
|
b"37" => *fg_color = Color::White,
|
||||||
|
|
||||||
|
b"39" => *fg_color = Color::Default,
|
||||||
|
b"40" => *fg_color = Color::Black,
|
||||||
|
b"41" => *bg_color = Color::Red,
|
||||||
|
b"42" => *bg_color = Color::Green,
|
||||||
|
b"43" => *bg_color = Color::Yellow,
|
||||||
|
b"44" => *bg_color = Color::Blue,
|
||||||
|
b"45" => *bg_color = Color::Magenta,
|
||||||
|
b"46" => *bg_color = Color::Cyan,
|
||||||
|
b"47" => *bg_color = Color::White,
|
||||||
|
|
||||||
|
b"49" => *bg_color = Color::Default,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(c, State::Csi1(ref mut buf)) if (c >= b'0' && c <= b'9') || c == b' ' => {
|
||||||
|
buf.push(c);
|
||||||
|
}
|
||||||
|
(_, State::Csi1(_)) => {
|
||||||
|
debug!("ignoring unknown code {}", 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'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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if scroll_region.top + cursor.1 >= terminal_size.1 {
|
||||||
|
cursor.1 = terminal_size.1.saturating_sub(1);
|
||||||
|
}
|
||||||
|
debug!("cursor became: {:?}", cursor);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(c, State::Csi2(_, ref mut buf)) if c >= b'0' && c <= b'9' => {
|
||||||
|
buf.push(c);
|
||||||
|
}
|
||||||
|
(b'r', State::Csi2(ref top, ref bottom)) => {
|
||||||
|
/* CSI Ps ; Ps r Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM). */
|
||||||
|
let top = unsafe { std::str::from_utf8_unchecked(top) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap_or(1);
|
||||||
|
let bottom = unsafe { std::str::from_utf8_unchecked(bottom) }
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap_or(1);
|
||||||
|
|
||||||
|
if bottom > top {
|
||||||
|
scroll_region.top = top - 1;
|
||||||
|
scroll_region.bottom = bottom - 1;
|
||||||
|
*cursor = (0, 0);
|
||||||
|
}
|
||||||
|
debug!("set scrolling region to {:?}", scroll_region);
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Csi2(_, _)) => {
|
||||||
|
debug!("ignoring unknown code {}", 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);
|
||||||
|
}
|
||||||
|
(b'm', State::Csi3(ref buf1, ref buf2, ref buf3)) if buf1 == b"38" && buf2 == b"5" => {
|
||||||
|
/* Set character attributes | foreground color */
|
||||||
|
*fg_color = if let Ok(byte) =
|
||||||
|
u8::from_str_radix(unsafe { std::str::from_utf8_unchecked(buf3) }, 10)
|
||||||
|
{
|
||||||
|
debug!("parsed buf as {}", byte);
|
||||||
|
Color::Byte(byte)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
grid[cursor_val!()].set_fg(*fg_color);
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(b'm', State::Csi3(ref buf1, ref buf2, ref buf3)) if buf1 == b"48" && buf2 == b"5" => {
|
||||||
|
/* Set character attributes | background color */
|
||||||
|
*bg_color = if let Ok(byte) =
|
||||||
|
u8::from_str_radix(unsafe { std::str::from_utf8_unchecked(buf3) }, 10)
|
||||||
|
{
|
||||||
|
debug!("parsed buf as {}", byte);
|
||||||
|
Color::Byte(byte)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
grid[cursor_val!()].set_bg(*bg_color);
|
||||||
|
debug!("{}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
(_, State::Csi3(_, _, _)) => {
|
||||||
|
debug!("ignoring unknown code {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
/* other stuff */
|
||||||
|
(_, State::G0) => {
|
||||||
|
debug!("ignoring {}", EscCode::from((&(*state), byte)));
|
||||||
|
*state = State::Normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,6 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crossbeam::{channel::Receiver, select};
|
use crossbeam::{channel::Receiver, select};
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use std::io;
|
|
||||||
use termion::event::Event as TermionEvent;
|
use termion::event::Event as TermionEvent;
|
||||||
use termion::event::Key as TermionKey;
|
use termion::event::Key as TermionKey;
|
||||||
use termion::input::TermRead;
|
use termion::input::TermRead;
|
||||||
|
@ -139,8 +138,18 @@ impl PartialEq<Key> for &Key {
|
||||||
enum InputMode {
|
enum InputMode {
|
||||||
Normal,
|
Normal,
|
||||||
Paste,
|
Paste,
|
||||||
|
PasteRaw(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum InputCommand {
|
||||||
|
Kill,
|
||||||
|
/// Send Raw bytes as well
|
||||||
|
Raw,
|
||||||
|
NoRaw,
|
||||||
|
}
|
||||||
|
|
||||||
|
use termion::input::TermReadEventsAndRaw;
|
||||||
/*
|
/*
|
||||||
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
|
* If we fork (for example start $EDITOR) we want the input-thread to stop reading from stdin. The
|
||||||
* best way I came up with right now is to send a signal to the thread that is read in the first
|
* best way I came up with right now is to send a signal to the thread that is read in the first
|
||||||
|
@ -150,23 +159,25 @@ enum InputMode {
|
||||||
* The main loop uses try_wait_on_child() to check if child has exited.
|
* The main loop uses try_wait_on_child() to check if child has exited.
|
||||||
*/
|
*/
|
||||||
pub fn get_events(
|
pub fn get_events(
|
||||||
stdin: io::Stdin,
|
|
||||||
mut closure: impl FnMut(Key),
|
mut closure: impl FnMut(Key),
|
||||||
mut exit: impl FnMut(),
|
closure_raw: impl FnMut((Key, Vec<u8>)),
|
||||||
rx: &Receiver<bool>,
|
rx: &Receiver<InputCommand>,
|
||||||
) {
|
) -> () {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
let mut input_mode = InputMode::Normal;
|
let mut input_mode = InputMode::Normal;
|
||||||
let mut paste_buf = String::with_capacity(256);
|
let mut paste_buf = String::with_capacity(256);
|
||||||
for c in stdin.events() {
|
for c in stdin.events() {
|
||||||
select! {
|
select! {
|
||||||
default => {},
|
default => {},
|
||||||
recv(rx) -> val => {
|
recv(rx) -> cmd => {
|
||||||
if let Ok(true) = val {
|
match cmd.unwrap() {
|
||||||
exit();
|
InputCommand::Kill => return,
|
||||||
return;
|
InputCommand::Raw => {
|
||||||
} else {
|
get_events_raw(closure, closure_raw, rx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
InputCommand::NoRaw => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,6 +201,60 @@ pub fn get_events(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_events_raw(
|
||||||
|
closure_nonraw: impl FnMut(Key),
|
||||||
|
mut closure: impl FnMut((Key, Vec<u8>)),
|
||||||
|
rx: &Receiver<InputCommand>,
|
||||||
|
) -> () {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let mut input_mode = InputMode::Normal;
|
||||||
|
let mut paste_buf = String::with_capacity(256);
|
||||||
|
for c in stdin.events_and_raw() {
|
||||||
|
select! {
|
||||||
|
default => {},
|
||||||
|
recv(rx) -> cmd => {
|
||||||
|
match cmd.unwrap() {
|
||||||
|
InputCommand::Kill => return,
|
||||||
|
InputCommand::NoRaw => {
|
||||||
|
get_events(closure_nonraw, closure, rx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
InputCommand::Raw => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match (c, &mut input_mode) {
|
||||||
|
(Ok((TermionEvent::Key(k), bytes)), InputMode::Normal) => {
|
||||||
|
closure((Key::from(k), bytes));
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Ok((TermionEvent::Key(TermionKey::Char(k)), ref mut bytes)),
|
||||||
|
InputMode::PasteRaw(ref mut buf),
|
||||||
|
) => {
|
||||||
|
paste_buf.push(k);
|
||||||
|
let bytes = std::mem::replace(bytes, Vec::new());
|
||||||
|
buf.extend(bytes.into_iter());
|
||||||
|
}
|
||||||
|
(Ok((TermionEvent::Unsupported(ref k), _)), _)
|
||||||
|
if k.as_slice() == BRACKET_PASTE_START =>
|
||||||
|
{
|
||||||
|
input_mode = InputMode::PasteRaw(Vec::new());
|
||||||
|
}
|
||||||
|
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::PasteRaw(ref mut buf))
|
||||||
|
if k.as_slice() == BRACKET_PASTE_END =>
|
||||||
|
{
|
||||||
|
let buf = std::mem::replace(buf, Vec::new());
|
||||||
|
input_mode = InputMode::Normal;
|
||||||
|
let ret = Key::from(&paste_buf);
|
||||||
|
paste_buf.clear();
|
||||||
|
closure((ret, buf));
|
||||||
|
}
|
||||||
|
_ => {} // Mouse events or errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const FIELDS: &[&str] = &[];
|
const FIELDS: &[&str] = &[];
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Key {
|
impl<'de> Deserialize<'de> for Key {
|
||||||
|
|
|
@ -48,6 +48,8 @@ pub enum ThreadEvent {
|
||||||
ThreadJoin(thread::ThreadId),
|
ThreadJoin(thread::ThreadId),
|
||||||
/// User input.
|
/// User input.
|
||||||
Input(Key),
|
Input(Key),
|
||||||
|
/// User input and input as raw bytes.
|
||||||
|
InputRaw((Key, Vec<u8>)),
|
||||||
/// A watched folder has been refreshed.
|
/// A watched folder has been refreshed.
|
||||||
RefreshMailbox(Box<RefreshEvent>),
|
RefreshMailbox(Box<RefreshEvent>),
|
||||||
UIEvent(UIEvent),
|
UIEvent(UIEvent),
|
||||||
|
@ -64,7 +66,10 @@ impl From<RefreshEvent> for ThreadEvent {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ForkType {
|
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),
|
Generic(std::process::Child),
|
||||||
NewDraft(File, std::process::Child),
|
NewDraft(File, std::process::Child),
|
||||||
}
|
}
|
||||||
|
@ -81,6 +86,7 @@ pub enum UIEvent {
|
||||||
Input(Key),
|
Input(Key),
|
||||||
ExInput(Key),
|
ExInput(Key),
|
||||||
InsertInput(Key),
|
InsertInput(Key),
|
||||||
|
EmbedInput((Key, Vec<u8>)),
|
||||||
RefreshMailbox((usize, FolderHash)), //view has changed to FolderHash mailbox
|
RefreshMailbox((usize, FolderHash)), //view has changed to FolderHash mailbox
|
||||||
//Quit?
|
//Quit?
|
||||||
Resize,
|
Resize,
|
||||||
|
@ -111,6 +117,8 @@ impl From<RefreshEvent> for UIEvent {
|
||||||
pub enum UIMode {
|
pub enum UIMode {
|
||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
|
/// Forward input to an embed pseudoterminal.
|
||||||
|
Embed,
|
||||||
Execute,
|
Execute,
|
||||||
Fork,
|
Fork,
|
||||||
}
|
}
|
||||||
|
@ -125,6 +133,7 @@ impl fmt::Display for UIMode {
|
||||||
UIMode::Insert => "INSERT",
|
UIMode::Insert => "INSERT",
|
||||||
UIMode::Execute => "EX",
|
UIMode::Execute => "EX",
|
||||||
UIMode::Fork => "FORK",
|
UIMode::Fork => "FORK",
|
||||||
|
UIMode::Embed => "EMBED",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue