Merge branch 'master' of github.com:crossterm-rs/crossterm
This commit is contained in:
commit
5a50d25c83
@ -34,7 +34,6 @@ event-stream = ["futures-core"]
|
|||||||
#
|
#
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
lazy_static = "1.4"
|
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
|
|
||||||
# optional deps only added when requested
|
# optional deps only added when requested
|
||||||
|
@ -144,8 +144,7 @@ features = ["event-stream"]
|
|||||||
| Dependency | Used for | Included |
|
| Dependency | Used for | Included |
|
||||||
| :----- | :----- | :-----
|
| :----- | :----- | :-----
|
||||||
| `bitflags` | `KeyModifiers`, those are differ based on input.| always
|
| `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` | locking `RwLock`s with a timeout, const mutexes. | always
|
||||||
| `parking_lot` | used for an RW LOCK. | always
|
|
||||||
| `libc` | UNIX terminal_size/raw modes/set_title and several other lowlevel functionality. | UNIX only
|
| `libc` | UNIX terminal_size/raw modes/set_title and several other lowlevel functionality. | UNIX only
|
||||||
| `Mio` | event readiness polling, waking up poller | 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
|
| `signal-hook`| signalhook is used to handle terminal resize SIGNAL with Mio. | UNIX only
|
||||||
|
@ -50,14 +50,10 @@ fn print_events() -> Result<()> {
|
|||||||
fn flush_resize_events(event: Event) -> ((u16, u16), (u16, u16)) {
|
fn flush_resize_events(event: Event) -> ((u16, u16), (u16, u16)) {
|
||||||
if let Event::Resize(x, y) = event {
|
if let Event::Resize(x, y) = event {
|
||||||
let mut last_resize = (x, y);
|
let mut last_resize = (x, y);
|
||||||
loop {
|
while let Ok(true) = poll(Duration::from_millis(50)) {
|
||||||
if let Ok(true) = poll(Duration::from_millis(50)) {
|
|
||||||
if let Ok(Event::Resize(x, y)) = read() {
|
if let Ok(Event::Resize(x, y)) = read() {
|
||||||
last_resize = (x, y);
|
last_resize = (x, y);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((x, y), last_resize);
|
return ((x, y), last_resize);
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use crossterm_winapi::{ConsoleMode, Handle};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
||||||
|
|
||||||
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;
|
use crate::Result;
|
||||||
|
|
||||||
@ -27,17 +28,21 @@ fn enable_vt_processing() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
static SUPPORTS_ANSI_ESCAPE_CODES: AtomicBool = AtomicBool::new(false);
|
||||||
static ref SUPPORTS_ANSI_ESCAPE_CODES: bool = {
|
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
|
// 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.
|
// 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
|
// 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.
|
// 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
|
SUPPORTS_ANSI_ESCAPE_CODES.store(supported, Ordering::SeqCst);
|
||||||
pub fn supports_ansi() -> bool {
|
});
|
||||||
*SUPPORTS_ANSI_ESCAPE_CODES
|
|
||||||
|
SUPPORTS_ANSI_ESCAPE_CODES.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
@ -46,11 +46,10 @@ use std::fmt;
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{impl_display, Command};
|
use crate::{csi, impl_display, Command};
|
||||||
|
|
||||||
pub use sys::position;
|
pub use sys::position;
|
||||||
|
|
||||||
mod ansi;
|
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
|
||||||
/// A command that moves the terminal cursor to the given position (column, row).
|
/// 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 {
|
impl Command for MoveTo {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -73,26 +72,6 @@ impl Command for MoveTo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A command that moves the terminal cursor up the given number of lines,
|
|
||||||
/// and moves it to the first column.
|
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
///
|
|
||||||
/// Commands must be executed/queued for execution otherwise they do nothing.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> {
|
|
||||||
sys::move_to_next_line(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A command that moves the terminal cursor down 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.
|
/// and moves it to the first column.
|
||||||
///
|
///
|
||||||
@ -100,11 +79,31 @@ impl Command for MoveToNextLine {
|
|||||||
///
|
///
|
||||||
/// Commands must be executed/queued for execution otherwise they do nothing.
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct MoveToNextLine(pub u16);
|
||||||
|
|
||||||
|
impl Command for MoveToNextLine {
|
||||||
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
|
write!(f, csi!("{}E"), self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> {
|
||||||
|
sys::move_to_next_line(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command that moves the terminal cursor up the given number of lines,
|
||||||
|
/// and moves it to the first column.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct MoveToPreviousLine(pub u16);
|
pub struct MoveToPreviousLine(pub u16);
|
||||||
|
|
||||||
impl Command for MoveToPreviousLine {
|
impl Command for MoveToPreviousLine {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -123,7 +122,7 @@ pub struct MoveToColumn(pub u16);
|
|||||||
|
|
||||||
impl Command for MoveToColumn {
|
impl Command for MoveToColumn {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -142,7 +141,10 @@ pub struct MoveUp(pub u16);
|
|||||||
|
|
||||||
impl Command for MoveUp {
|
impl Command for MoveUp {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -161,7 +163,10 @@ pub struct MoveRight(pub u16);
|
|||||||
|
|
||||||
impl Command for MoveRight {
|
impl Command for MoveRight {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -180,7 +185,10 @@ pub struct MoveDown(pub u16);
|
|||||||
|
|
||||||
impl Command for MoveDown {
|
impl Command for MoveDown {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -199,7 +207,10 @@ pub struct MoveLeft(pub u16);
|
|||||||
|
|
||||||
impl Command for MoveLeft {
|
impl Command for MoveLeft {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -221,7 +232,7 @@ pub struct SavePosition;
|
|||||||
|
|
||||||
impl Command for SavePosition {
|
impl Command for SavePosition {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -243,7 +254,7 @@ pub struct RestorePosition;
|
|||||||
|
|
||||||
impl Command for RestorePosition {
|
impl Command for RestorePosition {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -262,7 +273,7 @@ pub struct Hide;
|
|||||||
|
|
||||||
impl Command for Hide {
|
impl Command for Hide {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -281,7 +292,7 @@ pub struct Show;
|
|||||||
|
|
||||||
impl Command for Show {
|
impl Command for Show {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -301,7 +312,7 @@ pub struct EnableBlinking;
|
|||||||
|
|
||||||
impl Command for EnableBlinking {
|
impl Command for EnableBlinking {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -321,7 +332,7 @@ pub struct DisableBlinking;
|
|||||||
|
|
||||||
impl Command for DisableBlinking {
|
impl Command for DisableBlinking {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
|
@ -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");
|
|
@ -1,6 +1,8 @@
|
|||||||
//! WinAPI related logic to cursor manipulation.
|
//! 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 crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer};
|
||||||
use winapi::{
|
use winapi::{
|
||||||
@ -8,13 +10,13 @@ use winapi::{
|
|||||||
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
|
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
|
||||||
};
|
};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
lazy_static! {
|
/// The position of the cursor, written when you save the cursor's position.
|
||||||
static ref SAVED_CURSOR_POS: Mutex<Option<(i16, i16)>> = Mutex::new(None);
|
///
|
||||||
}
|
/// 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.
|
// 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.
|
// 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<()> {
|
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)?;
|
self.move_to(x, y)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +190,8 @@ impl ScreenBufferCursor {
|
|||||||
fn save_position(&self) -> Result<()> {
|
fn save_position(&self) -> Result<()> {
|
||||||
let position = self.position()?;
|
let position = self.position()?;
|
||||||
|
|
||||||
let mut locked_pos = SAVED_CURSOR_POS.lock().unwrap();
|
let bits = u64::from(u32::from(position.x as u16) << 16 | u32::from(position.y as u16));
|
||||||
*locked_pos = Some((position.x, position.y));
|
SAVED_CURSOR_POS.store(bits, Ordering::Relaxed);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
56
src/event.rs
56
src/event.rs
@ -75,21 +75,21 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
use bitflags::bitflags;
|
||||||
|
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{Command, Result};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
use crate::{Command, Result};
|
|
||||||
|
|
||||||
use filter::{EventFilter, Filter};
|
use filter::{EventFilter, Filter};
|
||||||
|
use read::InternalEventReader;
|
||||||
#[cfg(feature = "event-stream")]
|
#[cfg(feature = "event-stream")]
|
||||||
pub use stream::EventStream;
|
pub use stream::EventStream;
|
||||||
use timeout::PollTimeout;
|
use timeout::PollTimeout;
|
||||||
|
|
||||||
mod ansi;
|
|
||||||
pub(crate) mod filter;
|
pub(crate) mod filter;
|
||||||
mod read;
|
mod read;
|
||||||
mod source;
|
mod source;
|
||||||
@ -98,10 +98,22 @@ mod stream;
|
|||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
mod timeout;
|
mod timeout;
|
||||||
|
|
||||||
lazy_static! {
|
/// Static instance of `InternalEventReader`.
|
||||||
/// Static instance of `InternalEventReader`.
|
/// This needs to be static because there can be one event reader.
|
||||||
/// This needs to be static because there can be one event reader.
|
static INTERNAL_EVENT_READER: Mutex<Option<InternalEventReader>> = parking_lot::const_mutex(None);
|
||||||
static ref INTERNAL_EVENT_READER: RwLock<read::InternalEventReader> = RwLock::new(read::InternalEventReader::default());
|
|
||||||
|
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<MappedMutexGuard<'static, InternalEventReader>> {
|
||||||
|
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.
|
/// Checks if there is an [`Event`](enum.Event.html) available.
|
||||||
@ -202,13 +214,13 @@ where
|
|||||||
{
|
{
|
||||||
let (mut reader, timeout) = if let Some(timeout) = timeout {
|
let (mut reader, timeout) = if let Some(timeout) = timeout {
|
||||||
let poll_timeout = PollTimeout::new(Some(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())
|
(reader, poll_timeout.leftover())
|
||||||
} else {
|
} else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(INTERNAL_EVENT_READER.write(), None)
|
(lock_internal_event_reader(), None)
|
||||||
};
|
};
|
||||||
reader.poll(timeout, filter)
|
reader.poll(timeout, filter)
|
||||||
}
|
}
|
||||||
@ -218,7 +230,7 @@ pub(crate) fn read_internal<F>(filter: &F) -> Result<InternalEvent>
|
|||||||
where
|
where
|
||||||
F: Filter,
|
F: Filter,
|
||||||
{
|
{
|
||||||
let mut reader = INTERNAL_EVENT_READER.write();
|
let mut reader = lock_internal_event_reader();
|
||||||
reader.read(filter)
|
reader.read(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +242,18 @@ pub struct EnableMouseCapture;
|
|||||||
|
|
||||||
impl Command for EnableMouseCapture {
|
impl Command for EnableMouseCapture {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -252,7 +275,14 @@ pub struct DisableMouseCapture;
|
|||||||
|
|
||||||
impl Command for DisableMouseCapture {
|
impl Command for DisableMouseCapture {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
|
@ -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"),
|
|
||||||
);
|
|
@ -15,8 +15,8 @@ use futures_core::stream::Stream;
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
filter::EventFilter, poll_internal, read_internal, sys::Waker, Event, InternalEvent,
|
filter::EventFilter, lock_internal_event_reader, poll_internal, read_internal, sys::Waker,
|
||||||
INTERNAL_EVENT_READER,
|
Event, InternalEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A stream of `Result<Event>`.
|
/// A stream of `Result<Event>`.
|
||||||
@ -60,7 +60,7 @@ impl Default for EventStream {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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_executed: Arc::new(AtomicBool::new(false)),
|
||||||
stream_wake_task_should_shutdown: Arc::new(AtomicBool::new(false)),
|
stream_wake_task_should_shutdown: Arc::new(AtomicBool::new(false)),
|
||||||
task_sender,
|
task_sender,
|
||||||
|
@ -156,7 +156,7 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result<Option<InternalEvent>> {
|
|||||||
// The final byte of a CSI sequence can be in the range 64-126, so
|
// The final byte of a CSI sequence can be in the range 64-126, so
|
||||||
// let's keep reading anything else.
|
// let's keep reading anything else.
|
||||||
let last_byte = *buffer.last().unwrap();
|
let last_byte = *buffer.last().unwrap();
|
||||||
if last_byte < 64 || last_byte > 126 {
|
if !(64..=126).contains(&last_byte) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
match buffer[buffer.len() - 1] {
|
match buffer[buffer.len() - 1] {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
//! This is a WINDOWS specific implementation for input related action.
|
//! 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 crossterm_winapi::{ConsoleMode, Handle};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
@ -15,25 +15,24 @@ pub(crate) mod poll;
|
|||||||
|
|
||||||
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
|
||||||
|
|
||||||
lazy_static! {
|
/// This is a either `u64::MAX` if it's uninitialized or a valid `u32` that stores the original
|
||||||
static ref ORIGINAL_CONSOLE_MODE: Mutex<Option<u32>> = Mutex::new(None);
|
/// 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.
|
/// Initializes the default console color. It will will be skipped if it has already been initialized.
|
||||||
fn init_original_console_mode(original_mode: u32) {
|
fn init_original_console_mode(original_mode: u32) {
|
||||||
let mut lock = ORIGINAL_CONSOLE_MODE.lock().unwrap();
|
let _ = ORIGINAL_CONSOLE_MODE.compare_exchange(
|
||||||
|
u64::MAX,
|
||||||
if lock.is_none() {
|
u64::from(original_mode),
|
||||||
*lock = Some(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.
|
/// 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 {
|
fn original_console_mode() -> u32 {
|
||||||
|
u32::try_from(ORIGINAL_CONSOLE_MODE.load(Ordering::Relaxed))
|
||||||
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
|
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
|
||||||
ORIGINAL_CONSOLE_MODE
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.expect("Original console mode not set")
|
.expect("Original console mode not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
src/style.rs
28
src/style.rs
@ -117,7 +117,7 @@ use std::{
|
|||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::{impl_display, Command};
|
use crate::{csi, impl_display, Command};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
attributes::Attributes,
|
attributes::Attributes,
|
||||||
@ -129,7 +129,6 @@ pub use self::{
|
|||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod ansi;
|
|
||||||
mod attributes;
|
mod attributes;
|
||||||
mod content_style;
|
mod content_style;
|
||||||
mod styled_content;
|
mod styled_content;
|
||||||
@ -205,7 +204,7 @@ pub struct SetForegroundColor(pub Color);
|
|||||||
|
|
||||||
impl Command for SetForegroundColor {
|
impl Command for SetForegroundColor {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -229,7 +228,7 @@ pub struct SetBackgroundColor(pub Color);
|
|||||||
|
|
||||||
impl Command for SetBackgroundColor {
|
impl Command for SetBackgroundColor {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -265,10 +264,10 @@ pub struct SetColors(pub Colors);
|
|||||||
impl Command for SetColors {
|
impl Command for SetColors {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
if let Some(color) = self.0.foreground {
|
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 {
|
if let Some(color) = self.0.background {
|
||||||
ansi::set_bg_csi_sequence(f, color)?;
|
SetBackgroundColor(color).write_ansi(f)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -297,7 +296,7 @@ pub struct SetAttribute(pub Attribute);
|
|||||||
|
|
||||||
impl Command for SetAttribute {
|
impl Command for SetAttribute {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -319,7 +318,12 @@ pub struct SetAttributes(pub Attributes);
|
|||||||
|
|
||||||
impl Command for SetAttributes {
|
impl Command for SetAttributes {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -360,7 +364,7 @@ pub struct ResetColor;
|
|||||||
|
|
||||||
impl Command for ResetColor {
|
impl Command for ResetColor {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -399,3 +403,9 @@ impl_display!(for SetAttribute);
|
|||||||
impl_display!(for PrintStyledContent<String>);
|
impl_display!(for PrintStyledContent<String>);
|
||||||
impl_display!(for PrintStyledContent<&'static str>);
|
impl_display!(for PrintStyledContent<&'static str>);
|
||||||
impl_display!(for ResetColor);
|
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<Item = &'a str>) -> Option<u8> {
|
||||||
|
iter.next().and_then(|s| u8::from_str_radix(s, 10).ok())
|
||||||
|
}
|
||||||
|
@ -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<Item = &'a str>) -> Option<u8> {
|
|
||||||
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 [ <str> 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<Self> {
|
|
||||||
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> {
|
|
||||||
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<Item = &'a str>) -> Option<Self> {
|
|
||||||
let color = match parse_next_u8(values)? {
|
|
||||||
// 8 bit colors: `5;<n>`
|
|
||||||
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;<r>;<g>;<b>`
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer};
|
||||||
use winapi::um::wincon;
|
use winapi::um::wincon;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
||||||
use super::super::{Color, Colored};
|
use super::super::{Color, Colored};
|
||||||
@ -70,7 +69,7 @@ pub(crate) fn set_background_color(bg_color: Color) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset() -> 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)?)
|
Console::from(Handle::new(HandleType::CurrentOutputHandle)?)
|
||||||
.set_text_attribute(original_color)?;
|
.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.
|
/// Initializes the default console color. It will will be skipped if it has already been initialized.
|
||||||
pub(crate) fn init_console_color() -> Result<()> {
|
pub(crate) fn init_console_color() -> Result<()> {
|
||||||
let mut locked_pos = ORIGINAL_CONSOLE_COLOR.lock().unwrap();
|
if ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed) == u32::MAX {
|
||||||
|
|
||||||
if locked_pos.is_none() {
|
|
||||||
let screen_buffer = ScreenBuffer::current()?;
|
let screen_buffer = ScreenBuffer::current()?;
|
||||||
let attr = screen_buffer.info()?.attributes();
|
let attr = screen_buffer.info()?.attributes();
|
||||||
*locked_pos = Some(attr);
|
ORIGINAL_CONSOLE_COLOR.store(u32::from(attr), Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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.
|
/// 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 {
|
pub(crate) fn original_console_color() -> u16 {
|
||||||
|
u16::try_from(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed))
|
||||||
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
|
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
|
||||||
ORIGINAL_CONSOLE_COLOR
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.expect("Initial console color not set")
|
.expect("Initial console color not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
// This is either a valid u16 in which case it stores the original console color or it is u32::MAX
|
||||||
static ref ORIGINAL_CONSOLE_COLOR: Mutex<Option<u16>> = Mutex::new(None);
|
// in which case it is uninitialized.
|
||||||
}
|
static ORIGINAL_CONSOLE_COLOR: AtomicU32 = AtomicU32::new(u32::MAX);
|
||||||
|
|
||||||
impl From<Colored> for u16 {
|
impl From<Colored> for u16 {
|
||||||
/// Returns the WinAPI color value (u16) from the `Colored` struct.
|
/// Returns the WinAPI color value (u16) from the `Colored` struct.
|
||||||
@ -180,6 +175,8 @@ impl From<Colored> for u16 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use crate::style::sys::windows::set_foreground_color;
|
use crate::style::sys::windows::set_foreground_color;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -200,11 +197,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_original_console_color_is_set() {
|
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`
|
// will call `init_console_color`
|
||||||
set_foreground_color(Color::Blue).unwrap();
|
set_foreground_color(Color::Blue).unwrap();
|
||||||
|
|
||||||
assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_some());
|
assert_ne!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr};
|
|||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::style::parse_next_u8;
|
||||||
|
|
||||||
/// Represents a color.
|
/// Represents a color.
|
||||||
///
|
///
|
||||||
/// # Platform-specific Notes
|
/// # Platform-specific Notes
|
||||||
@ -90,6 +92,72 @@ pub enum Color {
|
|||||||
AnsiValue(u8),
|
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> {
|
||||||
|
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<Item = &'a str>) -> Option<Self> {
|
||||||
|
let color = match parse_next_u8(values)? {
|
||||||
|
// 8 bit colors: `5;<n>`
|
||||||
|
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;<r>;<g>;<b>`
|
||||||
|
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 {
|
impl TryFrom<&str> for Color {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
use std::fmt::{self, Formatter};
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::style::Color;
|
use crate::style::{parse_next_u8, Color};
|
||||||
|
|
||||||
/// Represents a foreground or background color.
|
/// Represents a foreground or background color.
|
||||||
///
|
///
|
||||||
@ -15,3 +17,236 @@ pub enum Colored {
|
|||||||
/// A background color.
|
/// A background color.
|
||||||
BackgroundColor(Color),
|
BackgroundColor(Color),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Colored {
|
||||||
|
/// Parse an ANSI foreground or background color.
|
||||||
|
/// This is the string that would appear within an `ESC [ <str> 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<Self> {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -92,9 +92,8 @@ use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
|
|||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
use crate::Command;
|
use crate::Command;
|
||||||
use crate::{impl_display, Result};
|
use crate::{csi, impl_display, Result};
|
||||||
|
|
||||||
mod ansi;
|
|
||||||
pub(crate) mod sys;
|
pub(crate) mod sys;
|
||||||
|
|
||||||
/// Enables raw mode.
|
/// Enables raw mode.
|
||||||
@ -124,7 +123,7 @@ pub struct DisableLineWrap;
|
|||||||
|
|
||||||
impl Command for DisableLineWrap {
|
impl Command for DisableLineWrap {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -143,7 +142,7 @@ pub struct EnableLineWrap;
|
|||||||
|
|
||||||
impl Command for EnableLineWrap {
|
impl Command for EnableLineWrap {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -183,7 +182,7 @@ pub struct EnterAlternateScreen;
|
|||||||
|
|
||||||
impl Command for EnterAlternateScreen {
|
impl Command for EnterAlternateScreen {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -221,7 +220,7 @@ pub struct LeaveAlternateScreen;
|
|||||||
|
|
||||||
impl Command for LeaveAlternateScreen {
|
impl Command for LeaveAlternateScreen {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -258,7 +257,10 @@ pub struct ScrollUp(pub u16);
|
|||||||
|
|
||||||
impl Command for ScrollUp {
|
impl Command for ScrollUp {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -277,7 +279,10 @@ pub struct ScrollDown(pub u16);
|
|||||||
|
|
||||||
impl Command for ScrollDown {
|
impl Command for ScrollDown {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -299,11 +304,11 @@ pub struct Clear(pub ClearType);
|
|||||||
impl Command for Clear {
|
impl Command for Clear {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
||||||
f.write_str(match self.0 {
|
f.write_str(match self.0 {
|
||||||
ClearType::All => ansi::CLEAR_ALL_CSI_SEQUENCE,
|
ClearType::All => csi!("2J"),
|
||||||
ClearType::FromCursorDown => ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE,
|
ClearType::FromCursorDown => csi!("J"),
|
||||||
ClearType::FromCursorUp => ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE,
|
ClearType::FromCursorUp => csi!("1J"),
|
||||||
ClearType::CurrentLine => ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE,
|
ClearType::CurrentLine => csi!("2K"),
|
||||||
ClearType::UntilNewLine => ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE,
|
ClearType::UntilNewLine => csi!("K"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,7 +328,7 @@ pub struct SetSize(pub u16, pub u16);
|
|||||||
|
|
||||||
impl Command for SetSize {
|
impl Command for SetSize {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
@ -338,16 +343,16 @@ impl Command for SetSize {
|
|||||||
///
|
///
|
||||||
/// Commands must be executed/queued for execution otherwise they do nothing.
|
/// Commands must be executed/queued for execution otherwise they do nothing.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct SetTitle<'a>(pub &'a str);
|
pub struct SetTitle<T>(pub T);
|
||||||
|
|
||||||
impl<'a> Command for SetTitle<'a> {
|
impl<T: fmt::Display> Command for SetTitle<T> {
|
||||||
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
|
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)]
|
#[cfg(windows)]
|
||||||
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> {
|
fn execute_winapi(&self, _writer: impl FnMut() -> Result<()>) -> Result<()> {
|
||||||
sys::set_window_title(self.0)
|
sys::set_window_title(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,35 +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: &str) -> fmt::Result {
|
|
||||||
write!(f, "\x1B]0;{}\x07", title)
|
|
||||||
}
|
|
@ -2,25 +2,23 @@
|
|||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::io::{IntoRawFd, RawFd};
|
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::{
|
use libc::{
|
||||||
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
|
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
|
||||||
TIOCGWINSZ,
|
TIOCGWINSZ,
|
||||||
};
|
};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
use crate::error::{ErrorKind, Result};
|
use crate::error::{ErrorKind, Result};
|
||||||
use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc};
|
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
|
||||||
// Some(Termios) -> we're in the raw mode and this is the previous mode
|
// None -> we're not in the raw mode
|
||||||
// None -> we're not in the raw mode
|
static TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = parking_lot::const_mutex(None);
|
||||||
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_raw_mode_enabled() -> bool {
|
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)]
|
#[allow(clippy::useless_conversion)]
|
||||||
@ -49,7 +47,7 @@ pub(crate) fn size() -> Result<(u16, u16)> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn enable_raw_mode() -> Result<()> {
|
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() {
|
if original_mode.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -70,7 +68,7 @@ pub(crate) fn enable_raw_mode() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn disable_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() {
|
if let Some(original_mode_ios) = original_mode.as_ref() {
|
||||||
let tty = tty_fd()?;
|
let tty = tty_fd()?;
|
||||||
@ -87,21 +85,18 @@ pub(crate) fn disable_raw_mode() -> Result<()> {
|
|||||||
///
|
///
|
||||||
/// The arg should be "cols" or "lines"
|
/// The arg should be "cols" or "lines"
|
||||||
fn tput_value(arg: &str) -> Option<u16> {
|
fn tput_value(arg: &str) -> Option<u16> {
|
||||||
match process::Command::new("tput").arg(arg).output() {
|
let output = process::Command::new("tput").arg(arg).output().ok()?;
|
||||||
Ok(process::Output { stdout, .. }) => {
|
let value = output
|
||||||
let value = stdout
|
.stdout
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|&b| b as u16)
|
.filter_map(|b| char::from(b).to_digit(10))
|
||||||
.take_while(|&b| b >= 48 && b <= 58)
|
.fold(0, |v, n| v * 10 + n as u16);
|
||||||
.fold(0, |v, b| v * 10 + (b - 48));
|
|
||||||
if value > 0 {
|
if value > 0 {
|
||||||
Some(value)
|
Some(value)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the size of the screen as determined by tput.
|
/// Returns the size of the screen as determined by tput.
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
//! WinAPI related logic for terminal manipulation.
|
//! WinAPI related logic for terminal manipulation.
|
||||||
|
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size};
|
use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size};
|
||||||
use winapi::{
|
use winapi::{
|
||||||
shared::minwindef::DWORD,
|
shared::minwindef::DWORD,
|
||||||
@ -190,9 +193,20 @@ pub(crate) fn set_size(width: u16, height: u16) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_window_title(title: &str) -> Result<()> {
|
pub(crate) fn set_window_title(title: impl fmt::Display) -> Result<()> {
|
||||||
let mut title: Vec<_> = title.encode_utf16().collect();
|
struct Utf16Encoder(Vec<u16>);
|
||||||
title.push(0);
|
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()) };
|
let result = unsafe { SetConsoleTitleW(title.as_ptr()) };
|
||||||
if result != 0 {
|
if result != 0 {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
Loading…
Reference in New Issue
Block a user