Vendor dependencies

Let's see how I like this workflow.
This commit is contained in:
John Doty 2022-12-19 08:27:18 -08:00
parent 34d1830413
commit 9c435dc440
7500 changed files with 1665121 additions and 99 deletions

48
vendor/crossterm/src/ansi_support.rs vendored Normal file
View file

@ -0,0 +1,48 @@
use std::sync::atomic::{AtomicBool, Ordering};
use crossterm_winapi::{ConsoleMode, Handle};
use parking_lot::Once;
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
use crate::Result;
/// Enable virtual terminal processing.
///
/// This method attempts to enable virtual terminal processing for this
/// console. If there was a problem enabling it, then an error returned.
/// On success, the caller may assume that enabling it was successful.
///
/// When virtual terminal processing is enabled, characters emitted to the
/// console are parsed for VT100 and similar control character sequences
/// that control color and other similar operations.
fn enable_vt_processing() -> Result<()> {
let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
let console_mode = ConsoleMode::from(Handle::current_out_handle()?);
let old_mode = console_mode.mode()?;
if old_mode & mask == 0 {
console_mode.set_mode(old_mode | mask)?;
}
Ok(())
}
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.
let supported = enable_vt_processing().is_ok()
|| std::env::var("TERM").map_or(false, |term| term != "dumb");
SUPPORTS_ANSI_ESCAPE_CODES.store(supported, Ordering::SeqCst);
});
SUPPORTS_ANSI_ESCAPE_CODES.load(Ordering::SeqCst)
}

230
vendor/crossterm/src/command.rs vendored Normal file
View file

@ -0,0 +1,230 @@
use std::fmt;
use std::io::{self, Write};
use super::error::Result;
/// An interface for a command that performs an action on the terminal.
///
/// Crossterm provides a set of commands,
/// and there is no immediate reason to implement a command yourself.
/// In order to understand how to use and execute commands,
/// it is recommended that you take a look at [Command API](./index.html#command-api) chapter.
pub trait Command {
/// Write an ANSI representation of this command to the given writer.
/// An ANSI code can manipulate the terminal by writing it to the terminal buffer.
/// However, only Windows 10 and UNIX systems support this.
///
/// This method does not need to be accessed manually, as it is used by the crossterm's [Command API](./index.html#command-api)
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result;
/// Execute this command.
///
/// Windows versions lower than windows 10 do not support ANSI escape codes,
/// therefore a direct WinAPI call is made.
///
/// This method does not need to be accessed manually, as it is used by the crossterm's [Command API](./index.html#command-api)
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()>;
/// Returns whether the ANSI code representation of this command is supported by windows.
///
/// A list of supported ANSI escape codes
/// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
super::ansi_support::supports_ansi()
}
}
impl<T: Command + ?Sized> Command for &T {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
(**self).write_ansi(f)
}
#[inline]
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
T::execute_winapi(self)
}
#[cfg(windows)]
#[inline]
fn is_ansi_code_supported(&self) -> bool {
T::is_ansi_code_supported(self)
}
}
/// An interface for types that can queue commands for further execution.
pub trait QueueableCommand {
/// Queues the given command for further execution.
fn queue(&mut self, command: impl Command) -> Result<&mut Self>;
}
/// An interface for types that can directly execute commands.
pub trait ExecutableCommand {
/// Executes the given command directly.
fn execute(&mut self, command: impl Command) -> Result<&mut Self>;
}
impl<T: Write + ?Sized> QueueableCommand for T {
/// Queues the given command for further execution.
///
/// Queued commands will be executed in the following cases:
///
/// * When `flush` is called manually on the given type implementing `io::Write`.
/// * The terminal will `flush` automatically if the buffer is full.
/// * Each line is flushed in case of `stdout`, because it is line buffered.
///
/// # Arguments
///
/// - [Command](./trait.Command.html)
///
/// The command that you want to queue for later execution.
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
///
/// use crossterm::{Result, QueueableCommand, style::Print};
///
/// fn main() -> Result<()> {
/// let mut stdout = stdout();
///
/// // `Print` will executed executed when `flush` is called.
/// stdout
/// .queue(Print("foo 1\n".to_string()))?
/// .queue(Print("foo 2".to_string()))?;
///
/// // some other code (no execution happening here) ...
///
/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
/// stdout.flush()?;
///
/// Ok(())
///
/// // ==== Output ====
/// // foo 1
/// // foo 2
/// }
/// ```
///
/// Have a look over at the [Command API](./index.html#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinAPI call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html)
/// and [queue](./trait.QueueableCommand.html) for those old Windows versions.
fn queue(&mut self, command: impl Command) -> Result<&mut Self> {
#[cfg(windows)]
if !command.is_ansi_code_supported() {
// There may be queued commands in this writer, but `execute_winapi` will execute the
// command immediately. To prevent commands being executed out of order we flush the
// writer now.
self.flush()?;
command.execute_winapi()?;
return Ok(self);
}
write_command_ansi(self, command)?;
Ok(self)
}
}
impl<T: Write + ?Sized> ExecutableCommand for T {
/// Executes the given command directly.
///
/// The given command its ANSI escape code will be written and flushed onto `Self`.
///
/// # Arguments
///
/// - [Command](./trait.Command.html)
///
/// The command that you want to execute directly.
///
/// # Example
///
/// ```rust
/// use std::io::{Write, stdout};
///
/// use crossterm::{Result, ExecutableCommand, style::Print};
///
/// fn main() -> Result<()> {
/// // will be executed directly
/// stdout()
/// .execute(Print("sum:\n".to_string()))?
/// .execute(Print(format!("1 + 1= {} ", 1 + 1)))?;
///
/// Ok(())
///
/// // ==== Output ====
/// // sum:
/// // 1 + 1 = 2
/// }
/// ```
///
/// Have a look over at the [Command API](./index.html#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinAPI call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html)
/// and [queue](./trait.QueueableCommand.html) for those old Windows versions.
fn execute(&mut self, command: impl Command) -> Result<&mut Self> {
self.queue(command)?;
self.flush()?;
Ok(self)
}
}
/// Writes the ANSI representation of a command to the given writer.
fn write_command_ansi<C: Command>(
io: &mut (impl io::Write + ?Sized),
command: C,
) -> io::Result<()> {
struct Adapter<T> {
inner: T,
res: io::Result<()>,
}
impl<T: Write> fmt::Write for Adapter<T> {
fn write_str(&mut self, s: &str) -> fmt::Result {
self.inner.write_all(s.as_bytes()).map_err(|e| {
self.res = Err(e);
fmt::Error
})
}
}
let mut adapter = Adapter {
inner: io,
res: Ok(()),
};
command
.write_ansi(&mut adapter)
.map_err(|fmt::Error| match adapter.res {
Ok(()) => panic!(
"<{}>::write_ansi incorrectly errored",
std::any::type_name::<C>()
),
Err(e) => e,
})
}
/// Executes the ANSI representation of a command, using the given `fmt::Write`.
pub(crate) fn execute_fmt(f: &mut impl fmt::Write, command: impl Command) -> fmt::Result {
#[cfg(windows)]
if !command.is_ansi_code_supported() {
return command.execute_winapi().map_err(|_| fmt::Error);
}
command.write_ansi(f)
}

499
vendor/crossterm/src/cursor.rs vendored Normal file
View file

