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]
|
[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"]
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()? {
|
||||||
|
|
|
@ -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
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")]
|
#[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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user