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]
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"]

View File

@ -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"] }

View File

@ -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<W: Write> Backend for CrosstermBackend<W> {
}
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<()> {

View File

@ -2,14 +2,18 @@ use crate::error;
pub fn get_backend<W: std::io::Write>(buf: W) -> error::Result<impl Backend> {
#[cfg(feature = "crossterm")]
CrosstermBackend::new(buf)
type Backend<W> = CrosstermBackend<W>;
#[cfg(feature = "termion")]
type Backend<W> = TermionBackend<W>;
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<B: Backend + ?Sized>(
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 {

View File

@ -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<W>
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<W: Write> {
buffer: RawTerminal<W>,
}
impl<W> TermionBackend<W>
where
W: Write,
{
pub fn new(stdout: W) -> TermionBackend<W> {
TermionBackend { stdout }
impl<W: Write> TermionBackend<W> {
pub fn new(buffer: W) -> error::Result<TermionBackend<W>> {
let buffer = buffer.into_raw_mode()?;
buffer.suspend_raw_mode()?;
Ok(TermionBackend { buffer })
}
}
impl<W> Write for TermionBackend<W>
where
W: Write,
{
impl<W: Write> Write for TermionBackend<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.stdout.write(buf)
self.buffer.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
self.buffer.flush()
}
}
impl<W> Backend for TermionBackend<W>
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<W: Write> Backend for TermionBackend<W> {
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<Item = (u16, u16, &'a Cell)>,
{
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<Rect> {
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<Size> {
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<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::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 crate::error;
pub fn next_event() -> error::Result<super::KeyEvent> {
loop {
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;
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<Stdin>,
}
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<KeyEvent>;
#[cfg(feature = "crossterm")]
fn next(&mut self) -> Option<Self::Item> {
#[cfg(feature = "crossterm")]
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")]
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);
}

View File

@ -194,12 +194,16 @@ impl<P: Prompt, B: Backend> Input<P, B> {
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<P, B: Backend> Input<P, B> {
#[allow(clippy::new_ret_no_self)]
/// Creates a new Input
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 {
prompt,
backend,