@ -0,0 +1,499 @@
//! # Cursor
//!
//! The `cursor` module provides functionality to work with the terminal cursor.
//!
//! This documentation does not contain a lot of examples. The reason is that it's fairly
//! obvious how to use this crate. Although, we do provide
//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Examples
//!
//! Cursor actions can be performed with commands.
//! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation.
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{
//! ExecutableCommand, execute, Result,
//! cursor::{DisableBlinking, EnableBlinking, MoveTo, RestorePosition, SavePosition}
//! };
//!
//! fn main() -> Result<()> {
//! // with macro
//! execute!(
//! stdout(),
//! SavePosition,
//! MoveTo(10, 10),
//! EnableBlinking,
//! DisableBlinking,
//! RestorePosition
//! );
//!
//! // with function
//! stdout()
//! .execute(MoveTo(11,11))?
//! .execute(RestorePosition);
//!
//! Ok(())
//! }
//! ```
//!
//! For manual execution control check out [crossterm::queue](../macro.queue.html).
use std::fmt;
#[cfg(windows)]
use crate::Result;
use crate::{csi, impl_display, Command};
pub use sys::position;
pub(crate) mod sys;
/// A command that moves the terminal cursor to the given position (column, row).
///
/// # Notes
/// * Top left cell is represented as `0,0`.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveTo(pub u16, pub u16);
impl Command for MoveTo {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{};{}H"), self.1 + 1, self.0 + 1)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_to(self.0, self.1)
}
}
/// A command that moves the terminal cursor down the given number of lines,
/// and moves it to the first column.
///
/// # Notes
/// * This command is 1 based, meaning `MoveToNextLine(1)` moves to the next line.
/// * Most terminals default 0 argument to 1.
/// * 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 {
write!(f, csi!("{}E"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
if self.0 != 0 {
sys::move_to_next_line(self.0)?;
}
Ok(())
}
}
/// A command that moves the terminal cursor up the given number of lines,
/// and moves it to the first column.
///
/// # Notes
/// * This command is 1 based, meaning `MoveToPreviousLine(1)` moves to the previous line.
/// * Most terminals default 0 argument to 1.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveToPreviousLine(pub u16);
impl Command for MoveToPreviousLine {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}F"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
if self.0 != 0 {
sys::move_to_previous_line(self.0)?;
}
Ok(())
}
}
/// A command that moves the terminal cursor to the given column on the current row.
///
/// # Notes
/// * This command is 0 based, meaning 0 is the leftmost column.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveToColumn(pub u16);
impl Command for MoveToColumn {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}G"), self.0 + 1)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_to_column(self.0)
}
}
/// A command that moves the terminal cursor to the given row on the current column.
///
/// # Notes
/// * This command is 0 based, meaning 0 is the topmost row.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveToRow(pub u16);
impl Command for MoveToRow {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}d"), self.0 + 1)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_to_row(self.0)
}
}
/// A command that moves the terminal cursor a given number of rows up.
///
/// # Notes
/// * This command is 1 based, meaning `MoveUp(1)` moves the cursor up one cell.
/// * Most terminals default 0 argument to 1.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveUp(pub u16);
impl Command for MoveUp {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}A"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_up(self.0)
}
}
/// A command that moves the terminal cursor a given number of columns to the right.
///
/// # Notes
/// * This command is 1 based, meaning `MoveRight(1)` moves the cursor right one cell.
/// * Most terminals default 0 argument to 1.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveRight(pub u16);
impl Command for MoveRight {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}C"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_right(self.0)
}
}
/// A command that moves the terminal cursor a given number of rows down.
///
/// # Notes
/// * This command is 1 based, meaning `MoveDown(1)` moves the cursor down one cell.
/// * Most terminals default 0 argument to 1.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveDown(pub u16);
impl Command for MoveDown {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}B"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_down(self.0)
}
}
/// A command that moves the terminal cursor a given number of columns to the left.
///
/// # Notes
/// * This command is 1 based, meaning `MoveLeft(1)` moves the cursor left one cell.
/// * Most terminals default 0 argument to 1.
/// * Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MoveLeft(pub u16);
impl Command for MoveLeft {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}D"), self.0)?;
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::move_left(self.0)
}
}
/// A command that saves the current terminal cursor position.
///
/// See the [RestorePosition](./struct.RestorePosition.html) command.
///
/// # Notes
///
/// - The cursor position is stored globally.
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SavePosition;
impl Command for SavePosition {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str("\x1B7")
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::save_position()
}
}
/// A command that restores the saved terminal cursor position.
///
/// See the [SavePosition](./struct.SavePosition.html) command.
///
/// # Notes
///
/// - The cursor position is stored globally.
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RestorePosition;
impl Command for RestorePosition {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str("\x1B8")
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::restore_position()
}
}
/// A command that hides the terminal cursor.
///
/// # Notes
///
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Hide;
impl Command for Hide {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?25l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::show_cursor(false)
}
}
/// A command that shows the terminal cursor.
///
/// # Notes
///
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Show;
impl Command for Show {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?25h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::show_cursor(true)
}
}
/// A command that enables blinking of the terminal cursor.
///
/// # Notes
///
/// - Windows versions lower than Windows 10 do not support this functionality.
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableBlinking;
impl Command for EnableBlinking {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?12h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// A command that disables blinking of the terminal cursor.
///
/// # Notes
///
/// - Windows versions lower than Windows 10 do not support this functionality.
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableBlinking;
impl Command for DisableBlinking {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?12l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// All supported cursor shapes
///
/// # Note
///
/// - Used with SetCursorShape
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorShape {
UnderScore,
Line,
Block,
}
/// A command that sets the shape of the cursor
///
/// # Note
///
/// - Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetCursorShape(pub CursorShape);
impl Command for SetCursorShape {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
use CursorShape::*;
match self.0 {
UnderScore => f.write_str("\x1b[3 q"),
Line => f.write_str("\x1b[5 q"),
Block => f.write_str("\x1b[2 q"),
}
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
impl_display!(for MoveTo);
impl_display!(for MoveToColumn);
impl_display!(for MoveToRow);
impl_display!(for MoveToNextLine);
impl_display!(for MoveToPreviousLine);
impl_display!(for MoveUp);
impl_display!(for MoveDown);
impl_display!(for MoveLeft);
impl_display!(for MoveRight);
impl_display!(for SavePosition);
impl_display!(for RestorePosition);
impl_display!(for Hide);
impl_display!(for Show);
impl_display!(for EnableBlinking);
impl_display!(for DisableBlinking);
impl_display!(for SetCursorShape);
#[cfg(test)]
mod tests {
use std::io::{self, stdout};
use crate::execute;
use super::{
position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition,
};
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_move_to() {
let (saved_x, saved_y) = position().unwrap();
execute!(stdout(), MoveTo(saved_x + 1, saved_y + 1)).unwrap();
assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
execute!(stdout(), MoveTo(saved_x, saved_y)).unwrap();
assert_eq!(position().unwrap(), (saved_x, saved_y));
}
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_move_right() {
let (saved_x, saved_y) = position().unwrap();
execute!(io::stdout(), MoveRight(1)).unwrap();
assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
}
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_move_left() {
execute!(stdout(), MoveTo(2, 0), MoveLeft(2)).unwrap();
assert_eq!(position().unwrap(), (0, 0));
}
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_move_up() {
execute!(stdout(), MoveTo(0, 2), MoveUp(2)).unwrap();
assert_eq!(position().unwrap(), (0, 0));
}
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_move_down() {
execute!(stdout(), MoveTo(0, 0), MoveDown(2)).unwrap();
assert_eq!(position().unwrap(), (0, 2));
}
// Test is disabled, because it's failing on Travis
#[test]
#[ignore]
fn test_save_restore_position() {
let (saved_x, saved_y) = position().unwrap();
execute!(
stdout(),
SavePosition,
MoveTo(saved_x + 1, saved_y + 1),
RestorePosition
)
.unwrap();
let (x, y) = position().unwrap();
assert_eq!(x, saved_x);
assert_eq!(y, saved_y);
}
}

17
vendor/crossterm/src/cursor/sys.rs vendored Normal file
View file

@ -0,0 +1,17 @@
//! This module provides platform related functions.
#[cfg(unix)]
pub use self::unix::position;
#[cfg(windows)]
pub use self::windows::position;
#[cfg(windows)]
pub(crate) use self::windows::{
move_down, move_left, move_right, move_to, move_to_column, move_to_next_line,
move_to_previous_line, move_to_row, move_up, restore_position, save_position, show_cursor,
};
#[cfg(windows)]
pub(crate) mod windows;
#[cfg(unix)]
pub(crate) mod unix;

57
vendor/crossterm/src/cursor/sys/unix.rs vendored Normal file
View file

@ -0,0 +1,57 @@
use std::{
io::{self, Error, ErrorKind, Write},
time::Duration,
};
use crate::{
event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
Result,
};
/// Returns the cursor position (column, row).
///
/// The top left cell is represented as `(0, 0)`.
///
/// On unix systems, this function will block and possibly time out while
/// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
pub fn position() -> Result<(u16, u16)> {
if is_raw_mode_enabled() {
read_position_raw()
} else {
read_position()
}
}
fn read_position() -> Result<(u16, u16)> {
enable_raw_mode()?;
let pos = read_position_raw();
disable_raw_mode()?;
pos
}
fn read_position_raw() -> Result<(u16, u16)> {
// Use `ESC [ 6 n` to and retrieve the cursor position.
let mut stdout = io::stdout();
stdout.write_all(b"\x1B[6n")?;
stdout.flush()?;
loop {
match poll_internal(Some(Duration::from_millis(2000)), &CursorPositionFilter) {
Ok(true) => {
if let Ok(InternalEvent::CursorPosition(x, y)) =
read_internal(&CursorPositionFilter)
{
return Ok((x, y));
}
}
Ok(false) => {
return Err(Error::new(
ErrorKind::Other,
"The cursor position could not be read within a normal duration",
));
}
Err(_) => {}
}
}
}

View file

@ -0,0 +1,317 @@
//! WinAPI related logic to cursor manipulation.
use std::convert::TryFrom;
use std::io;
use std::sync::atomic::{AtomicU64, Ordering};
use crossterm_winapi::{result, Coord, Handle, HandleType, ScreenBuffer};
use winapi::{
shared::minwindef::{FALSE, TRUE},
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
};
use crate::Result;
/// 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.
// This results in an 1-based coord zo subtract 1 to make cursor position 0-based.
pub fn parse_relative_y(y: i16) -> Result<i16> {
let window = ScreenBuffer::current()?.info()?;
let window_size = window.terminal_window();
let screen_size = window.terminal_size();
if y <= screen_size.height {
Ok(y)
} else {
Ok(y - window_size.top)
}
}
/// Returns the cursor position (column, row).
///
/// The top left cell is represented `0,0`.
pub fn position() -> Result<(u16, u16)> {
let cursor = ScreenBufferCursor::output()?;
let mut position = cursor.position()?;
// if position.y != 0 {
position.y = parse_relative_y(position.y)?;
// }
Ok(position.into())
}
pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
}
pub(crate) fn move_to(column: u16, row: u16) -> Result<()> {
let cursor = ScreenBufferCursor::output()?;
cursor.move_to(column as i16, row as i16)?;
Ok(())
}
pub(crate) fn move_up(count: u16) -> Result<()> {
let (column, row) = position()?;
move_to(column, row - count)?;
Ok(())
}
pub(crate) fn move_right(count: u16) -> Result<()> {
let (column, row) = position()?;
move_to(column + count, row)?;
Ok(())
}
pub(crate) fn move_down(count: u16) -> Result<()> {
let (column, row) = position()?;
move_to(column, row + count)?;
Ok(())
}
pub(crate) fn move_left(count: u16) -> Result<()> {
let (column, row) = position()?;
move_to(column - count, row)?;
Ok(())
}
pub(crate) fn move_to_column(new_column: u16) -> Result<()> {
let (_, row) = position()?;
move_to(new_column, row)?;
Ok(())
}
pub(crate) fn move_to_row(new_row: u16) -> Result<()> {
let (col, _) = position()?;
move_to(col, new_row)?;
Ok(())
}
pub(crate) fn move_to_next_line(count: u16) -> Result<()> {
let (_, row) = position()?;
move_to(0, row + count)?;
Ok(())
}
pub(crate) fn move_to_previous_line(count: u16) -> Result<()> {
let (_, row) = position()?;
move_to(0, row - count)?;
Ok(())
}
pub(crate) fn save_position() -> Result<()> {
ScreenBufferCursor::output()?.save_position()?;
Ok(())
}
pub(crate) fn restore_position() -> Result<()> {
ScreenBufferCursor::output()?.restore_position()?;
Ok(())
}
/// WinAPI wrapper over terminal cursor behaviour.
struct ScreenBufferCursor {
screen_buffer: ScreenBuffer,
}
impl ScreenBufferCursor {
fn output() -> Result<ScreenBufferCursor> {
Ok(ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
})
}
fn position(&self) -> Result<Coord> {
Ok(self.screen_buffer.info()?.cursor_pos())
}
fn move_to(&self, x: i16, y: i16) -> Result<()> {
if x < 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Argument Out of Range Exception when setting cursor position to X: {}",
x
),
));
}
if y < 0 {
return Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Argument Out of Range Exception when setting cursor position to Y: {}",
y
),
));
}
let position = COORD { X: x, Y: y };
unsafe {
if result(SetConsoleCursorPosition(
**self.screen_buffer.handle(),
position,
))
.is_err()
{
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn set_visibility(&self, visible: bool) -> Result<()> {
let cursor_info = CONSOLE_CURSOR_INFO {
dwSize: 100,
bVisible: if visible { TRUE } else { FALSE },
};
unsafe {
if result(SetConsoleCursorInfo(
**self.screen_buffer.handle(),
&cursor_info,
))
.is_err()
{
return Err(io::Error::last_os_error());
}
}
Ok(())
}
fn restore_position(&self) -> Result<()> {
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)?;
}
Ok(())
}
fn save_position(&self) -> Result<()> {
let position = self.position()?;
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(())
}
}
impl From<Handle> for ScreenBufferCursor {
fn from(handle: Handle) -> Self {
ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}
#[cfg(test)]
mod tests {
use super::{
move_down, move_left, move_right, move_to, move_to_column, move_to_next_line,
move_to_previous_line, move_to_row, move_up, position, restore_position, save_position,
};
#[test]
fn test_move_to_winapi() {
let (saved_x, saved_y) = position().unwrap();
move_to(saved_x + 1, saved_y + 1).unwrap();
assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
move_to(saved_x, saved_y).unwrap();
assert_eq!(position().unwrap(), (saved_x, saved_y));
}
#[test]
fn test_move_right_winapi() {
let (saved_x, saved_y) = position().unwrap();
move_right(1).unwrap();
assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
}
#[test]
fn test_move_left_winapi() {
move_to(2, 0).unwrap();
move_left(2).unwrap();
assert_eq!(position().unwrap(), (0, 0));
}
#[test]
fn test_move_up_winapi() {
move_to(0, 2).unwrap();
move_up(2).unwrap();
assert_eq!(position().unwrap(), (0, 0));
}
#[test]
fn test_move_to_next_line_winapi() {
move_to(0, 2).unwrap();
move_to_next_line(2).unwrap();
assert_eq!(position().unwrap(), (0, 4));
}
#[test]
fn test_move_to_previous_line_winapi() {
move_to(0, 2).unwrap();
move_to_previous_line(2).unwrap();
assert_eq!(position().unwrap(), (0, 0));
}
#[test]
fn test_move_to_column_winapi() {
move_to(0, 2).unwrap();
move_to_column(12).unwrap();
assert_eq!(position().unwrap(), (12, 2));
}
#[test]
fn test_move_to_row_winapi() {
move_to(0, 2).unwrap();
move_to_row(5).unwrap();
assert_eq!(position().unwrap(), (0, 5));
}
#[test]
fn test_move_down_winapi() {
move_to(0, 0).unwrap();
move_down(2).unwrap();
assert_eq!(position().unwrap(), (0, 2));
}
#[test]
fn test_save_restore_position_winapi() {
let (saved_x, saved_y) = position().unwrap();
save_position().unwrap();
move_to(saved_x + 1, saved_y + 1).unwrap();
restore_position().unwrap();
let (x, y) = position().unwrap();
assert_eq!(x, saved_x);
assert_eq!(y, saved_y);
}
}

8
vendor/crossterm/src/error.rs vendored Normal file
View file

@ -0,0 +1,8 @@
//! Module containing error handling logic.
use std::io;
/// The `crossterm` result type.
pub type Result<T> = std::result::Result<T, ErrorKind>;
pub type ErrorKind = io::Error;

973
vendor/crossterm/src/event.rs vendored Normal file
View file

@ -0,0 +1,973 @@
//! # Event
//!
//! The `event` module provides the functionality to read keyboard, mouse and terminal resize events.
//!
//! * The [`read`](fn.read.html) function returns an [`Event`](enum.Event.html) immediately
//! (if available) or blocks until an [`Event`](enum.Event.html) is available.
//!
//! * The [`poll`](fn.poll.html) function allows you to check if there is or isn't an [`Event`](enum.Event.html) available
//! within the given period of time. In other words - if subsequent call to the [`read`](fn.read.html)
//! function will block or not.
//!
//! It's **not allowed** to call these functions from different threads or combine them with the
//! [`EventStream`](struct.EventStream.html). You're allowed to either:
//!
//! * use the [`read`](fn.read.html) & [`poll`](fn.poll.html) functions on any, but same, thread
//! * or the [`EventStream`](struct.EventStream.html).
//!
//! **Make sure to enable [raw mode](../terminal/index.html#raw-mode) in order for keyboard events to work properly**
//!
//! ## Mouse Events
//!
//! Mouse events are not enabled by default. You have to enable them with the
//! [`EnableMouseCapture`](struct.EnableMouseCapture.html) command. See [Command API](../index.html#command-api)
//! for more information.
//!
//! ## Examples
//!
//! Blocking read:
//!
//! ```no_run
//! use crossterm::event::{read, Event};
//!
//! fn print_events() -> crossterm::Result<()> {
//! loop {
//! // `read()` blocks until an `Event` is available
//! match read()? {
//! Event::FocusGained => println!("FocusGained"),
//! Event::FocusLost => println!("FocusLost"),
//! Event::Key(event) => println!("{:?}", event),
//! Event::Mouse(event) => println!("{:?}", event),
//! #[cfg(feature = "bracketed-paste")]
//! Event::Paste(data) => println!("{:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! }
//! }
//! Ok(())
//! }
//! ```
//!
//! Non-blocking read:
//!
//! ```no_run
//! use std::time::Duration;
//!
//! use crossterm::event::{poll, read, Event};
//!
//! fn print_events() -> crossterm::Result<()> {
//! loop {
//! // `poll()` waits for an `Event` for a given time period
//! if poll(Duration::from_millis(500))? {
//! // It's guaranteed that the `read()` won't block when the `poll()`
//! // function returns `true`
//! match read()? {
//! Event::FocusGained => println!("FocusGained"),
//! Event::FocusLost => println!("FocusLost"),
//! Event::Key(event) => println!("{:?}", event),
//! Event::Mouse(event) => println!("{:?}", event),
//! #[cfg(feature = "bracketed-paste")]
//! Event::Paste(data) => println!("Pasted {:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! }
//! } else {
//! // Timeout expired and no `Event` is available
//! }
//! }
//! Ok(())
//! }
//! ```
//!
//! Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder for more of
//! them (`event-*`).
use std::fmt;
use std::hash::{Hash, Hasher};
#[cfg(windows)]
use std::io;
use std::time::Duration;
use bitflags::bitflags;
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{csi, Command, Result};
use filter::{EventFilter, Filter};
use read::InternalEventReader;
#[cfg(feature = "event-stream")]
pub use stream::EventStream;
use timeout::PollTimeout;
pub(crate) mod filter;
mod read;
mod source;
#[cfg(feature = "event-stream")]
mod stream;
pub(crate) mod sys;
mod timeout;
/// Static instance of `InternalEventReader`.
/// This needs to be static because there can be one event reader.
static INTERNAL_EVENT_READER: Mutex<Option<InternalEventReader>> = 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<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.
///
/// Returns `Ok(true)` if an [`Event`](enum.Event.html) is available otherwise it returns `Ok(false)`.
///
/// `Ok(true)` guarantees that subsequent call to the [`read`](fn.read.html) function
/// won't block.
///
/// # Arguments
///
/// * `timeout` - maximum waiting time for event availability
///
/// # Examples
///
/// Return immediately:
///
/// ```no_run
/// use std::time::Duration;
///
/// use crossterm::{event::poll, Result};
///
/// fn is_event_available() -> Result<bool> {
/// // Zero duration says that the `poll` function must return immediately
/// // with an `Event` availability information
/// poll(Duration::from_secs(0))
/// }
/// ```
///
/// Wait up to 100ms:
///
/// ```no_run
/// use std::time::Duration;
///
/// use crossterm::{event::poll, Result};
///
/// fn is_event_available() -> Result<bool> {
/// // Wait for an `Event` availability for 100ms. It returns immediately
/// // if an `Event` is/becomes available.
/// poll(Duration::from_millis(100))
/// }
/// ```
pub fn poll(timeout: Duration) -> Result<bool> {
poll_internal(Some(timeout), &EventFilter)
}
/// Reads a single [`Event`](enum.Event.html).
///
/// This function blocks until an [`Event`](enum.Event.html) is available. Combine it with the
/// [`poll`](fn.poll.html) function to get non-blocking reads.
///
/// # Examples
///
/// Blocking read:
///
/// ```no_run
/// use crossterm::{event::read, Result};
///
/// fn print_events() -> Result<bool> {
/// loop {
/// // Blocks until an `Event` is available
/// println!("{:?}", read()?);
/// }
/// }
/// ```
///
/// Non-blocking read:
///
/// ```no_run
/// use std::time::Duration;
///
/// use crossterm::{event::{read, poll}, Result};
///
/// fn print_events() -> Result<bool> {
/// loop {
/// if poll(Duration::from_millis(100))? {
/// // It's guaranteed that `read` won't block, because `poll` returned
/// // `Ok(true)`.
/// println!("{:?}", read()?);
/// } else {
/// // Timeout expired, no `Event` is available
/// }
/// }
/// }
/// ```
pub fn read() -> Result<Event> {
match read_internal(&EventFilter)? {
InternalEvent::Event(event) => Ok(event),
#[cfg(unix)]
_ => unreachable!(),
}
}
/// Polls to check if there are any `InternalEvent`s that can be read within the given duration.
pub(crate) fn poll_internal<F>(timeout: Option<Duration>, filter: &F) -> Result<bool>
where
F: Filter,
{
let (mut reader, timeout) = if let Some(timeout) = timeout {
let poll_timeout = PollTimeout::new(Some(timeout));
if let Some(reader) = try_lock_internal_event_reader_for(timeout) {
(reader, poll_timeout.leftover())
} else {
return Ok(false);
}
} else {
(lock_internal_event_reader(), None)
};
reader.poll(timeout, filter)
}
/// Reads a single `InternalEvent`.
pub(crate) fn read_internal<F>(filter: &F) -> Result<InternalEvent>
where
F: Filter,
{
let mut reader = lock_internal_event_reader();
reader.read(filter)
}
/// A command that enables mouse event capturing.
///
/// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableMouseCapture;
impl Command for EnableMouseCapture {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
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)]
fn execute_winapi(&self) -> Result<()> {
sys::windows::enable_mouse_capture()
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}
/// A command that disables mouse event capturing.
///
/// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableMouseCapture;
impl Command for DisableMouseCapture {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(concat!(
// The inverse commands of EnableMouseCapture, in reverse order.
csi!("?1006l"),
csi!("?1015l"),
csi!("?1003l"),
csi!("?1002l"),
csi!("?1000l"),
))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::windows::disable_mouse_capture()
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}
bitflags! {
/// Represents special flags that tell compatible terminals to add extra information to keyboard events.
///
/// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement> for more information.
///
/// Alternate keys and Unicode codepoints are not yet supported by crossterm.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardEnhancementFlags: u8 {
/// Represent Escape and modified keys using CSI-u sequences, so they can be unambiguously
/// read.
const DISAMBIGUATE_ESCAPE_CODES = 0b0000_0001;
/// Add extra events with [`KeyEvent.kind`] set to [`KeyEventKind::Repeat`] or
/// [`KeyEventKind::Release`] when keys are autorepeated or released.
const REPORT_EVENT_TYPES = 0b0000_0010;
// Send [alternate keycodes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#key-codes)
// in addition to the base keycode.
//
// *Note*: these are not yet supported by crossterm.
// const REPORT_ALTERNATE_KEYS = 0b0000_0100;
/// Represent all keyboard events as CSI-u sequences. This is required to get repeat/release
/// events for plain-text keys.
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b0000_1000;
// Send the Unicode codepoint as well as the keycode.
//
// *Note*: this is not yet supported by crossterm.
// const REPORT_ASSOCIATED_TEXT = 0b0001_0000;
}
}
/// A command that enables the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which adds extra information to keyboard events and removes ambiguity for modifier keys.
///
/// It should be paired with [`PopKeyboardEnhancementFlags`] at the end of execution.
///
/// Example usage:
/// ```no_run
/// use std::io::{Write, stdout};
/// use crossterm::execute;
/// use crossterm::event::{
/// KeyboardEnhancementFlags,
/// PushKeyboardEnhancementFlags,
/// PopKeyboardEnhancementFlags
/// };
///
/// let mut stdout = stdout();
///
/// execute!(
/// stdout,
/// PushKeyboardEnhancementFlags(
/// KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
/// )
/// );
///
/// // ...
///
/// execute!(stdout, PopKeyboardEnhancementFlags);
/// ```
///
/// Note that, currently, only the following support this protocol:
/// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
/// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
/// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
/// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
/// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
/// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
/// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PushKeyboardEnhancementFlags(pub KeyboardEnhancementFlags);
impl Command for PushKeyboardEnhancementFlags {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "{}{}u", csi!(">"), self.0.bits())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
))
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}
/// A command that disables extra kinds of keyboard events.
///
/// Specifically, it pops one level of keyboard enhancement flags.
///
/// See [`PushKeyboardEnhancementFlags`] and <https://sw.kovidgoyal.net/kitty/keyboard-protocol/> for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PopKeyboardEnhancementFlags;
impl Command for PopKeyboardEnhancementFlags {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("<1u"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented for the legacy Windows API.",
))
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}
/// A command that enables focus event emission.
///
/// It should be paired with [`DisableFocusChange`] at the end of execution.
///
/// Focus events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableFocusChange;
impl Command for EnableFocusChange {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?1004h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// Focus events are always enabled on Windows
Ok(())
}
}
/// A command that disables focus event emission.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableFocusChange;
impl Command for DisableFocusChange {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?1004l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// Focus events can't be disabled on Windows
Ok(())
}
}
/// A command that enables [bracketed paste mode](https://en.wikipedia.org/wiki/Bracketed-paste).
///
/// It should be paired with [`DisableBracketedPaste`] at the end of execution.
///
/// This is not supported in older Windows terminals without
/// [virtual terminal sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
#[cfg(feature = "bracketed-paste")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableBracketedPaste;
#[cfg(feature = "bracketed-paste")]
impl Command for EnableBracketedPaste {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?2004h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Bracketed paste not implemented in the legacy Windows API.",
))
}
}
/// A command that disables bracketed paste mode.
#[cfg(feature = "bracketed-paste")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableBracketedPaste;
#[cfg(feature = "bracketed-paste")]
impl Command for DisableBracketedPaste {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?2004l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// Represents an event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(not(feature = "bracketed-paste"), derive(Copy))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
pub enum Event {
/// The terminal gained focus
FocusGained,
/// The terminal lost focus
FocusLost,
/// A single key event with additional pressed modifiers.
Key(KeyEvent),
/// A single mouse event with additional pressed modifiers.
Mouse(MouseEvent),
/// A string that was pasted into the terminal. Only emitted if bracketed paste has been
/// enabled.
#[cfg(feature = "bracketed-paste")]
Paste(String),
/// An resize event with new dimensions after resize (columns, rows).
/// **Note** that resize events can be occur in batches.
Resize(u16, u16),
}
/// Represents a mouse event.
///
/// # Platform-specific Notes
///
/// ## Mouse Buttons
///
/// Some platforms/terminals do not report mouse button for the
/// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left`
/// is returned if we don't know which button was used.
///
/// ## Key Modifiers
///
/// Some platforms/terminals does not report all key modifiers
/// combinations for all mouse event types. For example - macOS reports
/// `Ctrl` + left mouse button click as a right mouse button click.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub struct MouseEvent {
/// The kind of mouse event that was caused.
pub kind: MouseEventKind,
/// The column that the event occurred on.
pub column: u16,
/// The row that the event occurred on.
pub row: u16,
/// The key modifiers active when the event occurred.
pub modifiers: KeyModifiers,
}
/// A mouse event kind.
///
/// # Platform-specific Notes
///
/// ## Mouse Buttons
///
/// Some platforms/terminals do not report mouse button for the
/// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left`
/// is returned if we don't know which button was used.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MouseEventKind {
/// Pressed mouse button. Contains the button that was pressed.
Down(MouseButton),
/// Released mouse button. Contains the button that was released.
Up(MouseButton),
/// Moved the mouse cursor while pressing the contained mouse button.
Drag(MouseButton),
/// Moved the mouse cursor while not pressing a mouse button.
Moved,
/// Scrolled mouse wheel downwards (towards the user).
ScrollDown,
/// Scrolled mouse wheel upwards (away from the user).
ScrollUp,
}
/// Represents a mouse button.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum MouseButton {
/// Left mouse button.
Left,
/// Right mouse button.
Right,
/// Middle mouse button.
Middle,
}
bitflags! {
/// Represents key modifiers (shift, control, alt, etc.).
///
/// **Note:** `SUPER`, `HYPER`, and `META` can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyModifiers: u8 {
const SHIFT = 0b0000_0001;
const CONTROL = 0b0000_0010;
const ALT = 0b0000_0100;
const SUPER = 0b0000_1000;
const HYPER = 0b0001_0000;
const META = 0b0010_0000;
const NONE = 0b0000_0000;
}
}
/// Represents a keyboard event kind.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum KeyEventKind {
Press,
Repeat,
Release,
}
bitflags! {
/// Represents extra state about the key event.
///
/// **Note:** This state can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyEventState: u8 {
/// The key event origins from the keypad.
const KEYPAD = 0b0000_0001;
/// Caps Lock was enabled for this key event.
///
/// **Note:** this is set for the initial press of Num Lock itself.
const CAPS_LOCK = 0b0000_1000;
/// Num Lock was enabled for this key event.
///
/// **Note:** this is set for the initial press of Num Lock itself.
const NUM_LOCK = 0b0000_1000;
const NONE = 0b0000_0000;
}
}
/// Represents a key event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, Clone, Copy)]
pub struct KeyEvent {
/// The key itself.
pub code: KeyCode,
/// Additional key modifiers.
pub modifiers: KeyModifiers,
/// Kind of event.
pub kind: KeyEventKind,
/// Keyboard state.
///
/// Only set if [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
pub state: KeyEventState,
}
impl KeyEvent {
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
pub const fn new_with_kind(
code: KeyCode,
modifiers: KeyModifiers,
kind: KeyEventKind,
) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind,
state: KeyEventState::empty(),
}
}
pub const fn new_with_kind_and_state(
code: KeyCode,
modifiers: KeyModifiers,
kind: KeyEventKind,
state: KeyEventState,
) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind,
state,
}
}
// modifies the KeyEvent,
// so that KeyModifiers::SHIFT is present iff
// an uppercase char is present.
fn normalize_case(mut self) -> KeyEvent {
let c = match self.code {
KeyCode::Char(c) => c,
_ => return self,
};
if c.is_ascii_uppercase() {
self.modifiers.insert(KeyModifiers::SHIFT);
} else if self.modifiers.contains(KeyModifiers::SHIFT) {
self.code = KeyCode::Char(c.to_ascii_uppercase())
}
self
}
}
impl From<KeyCode> for KeyEvent {
fn from(code: KeyCode) -> Self {
KeyEvent {
code,
modifiers: KeyModifiers::empty(),
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}
}
impl PartialEq for KeyEvent {
fn eq(&self, other: &KeyEvent) -> bool {
let KeyEvent {
code: lhs_code,
modifiers: lhs_modifiers,
kind: lhs_kind,
state: lhs_state,
} = self.normalize_case();
let KeyEvent {
code: rhs_code,
modifiers: rhs_modifiers,
kind: rhs_kind,
state: rhs_state,
} = other.normalize_case();
(lhs_code == rhs_code)
&& (lhs_modifiers == rhs_modifiers)
&& (lhs_kind == rhs_kind)
&& (lhs_state == rhs_state)
}
}
impl Eq for KeyEvent {}
impl Hash for KeyEvent {
fn hash<H: Hasher>(&self, hash_state: &mut H) {
let KeyEvent {
code,
modifiers,
kind,
state,
} = self.normalize_case();
code.hash(hash_state);
modifiers.hash(hash_state);
kind.hash(hash_state);
state.hash(hash_state);
}
}
/// Represents a media key (as part of [`KeyCode::Media`]).
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MediaKeyCode {
/// Play media key.
Play,
/// Pause media key.
Pause,
/// Play/Pause media key.
PlayPause,
/// Reverse media key.
Reverse,
/// Stop media key.
Stop,
/// Fast-forward media key.
FastForward,
/// Rewind media key.
Rewind,
/// Next-track media key.
TrackNext,
/// Previous-track media key.
TrackPrevious,
/// Record media key.
Record,
/// Lower-volume media key.
LowerVolume,
/// Raise-volume media key.
RaiseVolume,
/// Mute media key.
MuteVolume,
}
/// Represents a modifier key (as part of [`KeyCode::Modifier`]).
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ModifierKeyCode {
/// Left Shift key.
LeftShift,
/// Left Control key.
LeftControl,
/// Left Alt key.
LeftAlt,
/// Left Super key.
LeftSuper,
/// Left Hyper key.
LeftHyper,
/// Left Meta key.
LeftMeta,
/// Right Shift key.
RightShift,
/// Right Control key.
RightControl,
/// Right Alt key.
RightAlt,
/// Right Super key.
RightSuper,
/// Right Hyper key.
RightHyper,
/// Right Meta key.
RightMeta,
/// Iso Level3 Shift key.
IsoLevel3Shift,
/// Iso Level5 Shift key.
IsoLevel5Shift,
}
/// Represents a key.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyCode {
/// Backspace key.
Backspace,
/// Enter key.
Enter,
/// Left arrow key.
Left,
/// Right arrow key.
Right,
/// Up arrow key.
Up,
/// Down arrow key.
Down,
/// Home key.
Home,
/// End key.
End,
/// Page up key.
PageUp,
/// Page down key.
PageDown,
/// Tab key.
Tab,
/// Shift + Tab key.
BackTab,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// F key.
///
/// `KeyCode::F(1)` represents F1 key, etc.
F(u8),
/// A character.
///
/// `KeyCode::Char('c')` represents `c` character, etc.
Char(char),
/// Null.
Null,
/// Escape key.
Esc,
/// Caps Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
CapsLock,
/// Scroll Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
ScrollLock,
/// Num Lock key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
NumLock,
/// Print Screen key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
PrintScreen,
/// Pause key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Pause,
/// Menu key.
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Menu,
/// The "Begin" key (often mapped to the 5 key when Num Lock is turned on).
///
/// **Note:** this key can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
KeypadBegin,
/// A media key.
///
/// **Note:** these keys can only be read if
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
/// [`PushKeyboardEnhancementFlags`].
Media(MediaKeyCode),
/// A modifier key.
///
/// **Note:** these keys can only be read if **both**
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] have been enabled with
/// [`PushKeyboardEnhancementFlags`].
Modifier(ModifierKeyCode),
}
/// An internal event.
///
/// Encapsulates publicly available `Event` with additional internal
/// events that shouldn't be publicly available to the crate users.
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Eq)]
pub(crate) enum InternalEvent {
/// An event.
Event(Event),
/// A cursor position (`col`, `row`).
#[cfg(unix)]
CursorPosition(u16, u16),
}
#[cfg(test)]
mod tests {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use super::{KeyCode, KeyEvent, KeyModifiers};
#[test]
fn test_equality() {
let lowercase_d_with_shift = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT);
let uppercase_d_with_shift = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT);
let uppercase_d = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE);
assert_eq!(lowercase_d_with_shift, uppercase_d_with_shift);
assert_eq!(uppercase_d, uppercase_d_with_shift);
}
#[test]
fn test_hash() {
let lowercase_d_with_shift_hash = {
let mut hasher = DefaultHasher::new();
KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT).hash(&mut hasher);
hasher.finish()
};
let uppercase_d_with_shift_hash = {
let mut hasher = DefaultHasher::new();
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT).hash(&mut hasher);
hasher.finish()
};
let uppercase_d_hash = {
let mut hasher = DefaultHasher::new();
KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE).hash(&mut hasher);
hasher.finish()
};
assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash);
assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash);
}
}

68
vendor/crossterm/src/event/filter.rs vendored Normal file
View file

