aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-03-09 02:33:01 -0500
committerJesse Luehrs <doy@tozt.net>2021-03-09 02:40:29 -0500
commitdd845e949ac59e08bf12d0fcac8b4069c5c7645c (patch)
tree3f9dc84ac00fb692f80726c3539084185a9f88fa
parent3a92d03b3c47926b0eeaac8f301833b23d68a6ec (diff)
downloadtextmode-dd845e949ac59e08bf12d0fcac8b4069c5c7645c.tar.gz
textmode-dd845e949ac59e08bf12d0fcac8b4069c5c7645c.zip
add async implementation of Input
this is just copied and pasted for now, need to figure out how to generate one from the other
-rw-r--r--examples/tmux.rs21
-rw-r--r--src/blocking/input.rs247
-rw-r--r--src/blocking/mod.rs3
-rw-r--r--src/input.rs369
-rw-r--r--src/key.rs79
-rw-r--r--src/lib.rs13
-rw-r--r--src/raw_guard.rs45
7 files changed, 577 insertions, 200 deletions
diff --git a/examples/tmux.rs b/examples/tmux.rs
index 5d1578b..490a22b 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::blocking::Input,
+ mut input: textmode::Input,
) {
let notify = self.wevents.clone();
ex.spawn(async move {
@@ -103,12 +103,8 @@ impl State {
input.parse_special_keys(false);
loop {
input.parse_single(waiting_for_command);
- let key_input = smol::unblock(move || {
- let key = input.read_key();
- (input, key)
- });
- match key_input.await {
- (returned_input, Ok(Some(key))) => {
+ match input.read_key().await {
+ Ok(Some(key)) => {
if waiting_for_command {
match key {
textmode::Key::Ctrl(b'n') => {
@@ -151,12 +147,11 @@ impl State {
}
}
}
- input = returned_input;
}
- (_, Ok(None)) => {
+ Ok(None) => {
break;
}
- (_, Err(e)) => {
+ Err(e) => {
eprintln!("{}", e);
break;
}
@@ -299,8 +294,8 @@ impl State {
#[must_use]
struct Tmux {
- input: textmode::blocking::Input,
- _raw: textmode::blocking::RawGuard,
+ input: textmode::Input,
+ _raw: textmode::RawGuard,
tm: textmode::Output,
_screen: textmode::ScreenGuard,
state: State,
@@ -308,7 +303,7 @@ struct Tmux {
impl Tmux {
async fn new() -> Self {
- let (input, _raw) = textmode::blocking::Input::new();
+ let (input, _raw) = textmode::Input::new();
let (tm, _screen) = textmode::Output::new().await.unwrap();
let state = State::new();
Self {
diff --git a/src/blocking/input.rs b/src/blocking/input.rs
index bc4f401..716cfca 100644
--- a/src/blocking/input.rs
+++ b/src/blocking/input.rs
@@ -1,111 +1,4 @@
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<u8>),
- Byte(u8),
- Ctrl(u8),
- Meta(u8),
- Backspace,
- Escape,
- Up,
- Down,
- Right,
- Left,
- KeypadUp,
- KeypadDown,
- KeypadRight,
- KeypadLeft,
- Home,
- End,
- Insert,
- Delete,
- PageUp,
- PageDown,
- F(u8),
-}
-
-impl Key {
- pub fn into_bytes(self) -> Vec<u8> {
- 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(),
- Escape => b"\x1b".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<u8>,
@@ -120,24 +13,8 @@ pub struct Input {
#[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() -> (Self, crate::RawGuard) {
+ (Self::new_without_raw(), crate::RawGuard::new())
}
pub fn new_without_raw() -> Self {
@@ -172,7 +49,7 @@ impl Input {
self.parse_single = parse;
}
- pub fn read_key(&mut self) -> std::io::Result<Option<Key>> {
+ pub fn read_key(&mut self) -> std::io::Result<Option<crate::Key>> {
if self.parse_single {
self.read_single_key()
} else {
@@ -188,9 +65,11 @@ impl Input {
if !prefix.is_empty() {
self.pos += prefix.len();
match std::string::String::from_utf8(prefix) {
- Ok(s) => return Ok(Some(Key::String(s))),
+ Ok(s) => return Ok(Some(crate::Key::String(s))),
Err(e) => {
- return Ok(Some(Key::Bytes(e.into_bytes())))
+ return Ok(Some(crate::Key::Bytes(
+ e.into_bytes(),
+ )))
}
}
}
@@ -213,12 +92,12 @@ impl Input {
.collect();
if !prefix.is_empty() {
self.pos += prefix.len();
- return Ok(Some(Key::Bytes(prefix)));
+ return Ok(Some(crate::Key::Bytes(prefix)));
}
self.read_single_key().map(|key| {
- if let Some(Key::Byte(c)) = key {
- Some(Key::Bytes(vec![c]))
+ if let Some(crate::Key::Byte(c)) = key {
+ Some(crate::Key::Bytes(vec![c]))
} else {
key
}
@@ -226,50 +105,52 @@ impl Input {
}
}
- fn read_single_key(&mut self) -> std::io::Result<Option<Key>> {
+ fn read_single_key(&mut self) -> std::io::Result<Option<crate::Key>> {
match self.getc(true)? {
- Some(0) => Ok(Some(Key::Byte(0))),
+ Some(0) => Ok(Some(crate::Key::Byte(0))),
Some(c @ 1..=26) => {
if self.parse_ctrl {
- Ok(Some(Key::Ctrl(b'a' + c - 1)))
+ Ok(Some(crate::Key::Ctrl(b'a' + c - 1)))
} else {
- Ok(Some(Key::Byte(c)))
+ Ok(Some(crate::Key::Byte(c)))
}
}
Some(27) => {
if self.parse_meta || self.parse_special_keys {
self.read_escape_sequence()
} else {
- Ok(Some(Key::Byte(27)))
+ Ok(Some(crate::Key::Byte(27)))
}
}
- Some(c @ 28..=31) => Ok(Some(Key::Byte(c))),
+ Some(c @ 28..=31) => Ok(Some(crate::Key::Byte(c))),
Some(c @ 32..=126) => {
if self.parse_utf8 {
- Ok(Some(Key::Char(c as char)))
+ Ok(Some(crate::Key::Char(c as char)))
} else {
- Ok(Some(Key::Byte(c)))
+ Ok(Some(crate::Key::Byte(c)))
}
}
Some(127) => {
if self.parse_special_keys {
- Ok(Some(Key::Backspace))
+ Ok(Some(crate::Key::Backspace))
} else {
- Ok(Some(Key::Byte(127)))
+ Ok(Some(crate::Key::Byte(127)))
}
}
Some(c @ 128..=255) => {
if self.parse_utf8 {
self.read_utf8_char(c)
} else {
- Ok(Some(Key::Byte(c)))
+ Ok(Some(crate::Key::Byte(c)))
}
}
None => Ok(None),
}
}
- fn read_escape_sequence(&mut self) -> std::io::Result<Option<Key>> {
+ fn read_escape_sequence(
+ &mut self,
+ ) -> std::io::Result<Option<crate::Key>> {
let mut seen = vec![b'\x1b'];
macro_rules! fail {
@@ -278,9 +159,9 @@ impl Input {
self.ungetc(c);
}
if self.parse_special_keys {
- return Ok(Some(Key::Escape));
+ return Ok(Some(crate::Key::Escape));
} else {
- return Ok(Some(Key::Byte(27)));
+ return Ok(Some(crate::Key::Byte(27)));
}
}};
}
@@ -323,7 +204,7 @@ impl Input {
}
b' '..=b'N' | b'P'..=b'Z' | b'\\'..=b'~' => {
if self.parse_meta {
- return Ok(Some(Key::Meta(c)));
+ return Ok(Some(crate::Key::Meta(c)));
} else {
fail!()
}
@@ -331,47 +212,47 @@ impl Input {
_ => fail!(),
},
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'A' => return Ok(Some(crate::Key::Up)),
+ b'B' => return Ok(Some(crate::Key::Down)),
+ b'C' => return Ok(Some(crate::Key::Right)),
+ b'D' => return Ok(Some(crate::Key::Left)),
+ b'H' => return Ok(Some(crate::Key::Home)),
+ b'F' => return Ok(Some(crate::Key::End)),
b'0'..=b'9' => param.push(c),
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))),
+ [b'2'] => return Ok(Some(crate::Key::Insert)),
+ [b'3'] => return Ok(Some(crate::Key::Delete)),
+ [b'5'] => return Ok(Some(crate::Key::PageUp)),
+ [b'6'] => return Ok(Some(crate::Key::PageDown)),
+ [b'1', b'5'] => return Ok(Some(crate::Key::F(5))),
+ [b'1', b'7'] => return Ok(Some(crate::Key::F(6))),
+ [b'1', b'8'] => return Ok(Some(crate::Key::F(7))),
+ [b'1', b'9'] => return Ok(Some(crate::Key::F(8))),
+ [b'2', b'0'] => return Ok(Some(crate::Key::F(9))),
+ [b'2', b'1'] => return Ok(Some(crate::Key::F(10))),
+ [b'2', b'3'] => return Ok(Some(crate::Key::F(11))),
+ [b'2', b'4'] => return Ok(Some(crate::Key::F(12))),
+ [b'2', b'5'] => return Ok(Some(crate::Key::F(13))),
+ [b'2', b'6'] => return Ok(Some(crate::Key::F(14))),
+ [b'2', b'8'] => return Ok(Some(crate::Key::F(15))),
+ [b'2', b'9'] => return Ok(Some(crate::Key::F(16))),
+ [b'3', b'1'] => return Ok(Some(crate::Key::F(17))),
+ [b'3', b'2'] => return Ok(Some(crate::Key::F(18))),
+ [b'3', b'3'] => return Ok(Some(crate::Key::F(19))),
+ [b'3', b'4'] => return Ok(Some(crate::Key::F(20))),
_ => fail!(),
},
_ => fail!(),
},
EscapeState::CKM => 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))),
+ b'A' => return Ok(Some(crate::Key::KeypadUp)),
+ b'B' => return Ok(Some(crate::Key::KeypadDown)),
+ b'C' => return Ok(Some(crate::Key::KeypadRight)),
+ b'D' => return Ok(Some(crate::Key::KeypadLeft)),
+ b'P' => return Ok(Some(crate::Key::F(1))),
+ b'Q' => return Ok(Some(crate::Key::F(2))),
+ b'R' => return Ok(Some(crate::Key::F(3))),
+ b'S' => return Ok(Some(crate::Key::F(4))),
_ => fail!(),
},
}
@@ -381,7 +262,7 @@ impl Input {
fn read_utf8_char(
&mut self,
initial: u8,
- ) -> std::io::Result<Option<Key>> {
+ ) -> std::io::Result<Option<crate::Key>> {
let mut buf = vec![initial];
macro_rules! fail {
@@ -389,7 +270,7 @@ impl Input {
for &c in buf.iter().skip(1).rev() {
self.ungetc(c);
}
- return Ok(Some(Key::Byte(initial)));
+ return Ok(Some(crate::Key::Byte(initial)));
}};
}
macro_rules! next_byte {
@@ -425,7 +306,7 @@ impl Input {
}
match std::string::String::from_utf8(buf) {
- Ok(s) => Ok(Some(Key::Char(s.chars().next().unwrap()))),
+ Ok(s) => Ok(Some(crate::Key::Char(s.chars().next().unwrap()))),
Err(e) => {
buf = e.into_bytes();
fail!()
diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs
index d4ffe4a..919b08f 100644
--- a/src/blocking/mod.rs
+++ b/src/blocking/mod.rs
@@ -1,4 +1,5 @@
pub(crate) mod input;
-pub use input::{Input, RawGuard};
+pub use crate::RawGuard;
+pub use input::Input;
mod output;
pub use output::{Output, ScreenGuard};
diff --git a/src/input.rs b/src/input.rs
new file mode 100644
index 0000000..cb4d0a0
--- /dev/null
+++ b/src/input.rs
@@ -0,0 +1,369 @@
+use futures_lite::io::AsyncReadExt as _;
+
+pub struct Input {
+ buf: Vec<u8>,
+ pos: usize,
+
+ parse_utf8: bool,
+ parse_ctrl: bool,
+ parse_meta: bool,
+ parse_special_keys: bool,
+ parse_single: bool,
+}
+
+#[allow(clippy::new_without_default)]
+impl Input {
+ pub fn new() -> (Self, crate::RawGuard) {
+ (Self::new_without_raw(), crate::RawGuard::new())
+ }
+
+ pub fn new_without_raw() -> Self {
+ Self {
+ buf: Vec::with_capacity(4096),
+ pos: 0,
+ parse_utf8: true,
+ parse_ctrl: true,
+ parse_meta: true,
+ parse_special_keys: true,
+ parse_single: true,
+ }
+ }
+
+ pub fn parse_utf8(&mut self, parse: bool) {
+ self.parse_utf8 = parse;
+ }
+
+ pub fn parse_ctrl(&mut self, parse: bool) {
+ self.parse_ctrl = parse;
+ }
+
+ pub fn parse_meta(&mut self, parse: bool) {
+ self.parse_meta = parse;
+ }
+
+ pub fn parse_special_keys(&mut self, parse: bool) {
+ self.parse_special_keys = parse;
+ }
+
+ pub fn parse_single(&mut self, parse: bool) {
+ self.parse_single = parse;
+ }
+
+ pub async fn read_key(&mut self) -> std::io::Result<Option<crate::Key>> {
+ if self.parse_single {
+ self.read_single_key().await
+ } else {
+ self.maybe_fill_buf().await?;
+ if self.parse_utf8 {
+ let prefix: Vec<_> = self
+ .buf
+ .iter()
+ .copied()
+ .skip(self.pos)
+ .take_while(|&c| matches!(c, 32..=126 | 128..=255))
+ .collect();
+ if !prefix.is_empty() {
+ self.pos += prefix.len();
+ match std::string::String::from_utf8(prefix) {
+ Ok(s) => return Ok(Some(crate::Key::String(s))),
+ Err(e) => {
+ return Ok(Some(crate::Key::Bytes(
+ e.into_bytes(),
+ )))
+ }
+ }
+ }
+ }
+
+ let prefix: Vec<_> = self
+ .buf
+ .iter()
+ .copied()
+ .skip(self.pos)
+ .take_while(|&c| match c {
+ 0 => true,
+ 1..=26 => !self.parse_ctrl,
+ 27 => !self.parse_meta && !self.parse_special_keys,
+ 28..=31 => true,
+ 32..=126 => true,
+ 127 => !self.parse_special_keys,
+ 128..=255 => true,
+ })
+ .collect();
+ if !prefix.is_empty() {
+ self.pos += prefix.len();
+ return Ok(Some(crate::Key::Bytes(prefix)));
+ }
+
+ self.read_single_key().await.map(|key| {
+ if let Some(crate::Key::Byte(c)) = key {
+ Some(crate::Key::Bytes(vec![c]))
+ } else {
+ key
+ }
+ })
+ }
+ }
+
+ async fn read_single_key(
+ &mut self,
+ ) -> std::io::Result<Option<crate::Key>> {
+ match self.getc(true).await? {
+ Some(0) => Ok(Some(crate::Key::Byte(0))),
+ Some(c @ 1..=26) => {
+ if self.parse_ctrl {
+ Ok(Some(crate::Key::Ctrl(b'a' + c - 1)))
+ } else {
+ Ok(Some(crate::Key::Byte(c)))
+ }
+ }
+ Some(27) => {
+ if self.parse_meta || self.parse_special_keys {
+ self.read_escape_sequence().await
+ } else {
+ Ok(Some(crate::Key::Byte(27)))
+ }
+ }
+ Some(c @ 28..=31) => Ok(Some(crate::Key::Byte(c))),
+ Some(c @ 32..=126) => {
+ if self.parse_utf8 {
+ Ok(Some(crate::Key::Char(c as char)))
+ } else {
+ Ok(Some(crate::Key::Byte(c)))
+ }
+ }
+ Some(127) => {
+ if self.parse_special_keys {
+ Ok(Some(crate::Key::Backspace))
+ } else {
+ Ok(Some(crate::Key::Byte(127)))
+ }
+ }
+ Some(c @ 128..=255) => {
+ if self.parse_utf8 {
+ self.read_utf8_char(c).await
+ } else {
+ Ok(Some(crate::Key::Byte(c)))
+ }
+ }
+ None => Ok(None),
+ }
+ }
+
+ async fn read_escape_sequence(
+ &mut self,
+ ) -> std::io::Result<Option<crate::Key>> {
+ let mut seen = vec![b'\x1b'];
+
+ macro_rules! fail {
+ () => {{
+ for &c in seen.iter().skip(1).rev() {
+ self.ungetc(c);
+ }
+ if self.parse_special_keys {
+ return Ok(Some(crate::Key::Escape));
+ } else {
+ return Ok(Some(crate::Key::Byte(27)));
+ }
+ }};
+ }
+ macro_rules! next_byte {
+ () => {
+ match self.getc(false).await? {
+ Some(c) => c,
+ None => {
+ fail!()
+ }
+ }
+ };
+ }
+
+ enum EscapeState {
+ Escape,
+ CSI(Vec<u8>),
+ CKM,
+ }
+
+ let mut state = EscapeState::Escape;
+ loop {
+ let c = next_byte!();
+ seen.push(c);
+ match state {
+ EscapeState::Escape => match c {
+ b'[' => {
+ if self.parse_special_keys {
+ state = EscapeState::CSI(vec![]);
+ } else {
+ fail!()
+ }
+ }
+ b'O' => {
+ if self.parse_special_keys {
+ state = EscapeState::CKM;
+ } else {
+ fail!()
+ }
+ }
+ b' '..=b'N' | b'P'..=b'Z' | b'\\'..=b'~' => {
+ if self.parse_meta {
+ return Ok(Some(crate::Key::Meta(c)));
+ } else {
+ fail!()
+ }
+ }
+ _ => fail!(),
+ },
+ EscapeState::CSI(ref mut param) => match c {
+ b'A' => return Ok(Some(crate::Key::Up)),
+ b'B' => return Ok(Some(crate::Key::Down)),
+ b'C' => return Ok(Some(crate::Key::Right)),
+ b'D' => return Ok(Some(crate::Key::Left)),
+ b'H' => return Ok(Some(crate::Key::Home)),
+ b'F' => return Ok(Some(crate::Key::End)),
+ b'0'..=b'9' => param.push(c),
+ b'~' => match param.as_slice() {
+ [b'2'] => return Ok(Some(crate::Key::Insert)),
+ [b'3'] => return Ok(Some(crate::Key::Delete)),
+ [b'5'] => return Ok(Some(crate::Key::PageUp)),
+ [b'6'] => return Ok(Some(crate::Key::PageDown)),
+ [b'1', b'5'] => return Ok(Some(crate::Key::F(5))),
+ [b'1', b'7'] => return Ok(Some(crate::Key::F(6))),
+ [b'1', b'8'] => return Ok(Some(crate::Key::F(7))),
+ [b'1', b'9'] => return Ok(Some(crate::Key::F(8))),
+ [b'2', b'0'] => return Ok(Some(crate::Key::F(9))),
+ [b'2', b'1'] => return Ok(Some(crate::Key::F(10))),
+ [b'2', b'3'] => return Ok(Some(crate::Key::F(11))),
+ [b'2', b'4'] => return Ok(Some(crate::Key::F(12))),
+ [b'2', b'5'] => return Ok(Some(crate::Key::F(13))),
+ [b'2', b'6'] => return Ok(Some(crate::Key::F(14))),
+ [b'2', b'8'] => return Ok(Some(crate::Key::F(15))),
+ [b'2', b'9'] => return Ok(Some(crate::Key::F(16))),
+ [b'3', b'1'] => return Ok(Some(crate::Key::F(17))),
+ [b'3', b'2'] => return Ok(Some(crate::Key::F(18))),
+ [b'3', b'3'] => return Ok(Some(crate::Key::F(19))),
+ [b'3', b'4'] => return Ok(Some(crate::Key::F(20))),
+ _ => fail!(),
+ },
+ _ => fail!(),
+ },
+ EscapeState::CKM => match c {
+ b'A' => return Ok(Some(crate::Key::KeypadUp)),
+ b'B' => return Ok(Some(crate::Key::KeypadDown)),
+ b'C' => return Ok(Some(crate::Key::KeypadRight)),
+ b'D' => return Ok(Some(crate::Key::KeypadLeft)),
+ b'P' => return Ok(Some(crate::Key::F(1))),
+ b'Q' => return Ok(Some(crate::Key::F(2))),
+ b'R' => return Ok(Some(crate::Key::F(3))),
+ b'S' => return Ok(Some(crate::Key::F(4))),
+ _ => fail!(),
+ },
+ }
+ }
+ }
+
+ async fn read_utf8_char(
+ &mut self,
+ initial: u8,
+ ) -> std::io::Result<Option<crate::Key>> {
+ let mut buf = vec![initial];
+
+ macro_rules! fail {
+ () => {{
+ for &c in buf.iter().skip(1).rev() {
+ self.ungetc(c);
+ }
+ return Ok(Some(crate::Key::Byte(initial)));
+ }};
+ }
+ macro_rules! next_byte {
+ () => {
+ match self.getc(true).await? {
+ Some(c) => {
+ if (0b1000_0000..=0b1011_1111).contains(&c) {
+ c
+ } else {
+ fail!()
+ }
+ }
+ 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!());
+ }
+ _ => fail!(),
+ }
+
+ match std::string::String::from_utf8(buf) {
+ Ok(s) => Ok(Some(crate::Key::Char(s.chars().next().unwrap()))),
+ Err(e) => {
+ buf = e.into_bytes();
+ fail!()
+ }
+ }
+ }
+
+ async fn getc(&mut self, fill: bool) -> std::io::Result<Option<u8>> {
+ if fill {
+ if !self.maybe_fill_buf().await? {
+ return Ok(None);
+ }
+ } else {
+ if self.buf_is_empty() {
+ return Ok(None);
+ }
+ }
+ let c = self.buf[self.pos];
+ self.pos += 1;
+ Ok(Some(c))
+ }
+
+ fn ungetc(&mut self, c: u8) {
+ if self.pos == 0 {
+ self.buf.insert(0, c);
+ } else {
+ self.pos -= 1;
+ self.buf[self.pos] = c;
+ }
+ }
+
+ async fn maybe_fill_buf(&mut self) -> std::io::Result<bool> {
+ if self.buf_is_empty() {
+ self.fill_buf().await
+ } else {
+ Ok(true)
+ }
+ }
+
+ fn buf_is_empty(&self) -> bool {
+ self.pos >= self.buf.len()
+ }
+
+ async fn fill_buf(&mut self) -> std::io::Result<bool> {
+ self.buf.resize(4096, 0);
+ self.pos = 0;
+ let bytes = read_stdin(&mut self.buf).await?;
+ if bytes == 0 {
+ return Ok(false);
+ }
+ self.buf.truncate(bytes);
+ Ok(true)
+ }
+}
+
+async fn read_stdin(buf: &mut [u8]) -> std::io::Result<usize> {
+ blocking::Unblock::new(std::io::stdin()).read(buf).await
+}
diff --git a/src/key.rs b/src/key.rs
new file mode 100644
index 0000000..8c97ad8
--- /dev/null
+++ b/src/key.rs
@@ -0,0 +1,79 @@
+#[derive(Eq, PartialEq, Debug, Clone)]
+pub enum Key {
+ String(String),
+ Char(char),
+ Bytes(Vec<u8>),
+ Byte(u8),
+ Ctrl(u8),
+ Meta(u8),
+ Backspace,
+ Escape,
+ Up,
+ Down,
+ Right,
+ Left,
+ KeypadUp,
+ KeypadDown,
+ KeypadRight,
+ KeypadLeft,
+ Home,
+ End,
+ Insert,
+ Delete,
+ PageUp,
+ PageDown,
+ F(u8),
+}
+
+impl Key {
+ pub fn into_bytes(self) -> Vec<u8> {
+ 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(),
+ Escape => b"\x1b".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![],
+ },
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 1023167..5fc2429 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,21 @@
#![allow(clippy::collapsible_if)]
-pub mod color;
-
pub mod blocking;
-pub use crate::blocking::input::Key;
+
+pub mod color;
+mod key;
+pub use key::Key;
+mod raw_guard;
+pub use raw_guard::RawGuard;
#[cfg(feature = "async")]
mod output;
#[cfg(feature = "async")]
pub use output::{Output, ScreenGuard};
+#[cfg(feature = "async")]
+mod input;
+#[cfg(feature = "async")]
+pub use input::Input;
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/raw_guard.rs b/src/raw_guard.rs
new file mode 100644
index 0000000..909ba9e
--- /dev/null
+++ b/src/raw_guard.rs
@@ -0,0 +1,45 @@
+use std::os::unix::io::AsRawFd as _;
+
+pub struct RawGuard {
+ termios: nix::sys::termios::Termios,
+ cleaned_up: bool,
+}
+
+impl RawGuard {
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ 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 {
+ termios,
+ cleaned_up: false,
+ }
+ }
+
+ 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();
+ }
+}