add termion backend

This commit is contained in:
Lutetium-Vanadium 2021-04-29 20:20:54 +05:30
parent 16f8e3ad52
commit 176a22b9c0
10 changed files with 372 additions and 228 deletions

View File

@ -29,7 +29,7 @@ async-std-dep = { package = "async-std", version = "1.9", optional = true, featu
[features] [features]
default = ["crossterm", "ahash"] default = ["crossterm", "ahash"]
crossterm = ["ui/crossterm"] crossterm = ["ui/crossterm"]
# termion = ["ui/termion"] termion = ["ui/termion"]
# curses = ["ui/curses"] # curses = ["ui/curses"]
tokio = ["tokio-dep", "async-trait", "ui/tokio"] tokio = ["tokio-dep", "async-trait", "ui/tokio"]
smol = ["smol-dep", "async-trait", "ui/smol"] smol = ["smol-dep", "async-trait", "ui/smol"]

View File

@ -13,8 +13,8 @@ async-trait = { version = "0.1", optional = true }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
crossterm = { version = "0.19", optional = true } crossterm = { version = "0.19", optional = true }
termion = { version = "1.5", optional = true }
# TODO: add all of these # TODO: add all of these
# termion = { version = "1.5", optional = true }
# easycurses = { version = "0.12", optional = true } # easycurses = { version = "0.12", optional = true }
# pancurses = { version = "0.16", optional = true, features = ["win32a"] } # pancurses = { version = "0.16", optional = true, features = ["win32a"] }

View File

@ -1,4 +1,7 @@
use std::io::{self, Write}; use std::{
cmp::Ordering,
io::{self, Write},
};
use crossterm::{ use crossterm::{
cursor, queue, cursor, queue,
@ -75,12 +78,16 @@ impl<W: Write> Backend for CrosstermBackend<W> {
} }
fn scroll(&mut self, dist: i32) -> error::Result<()> { fn scroll(&mut self, dist: i32) -> error::Result<()> {
if dist >= 0 { match dist.cmp(&0) {
queue!(self.buffer, terminal::ScrollDown(dist as u16))?; Ordering::Greater => {
} else { queue!(self.buffer, terminal::ScrollDown(dist as u16))
queue!(self.buffer, terminal::ScrollUp(-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<()> { fn set_attributes(&mut self, attributes: Attributes) -> error::Result<()> {

View File

@ -2,14 +2,18 @@ use crate::error;
pub fn get_backend<W: std::io::Write>(buf: W) -> error::Result<impl Backend> { pub fn get_backend<W: std::io::Write>(buf: W) -> error::Result<impl Backend> {
#[cfg(feature = "crossterm")] #[cfg(feature = "crossterm")]
CrosstermBackend::new(buf) type Backend<W> = CrosstermBackend<W>;
#[cfg(feature = "termion")]
type Backend<W> = TermionBackend<W>;
Backend::new(buf)
} }
mod style; mod style;
// #[cfg(feature = "termion")] #[cfg(feature = "termion")]
// mod termion; mod termion;
// #[cfg(feature = "termion")] #[cfg(feature = "termion")]
// pub use self::termion::TermionBackend; pub use self::termion::TermionBackend;
#[cfg(feature = "crossterm")] #[cfg(feature = "crossterm")]
mod crossterm; mod crossterm;
@ -85,6 +89,12 @@ fn default_move_cursor<B: Backend + ?Sized>(
backend: &mut B, backend: &mut B,
direction: MoveDirection, direction: MoveDirection,
) -> error::Result<()> { ) -> 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()?; let (mut x, mut y) = backend.get_cursor()?;
match direction { match direction {

View File

@ -1,135 +1,128 @@
use super::Backend;
use crate::{
buffer::Cell,
layout::Rect,
style::{Color, Modifier},
};
use std::{ use std::{
cmp::Ordering,
fmt, fmt,
io::{self, Write}, io::{self, Write},
}; };
pub struct TermionBackend<W> use termion::{
where clear, color, cursor,
W: Write, raw::{IntoRawMode, RawTerminal},
{ scroll, style,
stdout: W, };
use super::{Attributes, Backend, ClearType, Color, MoveDirection, Size};
use crate::error;
pub struct TermionBackend<W: Write> {
buffer: RawTerminal<W>,
} }
impl<W> TermionBackend<W> impl<W: Write> TermionBackend<W> {
where pub fn new(buffer: W) -> error::Result<TermionBackend<W>> {
W: Write, let buffer = buffer.into_raw_mode()?;
{ buffer.suspend_raw_mode()?;
pub fn new(stdout: W) -> TermionBackend<W> { Ok(TermionBackend { buffer })
TermionBackend { stdout }
} }
} }
impl<W> Write for TermionBackend<W> impl<W: Write> Write for TermionBackend<W> {
where
W: Write,
{
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stdout.write(buf) self.buffer.write(buf)
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
self.stdout.flush() self.buffer.flush()
} }
} }
impl<W> Backend for TermionBackend<W> impl<W: Write> Backend for TermionBackend<W> {
where fn enable_raw_mode(&mut self) -> error::Result<()> {
W: Write, self.buffer.activate_raw_mode().map_err(Into::into)
{
/// 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()
} }
/// Hides cursor fn disable_raw_mode(&mut self) -> error::Result<()> {
fn hide_cursor(&mut self) -> io::Result<()> { self.buffer.suspend_raw_mode().map_err(Into::into)
write!(self.stdout, "{}", termion::cursor::Hide)?;
self.stdout.flush()
} }
/// Shows cursor fn hide_cursor(&mut self) -> error::Result<()> {
fn show_cursor(&mut self) -> io::Result<()> { write!(self.buffer, "{}", cursor::Hide).map_err(Into::into)
write!(self.stdout, "{}", termion::cursor::Show)?;
self.stdout.flush()
} }
/// Gets cursor position (0-based index) fn show_cursor(&mut self) -> error::Result<()> {
fn get_cursor(&mut self) -> io::Result<(u16, u16)> { write!(self.buffer, "{}", cursor::Show).map_err(Into::into)
termion::cursor::DetectCursorPos::cursor_pos(&mut self.stdout) }
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(|(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) -> error::Result<()> {
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { write!(self.buffer, "{}", cursor::Goto(x + 1, y + 1)).map_err(Into::into)
write!(self.stdout, "{}", termion::cursor::Goto(x + 1, y + 1))?;
self.stdout.flush()
} }
fn draw<'a, I>(&mut self, content: I) -> io::Result<()> fn move_cursor(&mut self, direction: MoveDirection) -> error::Result<()> {
where match direction {
I: Iterator<Item = (u16, u16, &'a Cell)>, MoveDirection::Up(n) => write!(self.buffer, "{}", cursor::Up(n))?,
{ MoveDirection::Down(n) => write!(self.buffer, "{}", cursor::Down(n))?,
use std::fmt::Write; MoveDirection::Left(n) => write!(self.buffer, "{}", cursor::Left(n))?,
MoveDirection::Right(n) => write!(self.buffer, "{}", cursor::Right(n))?,
let mut string = String::with_capacity(content.size_hint().0 * 3); _ => super::default_move_cursor(self, direction)?,
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);
} }
write!(
self.stdout, Ok(())
"{}{}{}{}",
string,
Fg(Color::Reset),
Bg(Color::Reset),
termion::style::Reset,
)
} }
/// Return the size of the terminal fn scroll(&mut self, dist: i32) -> error::Result<()> {
fn size(&self) -> io::Result<Rect> { match dist.cmp(&0) {
let terminal = termion::terminal_size()?; Ordering::Greater => {
Ok(Rect::new(0, 0, terminal.0, terminal.1)) 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<()> { fn set_attributes(&mut self, attributes: Attributes) -> error::Result<()> {
self.stdout.flush() 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<Size> {
termion::terminal_size().map(Into::into).map_err(Into::into)
} }
} }
@ -137,128 +130,122 @@ struct Fg(Color);
struct Bg(Color); struct Bg(Color);
struct ModifierDiff {
from: Modifier,
to: Modifier,
}
impl fmt::Display for Fg { impl fmt::Display for Fg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use termion::color::Color as TermionColor; use color::Color as TermionColor;
match self.0 { match self.0 {
Color::Reset => termion::color::Reset.write_fg(f), Color::Reset => color::Reset.write_fg(f),
Color::Black => termion::color::Black.write_fg(f), Color::Black => color::Black.write_fg(f),
Color::Red => termion::color::Red.write_fg(f), Color::Red => color::Red.write_fg(f),
Color::Green => termion::color::Green.write_fg(f), Color::Green => color::Green.write_fg(f),
Color::Yellow => termion::color::Yellow.write_fg(f), Color::Yellow => color::Yellow.write_fg(f),
Color::Blue => termion::color::Blue.write_fg(f), Color::Blue => color::Blue.write_fg(f),
Color::Magenta => termion::color::Magenta.write_fg(f), Color::Magenta => color::Magenta.write_fg(f),
Color::Cyan => termion::color::Cyan.write_fg(f), Color::Cyan => color::Cyan.write_fg(f),
Color::Gray => termion::color::White.write_fg(f), Color::Grey => color::White.write_fg(f),
Color::DarkGray => termion::color::LightBlack.write_fg(f), Color::DarkGrey => color::LightBlack.write_fg(f),
Color::LightRed => termion::color::LightRed.write_fg(f), Color::LightRed => color::LightRed.write_fg(f),
Color::LightGreen => termion::color::LightGreen.write_fg(f), Color::LightGreen => color::LightGreen.write_fg(f),
Color::LightBlue => termion::color::LightBlue.write_fg(f), Color::LightBlue => color::LightBlue.write_fg(f),
Color::LightYellow => termion::color::LightYellow.write_fg(f), Color::LightYellow => color::LightYellow.write_fg(f),
Color::LightMagenta => termion::color::LightMagenta.write_fg(f), Color::LightMagenta => color::LightMagenta.write_fg(f),
Color::LightCyan => termion::color::LightCyan.write_fg(f), Color::LightCyan => color::LightCyan.write_fg(f),
Color::White => termion::color::LightWhite.write_fg(f), Color::White => color::LightWhite.write_fg(f),
Color::Indexed(i) => termion::color::AnsiValue(i).write_fg(f), Color::Ansi(i) => color::AnsiValue(i).write_fg(f),
Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_fg(f), Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_fg(f),
} }
} }
} }
impl fmt::Display for Bg { impl fmt::Display for Bg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use termion::color::Color as TermionColor; use color::Color as TermionColor;
match self.0 { match self.0 {
Color::Reset => termion::color::Reset.write_bg(f), Color::Reset => color::Reset.write_bg(f),
Color::Black => termion::color::Black.write_bg(f), Color::Black => color::Black.write_bg(f),
Color::Red => termion::color::Red.write_bg(f), Color::Red => color::Red.write_bg(f),
Color::Green => termion::color::Green.write_bg(f), Color::Green => color::Green.write_bg(f),
Color::Yellow => termion::color::Yellow.write_bg(f), Color::Yellow => color::Yellow.write_bg(f),
Color::Blue => termion::color::Blue.write_bg(f), Color::Blue => color::Blue.write_bg(f),
Color::Magenta => termion::color::Magenta.write_bg(f), Color::Magenta => color::Magenta.write_bg(f),
Color::Cyan => termion::color::Cyan.write_bg(f), Color::Cyan => color::Cyan.write_bg(f),
Color::Gray => termion::color::White.write_bg(f), Color::Grey => color::White.write_bg(f),
Color::DarkGray => termion::color::LightBlack.write_bg(f), Color::DarkGrey => color::LightBlack.write_bg(f),
Color::LightRed => termion::color::LightRed.write_bg(f), Color::LightRed => color::LightRed.write_bg(f),
Color::LightGreen => termion::color::LightGreen.write_bg(f), Color::LightGreen => color::LightGreen.write_bg(f),
Color::LightBlue => termion::color::LightBlue.write_bg(f), Color::LightBlue => color::LightBlue.write_bg(f),
Color::LightYellow => termion::color::LightYellow.write_bg(f), Color::LightYellow => color::LightYellow.write_bg(f),
Color::LightMagenta => termion::color::LightMagenta.write_bg(f), Color::LightMagenta => color::LightMagenta.write_bg(f),
Color::LightCyan => termion::color::LightCyan.write_bg(f), Color::LightCyan => color::LightCyan.write_bg(f),
Color::White => termion::color::LightWhite.write_bg(f), Color::White => color::LightWhite.write_bg(f),
Color::Indexed(i) => termion::color::AnsiValue(i).write_bg(f), Color::Ansi(i) => color::AnsiValue(i).write_bg(f),
Color::Rgb(r, g, b) => termion::color::Rgb(r, g, b).write_bg(f), Color::Rgb(r, g, b) => color::Rgb(r, g, b).write_bg(f),
} }
} }
} }
impl fmt::Display for ModifierDiff { fn set_attributes<W: Write>(attributes: Attributes, mut w: W) -> error::Result<()> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if attributes.contains(Attributes::RESET) {
let remove = self.from - self.to; write!(w, "{}", style::Reset)?;
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(())
} }
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<W: Write>(
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(())
} }

View File

@ -1,6 +1,7 @@
use crate::error;
use crossterm::event; use crossterm::event;
use crate::error;
pub fn next_event() -> error::Result<super::KeyEvent> { pub fn next_event() -> error::Result<super::KeyEvent> {
loop { loop {
if let event::Event::Key(k) = event::read()? { if let event::Event::Key(k) = event::read()? {

View File

@ -1,3 +1,6 @@
#[cfg(feature = "termion")]
use std::io::{stdin, Stdin};
use crate::error; use crate::error;
crate::cfg_async! { crate::cfg_async! {
@ -14,6 +17,8 @@ pub use win::AsyncEvents;
#[cfg(feature = "crossterm")] #[cfg(feature = "crossterm")]
mod crossterm; mod crossterm;
#[cfg(feature = "termion")]
mod termion;
bitflags::bitflags! { bitflags::bitflags! {
/// Represents key modifiers (shift, control, alt). /// Represents key modifiers (shift, control, alt).
@ -93,20 +98,44 @@ pub enum KeyCode {
Esc, Esc,
} }
#[derive(Default)] pub struct Events {
pub struct Events {} #[cfg(feature = "termion")]
events: ::termion::input::Keys<Stdin>,
}
impl Events { 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 { pub fn new() -> Self {
Self {} Self {}
} }
} }
impl Default for Events {
fn default() -> Self {
Self::new()
}
}
impl Iterator for Events { impl Iterator for Events {
type Item = error::Result<KeyEvent>; type Item = error::Result<KeyEvent>;
#[cfg(feature = "crossterm")]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
#[cfg(feature = "crossterm")]
Some(self::crossterm::next_event()) Some(self::crossterm::next_event())
} }
#[cfg(feature = "termion")]
fn next(&mut self) -> Option<Self::Item> {
Some(self::termion::next_event(&mut self.events))
}
} }

104
ui/src/events/termion.rs Normal file
View File

@ -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<Stdin>,
) -> error::Result<super::KeyEvent> {
let e = events.next().unwrap()?;
e.try_into()
}
impl TryFrom<event::Key> for super::KeyEvent {
type Error = error::ErrorKind;
fn try_from(key: event::Key) -> error::Result<super::KeyEvent> {
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<super::KeyEvent> {
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))
}

View File

@ -30,7 +30,6 @@ impl AsyncEvents {
}) })
} }
// #[cfg_attr(feature = "tokio", allow(clippy::unnecessary_wraps))]
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
fn try_get_events(&mut self, cx: &mut Context<'_>) -> std::io::Result<()> { fn try_get_events(&mut self, cx: &mut Context<'_>) -> std::io::Result<()> {
#[cfg(nightly)] #[cfg(nightly)]
@ -45,7 +44,7 @@ impl AsyncEvents {
let tty = Pin::new(&mut self.tty); 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); self.parser.advance(buf.filled(), buf.remaining() == 0);
} }

View File

@ -194,12 +194,16 @@ impl<P: Prompt, B: Backend> Input<P, B> {
if e.modifiers.contains(KeyModifiers::CONTROL) => if e.modifiers.contains(KeyModifiers::CONTROL) =>
{ {
self.exit()?; 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( return Err(
io::Error::new(io::ErrorKind::Other, "CTRL+C").into() io::Error::new(io::ErrorKind::Other, "CTRL+C").into()
); );
} }
KeyCode::Null => { KeyCode::Null => {
self.exit()?; 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( return Err(
io::Error::new(io::ErrorKind::UnexpectedEof, "EOF").into() io::Error::new(io::ErrorKind::UnexpectedEof, "EOF").into()
); );
@ -234,6 +238,9 @@ impl<P, B: Backend> Input<P, B> {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
/// Creates a new Input /// Creates a new Input
pub fn new(prompt: P, backend: &mut B) -> Input<P, &mut B> { pub fn new(prompt: P, backend: &mut B) -> Input<P, &mut B> {
// 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 { Input {
prompt, prompt,
backend, backend,