@ -0,0 +1,68 @@
use crate::event::InternalEvent;
/// Interface for filtering an `InternalEvent`.
pub(crate) trait Filter: Send + Sync + 'static {
/// Returns whether the given event fulfills the filter.
fn eval(&self, event: &InternalEvent) -> bool;
}
#[cfg(unix)]
#[derive(Debug, Clone)]
pub(crate) struct CursorPositionFilter;
#[cfg(unix)]
impl Filter for CursorPositionFilter {
fn eval(&self, event: &InternalEvent) -> bool {
matches!(*event, InternalEvent::CursorPosition(_, _))
}
}
#[derive(Debug, Clone)]
pub(crate) struct EventFilter;
impl Filter for EventFilter {
#[cfg(unix)]
fn eval(&self, event: &InternalEvent) -> bool {
matches!(*event, InternalEvent::Event(_))
}
#[cfg(windows)]
fn eval(&self, _: &InternalEvent) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub(crate) struct InternalEventFilter;
impl Filter for InternalEventFilter {
fn eval(&self, _: &InternalEvent) -> bool {
true
}
}
#[cfg(test)]
#[cfg(unix)]
mod tests {
use super::{
super::Event, CursorPositionFilter, EventFilter, Filter, InternalEvent, InternalEventFilter,
};
#[test]
fn test_cursor_position_filter_filters_cursor_position() {
assert!(!CursorPositionFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
assert!(CursorPositionFilter.eval(&InternalEvent::CursorPosition(0, 0)));
}
#[test]
fn test_event_filter_filters_events() {
assert!(EventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
assert!(!EventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
}
#[test]
fn test_event_filter_filters_internal_events() {
assert!(InternalEventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
assert!(InternalEventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
}
}

445
vendor/crossterm/src/event/read.rs vendored Normal file
View file

@ -0,0 +1,445 @@
use std::{collections::vec_deque::VecDeque, io, time::Duration};
#[cfg(unix)]
use super::source::unix::UnixInternalEventSource;
#[cfg(windows)]
use super::source::windows::WindowsEventSource;
#[cfg(feature = "event-stream")]
use super::sys::Waker;
use super::{filter::Filter, source::EventSource, timeout::PollTimeout, InternalEvent, Result};
/// Can be used to read `InternalEvent`s.
pub(crate) struct InternalEventReader {
events: VecDeque<InternalEvent>,
source: Option<Box<dyn EventSource>>,
skipped_events: Vec<InternalEvent>,
}
impl Default for InternalEventReader {
fn default() -> Self {
#[cfg(windows)]
let source = WindowsEventSource::new();
#[cfg(unix)]
let source = UnixInternalEventSource::new();
let source = source.ok().map(|x| Box::new(x) as Box<dyn EventSource>);
InternalEventReader {
source,
events: VecDeque::with_capacity(32),
skipped_events: Vec::with_capacity(32),
}
}
}
impl InternalEventReader {
/// Returns a `Waker` allowing to wake/force the `poll` method to return `Ok(false)`.
#[cfg(feature = "event-stream")]
pub(crate) fn waker(&self) -> Waker {
self.source.as_ref().expect("reader source not set").waker()
}
pub(crate) fn poll<F>(&mut self, timeout: Option<Duration>, filter: &F) -> Result<bool>
where
F: Filter,
{
for event in &self.events {
if filter.eval(event) {
return Ok(true);
}
}
let event_source = match self.source.as_mut() {
Some(source) => source,
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
"Failed to initialize input reader",
))
}
};
let poll_timeout = PollTimeout::new(timeout);
loop {
let maybe_event = match event_source.try_read(poll_timeout.leftover()) {
Ok(None) => None,
Ok(Some(event)) => {
if filter.eval(&event) {
Some(event)
} else {
self.skipped_events.push(event);
None
}
}
Err(e) => {
if e.kind() == io::ErrorKind::Interrupted {
return Ok(false);
}
return Err(e);
}
};
if poll_timeout.elapsed() || maybe_event.is_some() {
self.events.extend(self.skipped_events.drain(..));
if let Some(event) = maybe_event {
self.events.push_front(event);
return Ok(true);
}
return Ok(false);
}
}
}
pub(crate) fn read<F>(&mut self, filter: &F) -> Result<InternalEvent>
where
F: Filter,
{
let mut skipped_events = VecDeque::new();
loop {
while let Some(event) = self.events.pop_front() {
if filter.eval(&event) {
while let Some(event) = skipped_events.pop_front() {
self.events.push_back(event);
}
return Ok(event);
} else {
// We can not directly write events back to `self.events`.
// If we did, we would put our self's into an endless loop
// that would enqueue -> dequeue -> enqueue etc.
// This happens because `poll` in this function will always return true if there are events in it's.
// And because we just put the non-fulfilling event there this is going to be the case.
// Instead we can store them into the temporary buffer,
// and then when the filter is fulfilled write all events back in order.
skipped_events.push_back(event);
}
}
let _ = self.poll(None, filter)?;
}
}
}
#[cfg(test)]
mod tests {
use std::io;
use std::{collections::VecDeque, time::Duration};
use crate::ErrorKind;
#[cfg(unix)]
use super::super::filter::CursorPositionFilter;
use super::{
super::{filter::InternalEventFilter, Event},
EventSource, InternalEvent, InternalEventReader,
};
#[test]
fn test_poll_fails_without_event_source() {
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert!(reader.poll(None, &InternalEventFilter).is_err());
assert!(reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.is_err());
assert!(reader
.poll(Some(Duration::from_secs(10)), &InternalEventFilter)
.is_err());
}
#[test]
fn test_poll_returns_true_for_matching_event_in_queue_at_front() {
let mut reader = InternalEventReader {
events: vec![InternalEvent::Event(Event::Resize(10, 10))].into(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert!(reader.poll(None, &InternalEventFilter).unwrap());
}
#[test]
#[cfg(unix)]
fn test_poll_returns_true_for_matching_event_in_queue_at_back() {
let mut reader = InternalEventReader {
events: vec![
InternalEvent::Event(Event::Resize(10, 10)),
InternalEvent::CursorPosition(10, 20),
]
.into(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert!(reader.poll(None, &CursorPositionFilter).unwrap());
}
#[test]
fn test_read_returns_matching_event_in_queue_at_front() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let mut reader = InternalEventReader {
events: vec![EVENT].into(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
}
#[test]
#[cfg(unix)]
fn test_read_returns_matching_event_in_queue_at_back() {
const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20);
let mut reader = InternalEventReader {
events: vec![InternalEvent::Event(Event::Resize(10, 10)), CURSOR_EVENT].into(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT);
}
#[test]
#[cfg(unix)]
fn test_read_does_not_consume_skipped_event() {
const SKIPPED_EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20);
let mut reader = InternalEventReader {
events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(),
source: None,
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT);
assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT);
}
#[test]
fn test_poll_timeouts_if_source_has_no_events() {
let source = FakeSource::default();
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert!(!reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.unwrap());
}
#[test]
fn test_poll_returns_true_if_source_has_at_least_one_event() {
let source = FakeSource::with_events(&[InternalEvent::Event(Event::Resize(10, 10))]);
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert!(reader.poll(None, &InternalEventFilter).unwrap());
assert!(reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.unwrap());
}
#[test]
fn test_reads_returns_event_if_source_has_at_least_one_event() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let source = FakeSource::with_events(&[EVENT]);
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
}
#[test]
fn test_read_returns_events_if_source_has_events() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]);
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
}
#[test]
fn test_poll_returns_false_after_all_source_events_are_consumed() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]);
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert!(!reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.unwrap());
}
#[test]
fn test_poll_propagates_error() {
let source = FakeSource::with_error(ErrorKind::from(io::ErrorKind::Other));
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(
reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.err()
.map(|e| format!("{:?}", &e)),
Some(format!("{:?}", ErrorKind::from(io::ErrorKind::Other)))
);
}
#[test]
fn test_read_propagates_error() {
let source = FakeSource::with_error(ErrorKind::from(io::ErrorKind::Other));
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(
reader
.read(&InternalEventFilter)
.err()
.map(|e| format!("{:?}", &e)),
Some(format!("{:?}", ErrorKind::from(io::ErrorKind::Other)))
);
}
#[test]
fn test_poll_continues_after_error() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let source = FakeSource::new(&[EVENT, EVENT], ErrorKind::from(io::ErrorKind::Other));
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert!(reader.read(&InternalEventFilter).is_err());
assert!(reader
.poll(Some(Duration::from_secs(0)), &InternalEventFilter)
.unwrap());
}
#[test]
fn test_read_continues_after_error() {
const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10));
let source = FakeSource::new(&[EVENT, EVENT], ErrorKind::from(io::ErrorKind::Other));
let mut reader = InternalEventReader {
events: VecDeque::new(),
source: Some(Box::new(source)),
skipped_events: Vec::with_capacity(32),
};
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
assert!(reader.read(&InternalEventFilter).is_err());
assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT);
}
#[derive(Default)]
struct FakeSource {
events: VecDeque<InternalEvent>,
error: Option<ErrorKind>,
}
impl FakeSource {
fn new(events: &[InternalEvent], error: ErrorKind) -> FakeSource {
FakeSource {
events: events.to_vec().into(),
error: Some(error),
}
}
fn with_events(events: &[InternalEvent]) -> FakeSource {
FakeSource {
events: events.to_vec().into(),
error: None,
}
}
fn with_error(error: ErrorKind) -> FakeSource {
FakeSource {
events: VecDeque::new(),
error: Some(error),
}
}
}
impl EventSource for FakeSource {
fn try_read(
&mut self,
_timeout: Option<Duration>,
) -> Result<Option<InternalEvent>, ErrorKind> {
// Return error if set in case there's just one remaining event
if self.events.len() == 1 {
if let Some(error) = self.error.take() {
return Err(error);
}
}
// Return all events from the queue
if let Some(event) = self.events.pop_front() {
return Ok(Some(event));
}
// Return error if there're no more events
if let Some(error) = self.error.take() {
return Err(error);
}
// Timeout
Ok(None)
}
#[cfg(feature = "event-stream")]
fn waker(&self) -> super::super::sys::Waker {
unimplemented!();
}
}
}

27
vendor/crossterm/src/event/source.rs vendored Normal file
View file

@ -0,0 +1,27 @@
use std::time::Duration;
#[cfg(feature = "event-stream")]
use super::sys::Waker;
use super::InternalEvent;
#[cfg(unix)]
pub(crate) mod unix;
#[cfg(windows)]
pub(crate) mod windows;
/// An interface for trying to read an `InternalEvent` within an optional `Duration`.
pub(crate) trait EventSource: Sync + Send {
/// Tries to read an `InternalEvent` within the given duration.
///
/// # Arguments
///
/// * `timeout` - `None` block indefinitely until an event is available, `Some(duration)` blocks
/// for the given timeout
///
/// Returns `Ok(None)` if there's no event available and timeout expires.
fn try_read(&mut self, timeout: Option<Duration>) -> crate::Result<Option<InternalEvent>>;
/// Returns a `Waker` allowing to wake/force the `try_read` method to return `Ok(None)`.
#[cfg(feature = "event-stream")]
fn waker(&self) -> Waker;
}

View file

@ -0,0 +1,241 @@
use std::{collections::VecDeque, io, time::Duration};
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
use signal_hook_mio::v0_8::Signals;
use crate::Result;
#[cfg(feature = "event-stream")]
use super::super::sys::Waker;
use super::super::{
source::EventSource,
sys::unix::{
file_descriptor::{tty_fd, FileDesc},
parse::parse_event,
},
timeout::PollTimeout,
Event, InternalEvent,
};
// Tokens to identify file descriptor
const TTY_TOKEN: Token = Token(0);
const SIGNAL_TOKEN: Token = Token(1);
#[cfg(feature = "event-stream")]
const WAKE_TOKEN: Token = Token(2);
// I (@zrzka) wasn't able to read more than 1_022 bytes when testing
// reading on macOS/Linux -> we don't need bigger buffer and 1k of bytes
// is enough.
const TTY_BUFFER_SIZE: usize = 1_024;
pub(crate) struct UnixInternalEventSource {
poll: Poll,
events: Events,
parser: Parser,
tty_buffer: [u8; TTY_BUFFER_SIZE],
tty_fd: FileDesc,
signals: Signals,
#[cfg(feature = "event-stream")]
waker: Waker,
}
impl UnixInternalEventSource {
pub fn new() -> Result<Self> {
UnixInternalEventSource::from_file_descriptor(tty_fd()?)
}
pub(crate) fn from_file_descriptor(input_fd: FileDesc) -> Result<Self> {
let poll = Poll::new()?;
let registry = poll.registry();
let tty_raw_fd = input_fd.raw_fd();
let mut tty_ev = SourceFd(&tty_raw_fd);
registry.register(&mut tty_ev, TTY_TOKEN, Interest::READABLE)?;
let mut signals = Signals::new(&[signal_hook::consts::SIGWINCH])?;
registry.register(&mut signals, SIGNAL_TOKEN, Interest::READABLE)?;
#[cfg(feature = "event-stream")]
let waker = Waker::new(registry, WAKE_TOKEN)?;
Ok(UnixInternalEventSource {
poll,
events: Events::with_capacity(3),
parser: Parser::default(),
tty_buffer: [0u8; TTY_BUFFER_SIZE],
tty_fd: input_fd,
signals,
#[cfg(feature = "event-stream")]
waker,
})
}
}
impl EventSource for UnixInternalEventSource {
fn try_read(&mut self, timeout: Option<Duration>) -> Result<Option<InternalEvent>> {
if let Some(event) = self.parser.next() {
return Ok(Some(event));
}
let timeout = PollTimeout::new(timeout);
loop {
if let Err(e) = self.poll.poll(&mut self.events, timeout.leftover()) {
// Mio will throw an interrupted error in case of cursor position retrieval. We need to retry until it succeeds.
// Previous versions of Mio (< 0.7) would automatically retry the poll call if it was interrupted (if EINTR was returned).
// https://docs.rs/mio/0.7.0/mio/struct.Poll.html#notes
if e.kind() == io::ErrorKind::Interrupted {
continue;
} else {
return Err(e);
}
};
if self.events.is_empty() {
// No readiness events = timeout
return Ok(None);
}
for token in self.events.iter().map(|x| x.token()) {
match token {
TTY_TOKEN => {
loop {
match self.tty_fd.read(&mut self.tty_buffer, TTY_BUFFER_SIZE) {
Ok(read_count) => {
if read_count > 0 {
self.parser.advance(
&self.tty_buffer[..read_count],
read_count == TTY_BUFFER_SIZE,
);
}
}
Err(e) => {
// No more data to read at the moment. We will receive another event
if e.kind() == io::ErrorKind::WouldBlock {
break;
}
// once more data is available to read.
else if e.kind() == io::ErrorKind::Interrupted {
continue;
}
}
};
if let Some(event) = self.parser.next() {
return Ok(Some(event));
}
}
}
SIGNAL_TOKEN => {
for signal in self.signals.pending() {
match signal {
signal_hook::consts::SIGWINCH => {
// TODO Should we remove tput?
//
// This can take a really long time, because terminal::size can
// launch new process (tput) and then it parses its output. It's
// not a really long time from the absolute time point of view, but
// it's a really long time from the mio, async-std/tokio executor, ...
// point of view.
let new_size = crate::terminal::size()?;
return Ok(Some(InternalEvent::Event(Event::Resize(
new_size.0, new_size.1,
))));
}
_ => unreachable!("Synchronize signal registration & handling"),
};
}
}
#[cfg(feature = "event-stream")]
WAKE_TOKEN => {
return Err(std::io::Error::new(
std::io::ErrorKind::Interrupted,
"Poll operation was woken up by `Waker::wake`",
));
}
_ => unreachable!("Synchronize Evented handle registration & token handling"),
}
}
// Processing above can take some time, check if timeout expired
if timeout.elapsed() {
return Ok(None);
}
}
}
#[cfg(feature = "event-stream")]
fn waker(&self) -> Waker {
self.waker.clone()
}
}
//
// Following `Parser` structure exists for two reasons:
//
// * mimic anes Parser interface
// * move the advancing, parsing, ... stuff out of the `try_read` method
//
#[derive(Debug)]
struct Parser {
buffer: Vec<u8>,
internal_events: VecDeque<InternalEvent>,
}
impl Default for Parser {
fn default() -> Self {
Parser {
// This buffer is used for -> 1 <- ANSI escape sequence. Are we
// aware of any ANSI escape sequence that is bigger? Can we make
// it smaller?
//
// Probably not worth spending more time on this as "there's a plan"
// to use the anes crate parser.
buffer: Vec::with_capacity(256),
// TTY_BUFFER_SIZE is 1_024 bytes. How many ANSI escape sequences can
// fit? What is an average sequence length? Let's guess here
// and say that the average ANSI escape sequence length is 8 bytes. Thus
// the buffer size should be 1024/8=128 to avoid additional allocations
// when processing large amounts of data.
//
// There's no need to make it bigger, because when you look at the `try_read`
// method implementation, all events are consumed before the next TTY_BUFFER
// is processed -> events pushed.
internal_events: VecDeque::with_capacity(128),
}
}
}
impl Parser {
fn advance(&mut self, buffer: &[u8], more: bool) {
for (idx, byte) in buffer.iter().enumerate() {
let more = idx + 1 < buffer.len() || more;
self.buffer.push(*byte);
match parse_event(&self.buffer, more) {
Ok(Some(ie)) => {
self.internal_events.push_back(ie);
self.buffer.clear();
}
Ok(None) => {
// Event can't be parsed, because we don't have enough bytes for
// the current sequence. Keep the buffer and process next bytes.
}
Err(_) => {
// Event can't be parsed (not enough parameters, parameter is not a number, ...).
// Clear the buffer and continue with another sequence.
self.buffer.clear();
}
}
}
}
}
impl Iterator for Parser {
type Item = InternalEvent;
fn next(&mut self) -> Option<Self::Item> {
self.internal_events.pop_front()
}
}

View file

@ -0,0 +1,96 @@
use std::time::Duration;
use crossterm_winapi::{Console, Handle, InputRecord};
use crate::event::{
sys::windows::{parse::MouseButtonsPressed, poll::WinApiPoll},
Event,
};
#[cfg(feature = "event-stream")]
use super::super::sys::Waker;
use super::super::{
source::EventSource,
sys::windows::parse::{handle_key_event, handle_mouse_event},
timeout::PollTimeout,
InternalEvent, Result,
};
pub(crate) struct WindowsEventSource {
console: Console,
poll: WinApiPoll,
surrogate_buffer: Option<u16>,
mouse_buttons_pressed: MouseButtonsPressed,
}
impl WindowsEventSource {
pub(crate) fn new() -> Result<WindowsEventSource> {
let console = Console::from(Handle::current_in_handle()?);
Ok(WindowsEventSource {
console,
#[cfg(not(feature = "event-stream"))]
poll: WinApiPoll::new(),
#[cfg(feature = "event-stream")]
poll: WinApiPoll::new()?,
surrogate_buffer: None,
mouse_buttons_pressed: MouseButtonsPressed::default(),
})
}
}
impl EventSource for WindowsEventSource {
fn try_read(&mut self, timeout: Option<Duration>) -> Result<Option<InternalEvent>> {
let poll_timeout = PollTimeout::new(timeout);
loop {
if let Some(event_ready) = self.poll.poll(poll_timeout.leftover())? {
let number = self.console.number_of_console_input_events()?;
if event_ready && number != 0 {
let event = match self.console.read_single_input_event()? {
InputRecord::KeyEvent(record) => {
handle_key_event(record, &mut self.surrogate_buffer)
}
InputRecord::MouseEvent(record) => {
let mouse_event =
handle_mouse_event(record, &self.mouse_buttons_pressed);
self.mouse_buttons_pressed = MouseButtonsPressed {
left: record.button_state.left_button(),
right: record.button_state.right_button(),
middle: record.button_state.middle_button(),
};
mouse_event
}
InputRecord::WindowBufferSizeEvent(record) => {
Some(Event::Resize(record.size.x as u16, record.size.y as u16))
}
InputRecord::FocusEvent(record) => {
let event = if record.set_focus {
Event::FocusGained
} else {
Event::FocusLost
};
Some(event)
}
_ => None,
};
if let Some(event) = event {
return Ok(Some(InternalEvent::Event(event)));
}
}
}
if poll_timeout.elapsed() {
return Ok(None);
}
}
}
#[cfg(feature = "event-stream")]
fn waker(&self) -> Waker {
self.poll.waker()
}
}

147
vendor/crossterm/src/event/stream.rs vendored Normal file
View file

@ -0,0 +1,147 @@
use std::{
pin::Pin,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, SyncSender},
Arc,
},
task::{Context, Poll},
thread,
time::Duration,
};
use futures_core::stream::Stream;
use crate::Result;
use super::{
filter::EventFilter, lock_internal_event_reader, poll_internal, read_internal, sys::Waker,
Event, InternalEvent,
};
/// A stream of `Result<Event>`.
///
/// **This type is not available by default. You have to use the `event-stream` feature flag
/// to make it available.**
///
/// It implements the [Stream](futures_core::stream::Stream)
/// trait and allows you to receive [`Event`]s with [`async-std`](https://crates.io/crates/async-std)
/// or [`tokio`](https://crates.io/crates/tokio) crates.
///
/// Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder to see how to use
/// it (`event-stream-*`).
#[derive(Debug)]
pub struct EventStream {
poll_internal_waker: Waker,
stream_wake_task_executed: Arc<AtomicBool>,
stream_wake_task_should_shutdown: Arc<AtomicBool>,
task_sender: SyncSender<Task>,
}
impl Default for EventStream {
fn default() -> Self {
let (task_sender, receiver) = mpsc::sync_channel::<Task>(1);
thread::spawn(move || {
while let Ok(task) = receiver.recv() {
loop {
if let Ok(true) = poll_internal(None, &EventFilter) {
break;
}
if task.stream_wake_task_should_shutdown.load(Ordering::SeqCst) {
break;
}
}
task.stream_wake_task_executed
.store(false, Ordering::SeqCst);
task.stream_waker.wake();
}
});
EventStream {
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,
}
}
}
impl EventStream {
/// Constructs a new instance of `EventStream`.
pub fn new() -> EventStream {
EventStream::default()
}
}
struct Task {
stream_waker: std::task::Waker,
stream_wake_task_executed: Arc<AtomicBool>,
stream_wake_task_should_shutdown: Arc<AtomicBool>,
}
// Note to future me
//
// We need two wakers in order to implement EventStream correctly.
//
// 1. futures::Stream waker
//
// Stream::poll_next can return Poll::Pending which means that there's no
// event available. We are going to spawn a thread with the
// poll_internal(None, &EventFilter) call. This call blocks until an
// event is available and then we have to wake up the executor with notification
// that the task can be resumed.
//
// 2. poll_internal waker
//
// There's no event available, Poll::Pending was returned, stream waker thread
// is up and sitting in the poll_internal. User wants to drop the EventStream.
// We have to wake up the poll_internal (force it to return Ok(false)) and quit
// the thread before we drop.
impl Stream for EventStream {
type Item = Result<Event>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let result = match poll_internal(Some(Duration::from_secs(0)), &EventFilter) {
Ok(true) => match read_internal(&EventFilter) {
Ok(InternalEvent::Event(event)) => Poll::Ready(Some(Ok(event))),
Err(e) => Poll::Ready(Some(Err(e))),
#[cfg(unix)]
_ => unreachable!(),
},
Ok(false) => {
if !self
.stream_wake_task_executed
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
// https://github.com/rust-lang/rust/issues/80486#issuecomment-752244166
.unwrap_or_else(|x| x)
{
let stream_waker = cx.waker().clone();
let stream_wake_task_executed = self.stream_wake_task_executed.clone();
let stream_wake_task_should_shutdown =
self.stream_wake_task_should_shutdown.clone();
stream_wake_task_should_shutdown.store(false, Ordering::SeqCst);
let _ = self.task_sender.send(Task {
stream_waker,
stream_wake_task_executed,
stream_wake_task_should_shutdown,
});
}
Poll::Pending
}
Err(e) => Poll::Ready(Some(Err(e))),
};
result
}
}
impl Drop for EventStream {
fn drop(&mut self) {
self.stream_wake_task_should_shutdown
.store(true, Ordering::SeqCst);
let _ = self.poll_internal_waker.wake();
}
}

