mirror of
https://github.com/ratatui/ratatui.git
synced 2025-09-28 05:21:23 +00:00
feat(backend): bump crossterm to 0.13
* removed flush calls because execute already calls flush under the hood. * moved some static functions into From traits * removed useless clone in demo * upgrade to crossterm 0.13 * map all errors
This commit is contained in:
parent
2233cdc9cc
commit
e4873e4da9
@ -30,7 +30,7 @@ unicode-segmentation = "1.2"
|
||||
unicode-width = "0.1"
|
||||
termion = { version = "1.5" , optional = true }
|
||||
rustbox = { version = "0.11", optional = true }
|
||||
crossterm = { version = "^0.10", optional = true }
|
||||
crossterm = { version = "0.13", optional = true }
|
||||
easycurses = { version = "0.12.2", optional = true }
|
||||
pancurses = { version = "0.16.1", optional = true, features = ["win32a"] }
|
||||
|
||||
|
@ -7,7 +7,10 @@ use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm::{input, AlternateScreen, InputEvent, KeyEvent};
|
||||
use crossterm::{
|
||||
input::{input, InputEvent, KeyEvent},
|
||||
screen::AlternateScreen,
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use tui::backend::CrosstermBackend;
|
||||
use tui::Terminal;
|
||||
@ -43,10 +46,10 @@ fn main() -> Result<(), failure::Error> {
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let input = input();
|
||||
let reader = input.read_sync();
|
||||
for event in reader {
|
||||
match event {
|
||||
InputEvent::Keyboard(key) => {
|
||||
let mut reader = input.read_sync();
|
||||
loop {
|
||||
match reader.next() {
|
||||
Some(InputEvent::Keyboard(key)) => {
|
||||
if let Err(_) = tx.send(Event::Input(key.clone())) {
|
||||
return;
|
||||
}
|
||||
@ -59,16 +62,14 @@ fn main() -> Result<(), failure::Error> {
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
|
||||
thread::spawn(move || {
|
||||
let tx = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let tx = tx.clone();
|
||||
loop {
|
||||
tx.send(Event::Tick).unwrap();
|
||||
thread::sleep(Duration::from_millis(cli.tick_rate));
|
||||
}
|
||||
});
|
||||
}
|
||||
loop {
|
||||
tx.send(Event::Tick).unwrap();
|
||||
thread::sleep(Duration::from_millis(cli.tick_rate));
|
||||
}
|
||||
});
|
||||
|
||||
let mut app = App::new("Crossterm Demo");
|
||||
|
||||
|
@ -4,19 +4,23 @@ use std::{
|
||||
};
|
||||
|
||||
use crossterm::{
|
||||
execute, queue, terminal, Clear, ClearType, Crossterm, ErrorKind, Goto, Hide, SetAttr, SetBg,
|
||||
SetFg, Show,
|
||||
cursor::{Hide, MoveTo, Show},
|
||||
execute, queue,
|
||||
screen::AlternateScreen,
|
||||
style::{
|
||||
Attribute as CAttribute, Color as CColor, SetAttribute, SetBackgroundColor,
|
||||
SetForegroundColor,
|
||||
},
|
||||
terminal::{self, Clear, ClearType},
|
||||
Output,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
buffer::Cell,
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
};
|
||||
use crate::backend::Backend;
|
||||
use crate::style::{Color, Modifier};
|
||||
use crate::{buffer::Cell, layout::Rect, style};
|
||||
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
alternate_screen: Option<crossterm::AlternateScreen>,
|
||||
alternate_screen: Option<AlternateScreen>,
|
||||
stdout: W,
|
||||
}
|
||||
|
||||
@ -33,7 +37,7 @@ where
|
||||
|
||||
pub fn with_alternate_screen(
|
||||
stdout: W,
|
||||
alternate_screen: crossterm::AlternateScreen,
|
||||
alternate_screen: AlternateScreen,
|
||||
) -> Result<CrosstermBackend<W>, io::Error> {
|
||||
Ok(CrosstermBackend {
|
||||
alternate_screen: Some(alternate_screen),
|
||||
@ -41,30 +45,12 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub fn alternate_screen(&self) -> Option<&crossterm::AlternateScreen> {
|
||||
pub fn alternate_screen(&self) -> Option<&AlternateScreen> {
|
||||
match &self.alternate_screen {
|
||||
Some(alt_screen) => Some(&alt_screen),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crossterm(&self) -> crossterm::Crossterm {
|
||||
Crossterm::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_error(error: ErrorKind) -> io::Error {
|
||||
match error {
|
||||
ErrorKind::IoError(err) => err,
|
||||
ErrorKind::FmtError(err) => {
|
||||
io::Error::new(io::ErrorKind::Other, format!("Invalid formatting: {}", err))
|
||||
}
|
||||
ErrorKind::ResizingTerminalFailure(err) => io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to resize terminal: {}", err),
|
||||
),
|
||||
_ => io::Error::new(io::ErrorKind::Other, "Unknown crossterm error"),
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> Write for CrosstermBackend<W>
|
||||
@ -84,53 +70,21 @@ impl<W> Backend for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
execute!(self.stdout, Clear(ClearType::All)).map_err(convert_error)
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
execute!(self.stdout, Hide).map_err(convert_error)
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
execute!(self.stdout, Show).map_err(convert_error)
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
let cursor = crossterm::cursor();
|
||||
Ok(cursor.pos())
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
execute!(self.stdout, Goto(x, y)).map_err(convert_error)
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let terminal = terminal();
|
||||
let (width, height) = terminal.terminal_size();
|
||||
// crossterm reports max 0-based col/row index instead of count
|
||||
Ok(Rect::new(0, 0, width, height))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.stdout.flush()
|
||||
}
|
||||
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
use std::fmt::Write;
|
||||
use fmt::Write;
|
||||
|
||||
let mut string = String::with_capacity(content.size_hint().0 * 3);
|
||||
let mut style = Style::default();
|
||||
let mut style = style::Style::default();
|
||||
let mut last_y = 0;
|
||||
let mut last_x = 0;
|
||||
let mut inst = 0;
|
||||
|
||||
for (x, y, cell) in content {
|
||||
if y != last_y || x != last_x + 1 || inst == 0 {
|
||||
queue!(string, Goto(x, y))?;
|
||||
map_error(queue!(string, MoveTo(x, y)))?;
|
||||
}
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
@ -144,14 +98,14 @@ where
|
||||
style.modifier = cell.style.modifier;
|
||||
}
|
||||
if cell.style.fg != style.fg {
|
||||
let color = to_crossterm_color(cell.style.fg);
|
||||
queue!(string, SetFg(color))?;
|
||||
let color = CColor::from(cell.style.fg);
|
||||
map_error(queue!(string, SetForegroundColor(color)))?;
|
||||
style.fg = cell.style.fg;
|
||||
inst += 1;
|
||||
}
|
||||
if cell.style.bg != style.bg {
|
||||
let color = to_crossterm_color(cell.style.bg);
|
||||
queue!(string, SetBg(color))?;
|
||||
let color = CColor::from(cell.style.bg);
|
||||
map_error(queue!(string, SetBackgroundColor(color)))?;
|
||||
style.bg = cell.style.bg;
|
||||
inst += 1;
|
||||
}
|
||||
@ -160,18 +114,75 @@ where
|
||||
inst += 1;
|
||||
}
|
||||
|
||||
write!(
|
||||
map_error(queue!(
|
||||
self.stdout,
|
||||
"{}{}{}{}",
|
||||
string,
|
||||
SetFg(crossterm::Color::Reset),
|
||||
SetBg(crossterm::Color::Reset),
|
||||
SetAttr(crossterm::Attribute::Reset)
|
||||
)?;
|
||||
Output(string),
|
||||
SetForegroundColor(CColor::Reset),
|
||||
SetBackgroundColor(CColor::Reset),
|
||||
SetAttribute(CAttribute::Reset)
|
||||
))
|
||||
}
|
||||
|
||||
Crossterm::new().color().reset()?;
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
map_error(execute!(self.stdout, Hide))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn show_cursor(&mut self) -> io::Result<()> {
|
||||
map_error(execute!(self.stdout, Show))
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
crossterm::cursor::position()
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
map_error(execute!(self.stdout, MoveTo(x, y)))
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
map_error(execute!(self.stdout, Clear(ClearType::All)))
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let (width, height) =
|
||||
terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
|
||||
Ok(Rect::new(0, 0, width, height))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
<CrosstermBackend<W> as Write>::flush(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn map_error(error: crossterm::Result<()>) -> io::Result<()> {
|
||||
error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
|
||||
}
|
||||
|
||||
impl From<Color> for CColor {
|
||||
fn from(color: Color) -> Self {
|
||||
match color {
|
||||
Color::Reset => CColor::Reset,
|
||||
Color::Black => CColor::Black,
|
||||
Color::Red => CColor::DarkRed,
|
||||
Color::Green => CColor::DarkGreen,
|
||||
Color::Yellow => CColor::DarkYellow,
|
||||
Color::Blue => CColor::DarkBlue,
|
||||
Color::Magenta => CColor::DarkMagenta,
|
||||
Color::Cyan => CColor::DarkCyan,
|
||||
Color::Gray => CColor::Grey,
|
||||
Color::DarkGray => CColor::DarkGrey,
|
||||
Color::LightRed => CColor::Red,
|
||||
Color::LightGreen => CColor::Green,
|
||||
Color::LightBlue => CColor::Blue,
|
||||
Color::LightYellow => CColor::Yellow,
|
||||
Color::LightMagenta => CColor::Magenta,
|
||||
Color::LightCyan => CColor::Cyan,
|
||||
Color::White => CColor::White,
|
||||
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,57 +198,57 @@ impl ModifierDiff {
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
use crossterm::Attribute;
|
||||
//use crossterm::Attribute;
|
||||
let removed = self.from - self.to;
|
||||
if removed.contains(Modifier::REVERSED) {
|
||||
queue!(w, SetAttr(Attribute::NoInverse))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?;
|
||||
}
|
||||
if removed.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttr(Attribute::NormalIntensity))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
||||
if self.to.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttr(Attribute::Dim))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
||||
}
|
||||
}
|
||||
if removed.contains(Modifier::ITALIC) {
|
||||
queue!(w, SetAttr(Attribute::NoItalic))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?;
|
||||
}
|
||||
if removed.contains(Modifier::UNDERLINED) {
|
||||
queue!(w, SetAttr(Attribute::NoUnderline))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
|
||||
}
|
||||
if removed.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttr(Attribute::NormalIntensity))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
||||
}
|
||||
if removed.contains(Modifier::CROSSED_OUT) {
|
||||
queue!(w, SetAttr(Attribute::NotCrossedOut))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?;
|
||||
}
|
||||
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
|
||||
queue!(w, SetAttr(Attribute::NoBlink))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?;
|
||||
}
|
||||
|
||||
let added = self.to - self.from;
|
||||
if added.contains(Modifier::REVERSED) {
|
||||
queue!(w, SetAttr(Attribute::Reverse))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?;
|
||||
}
|
||||
if added.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttr(Attribute::Bold))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
|
||||
}
|
||||
if added.contains(Modifier::ITALIC) {
|
||||
queue!(w, SetAttr(Attribute::Italic))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Italic)))?;
|
||||
}
|
||||
if added.contains(Modifier::UNDERLINED) {
|
||||
queue!(w, SetAttr(Attribute::Underlined))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
|
||||
}
|
||||
if added.contains(Modifier::DIM) {
|
||||
queue!(w, SetAttr(Attribute::Dim))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Dim)))?;
|
||||
}
|
||||
if added.contains(Modifier::CROSSED_OUT) {
|
||||
queue!(w, SetAttr(Attribute::CrossedOut))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?;
|
||||
}
|
||||
if added.contains(Modifier::SLOW_BLINK) {
|
||||
queue!(w, SetAttr(Attribute::SlowBlink))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?;
|
||||
}
|
||||
if added.contains(Modifier::RAPID_BLINK) {
|
||||
queue!(w, SetAttr(Attribute::RapidBlink))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -250,47 +261,21 @@ impl ModifierDiff {
|
||||
where
|
||||
W: fmt::Write,
|
||||
{
|
||||
use crossterm::Attribute;
|
||||
|
||||
let removed = self.from - self.to;
|
||||
if removed.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttr(Attribute::NormalIntensity))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?;
|
||||
}
|
||||
if removed.contains(Modifier::UNDERLINED) {
|
||||
queue!(w, SetAttr(Attribute::NoUnderline))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?;
|
||||
}
|
||||
|
||||
let added = self.to - self.from;
|
||||
if added.contains(Modifier::BOLD) {
|
||||
queue!(w, SetAttr(Attribute::Bold))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Bold)))?;
|
||||
}
|
||||
if added.contains(Modifier::UNDERLINED) {
|
||||
queue!(w, SetAttr(Attribute::Underlined))?;
|
||||
map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn to_crossterm_color(color: Color) -> crossterm::Color {
|
||||
match color {
|
||||
Color::Reset => crossterm::Color::Reset,
|
||||
Color::Black => crossterm::Color::Black,
|
||||
Color::Red => crossterm::Color::DarkRed,
|
||||
Color::Green => crossterm::Color::DarkGreen,
|
||||
Color::Yellow => crossterm::Color::DarkYellow,
|
||||
Color::Blue => crossterm::Color::DarkBlue,
|
||||
Color::Magenta => crossterm::Color::DarkMagenta,
|
||||
Color::Cyan => crossterm::Color::DarkCyan,
|
||||
Color::Gray => crossterm::Color::Grey,
|
||||
Color::DarkGray => crossterm::Color::DarkGrey,
|
||||
Color::LightRed => crossterm::Color::Red,
|
||||
Color::LightGreen => crossterm::Color::Green,
|
||||
Color::LightBlue => crossterm::Color::Blue,
|
||||
Color::LightYellow => crossterm::Color::Yellow,
|
||||
Color::LightMagenta => crossterm::Color::Magenta,
|
||||
Color::LightCyan => crossterm::Color::Cyan,
|
||||
Color::White => crossterm::Color::White,
|
||||
Color::Indexed(i) => crossterm::Color::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => crossterm::Color::Rgb { r, g, b },
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ pub struct RustboxBackend {
|
||||
|
||||
impl RustboxBackend {
|
||||
pub fn new() -> Result<RustboxBackend, rustbox::InitError> {
|
||||
let rustbox = r#try!(rustbox::RustBox::init(Default::default()));
|
||||
let rustbox = rustbox::RustBox::init(Default::default())?;
|
||||
Ok(RustboxBackend { rustbox })
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user