From 6a1114b2419e75bc32eb2accac346f536d46ad76 Mon Sep 17 00:00:00 2001 From: Koxiaet Date: Wed, 30 Dec 2020 17:50:44 +0000 Subject: [PATCH 1/5] Support taking any Display in SetTitle (#528) --- src/terminal.rs | 8 ++++---- src/terminal/ansi.rs | 5 ++++- src/terminal/sys/windows.rs | 20 +++++++++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/terminal.rs b/src/terminal.rs index 4d52112..43a6c41 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -338,16 +338,16 @@ impl Command for SetSize { /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetTitle<'a>(pub &'a str); +pub struct SetTitle(pub T); -impl<'a> Command for SetTitle<'a> { +impl Command for SetTitle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_title_ansi_sequence(f, self.0) + ansi::set_title_ansi_sequence(f, &self.0) } #[cfg(windows)] fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> { - sys::set_window_title(self.0) + sys::set_window_title(&self.0) } } diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs index 427509a..7e091aa 100644 --- a/src/terminal/ansi.rs +++ b/src/terminal/ansi.rs @@ -30,6 +30,9 @@ pub(crate) fn set_size_csi_sequence( write!(f, csi!("8;{};{}t"), height, width) } -pub(crate) fn set_title_ansi_sequence(f: &mut impl fmt::Write, title: &str) -> fmt::Result { +pub(crate) fn set_title_ansi_sequence( + f: &mut impl fmt::Write, + title: impl fmt::Display, +) -> fmt::Result { write!(f, "\x1B]0;{}\x07", title) } diff --git a/src/terminal/sys/windows.rs b/src/terminal/sys/windows.rs index a5b04a6..483ea3f 100644 --- a/src/terminal/sys/windows.rs +++ b/src/terminal/sys/windows.rs @@ -1,4 +1,7 @@ //! WinAPI related logic for terminal manipulation. + +use std::fmt::{self, Write}; + use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size}; use winapi::{ shared::minwindef::DWORD, @@ -190,9 +193,20 @@ pub(crate) fn set_size(width: u16, height: u16) -> Result<()> { Ok(()) } -pub(crate) fn set_window_title(title: &str) -> Result<()> { - let mut title: Vec<_> = title.encode_utf16().collect(); - title.push(0); +pub(crate) fn set_window_title(title: impl fmt::Display) -> Result<()> { + struct Utf16Encoder(Vec); + impl Write for Utf16Encoder { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.0.extend(s.encode_utf16()); + Ok(()) + } + } + + let mut title_utf16 = Utf16Encoder(Vec::new()); + write!(title_utf16, "{}", title).expect("formatting failed"); + title_utf16.0.push(0); + let title = title_utf16.0; + let result = unsafe { SetConsoleTitleW(title.as_ptr()) }; if result != 0 { Ok(()) From 6c0f8ebcf6b864f00b5b4e8a68b18f31c39a5a97 Mon Sep 17 00:00:00 2001 From: Koxiaet Date: Sat, 2 Jan 2021 14:24:34 +0000 Subject: [PATCH 2/5] Fix clippy warnings (#533) --- examples/event-read.rs | 10 +++------- src/event/sys/unix/parse.rs | 2 +- src/terminal/sys/unix.rs | 25 +++++++++++-------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/examples/event-read.rs b/examples/event-read.rs index 2ac2345..7de5f42 100644 --- a/examples/event-read.rs +++ b/examples/event-read.rs @@ -50,13 +50,9 @@ fn print_events() -> Result<()> { fn flush_resize_events(event: Event) -> ((u16, u16), (u16, u16)) { if let Event::Resize(x, y) = event { let mut last_resize = (x, y); - loop { - if let Ok(true) = poll(Duration::from_millis(50)) { - if let Ok(Event::Resize(x, y)) = read() { - last_resize = (x, y); - } - } else { - break; + while let Ok(true) = poll(Duration::from_millis(50)) { + if let Ok(Event::Resize(x, y)) = read() { + last_resize = (x, y); } } diff --git a/src/event/sys/unix/parse.rs b/src/event/sys/unix/parse.rs index d3a7f2c..d3dd221 100644 --- a/src/event/sys/unix/parse.rs +++ b/src/event/sys/unix/parse.rs @@ -156,7 +156,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { // The final byte of a CSI sequence can be in the range 64-126, so // let's keep reading anything else. let last_byte = *buffer.last().unwrap(); - if last_byte < 64 || last_byte > 126 { + if !(64..=126).contains(&last_byte) { None } else { match buffer[buffer.len() - 1] { diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index 4da3a88..18c626d 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -87,20 +87,17 @@ pub(crate) fn disable_raw_mode() -> Result<()> { /// /// The arg should be "cols" or "lines" fn tput_value(arg: &str) -> Option { - match process::Command::new("tput").arg(arg).output() { - Ok(process::Output { stdout, .. }) => { - let value = stdout - .iter() - .map(|&b| b as u16) - .take_while(|&b| b >= 48 && b <= 58) - .fold(0, |v, b| v * 10 + (b - 48)); - if value > 0 { - Some(value) - } else { - None - } - } - _ => None, + let output = process::Command::new("tput").arg(arg).output().ok()?; + let value = output + .stdout + .into_iter() + .filter_map(|b| char::from(b).to_digit(10)) + .fold(0, |v, n| v * 10 + n as u16); + + if value > 0 { + Some(value) + } else { + None } } From b442de72bed0943e14a70638d2456616f1e0979e Mon Sep 17 00:00:00 2001 From: joyal-mathew <76829818+joyal-mathew@users.noreply.github.com> Date: Sat, 2 Jan 2021 09:26:26 -0500 Subject: [PATCH 3/5] Fixed typo in src/cursor.rs (#531) --- src/cursor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cursor.rs b/src/cursor.rs index c212498..9491a1f 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -73,7 +73,7 @@ impl Command for MoveTo { } } -/// A command that moves the terminal cursor up the given number of lines, +/// A command that moves the terminal cursor down the given number of lines, /// and moves it to the first column. /// /// # Notes @@ -93,7 +93,7 @@ impl Command for MoveToNextLine { } } -/// A command that moves the terminal cursor down the given number of lines, +/// A command that moves the terminal cursor up the given number of lines, /// and moves it to the first column. /// /// # Notes From df0c2358fd0c1bbf0f380fcfb59b2ef909b033c3 Mon Sep 17 00:00:00 2001 From: Koxiaet Date: Sun, 3 Jan 2021 13:29:29 +0000 Subject: [PATCH 4/5] Inline ansi modules (#529) --- src/cursor.rs | 43 +++-- src/cursor/ansi.rs | 62 ------- src/event.rs | 25 ++- src/event/ansi.rs | 25 --- src/style.rs | 28 ++- src/style/ansi.rs | 341 ------------------------------------- src/style/types/color.rs | 68 ++++++++ src/style/types/colored.rs | 237 +++++++++++++++++++++++++- src/terminal.rs | 35 ++-- src/terminal/ansi.rs | 38 ----- 10 files changed, 391 insertions(+), 511 deletions(-) delete mode 100644 src/cursor/ansi.rs delete mode 100644 src/event/ansi.rs delete mode 100644 src/style/ansi.rs delete mode 100644 src/terminal/ansi.rs diff --git a/src/cursor.rs b/src/cursor.rs index 9491a1f..ee93ed0 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -46,11 +46,10 @@ use std::fmt; #[cfg(windows)] use crate::Result; -use crate::{impl_display, Command}; +use crate::{csi, impl_display, Command}; pub use sys::position; -mod ansi; pub(crate) mod sys; /// A command that moves the terminal cursor to the given position (column, row). @@ -64,7 +63,7 @@ pub struct MoveTo(pub u16, pub u16); impl Command for MoveTo { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_to_csi_sequence(f, self.0, self.1) + write!(f, csi!("{};{}H"), self.1 + 1, self.0 + 1) } #[cfg(windows)] @@ -84,7 +83,7 @@ pub struct MoveToNextLine(pub u16); impl Command for MoveToNextLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_to_next_line_csi_sequence(f, self.0) + write!(f, csi!("{}E"), self.0) } #[cfg(windows)] @@ -104,7 +103,7 @@ pub struct MoveToPreviousLine(pub u16); impl Command for MoveToPreviousLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_to_previous_line_csi_sequence(f, self.0) + write!(f, csi!("{}F"), self.0) } #[cfg(windows)] @@ -123,7 +122,7 @@ pub struct MoveToColumn(pub u16); impl Command for MoveToColumn { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_to_column_csi_sequence(f, self.0) + write!(f, csi!("{}G"), self.0) } #[cfg(windows)] @@ -142,7 +141,10 @@ pub struct MoveUp(pub u16); impl Command for MoveUp { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_up_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}A"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -161,7 +163,10 @@ pub struct MoveRight(pub u16); impl Command for MoveRight { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_right_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}C"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -180,7 +185,10 @@ pub struct MoveDown(pub u16); impl Command for MoveDown { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_down_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}B"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -199,7 +207,10 @@ pub struct MoveLeft(pub u16); impl Command for MoveLeft { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::move_left_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}D"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -221,7 +232,7 @@ pub struct SavePosition; impl Command for SavePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::SAVE_POSITION_CSI_SEQUENCE) + f.write_str("\x1B7") } #[cfg(windows)] @@ -243,7 +254,7 @@ pub struct RestorePosition; impl Command for RestorePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::RESTORE_POSITION_CSI_SEQUENCE) + f.write_str("\x1B8") } #[cfg(windows)] @@ -262,7 +273,7 @@ pub struct Hide; impl Command for Hide { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::HIDE_CSI_SEQUENCE) + f.write_str(csi!("?25l")) } #[cfg(windows)] @@ -281,7 +292,7 @@ pub struct Show; impl Command for Show { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::SHOW_CSI_SEQUENCE) + f.write_str(csi!("?25h")) } #[cfg(windows)] @@ -301,7 +312,7 @@ pub struct EnableBlinking; impl Command for EnableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::ENABLE_BLINKING_CSI_SEQUENCE) + f.write_str(csi!("?12h")) } #[cfg(windows)] @@ -321,7 +332,7 @@ pub struct DisableBlinking; impl Command for DisableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::DISABLE_BLINKING_CSI_SEQUENCE) + f.write_str(csi!("?12l")) } #[cfg(windows)] diff --git a/src/cursor/ansi.rs b/src/cursor/ansi.rs deleted file mode 100644 index c3c6b91..0000000 --- a/src/cursor/ansi.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! This module provides cursor related ANSI escape codes. - -use crate::csi; -use std::fmt; - -pub(crate) fn move_to_csi_sequence(f: &mut impl fmt::Write, x: u16, y: u16) -> fmt::Result { - write!(f, csi!("{};{}H"), y + 1, x + 1) -} - -pub(crate) fn move_up_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - if count != 0 { - write!(f, csi!("{}A"), count) - } else { - Ok(()) - } -} - -pub(crate) fn move_right_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - if count != 0 { - write!(f, csi!("{}C"), count) - } else { - Ok(()) - } -} - -pub(crate) fn move_down_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - if count != 0 { - write!(f, csi!("{}B"), count) - } else { - Ok(()) - } -} - -pub(crate) fn move_left_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - if count != 0 { - write!(f, csi!("{}D"), count) - } else { - Ok(()) - } -} - -pub(crate) fn move_to_column_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - write!(f, csi!("{}G"), count) -} - -pub(crate) fn move_to_previous_line_csi_sequence( - f: &mut impl fmt::Write, - count: u16, -) -> fmt::Result { - write!(f, csi!("{}F"), count) -} - -pub(crate) fn move_to_next_line_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - write!(f, csi!("{}E"), count) -} - -pub(crate) const SAVE_POSITION_CSI_SEQUENCE: &str = "\x1B7"; -pub(crate) const RESTORE_POSITION_CSI_SEQUENCE: &str = "\x1B8"; -pub(crate) const HIDE_CSI_SEQUENCE: &str = csi!("?25l"); -pub(crate) const SHOW_CSI_SEQUENCE: &str = csi!("?25h"); -pub(crate) const ENABLE_BLINKING_CSI_SEQUENCE: &str = csi!("?12h"); -pub(crate) const DISABLE_BLINKING_CSI_SEQUENCE: &str = csi!("?12l"); diff --git a/src/event.rs b/src/event.rs index ebb12b1..2cce657 100644 --- a/src/event.rs +++ b/src/event.rs @@ -82,14 +82,13 @@ use serde::{Deserialize, Serialize}; use bitflags::bitflags; use lazy_static::lazy_static; -use crate::{Command, Result}; +use crate::{csi, Command, Result}; use filter::{EventFilter, Filter}; #[cfg(feature = "event-stream")] pub use stream::EventStream; use timeout::PollTimeout; -mod ansi; pub(crate) mod filter; mod read; mod source; @@ -230,7 +229,18 @@ pub struct EnableMouseCapture; impl Command for EnableMouseCapture { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::ENABLE_MOUSE_MODE_CSI_SEQUENCE) + f.write_str(concat!( + // Normal tracking: Send mouse X & Y on button press and release + csi!("?1000h"), + // Button-event tracking: Report button motion events (dragging) + csi!("?1002h"), + // Any-event tracking: Report all motion events + csi!("?1003h"), + // RXVT mouse mode: Allows mouse coordinates of >223 + csi!("?1015h"), + // SGR mouse mode: Allows mouse coordinates of >223, preferred over RXVT mode + csi!("?1006h"), + )) } #[cfg(windows)] @@ -252,7 +262,14 @@ pub struct DisableMouseCapture; impl Command for DisableMouseCapture { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::DISABLE_MOUSE_MODE_CSI_SEQUENCE) + f.write_str(concat!( + // The inverse commands of EnableMouseCapture, in reverse order. + csi!("?1006l"), + csi!("?1015l"), + csi!("?1003l"), + csi!("?1002l"), + csi!("?1000l"), + )) } #[cfg(windows)] diff --git a/src/event/ansi.rs b/src/event/ansi.rs deleted file mode 100644 index b125950..0000000 --- a/src/event/ansi.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! This module provides input related ANSI escape codes. - -use crate::csi; - -pub(crate) const ENABLE_MOUSE_MODE_CSI_SEQUENCE: &str = concat!( - // Normal tracking: Send mouse X & Y on button press and release - csi!("?1000h"), - // Button-event tracking: Report button motion events (dragging) - csi!("?1002h"), - // Any-event tracking: Report all motion events - csi!("?1003h"), - // RXVT mouse mode: Allows mouse coordinates of >223 - csi!("?1015h"), - // SGR mouse mode: Allows mouse coordinates of >223, preferred over RXVT mode - csi!("?1006h"), -); - -pub(crate) const DISABLE_MOUSE_MODE_CSI_SEQUENCE: &str = concat!( - // The above, in reverse order. - csi!("?1006l"), - csi!("?1015l"), - csi!("?1003l"), - csi!("?1002l"), - csi!("?1000l"), -); diff --git a/src/style.rs b/src/style.rs index aba0b26..25899a0 100644 --- a/src/style.rs +++ b/src/style.rs @@ -117,7 +117,7 @@ use std::{ #[cfg(windows)] use crate::Result; -use crate::{impl_display, Command}; +use crate::{csi, impl_display, Command}; pub use self::{ attributes::Attributes, @@ -129,7 +129,6 @@ pub use self::{ #[macro_use] mod macros; -mod ansi; mod attributes; mod content_style; mod styled_content; @@ -205,7 +204,7 @@ pub struct SetForegroundColor(pub Color); impl Command for SetForegroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_fg_csi_sequence(f, self.0) + write!(f, csi!("{}m"), Colored::ForegroundColor(self.0)) } #[cfg(windows)] @@ -229,7 +228,7 @@ pub struct SetBackgroundColor(pub Color); impl Command for SetBackgroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_bg_csi_sequence(f, self.0) + write!(f, csi!("{}m"), Colored::BackgroundColor(self.0)) } #[cfg(windows)] @@ -265,10 +264,10 @@ pub struct SetColors(pub Colors); impl Command for SetColors { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if let Some(color) = self.0.foreground { - ansi::set_fg_csi_sequence(f, color)?; + SetForegroundColor(color).write_ansi(f)?; } if let Some(color) = self.0.background { - ansi::set_bg_csi_sequence(f, color)?; + SetBackgroundColor(color).write_ansi(f)?; } Ok(()) } @@ -297,7 +296,7 @@ pub struct SetAttribute(pub Attribute); impl Command for SetAttribute { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_attr_csi_sequence(f, self.0) + write!(f, csi!("{}m"), self.0.sgr()) } #[cfg(windows)] @@ -319,7 +318,12 @@ pub struct SetAttributes(pub Attributes); impl Command for SetAttributes { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_attrs_csi_sequence(f, self.0) + for attr in Attribute::iterator() { + if self.0.has(attr) { + SetAttribute(attr).write_ansi(f)?; + } + } + Ok(()) } #[cfg(windows)] @@ -360,7 +364,7 @@ pub struct ResetColor; impl Command for ResetColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::RESET_CSI_SEQUENCE) + f.write_str(csi!("0m")) } #[cfg(windows)] @@ -399,3 +403,9 @@ impl_display!(for SetAttribute); impl_display!(for PrintStyledContent); impl_display!(for PrintStyledContent<&'static str>); impl_display!(for ResetColor); + +/// Utility function for ANSI parsing in Color and Colored. +/// Gets the next element of `iter` and tries to parse it as a u8. +fn parse_next_u8<'a>(iter: &mut impl Iterator) -> Option { + iter.next().and_then(|s| u8::from_str_radix(s, 10).ok()) +} diff --git a/src/style/ansi.rs b/src/style/ansi.rs deleted file mode 100644 index 8107125..0000000 --- a/src/style/ansi.rs +++ /dev/null @@ -1,341 +0,0 @@ -//! This is a ANSI specific implementation for styling related action. -//! This module is used for Windows 10 terminals and Unix terminals by default. - -use std::fmt::{self, Formatter}; - -use crate::{ - csi, - style::{Attribute, Attributes, Color, Colored}, -}; - -pub(crate) fn set_fg_csi_sequence(f: &mut impl fmt::Write, fg_color: Color) -> fmt::Result { - write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color)) -} - -pub(crate) fn set_bg_csi_sequence(f: &mut impl fmt::Write, bg_color: Color) -> fmt::Result { - write!(f, csi!("{}m"), Colored::BackgroundColor(bg_color)) -} - -pub(crate) fn set_attr_csi_sequence(f: &mut impl fmt::Write, attribute: Attribute) -> fmt::Result { - write!(f, csi!("{}m"), attribute.sgr()) -} - -pub(crate) fn set_attrs_csi_sequence( - f: &mut impl fmt::Write, - attributes: Attributes, -) -> fmt::Result { - for attr in Attribute::iterator() { - if attributes.has(attr) { - write!(f, csi!("{}m"), attr.sgr())?; - } - } - Ok(()) -} - -pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m"); - -impl fmt::Display for Colored { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let color; - - match *self { - Colored::ForegroundColor(new_color) => { - if new_color == Color::Reset { - return f.write_str("39"); - } else { - f.write_str("38;")?; - color = new_color; - } - } - Colored::BackgroundColor(new_color) => { - if new_color == Color::Reset { - return f.write_str("49"); - } else { - f.write_str("48;")?; - color = new_color; - } - } - } - - match color { - Color::Black => f.write_str("5;0"), - Color::DarkGrey => f.write_str("5;8"), - Color::Red => f.write_str("5;9"), - Color::DarkRed => f.write_str("5;1"), - Color::Green => f.write_str("5;10"), - Color::DarkGreen => f.write_str("5;2"), - Color::Yellow => f.write_str("5;11"), - Color::DarkYellow => f.write_str("5;3"), - Color::Blue => f.write_str("5;12"), - Color::DarkBlue => f.write_str("5;4"), - Color::Magenta => f.write_str("5;13"), - Color::DarkMagenta => f.write_str("5;5"), - Color::Cyan => f.write_str("5;14"), - Color::DarkCyan => f.write_str("5;6"), - Color::White => f.write_str("5;15"), - Color::Grey => f.write_str("5;7"), - Color::Rgb { r, g, b } => write!(f, "2;{};{};{}", r, g, b), - Color::AnsiValue(val) => write!(f, "5;{}", val), - _ => Ok(()), - } - } -} - -/// Utility function for ANSI parsing in Color and Colored. -/// Gets the next element of `iter` and tries to parse it as a u8. -fn parse_next_u8<'a>(iter: &mut impl Iterator) -> Option { - iter.next() - .and_then(|s| u8::from_str_radix(s, 10).map(Some).unwrap_or(None)) -} - -impl Colored { - /// Parse an ANSI foreground or background color. - /// This is the string that would appear within an `ESC [ m` escape sequence, as found in - /// various configuration files. - /// - /// For example: - /// * `38;5;0 -> ForegroundColor(Black)`, - /// * `38;5;26 -> ForegroundColor(AnsiValue(26))` - /// * `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))` - /// * `49 -> BackgroundColor(Reset)` - /// Invalid sequences map to `None`. - /// - /// Currently, 3/4 bit color values aren't supported so return `None`. - /// - /// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi) - pub fn parse_ansi(ansi: &str) -> Option { - use Colored::{BackgroundColor, ForegroundColor}; - - let values = &mut ansi.split(';'); - - let output = match parse_next_u8(values)? { - 38 => return Color::parse_ansi_iter(values).map(ForegroundColor), - 48 => return Color::parse_ansi_iter(values).map(BackgroundColor), - - 39 => ForegroundColor(Color::Reset), - 49 => BackgroundColor(Color::Reset), - - _ => return None, - }; - - if values.next().is_some() { - return None; - } - - Some(output) - } -} - -impl<'a> Color { - /// Parses an ANSI color sequence. - /// For example: - /// * `5;0 -> Black`, - /// * `5;26 -> AnsiValue(26)`, - /// * `2;50;60;70 -> Rgb(50, 60, 70)`. - /// Invalid sequences map to `None`. - /// - /// Currently, 3/4 bit color values aren't supported so return `None`. - /// - /// See also: [Colored::parse_ansi](enum.Colored.html#method.parse_ansi) - pub fn parse_ansi(ansi: &str) -> Option { - Self::parse_ansi_iter(&mut ansi.split(';')) - } - - /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the - /// ';'). It's a separate function so it can be used by both Color::parse_ansi and - /// colored::parse_ansi. - /// Tested in Colored tests. - fn parse_ansi_iter(values: &mut impl Iterator) -> Option { - let color = match parse_next_u8(values)? { - // 8 bit colors: `5;` - 5 => { - let n = parse_next_u8(values)?; - - use Color::*; - [ - Black, // 0 - DarkRed, // 1 - DarkGreen, // 2 - DarkYellow, // 3 - DarkBlue, // 4 - DarkMagenta, // 5 - DarkCyan, // 6 - Grey, // 7 - DarkGrey, // 8 - Red, // 9 - Green, // 10 - Yellow, // 11 - Blue, // 12 - Magenta, // 13 - Cyan, // 14 - White, // 15 - ] - .get(n as usize) - .copied() - .unwrap_or(Color::AnsiValue(n)) - } - - // 24 bit colors: `2;;;` - 2 => Color::Rgb { - r: parse_next_u8(values)?, - g: parse_next_u8(values)?, - b: parse_next_u8(values)?, - }, - - _ => return None, - }; - // If there's another value, it's unexpected so return None. - if values.next().is_some() { - return None; - } - Some(color) - } -} - -#[cfg(test)] -mod tests { - use crate::style::{Color, Colored}; - - #[test] - fn test_format_fg_color() { - let colored = Colored::ForegroundColor(Color::Red); - assert_eq!(colored.to_string(), "38;5;9"); - } - - #[test] - fn test_format_bg_color() { - let colored = Colored::BackgroundColor(Color::Red); - assert_eq!(colored.to_string(), "48;5;9"); - } - - #[test] - fn test_format_reset_fg_color() { - let colored = Colored::ForegroundColor(Color::Reset); - assert_eq!(colored.to_string(), "39"); - } - - #[test] - fn test_format_reset_bg_color() { - let colored = Colored::BackgroundColor(Color::Reset); - assert_eq!(colored.to_string(), "49"); - } - - #[test] - fn test_format_fg_rgb_color() { - let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 }); - assert_eq!(colored.to_string(), "48;2;1;2;3"); - } - - #[test] - fn test_format_fg_ansi_color() { - let colored = Colored::ForegroundColor(Color::AnsiValue(255)); - assert_eq!(colored.to_string(), "38;5;255"); - } - - #[test] - fn test_parse_ansi_fg() { - test_parse_ansi(Colored::ForegroundColor) - } - - #[test] - fn test_parse_ansi_bg() { - test_parse_ansi(Colored::ForegroundColor) - } - - /// Used for test_parse_ansi_fg and test_parse_ansi_bg - fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) { - /// Formats a re-parses `color` to check the result. - macro_rules! test { - ($color:expr) => { - let colored = bg_or_fg($color); - assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored)); - }; - } - - use Color::*; - - test!(Reset); - test!(Black); - test!(DarkGrey); - test!(Red); - test!(DarkRed); - test!(Green); - test!(DarkGreen); - test!(Yellow); - test!(DarkYellow); - test!(Blue); - test!(DarkBlue); - test!(Magenta); - test!(DarkMagenta); - test!(Cyan); - test!(DarkCyan); - test!(White); - test!(Grey); - - // n in 0..=15 will give us the color values above back. - for n in 16..=255 { - test!(AnsiValue(n)); - } - - for r in 0..=255 { - for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() { - for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() { - test!(Rgb { r, g, b }); - } - } - } - } - - #[test] - fn test_parse_invalid_ansi_color() { - /// Checks that trying to parse `s` yields None. - fn test(s: &str) { - assert_eq!(Colored::parse_ansi(s), None); - } - test(""); - test(";"); - test(";;"); - test(";;"); - test("0"); - test("1"); - test("12"); - test("100"); - test("100048949345"); - test("39;"); - test("49;"); - test("39;2"); - test("49;2"); - test("38"); - test("38;"); - test("38;0"); - test("38;5"); - test("38;5;0;"); - test("38;5;0;2"); - test("38;5;80;"); - test("38;5;80;2"); - test("38;5;257"); - test("38;2"); - test("38;2;"); - test("38;2;0"); - test("38;2;0;2"); - test("38;2;0;2;257"); - test("38;2;0;2;25;"); - test("38;2;0;2;25;3"); - test("48"); - test("48;"); - test("48;0"); - test("48;5"); - test("48;5;0;"); - test("48;5;0;2"); - test("48;5;80;"); - test("48;5;80;2"); - test("48;5;257"); - test("48;2"); - test("48;2;"); - test("48;2;0"); - test("48;2;0;2"); - test("48;2;0;2;257"); - test("48;2;0;2;25;"); - test("48;2;0;2;25;3"); - } -} diff --git a/src/style/types/color.rs b/src/style/types/color.rs index 9507289..a98aa86 100644 --- a/src/style/types/color.rs +++ b/src/style/types/color.rs @@ -3,6 +3,8 @@ use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::style::parse_next_u8; + /// Represents a color. /// /// # Platform-specific Notes @@ -90,6 +92,72 @@ pub enum Color { AnsiValue(u8), } +impl Color { + /// Parses an ANSI color sequence. + /// For example: + /// * `5;0 -> Black`, + /// * `5;26 -> AnsiValue(26)`, + /// * `2;50;60;70 -> Rgb(50, 60, 70)`. + /// Invalid sequences map to `None`. + /// + /// Currently, 3/4 bit color values aren't supported so return `None`. + /// + /// See also: [`Colored::parse_ansi`](crate::style::Colored::parse_ansi). + pub fn parse_ansi(ansi: &str) -> Option { + Self::parse_ansi_iter(&mut ansi.split(';')) + } + + /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the + /// ';'). It's a separate function so it can be used by both Color::parse_ansi and + /// colored::parse_ansi. + /// Tested in Colored tests. + pub(crate) fn parse_ansi_iter<'a>(values: &mut impl Iterator) -> Option { + let color = match parse_next_u8(values)? { + // 8 bit colors: `5;` + 5 => { + let n = parse_next_u8(values)?; + + use Color::*; + [ + Black, // 0 + DarkRed, // 1 + DarkGreen, // 2 + DarkYellow, // 3 + DarkBlue, // 4 + DarkMagenta, // 5 + DarkCyan, // 6 + Grey, // 7 + DarkGrey, // 8 + Red, // 9 + Green, // 10 + Yellow, // 11 + Blue, // 12 + Magenta, // 13 + Cyan, // 14 + White, // 15 + ] + .get(n as usize) + .copied() + .unwrap_or(Color::AnsiValue(n)) + } + + // 24 bit colors: `2;;;` + 2 => Color::Rgb { + r: parse_next_u8(values)?, + g: parse_next_u8(values)?, + b: parse_next_u8(values)?, + }, + + _ => return None, + }; + // If there's another value, it's unexpected so return None. + if values.next().is_some() { + return None; + } + Some(color) + } +} + impl TryFrom<&str> for Color { type Error = (); diff --git a/src/style/types/colored.rs b/src/style/types/colored.rs index d4e7aeb..f88486b 100644 --- a/src/style/types/colored.rs +++ b/src/style/types/colored.rs @@ -1,7 +1,9 @@ +use std::fmt::{self, Formatter}; + #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::style::Color; +use crate::style::{parse_next_u8, Color}; /// Represents a foreground or background color. /// @@ -15,3 +17,236 @@ pub enum Colored { /// A background color. BackgroundColor(Color), } + +impl Colored { + /// Parse an ANSI foreground or background color. + /// This is the string that would appear within an `ESC [ m` escape sequence, as found in + /// various configuration files. + /// + /// For example: + /// * `38;5;0 -> ForegroundColor(Black)`, + /// * `38;5;26 -> ForegroundColor(AnsiValue(26))` + /// * `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))` + /// * `49 -> BackgroundColor(Reset)` + /// Invalid sequences map to `None`. + /// + /// Currently, 3/4 bit color values aren't supported so return `None`. + /// + /// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi) + pub fn parse_ansi(ansi: &str) -> Option { + use Colored::{BackgroundColor, ForegroundColor}; + + let values = &mut ansi.split(';'); + + let output = match parse_next_u8(values)? { + 38 => return Color::parse_ansi_iter(values).map(ForegroundColor), + 48 => return Color::parse_ansi_iter(values).map(BackgroundColor), + + 39 => ForegroundColor(Color::Reset), + 49 => BackgroundColor(Color::Reset), + + _ => return None, + }; + + if values.next().is_some() { + return None; + } + + Some(output) + } +} + +impl fmt::Display for Colored { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let color; + + match *self { + Colored::ForegroundColor(new_color) => { + if new_color == Color::Reset { + return f.write_str("39"); + } else { + f.write_str("38;")?; + color = new_color; + } + } + Colored::BackgroundColor(new_color) => { + if new_color == Color::Reset { + return f.write_str("49"); + } else { + f.write_str("48;")?; + color = new_color; + } + } + } + + match color { + Color::Black => f.write_str("5;0"), + Color::DarkGrey => f.write_str("5;8"), + Color::Red => f.write_str("5;9"), + Color::DarkRed => f.write_str("5;1"), + Color::Green => f.write_str("5;10"), + Color::DarkGreen => f.write_str("5;2"), + Color::Yellow => f.write_str("5;11"), + Color::DarkYellow => f.write_str("5;3"), + Color::Blue => f.write_str("5;12"), + Color::DarkBlue => f.write_str("5;4"), + Color::Magenta => f.write_str("5;13"), + Color::DarkMagenta => f.write_str("5;5"), + Color::Cyan => f.write_str("5;14"), + Color::DarkCyan => f.write_str("5;6"), + Color::White => f.write_str("5;15"), + Color::Grey => f.write_str("5;7"), + Color::Rgb { r, g, b } => write!(f, "2;{};{};{}", r, g, b), + Color::AnsiValue(val) => write!(f, "5;{}", val), + _ => Ok(()), + } + } +} + +#[cfg(test)] +mod tests { + use crate::style::{Color, Colored}; + + #[test] + fn test_format_fg_color() { + let colored = Colored::ForegroundColor(Color::Red); + assert_eq!(colored.to_string(), "38;5;9"); + } + + #[test] + fn test_format_bg_color() { + let colored = Colored::BackgroundColor(Color::Red); + assert_eq!(colored.to_string(), "48;5;9"); + } + + #[test] + fn test_format_reset_fg_color() { + let colored = Colored::ForegroundColor(Color::Reset); + assert_eq!(colored.to_string(), "39"); + } + + #[test] + fn test_format_reset_bg_color() { + let colored = Colored::BackgroundColor(Color::Reset); + assert_eq!(colored.to_string(), "49"); + } + + #[test] + fn test_format_fg_rgb_color() { + let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 }); + assert_eq!(colored.to_string(), "48;2;1;2;3"); + } + + #[test] + fn test_format_fg_ansi_color() { + let colored = Colored::ForegroundColor(Color::AnsiValue(255)); + assert_eq!(colored.to_string(), "38;5;255"); + } + + #[test] + fn test_parse_ansi_fg() { + test_parse_ansi(Colored::ForegroundColor) + } + + #[test] + fn test_parse_ansi_bg() { + test_parse_ansi(Colored::ForegroundColor) + } + + /// Used for test_parse_ansi_fg and test_parse_ansi_bg + fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) { + /// Formats a re-parses `color` to check the result. + macro_rules! test { + ($color:expr) => { + let colored = bg_or_fg($color); + assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored)); + }; + } + + use Color::*; + + test!(Reset); + test!(Black); + test!(DarkGrey); + test!(Red); + test!(DarkRed); + test!(Green); + test!(DarkGreen); + test!(Yellow); + test!(DarkYellow); + test!(Blue); + test!(DarkBlue); + test!(Magenta); + test!(DarkMagenta); + test!(Cyan); + test!(DarkCyan); + test!(White); + test!(Grey); + + // n in 0..=15 will give us the color values above back. + for n in 16..=255 { + test!(AnsiValue(n)); + } + + for r in 0..=255 { + for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() { + for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() { + test!(Rgb { r, g, b }); + } + } + } + } + + #[test] + fn test_parse_invalid_ansi_color() { + /// Checks that trying to parse `s` yields None. + fn test(s: &str) { + assert_eq!(Colored::parse_ansi(s), None); + } + test(""); + test(";"); + test(";;"); + test(";;"); + test("0"); + test("1"); + test("12"); + test("100"); + test("100048949345"); + test("39;"); + test("49;"); + test("39;2"); + test("49;2"); + test("38"); + test("38;"); + test("38;0"); + test("38;5"); + test("38;5;0;"); + test("38;5;0;2"); + test("38;5;80;"); + test("38;5;80;2"); + test("38;5;257"); + test("38;2"); + test("38;2;"); + test("38;2;0"); + test("38;2;0;2"); + test("38;2;0;2;257"); + test("38;2;0;2;25;"); + test("38;2;0;2;25;3"); + test("48"); + test("48;"); + test("48;0"); + test("48;5"); + test("48;5;0;"); + test("48;5;0;2"); + test("48;5;80;"); + test("48;5;80;2"); + test("48;5;257"); + test("48;2"); + test("48;2;"); + test("48;2;0"); + test("48;2;0;2"); + test("48;2;0;2;257"); + test("48;2;0;2;25;"); + test("48;2;0;2;25;3"); + } +} diff --git a/src/terminal.rs b/src/terminal.rs index 43a6c41..ea92302 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -92,9 +92,8 @@ use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT; #[doc(no_inline)] use crate::Command; -use crate::{impl_display, Result}; +use crate::{csi, impl_display, Result}; -mod ansi; pub(crate) mod sys; /// Enables raw mode. @@ -124,7 +123,7 @@ pub struct DisableLineWrap; impl Command for DisableLineWrap { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::DISABLE_LINE_WRAP_CSI_SEQUENCE) + f.write_str(csi!("?7l")) } #[cfg(windows)] @@ -143,7 +142,7 @@ pub struct EnableLineWrap; impl Command for EnableLineWrap { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::ENABLE_LINE_WRAP_CSI_SEQUENCE) + f.write_str(csi!("?7h")) } #[cfg(windows)] @@ -183,7 +182,7 @@ pub struct EnterAlternateScreen; impl Command for EnterAlternateScreen { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE) + f.write_str(csi!("?1049h")) } #[cfg(windows)] @@ -221,7 +220,7 @@ pub struct LeaveAlternateScreen; impl Command for LeaveAlternateScreen { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - f.write_str(ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE) + f.write_str(csi!("?1049l")) } #[cfg(windows)] @@ -258,7 +257,10 @@ pub struct ScrollUp(pub u16); impl Command for ScrollUp { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::scroll_up_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}S"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -277,7 +279,10 @@ pub struct ScrollDown(pub u16); impl Command for ScrollDown { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::scroll_down_csi_sequence(f, self.0) + if self.0 != 0 { + write!(f, csi!("{}T"), self.0)?; + } + Ok(()) } #[cfg(windows)] @@ -299,11 +304,11 @@ pub struct Clear(pub ClearType); impl Command for Clear { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(match self.0 { - ClearType::All => ansi::CLEAR_ALL_CSI_SEQUENCE, - ClearType::FromCursorDown => ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE, - ClearType::FromCursorUp => ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE, - ClearType::CurrentLine => ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE, - ClearType::UntilNewLine => ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE, + ClearType::All => csi!("2J"), + ClearType::FromCursorDown => csi!("J"), + ClearType::FromCursorUp => csi!("1J"), + ClearType::CurrentLine => csi!("2K"), + ClearType::UntilNewLine => csi!("K"), }) } @@ -323,7 +328,7 @@ pub struct SetSize(pub u16, pub u16); impl Command for SetSize { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_size_csi_sequence(f, self.0, self.1) + write!(f, csi!("8;{};{}t"), self.1, self.0) } #[cfg(windows)] @@ -342,7 +347,7 @@ pub struct SetTitle(pub T); impl Command for SetTitle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { - ansi::set_title_ansi_sequence(f, &self.0) + write!(f, "\x1B]0;{}\x07", &self.0) } #[cfg(windows)] diff --git a/src/terminal/ansi.rs b/src/terminal/ansi.rs deleted file mode 100644 index 7e091aa..0000000 --- a/src/terminal/ansi.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! This module provides terminal related ANSI escape codes. - -use std::fmt; - -use crate::csi; - -pub(crate) const CLEAR_ALL_CSI_SEQUENCE: &str = csi!("2J"); -pub(crate) const CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE: &str = csi!("J"); -pub(crate) const CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE: &str = csi!("1J"); -pub(crate) const CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE: &str = csi!("2K"); -pub(crate) const CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE: &str = csi!("K"); -pub(crate) const ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049h"); -pub(crate) const LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE: &str = csi!("?1049l"); -pub(crate) const DISABLE_LINE_WRAP_CSI_SEQUENCE: &str = csi!("?7l"); -pub(crate) const ENABLE_LINE_WRAP_CSI_SEQUENCE: &str = csi!("?7h"); - -pub(crate) fn scroll_up_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - write!(f, csi!("{}S"), count) -} - -pub(crate) fn scroll_down_csi_sequence(f: &mut impl fmt::Write, count: u16) -> fmt::Result { - write!(f, csi!("{}T"), count) -} - -pub(crate) fn set_size_csi_sequence( - f: &mut impl fmt::Write, - width: u16, - height: u16, -) -> fmt::Result { - write!(f, csi!("8;{};{}t"), height, width) -} - -pub(crate) fn set_title_ansi_sequence( - f: &mut impl fmt::Write, - title: impl fmt::Display, -) -> fmt::Result { - write!(f, "\x1B]0;{}\x07", title) -} From 1418580fed3ac104ed0612f3a3eb652e8d3d1697 Mon Sep 17 00:00:00 2001 From: Koxiaet Date: Sun, 3 Jan 2021 13:40:22 +0000 Subject: [PATCH 5/5] Remove lazy_static dependency (#530) --- Cargo.toml | 1 - README.md | 3 +-- src/ansi_support.rs | 27 ++++++++++++++++----------- src/cursor/sys/windows.rs | 22 +++++++++++++--------- src/event.rs | 33 +++++++++++++++++++++++---------- src/event/stream.rs | 6 +++--- src/event/sys/windows.rs | 27 +++++++++++++-------------- src/style/sys/windows.rs | 31 ++++++++++++++----------------- src/terminal/sys/unix.rs | 18 ++++++++---------- 9 files changed, 91 insertions(+), 77 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c6a3ea..749dba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ event-stream = ["futures-core"] # [dependencies] bitflags = "1.2" -lazy_static = "1.4" parking_lot = "0.11" # optional deps only added when requested diff --git a/README.md b/README.md index b4d8f77..81e3bda 100644 --- a/README.md +++ b/README.md @@ -144,8 +144,7 @@ features = ["event-stream"] | Dependency | Used for | Included | | :----- | :----- | :----- | `bitflags` | `KeyModifiers`, those are differ based on input.| always -| `lazy_static` | original console color, original terminal mode, saved cursor position, supports ANSI on windows, single event reader per application.| always -| `parking_lot` | used for an RW LOCK. | always +| `parking_lot` | locking `RwLock`s with a timeout, const mutexes. | always | `libc` | UNIX terminal_size/raw modes/set_title and several other lowlevel functionality. | UNIX only | `Mio` | event readiness polling, waking up poller | UNIX only | `signal-hook`| signalhook is used to handle terminal resize SIGNAL with Mio. | UNIX only diff --git a/src/ansi_support.rs b/src/ansi_support.rs index fb07c0f..9409ced 100644 --- a/src/ansi_support.rs +++ b/src/ansi_support.rs @@ -1,7 +1,8 @@ -use crossterm_winapi::{ConsoleMode, Handle}; -use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; +use std::sync::atomic::{AtomicBool, Ordering}; -use lazy_static::lazy_static; +use crossterm_winapi::{ConsoleMode, Handle}; +use parking_lot::Once; +use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; use crate::Result; @@ -27,17 +28,21 @@ fn enable_vt_processing() -> Result<()> { Ok(()) } -lazy_static! { - static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = { +static SUPPORTS_ANSI_ESCAPE_CODES: AtomicBool = AtomicBool::new(false); +static INITIALIZER: Once = Once::new(); + +/// Checks if the current terminal supports ansi escape sequences +pub fn supports_ansi() -> bool { + INITIALIZER.call_once(|| { // Some terminals on Windows like GitBash can't use WinAPI calls directly // so when we try to enable the ANSI-flag for Windows this won't work. // Because of that we should check first if the TERM-variable is set // and see if the current terminal is a terminal who does support ANSI. - std::env::var("TERM").map_or(false, |term| term != "dumb") || enable_vt_processing().is_ok() - }; -} + let supported = std::env::var("TERM").map_or(false, |term| term != "dumb") + || enable_vt_processing().is_ok(); -/// Checks if the current terminal supports ansi escape sequences -pub fn supports_ansi() -> bool { - *SUPPORTS_ANSI_ESCAPE_CODES + SUPPORTS_ANSI_ESCAPE_CODES.store(supported, Ordering::SeqCst); + }); + + SUPPORTS_ANSI_ESCAPE_CODES.load(Ordering::SeqCst) } diff --git a/src/cursor/sys/windows.rs b/src/cursor/sys/windows.rs index f41193a..1d95d71 100644 --- a/src/cursor/sys/windows.rs +++ b/src/cursor/sys/windows.rs @@ -1,6 +1,8 @@ //! WinAPI related logic to cursor manipulation. -use std::{io, sync::Mutex}; +use std::convert::TryFrom; +use std::io; +use std::sync::atomic::{AtomicU64, Ordering}; use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer}; use winapi::{ @@ -8,13 +10,13 @@ use winapi::{ um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD}, }; -use lazy_static::lazy_static; - use crate::Result; -lazy_static! { - static ref SAVED_CURSOR_POS: Mutex> = Mutex::new(None); -} +/// The position of the cursor, written when you save the cursor's position. +/// +/// This is `u64::MAX` initially. Otherwise, it stores the cursor's x position bit-shifted left 16 +/// times or-ed with the cursor's y position, where both are `i16`s. +static SAVED_CURSOR_POS: AtomicU64 = AtomicU64::new(u64::MAX); // The 'y' position of the cursor is not relative to the window but absolute to screen buffer. // We can calculate the relative cursor position by subtracting the top position of the terminal window from the y position. @@ -176,7 +178,9 @@ impl ScreenBufferCursor { } fn restore_position(&self) -> Result<()> { - if let Some((x, y)) = *SAVED_CURSOR_POS.lock().unwrap() { + if let Ok(val) = u32::try_from(SAVED_CURSOR_POS.load(Ordering::Relaxed)) { + let x = (val >> 16) as i16; + let y = val as i16; self.move_to(x, y)?; } @@ -186,8 +190,8 @@ impl ScreenBufferCursor { fn save_position(&self) -> Result<()> { let position = self.position()?; - let mut locked_pos = SAVED_CURSOR_POS.lock().unwrap(); - *locked_pos = Some((position.x, position.y)); + let bits = u64::from(u32::from(position.x as u16) << 16 | u32::from(position.y as u16)); + SAVED_CURSOR_POS.store(bits, Ordering::Relaxed); Ok(()) } diff --git a/src/event.rs b/src/event.rs index 2cce657..89d05cf 100644 --- a/src/event.rs +++ b/src/event.rs @@ -75,16 +75,17 @@ use std::fmt; use std::time::Duration; -use parking_lot::RwLock; +use bitflags::bitflags; +use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +use crate::{Command, Result}; use bitflags::bitflags; use lazy_static::lazy_static; -use crate::{csi, Command, Result}; - use filter::{EventFilter, Filter}; +use read::InternalEventReader; #[cfg(feature = "event-stream")] pub use stream::EventStream; use timeout::PollTimeout; @@ -97,10 +98,22 @@ mod stream; pub(crate) mod sys; mod timeout; -lazy_static! { - /// Static instance of `InternalEventReader`. - /// This needs to be static because there can be one event reader. - static ref INTERNAL_EVENT_READER: RwLock = RwLock::new(read::InternalEventReader::default()); +/// Static instance of `InternalEventReader`. +/// This needs to be static because there can be one event reader. +static INTERNAL_EVENT_READER: Mutex> = parking_lot::const_mutex(None); + +fn lock_internal_event_reader() -> MappedMutexGuard<'static, InternalEventReader> { + MutexGuard::map(INTERNAL_EVENT_READER.lock(), |reader| { + reader.get_or_insert_with(InternalEventReader::default) + }) +} +fn try_lock_internal_event_reader_for( + duration: Duration, +) -> Option> { + Some(MutexGuard::map( + INTERNAL_EVENT_READER.try_lock_for(duration)?, + |reader| reader.get_or_insert_with(InternalEventReader::default), + )) } /// Checks if there is an [`Event`](enum.Event.html) available. @@ -201,13 +214,13 @@ where { let (mut reader, timeout) = if let Some(timeout) = timeout { let poll_timeout = PollTimeout::new(Some(timeout)); - if let Some(reader) = INTERNAL_EVENT_READER.try_write_for(timeout) { + if let Some(reader) = try_lock_internal_event_reader_for(timeout) { (reader, poll_timeout.leftover()) } else { return Ok(false); } } else { - (INTERNAL_EVENT_READER.write(), None) + (lock_internal_event_reader(), None) }; reader.poll(timeout, filter) } @@ -217,7 +230,7 @@ pub(crate) fn read_internal(filter: &F) -> Result where F: Filter, { - let mut reader = INTERNAL_EVENT_READER.write(); + let mut reader = lock_internal_event_reader(); reader.read(filter) } diff --git a/src/event/stream.rs b/src/event/stream.rs index a7f61b5..fd677fd 100644 --- a/src/event/stream.rs +++ b/src/event/stream.rs @@ -15,8 +15,8 @@ use futures_core::stream::Stream; use crate::Result; use super::{ - filter::EventFilter, poll_internal, read_internal, sys::Waker, Event, InternalEvent, - INTERNAL_EVENT_READER, + filter::EventFilter, lock_internal_event_reader, poll_internal, read_internal, sys::Waker, + Event, InternalEvent, }; /// A stream of `Result`. @@ -60,7 +60,7 @@ impl Default for EventStream { }); EventStream { - poll_internal_waker: INTERNAL_EVENT_READER.write().waker(), + poll_internal_waker: lock_internal_event_reader().waker(), stream_wake_task_executed: Arc::new(AtomicBool::new(false)), stream_wake_task_should_shutdown: Arc::new(AtomicBool::new(false)), task_sender, diff --git a/src/event/sys/windows.rs b/src/event/sys/windows.rs index d872a6c..7485d55 100644 --- a/src/event/sys/windows.rs +++ b/src/event/sys/windows.rs @@ -1,9 +1,9 @@ //! This is a WINDOWS specific implementation for input related action. -use std::sync::Mutex; +use std::convert::TryFrom; +use std::sync::atomic::{AtomicU64, Ordering}; use crossterm_winapi::{ConsoleMode, Handle}; -use lazy_static::lazy_static; use crate::Result; @@ -15,25 +15,24 @@ pub(crate) mod poll; const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008; -lazy_static! { - static ref ORIGINAL_CONSOLE_MODE: Mutex> = Mutex::new(None); -} +/// This is a either `u64::MAX` if it's uninitialized or a valid `u32` that stores the original +/// console mode if it's initialized. +static ORIGINAL_CONSOLE_MODE: AtomicU64 = AtomicU64::new(u64::MAX); /// Initializes the default console color. It will will be skipped if it has already been initialized. fn init_original_console_mode(original_mode: u32) { - let mut lock = ORIGINAL_CONSOLE_MODE.lock().unwrap(); - - if lock.is_none() { - *lock = Some(original_mode); - } + let _ = ORIGINAL_CONSOLE_MODE.compare_exchange( + u64::MAX, + u64::from(original_mode), + Ordering::Relaxed, + Ordering::Relaxed, + ); } /// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic. fn original_console_mode() -> u32 { - // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` - ORIGINAL_CONSOLE_MODE - .lock() - .unwrap() + u32::try_from(ORIGINAL_CONSOLE_MODE.load(Ordering::Relaxed)) + // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` .expect("Original console mode not set") } diff --git a/src/style/sys/windows.rs b/src/style/sys/windows.rs index 80c9ede..b2bb7f6 100644 --- a/src/style/sys/windows.rs +++ b/src/style/sys/windows.rs @@ -1,10 +1,9 @@ -use std::sync::Mutex; +use std::convert::TryFrom; +use std::sync::atomic::{AtomicU32, Ordering}; use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; use winapi::um::wincon; -use lazy_static::lazy_static; - use crate::Result; use super::super::{Color, Colored}; @@ -70,7 +69,7 @@ pub(crate) fn set_background_color(bg_color: Color) -> Result<()> { } pub(crate) fn reset() -> Result<()> { - if let Some(original_color) = *ORIGINAL_CONSOLE_COLOR.lock().unwrap() { + if let Ok(original_color) = u16::try_from(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed)) { Console::from(Handle::new(HandleType::CurrentOutputHandle)?) .set_text_attribute(original_color)?; } @@ -80,12 +79,10 @@ pub(crate) fn reset() -> Result<()> { /// Initializes the default console color. It will will be skipped if it has already been initialized. pub(crate) fn init_console_color() -> Result<()> { - let mut locked_pos = ORIGINAL_CONSOLE_COLOR.lock().unwrap(); - - if locked_pos.is_none() { + if ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed) == u32::MAX { let screen_buffer = ScreenBuffer::current()?; let attr = screen_buffer.info()?.attributes(); - *locked_pos = Some(attr); + ORIGINAL_CONSOLE_COLOR.store(u32::from(attr), Ordering::Relaxed); } Ok(()) @@ -93,16 +90,14 @@ pub(crate) fn init_console_color() -> Result<()> { /// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic. pub(crate) fn original_console_color() -> u16 { - // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` - ORIGINAL_CONSOLE_COLOR - .lock() - .unwrap() + u16::try_from(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed)) + // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` .expect("Initial console color not set") } -lazy_static! { - static ref ORIGINAL_CONSOLE_COLOR: Mutex> = Mutex::new(None); -} +// This is either a valid u16 in which case it stores the original console color or it is u32::MAX +// in which case it is uninitialized. +static ORIGINAL_CONSOLE_COLOR: AtomicU32 = AtomicU32::new(u32::MAX); impl From for u16 { /// Returns the WinAPI color value (u16) from the `Colored` struct. @@ -180,6 +175,8 @@ impl From for u16 { #[cfg(test)] mod tests { + use std::sync::atomic::Ordering; + use crate::style::sys::windows::set_foreground_color; use super::{ @@ -200,11 +197,11 @@ mod tests { #[test] fn test_original_console_color_is_set() { - assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_none()); + assert_eq!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX); // will call `init_console_color` set_foreground_color(Color::Blue).unwrap(); - assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_some()); + assert_ne!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX); } } diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index 18c626d..d5e5827 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -2,25 +2,23 @@ use std::fs::File; use std::os::unix::io::{IntoRawFd, RawFd}; -use std::{io, mem, process, sync::Mutex}; +use std::{io, mem, process}; -use lazy_static::lazy_static; use libc::{ cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, }; +use parking_lot::Mutex; use crate::error::{ErrorKind, Result}; use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc}; -lazy_static! { - // Some(Termios) -> we're in the raw mode and this is the previous mode - // None -> we're not in the raw mode - static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = Mutex::new(None); -} +// Some(Termios) -> we're in the raw mode and this is the previous mode +// None -> we're not in the raw mode +static TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = parking_lot::const_mutex(None); pub(crate) fn is_raw_mode_enabled() -> bool { - TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() + TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some() } #[allow(clippy::useless_conversion)] @@ -49,7 +47,7 @@ pub(crate) fn size() -> Result<(u16, u16)> { } pub(crate) fn enable_raw_mode() -> Result<()> { - let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock(); if original_mode.is_some() { return Ok(()); @@ -70,7 +68,7 @@ pub(crate) fn enable_raw_mode() -> Result<()> { } pub(crate) fn disable_raw_mode() -> Result<()> { - let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock(); if let Some(original_mode_ios) = original_mode.as_ref() { let tty = tty_fd()?;