9
vendor/crossterm/src/event/sys.rs vendored Normal file
View file

@ -0,0 +1,9 @@
#[cfg(all(unix, feature = "event-stream"))]
pub(crate) use unix::waker::Waker;
#[cfg(all(windows, feature = "event-stream"))]
pub(crate) use windows::waker::Waker;
#[cfg(unix)]
pub(crate) mod unix;
#[cfg(windows)]
pub(crate) mod windows;

View file

@ -0,0 +1,5 @@
#[cfg(feature = "event-stream")]
pub(crate) mod waker;
pub(crate) mod file_descriptor;
pub(crate) mod parse;

View file

@ -0,0 +1,82 @@
use std::{
fs, io,
os::unix::io::{IntoRawFd, RawFd},
};
use libc::size_t;
use crate::Result;
/// A file descriptor wrapper.
///
/// It allows to retrieve raw file descriptor, write to the file descriptor and
/// mainly it closes the file descriptor once dropped.
#[derive(Debug)]
pub struct FileDesc {
fd: RawFd,
close_on_drop: bool,
}
impl FileDesc {
/// Constructs a new `FileDesc` with the given `RawFd`.
///
/// # Arguments
///
/// * `fd` - raw file descriptor
/// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped
pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc {
FileDesc { fd, close_on_drop }
}
pub fn read(&self, buffer: &mut [u8], size: usize) -> Result<usize> {
let result = unsafe {
libc::read(
self.fd,
buffer.as_mut_ptr() as *mut libc::c_void,
size as size_t,
) as isize
};
if result < 0 {
Err(io::Error::last_os_error())
} else {
Ok(result as usize)
}
}
/// Returns the underlying file descriptor.
pub fn raw_fd(&self) -> RawFd {
self.fd
}
}
impl Drop for FileDesc {
fn drop(&mut self) {
if self.close_on_drop {
// Note that errors are ignored when closing a file descriptor. The
// reason for this is that if an error occurs we don't actually know if
// the file descriptor was closed or not, and if we retried (for
// something like EINTR), we might close another valid file descriptor
// opened after we closed ours.
let _ = unsafe { libc::close(self.fd) };
}
}
}
/// Creates a file descriptor pointing to the standard input or `/dev/tty`.
pub fn tty_fd() -> Result<FileDesc> {
let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } {
(libc::STDIN_FILENO, false)
} else {
(
fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?
.into_raw_fd(),
true,
)
};
Ok(FileDesc::new(fd, close_on_drop))
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,36 @@
use std::sync::{Arc, Mutex};
use mio::{Registry, Token};
use crate::Result;
/// Allows to wake up the `mio::Poll::poll()` method.
/// This type wraps `mio::Waker`, for more information see its documentation.
#[derive(Clone, Debug)]
pub(crate) struct Waker {
inner: Arc<Mutex<mio::Waker>>,
}
impl Waker {
/// Create a new `Waker`.
pub(crate) fn new(registry: &Registry, waker_token: Token) -> Result<Self> {
Ok(Self {
inner: Arc::new(Mutex::new(mio::Waker::new(registry, waker_token)?)),
})
}
/// Wake up the [`Poll`] associated with this `Waker`.
///
/// Readiness is set to `Ready::readable()`.
pub(crate) fn wake(&self) -> Result<()> {
self.inner.lock().unwrap().wake()
}
/// Resets the state so the same waker can be reused.
///
/// This function is not impl
#[allow(dead_code, clippy::clippy::unnecessary_wraps)]
pub(crate) fn reset(&self) -> Result<()> {
Ok(())
}
}

View file

@ -0,0 +1,51 @@
//! This is a WINDOWS specific implementation for input related action.
use std::convert::TryFrom;
use std::io;
use std::sync::atomic::{AtomicU64, Ordering};
use crossterm_winapi::{ConsoleMode, Handle};
use crate::Result;
#[cfg(feature = "event-stream")]
pub(crate) mod waker;
pub(crate) mod parse;
pub(crate) mod poll;
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
/// 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 _ = 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() -> Result<u32> {
u32::try_from(ORIGINAL_CONSOLE_MODE.load(Ordering::Relaxed))
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Initial console modes not set"))
}
pub(crate) fn enable_mouse_capture() -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
init_original_console_mode(mode.mode()?);
mode.set_mode(ENABLE_MOUSE_MODE)?;
Ok(())
}
pub(crate) fn disable_mouse_capture() -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
mode.set_mode(original_console_mode()?)?;
Ok(())
}

View file

@ -0,0 +1,366 @@
use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer};
use winapi::um::{
wincon::{
CAPSLOCK_ON, LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED,
SHIFT_PRESSED,
},
winuser::{
GetForegroundWindow, GetKeyboardLayout, GetWindowThreadProcessId, ToUnicodeEx, VK_BACK,
VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT,
VK_LEFT, VK_MENU, VK_NEXT, VK_NUMPAD0, VK_NUMPAD9, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT,
VK_TAB, VK_UP,
},
};
use crate::{
event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind},
Result,
};
#[derive(Default)]
pub struct MouseButtonsPressed {
pub(crate) left: bool,
pub(crate) right: bool,
pub(crate) middle: bool,
}
pub(crate) fn handle_mouse_event(
mouse_event: MouseEvent,
buttons_pressed: &MouseButtonsPressed,
) -> Option<Event> {
if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event, buttons_pressed) {
return Some(Event::Mouse(event));
}
None
}
enum WindowsKeyEvent {
KeyEvent(KeyEvent),
Surrogate(u16),
}
pub(crate) fn handle_key_event(
key_event: KeyEventRecord,
surrogate_buffer: &mut Option<u16>,
) -> Option<Event> {
let windows_key_event = parse_key_event_record(&key_event)?;
match windows_key_event {
WindowsKeyEvent::KeyEvent(key_event) => {
// Discard any buffered surrogate value if another valid key event comes before the
// next surrogate value.
*surrogate_buffer = None;
Some(Event::Key(key_event))
}
WindowsKeyEvent::Surrogate(new_surrogate) => {
let ch = handle_surrogate(surrogate_buffer, new_surrogate)?;
let modifiers = KeyModifiers::from(&key_event.control_key_state);
let key_event = KeyEvent::new(KeyCode::Char(ch), modifiers);
Some(Event::Key(key_event))
}
}
}
fn handle_surrogate(surrogate_buffer: &mut Option<u16>, new_surrogate: u16) -> Option<char> {
match *surrogate_buffer {
Some(buffered_surrogate) => {
*surrogate_buffer = None;
std::char::decode_utf16([buffered_surrogate, new_surrogate])
.next()
.unwrap()
.ok()
}
None => {
*surrogate_buffer = Some(new_surrogate);
None
}
}
}
impl From<&ControlKeyState> for KeyModifiers {
fn from(state: &ControlKeyState) -> Self {
let shift = state.has_state(SHIFT_PRESSED);
let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED);
let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED);
let mut modifier = KeyModifiers::empty();
if shift {
modifier |= KeyModifiers::SHIFT;
}
if control {
modifier |= KeyModifiers::CONTROL;
}
if alt {
modifier |= KeyModifiers::ALT;
}
modifier
}
}
enum CharCase {
LowerCase,
UpperCase,
}
fn try_ensure_char_case(ch: char, desired_case: CharCase) -> char {
match desired_case {
CharCase::LowerCase if ch.is_uppercase() => {
let mut iter = ch.to_lowercase();
// Unwrap is safe; iterator yields one or more chars.
let ch_lower = iter.next().unwrap();
if iter.next() == None {
ch_lower
} else {
ch
}
}
CharCase::UpperCase if ch.is_lowercase() => {
let mut iter = ch.to_uppercase();
// Unwrap is safe; iterator yields one or more chars.
let ch_upper = iter.next().unwrap();
if iter.next() == None {
ch_upper
} else {
ch
}
}
_ => ch,
}
}
// Attempts to return the character for a key event accounting for the user's keyboard layout.
// The returned character (if any) is capitalized (if applicable) based on shift and capslock state.
// Returns None if the key doesn't map to a character or if it is a dead key.
// We use the *currently* active keyboard layout (if it can be determined). This layout may not
// correspond to the keyboard layout that was active when the user typed their input, since console
// applications get their input asynchronously from the terminal. By the time a console application
// can process a key input, the user may have changed the active layout. In this case, the character
// returned might not correspond to what the user expects, but there is no way for a console
// application to know what the keyboard layout actually was for a key event, so this is our best
// effort. If a console application processes input in a timely fashion, then it is unlikely that a
// user has time to change their keyboard layout before a key event is processed.
fn get_char_for_key(key_event: &KeyEventRecord) -> Option<char> {
let virtual_key_code = key_event.virtual_key_code as u32;
let virtual_scan_code = key_event.virtual_scan_code as u32;
let key_state = [0u8; 256];
let mut utf16_buf = [0u16, 16];
let dont_change_kernel_keyboard_state = 0x4;
// Best-effort attempt at determining the currently active keyboard layout.
// At the time of writing, this works for a console application running in Windows Terminal, but
// doesn't work under a Conhost terminal. For Conhost, the window handle returned by
// GetForegroundWindow() does not appear to actually be the foreground window which has the
// keyboard layout associated with it (or perhaps it is, but also has special protection that
// doesn't allow us to query it).
// When this determination fails, the returned keyboard layout handle will be null, which is an
// acceptable input for ToUnicodeEx, as that argument is optional. In this case ToUnicodeEx
// appears to use the keyboard layout associated with the current thread, which will be the
// layout that was inherited when the console application started (or possibly when the current
// thread was spawned). This is then unfortunately not updated when the user changes their
// keyboard layout in the terminal, but it's what we get.
let active_keyboard_layout = unsafe {
let foreground_window = GetForegroundWindow();
let foreground_thread = GetWindowThreadProcessId(foreground_window, std::ptr::null_mut());
GetKeyboardLayout(foreground_thread)
};
let ret = unsafe {
ToUnicodeEx(
virtual_key_code,
virtual_scan_code,
key_state.as_ptr(),
utf16_buf.as_mut_ptr(),
utf16_buf.len() as i32,
dont_change_kernel_keyboard_state,
active_keyboard_layout,
)
};
// -1 indicates a dead key.
// 0 indicates no character for this key.
if ret < 1 {
return None;
}
let mut ch_iter = std::char::decode_utf16(utf16_buf.into_iter().take(ret as usize));
let mut ch = ch_iter.next()?.ok()?;
if ch_iter.next() != None {
// Key doesn't map to a single char.
return None;
}
let is_shift_pressed = key_event.control_key_state.has_state(SHIFT_PRESSED);
let is_capslock_on = key_event.control_key_state.has_state(CAPSLOCK_ON);
let desired_case = if is_shift_pressed ^ is_capslock_on {
CharCase::UpperCase
} else {
CharCase::LowerCase
};
ch = try_ensure_char_case(ch, desired_case);
Some(ch)
}
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<WindowsKeyEvent> {
let modifiers = KeyModifiers::from(&key_event.control_key_state);
let virtual_key_code = key_event.virtual_key_code as i32;
// We normally ignore all key release events, but we will make an exception for an Alt key
// release if it carries a u_char value, as this indicates an Alt code.
let is_alt_code = virtual_key_code == VK_MENU && !key_event.key_down && key_event.u_char != 0;
if is_alt_code {
let utf16 = key_event.u_char;
match utf16 {
surrogate @ 0xD800..=0xDFFF => {
return Some(WindowsKeyEvent::Surrogate(surrogate));
}
unicode_scalar_value => {
// Unwrap is safe: We tested for surrogate values above and those are the only
// u16 values that are invalid when directly interpreted as unicode scalar
// values.
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
let key_code = KeyCode::Char(ch);
let key_event = KeyEvent::new(key_code, modifiers);
return Some(WindowsKeyEvent::KeyEvent(key_event));
}
}
}
// Don't generate events for numpad key presses when they're producing Alt codes.
let is_numpad_numeric_key = (VK_NUMPAD0..=VK_NUMPAD9).contains(&virtual_key_code);
let is_only_alt_modifier = modifiers.contains(KeyModifiers::ALT)
&& !modifiers.contains(KeyModifiers::SHIFT | KeyModifiers::CONTROL);
if is_only_alt_modifier && is_numpad_numeric_key {
return None;
}
if !key_event.key_down {
return None;
}
let parse_result = match virtual_key_code {
VK_SHIFT | VK_CONTROL | VK_MENU => None,
VK_BACK => Some(KeyCode::Backspace),
VK_ESCAPE => Some(KeyCode::Esc),
VK_RETURN => Some(KeyCode::Enter),
VK_F1..=VK_F24 => Some(KeyCode::F((key_event.virtual_key_code - 111) as u8)),
VK_LEFT => Some(KeyCode::Left),
VK_UP => Some(KeyCode::Up),
VK_RIGHT => Some(KeyCode::Right),
VK_DOWN => Some(KeyCode::Down),
VK_PRIOR => Some(KeyCode::PageUp),
VK_NEXT => Some(KeyCode::PageDown),
VK_HOME => Some(KeyCode::Home),
VK_END => Some(KeyCode::End),
VK_DELETE => Some(KeyCode::Delete),
VK_INSERT => Some(KeyCode::Insert),
VK_TAB if modifiers.contains(KeyModifiers::SHIFT) => Some(KeyCode::BackTab),
VK_TAB => Some(KeyCode::Tab),
_ => {
let utf16 = key_event.u_char;
match utf16 {
0x00..=0x1f => {
// Some key combinations generate either no u_char value or generate control
// codes. To deliver back a KeyCode::Char(...) event we want to know which
// character the key normally maps to on the user's keyboard layout.
// The keys that intentionally generate control codes (ESC, ENTER, TAB, etc.)
// are handled by their virtual key codes above.
get_char_for_key(key_event).map(KeyCode::Char)
}
surrogate @ 0xD800..=0xDFFF => {
return Some(WindowsKeyEvent::Surrogate(surrogate));
}
unicode_scalar_value => {
// Unwrap is safe: We tested for surrogate values above and those are the only
// u16 values that are invalid when directly interpreted as unicode scalar
// values.
let ch = std::char::from_u32(unicode_scalar_value as u32).unwrap();
Some(KeyCode::Char(ch))
}
}
}
};
if let Some(key_code) = parse_result {
let key_event = KeyEvent::new(key_code, modifiers);
return Some(WindowsKeyEvent::KeyEvent(key_event));
}
None
}
// The 'y' position of a mouse event or resize event is not relative to the window but absolute to screen buffer.
// This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells conting from the absolute buffer height) instead of relative x: 0, y: 0 to the window.
pub fn parse_relative_y(y: i16) -> Result<i16> {
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
Ok(y - window_size.top)
}
fn parse_mouse_event_record(
event: &MouseEvent,
buttons_pressed: &MouseButtonsPressed,
) -> Result<Option<crate::event::MouseEvent>> {
let modifiers = KeyModifiers::from(&event.control_key_state);
let xpos = event.mouse_position.x as u16;
let ypos = parse_relative_y(event.mouse_position.y)? as u16;
let button_state = event.button_state;
let kind = match event.event_flags {
EventFlags::PressOrRelease => {
if button_state.left_button() && !buttons_pressed.left {
Some(MouseEventKind::Down(MouseButton::Left))
} else if !button_state.left_button() && buttons_pressed.left {
Some(MouseEventKind::Up(MouseButton::Left))
} else if button_state.right_button() && !buttons_pressed.right {
Some(MouseEventKind::Down(MouseButton::Right))
} else if !button_state.right_button() && buttons_pressed.right {
Some(MouseEventKind::Up(MouseButton::Right))
} else if button_state.middle_button() && !buttons_pressed.middle {
Some(MouseEventKind::Down(MouseButton::Middle))
} else if !button_state.middle_button() && buttons_pressed.middle {
Some(MouseEventKind::Up(MouseButton::Middle))
} else {
None
}
}
EventFlags::MouseMoved => {
let button = if button_state.right_button() {
MouseButton::Right
} else if button_state.middle_button() {
MouseButton::Middle
} else {
MouseButton::Left
};
if button_state.release_button() {
Some(MouseEventKind::Moved)
} else {
Some(MouseEventKind::Drag(button))
}
}
EventFlags::MouseWheeled => {
// Vertical scroll
// from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
// if `button_state` is negative then the wheel was rotated backward, toward the user.
if button_state.scroll_down() {
Some(MouseEventKind::ScrollDown)
} else if button_state.scroll_up() {
Some(MouseEventKind::ScrollUp)
} else {
None
}
}
EventFlags::DoubleClick => None, // double click not supported by unix terminals
EventFlags::MouseHwheeled => None, // horizontal scroll not supported by unix terminals
_ => None,
};
Ok(kind.map(|kind| crate::event::MouseEvent {
kind,
column: xpos,
row: ypos,
modifiers,
}))
}

View file

@ -0,0 +1,89 @@
use std::io;
use std::time::Duration;
use crossterm_winapi::Handle;
use winapi::{
shared::winerror::WAIT_TIMEOUT,
um::{
synchapi::WaitForMultipleObjects,
winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0},
},
};
use crate::Result;
#[cfg(feature = "event-stream")]
pub(crate) use super::waker::Waker;
#[derive(Debug)]
pub(crate) struct WinApiPoll {
#[cfg(feature = "event-stream")]
waker: Waker,
}
impl WinApiPoll {
#[cfg(not(feature = "event-stream"))]
pub(crate) fn new() -> WinApiPoll {
WinApiPoll {}
}
#[cfg(feature = "event-stream")]
pub(crate) fn new() -> Result<WinApiPoll> {
Ok(WinApiPoll {
waker: Waker::new()?,
})
}
}
impl WinApiPoll {
pub fn poll(&mut self, timeout: Option<Duration>) -> Result<Option<bool>> {
let dw_millis = if let Some(duration) = timeout {
duration.as_millis() as u32
} else {
INFINITE
};
let console_handle = Handle::current_in_handle()?;
#[cfg(feature = "event-stream")]
let semaphore = self.waker.semaphore();
#[cfg(feature = "event-stream")]
let handles = &[*console_handle, **semaphore.handle()];
#[cfg(not(feature = "event-stream"))]
let handles = &[*console_handle];
let output =
unsafe { WaitForMultipleObjects(handles.len() as u32, handles.as_ptr(), 0, dw_millis) };
match output {
output if output == WAIT_OBJECT_0 => {
// input handle triggered
Ok(Some(true))
}
#[cfg(feature = "event-stream")]
output if output == WAIT_OBJECT_0 + 1 => {
// semaphore handle triggered
let _ = self.waker.reset();
Err(io::Error::new(
io::ErrorKind::Interrupted,
"Poll operation was woken up by `Waker::wake`",
)
.into())
}
WAIT_TIMEOUT | WAIT_ABANDONED_0 => {
// timeout elapsed
Ok(None)
}
WAIT_FAILED => Err(io::Error::last_os_error()),
_ => Err(io::Error::new(
io::ErrorKind::Other,
"WaitForMultipleObjects returned unexpected result.",
)),
}
}
#[cfg(feature = "event-stream")]
pub fn waker(&self) -> Waker {
self.waker.clone()
}
}

View file

@ -0,0 +1,42 @@
use std::sync::{Arc, Mutex};
use crossterm_winapi::Semaphore;
use crate::Result;
/// Allows to wake up the `WinApiPoll::poll()` method.
#[derive(Clone, Debug)]
pub(crate) struct Waker {
inner: Arc<Mutex<Semaphore>>,
}
impl Waker {
/// Creates a new waker.
///
/// `Waker` is based on the `Semaphore`. You have to use the semaphore
/// handle along with the `WaitForMultipleObjects`.
pub(crate) fn new() -> Result<Self> {
let inner = Semaphore::new()?;
Ok(Self {
inner: Arc::new(Mutex::new(inner)),
})
}
/// Wakes the `WaitForMultipleObjects`.
pub(crate) fn wake(&self) -> Result<()> {
self.inner.lock().unwrap().release()?;
Ok(())
}
/// Replaces the current semaphore with a new one allowing us to reuse the same `Waker`.
pub(crate) fn reset(&self) -> Result<()> {
*self.inner.lock().unwrap() = Semaphore::new()?;
Ok(())
}
/// Returns the semaphore associated with the waker.
pub(crate) fn semaphore(&self) -> Semaphore {
self.inner.lock().unwrap().clone()
}
}

92
vendor/crossterm/src/event/timeout.rs vendored Normal file
View file

@ -0,0 +1,92 @@
use std::time::{Duration, Instant};
/// Keeps track of the elapsed time since the moment the polling started.
#[derive(Debug, Clone)]
pub struct PollTimeout {
timeout: Option<Duration>,
start: Instant,
}
impl PollTimeout {
/// Constructs a new `PollTimeout` with the given optional `Duration`.
pub fn new(timeout: Option<Duration>) -> PollTimeout {
PollTimeout {
timeout,
start: Instant::now(),
}
}
/// Returns whether the timeout has elapsed.
///
/// It always returns `false` if the initial timeout was set to `None`.
pub fn elapsed(&self) -> bool {
self.timeout
.map(|timeout| self.start.elapsed() >= timeout)
.unwrap_or(false)
}
/// Returns the timeout leftover (initial timeout duration - elapsed duration).
pub fn leftover(&self) -> Option<Duration> {
self.timeout.map(|timeout| {
let elapsed = self.start.elapsed();
if elapsed >= timeout {
Duration::from_secs(0)
} else {
timeout - elapsed
}
})
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
use super::PollTimeout;
#[test]
pub fn test_timeout_without_duration_does_not_have_leftover() {
let timeout = PollTimeout::new(None);
assert_eq!(timeout.leftover(), None)
}
#[test]
pub fn test_timeout_without_duration_never_elapses() {
let timeout = PollTimeout::new(None);
assert!(!timeout.elapsed());
}
#[test]
pub fn test_timeout_elapses() {
const TIMEOUT_MILLIS: u64 = 100;
let timeout = PollTimeout {
timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)),
start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS),
};
assert!(timeout.elapsed());
}
#[test]
pub fn test_elapsed_timeout_has_zero_leftover() {
const TIMEOUT_MILLIS: u64 = 100;
let timeout = PollTimeout {
timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)),
start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS),
};
assert!(timeout.elapsed());
assert_eq!(timeout.leftover(), Some(Duration::from_millis(0)));
}
#[test]
pub fn test_not_elapsed_timeout_has_positive_leftover() {
let timeout = PollTimeout::new(Some(Duration::from_secs(60)));
assert!(!timeout.elapsed());
assert!(timeout.leftover().unwrap() > Duration::from_secs(0));
}
}

