From 5ec96e5d9a321a654777449bcd597ebabfaa6ffa Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 7 Mar 2021 19:04:34 -0500 Subject: reorganize a bit --- examples/tmux.rs | 8 +- src/async.rs | 94 ----------- src/blocking.rs | 92 ----------- src/blocking/input.rs | 415 +++++++++++++++++++++++++++++++++++++++++++++++++ src/blocking/mod.rs | 4 + src/blocking/output.rs | 92 +++++++++++ src/input.rs | 415 ------------------------------------------------- src/lib.rs | 7 +- src/output.rs | 94 +++++++++++ 9 files changed, 612 insertions(+), 609 deletions(-) delete mode 100644 src/async.rs delete mode 100644 src/blocking.rs create mode 100644 src/blocking/input.rs create mode 100644 src/blocking/mod.rs create mode 100644 src/blocking/output.rs delete mode 100644 src/input.rs create mode 100644 src/output.rs diff --git a/examples/tmux.rs b/examples/tmux.rs index b85b603..ac7205d 100644 --- a/examples/tmux.rs +++ b/examples/tmux.rs @@ -93,7 +93,7 @@ impl State { fn spawn_input_task( &self, ex: &smol::Executor<'_>, - mut input: textmode::Input, + mut input: textmode::blocking::Input, ) { let notify = self.wevents.clone(); ex.spawn(async move { @@ -309,8 +309,8 @@ impl State { #[must_use] struct Tmux { - input: textmode::Input, - _raw: textmode::RawGuard, + input: textmode::blocking::Input, + _raw: textmode::blocking::RawGuard, tm: textmode::Output, _screen: textmode::ScreenGuard, state: State, @@ -318,7 +318,7 @@ struct Tmux { impl Tmux { async fn new() -> Self { - let (input, _raw) = textmode::Input::new(); + let (input, _raw) = textmode::blocking::Input::new(); let (tm, _screen) = textmode::Output::new().await.unwrap(); let state = State::new(); Self { diff --git a/src/async.rs b/src/async.rs deleted file mode 100644 index 9e8c220..0000000 --- a/src/async.rs +++ /dev/null @@ -1,94 +0,0 @@ -use futures_lite::io::AsyncWriteExt as _; - -use super::private::TextmodeImpl as _; - -pub struct ScreenGuard { - cleaned_up: bool, -} - -impl ScreenGuard { - pub async fn cleanup(&mut self) -> std::io::Result<()> { - if self.cleaned_up { - return Ok(()); - } - self.cleaned_up = true; - write_stdout(super::DEINIT).await - } -} - -impl Drop for ScreenGuard { - fn drop(&mut self) { - futures_lite::future::block_on(async { - let _ = self.cleanup().await; - }); - } -} - -pub struct Output { - cur: vt100::Parser, - next: vt100::Parser, -} - -impl super::private::TextmodeImpl for Output { - fn cur(&self) -> &vt100::Parser { - &self.cur - } - - fn cur_mut(&mut self) -> &mut vt100::Parser { - &mut self.cur - } - - fn next(&self) -> &vt100::Parser { - &self.next - } - - fn next_mut(&mut self) -> &mut vt100::Parser { - &mut self.next - } -} - -impl super::Textmode for Output {} - -impl Output { - pub async fn new() -> std::io::Result<(Self, ScreenGuard)> { - write_stdout(super::INIT).await?; - - Ok(( - Self::new_without_screen(), - ScreenGuard { cleaned_up: false }, - )) - } - - pub fn new_without_screen() -> Self { - let (rows, cols) = match terminal_size::terminal_size() { - Some((terminal_size::Width(w), terminal_size::Height(h))) => { - (h, w) - } - _ => (24, 80), - }; - let cur = vt100::Parser::new(rows, cols, 0); - let next = vt100::Parser::new(rows, cols, 0); - Self { cur, next } - } - - pub async fn refresh(&mut self) -> std::io::Result<()> { - let diffs = &[ - self.next().screen().contents_diff(self.cur().screen()), - self.next().screen().input_mode_diff(self.cur().screen()), - self.next().screen().title_diff(self.cur().screen()), - self.next().screen().bells_diff(self.cur().screen()), - ]; - for diff in diffs { - write_stdout(&diff).await?; - self.cur_mut().process(&diff); - } - Ok(()) - } -} - -async fn write_stdout(buf: &[u8]) -> std::io::Result<()> { - let mut stdout = blocking::Unblock::new(std::io::stdout()); - stdout.write_all(buf).await?; - stdout.flush().await?; - Ok(()) -} diff --git a/src/blocking.rs b/src/blocking.rs deleted file mode 100644 index fe33e6e..0000000 --- a/src/blocking.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::io::Write as _; - -use super::private::TextmodeImpl as _; - -pub struct ScreenGuard { - cleaned_up: bool, -} - -impl ScreenGuard { - pub fn cleanup(&mut self) -> std::io::Result<()> { - if self.cleaned_up { - return Ok(()); - } - self.cleaned_up = true; - write_stdout(super::DEINIT) - } -} - -impl Drop for ScreenGuard { - fn drop(&mut self) { - let _ = self.cleanup(); - } -} - -pub struct Output { - cur: vt100::Parser, - next: vt100::Parser, -} - -impl super::private::TextmodeImpl for Output { - fn cur(&self) -> &vt100::Parser { - &self.cur - } - - fn cur_mut(&mut self) -> &mut vt100::Parser { - &mut self.cur - } - - fn next(&self) -> &vt100::Parser { - &self.next - } - - fn next_mut(&mut self) -> &mut vt100::Parser { - &mut self.next - } -} - -impl super::Textmode for Output {} - -impl Output { - pub fn new() -> std::io::Result<(Self, ScreenGuard)> { - write_stdout(super::INIT)?; - Ok(( - Self::new_without_screen(), - ScreenGuard { cleaned_up: false }, - )) - } - - pub fn new_without_screen() -> Self { - let (rows, cols) = match terminal_size::terminal_size() { - Some((terminal_size::Width(w), terminal_size::Height(h))) => { - (h, w) - } - _ => (24, 80), - }; - let cur = vt100::Parser::new(rows, cols, 0); - let next = vt100::Parser::new(rows, cols, 0); - - Self { cur, next } - } - - pub fn refresh(&mut self) -> std::io::Result<()> { - let diffs = &[ - self.next().screen().contents_diff(self.cur().screen()), - self.next().screen().input_mode_diff(self.cur().screen()), - self.next().screen().title_diff(self.cur().screen()), - self.next().screen().bells_diff(self.cur().screen()), - ]; - for diff in diffs { - write_stdout(&diff)?; - self.cur_mut().process(&diff); - } - Ok(()) - } -} - -fn write_stdout(buf: &[u8]) -> std::io::Result<()> { - let mut stdout = std::io::stdout(); - stdout.write_all(buf)?; - stdout.flush()?; - Ok(()) -} diff --git a/src/blocking/input.rs b/src/blocking/input.rs new file mode 100644 index 0000000..a61afa5 --- /dev/null +++ b/src/blocking/input.rs @@ -0,0 +1,415 @@ +use std::io::Read as _; +use std::os::unix::io::AsRawFd as _; + +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum Key { + String(String), + Char(char), + Bytes(Vec), + Byte(u8), + Ctrl(u8), + Meta(u8), + Backspace, + Up, + Down, + Right, + Left, + KeypadUp, + KeypadDown, + KeypadRight, + KeypadLeft, + Home, + End, + Insert, + Delete, + PageUp, + PageDown, + F(u8), +} + +impl Key { + pub fn into_bytes(self) -> Vec { + use Key::*; + match self { + String(s) => s.into_bytes(), + Char(c) => c.to_string().into_bytes(), + Bytes(s) => s, + Byte(c) => vec![c], + Ctrl(c) => vec![c - b'a' + 1], + Meta(c) => vec![b'\x1b', c], + Backspace => b"\x7f".to_vec(), + Up => b"\x1b[A".to_vec(), + Down => b"\x1b[B".to_vec(), + Right => b"\x1b[C".to_vec(), + Left => b"\x1b[D".to_vec(), + KeypadUp => b"\x1bOA".to_vec(), + KeypadDown => b"\x1bOB".to_vec(), + KeypadRight => b"\x1bOC".to_vec(), + KeypadLeft => b"\x1bOD".to_vec(), + Home => b"\x1b[H".to_vec(), + End => b"\x1b[F".to_vec(), + Insert => b"\x1b[2~".to_vec(), + Delete => b"\x1b[3~".to_vec(), + PageUp => b"\x1b[5~".to_vec(), + PageDown => b"\x1b[6~".to_vec(), + F(c) => match c { + 1 => b"\x1bOP".to_vec(), + 2 => b"\x1bOQ".to_vec(), + 3 => b"\x1bOR".to_vec(), + 4 => b"\x1bOS".to_vec(), + 5 => b"\x1b[15~".to_vec(), + 6 => b"\x1b[17~".to_vec(), + 7 => b"\x1b[18~".to_vec(), + 8 => b"\x1b[19~".to_vec(), + 9 => b"\x1b[20~".to_vec(), + 10 => b"\x1b[21~".to_vec(), + 11 => b"\x1b[23~".to_vec(), + 12 => b"\x1b[24~".to_vec(), + 13 => b"\x1b[25~".to_vec(), + 14 => b"\x1b[26~".to_vec(), + 15 => b"\x1b[28~".to_vec(), + 16 => b"\x1b[29~".to_vec(), + 17 => b"\x1b[31~".to_vec(), + 18 => b"\x1b[32~".to_vec(), + 19 => b"\x1b[33~".to_vec(), + 20 => b"\x1b[34~".to_vec(), + _ => vec![], + }, + } + } +} + +pub struct RawGuard { + termios: nix::sys::termios::Termios, + cleaned_up: bool, +} + +impl RawGuard { + pub fn cleanup(&mut self) { + if self.cleaned_up { + return; + } + self.cleaned_up = true; + let stdin = std::io::stdin().as_raw_fd(); + let _ = nix::sys::termios::tcsetattr( + stdin, + nix::sys::termios::SetArg::TCSANOW, + &self.termios, + ); + } +} + +impl Drop for RawGuard { + fn drop(&mut self) { + self.cleanup(); + } +} + +pub struct Input { + buf: Vec, +} + +#[allow(clippy::new_without_default)] +impl Input { + pub fn new() -> (Self, RawGuard) { + let stdin = std::io::stdin().as_raw_fd(); + let termios = nix::sys::termios::tcgetattr(stdin).unwrap(); + let mut termios_raw = termios.clone(); + nix::sys::termios::cfmakeraw(&mut termios_raw); + nix::sys::termios::tcsetattr( + stdin, + nix::sys::termios::SetArg::TCSANOW, + &termios_raw, + ) + .unwrap(); + ( + Self::new_without_raw(), + RawGuard { + termios, + cleaned_up: false, + }, + ) + } + + pub fn new_without_raw() -> Self { + Self { + buf: Vec::with_capacity(4096), + } + } + + pub fn read_keys(&mut self) -> std::io::Result> { + self.real_read_key(true, false) + } + + pub fn read_keys_string(&mut self) -> std::io::Result> { + self.real_read_key(true, true) + } + + pub fn read_key(&mut self) -> std::io::Result> { + self.real_read_key(false, false) + } + + pub fn read_key_char(&mut self) -> std::io::Result> { + self.real_read_key(false, true) + } + + fn real_read_key( + &mut self, + combine: bool, + utf8: bool, + ) -> std::io::Result> { + match self.next_byte(true)? { + Some(c @ 32..=126) | Some(c @ 128..=255) => { + self.parse_text(c, combine, utf8) + } + Some(c @ 1..=26) => Ok(Some(Key::Ctrl(b'a' + c - 1))), + Some(27) => self.parse_escape_sequence(), + Some(c @ 0) | Some(c @ 28..=31) => { + self.parse_unknown_char(c, combine) + } + Some(127) => Ok(Some(Key::Backspace)), + None => Ok(None), + } + } + + fn parse_text( + &mut self, + c: u8, + combine: bool, + utf8: bool, + ) -> std::io::Result> { + if combine { + let idx = self + .buf + .iter() + .take_while(|&c| { + (32..=126).contains(c) || (128..=255).contains(c) + }) + .count(); + let mut rest = self.buf.split_off(idx); + std::mem::swap(&mut self.buf, &mut rest); + rest.insert(0, c); + if utf8 { + match std::string::String::from_utf8(rest) { + Ok(s) => Ok(Some(Key::String(s))), + Err(e) => Ok(Some(Key::Bytes(e.into_bytes()))), + } + } else { + Ok(Some(Key::Bytes(rest))) + } + } else { + if utf8 { + self.parse_utf8_char(c) + } else { + Ok(Some(Key::Byte(c))) + } + } + } + + #[allow(clippy::unnecessary_wraps)] + fn parse_unknown_char( + &mut self, + c: u8, + combine: bool, + ) -> std::io::Result> { + if combine { + let idx = self + .buf + .iter() + .take_while(|&c| *c == 0 || (28..=31).contains(c)) + .count(); + let mut rest = self.buf.split_off(idx); + std::mem::swap(&mut self.buf, &mut rest); + rest.insert(0, c); + Ok(Some(Key::Bytes(rest))) + } else { + Ok(Some(Key::Byte(c))) + } + } + + fn parse_escape_sequence(&mut self) -> std::io::Result> { + let mut seen = vec![b'\x1b']; + macro_rules! next_byte { + () => { + match self.next_byte(false)? { + Some(c) => c, + None => return Ok(Some(Key::Bytes(seen))), + } + }; + } + enum EscapeState { + Escape, + CSI(Vec), + CKM(Vec), + } + let mut state = EscapeState::Escape; + loop { + let c = next_byte!(); + seen.push(c); + match state { + EscapeState::Escape => match c { + b'[' => { + state = EscapeState::CSI(vec![]); + } + b'O' => { + state = EscapeState::CKM(vec![]); + } + _ => { + return Ok(Some(Key::Meta(c))); + } + }, + EscapeState::CSI(ref mut param) => match c { + b'A' => return Ok(Some(Key::Up)), + b'B' => return Ok(Some(Key::Down)), + b'C' => return Ok(Some(Key::Right)), + b'D' => return Ok(Some(Key::Left)), + b'H' => return Ok(Some(Key::Home)), + b'F' => return Ok(Some(Key::End)), + b'0'..=b'9' => { + param.push(c); + state = EscapeState::CSI(param.to_vec()); + } + b'~' => match param.as_slice() { + [b'2'] => return Ok(Some(Key::Insert)), + [b'3'] => return Ok(Some(Key::Delete)), + [b'5'] => return Ok(Some(Key::PageUp)), + [b'6'] => return Ok(Some(Key::PageDown)), + [b'1', b'5'] => return Ok(Some(Key::F(5))), + [b'1', b'7'] => return Ok(Some(Key::F(6))), + [b'1', b'8'] => return Ok(Some(Key::F(7))), + [b'1', b'9'] => return Ok(Some(Key::F(8))), + [b'2', b'0'] => return Ok(Some(Key::F(9))), + [b'2', b'1'] => return Ok(Some(Key::F(10))), + [b'2', b'3'] => return Ok(Some(Key::F(11))), + [b'2', b'4'] => return Ok(Some(Key::F(12))), + [b'2', b'5'] => return Ok(Some(Key::F(13))), + [b'2', b'6'] => return Ok(Some(Key::F(14))), + [b'2', b'8'] => return Ok(Some(Key::F(15))), + [b'2', b'9'] => return Ok(Some(Key::F(16))), + [b'3', b'1'] => return Ok(Some(Key::F(17))), + [b'3', b'2'] => return Ok(Some(Key::F(18))), + [b'3', b'3'] => return Ok(Some(Key::F(19))), + [b'3', b'4'] => return Ok(Some(Key::F(20))), + _ => { + let mut seq = vec![b'\x1b', b'[']; + seq.extend(param.iter()); + seq.push(b'~'); + return Ok(Some(Key::Bytes(seq))); + } + }, + _ => { + let mut seq = vec![b'\x1b', b'[']; + seq.extend(param.iter()); + seq.push(c); + return Ok(Some(Key::Bytes(seq))); + } + }, + EscapeState::CKM(ref mut param) => match c { + b'A' => return Ok(Some(Key::KeypadUp)), + b'B' => return Ok(Some(Key::KeypadDown)), + b'C' => return Ok(Some(Key::KeypadRight)), + b'D' => return Ok(Some(Key::KeypadLeft)), + b'P' => return Ok(Some(Key::F(1))), + b'Q' => return Ok(Some(Key::F(2))), + b'R' => return Ok(Some(Key::F(3))), + b'S' => return Ok(Some(Key::F(4))), + _ => { + let mut seq = vec![b'\x1b', b'O']; + seq.extend(param.iter()); + seq.push(c); + return Ok(Some(Key::Bytes(seq))); + } + }, + } + } + } + + fn parse_utf8_char( + &mut self, + initial: u8, + ) -> std::io::Result> { + let mut buf = vec![initial]; + + macro_rules! next_byte { + () => { + match self.next_byte(true)? { + Some(c) => { + if (0b1000_0000..=0b1011_1111).contains(&c) { + c + } else { + self.buf = buf + .iter() + .skip(1) + .copied() + .chain(self.buf.iter().copied()) + .collect(); + return Ok(Some(Key::Byte(initial))); + } + } + None => return Ok(None), + } + }; + } + + match initial { + 0b0000_0000..=0b0111_1111 => {} + 0b1100_0000..=0b1101_1111 => { + buf.push(next_byte!()); + } + 0b1110_0000..=0b1110_1111 => { + buf.push(next_byte!()); + buf.push(next_byte!()); + } + 0b1111_0000..=0b1111_0111 => { + buf.push(next_byte!()); + buf.push(next_byte!()); + buf.push(next_byte!()); + } + _ => { + return Ok(Some(Key::Bytes(buf))); + } + } + match std::string::String::from_utf8(buf) { + Ok(s) => Ok(Some(Key::Char(s.chars().next().unwrap()))), + Err(e) => { + let buf = e.into_bytes(); + self.buf = buf + .iter() + .skip(1) + .copied() + .chain(self.buf.iter().copied()) + .collect(); + Ok(Some(Key::Byte(initial))) + } + } + } + + fn next_byte(&mut self, fill: bool) -> std::io::Result> { + if self.buf.is_empty() { + if !fill || !self.fill_buf()? { + return Ok(None); + } + } + let c = self.buf.remove(0); + Ok(Some(c)) + } + + fn fill_buf(&mut self) -> std::io::Result { + self.buf.resize(4096, 0); + // can't use self.read here because the borrow checker can't tell + // that our read implementation doesn't actually need to mutably + // borrow self + let bytes = std::io::stdin().read(&mut self.buf)?; + if bytes == 0 { + return Ok(false); + } + self.buf.truncate(bytes); + Ok(true) + } +} + +impl std::io::Read for Input { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + std::io::stdin().read(buf) + } +} diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs new file mode 100644 index 0000000..d4ffe4a --- /dev/null +++ b/src/blocking/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod input; +pub use input::{Input, RawGuard}; +mod output; +pub use output::{Output, ScreenGuard}; diff --git a/src/blocking/output.rs b/src/blocking/output.rs new file mode 100644 index 0000000..3049cac --- /dev/null +++ b/src/blocking/output.rs @@ -0,0 +1,92 @@ +use std::io::Write as _; + +use crate::private::TextmodeImpl as _; + +pub struct ScreenGuard { + cleaned_up: bool, +} + +impl ScreenGuard { + pub fn cleanup(&mut self) -> std::io::Result<()> { + if self.cleaned_up { + return Ok(()); + } + self.cleaned_up = true; + write_stdout(crate::DEINIT) + } +} + +impl Drop for ScreenGuard { + fn drop(&mut self) { + let _ = self.cleanup(); + } +} + +pub struct Output { + cur: vt100::Parser, + next: vt100::Parser, +} + +impl crate::private::TextmodeImpl for Output { + fn cur(&self) -> &vt100::Parser { + &self.cur + } + + fn cur_mut(&mut self) -> &mut vt100::Parser { + &mut self.cur + } + + fn next(&self) -> &vt100::Parser { + &self.next + } + + fn next_mut(&mut self) -> &mut vt100::Parser { + &mut self.next + } +} + +impl crate::Textmode for Output {} + +impl Output { + pub fn new() -> std::io::Result<(Self, ScreenGuard)> { + write_stdout(crate::INIT)?; + Ok(( + Self::new_without_screen(), + ScreenGuard { cleaned_up: false }, + )) + } + + pub fn new_without_screen() -> Self { + let (rows, cols) = match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (h, w) + } + _ => (24, 80), + }; + let cur = vt100::Parser::new(rows, cols, 0); + let next = vt100::Parser::new(rows, cols, 0); + + Self { cur, next } + } + + pub fn refresh(&mut self) -> std::io::Result<()> { + let diffs = &[ + self.next().screen().contents_diff(self.cur().screen()), + self.next().screen().input_mode_diff(self.cur().screen()), + self.next().screen().title_diff(self.cur().screen()), + self.next().screen().bells_diff(self.cur().screen()), + ]; + for diff in diffs { + write_stdout(&diff)?; + self.cur_mut().process(&diff); + } + Ok(()) + } +} + +fn write_stdout(buf: &[u8]) -> std::io::Result<()> { + let mut stdout = std::io::stdout(); + stdout.write_all(buf)?; + stdout.flush()?; + Ok(()) +} diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index a61afa5..0000000 --- a/src/input.rs +++ /dev/null @@ -1,415 +0,0 @@ -use std::io::Read as _; -use std::os::unix::io::AsRawFd as _; - -#[derive(Eq, PartialEq, Debug, Clone)] -pub enum Key { - String(String), - Char(char), - Bytes(Vec), - Byte(u8), - Ctrl(u8), - Meta(u8), - Backspace, - Up, - Down, - Right, - Left, - KeypadUp, - KeypadDown, - KeypadRight, - KeypadLeft, - Home, - End, - Insert, - Delete, - PageUp, - PageDown, - F(u8), -} - -impl Key { - pub fn into_bytes(self) -> Vec { - use Key::*; - match self { - String(s) => s.into_bytes(), - Char(c) => c.to_string().into_bytes(), - Bytes(s) => s, - Byte(c) => vec![c], - Ctrl(c) => vec![c - b'a' + 1], - Meta(c) => vec![b'\x1b', c], - Backspace => b"\x7f".to_vec(), - Up => b"\x1b[A".to_vec(), - Down => b"\x1b[B".to_vec(), - Right => b"\x1b[C".to_vec(), - Left => b"\x1b[D".to_vec(), - KeypadUp => b"\x1bOA".to_vec(), - KeypadDown => b"\x1bOB".to_vec(), - KeypadRight => b"\x1bOC".to_vec(), - KeypadLeft => b"\x1bOD".to_vec(), - Home => b"\x1b[H".to_vec(), - End => b"\x1b[F".to_vec(), - Insert => b"\x1b[2~".to_vec(), - Delete => b"\x1b[3~".to_vec(), - PageUp => b"\x1b[5~".to_vec(), - PageDown => b"\x1b[6~".to_vec(), - F(c) => match c { - 1 => b"\x1bOP".to_vec(), - 2 => b"\x1bOQ".to_vec(), - 3 => b"\x1bOR".to_vec(), - 4 => b"\x1bOS".to_vec(), - 5 => b"\x1b[15~".to_vec(), - 6 => b"\x1b[17~".to_vec(), - 7 => b"\x1b[18~".to_vec(), - 8 => b"\x1b[19~".to_vec(), - 9 => b"\x1b[20~".to_vec(), - 10 => b"\x1b[21~".to_vec(), - 11 => b"\x1b[23~".to_vec(), - 12 => b"\x1b[24~".to_vec(), - 13 => b"\x1b[25~".to_vec(), - 14 => b"\x1b[26~".to_vec(), - 15 => b"\x1b[28~".to_vec(), - 16 => b"\x1b[29~".to_vec(), - 17 => b"\x1b[31~".to_vec(), - 18 => b"\x1b[32~".to_vec(), - 19 => b"\x1b[33~".to_vec(), - 20 => b"\x1b[34~".to_vec(), - _ => vec![], - }, - } - } -} - -pub struct RawGuard { - termios: nix::sys::termios::Termios, - cleaned_up: bool, -} - -impl RawGuard { - pub fn cleanup(&mut self) { - if self.cleaned_up { - return; - } - self.cleaned_up = true; - let stdin = std::io::stdin().as_raw_fd(); - let _ = nix::sys::termios::tcsetattr( - stdin, - nix::sys::termios::SetArg::TCSANOW, - &self.termios, - ); - } -} - -impl Drop for RawGuard { - fn drop(&mut self) { - self.cleanup(); - } -} - -pub struct Input { - buf: Vec, -} - -#[allow(clippy::new_without_default)] -impl Input { - pub fn new() -> (Self, RawGuard) { - let stdin = std::io::stdin().as_raw_fd(); - let termios = nix::sys::termios::tcgetattr(stdin).unwrap(); - let mut termios_raw = termios.clone(); - nix::sys::termios::cfmakeraw(&mut termios_raw); - nix::sys::termios::tcsetattr( - stdin, - nix::sys::termios::SetArg::TCSANOW, - &termios_raw, - ) - .unwrap(); - ( - Self::new_without_raw(), - RawGuard { - termios, - cleaned_up: false, - }, - ) - } - - pub fn new_without_raw() -> Self { - Self { - buf: Vec::with_capacity(4096), - } - } - - pub fn read_keys(&mut self) -> std::io::Result> { - self.real_read_key(true, false) - } - - pub fn read_keys_string(&mut self) -> std::io::Result> { - self.real_read_key(true, true) - } - - pub fn read_key(&mut self) -> std::io::Result> { - self.real_read_key(false, false) - } - - pub fn read_key_char(&mut self) -> std::io::Result> { - self.real_read_key(false, true) - } - - fn real_read_key( - &mut self, - combine: bool, - utf8: bool, - ) -> std::io::Result> { - match self.next_byte(true)? { - Some(c @ 32..=126) | Some(c @ 128..=255) => { - self.parse_text(c, combine, utf8) - } - Some(c @ 1..=26) => Ok(Some(Key::Ctrl(b'a' + c - 1))), - Some(27) => self.parse_escape_sequence(), - Some(c @ 0) | Some(c @ 28..=31) => { - self.parse_unknown_char(c, combine) - } - Some(127) => Ok(Some(Key::Backspace)), - None => Ok(None), - } - } - - fn parse_text( - &mut self, - c: u8, - combine: bool, - utf8: bool, - ) -> std::io::Result> { - if combine { - let idx = self - .buf - .iter() - .take_while(|&c| { - (32..=126).contains(c) || (128..=255).contains(c) - }) - .count(); - let mut rest = self.buf.split_off(idx); - std::mem::swap(&mut self.buf, &mut rest); - rest.insert(0, c); - if utf8 { - match std::string::String::from_utf8(rest) { - Ok(s) => Ok(Some(Key::String(s))), - Err(e) => Ok(Some(Key::Bytes(e.into_bytes()))), - } - } else { - Ok(Some(Key::Bytes(rest))) - } - } else { - if utf8 { - self.parse_utf8_char(c) - } else { - Ok(Some(Key::Byte(c))) - } - } - } - - #[allow(clippy::unnecessary_wraps)] - fn parse_unknown_char( - &mut self, - c: u8, - combine: bool, - ) -> std::io::Result> { - if combine { - let idx = self - .buf - .iter() - .take_while(|&c| *c == 0 || (28..=31).contains(c)) - .count(); - let mut rest = self.buf.split_off(idx); - std::mem::swap(&mut self.buf, &mut rest); - rest.insert(0, c); - Ok(Some(Key::Bytes(rest))) - } else { - Ok(Some(Key::Byte(c))) - } - } - - fn parse_escape_sequence(&mut self) -> std::io::Result> { - let mut seen = vec![b'\x1b']; - macro_rules! next_byte { - () => { - match self.next_byte(false)? { - Some(c) => c, - None => return Ok(Some(Key::Bytes(seen))), - } - }; - } - enum EscapeState { - Escape, - CSI(Vec), - CKM(Vec), - } - let mut state = EscapeState::Escape; - loop { - let c = next_byte!(); - seen.push(c); - match state { - EscapeState::Escape => match c { - b'[' => { - state = EscapeState::CSI(vec![]); - } - b'O' => { - state = EscapeState::CKM(vec![]); - } - _ => { - return Ok(Some(Key::Meta(c))); - } - }, - EscapeState::CSI(ref mut param) => match c { - b'A' => return Ok(Some(Key::Up)), - b'B' => return Ok(Some(Key::Down)), - b'C' => return Ok(Some(Key::Right)), - b'D' => return Ok(Some(Key::Left)), - b'H' => return Ok(Some(Key::Home)), - b'F' => return Ok(Some(Key::End)), - b'0'..=b'9' => { - param.push(c); - state = EscapeState::CSI(param.to_vec()); - } - b'~' => match param.as_slice() { - [b'2'] => return Ok(Some(Key::Insert)), - [b'3'] => return Ok(Some(Key::Delete)), - [b'5'] => return Ok(Some(Key::PageUp)), - [b'6'] => return Ok(Some(Key::PageDown)), - [b'1', b'5'] => return Ok(Some(Key::F(5))), - [b'1', b'7'] => return Ok(Some(Key::F(6))), - [b'1', b'8'] => return Ok(Some(Key::F(7))), - [b'1', b'9'] => return Ok(Some(Key::F(8))), - [b'2', b'0'] => return Ok(Some(Key::F(9))), - [b'2', b'1'] => return Ok(Some(Key::F(10))), - [b'2', b'3'] => return Ok(Some(Key::F(11))), - [b'2', b'4'] => return Ok(Some(Key::F(12))), - [b'2', b'5'] => return Ok(Some(Key::F(13))), - [b'2', b'6'] => return Ok(Some(Key::F(14))), - [b'2', b'8'] => return Ok(Some(Key::F(15))), - [b'2', b'9'] => return Ok(Some(Key::F(16))), - [b'3', b'1'] => return Ok(Some(Key::F(17))), - [b'3', b'2'] => return Ok(Some(Key::F(18))), - [b'3', b'3'] => return Ok(Some(Key::F(19))), - [b'3', b'4'] => return Ok(Some(Key::F(20))), - _ => { - let mut seq = vec![b'\x1b', b'[']; - seq.extend(param.iter()); - seq.push(b'~'); - return Ok(Some(Key::Bytes(seq))); - } - }, - _ => { - let mut seq = vec![b'\x1b', b'[']; - seq.extend(param.iter()); - seq.push(c); - return Ok(Some(Key::Bytes(seq))); - } - }, - EscapeState::CKM(ref mut param) => match c { - b'A' => return Ok(Some(Key::KeypadUp)), - b'B' => return Ok(Some(Key::KeypadDown)), - b'C' => return Ok(Some(Key::KeypadRight)), - b'D' => return Ok(Some(Key::KeypadLeft)), - b'P' => return Ok(Some(Key::F(1))), - b'Q' => return Ok(Some(Key::F(2))), - b'R' => return Ok(Some(Key::F(3))), - b'S' => return Ok(Some(Key::F(4))), - _ => { - let mut seq = vec![b'\x1b', b'O']; - seq.extend(param.iter()); - seq.push(c); - return Ok(Some(Key::Bytes(seq))); - } - }, - } - } - } - - fn parse_utf8_char( - &mut self, - initial: u8, - ) -> std::io::Result> { - let mut buf = vec![initial]; - - macro_rules! next_byte { - () => { - match self.next_byte(true)? { - Some(c) => { - if (0b1000_0000..=0b1011_1111).contains(&c) { - c - } else { - self.buf = buf - .iter() - .skip(1) - .copied() - .chain(self.buf.iter().copied()) - .collect(); - return Ok(Some(Key::Byte(initial))); - } - } - None => return Ok(None), - } - }; - } - - match initial { - 0b0000_0000..=0b0111_1111 => {} - 0b1100_0000..=0b1101_1111 => { - buf.push(next_byte!()); - } - 0b1110_0000..=0b1110_1111 => { - buf.push(next_byte!()); - buf.push(next_byte!()); - } - 0b1111_0000..=0b1111_0111 => { - buf.push(next_byte!()); - buf.push(next_byte!()); - buf.push(next_byte!()); - } - _ => { - return Ok(Some(Key::Bytes(buf))); - } - } - match std::string::String::from_utf8(buf) { - Ok(s) => Ok(Some(Key::Char(s.chars().next().unwrap()))), - Err(e) => { - let buf = e.into_bytes(); - self.buf = buf - .iter() - .skip(1) - .copied() - .chain(self.buf.iter().copied()) - .collect(); - Ok(Some(Key::Byte(initial))) - } - } - } - - fn next_byte(&mut self, fill: bool) -> std::io::Result> { - if self.buf.is_empty() { - if !fill || !self.fill_buf()? { - return Ok(None); - } - } - let c = self.buf.remove(0); - Ok(Some(c)) - } - - fn fill_buf(&mut self) -> std::io::Result { - self.buf.resize(4096, 0); - // can't use self.read here because the borrow checker can't tell - // that our read implementation doesn't actually need to mutably - // borrow self - let bytes = std::io::stdin().read(&mut self.buf)?; - if bytes == 0 { - return Ok(false); - } - self.buf.truncate(bytes); - Ok(true) - } -} - -impl std::io::Read for Input { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - std::io::stdin().read(buf) - } -} diff --git a/src/lib.rs b/src/lib.rs index da46ba6..1023167 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,13 +3,12 @@ pub mod color; pub mod blocking; -mod input; -pub use input::{Input, Key, RawGuard}; +pub use crate::blocking::input::Key; #[cfg(feature = "async")] -mod r#async; +mod output; #[cfg(feature = "async")] -pub use r#async::{Output, ScreenGuard}; +pub use output::{Output, ScreenGuard}; const INIT: &[u8] = b"\x1b7\x1b[?47h\x1b[2J\x1b[H\x1b[?25h"; const DEINIT: &[u8] = b"\x1b[?47l\x1b8\x1b[?25h"; diff --git a/src/output.rs b/src/output.rs new file mode 100644 index 0000000..9e8c220 --- /dev/null +++ b/src/output.rs @@ -0,0 +1,94 @@ +use futures_lite::io::AsyncWriteExt as _; + +use super::private::TextmodeImpl as _; + +pub struct ScreenGuard { + cleaned_up: bool, +} + +impl ScreenGuard { + pub async fn cleanup(&mut self) -> std::io::Result<()> { + if self.cleaned_up { + return Ok(()); + } + self.cleaned_up = true; + write_stdout(super::DEINIT).await + } +} + +impl Drop for ScreenGuard { + fn drop(&mut self) { + futures_lite::future::block_on(async { + let _ = self.cleanup().await; + }); + } +} + +pub struct Output { + cur: vt100::Parser, + next: vt100::Parser, +} + +impl super::private::TextmodeImpl for Output { + fn cur(&self) -> &vt100::Parser { + &self.cur + } + + fn cur_mut(&mut self) -> &mut vt100::Parser { + &mut self.cur + } + + fn next(&self) -> &vt100::Parser { + &self.next + } + + fn next_mut(&mut self) -> &mut vt100::Parser { + &mut self.next + } +} + +impl super::Textmode for Output {} + +impl Output { + pub async fn new() -> std::io::Result<(Self, ScreenGuard)> { + write_stdout(super::INIT).await?; + + Ok(( + Self::new_without_screen(), + ScreenGuard { cleaned_up: false }, + )) + } + + pub fn new_without_screen() -> Self { + let (rows, cols) = match terminal_size::terminal_size() { + Some((terminal_size::Width(w), terminal_size::Height(h))) => { + (h, w) + } + _ => (24, 80), + }; + let cur = vt100::Parser::new(rows, cols, 0); + let next = vt100::Parser::new(rows, cols, 0); + Self { cur, next } + } + + pub async fn refresh(&mut self) -> std::io::Result<()> { + let diffs = &[ + self.next().screen().contents_diff(self.cur().screen()), + self.next().screen().input_mode_diff(self.cur().screen()), + self.next().screen().title_diff(self.cur().screen()), + self.next().screen().bells_diff(self.cur().screen()), + ]; + for diff in diffs { + write_stdout(&diff).await?; + self.cur_mut().process(&diff); + } + Ok(()) + } +} + +async fn write_stdout(buf: &[u8]) -> std::io::Result<()> { + let mut stdout = blocking::Unblock::new(std::io::stdout()); + stdout.write_all(buf).await?; + stdout.flush().await?; + Ok(()) +} -- cgit v1.2.3