diff --git a/Cargo.toml b/Cargo.toml index 01302b4..d818ee9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ async-std-dep = { package = "async-std", version = "1.9", optional = true, featu [features] default = ["crossterm", "ahash"] crossterm = ["ui/crossterm"] -# termion = ["ui/termion"] +termion = ["ui/termion"] # curses = ["ui/curses"] tokio = ["tokio-dep", "async-trait", "ui/tokio"] smol = ["smol-dep", "async-trait", "ui/smol"] diff --git a/ui/Cargo.toml b/ui/Cargo.toml index bd614a0..c738159 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -13,8 +13,8 @@ async-trait = { version = "0.1", optional = true } futures = { version = "0.3", optional = true } crossterm = { version = "0.19", optional = true } +termion = { version = "1.5", optional = true } # TODO: add all of these -# termion = { version = "1.5", optional = true } # easycurses = { version = "0.12", optional = true } # pancurses = { version = "0.16", optional = true, features = ["win32a"] } diff --git a/ui/src/backend/crossterm.rs b/ui/src/backend/crossterm.rs index 0eb1208..7d0b548 100644 --- a/ui/src/backend/crossterm.rs +++ b/ui/src/backend/crossterm.rs @@ -1,4 +1,7 @@ -use std::io::{self, Write}; +use std::{ + cmp::Ordering, + io::{self, Write}, +}; use crossterm::{ cursor, queue, @@ -75,12 +78,16 @@ impl Backend for CrosstermBackend { } fn scroll(&mut self, dist: i32) -> error::Result<()> { - if dist >= 0 { - queue!(self.buffer, terminal::ScrollDown(dist as u16))?; - } else { - queue!(self.buffer, terminal::ScrollUp(-dist as u16))?; + match dist.cmp(&0) { + Ordering::Greater => { + queue!(self.buffer, terminal::ScrollDown(dist as u16)) + } + Ordering::Less => { + queue!(self.buffer, terminal::ScrollUp(-dist as u16)) + } + Ordering::Equal => Ok(()), } - Ok(()) + .map_err(Into::into) } fn set_attributes(&mut self, attributes: Attributes) -> error::Result<()> { diff --git a/ui/src/backend/mod.rs b/ui/src/backend/mod.rs index 43f3b4c..d56fada 100644 --- a/ui/src/backend/mod.rs +++ b/ui/src/backend/mod.rs @@ -2,14 +2,18 @@ use crate::error; pub fn get_backend(buf: W) -> error::Result { #[cfg(feature = "crossterm")] - CrosstermBackend::new(buf) + type Backend = CrosstermBackend; + #[cfg(feature = "termion")] + type Backend = TermionBackend; + + Backend::new(buf) } mod style; -// #[cfg(feature = "termion")] -// mod termion; -// #[cfg(feature = "termion")] -// pub use self::termion::TermionBackend; +#[cfg(feature = "termion")] +mod termion; +#[cfg(feature = "termion")] +pub use self::termion::TermionBackend; #[cfg(feature = "crossterm")] mod crossterm; @@ -85,6 +89,12 @@ fn default_move_cursor( backend: &mut B, direction: MoveDirection, ) -> error::Result<()> { + // There are a lot of `MoveDirection::NextLine(1)`, so this will slightly speed up + // the rendering process as the cursor doesn't need to be gotten every time + if let MoveDirection::NextLine(1) = direction { + return write!(backend, "\n\r").map_err(Into::into); + } + let (mut x, mut y) = backend.get_cursor()?; match direction { diff --git a/ui/src/backend/termion.rs b/ui/src/backend/termion.rs index d09e7f2..b92772f 100644 --- a/ui/src/backend/termion.rs +++ b/ui/src/backend/termion.rs @@ -1,135 +1,128 @@ -use super::Backend; -use crate::{ - buffer::Cell, - layout::Rect, - style::{Color, Modifier}, -}; use std::{ + cmp::Ordering, fmt, io::{self, Write}, }; -pub struct TermionBackend -where - W: Write, -{ - stdout: W, +use termion::{ + clear, color, cursor, + raw::{IntoRawMode, RawTerminal}, + scroll, style, +}; + +use super::{Attributes, Backend, ClearType, Color, MoveDirection, Size}; +use crate::error; + +pub struct TermionBackend { + buffer: RawTerminal, } -impl TermionBackend -where - W: Write, -{ - pub fn new(stdout: W) -> TermionBackend { - TermionBackend { stdout } +impl TermionBackend { + pub fn new(buffer: W) -> error::Result> { + let buffer = buffer.into_raw_mode()?; + buffer.suspend_raw_mode()?; + Ok(TermionBackend { buffer }) } } -impl Write for TermionBackend -where - W: Write, -{ +impl Write for TermionBackend { fn write(&mut self, buf: &[u8]) -> io::Result { - self.stdout.write(buf) + self.buffer.write(buf) } fn flush(&mut self) -> io::Result<()> { - self.stdout.flush() + self.buffer.flush() } } -impl Backend for TermionBackend -where - W: Write, -{ - /// Clears the entire screen and move the cursor to the top left of the screen - fn clear(&mut self) -> io::Result<()> { - write!(self.stdout, "{}", termion::clear::All)?; - write!(self.stdout, "{}", termion::cursor::Goto(1, 1))?; - self.stdout.flush() +impl Backend for TermionBackend { + fn enable_raw_mode(&mut self) -> error::Result<()> { + self.buffer.activate_raw_mode().map_err(Into::into) } - /// Hides cursor - fn hide_cursor(&mut self) -> io::Result<()> { - write!(self.stdout, "{}", termion::cursor::Hide)?; - self.stdout.flush() + fn disable_raw_mode(&mut self) -> error::Result<()> { + self.buffer.suspend_raw_mode().map_err(Into::into) } - /// Shows cursor - fn show_cursor(&mut self) -> io::Result<()> { - write!(self.stdout, "{}", termion::cursor::Show)?; - self.stdout.flush() + fn hide_cursor(&mut self) -> error::Result<()> { + write!(self.buffer, "{}", cursor::Hide).map_err(Into::into) } - /// Gets cursor position (0-based index) - fn get_cursor(&mut self) -> io::Result<(u16, u16)> { - termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout) + fn show_cursor(&mut self) -> error::Result<()> { + write!(self.buffer, "{}", cursor::Show).map_err(Into::into) + } + + fn get_cursor(&mut self) -> error::Result<(u16, u16)> { + cursor::DetectCursorPos::cursor_pos(&mut self.buffer) + // 0 index the position .map(|(x, y)| (x - 1, y - 1)) + .map_err(Into::into) } - /// Sets cursor position (0-based index) - fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { - write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?; - self.stdout.flush() + fn set_cursor(&mut self, x: u16, y: u16) -> error::Result<()> { + write!(self.buffer, "{}", cursor::Goto(x + 1, y + 1)).map_err(Into::into) } - fn draw<'a, I>(&mut self, content: I) -> io::Result<()> - where - I: Iterator, - { - use std::fmt::Write; - - let mut string = String::with_capacity(content.size_hint().0 * 3); - let mut fg = Color::Reset; - let mut bg = Color::Reset; - let mut modifier = Modifier::empty(); - let mut last_pos: Option<(u16, u16)> = None; - for (x, y, cell) in content { - // Move the cursor if the previous location was not (x - 1, y) - if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) { - write!(string, "{}", termion::cursor::Goto(x + 1, y + 1)).unwrap(); - } - last_pos = Some((x, y)); - if cell.modifier != modifier { - write!( - string, - "{}", - ModifierDiff { - from: modifier, - to: cell.modifier - } - ) - .unwrap(); - modifier = cell.modifier; - } - if cell.fg != fg { - write!(string, "{}", Fg(cell.fg)).unwrap(); - fg = cell.fg; - } - if cell.bg != bg { - write!(string, "{}", Bg(cell.bg)).unwrap(); - bg = cell.bg; - } - string.push_str(&cell.symbol); + fn move_cursor(&mut self, direction: MoveDirection) -> error::Result<()> { + match direction { + MoveDirection::Up(n) => write!(self.buffer, "{}", cursor::Up(n))?, + MoveDirection::Down(n) => write!(self.buffer, "{}", cursor::Down(n))?, + MoveDirection::Left(n) => write!(self.buffer, "{}", cursor::Left(n))?, + MoveDirection::Right(n) => write!(self.buffer, "{}", cursor::Right(n))?, + _ => super::default_move_cursor(self, direction)?, } - write!( - self.stdout, - "{}{}{}{}", - string, - Fg(Color::Reset), - Bg(Color::Reset), - termion::style::Reset, - ) + + Ok(()) } - /// Return the size of the terminal - fn size(&self) -> io::Result { - let terminal = termion::terminal_size()?; - Ok(Rect::new(0, 0, terminal.0, terminal.1)) + fn scroll(&mut self, dist: i32) -> error::Result<()> { + match dist.cmp(&0) { + Ordering::Greater => { + write!(self.buffer, "{}", scroll::Down(dist as u16)) + } + Ordering::Less => { + write!(self.buffer, "{}", scroll::Up(-dist as u16)) + } + Ordering::Equal => Ok(()), + } + .map_err(Into::into) } - fn flush(&mut self) -> io::Result<()> { - self.stdout.flush() + fn set_attributes(&mut self, attributes: Attributes) -> error::Result<()> { + set_attributes(attributes, &mut self.buffer) + } + + fn remove_attributes(&mut self, attributes: Attributes) -> error::Result<()> { + remove_attributes(attributes, &mut self.buffer) + } + + fn set_fg(&mut self, color: Color) -> error::Result<()> { + write!(self.buffer, "{}", Fg(color)).map_err(Into::into) + } + + fn set_bg(&mut self, color: Color) -> error::Result<()> { + write!(self.buffer, "{}", Bg(color)).map_err(Into::into) + } + + fn clear(&mut self, clear_type: ClearType) -> error::Result<()> { + match clear_type { + ClearType::All => write!(self.buffer, "{}", clear::All), + ClearType::FromCursorDown => { + write!(self.buffer, "{}", clear::AfterCursor) + } + ClearType::FromCursorUp => { + write!(self.buffer, "{}", clear::BeforeCursor) + } + ClearType::CurrentLine => write!(self.buffer, "{}", clear::CurrentLine), + ClearType::UntilNewLine => { + write!(self.buffer, "{}", clear::UntilNewline) + } + } + .map_err(Into::into) + } + + fn size(&self) -> error::Result { + termion::terminal_size().map(Into::into).map_err(Into::into) } } @@ -137,128 +130,122 @@ struct Fg(Color); struct Bg(Color); -struct ModifierDiff { - from: Modifier, - to: Modifier, -} - impl fmt::Display for Fg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use termion::color::Color as TermionColor; + use color::Color as TermionColor; match self.0 { - Color::Reset => termion::color::Reset.write_fg(f), - Color::Black => termion::color::Black.write_fg(f), - Color::Red => termion::color::Red.write_fg(f), - Color::Green => termion::color::Green.write_fg(f), - Color::Yellow => termion::color::Yellow.write_fg(f), - Color::Blue => termion::color::Blue.write_fg(f), - Color::Magenta => termion::color::Magenta.write_fg(f), - Color::Cyan => termion::color::Cyan.write_fg(f), - Color::Gray => termion::color::White.write_fg(f), - Color::DarkGray => termion::color::LightBlack.write_fg(f), - Color::LightRed => termion::color::LightRed.write_fg(f), - Color::LightGreen => termion::color::LightGreen.write_fg(f), - Color::LightBlue => termion::color::LightBlue.write_fg(f), - Color::LightYellow => termion::color::LightYellow.write_fg(f), - Color::LightMagenta => termion::color::LightMagenta.write_fg(f), - Color::LightCyan => termion::color::LightCyan.write_fg(f), - Color::White => termion::color::LightWhite.write_fg(f), - Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f), - Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f), + Color::Reset => color::Reset.write_fg(f), + Color::Black => color::Black.write_fg(f), + Color::Red => color::Red.write_fg(f), + Color::Green => color::Green.write_fg(f), + Color::Yellow => color::Yellow.write_fg(f), + Color::Blue => color::Blue.write_fg(f), + Color::Magenta => color::Magenta.write_fg(f), + Color::Cyan => color::Cyan.write_fg(f), + Color::Grey => color::White.write_fg(f), + Color::DarkGrey => color::LightBlack.write_fg(f), + Color::LightRed => color::LightRed.write_fg(f), + Color::LightGreen => color::LightGreen.write_fg(f), + Color::LightBlue => color::LightBlue.write_fg(f), + Color::LightYellow => color::LightYellow.write_fg(f), + Color::LightMagenta => color::LightMagenta.write_fg(f), + Color::LightCyan => color::LightCyan.write_fg(f), + Color::White => color::LightWhite.write_fg(f), + Color::Ansi(i) => color::AnsiValue(i).write_fg(f), + Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_fg(f), } } } impl fmt::Display for Bg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use termion::color::Color as TermionColor; + use color::Color as TermionColor; match self.0 { - Color::Reset => termion::color::Reset.write_bg(f), - Color::Black => termion::color::Black.write_bg(f), - Color::Red => termion::color::Red.write_bg(f), - Color::Green => termion::color::Green.write_bg(f), - Color::Yellow => termion::color::Yellow.write_bg(f), - Color::Blue => termion::color::Blue.write_bg(f), - Color::Magenta => termion::color::Magenta.write_bg(f), - Color::Cyan => termion::color::Cyan.write_bg(f), - Color::Gray => termion::color::White.write_bg(f), - Color::DarkGray => termion::color::LightBlack.write_bg(f), - Color::LightRed => termion::color::LightRed.write_bg(f), - Color::LightGreen => termion::color::LightGreen.write_bg(f), - Color::LightBlue => termion::color::LightBlue.write_bg(f), - Color::LightYellow => termion::color::LightYellow.write_bg(f), - Color::LightMagenta => termion::color::LightMagenta.write_bg(f), - Color::LightCyan => termion::color::LightCyan.write_bg(f), - Color::White => termion::color::LightWhite.write_bg(f), - Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f), - Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f), + Color::Reset => color::Reset.write_bg(f), + Color::Black => color::Black.write_bg(f), + Color::Red => color::Red.write_bg(f), + Color::Green => color::Green.write_bg(f), + Color::Yellow => color::Yellow.write_bg(f), + Color::Blue => color::Blue.write_bg(f), + Color::Magenta => color::Magenta.write_bg(f), + Color::Cyan => color::Cyan.write_bg(f), + Color::Grey => color::White.write_bg(f), + Color::DarkGrey => color::LightBlack.write_bg(f), + Color::LightRed => color::LightRed.write_bg(f), + Color::LightGreen => color::LightGreen.write_bg(f), + Color::LightBlue => color::LightBlue.write_bg(f), + Color::LightYellow => color::LightYellow.write_bg(f), + Color::LightMagenta => color::LightMagenta.write_bg(f), + Color::LightCyan => color::LightCyan.write_bg(f), + Color::White => color::LightWhite.write_bg(f), + Color::Ansi(i) => color::AnsiValue(i).write_bg(f), + Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_bg(f), } } } -impl fmt::Display for ModifierDiff { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let remove = self.from - self.to; - if remove.contains(Modifier::REVERSED) { - write!(f, "{}", termion::style::NoInvert)?; - } - if remove.contains(Modifier::BOLD) { - // XXX: the termion NoBold flag actually enables double-underline on ECMA-48 compliant - // terminals, and NoFaint additionally disables bold... so we use this trick to get - // the right semantics. - write!(f, "{}", termion::style::NoFaint)?; - - if self.to.contains(Modifier::DIM) { - write!(f, "{}", termion::style::Faint)?; - } - } - if remove.contains(Modifier::ITALIC) { - write!(f, "{}", termion::style::NoItalic)?; - } - if remove.contains(Modifier::UNDERLINED) { - write!(f, "{}", termion::style::NoUnderline)?; - } - if remove.contains(Modifier::DIM) { - write!(f, "{}", termion::style::NoFaint)?; - - // XXX: the NoFaint flag additionally disables bold as well, so we need to re-enable it - // here if we want it. - if self.to.contains(Modifier::BOLD) { - write!(f, "{}", termion::style::Bold)?; - } - } - if remove.contains(Modifier::CROSSED_OUT) { - write!(f, "{}", termion::style::NoCrossedOut)?; - } - if remove.contains(Modifier::SLOW_BLINK) - || remove.contains(Modifier::RAPID_BLINK) - { - write!(f, "{}", termion::style::NoBlink)?; - } - - let add = self.to - self.from; - if add.contains(Modifier::REVERSED) { - write!(f, "{}", termion::style::Invert)?; - } - if add.contains(Modifier::BOLD) { - write!(f, "{}", termion::style::Bold)?; - } - if add.contains(Modifier::ITALIC) { - write!(f, "{}", termion::style::Italic)?; - } - if add.contains(Modifier::UNDERLINED) { - write!(f, "{}", termion::style::Underline)?; - } - if add.contains(Modifier::DIM) { - write!(f, "{}", termion::style::Faint)?; - } - if add.contains(Modifier::CROSSED_OUT) { - write!(f, "{}", termion::style::CrossedOut)?; - } - if add.contains(Modifier::SLOW_BLINK) || add.contains(Modifier::RAPID_BLINK) - { - write!(f, "{}", termion::style::Blink)?; - } - - Ok(()) +fn set_attributes(attributes: Attributes, mut w: W) -> error::Result<()> { + if attributes.contains(Attributes::RESET) { + write!(w, "{}", style::Reset)?; } + + if attributes.contains(Attributes::REVERSED) { + write!(w, "{}", style::Invert)?; + } + if attributes.contains(Attributes::BOLD) { + write!(w, "{}", style::Bold)?; + } + if attributes.contains(Attributes::ITALIC) { + write!(w, "{}", style::Italic)?; + } + if attributes.contains(Attributes::UNDERLINED) { + write!(w, "{}", style::Underline)?; + } + if attributes.contains(Attributes::DIM) { + write!(w, "{}", style::Faint)?; + } + if attributes.contains(Attributes::CROSSED_OUT) { + write!(w, "{}", style::CrossedOut)?; + } + if attributes.contains(Attributes::SLOW_BLINK) + || attributes.contains(Attributes::RAPID_BLINK) + { + write!(w, "{}", style::Blink)?; + } + + Ok(()) +} + +fn remove_attributes( + attributes: Attributes, + mut w: W, +) -> error::Result<()> { + if attributes.contains(Attributes::RESET) { + write!(w, "{}", style::Reset)?; + } + + if attributes.contains(Attributes::REVERSED) { + write!(w, "{}", style::NoInvert)?; + } + if attributes.contains(Attributes::BOLD) { + write!(w, "{}", style::NoBold)?; + } + if attributes.contains(Attributes::ITALIC) { + write!(w, "{}", style::NoItalic)?; + } + if attributes.contains(Attributes::UNDERLINED) { + write!(w, "{}", style::NoUnderline)?; + } + if attributes.contains(Attributes::DIM) { + write!(w, "{}", style::NoFaint)?; + } + if attributes.contains(Attributes::CROSSED_OUT) { + write!(w, "{}", style::NoCrossedOut)?; + } + if attributes.contains(Attributes::SLOW_BLINK) + || attributes.contains(Attributes::RAPID_BLINK) + { + write!(w, "{}", style::NoBlink)?; + } + + Ok(()) } diff --git a/ui/src/events/crossterm.rs b/ui/src/events/crossterm.rs index dfd0bb1..d4be0d9 100644 --- a/ui/src/events/crossterm.rs +++ b/ui/src/events/crossterm.rs @@ -1,6 +1,7 @@ -use crate::error; use crossterm::event; +use crate::error; + pub fn next_event() -> error::Result { loop { if let event::Event::Key(k) = event::read()? { diff --git a/ui/src/events/mod.rs b/ui/src/events/mod.rs index c2d6565..35433f1 100644 --- a/ui/src/events/mod.rs +++ b/ui/src/events/mod.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "termion")] +use std::io::{stdin, Stdin}; + use crate::error; crate::cfg_async! { @@ -14,6 +17,8 @@ pub use win::AsyncEvents; #[cfg(feature = "crossterm")] mod crossterm; +#[cfg(feature = "termion")] +mod termion; bitflags::bitflags! { /// Represents key modifiers (shift, control, alt). @@ -93,20 +98,44 @@ pub enum KeyCode { Esc, } -#[derive(Default)] -pub struct Events {} +pub struct Events { + #[cfg(feature = "termion")] + events: ::termion::input::Keys, +} impl Events { + #[cfg(feature = "termion")] + pub fn new() -> Self { + #[rustfmt::skip] + use ::termion::input::TermRead; + + Self { + events: stdin().keys(), + } + } + + #[cfg(not(feature = "termion"))] pub fn new() -> Self { Self {} } } +impl Default for Events { + fn default() -> Self { + Self::new() + } +} + impl Iterator for Events { type Item = error::Result; + #[cfg(feature = "crossterm")] fn next(&mut self) -> Option { - #[cfg(feature = "crossterm")] Some(self::crossterm::next_event()) } + + #[cfg(feature = "termion")] + fn next(&mut self) -> Option { + Some(self::termion::next_event(&mut self.events)) + } } diff --git a/ui/src/events/termion.rs b/ui/src/events/termion.rs new file mode 100644 index 0000000..88b00d9 --- /dev/null +++ b/ui/src/events/termion.rs @@ -0,0 +1,104 @@ +use std::{ + convert::{TryFrom, TryInto}, + io::{self, Stdin}, +}; + +use termion::{event, input}; + +use crate::error; + +pub fn next_event( + events: &mut input::Keys, +) -> error::Result { + let e = events.next().unwrap()?; + e.try_into() +} + +impl TryFrom for super::KeyEvent { + type Error = error::ErrorKind; + + fn try_from(key: event::Key) -> error::Result { + let key = match key { + event::Key::Backspace => super::KeyCode::Backspace.into(), + event::Key::Left => super::KeyCode::Left.into(), + event::Key::Right => super::KeyCode::Right.into(), + event::Key::Up => super::KeyCode::Up.into(), + event::Key::Down => super::KeyCode::Down.into(), + event::Key::Home => super::KeyCode::Home.into(), + event::Key::End => super::KeyCode::End.into(), + event::Key::PageUp => super::KeyCode::PageUp.into(), + event::Key::PageDown => super::KeyCode::PageDown.into(), + event::Key::BackTab => super::KeyCode::BackTab.into(), + event::Key::Delete => super::KeyCode::Delete.into(), + event::Key::Insert => super::KeyCode::Insert.into(), + event::Key::F(n) => super::KeyCode::F(n).into(), + event::Key::Char('\n') => super::KeyCode::Enter.into(), + event::Key::Char('\t') => super::KeyCode::Tab.into(), + event::Key::Char(c) => super::KeyCode::Char(c).into(), + event::Key::Alt(c) => parse_char(c, super::KeyModifiers::ALT)?, + event::Key::Ctrl(c) => parse_char(c, super::KeyModifiers::CONTROL)?, + event::Key::Null => super::KeyCode::Null.into(), + event::Key::Esc => super::KeyCode::Esc.into(), + _ => unreachable!(), + }; + + Ok(key) + } +} + +fn parse_char( + mut c: char, + mut modifiers: super::KeyModifiers, +) -> error::Result { + let code = loop { + if c as u32 >= 256 { + break super::KeyCode::Char(c); + } + + let k = match event::parse_event(c as u8, &mut std::iter::empty())? { + event::Event::Key(k) => k, + _ => match char::try_from(c as u32) { + Ok(c) => break super::KeyCode::Char(c), + Err(_) => { + return Err(io::Error::new( + io::ErrorKind::Other, + "Could not parse an event", + ) + .into()) + } + }, + }; + + match k { + event::Key::Backspace => break super::KeyCode::Backspace, + event::Key::Left => break super::KeyCode::Left, + event::Key::Right => break super::KeyCode::Right, + event::Key::Up => break super::KeyCode::Up, + event::Key::Down => break super::KeyCode::Down, + event::Key::Home => break super::KeyCode::Home, + event::Key::End => break super::KeyCode::End, + event::Key::PageUp => break super::KeyCode::PageUp, + event::Key::PageDown => break super::KeyCode::PageDown, + event::Key::BackTab => break super::KeyCode::BackTab, + event::Key::Delete => break super::KeyCode::Delete, + event::Key::Insert => break super::KeyCode::Insert, + event::Key::F(n) => break super::KeyCode::F(n), + event::Key::Char('\n') => break super::KeyCode::Enter, + event::Key::Char('\t') => break super::KeyCode::Tab, + event::Key::Char(c) => break super::KeyCode::Char(c), + event::Key::Alt(new_c) => { + modifiers |= super::KeyModifiers::ALT; + c = new_c + } + event::Key::Ctrl(new_c) => { + modifiers |= super::KeyModifiers::CONTROL; + c = new_c + } + event::Key::Null => break super::KeyCode::Null, + event::Key::Esc => break super::KeyCode::Esc, + _ => unreachable!(), + } + }; + + Ok(super::KeyEvent::new(code, modifiers)) +} diff --git a/ui/src/events/unix.rs b/ui/src/events/unix.rs index 12d1e39..0987430 100644 --- a/ui/src/events/unix.rs +++ b/ui/src/events/unix.rs @@ -30,7 +30,6 @@ impl AsyncEvents { }) } - // #[cfg_attr(feature = "tokio", allow(clippy::unnecessary_wraps))] #[cfg(feature = "tokio")] fn try_get_events(&mut self, cx: &mut Context<'_>) -> std::io::Result<()> { #[cfg(nightly)] @@ -45,7 +44,7 @@ impl AsyncEvents { let tty = Pin::new(&mut self.tty); - if tty.poll_read(cx, &mut buf[..]).is_ready() { + if tty.poll_read(cx, &mut buf).is_ready() { self.parser.advance(buf.filled(), buf.remaining() == 0); } diff --git a/ui/src/sync_input.rs b/ui/src/sync_input.rs index 76f6782..c18295e 100644 --- a/ui/src/sync_input.rs +++ b/ui/src/sync_input.rs @@ -194,12 +194,16 @@ impl Input { if e.modifiers.contains(KeyModifiers::CONTROL) => { self.exit()?; + // The exit handler does not return the never (`!`) type, as a + // custom exit handler does not have to exit the program return Err( io::Error::new(io::ErrorKind::Other, "CTRL+C").into() ); } KeyCode::Null => { self.exit()?; + // The exit handler does not return the never (`!`) type, as a + // custom exit handler does not have to exit the program return Err( io::Error::new(io::ErrorKind::UnexpectedEof, "EOF").into() ); @@ -234,6 +238,9 @@ impl Input { #[allow(clippy::new_ret_no_self)] /// Creates a new Input pub fn new(prompt: P, backend: &mut B) -> Input { + // The method doesn't return self directly, as its always used with a `&mut B`, + // and this tells the compiler that it doesn't need to consume the `&mut B`, but + // once the Input goes out of scope, it can be used again Input { prompt, backend,