260
vendor/crossterm/src/lib.rs vendored Normal file
View file

@ -0,0 +1,260 @@
#![deny(unused_imports, unused_must_use)]
//! # Crossterm
//!
//! Have you ever been disappointed when a terminal library for rust was only written for UNIX systems?
//! Crossterm provides clearing, event (input) handling, styling, cursor movement, and terminal actions for both
//! Windows and UNIX systems.
//!
//! Crossterm aims to be simple and easy to call in code. Through the simplicity of Crossterm, you do not
//! have to worry about the platform you are working with.
//!
//! This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested
//! see [Tested Terminals](https://github.com/crossterm-rs/crossterm#tested-terminals)
//! for more info).
//!
//! ## Command API
//!
//! The command API makes the use of `crossterm` much easier and offers more control over when and how a
//! command is executed. A command is just an action you can perform on the terminal e.g. cursor movement.
//!
//! The command API offers:
//!
//! * Better Performance.
//! * Complete control over when to flush.
//! * Complete control over where the ANSI escape commands are executed to.
//! * Way easier and nicer API.
//!
//! There are two ways to use the API command:
//!
//! * Functions can execute commands on types that implement Write. Functions are easier to use and debug.
//! There is a disadvantage, and that is that there is a boilerplate code involved.
//! * Macros are generally seen as more difficult and aren't always well supported by editors but offer an API with less boilerplate code. If you are
//! not afraid of macros, this is a recommendation.
//!
//! Linux and Windows 10 systems support ANSI escape codes. Those ANSI escape codes are strings or rather a
//! byte sequence. When we `write` and `flush` those to the terminal we can perform some action.
//! For older windows systems a WinAPI call is made.
//!
//! ### Supported Commands
//!
//! - Module [`cursor`](cursor/index.html)
//! - Visibility - [`Show`](cursor/struct.Show.html), [`Hide`](cursor/struct.Hide.html)
//! - Appearance - [`EnableBlinking`](cursor/struct.EnableBlinking.html),
//! [`DisableBlinking`](cursor/struct.DisableBlinking.html)
//! - Position -
//! [`SavePosition`](cursor/struct.SavePosition.html), [`RestorePosition`](cursor/struct.RestorePosition.html),
//! [`MoveUp`](cursor/struct.MoveUp.html), [`MoveDown`](cursor/struct.MoveDown.html),
//! [`MoveLeft`](cursor/struct.MoveLeft.html), [`MoveRight`](cursor/struct.MoveRight.html),
//! [`MoveTo`](cursor/struct.MoveTo.html), [`MoveToColumn`](cursor/struct.MoveToColumn.html),[`MoveToRow`](cursor/struct.MoveToRow.html),
//! [`MoveToNextLine`](cursor/struct.MoveToNextLine.html), [`MoveToPreviousLine`](cursor/struct.MoveToPreviousLine.html),
//! - Shape -
//! [`SetCursorShape`](cursor/struct.SetCursorShape.html)
//! - Module [`event`](event/index.html)
//! - Keyboard events -
//! [`PushKeyboardEnhancementFlags`](event/struct.PushKeyboardEnhancementFlags.html),
//! [`PopKeyboardEnhancementFlags`](event/struct.PopKeyboardEnhancementFlags.html)
//! - Mouse events - [`EnableMouseCapture`](event/struct.EnableMouseCapture.html),
//! [`DisableMouseCapture`](event/struct.DisableMouseCapture.html)
//! - Module [`style`](style/index.html)
//! - Colors - [`SetForegroundColor`](style/struct.SetForegroundColor.html),
//! [`SetBackgroundColor`](style/struct.SetBackgroundColor.html),
//! [`ResetColor`](style/struct.ResetColor.html), [`SetColors`](style/struct.SetColors.html)
//! - Attributes - [`SetAttribute`](style/struct.SetAttribute.html), [`SetAttributes`](style/struct.SetAttributes.html),
//! [`PrintStyledContent`](style/struct.PrintStyledContent.html)
//! - Module [`terminal`](terminal/index.html)
//! - Scrolling - [`ScrollUp`](terminal/struct.ScrollUp.html),
//! [`ScrollDown`](terminal/struct.ScrollDown.html)
//! - Miscellaneous - [`Clear`](terminal/struct.Clear.html),
//! [`SetSize`](terminal/struct.SetSize.html)
//! [`SetTitle`](terminal/struct.SetTitle.html)
//! [`DisableLineWrap`](terminal/struct.DisableLineWrap.html)
//! [`EnableLineWrap`](terminal/struct.EnableLineWrap.html)
//! - Alternate screen - [`EnterAlternateScreen`](terminal/struct.EnterAlternateScreen.html),
//! [`LeaveAlternateScreen`](terminal/struct.LeaveAlternateScreen.html)
//!
//! ### Command Execution
//!
//! There are two different ways to execute commands:
//!
//! * [Lazy Execution](#lazy-execution)
//! * [Direct Execution](#direct-execution)
//!
//! #### Lazy Execution
//!
//! Flushing bytes to the terminal buffer is a heavy system call. If we perform a lot of actions with the terminal,
//! we want to do this periodically - like with a TUI editor - so that we can flush more data to the terminal buffer
//! at the same time.
//!
//! Crossterm offers the possibility to do this with `queue`.
//! With `queue` you can queue commands, and when you call [Write::flush][flush] these commands will be executed.
//!
//! You can pass a custom buffer implementing [std::io::Write][write] to this `queue` operation.
//! The commands will be executed on that buffer.
//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well.
//!
//! ##### Examples
//!
//! A simple demonstration that shows the command API in action with cursor commands.
//!
//! Functions:
//!
//! ```no_run
//! use std::io::{Write, stdout};
//! use crossterm::{QueueableCommand, cursor};
//!
//! let mut stdout = stdout();
//! stdout.queue(cursor::MoveTo(5,5));
//!
//! // some other code ...
//!
//! stdout.flush();
//! ```
//!
//! The [queue](./trait.QueueableCommand.html) function returns itself, therefore you can use this to queue another
//! command. Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))`.
//!
//! Macros:
//!
//! ```no_run
//! use std::io::{Write, stdout};
//! use crossterm::{queue, QueueableCommand, cursor};
//!
//! let mut stdout = stdout();
//! queue!(stdout, cursor::MoveTo(5, 5));
//!
//! // some other code ...
//!
//! // move operation is performed only if we flush the buffer.
//! stdout.flush();
//! ```
//!
//! You can pass more than one command into the [queue](./macro.queue.html) macro like
//! `queue!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and
//! they will be executed in the given order from left to right.
//!
//! #### Direct Execution
//!
//! For many applications it is not at all important to be efficient with 'flush' operations.
//! For this use case there is the `execute` operation.
//! This operation executes the command immediately, and calls the `flush` under water.
//!
//! You can pass a custom buffer implementing [std::io::Write][write] to this `execute` operation.
//! The commands will be executed on that buffer.
//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well.
//!
//! ##### Examples
//!
//! Functions:
//!
//! ```no_run
//! use std::io::{Write, stdout};
//! use crossterm::{ExecutableCommand, cursor};
//!
//! let mut stdout = stdout();
//! stdout.execute(cursor::MoveTo(5,5));
//! ```
//! The [execute](./trait.ExecutableCommand.html) function returns itself, therefore you can use this to queue
//! another command. Like `stdout.execute(Goto(5,5))?.execute(Clear(ClearType::All))`.
//!
//! Macros:
//!
//! ```no_run
//! use std::io::{Write, stdout};
//! use crossterm::{execute, ExecutableCommand, cursor};
//!
//! let mut stdout = stdout();
//! execute!(stdout, cursor::MoveTo(5, 5));
//! ```
//! You can pass more than one command into the [execute](./macro.execute.html) macro like
//! `execute!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and they will be executed in the given order from
//! left to right.
//!
//! ## Examples
//!
//! Print a rectangle colored with magenta and use both direct execution and lazy execution.
//!
//! Functions:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//! use crossterm::{
//! ExecutableCommand, QueueableCommand,
//! terminal, cursor, style::{self, Stylize}, Result
//! };
//!
//! fn main() -> Result<()> {
//! let mut stdout = stdout();
//!
//! stdout.execute(terminal::Clear(terminal::ClearType::All))?;
//!
//! for y in 0..40 {
//! for x in 0..150 {
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
//! // in this loop we are more efficient by not flushing the buffer.
//! stdout
//! .queue(cursor::MoveTo(x,y))?
//! .queue(style::PrintStyledContent( "█".magenta()))?;
//! }
//! }
//! }
//! stdout.flush()?;
//! Ok(())
//! }
//! ```
//!
//! Macros:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//! use crossterm::{
//! execute, queue,
//! style::{self, Stylize}, cursor, terminal, Result
//! };
//!
//! fn main() -> Result<()> {
//! let mut stdout = stdout();
//!
//! execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
//!
//! for y in 0..40 {
//! for x in 0..150 {
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
//! // in this loop we are more efficient by not flushing the buffer.
//! queue!(stdout, cursor::MoveTo(x,y), style::PrintStyledContent( "█".magenta()))?;
//! }
//! }
//! }
//! stdout.flush()?;
//! Ok(())
//! }
//!```
//!
//! [write]: https://doc.rust-lang.org/std/io/trait.Write.html
//! [stdout]: https://doc.rust-lang.org/std/io/fn.stdout.html
//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html
//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush
pub use crate::{
command::{Command, ExecutableCommand, QueueableCommand},
error::{ErrorKind, Result},
};
/// A module to work with the terminal cursor
pub mod cursor;
/// A module to read events.
pub mod event;
/// A module to apply attributes and colors on your text.
pub mod style;
/// A module to work with the terminal.
pub mod terminal;
/// A module to query if the current instance is a tty.
pub mod tty;
#[cfg(windows)]
/// A module that exposes one function to check if the current terminal supports ANSI sequences.
pub mod ansi_support;
mod command;
mod error;
pub(crate) mod macros;

372
vendor/crossterm/src/macros.rs vendored Normal file
View file

@ -0,0 +1,372 @@
/// Append a the first few characters of an ANSI escape code to the given string.
#[macro_export]
#[doc(hidden)]
macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
}
/// Queues one or more command(s) for further execution.
///
/// Queued commands must be flushed to the underlying device to be executed.
/// This generally happens in the following cases:
///
/// * When `flush` is called manually on the given type implementing `io::Write`.
/// * The terminal will `flush` automatically if the buffer is full.
/// * Each line is flushed in case of `stdout`, because it is line buffered.
///
/// # Arguments
///
/// - [std::io::Writer](std::io::Write)
///
/// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html)
///
/// One or more commands
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{queue, style::Print};
///
/// let mut stdout = stdout();
///
/// // `Print` will executed executed when `flush` is called.
/// queue!(stdout, Print("foo".to_string()));
///
/// // some other code (no execution happening here) ...
///
/// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed.
/// stdout.flush();
///
/// // ==== Output ====
/// // foo
/// ```
///
/// Have a look over at the [Command API](./index.html#command-api) for more details.
///
/// # Notes
///
/// In case of Windows versions lower than 10, a direct WinAPI call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
///
#[macro_export]
macro_rules! queue {
($writer:expr $(, $command:expr)* $(,)?) => {{
use ::std::io::Write;
// This allows the macro to take both mut impl Write and &mut impl Write.
Ok($writer.by_ref())
$(.and_then(|writer| $crate::QueueableCommand::queue(writer, $command)))*
.map(|_| ())
}}
}
/// Executes one or more command(s).
///
/// # Arguments
///
/// - [std::io::Writer](std::io::Write)
///
/// ANSI escape codes are written on the given 'writer', after which they are flushed.
///
/// - [Command](./trait.Command.html)
///
/// One or more commands
///
/// # Examples
///
/// ```rust
/// use std::io::{Write, stdout};
/// use crossterm::{execute, style::Print};
///
/// // will be executed directly
/// execute!(stdout(), Print("sum:\n".to_string()));
///
/// // will be executed directly
/// execute!(stdout(), Print("1 + 1= ".to_string()), Print((1+1).to_string()));
///
/// // ==== Output ====
/// // sum:
/// // 1 + 1 = 2
/// ```
///
/// Have a look over at the [Command API](./index.html#command-api) for more details.
///
/// # Notes
///
/// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'.
/// * In case of Windows versions lower than 10, a direct WinAPI call will be made.
/// The reason for this is that Windows versions lower than 10 do not support ANSI codes,
/// and can therefore not be written to the given `writer`.
/// Therefore, there is no difference between [execute](macro.execute.html)
/// and [queue](macro.queue.html) for those old Windows versions.
#[macro_export]
macro_rules! execute {
($writer:expr $(, $command:expr)* $(,)? ) => {{
use ::std::io::Write;
// Queue each command, then flush
$crate::queue!($writer $(, $command)*)
.and_then(|()| {
::std::io::Write::flush($writer.by_ref())
})
}}
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_display {
(for $($t:ty),+) => {
$(impl ::std::fmt::Display for $t {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
$crate::command::execute_fmt(f, self)
}
})*
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! impl_from {
($from:path, $to:expr) => {
impl From<$from> for ErrorKind {
fn from(e: $from) -> Self {
$to(e)
}
}
};
}
#[cfg(test)]
mod tests {
use std::io;
use std::str;
// Helper for execute tests to confirm flush
#[derive(Default, Debug, Clone)]
pub(self) struct FakeWrite {
buffer: String,
flushed: bool,
}
impl io::Write for FakeWrite {
fn write(&mut self, content: &[u8]) -> io::Result<usize> {
let content = str::from_utf8(content)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
self.buffer.push_str(content);
self.flushed = false;
Ok(content.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flushed = true;
Ok(())
}
}
#[cfg(not(windows))]
mod unix {
use std::fmt;
use super::FakeWrite;
use crate::command::Command;
pub struct FakeCommand;
impl Command for FakeCommand {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str("cmd")
}
}
#[test]
fn test_queue_one() {
let mut result = FakeWrite::default();
queue!(&mut result, FakeCommand).unwrap();
assert_eq!(&result.buffer, "cmd");
assert!(!result.flushed);
}
#[test]
fn test_queue_many() {
let mut result = FakeWrite::default();
queue!(&mut result, FakeCommand, FakeCommand).unwrap();
assert_eq!(&result.buffer, "cmdcmd");
assert!(!result.flushed);
}
#[test]
fn test_queue_trailing_comma() {
let mut result = FakeWrite::default();
queue!(&mut result, FakeCommand, FakeCommand,).unwrap();
assert_eq!(&result.buffer, "cmdcmd");
assert!(!result.flushed);
}
#[test]
fn test_execute_one() {
let mut result = FakeWrite::default();
execute!(&mut result, FakeCommand).unwrap();
assert_eq!(&result.buffer, "cmd");
assert!(result.flushed);
}
#[test]
fn test_execute_many() {
let mut result = FakeWrite::default();
execute!(&mut result, FakeCommand, FakeCommand).unwrap();
assert_eq!(&result.buffer, "cmdcmd");
assert!(result.flushed);
}
#[test]
fn test_execute_trailing_comma() {
let mut result = FakeWrite::default();
execute!(&mut result, FakeCommand, FakeCommand,).unwrap();
assert_eq!(&result.buffer, "cmdcmd");
assert!(result.flushed);
}
}
#[cfg(windows)]
mod windows {
use std::fmt;
use std::cell::RefCell;
use std::fmt::Debug;
use super::FakeWrite;
use crate::command::Command;
use crate::error::Result as CrosstermResult;
// We need to test two different APIs: WinAPI and the write api. We
// don't know until runtime which we're supporting (via
// Command::is_ansi_code_supported), so we have to test them both. The
// CI environment hopefully includes both versions of windows.
// WindowsEventStream is a place for execute_winapi to push strings,
// when called.
type WindowsEventStream = Vec<&'static str>;
struct FakeCommand<'a> {
// Need to use a refcell because we want execute_winapi to be able
// push to the vector, but execute_winapi take &self.
stream: RefCell<&'a mut WindowsEventStream>,
value: &'static str,
}
impl<'a> FakeCommand<'a> {
fn new(stream: &'a mut WindowsEventStream, value: &'static str) -> Self {
Self {
value,
stream: RefCell::new(stream),
}
}
}
impl<'a> Command for FakeCommand<'a> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(self.value)
}
fn execute_winapi(&self) -> CrosstermResult<()> {
self.stream.borrow_mut().push(self.value);
Ok(())
}
}
// Helper function for running tests against either WinAPI or an
// io::Write.
//
// This function will execute the `test` function, which should
// queue some commands against the given FakeWrite and
// WindowsEventStream. It will then test that the correct data sink
// was populated. It does not currently check is_ansi_code_supported;
// for now it simply checks that one of the two streams was correctly
// populated.
//
// If the stream was populated, it tests that the two arrays are equal.
// If the writer was populated, it tests that the contents of the
// write buffer are equal to the concatenation of `stream_result`.
fn test_harness<E: Debug>(
stream_result: &[&'static str],
test: impl FnOnce(&mut FakeWrite, &mut WindowsEventStream) -> Result<(), E>,
) {
let mut stream = WindowsEventStream::default();
let mut writer = FakeWrite::default();
if let Err(err) = test(&mut writer, &mut stream) {
panic!("Error returned from test function: {:?}", err);
}
// We need this for type inference, for whatever reason.
const EMPTY_RESULT: [&str; 0] = [];
// TODO: confirm that the correct sink was used, based on
// is_ansi_code_supported
match (writer.buffer.is_empty(), stream.is_empty()) {
(true, true) if stream_result == EMPTY_RESULT => {}
(true, true) => panic!(
"Neither the event stream nor the writer were populated. Expected {:?}",
stream_result
),
// writer is populated
(false, true) => {
// Concat the stream result to find the string result
let result: String = stream_result.iter().copied().collect();
assert_eq!(result, writer.buffer);
assert_eq!(&stream, &EMPTY_RESULT);
}
// stream is populated
(true, false) => {
assert_eq!(stream, stream_result);
assert_eq!(writer.buffer, "");
}
// Both are populated
(false, false) => panic!(
"Both the writer and the event stream were written to.\n\
Only one should be used, based on is_ansi_code_supported.\n\
stream: {stream:?}\n\
writer: {writer:?}",
stream = stream,
writer = writer,
),
}
}
#[test]
fn test_queue_one() {
test_harness(&["cmd1"], |writer, stream| {
queue!(writer, FakeCommand::new(stream, "cmd1"))
})
}
#[test]
fn test_queue_some() {
test_harness(&["cmd1", "cmd2"], |writer, stream| {
queue!(
writer,
FakeCommand::new(stream, "cmd1"),
FakeCommand::new(stream, "cmd2"),
)
})
}
#[test]
fn test_many_queues() {
test_harness(&["cmd1", "cmd2", "cmd3"], |writer, stream| {
queue!(writer, FakeCommand::new(stream, "cmd1"))?;
queue!(writer, FakeCommand::new(stream, "cmd2"))?;
queue!(writer, FakeCommand::new(stream, "cmd3"))
})
}
}
}

501
vendor/crossterm/src/style.rs vendored Normal file
View file

