add termion backend
This commit is contained in:
parent
16f8e3ad52
commit
176a22b9c0
|
@ -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"]
|
||||
|
|
|
@ -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"] }
|
||||
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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()? {
|
||||
|
|
|
@ -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
104
ui/src/events/termion.rs
Normal 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))
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue
Block a user