@ -0,0 +1,501 @@
//! # Style
//!
//! The `style` module provides a functionality to apply attributes and colors on your text.
//!
//! This documentation does not contain a lot of examples. The reason is that it's fairly
//! obvious how to use this crate. Although, we do provide
//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Platform-specific Notes
//!
//! Not all features are supported on all terminals/platforms. You should always consult
//! platform-specific notes of the following types:
//!
//! * [Color](enum.Color.html#platform-specific-notes)
//! * [Attribute](enum.Attribute.html#platform-specific-notes)
//!
//! ## Examples
//!
//! A few examples of how to use the style module.
//!
//! ### Colors
//!
//! How to change the terminal text color.
//!
//! Command API:
//!
//! Using the Command API to color text.
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{execute, Result};
//! use crossterm::style::{Print, SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute};
//!
//! fn main() -> Result<()> {
//! execute!(
//! stdout(),
//! // Blue foreground
//! SetForegroundColor(Color::Blue),
//! // Red background
//! SetBackgroundColor(Color::Red),
//! // Print text
//! Print("Blue text on Red.".to_string()),
//! // Reset to default colors
//! ResetColor
//! )
//! }
//! ```
//!
//! Functions:
//!
//! Using functions from [`Stylize`](crate::style::Stylize) on a `String` or `&'static str` to color
//! it.
//!
//! ```no_run
//! use crossterm::style::Stylize;
//!
//! println!("{}", "Red foreground color & blue background.".red().on_blue());
//! ```
//!
//! ### Attributes
//!
//! How to apply terminal attributes to text.
//!
//! Command API:
//!
//! Using the Command API to set attributes.
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{execute, Result, style::Print};
//! use crossterm::style::{SetAttribute, Attribute};
//!
//! fn main() -> Result<()> {
//! execute!(
//! stdout(),
//! // Set to bold
//! SetAttribute(Attribute::Bold),
//! Print("Bold text here.".to_string()),
//! // Reset all attributes
//! SetAttribute(Attribute::Reset)
//! )
//! }
//! ```
//!
//! Functions:
//!
//! Using [`Stylize`](crate::style::Stylize) functions on a `String` or `&'static str` to set
//! attributes to it.
//!
//! ```no_run
//! use crossterm::style::Stylize;
//!
//! println!("{}", "Bold".bold());
//! println!("{}", "Underlined".underlined());
//! println!("{}", "Negative".negative());
//! ```
//!
//! Displayable:
//!
//! [`Attribute`](enum.Attribute.html) implements [Display](https://doc.rust-lang.org/beta/std/fmt/trait.Display.html) and therefore it can be formatted like:
//!
//! ```no_run
//! use crossterm::style::Attribute;
//!
//! println!(
//! "{} Underlined {} No Underline",
//! Attribute::Underlined,
//! Attribute::NoUnderline
//! );
//! ```
use std::{
env,
fmt::{self, Display},
};
use crate::command::execute_fmt;
#[cfg(windows)]
use crate::Result;
use crate::{csi, impl_display, Command};
pub use self::{
attributes::Attributes,
content_style::ContentStyle,
styled_content::StyledContent,
stylize::Stylize,
types::{Attribute, Color, Colored, Colors},
};
mod attributes;
mod content_style;
mod styled_content;
mod stylize;
mod sys;
mod types;
/// Creates a `StyledContent`.
///
/// This could be used to style any type that implements `Display` with colors and text attributes.
///
/// See [`StyledContent`](struct.StyledContent.html) for more info.
///
/// # Examples
///
/// ```no_run
/// use crossterm::style::{style, Stylize, Color};
///
/// let styled_content = style("Blue colored text on yellow background")
/// .with(Color::Blue)
/// .on(Color::Yellow);
///
/// println!("{}", styled_content);
/// ```
pub fn style<D: Display>(val: D) -> StyledContent<D> {
ContentStyle::new().apply(val)
}
/// Returns available color count.
///
/// # Notes
///
/// This does not always provide a good result.
pub fn available_color_count() -> u16 {
env::var("TERM")
.map(|x| if x.contains("256color") { 256 } else { 8 })
.unwrap_or(8)
}
/// A command that sets the the foreground color.
///
/// See [`Color`](enum.Color.html) for more info.
///
/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
/// color in one command.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetForegroundColor(pub Color);
impl Command for SetForegroundColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::ForegroundColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::windows::set_foreground_color(self.0)
}
}
/// A command that sets the the background color.
///
/// See [`Color`](enum.Color.html) for more info.
///
/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
/// color with one command.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetBackgroundColor(pub Color);
impl Command for SetBackgroundColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::BackgroundColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::windows::set_background_color(self.0)
}
}
/// A command that sets the the underline color.
///
/// See [`Color`](enum.Color.html) for more info.
///
/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background
/// color with one command.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetUnderlineColor(pub Color);
impl Command for SetUnderlineColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), Colored::UnderlineColor(self.0))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
"SetUnderlineColor not supported by winapi.",
))
}
}
/// A command that optionally sets the foreground and/or background color.
///
/// For example:
/// ```no_run
/// use std::io::{stdout, Write};
///
/// use crossterm::execute;
/// use crossterm::style::{Color::{Green, Black}, Colors, Print, SetColors};
///
/// execute!(
/// stdout(),
/// SetColors(Colors::new(Green, Black)),
/// Print("Hello, world!".to_string()),
/// ).unwrap();
/// ```
///
/// See [`Colors`](struct.Colors.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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 {
SetForegroundColor(color).write_ansi(f)?;
}
if let Some(color) = self.0.background {
SetBackgroundColor(color).write_ansi(f)?;
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
if let Some(color) = self.0.foreground {
sys::windows::set_foreground_color(color)?;
}
if let Some(color) = self.0.background {
sys::windows::set_background_color(color)?;
}
Ok(())
}
}
/// A command that sets an attribute.
///
/// See [`Attribute`](enum.Attribute.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetAttribute(pub Attribute);
impl Command for SetAttribute {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("{}m"), self.0.sgr())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
/// A command that sets several attributes.
///
/// See [`Attributes`](struct.Attributes.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetAttributes(pub Attributes);
impl Command for SetAttributes {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
for attr in Attribute::iterator() {
if self.0.has(attr) {
SetAttribute(attr).write_ansi(f)?;
}
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
/// A command that sets a style (colors and attributes).
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetStyle(pub ContentStyle);
impl Command for SetStyle {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
if let Some(bg) = self.0.background_color {
execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
}
if let Some(fg) = self.0.foreground_color {
execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
}
if let Some(ul) = self.0.underline_color {
execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
}
if !self.0.attributes.is_empty() {
execute_fmt(f, SetAttributes(self.0.attributes)).map_err(|_| fmt::Error)?;
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
panic!("tried to execute SetStyle command using WinAPI, use ANSI instead");
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
/// A command that prints styled content.
///
/// See [`StyledContent`](struct.StyledContent.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Copy, Clone)]
pub struct PrintStyledContent<D: Display>(pub StyledContent<D>);
impl<D: Display> Command for PrintStyledContent<D> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
let style = self.0.style();
let mut reset_background = false;
let mut reset_foreground = false;
let mut reset = false;
if let Some(bg) = style.background_color {
execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?;
reset_background = true;
}
if let Some(fg) = style.foreground_color {
execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?;
reset_foreground = true;
}
if let Some(ul) = style.underline_color {
execute_fmt(f, SetUnderlineColor(ul)).map_err(|_| fmt::Error)?;
reset_foreground = true;
}
if !style.attributes.is_empty() {
execute_fmt(f, SetAttributes(style.attributes)).map_err(|_| fmt::Error)?;
reset = true;
}
write!(f, "{}", self.0.content())?;
if reset {
// NOTE: This will reset colors even though self has no colors, hence produce unexpected
// resets.
// TODO: reset the set attributes only.
execute_fmt(f, ResetColor).map_err(|_| fmt::Error)?;
} else {
// NOTE: Since the above bug, we do not need to reset colors when we reset attributes.
if reset_background {
execute_fmt(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
}
if reset_foreground {
execute_fmt(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?;
}
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// A command that resets the colors back to default.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResetColor;
impl Command for ResetColor {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("0m"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::windows::reset()
}
}
/// A command that prints the given displayable type.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Print<T: Display>(pub T);
impl<T: Display> Command for Print<T> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "{}", self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
panic!("tried to execute Print command using WinAPI, use ANSI instead");
}
#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
true
}
}
impl<T: Display> Display for Print<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl_display!(for SetForegroundColor);
impl_display!(for SetBackgroundColor);
impl_display!(for SetColors);
impl_display!(for SetAttribute);
impl_display!(for PrintStyledContent<String>);
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<Item = &'a str>) -> Option<u8> {
iter.next().and_then(|s| s.parse().ok())
}

120
vendor/crossterm/src/style/attributes.rs vendored Normal file
View file

@ -0,0 +1,120 @@
use std::ops::{BitAnd, BitOr, BitXor};
use crate::style::Attribute;
/// a bitset for all possible attributes
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Attributes(u32);
impl From<Attribute> for Attributes {
fn from(attribute: Attribute) -> Self {
Self(attribute.bytes())
}
}
impl From<&[Attribute]> for Attributes {
fn from(arr: &[Attribute]) -> Self {
let mut attributes = Attributes::default();
for &attr in arr {
attributes.set(attr);
}
attributes
}
}
impl BitAnd<Attribute> for Attributes {
type Output = Self;
fn bitand(self, rhs: Attribute) -> Self {
Self(self.0 & rhs.bytes())
}
}
impl BitAnd for Attributes {
type Output = Self;
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl BitOr<Attribute> for Attributes {
type Output = Self;
fn bitor(self, rhs: Attribute) -> Self {
Self(self.0 | rhs.bytes())
}
}
impl BitOr for Attributes {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl BitXor<Attribute> for Attributes {
type Output = Self;
fn bitxor(self, rhs: Attribute) -> Self {
Self(self.0 ^ rhs.bytes())
}
}
impl BitXor for Attributes {
type Output = Self;
fn bitxor(self, rhs: Self) -> Self {
Self(self.0 ^ rhs.0)
}
}
impl Attributes {
/// Sets the attribute.
/// If it's already set, this does nothing.
#[inline(always)]
pub fn set(&mut self, attribute: Attribute) {
self.0 |= attribute.bytes();
}
/// Unsets the attribute.
/// If it's not set, this changes nothing.
#[inline(always)]
pub fn unset(&mut self, attribute: Attribute) {
self.0 &= !attribute.bytes();
}
/// Sets the attribute if it's unset, unset it
/// if it is set.
#[inline(always)]
pub fn toggle(&mut self, attribute: Attribute) {
self.0 ^= attribute.bytes();
}
/// Returns whether the attribute is set.
#[inline(always)]
pub const fn has(self, attribute: Attribute) -> bool {
self.0 & attribute.bytes() != 0
}
/// Sets all the passed attributes. Removes none.
#[inline(always)]
pub fn extend(&mut self, attributes: Attributes) {
self.0 |= attributes.0;
}
/// Returns whether there is no attribute set.
#[inline(always)]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
#[cfg(test)]
mod tests {
use super::{Attribute, Attributes};
#[test]
fn test_attributes() {
let mut attributes: Attributes = Attribute::Bold.into();
assert!(attributes.has(Attribute::Bold));
attributes.set(Attribute::Italic);
assert!(attributes.has(Attribute::Italic));
attributes.unset(Attribute::Italic);
assert!(!attributes.has(Attribute::Italic));
attributes.toggle(Attribute::Bold);
assert!(attributes.is_empty());
}
}

View file

@ -0,0 +1,43 @@
//! This module contains the `content style` that can be applied to an `styled content`.
use std::fmt::Display;
use crate::style::{Attributes, Color, StyledContent};
/// The style that can be put on content.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
pub struct ContentStyle {
/// The foreground color.
pub foreground_color: Option<Color>,
/// The background color.
pub background_color: Option<Color>,
/// The underline color.
pub underline_color: Option<Color>,
/// List of attributes.
pub attributes: Attributes,
}
impl ContentStyle {
/// Creates a `StyledContent` by applying the style to the given `val`.
#[inline]
pub fn apply<D: Display>(self, val: D) -> StyledContent<D> {
StyledContent::new(self, val)
}
/// Creates a new `ContentStyle`.
#[inline]
pub fn new() -> ContentStyle {
ContentStyle::default()
}
}
impl AsRef<ContentStyle> for ContentStyle {
fn as_ref(&self) -> &Self {
self
}
}
impl AsMut<ContentStyle> for ContentStyle {
fn as_mut(&mut self) -> &mut Self {
self
}
}

View file

@ -0,0 +1,77 @@
//! This module contains the logic to style some content.
use std::fmt::{self, Display, Formatter};
use super::{ContentStyle, PrintStyledContent};
/// The style with the content to be styled.
///
/// # Examples
///
/// ```rust
/// use crossterm::style::{style, Color, Attribute, Stylize};
///
/// let styled = "Hello there"
/// .with(Color::Yellow)
/// .on(Color::Blue)
/// .attribute(Attribute::Bold);
///
/// println!("{}", styled);
/// ```
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct StyledContent<D: Display> {
/// The style (colors, content attributes).
style: ContentStyle,
/// A content to apply the style on.
content: D,
}
impl<D: Display> StyledContent<D> {
/// Creates a new `StyledContent`.
#[inline]
pub fn new(style: ContentStyle, content: D) -> StyledContent<D> {
StyledContent { style, content }
}
/// Returns the content.
#[inline]
pub fn content(&self) -> &D {
&self.content
}
/// Returns the style.
#[inline]
pub fn style(&self) -> &ContentStyle {
&self.style
}
/// Returns a mutable reference to the style, so that it can be further
/// manipulated
#[inline]
pub fn style_mut(&mut self) -> &mut ContentStyle {
&mut self.style
}
}
impl<D: Display> AsRef<ContentStyle> for StyledContent<D> {
fn as_ref(&self) -> &ContentStyle {
&self.style
}
}
impl<D: Display> AsMut<ContentStyle> for StyledContent<D> {
fn as_mut(&mut self) -> &mut ContentStyle {
&mut self.style
}
}
impl<D: Display> Display for StyledContent<D> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
crate::command::execute_fmt(
f,
PrintStyledContent(StyledContent {
style: self.style,
content: &self.content,
}),
)
}
}

200
vendor/crossterm/src/style/stylize.rs vendored Normal file
View file

@ -0,0 +1,200 @@
use std::fmt::Display;
use super::{style, Attribute, Color, ContentStyle, StyledContent};
macro_rules! stylize_method {
($method_name:ident Attribute::$attribute:ident) => {
calculated_docs! {
#[doc = concat!(
"Applies the [`",
stringify!($attribute),
"`](Attribute::",
stringify!($attribute),
") attribute to the text.",
)]
fn $method_name(self) -> Self::Styled {
self.attribute(Attribute::$attribute)
}
}
};
($method_name_fg:ident, $method_name_bg:ident, $method_name_ul:ident Color::$color:ident) => {
calculated_docs! {
#[doc = concat!(
"Sets the foreground color to [`",
stringify!($color),
"`](Color::",
stringify!($color),
")."
)]
fn $method_name_fg(self) -> Self::Styled {
self.with(Color::$color)
}
#[doc = concat!(
"Sets the background color to [`",
stringify!($color),
"`](Color::",
stringify!($color),
")."
)]
fn $method_name_bg(self) -> Self::Styled {
self.on(Color::$color)
}
#[doc = concat!(
"Sets the underline color to [`",
stringify!($color),
"`](Color::",
stringify!($color),
")."
)]
fn $method_name_ul(self) -> Self::Styled {
self.underline(Color::$color)
}
}
};
}
/// Provides a set of methods to set attributes and colors.
///
/// # Examples
///
/// ```no_run
/// use crossterm::style::Stylize;
///
/// println!("{}", "Bold text".bold());
/// println!("{}", "Underlined text".underlined());
/// println!("{}", "Negative text".negative());
/// println!("{}", "Red on blue".red().on_blue());
/// ```
pub trait Stylize: Sized {
/// This type with styles applied.
type Styled: AsRef<ContentStyle> + AsMut<ContentStyle>;
/// Styles this type.
fn stylize(self) -> Self::Styled;
/// Sets the foreground color.
fn with(self, color: Color) -> Self::Styled {
let mut styled = self.stylize();
styled.as_mut().foreground_color = Some(color);
styled
}
/// Sets the background color.
fn on(self, color: Color) -> Self::Styled {
let mut styled = self.stylize();
styled.as_mut().background_color = Some(color);
styled
}
/// Sets the underline color.
fn underline(self, color: Color) -> Self::Styled {
let mut styled = self.stylize();
styled.as_mut().underline_color = Some(color);
styled
}
/// Styles the content with the attribute.
fn attribute(self, attr: Attribute) -> Self::Styled {
let mut styled = self.stylize();
styled.as_mut().attributes.set(attr);
styled
}
stylize_method!(reset Attribute::Reset);
stylize_method!(bold Attribute::Bold);
stylize_method!(underlined Attribute::Underlined);
stylize_method!(reverse Attribute::Reverse);
stylize_method!(dim Attribute::Dim);
stylize_method!(italic Attribute::Italic);
stylize_method!(negative Attribute::Reverse);
stylize_method!(slow_blink Attribute::SlowBlink);
stylize_method!(rapid_blink Attribute::RapidBlink);
stylize_method!(hidden Attribute::Hidden);
stylize_method!(crossed_out Attribute::CrossedOut);
stylize_method!(black, on_black, underline_black Color::Black);
stylize_method!(dark_grey, on_dark_grey, underline_dark_grey Color::DarkGrey);
stylize_method!(red, on_red, underline_red Color::Red);
stylize_method!(dark_red, on_dark_red, underline_dark_red Color::DarkRed);
stylize_method!(green, on_green, underline_green Color::Green);
stylize_method!(dark_green, on_dark_green, underline_dark_green Color::DarkGreen);
stylize_method!(yellow, on_yellow, underline_yellow Color::Yellow);
stylize_method!(dark_yellow, on_dark_yellow, underline_dark_yellow Color::DarkYellow);
stylize_method!(blue, on_blue, underline_blue Color::Blue);
stylize_method!(dark_blue, on_dark_blue, underline_dark_blue Color::DarkBlue);
stylize_method!(magenta, on_magenta, underline_magenta Color::Magenta);
stylize_method!(dark_magenta, on_dark_magenta, underline_dark_magenta Color::DarkMagenta);
stylize_method!(cyan, on_cyan, underline_cyan Color::Cyan);
stylize_method!(dark_cyan, on_dark_cyan, underline_dark_cyan Color::DarkCyan);
stylize_method!(white, on_white, underline_white Color::White);
stylize_method!(grey, on_grey, underline_grey Color::Grey);
}
macro_rules! impl_stylize_for_display {
($($t:ty),*) => { $(
impl Stylize for $t {
type Styled = StyledContent<Self>;
#[inline]
fn stylize(self) -> Self::Styled {
style(self)
}
}
)* }
}
impl_stylize_for_display!(String, char, &str);
impl Stylize for ContentStyle {
type Styled = Self;
#[inline]
fn stylize(self) -> Self::Styled {
self
}
}
impl<D: Display> Stylize for StyledContent<D> {
type Styled = StyledContent<D>;
fn stylize(self) -> Self::Styled {
self
}
}
// Workaround for https://github.com/rust-lang/rust/issues/78835
macro_rules! calculated_docs {
($(#[doc = $doc:expr] $item:item)*) => { $(#[doc = $doc] $item)* };
}
// Remove once https://github.com/rust-lang/rust-clippy/issues/7106 stabilizes.
#[allow(clippy::single_component_path_imports)]
#[allow(clippy::useless_attribute)]
use calculated_docs;
#[cfg(test)]
mod tests {
use super::super::{Attribute, Color, ContentStyle, Stylize};
#[test]
fn set_fg_bg_add_attr() {
let style = ContentStyle::new()
.with(Color::Blue)
.on(Color::Red)
.attribute(Attribute::Bold);
assert_eq!(style.foreground_color, Some(Color::Blue));
assert_eq!(style.background_color, Some(Color::Red));
assert!(style.attributes.has(Attribute::Bold));
let mut styled_content = style.apply("test");
styled_content = styled_content
.with(Color::Green)
.on(Color::Magenta)
.attribute(Attribute::NoItalic);
let style = styled_content.style();
assert_eq!(style.foreground_color, Some(Color::Green));
assert_eq!(style.background_color, Some(Color::Magenta));
assert!(style.attributes.has(Attribute::Bold));
assert!(style.attributes.has(Attribute::NoItalic));
}
}

2
vendor/crossterm/src/style/sys.rs vendored Normal file
View file

@ -0,0 +1,2 @@
#[cfg(windows)]
pub(crate) mod windows;

View file

@ -0,0 +1,206 @@
use std::convert::TryFrom;
use std::sync::atomic::{AtomicU32, Ordering};
use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer};
use winapi::um::wincon;
use crate::Result;
use super::super::{Color, Colored};
const FG_GREEN: u16 = wincon::FOREGROUND_GREEN;
const FG_RED: u16 = wincon::FOREGROUND_RED;
const FG_BLUE: u16 = wincon::FOREGROUND_BLUE;
const FG_INTENSITY: u16 = wincon::FOREGROUND_INTENSITY;
const BG_GREEN: u16 = wincon::BACKGROUND_GREEN;
const BG_RED: u16 = wincon::BACKGROUND_RED;
const BG_BLUE: u16 = wincon::BACKGROUND_BLUE;
const BG_INTENSITY: u16 = wincon::BACKGROUND_INTENSITY;
pub(crate) fn set_foreground_color(fg_color: Color) -> Result<()> {
init_console_color()?;
let color_value: u16 = Colored::ForegroundColor(fg_color).into();
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So we need to use bitwise operators to check if the values exists or to get current console colors.
let attrs = csbi.attributes();
let bg_color = attrs & 0x0070;
let mut color = color_value | bg_color;
// background intensity is a separate value in attrs,
// we need to check if this was applied to the current bg color.
if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 {
color |= wincon::BACKGROUND_INTENSITY as u16;
}
Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?;
Ok(())
}
pub(crate) fn set_background_color(bg_color: Color) -> Result<()> {
init_console_color()?;
let color_value: u16 = Colored::BackgroundColor(bg_color).into();
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So we need to use bitwise operators to check if the values exists or to get current console colors.
let attrs = csbi.attributes();
let fg_color = attrs & 0x0007;
let mut color = fg_color | color_value;
// Foreground intensity is a separate value in attrs,
// So we need to check if this was applied to the current fg color.
if (attrs & wincon::FOREGROUND_INTENSITY as u16) != 0 {
color |= wincon::FOREGROUND_INTENSITY as u16;
}
Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?;
Ok(())
}
pub(crate) fn reset() -> Result<()> {
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)?;
}
Ok(())
}
/// Initializes the default console color. It will will be skipped if it has already been initialized.
pub(crate) fn init_console_color() -> Result<()> {
if ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed) == u32::MAX {
let screen_buffer = ScreenBuffer::current()?;
let attr = screen_buffer.info()?.attributes();
ORIGINAL_CONSOLE_COLOR.store(u32::from(attr), Ordering::Relaxed);
}
Ok(())
}
/// 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 {
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")
}
// 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<Colored> for u16 {
/// Returns the WinAPI color value (u16) from the `Colored` struct.
fn from(colored: Colored) -> Self {
match colored {
Colored::ForegroundColor(color) => {
match color {
Color::Black => 0,
Color::DarkGrey => FG_INTENSITY,
Color::Red => FG_INTENSITY | FG_RED,
Color::DarkRed => FG_RED,
Color::Green => FG_INTENSITY | FG_GREEN,
Color::DarkGreen => FG_GREEN,
Color::Yellow => FG_INTENSITY | FG_GREEN | FG_RED,
Color::DarkYellow => FG_GREEN | FG_RED,
Color::Blue => FG_INTENSITY | FG_BLUE,
Color::DarkBlue => FG_BLUE,
Color::Magenta => FG_INTENSITY | FG_RED | FG_BLUE,
Color::DarkMagenta => FG_RED | FG_BLUE,
Color::Cyan => FG_INTENSITY | FG_GREEN | FG_BLUE,
Color::DarkCyan => FG_GREEN | FG_BLUE,
Color::White => FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE,
Color::Grey => FG_RED | FG_GREEN | FG_BLUE,
Color::Reset => {
// safe unwrap, initial console color was set with `init_console_color`.
let original_color = original_console_color();
const REMOVE_BG_MASK: u16 = BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE;
// remove all background values from the original color, we don't want to reset those.
original_color & !REMOVE_BG_MASK
}
/* WinAPI will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/
Color::Rgb { .. } => 0,
Color::AnsiValue(_val) => 0,
}
}
Colored::BackgroundColor(color) => {
match color {
Color::Black => 0,
Color::DarkGrey => BG_INTENSITY,
Color::Red => BG_INTENSITY | BG_RED,
Color::DarkRed => BG_RED,
Color::Green => BG_INTENSITY | BG_GREEN,
Color::DarkGreen => BG_GREEN,
Color::Yellow => BG_INTENSITY | BG_GREEN | BG_RED,
Color::DarkYellow => BG_GREEN | BG_RED,
Color::Blue => BG_INTENSITY | BG_BLUE,
Color::DarkBlue => BG_BLUE,
Color::Magenta => BG_INTENSITY | BG_RED | BG_BLUE,
Color::DarkMagenta => BG_RED | BG_BLUE,
Color::Cyan => BG_INTENSITY | BG_GREEN | BG_BLUE,
Color::DarkCyan => BG_GREEN | BG_BLUE,
Color::White => BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE,
Color::Grey => BG_RED | BG_GREEN | BG_BLUE,
Color::Reset => {
let original_color = original_console_color();
const REMOVE_FG_MASK: u16 = FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE;
// remove all foreground values from the original color, we don't want to reset those.
original_color & !REMOVE_FG_MASK
}
/* WinAPI will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/
Color::Rgb { .. } => 0,
Color::AnsiValue(_val) => 0,
}
}
Colored::UnderlineColor(_) => 0,
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::Ordering;
use crate::style::sys::windows::set_foreground_color;
use super::{
Color, Colored, BG_INTENSITY, BG_RED, FG_INTENSITY, FG_RED, ORIGINAL_CONSOLE_COLOR,
};
#[test]
fn test_parse_fg_color() {
let colored = Colored::ForegroundColor(Color::Red);
assert_eq!(Into::<u16>::into(colored), FG_INTENSITY | FG_RED);
}
#[test]
fn test_parse_bg_color() {
let colored = Colored::BackgroundColor(Color::Red);
assert_eq!(Into::<u16>::into(colored), BG_INTENSITY | BG_RED);
}
#[test]
fn test_original_console_color_is_set() {
assert_eq!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX);
// will call `init_console_color`
set_foreground_color(Color::Blue).unwrap();
assert_ne!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX);
}
}

6
vendor/crossterm/src/style/types.rs vendored Normal file
View file

@ -0,0 +1,6 @@
pub use self::{attribute::Attribute, color::Color, colored::Colored, colors::Colors};
mod attribute;
mod color;
mod colored;
mod colors;

View file

@ -0,0 +1,183 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::super::SetAttribute;
// This macro generates the Attribute enum, its iterator
// function, and the static array containing the sgr code
// of each attribute
macro_rules! Attribute {
(
$(
$(#[$inner:ident $($args:tt)*])*
$name:ident = $sgr:expr,
)*
) => {
/// Represents an attribute.
///
/// # Platform-specific Notes
///
/// * Only UNIX and Windows 10 terminals do support text attributes.
/// * Keep in mind that not all terminals support all attributes.
/// * Crossterm implements almost all attributes listed in the
/// [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters).
///
/// | Attribute | Windows | UNIX | Notes |
/// | :-- | :--: | :--: | :-- |
/// | `Reset` | ✓ | ✓ | |
/// | `Bold` | ✓ | ✓ | |
/// | `Dim` | ✓ | ✓ | |
/// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. |
/// | `Underlined` | ✓ | ✓ | |
/// | `SlowBlink` | ? | ? | Not widely supported, sometimes treated as inverse. |
/// | `RapidBlink` | ? | ? | Not widely supported. MS-DOS ANSI.SYS; 150+ per minute. |
/// | `Reverse` | ✓ | ✓ | |
/// | `Hidden` | ✓ | ✓ | Also known as Conceal. |
/// | `Fraktur` | ✗ | ✓ | Legible characters, but marked for deletion. |
/// | `DefaultForegroundColor` | ? | ? | Implementation specific (according to standard). |
/// | `DefaultBackgroundColor` | ? | ? | Implementation specific (according to standard). |
/// | `Framed` | ? | ? | Not widely supported. |
/// | `Encircled` | ? | ? | This should turn on the encircled attribute. |
/// | `OverLined` | ? | ? | This should draw a line at the top of the text. |
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::style::Attribute;
///
/// println!(
/// "{} Underlined {} No Underline",
/// Attribute::Underlined,
/// Attribute::NoUnderline
/// );
/// ```
///
/// Style existing text:
///
/// ```no_run
/// use crossterm::style::Stylize;
///
/// println!("{}", "Bold text".bold());
/// println!("{}", "Underlined text".underlined());
/// println!("{}", "Negative text".negative());
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[non_exhaustive]
pub enum Attribute {
$(
$(#[$inner $($args)*])*
$name,
)*
}
pub static SGR: &'static[i16] = &[
$($sgr,)*
];
impl Attribute {
/// Iterates over all the variants of the Attribute enum.
pub fn iterator() -> impl Iterator<Item = Attribute> {
use self::Attribute::*;
[ $($name,)* ].iter().copied()
}
}
}
}
Attribute! {
/// Resets all the attributes.
Reset = 0,
/// Increases the text intensity.
Bold = 1,
/// Decreases the text intensity.
Dim = 2,
/// Emphasises the text.
Italic = 3,
/// Underlines the text.
Underlined = 4,
// Other types of underlining
/// Double underlines the text.
DoubleUnderlined = 2,
/// Undercurls the text.
Undercurled = 3,
/// Underdots the text.
Underdotted = 4,
/// Underdashes the text.
Underdashed = 5,
/// Makes the text blinking (< 150 per minute).
SlowBlink = 5,
/// Makes the text blinking (>= 150 per minute).
RapidBlink = 6,
/// Swaps foreground and background colors.
Reverse = 7,
/// Hides the text (also known as Conceal).
Hidden = 8,
/// Crosses the text.
CrossedOut = 9,
/// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface.
///
/// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols).
Fraktur = 20,
/// Turns off the `Bold` attribute. - Inconsistent - Prefer to use NormalIntensity
NoBold = 21,
/// Switches the text back to normal intensity (no bold, italic).
NormalIntensity = 22,
/// Turns off the `Italic` attribute.
NoItalic = 23,
/// Turns off the `Underlined` attribute.
NoUnderline = 24,
/// Turns off the text blinking (`SlowBlink` or `RapidBlink`).
NoBlink = 25,
/// Turns off the `Reverse` attribute.
NoReverse = 27,
/// Turns off the `Hidden` attribute.
NoHidden = 28,
/// Turns off the `CrossedOut` attribute.
NotCrossedOut = 29,
/// Makes the text framed.
Framed = 51,
/// Makes the text encircled.
Encircled = 52,
/// Draws a line at the top of the text.
OverLined = 53,
/// Turns off the `Frame` and `Encircled` attributes.
NotFramedOrEncircled = 54,
/// Turns off the `OverLined` attribute.
NotOverLined = 55,
}
impl Display for Attribute {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", SetAttribute(*self))?;
Ok(())
}
}
impl Attribute {
/// Returns a u32 with one bit set, which is the
/// signature of this attribute in the Attributes
/// bitset.
///
/// The +1 enables storing Reset (whose index is 0)
/// in the bitset Attributes.
#[inline(always)]
pub const fn bytes(self) -> u32 {
1 << ((self as u32) + 1)
}
/// Returns the SGR attribute value.
///
/// See <https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters>
pub fn sgr(self) -> String {
if (self as usize) > 4 && (self as usize) < 9 {
return "4:".to_string() + SGR[self as usize].to_string().as_str();
}
SGR[self as usize].to_string()
}
}

View file

@ -0,0 +1,476 @@
use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr};
#[cfg(feature = "serde")]
use std::fmt;
use crate::style::parse_next_u8;
/// Represents a color.
///
/// # Platform-specific Notes
///
/// The following list of 16 base colors are available for almost all terminals (Windows 7 and 8 included).
///
/// | Light | Dark |
/// | :--| :-- |
/// | `DarkGrey` | `Black` |
/// | `Red` | `DarkRed` |
/// | `Green` | `DarkGreen` |
/// | `Yellow` | `DarkYellow` |
/// | `Blue` | `DarkBlue` |
/// | `Magenta` | `DarkMagenta` |
/// | `Cyan` | `DarkCyan` |
/// | `White` | `Grey` |
///
/// Most UNIX terminals and Windows 10 consoles support additional colors.
/// See [`Color::Rgb`] or [`Color::AnsiValue`] for more info.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Color {
/// Resets the terminal color.
Reset,
/// Black color.
Black,
/// Dark grey color.
DarkGrey,
/// Light red color.
Red,
/// Dark red color.
DarkRed,
/// Light green color.
Green,
/// Dark green color.
DarkGreen,
/// Light yellow color.
Yellow,
/// Dark yellow color.
DarkYellow,
/// Light blue color.
Blue,
/// Dark blue color.
DarkBlue,
/// Light magenta color.
Magenta,
/// Dark magenta color.
DarkMagenta,
/// Light cyan color.
Cyan,
/// Dark cyan color.
DarkCyan,
/// White color.
White,
/// Grey color.
Grey,
/// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info.
///
/// Most UNIX terminals and Windows 10 supported only.
/// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
Rgb { r: u8, g: u8, b: u8 },
/// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info.
///
/// Most UNIX terminals and Windows 10 supported only.
/// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
AnsiValue(u8),
}
impl Color {
/// Parses an ANSI color sequence.
///
/// # Examples
///
/// ```
/// use crossterm::style::Color;
///
/// assert_eq!(Color::parse_ansi("5;0"), Some(Color::Black));
/// assert_eq!(Color::parse_ansi("5;26"), Some(Color::AnsiValue(26)));
/// assert_eq!(Color::parse_ansi("2;50;60;70"), Some(Color::Rgb { r: 50, g: 60, b: 70 }));
/// assert_eq!(Color::parse_ansi("invalid color"), 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 {
type Error = ();
/// Try to create a `Color` from the string representation. This returns an error if the string does not match.
fn try_from(src: &str) -> Result<Self, Self::Error> {
let src = src.to_lowercase();
match src.as_ref() {
"black" => Ok(Color::Black),
"dark_grey" => Ok(Color::DarkGrey),
"red" => Ok(Color::Red),
"dark_red" => Ok(Color::DarkRed),
"green" => Ok(Color::Green),
"dark_green" => Ok(Color::DarkGreen),
"yellow" => Ok(Color::Yellow),
"dark_yellow" => Ok(Color::DarkYellow),
"blue" => Ok(Color::Blue),
"dark_blue" => Ok(Color::DarkBlue),
"magenta" => Ok(Color::Magenta),
"dark_magenta" => Ok(Color::DarkMagenta),
"cyan" => Ok(Color::Cyan),
"dark_cyan" => Ok(Color::DarkCyan),
"white" => Ok(Color::White),
"grey" => Ok(Color::Grey),
_ => Err(()),
}
}
}
impl FromStr for Color {
type Err = ();
/// Creates a `Color` from the string representation.
///
/// # Notes
///
/// * Returns `Color::White` in case of an unknown color.
/// * Does not return `Err` and you can safely unwrap.
fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(Color::try_from(src).unwrap_or(Color::White))
}
}
impl From<(u8, u8, u8)> for Color {
/// Creates a 'Color' from the tuple representation.
fn from(val: (u8, u8, u8)) -> Self {
let (r, g, b) = val;
Self::Rgb { r, g, b }
}
}
#[cfg(feature = "serde")]
impl serde::ser::Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let str = match *self {
Color::Black => "black",
Color::DarkGrey => "dark_grey",
Color::Red => "red",
Color::DarkRed => "dark_red",
Color::Green => "green",
Color::DarkGreen => "dark_green",
Color::Yellow => "yellow",
Color::DarkYellow => "dark_yellow",
Color::Blue => "blue",
Color::DarkBlue => "dark_blue",
Color::Magenta => "magenta",
Color::DarkMagenta => "dark_magenta",
Color::Cyan => "cyan",
Color::DarkCyan => "dark_cyan",
Color::White => "white",
Color::Grey => "grey",
_ => "",
};
if str == "" {
match *self {
Color::AnsiValue(value) => {
return serializer.serialize_str(&format!("ansi_({})", value));
}
Color::Rgb { r, g, b } => {
return serializer.serialize_str(&format!("rgb_({},{},{})", r, g, b));
}
_ => {
return Err(serde::ser::Error::custom("Could not serialize enum type"));
}
}
} else {
return serializer.serialize_str(str);
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::de::Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Color, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> serde::de::Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(
"`black`, `blue`, `dark_blue`, `cyan`, `dark_cyan`, `green`, `dark_green`, `grey`, `dark_grey`, `magenta`, `dark_magenta`, `red`, `dark_red`, `white`, `yellow`, `dark_yellow`, `ansi_(value)`, or `rgb_(r,g,b)`",
)
}
fn visit_str<E>(self, value: &str) -> Result<Color, E>
where
E: serde::de::Error,
{
if let Ok(c) = Color::try_from(value) {
Ok(c)
} else {
if value.contains("ansi") {
// strip away `ansi_(..)' and get the inner value between parenthesis.
let results = value.replace("ansi_(", "").replace(")", "");
let ansi_val = results.parse::<u8>();
if let Ok(ansi) = ansi_val {
return Ok(Color::AnsiValue(ansi));
}
} else if value.contains("rgb") {
// strip away `rgb_(..)' and get the inner values between parenthesis.
let results = value
.replace("rgb_(", "")
.replace(")", "")
.split(',')
.map(|x| x.to_string())
.collect::<Vec<String>>();
if results.len() == 3 {
let r = results[0].parse::<u8>();
let g = results[1].parse::<u8>();
let b = results[2].parse::<u8>();
if r.is_ok() && g.is_ok() && b.is_ok() {
return Ok(Color::Rgb {
r: r.unwrap(),
g: g.unwrap(),
b: b.unwrap(),
});
}
}
}
Err(E::invalid_value(serde::de::Unexpected::Str(value), &self))
}
}
}
deserializer.deserialize_str(ColorVisitor)
}
}
#[cfg(test)]
mod tests {
use super::Color;
#[test]
fn test_known_color_conversion() {
assert_eq!("grey".parse(), Ok(Color::Grey));
assert_eq!("dark_grey".parse(), Ok(Color::DarkGrey));
assert_eq!("red".parse(), Ok(Color::Red));
assert_eq!("dark_red".parse(), Ok(Color::DarkRed));
assert_eq!("green".parse(), Ok(Color::Green));
assert_eq!("dark_green".parse(), Ok(Color::DarkGreen));
assert_eq!("yellow".parse(), Ok(Color::Yellow));
assert_eq!("dark_yellow".parse(), Ok(Color::DarkYellow));
assert_eq!("blue".parse(), Ok(Color::Blue));
assert_eq!("dark_blue".parse(), Ok(Color::DarkBlue));
assert_eq!("magenta".parse(), Ok(Color::Magenta));
assert_eq!("dark_magenta".parse(), Ok(Color::DarkMagenta));
assert_eq!("cyan".parse(), Ok(Color::Cyan));
assert_eq!("dark_cyan".parse(), Ok(Color::DarkCyan));
assert_eq!("white".parse(), Ok(Color::White));
assert_eq!("black".parse(), Ok(Color::Black));
}
#[test]
fn test_unknown_color_conversion_yields_white() {
assert_eq!("foo".parse(), Ok(Color::White));
}
#[test]
fn test_know_rgb_color_conversion() {
assert_eq!(Color::from((0, 0, 0)), Color::Rgb { r: 0, g: 0, b: 0 });
assert_eq!(
Color::from((255, 255, 255)),
Color::Rgb {
r: 255,
g: 255,
b: 255
}
);
}
}
#[cfg(test)]
#[cfg(feature = "serde")]
mod serde_tests {
use super::Color;
use serde_json;
#[test]
fn test_deserial_known_color_conversion() {
assert_eq!(
serde_json::from_str::<Color>("\"Red\"").unwrap(),
Color::Red
);
assert_eq!(
serde_json::from_str::<Color>("\"red\"").unwrap(),
Color::Red
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_red\"").unwrap(),
Color::DarkRed
);
assert_eq!(
serde_json::from_str::<Color>("\"grey\"").unwrap(),
Color::Grey
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_grey\"").unwrap(),
Color::DarkGrey
);
assert_eq!(
serde_json::from_str::<Color>("\"green\"").unwrap(),
Color::Green
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_green\"").unwrap(),
Color::DarkGreen
);
assert_eq!(
serde_json::from_str::<Color>("\"yellow\"").unwrap(),
Color::Yellow
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_yellow\"").unwrap(),
Color::DarkYellow
);
assert_eq!(
serde_json::from_str::<Color>("\"blue\"").unwrap(),
Color::Blue
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_blue\"").unwrap(),
Color::DarkBlue
);
assert_eq!(
serde_json::from_str::<Color>("\"magenta\"").unwrap(),
Color::Magenta
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_magenta\"").unwrap(),
Color::DarkMagenta
);
assert_eq!(
serde_json::from_str::<Color>("\"cyan\"").unwrap(),
Color::Cyan
);
assert_eq!(
serde_json::from_str::<Color>("\"dark_cyan\"").unwrap(),
Color::DarkCyan
);
assert_eq!(
serde_json::from_str::<Color>("\"white\"").unwrap(),
Color::White
);
assert_eq!(
serde_json::from_str::<Color>("\"black\"").unwrap(),
Color::Black
);
}
#[test]
fn test_deserial_unknown_color_conversion() {
assert!(serde_json::from_str::<Color>("\"unknown\"").is_err());
}
#[test]
fn test_deserial_ansi_value() {
assert_eq!(
serde_json::from_str::<Color>("\"ansi_(255)\"").unwrap(),
Color::AnsiValue(255)
);
}
#[test]
fn test_deserial_unvalid_ansi_value() {
assert!(serde_json::from_str::<Color>("\"ansi_(256)\"").is_err());
assert!(serde_json::from_str::<Color>("\"ansi_(-1)\"").is_err());
}
#[test]
fn test_deserial_rgb() {
assert_eq!(
serde_json::from_str::<Color>("\"rgb_(255,255,255)\"").unwrap(),
Color::from((255, 255, 255))
);
}
#[test]
fn test_deserial_unvalid_rgb() {
assert!(serde_json::from_str::<Color>("\"rgb_(255,255,255,255)\"").is_err());
assert!(serde_json::from_str::<Color>("\"rgb_(256,255,255)\"").is_err());
}
}

View file

@ -0,0 +1,270 @@
use std::fmt::{self, Formatter};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::style::{parse_next_u8, Color};
/// Represents a foreground or background color.
///
/// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied
/// using the [SetColors](struct.SetColors.html) command.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Colored {
/// A foreground color.
ForegroundColor(Color),
/// A background color.
BackgroundColor(Color),
/// An underline color.
/// Imporant: doesnt work on windows 10 or lower.
UnderlineColor(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.
///
/// # Examples
///
/// ```
/// use crossterm::style::{Colored::{self, ForegroundColor, BackgroundColor}, Color};
///
/// assert_eq!(Colored::parse_ansi("38;5;0"), Some(ForegroundColor(Color::Black)));
/// assert_eq!(Colored::parse_ansi("38;5;26"), Some(ForegroundColor(Color::AnsiValue(26))));
/// assert_eq!(Colored::parse_ansi("48;2;50;60;70"), Some(BackgroundColor(Color::Rgb { r: 50, g: 60, b: 70 })));
/// assert_eq!(Colored::parse_ansi("49"), Some(BackgroundColor(Color::Reset)));
/// assert_eq!(Colored::parse_ansi("invalid color"), None);
/// ```
///
/// Currently, 3/4 bit color values aren't supported so return `None`.
///
/// See also: [`Color::parse_ansi`].
pub fn parse_ansi(ansi: &str) -> Option<Self> {
use Colored::{BackgroundColor, ForegroundColor, UnderlineColor};
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),
58 => return Color::parse_ansi_iter(values).map(UnderlineColor),
39 => ForegroundColor(Color::Reset),
49 => BackgroundColor(Color::Reset),
59 => UnderlineColor(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;
}
}
Colored::UnderlineColor(new_color) => {
if new_color == Color::Reset {
return f.write_str("59");
} else {
f.write_str("58;")?;
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");
}
}

View file

@ -0,0 +1,234 @@
use crate::style::{Color, Colored};
/// Represents, optionally, a foreground and/or a background color.
///
/// It can be applied using the `SetColors` command.
///
/// It can also be created from a [Colored](enum.Colored.html) value or a tuple of
/// `(Color, Color)` in the order `(foreground, background)`.
///
/// The [then](#method.then) method can be used to combine `Colors` values.
///
/// For example:
/// ```no_run
/// use crossterm::style::{Color, Colors, Colored};
///
/// // An example color, loaded from a config, file in ANSI format.
/// let config_color = "38;2;23;147;209";
///
/// // Default to green text on a black background.
/// let default_colors = Colors::new(Color::Green, Color::Black);
/// // Load a colored value from a config and override the default colors
/// let colors = match Colored::parse_ansi(config_color) {
/// Some(colored) => default_colors.then(&colored.into()),
/// None => default_colors,
/// };
/// ```
///
/// See [Color](enum.Color.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Colors {
pub foreground: Option<Color>,
pub background: Option<Color>,
}
impl Colors {
/// Returns a new `Color` which, when applied, has the same effect as applying `self` and *then*
/// `other`.
pub fn then(&self, other: &Colors) -> Colors {
Colors {
foreground: other.foreground.or(self.foreground),
background: other.background.or(self.background),
}
}
}
impl Colors {
pub fn new(foreground: Color, background: Color) -> Colors {
Colors {
foreground: Some(foreground),
background: Some(background),
}
}
}
impl From<Colored> for Colors {
fn from(colored: Colored) -> Colors {
match colored {
Colored::ForegroundColor(color) => Colors {
foreground: Some(color),
background: None,
},
Colored::BackgroundColor(color) => Colors {
foreground: None,
background: Some(color),
},
Colored::UnderlineColor(color) => Colors {
foreground: None,
background: Some(color),
},
}
}
}
#[cfg(test)]
mod tests {
use crate::style::{Color, Colors};
#[test]
fn test_colors_then() {
use Color::*;
assert_eq!(
Colors {
foreground: None,
background: None,
}
.then(&Colors {
foreground: None,
background: None,
}),
Colors {
foreground: None,
background: None,
}
);
assert_eq!(
Colors {
foreground: None,
background: None,
}
.then(&Colors {
foreground: Some(Black),
background: None,
}),
Colors {
foreground: Some(Black),
background: None,
}
);
assert_eq!(
Colors {
foreground: None,
background: None,
}
.then(&Colors {
foreground: None,
background: Some(Grey),
}),
Colors {
foreground: None,
background: Some(Grey),
}
);
assert_eq!(
Colors {
foreground: None,
background: None,
}
.then(&Colors::new(White, Grey)),
Colors::new(White, Grey),
);
assert_eq!(
Colors {
foreground: None,
background: Some(Blue),
}
.then(&Colors::new(White, Grey)),
Colors::new(White, Grey),
);
assert_eq!(
Colors {
foreground: Some(Blue),
background: None,
}
.then(&Colors::new(White, Grey)),
Colors::new(White, Grey),
);
assert_eq!(
Colors::new(Blue, Green).then(&Colors::new(White, Grey)),
Colors::new(White, Grey),
);
assert_eq!(
Colors {
foreground: Some(Blue),
background: Some(Green),
}
.then(&Colors {
foreground: None,
background: Some(Grey),
}),
Colors {
foreground: Some(Blue),
background: Some(Grey),
}
);
assert_eq!(
Colors {
foreground: Some(Blue),
background: Some(Green),
}
.then(&Colors {
foreground: Some(White),
background: None,
}),
Colors {
foreground: Some(White),
background: Some(Green),
}
);
assert_eq!(
Colors {
foreground: Some(Blue),
background: Some(Green),
}
.then(&Colors {
foreground: None,
background: None,
}),
Colors {
foreground: Some(Blue),
background: Some(Green),
}
);
assert_eq!(
Colors {
foreground: None,
background: Some(Green),
}
.then(&Colors {
foreground: None,
background: None,
}),
Colors {
foreground: None,
background: Some(Green),
}
);
assert_eq!(
Colors {
foreground: Some(Blue),
background: None,
}
.then(&Colors {
foreground: None,
background: None,
}),
Colors {
foreground: Some(Blue),
background: None,
}
);
}
}

442
vendor/crossterm/src/terminal.rs vendored Normal file
View file

@ -0,0 +1,442 @@
//! # Terminal
//!
//! The `terminal` module provides functionality to work with the terminal.
//!
//! This documentation does not contain a lot of examples. The reason is that it's fairly
//! obvious how to use this crate. Although, we do provide
//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository
//! to demonstrate the capabilities.
//!
//! Most terminal actions can be performed with commands.
//! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation.
//!
//! ## Screen Buffer
//!
//! A screen buffer is a two-dimensional array of character
//! and color data which is displayed in a terminal screen.
//!
//! The terminal has several of those buffers and is able to switch between them.
//! The default screen in which you work is called the 'main screen'.
//! The other screens are called the 'alternative screen'.
//!
//! It is important to understand that crossterm does not yet support creating screens,
//! or switch between more than two buffers, and only offers the ability to change
//! between the 'alternate' and 'main screen'.
//!
//! ### Alternate Screen
//!
//! By default, you will be working on the main screen.
//! There is also another screen called the 'alternative' screen.
//! This screen is slightly different from the main screen.
//! For example, it has the exact dimensions of the terminal window,
//! without any scroll-back area.
//!
//! Crossterm offers the possibility to switch to the 'alternative' screen,
//! make some modifications, and move back to the 'main' screen again.
//! The main screen will stay intact and will have the original data as we performed all
//! operations on the alternative screen.
//!
//! An good example of this is Vim.
//! When it is launched from bash, a whole new buffer is used to modify a file.
//! Then, when the modification is finished, it closes again and continues on the main screen.
//!
//! ### Raw Mode
//!
//! By default, the terminal functions in a certain way.
//! For example, it will move the cursor to the beginning of the next line when the input hits the end of a line.
//! Or that the backspace is interpreted for character removal.
//!
//! Sometimes these default modes are irrelevant,
//! and in this case, we can turn them off.
//! This is what happens when you enable raw modes.
//!
//! Those modes will be set when enabling raw modes:
//!
//! - Input will not be forwarded to screen
//! - Input will not be processed on enter press
//! - Input will not be line buffered (input sent byte-by-byte to input buffer)
//! - Special keys like backspace and CTRL+C will not be processed by terminal driver
//! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
//!
//! Raw mode can be enabled/disabled with the [enable_raw_mode](terminal::enable_raw_mode) and [disable_raw_mode](terminal::disable_raw_mode) functions.
//!
//! ## Examples
//!
//! ```no_run
//! use std::io::{stdout, Write};
//! use crossterm::{execute, Result, terminal::{ScrollUp, SetSize, size}};
//!
//! fn main() -> Result<()> {
//! let (cols, rows) = size()?;
//! // Resize terminal and scroll up.
//! execute!(
//! stdout(),
//! SetSize(10, 10),
//! ScrollUp(5)
//! )?;
//!
//! // Be a good citizen, cleanup
//! execute!(stdout(), SetSize(cols, rows))?;
//! Ok(())
//! }
//! ```
//!
//! For manual execution control check out [crossterm::queue](../macro.queue.html).
use std::fmt;
#[cfg(windows)]
use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(windows)]
use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT;
#[doc(no_inline)]
use crate::Command;
use crate::{csi, impl_display, Result};
pub(crate) mod sys;
/// Tells whether the raw mode is enabled.
///
/// Please have a look at the [raw mode](./index.html#raw-mode) section.
pub fn is_raw_mode_enabled() -> Result<bool> {
#[cfg(unix)]
{
Ok(sys::is_raw_mode_enabled())
}
#[cfg(windows)]
{
sys::is_raw_mode_enabled()
}
}
/// Enables raw mode.
///
/// Please have a look at the [raw mode](./index.html#raw-mode) section.
pub fn enable_raw_mode() -> Result<()> {
sys::enable_raw_mode()
}
/// Disables raw mode.
///
/// Please have a look at the [raw mode](./index.html#raw-mode) section.
pub fn disable_raw_mode() -> Result<()> {
sys::disable_raw_mode()
}
/// Returns the terminal size `(columns, rows)`.
///
/// The top left cell is represented `(1, 1)`.
pub fn size() -> Result<(u16, u16)> {
sys::size()
}
/// Disables line wrapping.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableLineWrap;
impl Command for DisableLineWrap {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?7l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
let new_mode = console_mode.mode()? & !ENABLE_WRAP_AT_EOL_OUTPUT;
console_mode.set_mode(new_mode)?;
Ok(())
}
}
/// Enable line wrapping.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableLineWrap;
impl Command for EnableLineWrap {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?7h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let console_mode = ConsoleMode::from(screen_buffer.handle().clone());
let new_mode = console_mode.mode()? | ENABLE_WRAP_AT_EOL_OUTPUT;
console_mode.set_mode(new_mode)?;
Ok(())
}
}
/// A command that switches to alternate screen.
///
/// # Notes
///
/// * Commands must be executed/queued for execution otherwise they do nothing.
/// * Use [LeaveAlternateScreen](./struct.LeaveAlternateScreen.html) command to leave the entered alternate screen.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnterAlternateScreen;
impl Command for EnterAlternateScreen {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?1049h"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate_screen = ScreenBuffer::create()?;
alternate_screen.show()?;
Ok(())
}
}
/// A command that switches back to the main screen.
///
/// # Notes
///
/// * Commands must be executed/queued for execution otherwise they do nothing.
/// * Use [EnterAlternateScreen](./struct.EnterAlternateScreen.html) to enter the alternate screen.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
///
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LeaveAlternateScreen;
impl Command for LeaveAlternateScreen {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("?1049l"))
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::current_out_handle()?);
screen_buffer.show()?;
Ok(())
}
}
/// Different ways to clear the terminal buffer.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ClearType {
/// All cells.
All,
/// All plus history
Purge,
/// All cells from the cursor position downwards.
FromCursorDown,
/// All cells from the cursor position upwards.
FromCursorUp,
/// All cells at the cursor row.
CurrentLine,
/// All cells from the cursor position until the new line.
UntilNewLine,
}
/// A command that scrolls the terminal screen a given number of rows up.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScrollUp(pub u16);
impl Command for ScrollUp {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
if self.0 != 0 {
write!(f, csi!("{}S"), self.0)?;
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::scroll_up(self.0)
}
}
/// A command that scrolls the terminal screen a given number of rows down.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ScrollDown(pub u16);
impl Command for ScrollDown {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
if self.0 != 0 {
write!(f, csi!("{}T"), self.0)?;
}
Ok(())
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::scroll_down(self.0)
}
}
/// A command that clears the terminal screen buffer.
///
/// See the [`ClearType`](enum.ClearType.html) enum.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
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 => csi!("2J"),
ClearType::Purge => csi!("3J"),
ClearType::FromCursorDown => csi!("J"),
ClearType::FromCursorUp => csi!("1J"),
ClearType::CurrentLine => csi!("2K"),
ClearType::UntilNewLine => csi!("K"),
})
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::clear(self.0)
}
}
/// A command that sets the terminal buffer size `(columns, rows)`.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetSize(pub u16, pub u16);
impl Command for SetSize {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, csi!("8;{};{}t"), self.1, self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::set_size(self.0, self.1)
}
}
/// A command that sets the terminal title
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SetTitle<T>(pub T);
impl<T: fmt::Display> Command for SetTitle<T> {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "\x1B]0;{}\x07", &self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
sys::set_window_title(&self.0)
}
}
impl_display!(for ScrollUp);
impl_display!(for ScrollDown);
impl_display!(for SetSize);
impl_display!(for Clear);
#[cfg(test)]
mod tests {
use std::{io::stdout, thread, time};
use crate::execute;
use super::*;
// Test is disabled, because it's failing on Travis CI
#[test]
#[ignore]
fn test_resize_ansi() {
let (width, height) = size().unwrap();
execute!(stdout(), SetSize(35, 35)).unwrap();
// see issue: https://github.com/eminence/terminal-size/issues/11
thread::sleep(time::Duration::from_millis(30));
assert_eq!((35, 35), size().unwrap());
// reset to previous size
execute!(stdout(), SetSize(width, height)).unwrap();
// see issue: https://github.com/eminence/terminal-size/issues/11
thread::sleep(time::Duration::from_millis(30));
assert_eq!((width, height), size().unwrap());
}
#[test]
fn test_raw_mode() {
// check we start from normal mode (may fail on some test harnesses)
assert!(!is_raw_mode_enabled().unwrap());
// enable the raw mode
if enable_raw_mode().is_err() {
// Enabling raw mode doesn't work on the ci
// So we just ignore it
return;
}
// check it worked (on unix it doesn't really check the underlying
// tty but rather check that the code is consistent)
assert!(is_raw_mode_enabled().unwrap());
// enable it again, this should not change anything
enable_raw_mode().unwrap();
// check we're still in raw mode
assert!(is_raw_mode_enabled().unwrap());
// now let's disable it
disable_raw_mode().unwrap();
// check we're back to normal mode
assert!(!is_raw_mode_enabled().unwrap());
}
}

15
vendor/crossterm/src/terminal/sys.rs vendored Normal file
View file

@ -0,0 +1,15 @@
//! This module provides platform related functions.
#[cfg(unix)]
pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size};
#[cfg(windows)]
pub(crate) use self::windows::{
clear, disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, scroll_down, scroll_up,
set_size, set_window_title, size,
};
#[cfg(windows)]
mod windows;
#[cfg(unix)]
mod unix;

View file

@ -0,0 +1,144 @@
//! UNIX related logic for terminal manipulation.
use std::fs::File;
use std::os::unix::io::{IntoRawFd, RawFd};
use std::{io, mem, process};
use libc::{
cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW,
TIOCGWINSZ,
};
use parking_lot::Mutex;
use crate::error::Result;
use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc};
// 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<Option<Termios>> = parking_lot::const_mutex(None);
pub(crate) fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some()
}
#[allow(clippy::useless_conversion)]
pub(crate) fn size() -> Result<(u16, u16)> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true)));
let fd = if let Ok(file) = &file {
file.raw_fd()
} else {
// Fallback to libc::STDOUT_FILENO if /dev/tty is missing
STDOUT_FILENO
};
if wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok()
&& size.ws_col != 0
&& size.ws_row != 0
{
return Ok((size.ws_col, size.ws_row));
}
tput_size().ok_or_else(|| std::io::Error::last_os_error().into())
}
pub(crate) fn enable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
if original_mode.is_some() {
return Ok(());
}
let tty = tty_fd()?;
let fd = tty.raw_fd();
let mut ios = get_terminal_attr(fd)?;
let original_mode_ios = ios;
raw_terminal_attr(&mut ios);
set_terminal_attr(fd, &ios)?;
// Keep it last - set the original mode only if we were able to switch to the raw mode
*original_mode = Some(original_mode_ios);
Ok(())
}
/// Reset the raw mode.
///
/// More precisely, reset the whole termios mode to what it was before the first call
/// to [enable_raw_mode]. If you don't mess with termios outside of crossterm, it's
/// effectively disabling the raw mode and doing nothing else.
pub(crate) fn disable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock();
if let Some(original_mode_ios) = original_mode.as_ref() {
let tty = tty_fd()?;
set_terminal_attr(tty.raw_fd(), original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
}
Ok(())
}
/// execute tput with the given argument and parse
/// the output as a u16.
///
/// The arg should be "cols" or "lines"
fn tput_value(arg: &str) -> Option<u16> {
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
}
}
/// Returns the size of the screen as determined by tput.
///
/// This alternate way of computing the size is useful
/// when in a subshell.
fn tput_size() -> Option<(u16, u16)> {
match (tput_value("cols"), tput_value("lines")) {
(Some(w), Some(h)) => Some((w, h)),
_ => None,
}
}
// Transform the given mode into an raw mode (non-canonical) mode.
fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
}
fn get_terminal_attr(fd: RawFd) -> Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(fd, &mut termios))?;
Ok(termios)
}
}
fn set_terminal_attr(fd: RawFd, termios: &Termios) -> Result<()> {
wrap_with_result(unsafe { tcsetattr(fd, TCSANOW, termios) })
}
fn wrap_with_result(result: i32) -> Result<()> {
if result == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}

View file

@ -0,0 +1,396 @@
//! WinAPI related logic for terminal manipulation.
use std::fmt::{self, Write};
use std::io;
use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size};
use winapi::{
shared::minwindef::DWORD,
um::wincon::{SetConsoleTitleW, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT},
};
use crate::{cursor, terminal::ClearType, ErrorKind, Result};
/// bits which can't be set in raw mode
const NOT_RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT;
pub(crate) fn is_raw_mode_enabled() -> Result<bool> {
let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
let dw_mode = console_mode.mode()?;
Ok(
// check none of the "not raw" bits is set
dw_mode & NOT_RAW_MODE_MASK == 0,
)
}
pub(crate) fn enable_raw_mode() -> Result<()> {
let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode & !NOT_RAW_MODE_MASK;
console_mode.set_mode(new_mode)?;
Ok(())
}
pub(crate) fn disable_raw_mode() -> Result<()> {
let console_mode = ConsoleMode::from(Handle::current_in_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode | NOT_RAW_MODE_MASK;
console_mode.set_mode(new_mode)?;
Ok(())
}
pub(crate) fn size() -> Result<(u16, u16)> {
let terminal_size = ScreenBuffer::current()?.info()?.terminal_size();
// windows starts counting at 0, unix at 1, add one to replicated unix behaviour.
Ok((
(terminal_size.width + 1) as u16,
(terminal_size.height + 1) as u16,
))
}
pub(crate) fn clear(clear_type: ClearType) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
let pos = csbi.cursor_pos();
let buffer_size = csbi.buffer_size();
let current_attribute = csbi.attributes();
match clear_type {
ClearType::All => {
clear_entire_screen(buffer_size, current_attribute)?;
}
ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?,
ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?,
ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?,
ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?,
_ => {
clear_entire_screen(buffer_size, current_attribute)?;
} //TODO: make purge flush the entire screen buffer not just the visible window.
};
Ok(())
}
pub(crate) fn scroll_up(row_count: u16) -> Result<()> {
let csbi = ScreenBuffer::current()?;
let mut window = csbi.info()?.terminal_window();
// check whether the window is too close to the screen buffer top
let count = row_count as i16;
if window.top >= count {
window.top -= count; // move top down
window.bottom -= count; // move bottom down
Console::output()?.set_console_info(true, window)?;
}
Ok(())
}
pub(crate) fn scroll_down(row_count: u16) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
let mut window = csbi.terminal_window();
let buffer_size = csbi.buffer_size();
// check whether the window is too close to the screen buffer top
let count = row_count as i16;
if window.bottom < buffer_size.height - count {
window.top += count; // move top down
window.bottom += count; // move bottom down
Console::output()?.set_console_info(true, window)?;
}
Ok(())
}
pub(crate) fn set_size(width: u16, height: u16) -> Result<()> {
if width <= 1 {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
"terminal width must be at least 1",
));
}
if height <= 1 {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
"terminal height must be at least 1",
));
}
// get the position of the current console window
let screen_buffer = ScreenBuffer::current()?;
let console = Console::from(screen_buffer.handle().clone());
let csbi = screen_buffer.info()?;
let current_size = csbi.buffer_size();
let window = csbi.terminal_window();
let mut new_size = Size::new(current_size.width, current_size.height);
// If the buffer is smaller than this new window size, resize the
// buffer to be large enough. Include window position.
let mut resize_buffer = false;
let width = width as i16;
if current_size.width < window.left + width {
if window.left >= i16::max_value() - width {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
"terminal width too large",
));
}
new_size.width = window.left + width;
resize_buffer = true;
}
let height = height as i16;
if current_size.height < window.top + height {
if window.top >= i16::max_value() - height {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
"terminal height too large",
));
}
new_size.height = window.top + height;
resize_buffer = true;
}
if resize_buffer {
screen_buffer.set_size(new_size.width - 1, new_size.height - 1)?;
}
let mut window = window;
// preserve the position, but change the size.
window.bottom = window.top + height - 1;
window.right = window.left + width - 1;
console.set_console_info(true, window)?;
// if we resized the buffer, un-resize it.
if resize_buffer {
screen_buffer.set_size(current_size.width - 1, current_size.height - 1)?;
}
let bounds = console.largest_window_size()?;
if width > bounds.x {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
format!("terminal width {} too large", width),
));
}
if height > bounds.y {
return Err(ErrorKind::new(
io::ErrorKind::InvalidInput,
format!("terminal height {} too large", height),
));
}
Ok(())
}
pub(crate) fn set_window_title(title: impl fmt::Display) -> Result<()> {
struct Utf16Encoder(Vec<u16>);
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(())
} else {
Err(ErrorKind::last_os_error())
}
}
fn clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
let (mut x, mut y) = (location.x, location.y);
// if cursor position is at the outer right position
if x as i16 > buffer_size.width {
y += 1;
x = 0;
}
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
clear_winapi(start_location, cells_to_write, current_attribute)
}
fn clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
let (xpos, ypos) = (location.x, location.y);
// one cell after cursor position
let x = 0;
// one at row of cursor position
let y = 0;
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1);
// clear everything before cursor position
clear_winapi(start_location, cells_to_write, current_attribute)
}
fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> {
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
// location where to start clearing
let start_location = Coord::new(0, 0);
// clear the entire screen
clear_winapi(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 0,0
cursor::sys::move_to(0, 0)?;
Ok(())
}
fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
// location where to start clearing
let start_location = Coord::new(0, location.y);
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32;
// clear the whole current line
clear_winapi(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 1 on current row
cursor::sys::move_to(0, location.y as u16)?;
Ok(())
}
fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
let (x, y) = (location.x, location.y);
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = (buffer_size.width - x as i16) as u32;
// clear until the current line
clear_winapi(start_location, cells_to_write, current_attribute)?;
// put the cursor back at original cursor position before we did the clearing
cursor::sys::move_to(x as u16, y as u16)?;
Ok(())
}
fn clear_winapi(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> {
let console = Console::from(Handle::current_out_handle()?);
console.fill_whit_character(start_location, cells_to_write, ' ')?;
console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::{ffi::OsString, os::windows::ffi::OsStringExt};
use crossterm_winapi::ScreenBuffer;
use winapi::um::wincon::GetConsoleTitleW;
use super::{scroll_down, scroll_up, set_size, set_window_title, size};
#[test]
fn test_resize_winapi() {
let (width, height) = size().unwrap();
set_size(30, 30).unwrap();
assert_eq!((30, 30), size().unwrap());
// reset to previous size
set_size(width, height).unwrap();
assert_eq!((width, height), size().unwrap());
}
// Test is disabled, because it's failing on Travis CI
#[test]
#[ignore]
fn test_scroll_down_winapi() {
let current_window = ScreenBuffer::current()
.unwrap()
.info()
.unwrap()
.terminal_window();
scroll_down(2).unwrap();
let new_window = ScreenBuffer::current()
.unwrap()
.info()
.unwrap()
.terminal_window();
assert_eq!(new_window.top, current_window.top + 2);
assert_eq!(new_window.bottom, current_window.bottom + 2);
}
// Test is disabled, because it's failing on Travis CI
#[test]
#[ignore]
fn test_scroll_up_winapi() {
// move the terminal buffer down before moving it up
test_scroll_down_winapi();
let current_window = ScreenBuffer::current()
.unwrap()
.info()
.unwrap()
.terminal_window();
scroll_up(2).unwrap();
let new_window = ScreenBuffer::current()
.unwrap()
.info()
.unwrap()
.terminal_window();
assert_eq!(new_window.top, current_window.top - 2);
assert_eq!(new_window.bottom, current_window.bottom - 2);
}
#[test]
fn test_set_title_winapi() {
let test_title = "this is a crossterm test title";
set_window_title(test_title).unwrap();
let mut raw = [0_u16; 128];
let length = unsafe { GetConsoleTitleW(raw.as_mut_ptr(), raw.len() as u32) } as usize;
assert_ne!(0, length);
let console_title = OsString::from_wide(&raw[..length]).into_string().unwrap();
assert_eq!(test_title, &console_title[..]);
}
}

46
vendor/crossterm/src/tty.rs vendored Normal file
View file

@ -0,0 +1,46 @@
//! Making it a little more convenient and safe to query whether
//! something is a terminal teletype or not.
//! This module defines the IsTty trait and the is_tty method to
//! return true if the item represents a terminal.
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use winapi::um::consoleapi::GetConsoleMode;
/// Adds the `is_tty` method to types that might represent a terminal
///
/// ```rust
/// use std::io::stdout;
/// use crossterm::tty::IsTty;
///
/// let is_tty: bool = stdout().is_tty();
/// ```
pub trait IsTty {
/// Returns true when an instance is a terminal teletype, otherwise false.
fn is_tty(&self) -> bool;
}
/// On UNIX, the `isatty()` function returns true if a file
/// descriptor is a terminal.
#[cfg(unix)]
impl<S: AsRawFd> IsTty for S {
fn is_tty(&self) -> bool {
let fd = self.as_raw_fd();
unsafe { libc::isatty(fd) == 1 }
}
}
/// On windows, `GetConsoleMode` will return true if we are in a terminal.
/// Otherwise false.
#[cfg(windows)]
impl<S: AsRawHandle> IsTty for S {
fn is_tty(&self) -> bool {
let mut mode = 0;
let ok = unsafe { GetConsoleMode(self.as_raw_handle() as *mut _, &mut mode) };
ok == 1
}
}