Merge sub crates back into crossterm (#280)

This commit is contained in:
Zrzka 2019-10-22 16:33:38 +02:00 committed by Timon
parent c3c2652b91
commit 2815833a83
55 changed files with 6984 additions and 85 deletions

View File

@ -47,9 +47,24 @@ jobs:
- name: Test Build
run: cargo build
continue-on-error: ${{ matrix.can-fail }}
- name: Run Tests
- name: Test all features
run: cargo test --all-features -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test cursor Feature
run: cargo test --features cursor -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test style Feature
run: cargo test --features style -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test terminal Feature
run: cargo test --features terminal -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test screen Feature
run: cargo test --features screen -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test input Feature
run: cargo test --features input -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
- name: Test Packaging
if: matrix.rust == 'stable'
run: cargo package

View File

@ -34,3 +34,8 @@ script:
- if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi
- cargo build
- cargo test --all-features -- --nocapture --test-threads 1
- cargo test --features cursor -- --nocapture --test-threads 1
- cargo test --features style -- --nocapture --test-threads 1
- cargo test --features terminal -- --nocapture --test-threads 1
- cargo test --features screen -- --nocapture --test-threads 1
- cargo test --features input -- --nocapture --test-threads 1

View File

@ -1,3 +1,10 @@
# Version 0.12.1
- All the `crossterm_` crates code was moved to the `crossterm` crate
- `crossterm_cursor` is in the `cursor` module, etc.
- All these modules are public
- No public API breaking changes
# Version 0.12.0
- Following crates are deprecated and no longer maintained

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm"
version = "0.12.0"
version = "0.12.1"
authors = ["T. Post"]
description = "An crossplatform terminal library for manipulating terminals."
repository = "https://github.com/crossterm-rs/crossterm"
@ -13,20 +13,23 @@ edition = "2018"
[features]
default = ["cursor", "style", "terminal", "screen", "input"]
cursor = ["crossterm_cursor"]
style = ["crossterm_style"]
terminal = ["crossterm_terminal"]
screen = ["crossterm_screen"]
input = ["crossterm_input"]
cursor = ["lazy_static", "input", "winapi/wincon", "winapi/winnt", "winapi/minwindef"]
style = ["lazy_static", "winapi/wincon"]
terminal = ["cursor"]
screen = ["winapi/wincon", "winapi/minwindef"]
input = ["mio", "lazy_static", "screen", "winapi/winnt", "winapi/winuser"]
[dependencies]
crossterm_screen = { version = "0.3.2", optional = true }
crossterm_cursor = { version = "0.4.0", optional = true }
crossterm_terminal = { version = "0.3.2", optional = true }
crossterm_style = { version = "0.5.2", optional = true }
crossterm_input = { version = "0.5.0", optional = true }
crossterm_utils = { version = "0.4.0", optional = false }
lazy_static = { version = "1.4", optional = true }
serde = { version = "1.0.0", features = ["derive"], optional = true }
[target.'cfg(windows)'.dependencies]
winapi = "0.3.8"
crossterm_winapi = "0.3.0"
[target.'cfg(unix)'.dependencies]
libc = "0.2.51"
mio = { version = "0.6.19", optional = true }
[lib]
name = "crossterm"

View File

@ -86,7 +86,7 @@ Click to show Cargo.toml.
```toml
[dependencies]
crossterm = "0.11"
crossterm = "0.12"
```
</details>
@ -117,57 +117,18 @@ All features are enabled by default. You can disable default features and enable
```toml
[dependencies.crossterm]
version = "0.11"
version = "0.12"
default-features = false # Disable default features
features = ["cursor", "screen"] # Enable required features only
```
| Feature | Description | Links |
| :-- | :-- | :-- |
| `input` | Sync/Async input readers | [API documentation](https://docs.rs/crossterm_input), [crates.io](https://crates.io/crates/crossterm_input), [GitHub](https://github.com/crossterm-rs/crossterm-input) |
| `cursor` | Cursor manipulation | [API documentation](https://docs.rs/crossterm_cursor), [crates.io](https://crates.io/crates/crossterm_cursor), [GitHub](https://github.com/crossterm-rs/crossterm-cursor) |
| `screen` | Alternate screen & raw mode | [API documentation](https://docs.rs/crossterm_screen), [crates.io](https://crates.io/crates/crossterm_screen), [GitHub](https://github.com/crossterm-rs/crossterm-screen) |
| `terminal` | Size, clear, scroll | [API documentation](https://docs.rs/crossterm_terminal), [crates.io](https://crates.io/crates/crossterm_terminal), [GitHub](https://github.com/crossterm-rs/crossterm-terminal) |
| `style` | Colors, text attributes | [API documentation](https://docs.rs/crossterm_style), [crates.io](https://crates.io/crates/crossterm_style), [GitHub](https://github.com/crossterm-rs/crossterm-style) |
### `crossterm` vs `crossterm_*` crates
There're two ways how to use the Crossterm library:
* `crossterm` crate with the `cursor` feature flag,
* `crossterm_cursor` crate.
Both provide same functionality, the only difference is the namespace (`crossterm` vs `crossterm_cursor`).
The first way (`crossterm` crate with feature flags) is preferred. The second way will be
deprecated and no longer supported soon. The `crossterm_*` crates will be marked as deprecated and
repositories archived on the GitHub. See the
[Merge sub-crates to the crossterm crate](https://github.com/crossterm-rs/crossterm/issues/265)
for more details.
#### `crossterm` crate:
```toml
[dependencies.crossterm]
version = "0.11"
default-features = false # Disable default features
features = ["cursor"] # Enable cursor feature only
```
```rust
use crossterm::cursor;
```
#### `crossterm_cursor` crate:
```toml
[dependencies]
crossterm_cursor = "0.3"
```
```rust
use crossterm_cursor::cursor;
```
| Feature | Description |
| :-- | :-- |
| `input` | Sync/Async input readers |
| `cursor` | Cursor manipulation |
| `screen` | Alternate screen & raw mode |
| `terminal` | Size, clear, scroll |
| `style` | Colors, text attributes |
### Other Resources

View File

@ -12,34 +12,34 @@ impl Crossterm {
/// Crates a new `TerminalCursor`.
#[cfg(feature = "cursor")]
pub fn cursor(&self) -> crossterm_cursor::TerminalCursor {
crossterm_cursor::TerminalCursor::new()
pub fn cursor(&self) -> crate::cursor::TerminalCursor {
crate::cursor::TerminalCursor::new()
}
/// Creates a new `TerminalInput`.
#[cfg(feature = "input")]
pub fn input(&self) -> crossterm_input::TerminalInput {
crossterm_input::TerminalInput::new()
pub fn input(&self) -> crate::input::TerminalInput {
crate::input::TerminalInput::new()
}
/// Creates a new `Terminal`.
#[cfg(feature = "terminal")]
pub fn terminal(&self) -> crossterm_terminal::Terminal {
crossterm_terminal::Terminal::new()
pub fn terminal(&self) -> crate::terminal::Terminal {
crate::terminal::Terminal::new()
}
/// Creates a new `TerminalColor`.
#[cfg(feature = "style")]
pub fn color(&self) -> crossterm_style::TerminalColor {
crossterm_style::TerminalColor::new()
pub fn color(&self) -> crate::style::TerminalColor {
crate::style::TerminalColor::new()
}
/// Creates a new `StyledObject`.
#[cfg(feature = "style")]
pub fn style<D>(&self, val: D) -> crossterm_style::StyledObject<D>
pub fn style<D>(&self, val: D) -> crate::style::StyledObject<D>
where
D: Display + Clone,
{
crossterm_style::ObjectStyle::new().apply_to(val)
crate::style::ObjectStyle::new().apply_to(val)
}
}

460
src/cursor.rs Normal file
View File

@ -0,0 +1,460 @@
//! # Cursor
//!
//! The `cursor` module provides a 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/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Examples
//!
//! Basic usage:
//!
//! ```no_run
//! // You can replace the following line with `use crossterm::TerminalCursor;`
//! // if you're using the `crossterm` crate with the `cursor` feature enabled.
//! use crossterm::{Result, TerminalCursor};
//!
//! fn main() -> Result<()> {
//! // Get a cursor, save position
//! let cursor = TerminalCursor::new();
//! cursor.save_position()?;
//!
//! // Do something with the cursor
//! cursor.goto(10, 10)?;
//! cursor.blink(true)?;
//!
//! // Be a good citizen, cleanup
//! cursor.blink(false)?;
//! cursor.restore_position()
//! }
//! ```
//!
//! Commands:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{BlinkOff, BlinkOn, execute, Goto, ResetPos, Result, SavePos};
//!
//!
//! fn main() -> Result<()> {
//! execute!(
//! stdout(),
//! SavePos,
//! Goto(10, 10),
//! BlinkOn,
//! BlinkOff,
//! ResetPos
//! )
//! }
//! ```
use cursor::ansi::{self, AnsiCursor};
#[cfg(windows)]
use cursor::windows::WinApiCursor;
use cursor::Cursor;
use crate::impl_display;
#[cfg(windows)]
use crate::utils::supports_ansi;
use crate::utils::{Command, Result};
mod cursor;
mod sys;
/// A terminal cursor.
///
/// The `TerminalCursor` instance is stateless and does not hold any data.
/// You can create as many instances as you want and they will always refer to the
/// same terminal cursor.
///
/// The cursor position is 0 based. For example `0` means first column/row, `1`
/// second column/row, etc.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{Result, TerminalCursor};
///
/// fn main() -> Result<()> {
/// let cursor = TerminalCursor::new();
/// cursor.save_position()?;
///
/// cursor.goto(10, 10)?;
/// cursor.blink(true)?;
///
/// cursor.blink(false)?;
/// cursor.restore_position()
/// }
/// ```
pub struct TerminalCursor {
#[cfg(windows)]
cursor: Box<(dyn Cursor + Sync + Send)>,
#[cfg(unix)]
cursor: AnsiCursor,
}
impl TerminalCursor {
/// Creates a new `TerminalCursor`.
pub fn new() -> TerminalCursor {
#[cfg(windows)]
let cursor = if supports_ansi() {
Box::new(AnsiCursor::new()) as Box<(dyn Cursor + Sync + Send)>
} else {
Box::new(WinApiCursor::new()) as Box<(dyn Cursor + Sync + Send)>
};
#[cfg(unix)]
let cursor = AnsiCursor::new();
TerminalCursor { cursor }
}
/// Moves the cursor to the given position.
pub fn goto(&self, column: u16, row: u16) -> Result<()> {
self.cursor.goto(column, row)
}
/// Returns the cursor position (`(column, row)` tuple).
pub fn pos(&self) -> Result<(u16, u16)> {
self.cursor.pos()
}
/// Moves the cursor `row_count` times up.
pub fn move_up(&mut self, row_count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_up(row_count)?;
Ok(self)
}
/// Moves the cursor `col_count` times right.
pub fn move_right(&mut self, col_count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_right(col_count)?;
Ok(self)
}
/// Moves the cursor `row_count` times down.
pub fn move_down(&mut self, row_count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_down(row_count)?;
Ok(self)
}
/// Moves the cursor `col_count` times left.
pub fn move_left(&mut self, col_count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_left(col_count)?;
Ok(self)
}
/// Saves the cursor position.
///
/// See the [restore_position](struct.TerminalCursor.html#method.restore_position) method.
///
/// # Notes
///
/// The cursor position is stored globally and is not related to the current/any
/// `TerminalCursor` instance.
pub fn save_position(&self) -> Result<()> {
self.cursor.save_position()
}
/// Restores the saved cursor position.
///
/// See the [save_position](struct.TerminalCursor.html#method.save_position) method.
pub fn restore_position(&self) -> Result<()> {
self.cursor.restore_position()
}
/// Hides the cursor.
///
/// See the [show](struct.TerminalCursor.html#method.show) method.
pub fn hide(&self) -> Result<()> {
self.cursor.hide()
}
/// Shows the cursor.
///
/// See the [hide](struct.TerminalCursor.html#method.hide) method.
pub fn show(&self) -> Result<()> {
self.cursor.show()
}
/// Enables or disables the cursor blinking.
///
/// # Notes
///
/// Windows versions lower than Windows 10 do not support this functionality.
pub fn blink(&self, blink: bool) -> Result<()> {
self.cursor.blink(blink)
}
}
/// Creates a new `TerminalCursor`.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{cursor, Result};
///
/// fn main() -> Result<()> {
/// let cursor = cursor();
/// cursor.save_position()?;
///
/// cursor.goto(10, 10)?;
/// cursor.blink(true)?;
///
/// cursor.blink(false)?;
/// cursor.restore_position()
/// }
/// ```
pub fn cursor() -> TerminalCursor {
TerminalCursor::new()
}
/// A command to move the cursor to the given position.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Goto(pub u16, pub u16);
impl Command for Goto {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::goto_csi_sequence(self.0, self.1)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().goto(self.0, self.1)
}
}
/// A command to move the cursor given rows up.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Up(pub u16);
impl Command for Up {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::move_up_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_up(self.0)
}
}
/// A command to move the cursor given rows down.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Down(pub u16);
impl Command for Down {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::move_down_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_down(self.0)
}
}
/// A command to move the cursor given columns left.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Left(pub u16);
impl Command for Left {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::move_left_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_left(self.0)
}
}
/// A command to move the cursor given columns right.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Right(pub u16);
impl Command for Right {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::move_right_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_right(self.0)
}
}
/// A command to save the cursor position.
///
/// # Notes
///
/// The cursor position is stored globally and is not related to the current/any
/// `TerminalCursor` instance.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct SavePos;
impl Command for SavePos {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::SAVE_POSITION_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().save_position()
}
}
/// A command to restore the saved cursor position.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct ResetPos;
impl Command for ResetPos {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::RESTORE_POSITION_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().restore_position()
}
}
/// A command to hide the cursor.
///
/// # Notes
///
/// The cursor position is stored globally and is not related to the current/any
/// `TerminalCursor` instance.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Hide;
impl Command for Hide {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::HIDE_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().hide()
}
}
/// A command to show the cursor.
///
/// # Notes
///
/// The cursor position is stored globally and is not related to the current/any
/// `TerminalCursor` instance.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Show;
impl Command for Show {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::SHOW_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().show()
}
}
/// A command to enable the cursor blinking.
///
/// # Notes
///
/// Windows versions lower than Windows 10 do not support this functionality.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct BlinkOn;
impl Command for BlinkOn {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::BLINKING_ON_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// A command to disable the cursor blinking.
///
/// # Notes
///
/// Windows versions lower than Windows 10 do not support this functionality.
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct BlinkOff;
impl Command for BlinkOff {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi::BLINKING_OFF_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
impl_display!(for Goto);
impl_display!(for Up);
impl_display!(for Down);
impl_display!(for Left);
impl_display!(for Right);
impl_display!(for SavePos);
impl_display!(for ResetPos);
impl_display!(for Hide);
impl_display!(for Show);
impl_display!(for BlinkOn);
impl_display!(for BlinkOff);

45
src/cursor/cursor.rs Normal file
View File

@ -0,0 +1,45 @@
//! A module that contains all the actions related to cursor movement in the terminal.
//! Like: moving the cursor position; saving and resetting the cursor position; hiding showing and control
//! the blinking of the cursor.
//!
//! Note that positions of the cursor are 0 -based witch means that the coordinates (cells) starts counting from 0
use crate::utils::Result;
pub(crate) mod ansi;
#[cfg(windows)]
pub(crate) mod windows;
///! This trait defines the actions that can be performed with the terminal cursor.
///! This trait can be implemented so that a concrete implementation of the ITerminalCursor can fulfill
///! the wishes to work on a specific platform.
///!
///! ## For example:
///!
///! This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
///! so that cursor related actions can be performed on both UNIX and Windows systems.
pub(crate) trait Cursor: Sync + Send {
/// Goto location (`x`, `y`) in the current terminal window.
fn goto(&self, x: u16, y: u16) -> Result<()>;
/// Get the cursor location `(x, y)` in the current terminal window.
fn pos(&self) -> Result<(u16, u16)>;
/// Move cursor `n` times up
fn move_up(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times to the right.
fn move_right(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times down.
fn move_down(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times left.
fn move_left(&self, count: u16) -> Result<()>;
/// Save cursor position so that its saved position can be recalled later. Note that this position
/// is stored program based not per instance of the cursor struct.
fn save_position(&self) -> Result<()>;
/// Return to saved cursor position
fn restore_position(&self) -> Result<()>;
/// Hide the terminal cursor.
fn hide(&self) -> Result<()>;
/// Show the terminal cursor
fn show(&self) -> Result<()>;
/// Enable or disable the blinking of the cursor.
fn blink(&self, blink: bool) -> Result<()>;
}

166
src/cursor/cursor/ansi.rs Normal file
View File

@ -0,0 +1,166 @@
//! This is an ANSI specific implementation for cursor related action.
//! This module is used for windows 10 terminals and UNIX terminals by default.
//! Note that the cursor position is 0 based. This means that we start counting at 0 when setting the cursor position etc.
use crate::utils::Result;
use crate::{csi, write_cout};
use super::{
super::sys::{get_cursor_position, show_cursor},
Cursor,
};
pub(crate) fn goto_csi_sequence(x: u16, y: u16) -> String {
format!(csi!("{};{}H"), y + 1, x + 1)
}
pub(crate) fn move_up_csi_sequence(count: u16) -> String {
format!(csi!("{}A"), count)
}
pub(crate) fn move_right_csi_sequence(count: u16) -> String {
format!(csi!("{}C"), count)
}
pub(crate) fn move_down_csi_sequence(count: u16) -> String {
format!(csi!("{}B"), count)
}
pub(crate) fn move_left_csi_sequence(count: u16) -> String {
format!(csi!("{}D"), count)
}
pub(crate) static SAVE_POSITION_CSI_SEQUENCE: &'static str = csi!("s");
pub(crate) static RESTORE_POSITION_CSI_SEQUENCE: &'static str = csi!("u");
pub(crate) static HIDE_CSI_SEQUENCE: &'static str = csi!("?25l");
pub(crate) static SHOW_CSI_SEQUENCE: &'static str = csi!("?25h");
pub(crate) static BLINKING_ON_CSI_SEQUENCE: &'static str = csi!("?12h");
pub(crate) static BLINKING_OFF_CSI_SEQUENCE: &'static str = csi!("?12l");
/// This struct is an ANSI implementation for cursor related actions.
pub(crate) struct AnsiCursor;
impl AnsiCursor {
pub(crate) fn new() -> AnsiCursor {
AnsiCursor
}
}
impl Cursor for AnsiCursor {
fn goto(&self, x: u16, y: u16) -> Result<()> {
write_cout!(goto_csi_sequence(x, y))?;
Ok(())
}
fn pos(&self) -> Result<(u16, u16)> {
get_cursor_position()
}
fn move_up(&self, count: u16) -> Result<()> {
write_cout!(move_up_csi_sequence(count))?;
Ok(())
}
fn move_right(&self, count: u16) -> Result<()> {
write_cout!(move_right_csi_sequence(count))?;
Ok(())
}
fn move_down(&self, count: u16) -> Result<()> {
write_cout!(move_down_csi_sequence(count))?;
Ok(())
}
fn move_left(&self, count: u16) -> Result<()> {
write_cout!(move_left_csi_sequence(count))?;
Ok(())
}
fn save_position(&self) -> Result<()> {
write_cout!(SAVE_POSITION_CSI_SEQUENCE)?;
Ok(())
}
fn restore_position(&self) -> Result<()> {
write_cout!(RESTORE_POSITION_CSI_SEQUENCE)?;
Ok(())
}
fn hide(&self) -> Result<()> {
show_cursor(false)?;
Ok(())
}
fn show(&self) -> Result<()> {
show_cursor(true)?;
Ok(())
}
fn blink(&self, blink: bool) -> Result<()> {
if blink {
write_cout!(BLINKING_ON_CSI_SEQUENCE)?;
} else {
write_cout!(BLINKING_OFF_CSI_SEQUENCE)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{AnsiCursor, Cursor};
// TODO - Test is ingored, because it's stalled on Travis CI
#[test]
#[ignore]
fn test_save_restore_position() {
if try_enable_ansi() {
let cursor = AnsiCursor::new();
let (saved_x, saved_y) = cursor.pos().unwrap();
cursor.save_position().unwrap();
cursor.goto(saved_x + 1, saved_y + 1).unwrap();
cursor.restore_position().unwrap();
let (x, y) = cursor.pos().unwrap();
assert_eq!(x, saved_x);
assert_eq!(y, saved_y);
}
}
// TODO - Test is ingored, because it's stalled on Travis CI
#[test]
#[ignore]
fn test_goto() {
if try_enable_ansi() {
let cursor = AnsiCursor::new();
let (saved_x, saved_y) = cursor.pos().unwrap();
cursor.goto(saved_x + 1, saved_y + 1).unwrap();
assert_eq!(cursor.pos().unwrap(), (saved_x + 1, saved_y + 1));
cursor.goto(saved_x, saved_y).unwrap();
assert_eq!(cursor.pos().unwrap(), (saved_x, saved_y));
}
}
fn try_enable_ansi() -> bool {
#[cfg(windows)]
{
if cfg!(target_os = "windows") {
use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing;
// if it is not listed we should try with WinApi to check if we do support ANSI-codes.
match set_virtual_terminal_processing(true) {
Ok(_) => return true,
Err(_) => return false,
}
}
}
true
}
}

View File

@ -0,0 +1,111 @@
//! This is a WINAPI specific implementation for cursor related actions.
//! This module is used for Windows terminals that do not support ANSI escape codes.
//! Note that the cursor position is 0 based. This means that we start counting at 0 when setting the cursor position.
use crate::utils::Result;
use super::{super::sys::windows::ScreenBufferCursor, Cursor};
/// This struct is a windows implementation for cursor related actions.
pub(crate) struct WinApiCursor;
impl WinApiCursor {
pub(crate) fn new() -> WinApiCursor {
WinApiCursor
}
}
impl Cursor for WinApiCursor {
fn goto(&self, x: u16, y: u16) -> Result<()> {
let cursor = ScreenBufferCursor::new()?;
cursor.goto(x as i16, y as i16)?;
Ok(())
}
fn pos(&self) -> Result<(u16, u16)> {
let cursor = ScreenBufferCursor::new()?;
Ok(cursor.position()?.into())
}
fn move_up(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos()?;
self.goto(xpos, ypos - count)?;
Ok(())
}
fn move_right(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos()?;
self.goto(xpos + count, ypos)?;
Ok(())
}
fn move_down(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos()?;
self.goto(xpos, ypos + count)?;
Ok(())
}
fn move_left(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos()?;
self.goto(xpos - count, ypos)?;
Ok(())
}
fn save_position(&self) -> Result<()> {
ScreenBufferCursor::save_cursor_pos()?;
Ok(())
}
fn restore_position(&self) -> Result<()> {
ScreenBufferCursor::restore_cursor_pos()?;
Ok(())
}
fn hide(&self) -> Result<()> {
ScreenBufferCursor::new()?.set_visibility(false)?;
Ok(())
}
fn show(&self) -> Result<()> {
ScreenBufferCursor::new()?.set_visibility(true)?;
Ok(())
}
fn blink(&self, _blink: bool) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Cursor, WinApiCursor};
#[test]
fn test_goto() {
let cursor = WinApiCursor::new();
let (saved_x, saved_y) = cursor.pos().unwrap();
cursor.goto(saved_x + 1, saved_y + 1).unwrap();
assert_eq!(cursor.pos().unwrap(), (saved_x + 1, saved_y + 1));
cursor.goto(saved_x, saved_y).unwrap();
assert_eq!(cursor.pos().unwrap(), (saved_x, saved_y));
}
#[test]
fn test_save_restore_position() {
let cursor = WinApiCursor::new();
let (saved_x, saved_y) = cursor.pos().unwrap();
cursor.save_position().unwrap();
cursor.goto(saved_x + 1, saved_y + 1).unwrap();
cursor.restore_position().unwrap();
let (x, y) = cursor.pos().unwrap();
assert_eq!(x, saved_x);
assert_eq!(y, saved_y);
}
}

14
src/cursor/sys.rs Normal file
View File

@ -0,0 +1,14 @@
#[cfg(unix)]
pub(crate) use self::unix::get_cursor_position;
#[cfg(unix)]
pub(crate) use self::unix::show_cursor;
#[cfg(windows)]
pub(crate) use self::windows::get_cursor_position;
#[cfg(windows)]
pub(crate) use self::windows::show_cursor;
#[cfg(windows)]
pub(crate) mod windows;
#[cfg(unix)]
pub(crate) mod unix;

50
src/cursor/sys/unix.rs Normal file
View File

@ -0,0 +1,50 @@
use std::io::{self, Write};
use crate::input::{InputEvent, TerminalInput};
use crate::utils::{
sys::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled},
Result,
};
use crate::{csi, write_cout};
pub(crate) fn get_cursor_position() -> Result<(u16, u16)> {
if is_raw_mode_enabled() {
pos_raw()
} else {
pos()
}
}
pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
if show_cursor {
write_cout!(csi!("?25h"))?;
} else {
write_cout!(csi!("?25l"))?;
}
Ok(())
}
fn pos() -> Result<(u16, u16)> {
enable_raw_mode()?;
let pos = pos_raw();
disable_raw_mode()?;
pos
}
fn pos_raw() -> Result<(u16, u16)> {
// Where is the cursor?
// Use `ESC [ 6 n`.
let mut stdout = io::stdout();
// Write command
stdout.write_all(b"\x1B[6n")?;
stdout.flush()?;
let mut reader = TerminalInput::new().read_sync();
loop {
if let Some(InputEvent::CursorPosition(x, y)) = reader.next() {
return Ok((x, y));
}
}
}

136
src/cursor/sys/windows.rs Normal file
View File

@ -0,0 +1,136 @@
//! This module handles some logic for cursor interaction in the windows console.
use std::io;
use std::sync::Mutex;
use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer};
use winapi::{
shared::minwindef::{FALSE, TRUE},
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
um::winnt::HANDLE,
};
use lazy_static::lazy_static;
use crate::utils::Result;
pub(crate) fn get_cursor_position() -> Result<(u16, u16)> {
let cursor = ScreenBufferCursor::new()?;
Ok(cursor.position()?.into())
}
pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> {
ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
}
lazy_static! {
static ref SAVED_CURSOR_POS: Mutex<Option<(i16, i16)>> = Mutex::new(None);
}
pub(crate) struct ScreenBufferCursor {
screen_buffer: ScreenBuffer,
}
impl ScreenBufferCursor {
pub(crate) fn new() -> Result<ScreenBufferCursor> {
Ok(ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
})
}
/// get the current cursor position.
pub(crate) fn position(&self) -> Result<Coord> {
Ok(self.screen_buffer.info()?.cursor_pos())
}
/// Set the cursor position to the given x and y. Note that this is 0 based.
pub(crate) fn goto(&self, x: i16, y: i16) -> Result<()> {
if x < 0 || x >= <i16>::max_value() {
Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Argument Out of Range Exception when setting cursor position to X: {}",
x
),
))?;
}
if y < 0 || y >= <i16>::max_value() {
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 !is_true(SetConsoleCursorPosition(
**self.screen_buffer.handle(),
position,
)) {
Err(io::Error::last_os_error())?;
}
}
Ok(())
}
/// change the cursor visibility.
pub(crate) fn set_visibility(&self, visible: bool) -> Result<()> {
let cursor_info = CONSOLE_CURSOR_INFO {
dwSize: 100,
bVisible: if visible { TRUE } else { FALSE },
};
unsafe {
if !is_true(SetConsoleCursorInfo(
**self.screen_buffer.handle(),
&cursor_info,
)) {
Err(io::Error::last_os_error())?;
}
}
Ok(())
}
/// Reset to saved cursor position
pub(crate) fn restore_cursor_pos() -> Result<()> {
let cursor = ScreenBufferCursor::new()?;
if let Some((x, y)) = *SAVED_CURSOR_POS.lock().unwrap() {
cursor.goto(x, y)?;
}
Ok(())
}
/// Save current cursor position to recall later.
pub(crate) fn save_cursor_pos() -> Result<()> {
let cursor = ScreenBufferCursor::new()?;
let position = cursor.position()?;
let mut locked_pos = SAVED_CURSOR_POS.lock().unwrap();
*locked_pos = Some((position.x, position.y));
Ok(())
}
}
impl From<Handle> for ScreenBufferCursor {
fn from(handle: Handle) -> Self {
ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}
impl From<HANDLE> for ScreenBufferCursor {
fn from(handle: HANDLE) -> Self {
ScreenBufferCursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}

425
src/input.rs Normal file
View File

@ -0,0 +1,425 @@
//! # Input
//!
//! The `input` module provides a functionality to read the input events.
//!
//! 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/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Synchronous vs Asynchronous
//!
//! ### Synchronous Reading
//!
//! Read the input synchronously from the user, the reads performed will be blocking calls.
//! Using synchronous over asynchronous reading has the benefit that it is using fewer resources than
//! the asynchronous because background thread and queues are left away.
//!
//! See the [`SyncReader`](struct.SyncReader.html) documentation for more details.
//!
//! ### Asynchronous Reading
//!
//! Read the input asynchronously, input events are gathered in the background and queued for you to read.
//! Using asynchronous reading has the benefit that input events are queued until you read them. You can poll
//! for occurred events, and the reads won't block your program.
//!
//! See the [`AsyncReader`](struct.AsyncReader.html) documentation for more details.
//!
//! ### Technical details
//!
//! On UNIX systems crossterm reads from the TTY, on Windows, it uses `ReadConsoleInputW`.
//! For asynchronous reading, a background thread will be fired up to read input events,
//! occurred events will be queued on an MPSC-channel, and the user can iterate over those events.
//!
//! The terminal has to be in the raw mode, raw mode prevents the input of the user to be displayed
//! on the terminal screen. See the
//! [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::utils::Result;
#[cfg(unix)]
use self::input::unix::UnixInput;
#[cfg(windows)]
use self::input::windows::WindowsInput;
use self::input::Input;
pub use self::input::{AsyncReader, SyncReader};
mod input;
mod sys;
/// Represents an input event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone)]
pub enum InputEvent {
/// A single key or a combination of keys.
Keyboard(KeyEvent),
/// A mouse event.
Mouse(MouseEvent),
/// An unsupported event.
///
/// You can ignore this type of event, because it isn't used.
Unsupported(Vec<u8>), // TODO Not used, should be removed.
/// An unknown event.
Unknown,
/// Internal cursor position event. Don't use it, it will be removed in the
/// `crossterm` 1.0.
#[doc(hidden)]
#[cfg(unix)]
CursorPosition(u16, u16), // TODO 1.0: Remove
}
/// Represents a mouse event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Copy)]
pub enum MouseEvent {
/// Pressed mouse button at the location (column, row).
Press(MouseButton, u16, u16),
/// Released mouse button at the location (column, row).
Release(u16, u16),
/// Mouse moved with a pressed left button to the new location (column, row).
Hold(u16, u16),
/// An unknown mouse event.
Unknown,
}
/// Represents a mouse button/wheel.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Copy)]
pub enum MouseButton {
/// Left mouse button.
Left,
/// Right mouse button.
Right,
/// Middle mouse button.
Middle,
/// Wheel scrolled up.
WheelUp,
/// Wheel scrolled down.
WheelDown,
}
/// Represents a key or a combination of keys.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyEvent {
/// 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 dow key.
PageDown,
/// Tab key.
Tab,
/// Shift + Tab key.
BackTab,
/// Delete key.
Delete,
/// Insert key.
Insert,
/// F key.
///
/// `KeyEvent::F(1)` represents F1 key, etc.
F(u8),
/// A character.
///
/// `KeyEvent::Char('c')` represents `c` character, etc.
Char(char),
/// Alt key + character.
///
/// `KeyEvent::Alt('c')` represents `Alt + c`, etc.
Alt(char),
/// Ctrl key + character.
///
/// `KeyEvent::Ctrl('c') ` represents `Ctrl + c`, etc.
Ctrl(char),
/// Null.
Null,
/// Escape key.
Esc,
/// Ctrl + up arrow key.
CtrlUp,
/// Ctrl + down arrow key.
CtrlDown,
/// Ctrl + right arrow key.
CtrlRight,
/// Ctrl + left arrow key.
CtrlLeft,
/// Shift + up arrow key.
ShiftUp,
/// Shift + down arrow key.
ShiftDown,
/// Shift + right arrow key.
ShiftRight,
/// Shift + left arrow key.
ShiftLeft,
}
/// An internal event.
///
/// Encapsulates publicly available `InputEvent` with additional internal
/// events that shouldn't be publicly available to the crate users.
#[cfg(unix)]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone)]
pub(crate) enum InternalEvent {
/// An input event.
Input(InputEvent),
/// A cursor position (`x`, `y`).
CursorPosition(u16, u16),
}
/// Converts an `InternalEvent` into a possible `InputEvent`.
#[cfg(unix)]
impl From<InternalEvent> for Option<InputEvent> {
fn from(ie: InternalEvent) -> Self {
match ie {
InternalEvent::Input(input_event) => Some(input_event),
// TODO 1.0: Swallow `CursorPosition` and return `None`.
// `cursor::pos_raw()` will be able to use this module `internal_event_receiver()`
InternalEvent::CursorPosition(x, y) => Some(InputEvent::CursorPosition(x, y)),
}
}
}
/// A terminal input.
///
/// # Examples
///
/// ```no_run
/// // You can replace the following line with `use crossterm::...;`
/// // if you're using the `crossterm` crate with the `input` feature enabled.
/// use crossterm::{Result, TerminalInput, RawScreen};
///
/// fn main() -> Result<()> {
/// let input = TerminalInput::new();
/// // Read a single character
/// let char = input.read_char()?;
/// // Read a single line
/// let line = input.read_line()?;
///
/// // Make sure to enable raw screen when reading input events
/// let screen = RawScreen::into_raw_mode();
///
/// // Create async reader
/// let mut async_stdin = input.read_async();
///
/// // Create sync reader
/// let mut sync_stdin = input.read_sync();
///
/// // Enable mouse input events
/// input.enable_mouse_mode()?;
/// // Disable mouse input events
/// input.disable_mouse_mode()
/// }
/// ```
pub struct TerminalInput {
#[cfg(windows)]
input: WindowsInput,
#[cfg(unix)]
input: UnixInput,
}
impl TerminalInput {
/// Creates a new `TerminalInput`.
pub fn new() -> TerminalInput {
#[cfg(windows)]
let input = WindowsInput::new();
#[cfg(unix)]
let input = UnixInput::new();
TerminalInput { input }
}
/// Reads one line from the user input and strips the new line character(s).
///
/// This function **does not work** when the raw mode is enabled (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation
/// to learn more). You should use the
/// [`read_async`](struct.TerminalInput.html#method.read_async),
/// [`read_until_async`](struct.TerminalInput.html#method.read_until_async)
/// or [`read_sync`](struct.TerminalInput.html#method.read_sync) method if the
/// raw mode is enabled.
///
/// # Examples
///
/// ```no_run
/// let input = crossterm::input();
/// match input.read_line() {
/// Ok(s) => println!("string typed: {}", s),
/// Err(e) => println!("error: {}", e),
/// }
/// ```
pub fn read_line(&self) -> Result<String> {
self.input.read_line()
}
/// Reads one character from the user input.
///
/// # Examples
///
/// ```no_run
/// let input = crossterm::input();
/// match input.read_char() {
/// Ok(c) => println!("character pressed: {}", c),
/// Err(e) => println!("error: {}", e),
/// }
/// ```
pub fn read_char(&self) -> Result<char> {
self.input.read_char()
}
/// Creates a new `AsyncReader` allowing to read the input asynchronously (not blocking).
///
/// If you want a blocking, or less resource consuming read, see the
/// [`read_sync`](struct.TerminalInput.html#method.read_sync) method.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * A thread is spawned to read the input.
/// * The reading thread is cleaned up when you drop the [`AsyncReader`](struct.AsyncReader.html).
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
/// use crossterm::input;
///
/// let mut async_stdin = input().read_async();
///
/// loop {
/// if let Some(key_event) = async_stdin.next() {
/// /* Check which event occurred here */
/// }
///
/// thread::sleep(Duration::from_millis(50));
/// }
/// ```
pub fn read_async(&self) -> AsyncReader {
self.input.read_async()
}
/// Creates a new `AsyncReader` allowing to read the input asynchronously (not blocking) until the
/// given `delimiter`.
///
/// It behaves in the same way as the [`read_async`](struct.TerminalInput.html#method.read_async)
/// method, but it stops reading when the `delimiter` is hit.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * A thread is spawned to read the input.
/// * The reading thread is cleaned up when you drop the [`AsyncReader`](struct.AsyncReader.html).
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// let mut async_stdin = crossterm::input().read_until_async(b'x');
///
/// loop {
/// if let Some(key_event) = async_stdin.next() {
/// /* Check which event occurred here */
/// }
///
/// thread::sleep(Duration::from_millis(50));
/// }
/// ```
pub fn read_until_async(&self, delimiter: u8) -> AsyncReader {
self.input.read_until_async(delimiter)
}
/// Creates a new `SyncReader` allowing to read the input synchronously (blocking).
///
/// It's less resource hungry when compared to the
/// [`read_async`](struct.TerminalInput.html#method.read_async) method, because it doesn't
/// spawn any reading threads.
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// let mut sync_stdin = crossterm::input().read_sync();
///
/// loop {
/// if let Some(key_event) = sync_stdin.next() {
/// /* Check which event occurred here */
/// }
/// }
/// ```
pub fn read_sync(&self) -> SyncReader {
self.input.read_sync()
}
/// Enables mouse events.
///
/// Mouse events will be produced by the
/// [`AsyncReader`](struct.AsyncReader.html)/[`SyncReader`](struct.SyncReader.html).
pub fn enable_mouse_mode(&self) -> Result<()> {
self.input.enable_mouse_mode()
}
/// Disables mouse events.
///
/// Mouse events wont be produced by the
/// [`AsyncReader`](struct.AsyncReader.html)/[`SyncReader`](struct.SyncReader.html).
pub fn disable_mouse_mode(&self) -> Result<()> {
self.input.disable_mouse_mode()
}
}
/// Creates a new `TerminalInput`.
///
/// # Examples
///
/// ```no_run
/// // You can replace the following line with `use crossterm::...;`
/// // if you're using the `crossterm` crate with the `input` feature enabled.
/// use crossterm::{input, RawScreen, Result};
///
/// fn main() -> Result<()> {
/// let input = input();
/// // Read a single character
/// let char = input.read_char()?;
/// // Read a single line
/// let line = input.read_line()?;
///
/// // Make sure to enable raw screen when reading input events
/// let screen = RawScreen::into_raw_mode();
///
/// // Create async reader
/// let mut async_stdin = input.read_async();
///
/// // Create sync reader
/// let mut sync_stdin = input.read_sync();
///
/// // Enable mouse input events
/// input.enable_mouse_mode()?;
/// // Disable mouse input events
/// input.disable_mouse_mode()
/// }
/// ```
pub fn input() -> TerminalInput {
TerminalInput::new()
}

68
src/input/input.rs Normal file
View File

@ -0,0 +1,68 @@
//! A module that contains all the actions related to reading input from the terminal.
//! Like reading a line, reading a character and reading asynchronously.
use std::io;
use crate::utils::Result;
// TODO Create a new common AsyncReader structure (like TerminalCursor, TerminalInput, ...).
// To avoid copy & pasting of the documentation, to sync the code organization, ...
#[cfg(unix)]
pub use self::unix::{AsyncReader, SyncReader};
#[cfg(windows)]
pub use self::windows::{AsyncReader, SyncReader};
#[cfg(unix)]
pub(crate) mod unix;
#[cfg(windows)]
pub(crate) mod windows;
/// This trait defines the actions that can be performed with the terminal input.
/// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill
/// the wishes to work on a specific platform.
///
/// ## For example:
///
/// This trait is implemented for Windows and UNIX systems.
/// Unix is using the 'TTY' and windows is using 'libc' C functions to read the input.
pub(crate) trait Input {
/// Reads one line from the user input and strips the new line character(s).
///
/// This function **does not work** when the raw mode is enabled (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation
/// to learn more). You should use the
/// [`read_async`](struct.TerminalInput.html#method.read_async),
/// [`read_until_async`](struct.TerminalInput.html#method.read_until_async)
/// or [`read_sync`](struct.TerminalInput.html#method.read_sync) method if the
/// raw mode is enabled.
///
/// # Examples
///
/// ```no_run
/// let input = crossterm::input();
/// match input.read_line() {
/// Ok(s) => println!("string typed: {}", s),
/// Err(e) => println!("error: {}", e),
/// }
/// ```
fn read_line(&self) -> Result<String> {
let mut rv = String::new();
io::stdin().read_line(&mut rv)?;
let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
rv.truncate(len);
Ok(rv)
}
/// Read one character from the user input
fn read_char(&self) -> Result<char>;
/// Read the input asynchronously from the user.
fn read_async(&self) -> AsyncReader;
/// Read the input asynchronously until a certain character is hit.
fn read_until_async(&self, delimiter: u8) -> AsyncReader;
/// Read the input synchronously from the user.
fn read_sync(&self) -> SyncReader;
/// Start monitoring mouse events.
fn enable_mouse_mode(&self) -> Result<()>;
/// Stop monitoring mouse events.
fn disable_mouse_mode(&self) -> Result<()>;
}

299
src/input/input/unix.rs Normal file
View File

@ -0,0 +1,299 @@
//! This is a UNIX specific implementation for input related action.
use std::sync::mpsc::Receiver;
use std::{char, sync::mpsc};
use crate::utils::Result;
use crate::{csi, write_cout};
use super::{
super::{sys::unix::internal_event_receiver, InputEvent, InternalEvent, KeyEvent},
Input,
};
pub(crate) struct UnixInput;
impl UnixInput {
pub fn new() -> UnixInput {
UnixInput {}
}
}
impl Input for UnixInput {
fn read_char(&self) -> Result<char> {
let mut reader = self.read_sync();
loop {
if let Some(InputEvent::Keyboard(KeyEvent::Char(ch))) = reader.next() {
return Ok(ch);
}
}
}
fn read_async(&self) -> AsyncReader {
AsyncReader::new(None)
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
let sentinel = match delimiter {
b'\n' | b'\r' => Some(KeyEvent::Enter),
b'\x1B' => Some(KeyEvent::Esc),
c if c.is_ascii() => Some(KeyEvent::Char(c as char)),
_ => None,
}
.map(InputEvent::Keyboard);
AsyncReader::new(sentinel)
}
fn read_sync(&self) -> SyncReader {
SyncReader::new()
}
fn enable_mouse_mode(&self) -> Result<()> {
write_cout!(&format!(
"{}h{}h{}h{}h",
csi!("?1000"),
csi!("?1002"),
csi!("?1015"),
csi!("?1006")
))?;
Ok(())
}
fn disable_mouse_mode(&self) -> Result<()> {
write_cout!(&format!(
"{}l{}l{}l{}l",
csi!("?1006"),
csi!("?1015"),
csi!("?1002"),
csi!("?1000")
))?;
Ok(())
}
}
/// An asynchronous input reader (not blocking).
///
/// `AsyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
/// trait. Documentation says:
///
/// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
/// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
/// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
/// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
///
/// `AsyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
/// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
/// received `None`.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * A thread is spawned/reused to read the input.
/// * The reading thread is cleaned up when you drop the `AsyncReader`.
/// * See the [`SyncReader`](struct.SyncReader.html) if you want a blocking,
/// or a less resource hungry reader.
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// use crossterm::{input, InputEvent, KeyEvent, RawScreen};
///
/// fn main() {
/// println!("Press 'ESC' to quit.");
///
/// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
/// let _raw = RawScreen::into_raw_mode();
///
/// // Create an input from our screen
/// let input = input();
///
/// // Create an async reader
/// let mut reader = input.read_async();
///
/// loop {
/// if let Some(event) = reader.next() { // Not a blocking call
/// match event {
/// InputEvent::Keyboard(KeyEvent::Esc) => {
/// println!("Program closing ...");
/// break;
/// }
/// InputEvent::Mouse(event) => { /* Mouse event */ }
/// _ => { /* Other events */ }
/// }
/// }
/// thread::sleep(Duration::from_millis(50));
/// }
/// } // `reader` dropped <- thread cleaned up, `_raw` dropped <- raw mode disabled
/// ```
pub struct AsyncReader {
rx: Option<Receiver<InternalEvent>>,
stop_event: Option<InputEvent>,
}
impl AsyncReader {
/// Creates a new `AsyncReader`.
///
/// # Arguments
///
/// * `stop_event` - if set, no more events will be produced if this exact event is reached.
///
/// # Notes
///
/// * A thread is spawned/reused to read the input.
/// * The reading thread is cleaned up when you drop the `AsyncReader`.
fn new(stop_event: Option<InputEvent>) -> AsyncReader {
// TODO 1.0: Following expect is here to keep the API compatible (no Result)
AsyncReader {
rx: Some(internal_event_receiver().expect("Unable to get event receiver")),
stop_event,
}
}
// TODO If we we keep the Drop semantics, do we really need this in the public API? It's useless as
// there's no `start`, etc.
/// Stops the input reader.
///
/// # Notes
///
/// * You don't need to call this method, because it will be automatically called when the
/// `AsyncReader` is dropped.
pub fn stop(&mut self) {
self.rx = None;
}
}
impl Iterator for AsyncReader {
type Item = InputEvent;
/// Tries to read the next input event (not blocking).
///
/// `None` doesn't mean that the iteration is finished. See the
/// [`AsyncReader`](struct.AsyncReader.html) documentation for more information.
fn next(&mut self) -> Option<Self::Item> {
// TODO 1.0: This whole `InternalEvent` -> `InputEvent` mapping should be shared
// between UNIX & Windows implementations
let ref mut rx = match self.rx.as_ref() {
Some(rx) => rx,
None => return None,
};
match rx.try_recv() {
Ok(internal_event) => {
let input_event = internal_event.into();
if self.stop_event.is_some() && input_event == self.stop_event {
// Drop the receiver, stop event received
self.rx = None;
}
input_event
}
Err(mpsc::TryRecvError::Empty) => None,
Err(mpsc::TryRecvError::Disconnected) => {
// Sender dropped, drop the receiver
self.rx = None;
None
}
}
}
}
/// A synchronous input reader (blocking).
///
/// `SyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
/// trait. Documentation says:
///
/// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
/// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
/// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
/// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
///
/// `SyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
/// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
/// received `None`. Unfortunately, `None` means that an error occurred, but you're free to call `next`
/// again. This behavior will be changed in the future to avoid errors consumption.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * See the [`AsyncReader`](struct.AsyncReader.html) if you want a non blocking reader.
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// use crossterm::{input, InputEvent, KeyEvent, RawScreen};
///
/// fn main() {
/// println!("Press 'ESC' to quit.");
///
/// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
/// let _raw = RawScreen::into_raw_mode();
///
/// // Create an input from our screen
/// let input = input();
///
/// // Create a sync reader
/// let mut reader = input.read_sync();
///
/// loop {
/// if let Some(event) = reader.next() { // Blocking call
/// match event {
/// InputEvent::Keyboard(KeyEvent::Esc) => {
/// println!("Program closing ...");
/// break;
/// }
/// InputEvent::Mouse(event) => { /* Mouse event */ }
/// _ => { /* Other events */ }
/// }
/// }
/// thread::sleep(Duration::from_millis(50));
/// }
/// } // `_raw` dropped <- raw mode disabled
/// ```
pub struct SyncReader {
rx: Option<Receiver<InternalEvent>>,
}
impl SyncReader {
fn new() -> SyncReader {
// TODO 1.0: Following expect is here to keep the API compatible (no Result)
SyncReader {
rx: Some(internal_event_receiver().expect("Unable to get event receiver")),
}
}
}
impl Iterator for SyncReader {
type Item = InputEvent;
/// Tries to read the next input event (blocking).
///
/// `None` doesn't mean that the iteration is finished. See the
/// [`SyncReader`](struct.SyncReader.html) documentation for more information.
fn next(&mut self) -> Option<Self::Item> {
// TODO 1.0: This whole `InternalEvent` -> `InputEvent` mapping should be shared
// between UNIX & Windows implementations
let ref mut rx = match self.rx.as_ref() {
Some(rx) => rx,
None => return None,
};
match rx.recv() {
Ok(internal_event) => internal_event.into(),
Err(mpsc::RecvError) => {
// Sender is dropped, drop the receiver
self.rx = None;
None
}
}
}
}

616
src/input/input/windows.rs Normal file
View File

@ -0,0 +1,616 @@
//! This is a WINDOWS specific implementation for input related action.
use std::{
char, io,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc, Mutex,
},
thread,
time::Duration,
};
use crossterm_winapi::{
ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
MouseEvent, ScreenBuffer,
};
use winapi::um::{
wincon::{
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
},
winnt::INT,
winuser::{
VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12,
VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT,
VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP,
},
};
use lazy_static::lazy_static;
use crate::utils::Result;
use super::{
super::{InputEvent, KeyEvent, MouseButton},
Input,
};
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
lazy_static! {
static ref ORIGINAL_CONSOLE_MODE: Mutex<Option<u32>> = Mutex::new(None);
}
/// Initializes the default console color. It will will be skipped if it has already been initialized.
fn init_original_console_mode(original_mode: u32) {
let mut lock = ORIGINAL_CONSOLE_MODE.lock().unwrap();
if lock.is_none() {
*lock = Some(original_mode);
}
}
/// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic.
fn original_console_mode() -> u32 {
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
ORIGINAL_CONSOLE_MODE
.lock()
.unwrap()
.expect("Original console mode not set")
}
pub(crate) struct WindowsInput;
impl WindowsInput {
pub fn new() -> WindowsInput {
WindowsInput
}
}
impl Input for WindowsInput {
fn read_char(&self) -> Result<char> {
// _getwch is without echo and _getwche is with echo
let pressed_char = unsafe { _getwche() };
// we could return error but maybe option to keep listening until valid character is inputted.
if pressed_char == 0 || pressed_char == 0xe0 {
Err(io::Error::new(
io::ErrorKind::Other,
"Given input char is not a valid char, mostly occurs when pressing special keys",
))?;
}
let ch = char::from_u32(pressed_char as u32).ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Could not parse given input to char")
})?;
Ok(ch)
}
fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in read_input_events().unwrap().1 {
if event_tx.send(i).is_err() {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
}
thread::sleep(Duration::from_millis(1));
}))
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for event in read_input_events().unwrap().1 {
if let InputEvent::Keyboard(KeyEvent::Char(key)) = event {
if (key as u8) == delimiter {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
} else {
if event_tx.send(event).is_err() {
return;
}
}
thread::sleep(Duration::from_millis(1));
}
}))
}
fn read_sync(&self) -> SyncReader {
SyncReader
}
fn enable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
init_original_console_mode(mode.mode()?);
mode.set_mode(ENABLE_MOUSE_MODE)?;
Ok(())
}
fn disable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
mode.set_mode(original_console_mode())?;
Ok(())
}
}
/// A synchronous input reader (blocking).
///
/// `SyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
/// trait. Documentation says:
///
/// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
/// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
/// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
/// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
///
/// `SyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
/// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
/// received `None`. Unfortunately, `None` means that an error occurred, but you're free to call `next`
/// again. This behavior will be changed in the future to avoid errors consumption.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * See the [`AsyncReader`](struct.AsyncReader.html) if you want a non blocking reader.
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// use crossterm::{input, InputEvent, KeyEvent, RawScreen};
///
/// fn main() {
/// println!("Press 'ESC' to quit.");
///
/// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
/// let _raw = RawScreen::into_raw_mode();
///
/// // Create an input from our screen
/// let input = input();
///
/// // Create a sync reader
/// let mut reader = input.read_sync();
///
/// loop {
/// if let Some(event) = reader.next() { // Blocking call
/// match event {
/// InputEvent::Keyboard(KeyEvent::Esc) => {
/// println!("Program closing ...");
/// break;
/// }
/// InputEvent::Mouse(event) => { /* Mouse event */ }
/// _ => { /* Other events */ }
/// }
/// }
/// thread::sleep(Duration::from_millis(50));
/// }
/// } // `_raw` dropped <- raw mode disabled
/// ```
pub struct SyncReader;
impl Iterator for SyncReader {
type Item = InputEvent;
/// Tries to read the next input event (blocking).
///
/// `None` doesn't mean that the iteration is finished. See the
/// [`SyncReader`](struct.SyncReader.html) documentation for more information.
fn next(&mut self) -> Option<Self::Item> {
// This synces the behaviour with the unix::SyncReader (& documentation) where
// None is returned in case of error.
read_single_event().unwrap_or(None)
}
}
/// An asynchronous input reader (not blocking).
///
/// `AsyncReader` implements the [`Iterator`](https://doc.rust-lang.org/std/iter/index.html#iterator)
/// trait. Documentation says:
///
/// > An iterator has a method, `next`, which when called, returns an `Option<Item>`. `next` will return
/// > `Some(Item)` as long as there are elements, and once they've all been exhausted, will return `None`
/// > to indicate that iteration is finished. Individual iterators may choose to resume iteration, and
/// > so calling `next` again may or may not eventually start returning `Some(Item)` again at some point.
///
/// `AsyncReader` is an individual iterator and it doesn't use `None` to indicate that the iteration is
/// finished. You can expect additional `Some(InputEvent)` after calling `next` even if you have already
/// received `None`.
///
/// # Notes
///
/// * It requires enabled raw mode (see the
/// [`crossterm_screen`](https://docs.rs/crossterm_screen/) crate documentation to learn more).
/// * A thread is spawned to read the input.
/// * The reading thread is cleaned up when you drop the `AsyncReader`.
/// * See the [`SyncReader`](struct.SyncReader.html) if you want a blocking,
/// or a less resource hungry reader.
///
/// # Examples
///
/// ```no_run
/// use std::{thread, time::Duration};
///
/// use crossterm::{input, InputEvent, KeyEvent, RawScreen};
///
/// fn main() {
/// println!("Press 'ESC' to quit.");
///
/// // Enable raw mode and keep the `_raw` around otherwise the raw mode will be disabled
/// let _raw = RawScreen::into_raw_mode();
///
/// // Create an input from our screen
/// let input = input();
///
/// // Create an async reader
/// let mut reader = input.read_async();
///
/// loop {
/// if let Some(event) = reader.next() { // Not a blocking call
/// match event {
/// InputEvent::Keyboard(KeyEvent::Esc) => {
/// println!("Program closing ...");
/// break;
/// }
/// InputEvent::Mouse(event) => { /* Mouse event */ }
/// _ => { /* Other events */ }
/// }
/// }
/// thread::sleep(Duration::from_millis(50));
/// }
/// } // `reader` dropped <- thread cleaned up, `_raw` dropped <- raw mode disabled
/// ```
pub struct AsyncReader {
event_rx: Receiver<InputEvent>,
shutdown: Arc<AtomicBool>,
}
impl AsyncReader {
// TODO Should the new() really be public?
/// Creates a new `AsyncReader`.
///
/// # Notes
///
/// * A thread is spawned to read the input.
/// * The reading thread is cleaned up when you drop the `AsyncReader`.
pub fn new(function: Box<dyn Fn(&Sender<InputEvent>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
let shutdown_handle = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx) = mpsc::channel();
let thread_shutdown = shutdown_handle.clone();
thread::spawn(move || loop {
function(&event_tx, &thread_shutdown);
});
AsyncReader {
event_rx,
shutdown: shutdown_handle,
}
}
// TODO If we we keep the Drop semantics, do we really need this in the public API? It's useless as
// there's no `start`, etc.
/// Stops the input reader.
///
/// # Notes
///
/// * The reading thread is cleaned up.
/// * You don't need to call this method, because it will be automatically called when the
/// `AsyncReader` is dropped.
pub fn stop(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
}
}
impl Drop for AsyncReader {
fn drop(&mut self) {
self.stop();
}
}
impl Iterator for AsyncReader {
type Item = InputEvent;
/// Tries to read the next input event (not blocking).
///
/// `None` doesn't mean that the iteration is finished. See the
/// [`AsyncReader`](struct.AsyncReader.html) documentation for more information.
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.event_rx.try_iter();
iterator.next()
}
}
extern "C" {
fn _getwche() -> INT;
}
fn read_single_event() -> Result<Option<InputEvent>> {
let console = Console::from(Handle::current_in_handle()?);
let input = console.read_single_input_event()?;
match input.event_type {
InputEventType::KeyEvent => {
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
}
InputEventType::MouseEvent => {
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
}
// NOTE (@imdaveho): ignore below
InputEventType::WindowBufferSizeEvent => return Ok(None), // TODO implement terminal resize event
InputEventType::FocusEvent => Ok(None),
InputEventType::MenuEvent => Ok(None),
}
}
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
fn read_input_events() -> Result<(u32, Vec<InputEvent>)> {
let console = Console::from(Handle::current_in_handle()?);
let result = console.read_console_input()?;
let mut input_events = Vec::with_capacity(result.0 as usize);
for input in result.1 {
match input.event_type {
InputEventType::KeyEvent => {
if let Ok(Some(event)) =
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
{
input_events.push(event)
}
}
InputEventType::MouseEvent => {
if let Ok(Some(event)) =
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
{
input_events.push(event)
}
}
// NOTE (@imdaveho): ignore below
InputEventType::WindowBufferSizeEvent => (), // TODO implement terminal resize event
InputEventType::FocusEvent => (),
InputEventType::MenuEvent => (),
}
}
return Ok((result.0, input_events));
}
fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>> {
if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) {
return Ok(Some(InputEvent::Mouse(event)));
}
Ok(None)
}
fn handle_key_event(key_event: KeyEventRecord) -> Result<Option<InputEvent>> {
if key_event.key_down {
if let Some(event) = parse_key_event_record(&key_event) {
return Ok(Some(InputEvent::Keyboard(event)));
}
}
return Ok(None);
}
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
let key_code = key_event.virtual_key_code as i32;
match key_code {
VK_SHIFT | VK_CONTROL | VK_MENU => None,
VK_BACK => Some(KeyEvent::Backspace),
VK_ESCAPE => Some(KeyEvent::Esc),
VK_RETURN => Some(KeyEvent::Enter),
VK_F1 | VK_F2 | VK_F3 | VK_F4 | VK_F5 | VK_F6 | VK_F7 | VK_F8 | VK_F9 | VK_F10 | VK_F11
| VK_F12 => Some(KeyEvent::F((key_event.virtual_key_code - 111) as u8)),
VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
// Modifier Keys (Ctrl, Shift) Support
let key_state = &key_event.control_key_state;
let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
let shift_pressed = key_state.has_state(SHIFT_PRESSED);
let event = match key_code {
VK_LEFT => {
if ctrl_pressed {
Some(KeyEvent::CtrlLeft)
} else if shift_pressed {
Some(KeyEvent::ShiftLeft)
} else {
Some(KeyEvent::Left)
}
}
VK_UP => {
if ctrl_pressed {
Some(KeyEvent::CtrlUp)
} else if shift_pressed {
Some(KeyEvent::ShiftUp)
} else {
Some(KeyEvent::Up)
}
}
VK_RIGHT => {
if ctrl_pressed {
Some(KeyEvent::CtrlRight)
} else if shift_pressed {
Some(KeyEvent::ShiftRight)
} else {
Some(KeyEvent::Right)
}
}
VK_DOWN => {
if ctrl_pressed {
Some(KeyEvent::CtrlDown)
} else if shift_pressed {
Some(KeyEvent::ShiftDown)
} else {
Some(KeyEvent::Down)
}
}
_ => None,
};
event
}
VK_PRIOR | VK_NEXT => {
if key_code == VK_PRIOR {
Some(KeyEvent::PageUp)
} else if key_code == VK_NEXT {
Some(KeyEvent::PageDown)
} else {
None
}
}
VK_END | VK_HOME => {
if key_code == VK_HOME {
Some(KeyEvent::Home)
} else if key_code == VK_END {
Some(KeyEvent::End)
} else {
None
}
}
VK_DELETE => Some(KeyEvent::Delete),
VK_INSERT => Some(KeyEvent::Insert),
_ => {
// Modifier Keys (Ctrl, Alt, Shift) Support
let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
if character_raw < 255 {
let character = character_raw as u8 as char;
let key_state = &key_event.control_key_state;
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
// The pressed command is stored in `virtual_key_code`.
let command = key_event.virtual_key_code as u8 as char;
if (command).is_alphabetic() {
Some(KeyEvent::Alt(command))
} else {
None
}
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
match character_raw as u8 {
c @ b'\x01'..=b'\x1A' => {
Some(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
}
c @ b'\x1C'..=b'\x1F' => {
Some(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
}
_ => None,
}
} else if key_state.has_state(SHIFT_PRESSED) && character == '\t' {
Some(KeyEvent::BackTab)
} else {
if character == '\t' {
Some(KeyEvent::Tab)
} else {
// Shift + key press, essentially the same as single key press
// Separating to be explicit about the Shift press.
Some(KeyEvent::Char(character))
}
}
} else {
None
}
}
}
}
fn parse_mouse_event_record(event: &MouseEvent) -> Result<Option<crate::MouseEvent>> {
// NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them
// individually as bytes into a buffer; the below cxbs and cybs replicates that and
// mimicks the behavior; additionally, in xterm, mouse move is only handled when a
// mouse button is held down (ie. mouse drag)
let window_size = ScreenBuffer::current()?.info()?.terminal_window();
let xpos = event.mouse_position.x;
let mut ypos = event.mouse_position.y;
// The 'y' position of a mouse 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 counting from the absolute buffer height) instead of relative x: 0, y: 0 to the window.
ypos = ypos - window_size.top;
Ok(match event.event_flags {
EventFlags::PressOrRelease => {
// Single click
match event.button_state {
ButtonState::Release => Some(crate::MouseEvent::Release(xpos as u16, ypos as u16)),
ButtonState::FromLeft1stButtonPressed => {
// left click
Some(crate::MouseEvent::Press(
MouseButton::Left,
xpos as u16,
ypos as u16,
))
}
ButtonState::RightmostButtonPressed => {
// right click
Some(crate::MouseEvent::Press(
MouseButton::Right,
xpos as u16,
ypos as u16,
))
}
ButtonState::FromLeft2ndButtonPressed => {
// middle click
Some(crate::MouseEvent::Press(
MouseButton::Middle,
xpos as u16,
ypos as u16,
))
}
_ => None,
}
}
EventFlags::MouseMoved => {
// Click + Move
// NOTE (@imdaveho) only register when mouse is not released
if event.button_state != ButtonState::Release {
Some(crate::MouseEvent::Hold(xpos as u16, ypos as u16))
} else {
None
}
}
EventFlags::MouseWheeled => {
// Vertical scroll
// NOTE (@imdaveho) 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 event.button_state != ButtonState::Negative {
Some(crate::MouseEvent::Press(
MouseButton::WheelUp,
xpos as u16,
ypos as u16,
))
} else {
Some(crate::MouseEvent::Press(
MouseButton::WheelDown,
xpos as u16,
ypos as u16,
))
}
}
EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals
EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
// TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
})
}

2
src/input/sys.rs Normal file
View File

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

1056
src/input/sys/unix.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,5 @@
#![deny(unused_imports, unused_must_use)]
//! # Crossterm
//!
//! Have you ever been disappointed when a terminal library for rust was only written for UNIX systems?
@ -11,37 +13,193 @@
//! see [Tested Terminals](https://github.com/crossterm-rs/crossterm/tree/zrzka/docs-update#tested-terminals)
//! for more info).
//!
//! ## Important
//! ## Command API
//!
//! This crate re-exports all other `crossterm_*` crates types only. Please, consult the
//! `crossterm` crate repository [README](https://github.com/crossterm-rs/crossterm/blob/master/README.md) to
//! learn how to use features to enable/disable functionality, what's planned, etc. There will be
//! new code organization, breaking API changes, etc.
//! The command API makes the use of `crossterm` much easier and offers more control over when and how a
//! command such as moving the cursor is executed. 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 but offer an API with less boilerplate code. If you are
//! not afraid of macros, this is a recommendation.
//!
//! Before `crossterm` 10.0 was released, `crossterm` had some performance issues. It did a `flush` after each command
//! (cursor movement). A `flush` is heavy action on the terminal, and if it is done more often the performance
//! will go down quickly.
//!
//! 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.
//!
//! ## Lazy Execution
//!
//! Because `flush` is a heavy system call we can instead `write` the commands to the `stdout` without flushing.
//! When can do a `flush` when we do want to execute the commands.
//!
//! If you create a terminal editor or TUI, it is wise to use this option. For example, you can `write` commands
//! to the terminal `stdout` and `flush` the `stdout` at every frame. By doing this you can make efficient use of the
//! terminal buffer and get better performance because you are not calling `flush` after every command.
//!
//! ### Examples
//!
//! Functions:
//!
//! ```no_run
//! use std::io::Write;
//! use crossterm::{Goto, QueueableCommand};
//!
//! let mut stdout = std::io::stdout();
//! stdout.queue(Goto(5,5));
//!
//! // some other code ...
//!
//! stdout.flush();
//! ```
//!
//! The `queue` 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;
//! use crossterm::{queue, Goto, QueueableCommand};
//!
//! let mut stdout = std::io::stdout();
//! queue!(stdout, Goto(5, 5));
//!
//! // some other code ...
//!
//! stdout.flush();
//! ```
//!
//! You can pass more than one command into the macro like `queue!(stdout, Goto(5, 5), Clear(ClearType::All))` and
//! they will be executed in the given order from left to right.
//!
//! ## Direct Execution
//!
//! If you want to execute commands directly, this is also possible. You don't have to flush the 'stdout',
//! as described above. This is fine if you are not executing lots of commands.
//!
//! ### Examples
//!
//! Functions:
//!
//! ```no_run
//! use std::io::Write;
//! use crossterm::{ExecutableCommand, Goto};
//!
//! let mut stdout = std::io::stdout();
//! stdout.execute(Goto(5,5));
//! ```
//!
//! Macros:
//!
//! ```no_run
//! use std::io::Write;
//! use crossterm::{execute, ExecutableCommand, Goto};
//!
//! let mut stdout = std::io::stdout();
//! execute!(stdout, Goto(5, 5));
//! ```
//!
//! ## 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, Color, PrintStyledFont, Colorize, Clear, ClearType, Goto, Result};
//!
//! fn main() -> Result<()> {
//! let mut stdout = stdout();
//!
//! stdout.execute(Clear(ClearType::All))?;
//!
//! for y in 0..40 {
//! for x in 0..150 {
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
//! stdout
//! .queue(Goto(x,y))?
//! .queue(PrintStyledFont( "█".magenta()))?;
//! }
//! }
//! }
//! stdout.flush()?;
//! Ok(())
//! }
//! ```
//!
//! Macros:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//! use crossterm::{execute, queue, Color, PrintStyledFont, Colorize, Goto, Clear, ClearType, Result};
//!
//! fn main() -> Result<()> {
//! let mut stdout = stdout();
//!
//! execute!(stdout, Clear(ClearType::All))?;
//!
//! for y in 0..40 {
//! for x in 0..150 {
//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) {
//! queue!(stdout, Goto(x,y), PrintStyledFont( "█".magenta()))?;
//! }
//! }
//! }
//! stdout.flush()?;
//! Ok(())
//! }
//!```
#[cfg(feature = "cursor")]
pub use crossterm_cursor::{
pub use cursor::{
cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show,
TerminalCursor, Up,
};
#[cfg(feature = "input")]
pub use crossterm_input::{
pub use input::{
input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput,
};
#[cfg(feature = "screen")]
pub use crossterm_screen::{
pub use screen::{
AlternateScreen, EnterAlternateScreen, IntoRawMode, LeaveAlternateScreen, RawScreen,
};
#[cfg(feature = "style")]
pub use crossterm_style::{
pub use style::{
color, style, Attribute, Color, Colored, Colorize, ObjectStyle, PrintStyledFont, ResetColor,
SetAttr, SetBg, SetFg, StyledObject, Styler, TerminalColor,
};
#[cfg(feature = "terminal")]
pub use crossterm_terminal::{terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal};
pub use crossterm_utils::{
execute, queue, Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result,
};
pub use terminal::{terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal};
pub use utils::{Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result};
pub use self::crossterm::Crossterm;
mod crossterm;
/// A functionality to work with the terminal cursor
#[cfg(feature = "cursor")]
pub mod cursor;
/// A functionality to read the input events.
#[cfg(feature = "input")]
pub mod input;
/// A functionality to work with the terminal screen.
#[cfg(feature = "screen")]
pub mod screen;
/// A functionality to apply attributes and colors on your text.
#[cfg(feature = "style")]
pub mod style;
/// A functionality to work with the terminal.
#[cfg(feature = "terminal")]
pub mod terminal;
/// Shared utilities.
pub mod utils;

208
src/screen.rs Normal file
View File

@ -0,0 +1,208 @@
//! # Screen
//!
//! The `screen` module provides the functionality to work with the terminal screen.
//!
//! 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/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Screen Buffer
//!
//! A screen buffer is a two-dimensional array of characters and color data to be output in a console window.
//! A terminal can have multiple of those screen buffers, and the active screen buffer is the one that is
//! displayed on the screen.
//!
//! Crossterm allows you to switch between those buffers; the screen you are working in is called the
//! 'main screen'. We call the other screen the 'alternate screen'. One note to take is that crossterm
//! does not support the creation and switching between several buffers.
//!
//! ### Alternate Screen
//!
//! Normally you are working on the main screen but an alternate screen is somewhat different from a
//! normal screen. For example, it has the exact dimensions of the terminal window, without any
//! scroll back region. An example of this is vim when it is launched from bash.
//!
//! Vim uses the entirety of the screen to edit the file, then exits to bash leaving the original buffer unchanged.
//!
//! Crossterm provides the ability to switch to the alternate screen, make some changes, and then go back
//! to the main screen. The main screen will still have its original data since we made all the edits on
//! the alternate screen.
//!
//! ### Raw Mode
//!
//! By default, the terminal behaves in a certain way.
//! You can think of going to a new line if the input is at the end of the current line, or interpreting backspace
//! to remove letters. Sometimes it can be useful to disable these modes because this is undesirable.
//! This may be undesirable if your application wants to read the input without it being shown on the screen.
//! Raw modes are the modes to create this possibility.
//
//! 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 CTL+C will not be processed by terminal driver
//! - New line character will not be processed therefore `println!` can't be used, use `write!` instead
// This brings the trait into scope, so we're able to call enter()/leave(),
// but it it's false positive for unused_imports check
#[allow(unused_imports)]
use alternate::AlternateScreen as _;
use crate::utils::{Command, Result};
pub use self::raw::{IntoRawMode, RawScreen};
mod alternate;
mod raw;
mod sys;
/// An alternate screen.
///
/// With this type, you will be able to switch to the alternate screen and then back to
/// the main screen.
///
/// Be aware that you'll be switched back to the main screen when you drop the
/// `AlternateScreen` value.
///
/// It's recommended to use the command API. See the
/// [`EnterAlternateScreen`](struct.EnterAlternateScreen.html)
/// and [`LeaveAlternateScreen`](struct.LeaveAlternateScreen.html)
/// commands documentation for more info.
///
/// # Examples
///
/// Alternate screen with raw mode enabled:
///
/// ```no_run
/// use crossterm::{AlternateScreen, Result};
///
/// fn main() -> Result<()> {
/// let _alternate = AlternateScreen::to_alternate(true)?;
///
/// // Do something on the alternate screen in the raw mode
///
/// Ok(())
/// } // `_alternate` dropped here <- raw mode disabled & back to main screen
/// ```
pub struct AlternateScreen {
#[cfg(windows)]
alternate: Box<(dyn alternate::AlternateScreen + Sync + Send)>,
#[cfg(unix)]
alternate: alternate::AnsiAlternateScreen,
raw_screen: Option<RawScreen>,
}
impl AlternateScreen {
/// Switches to the alternate screen.
///
/// # Arguments
///
/// * `raw_mode` - `true` enables the raw mode as well
///
/// # Notes
///
/// You'll be automatically switched to the main screen if this function
/// fails.
pub fn to_alternate(raw_mode: bool) -> Result<AlternateScreen> {
let alternate = alternate::alternate_screen();
alternate.enter()?;
let mut alternate = AlternateScreen {
alternate,
raw_screen: None,
};
if raw_mode {
// If into_raw_mode fails, `alternate` will be dropped and
// we'll switch back to the main screen.
alternate.raw_screen = Some(RawScreen::into_raw_mode()?);
}
Ok(alternate)
}
/// Switches to the main screen.
pub fn to_main(&self) -> Result<()> {
self.alternate.leave()
}
}
impl Drop for AlternateScreen {
fn drop(&mut self) {
let _ = self.to_main();
}
}
/// A command to switch to the alternate screen.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, EnterAlternateScreen, LeaveAlternateScreen};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
pub struct EnterAlternateScreen;
impl Command for EnterAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
alternate::ansi::ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate = alternate::alternate_screen();
alternate.enter()
}
}
/// A command to switch back to the main screen.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
///
/// # Examples
///
/// ```no_run
/// use std::io::{stdout, Write};
/// use crossterm::{execute, Result, EnterAlternateScreen, LeaveAlternateScreen};
///
/// fn main() -> Result<()> {
/// execute!(stdout(), EnterAlternateScreen)?;
///
/// // Do anything on the alternate screen
///
/// execute!(stdout(), LeaveAlternateScreen)
/// }
/// ```
pub struct LeaveAlternateScreen;
impl Command for LeaveAlternateScreen {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
alternate::ansi::LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
let alternate = alternate::alternate_screen();
alternate.leave()
}
}

30
src/screen/alternate.rs Normal file
View File

@ -0,0 +1,30 @@
#[cfg(windows)]
use crate::utils::supports_ansi;
use crate::utils::Result;
pub(crate) use ansi::AnsiAlternateScreen;
#[cfg(windows)]
pub(crate) use windows::WinApiAlternateScreen;
pub(crate) mod ansi;
#[cfg(windows)]
pub(crate) mod windows;
pub(crate) trait AlternateScreen: Sync + Send {
fn enter(&self) -> Result<()>;
fn leave(&self) -> Result<()>;
}
#[cfg(windows)]
pub(crate) fn alternate_screen() -> Box<dyn AlternateScreen + Send + Sync> {
if supports_ansi() {
Box::new(AnsiAlternateScreen)
} else {
Box::new(WinApiAlternateScreen)
}
}
#[cfg(unix)]
pub(crate) fn alternate_screen() -> AnsiAlternateScreen {
AnsiAlternateScreen
}

View File

@ -0,0 +1,21 @@
use crate::utils::Result;
use crate::{csi, write_cout};
use super::AlternateScreen;
pub(crate) static ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE: &'static str = csi!("?1049h");
pub(crate) static LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE: &'static str = csi!("?1049l");
pub(crate) struct AnsiAlternateScreen;
impl AlternateScreen for AnsiAlternateScreen {
fn enter(&self) -> Result<()> {
write_cout!(ENTER_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
Ok(())
}
fn leave(&self) -> Result<()> {
write_cout!(LEAVE_ALTERNATE_SCREEN_CSI_SEQUENCE)?;
Ok(())
}
}

View File

@ -0,0 +1,21 @@
use crossterm_winapi::{Handle, ScreenBuffer};
use crate::utils::Result;
use super::AlternateScreen;
pub(crate) struct WinApiAlternateScreen;
impl AlternateScreen for WinApiAlternateScreen {
fn enter(&self) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
fn leave(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::output_handle()?);
screen_buffer.show()?;
Ok(())
}
}

128
src/screen/raw.rs Normal file
View File

@ -0,0 +1,128 @@
use std::io::{Stdout, Write};
use crate::utils::Result;
use super::sys;
/// A raw screen.
///
/// Be aware that the raw mode is disabled when you drop the `RawScreen` value.
/// Call the [`keep_raw_mode_on_drop`](struct.RawScreen.html#method.keep_raw_mode_on_drop)
/// method to disable this behavior (keep the raw mode enabled).
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{RawScreen, Result};
///
/// fn main() -> Result<()> {
/// let _raw = RawScreen::into_raw_mode()?;
/// // Do something in the raw mode
/// Ok(())
/// } // `_raw` is dropped here <- raw mode is disabled
/// ```
///
/// Do not disable the raw mode implicitly:
///
/// ```no_run
/// use crossterm::{RawScreen, Result};
///
/// fn main() -> Result<()> {
/// let mut raw = RawScreen::into_raw_mode()?;
/// raw.keep_raw_mode_on_drop();
/// // Feel free to leave `raw` on it's own/drop it, the raw
/// // mode won't be disabled
///
/// // Do something in the raw mode
///
/// // Disable raw mode explicitly
/// RawScreen::disable_raw_mode()
/// }
/// ```
pub struct RawScreen {
disable_raw_mode_on_drop: bool,
}
impl RawScreen {
// TODO enable_raw_mode() to keep it synced with enable/disable?
/// Enables raw mode.
pub fn into_raw_mode() -> Result<RawScreen> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let mut command = sys::winapi::RawModeCommand::new();
command.enable()?;
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
/// Disables raw mode.
pub fn disable_raw_mode() -> Result<()> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let command = sys::winapi::RawModeCommand::new();
command.disable()?;
Ok(())
}
/// Keeps the raw mode enabled when `self` is dropped.
///
/// See the [`RawScreen`](struct.RawScreen.html) documentation for more
/// information.
pub fn keep_raw_mode_on_drop(&mut self) {
self.disable_raw_mode_on_drop = false;
}
}
/// Allows to enable raw mode.
///
/// Why this type must be implemented on writers?
///
/// TTYs has their state controlled by the writer, not the reader. You use the writer to
/// clear the screen, move the cursor and so on, so naturally you use the writer to change
/// the mode as well.
///
/// # Examples
///
/// ```no_run
/// use std::io::stdout;
/// use crossterm::{IntoRawMode, Result};
///
/// fn main() -> Result<()> {
/// let stdout = stdout();
/// let _raw = stdout.into_raw_mode()?;
///
/// // Do something in the raw mode
///
/// Ok(())
/// } // `_raw` dropped here <- raw mode disabled
/// ```
pub trait IntoRawMode: Write + Sized {
/// Enables raw mode.
fn into_raw_mode(self) -> Result<RawScreen>;
}
impl IntoRawMode for Stdout {
fn into_raw_mode(self) -> Result<RawScreen> {
RawScreen::into_raw_mode()?;
// this make's sure that raw screen will be disabled when it goes out of scope.
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
}
impl Drop for RawScreen {
fn drop(&mut self) {
if self.disable_raw_mode_on_drop {
let _ = RawScreen::disable_raw_mode();
}
}
}

5
src/screen/sys.rs Normal file
View File

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

22
src/screen/sys/unix.rs Normal file
View File

@ -0,0 +1,22 @@
use crate::utils::Result;
/// This command is used for enabling and disabling raw mode for the terminal.
pub struct RawModeCommand;
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand
}
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
crate::utils::sys::unix::enable_raw_mode()?;
Ok(())
}
/// Disables raw mode.
pub fn disable(&mut self) -> Result<()> {
crate::utils::sys::unix::disable_raw_mode()?;
Ok(())
}
}

50
src/screen/sys/winapi.rs Normal file
View File

@ -0,0 +1,50 @@
use crossterm_winapi::{ConsoleMode, Handle};
use winapi::shared::minwindef::DWORD;
use winapi::um::wincon;
use crate::utils::Result;
use self::wincon::{ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT};
/// This command is used for enabling and disabling raw mode for Windows systems.
/// For more info check: https://docs.microsoft.com/en-us/windows/console/high-level-console-modes.
#[derive(Clone, Copy)]
pub struct RawModeCommand {
mask: DWORD,
}
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand {
mask: ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT,
}
}
}
impl RawModeCommand {
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode & !self.mask;
console_mode.set_mode(new_mode)?;
Ok(())
}
/// Disables raw mode.
pub fn disable(&self) -> Result<()> {
let console_mode = ConsoleMode::from(Handle::input_handle()?);
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode | self.mask;
console_mode.set_mode(new_mode)?;
return Ok(());
}
}

418
src/style.rs Normal file
View File

@ -0,0 +1,418 @@
//! # 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/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
//!
//! ### Colors
//!
//! The command API:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{execute, Result, Output};
//! use crossterm::{SetBg, SetFg, ResetColor, Color, Attribute};
//!
//! fn main() -> Result<()> {
//! execute!(
//! stdout(),
//! // Blue foreground
//! SetFg(Color::Blue),
//! // Red background
//! SetBg(Color::Red),
//! Output("Styled text here.".to_string()),
//! // Reset to default colors
//! ResetColor
//! )
//! }
//! ```
//!
//! The [`Colored`](enum.Colored.html) & [`Color`](enum.Color.html) enums:
//!
//! ```no_run
//! use crossterm::{Colored, Color};
//!
//! println!("{} Red foreground", Colored::Fg(Color::Red));
//! println!("{} Blue background", Colored::Bg(Color::Blue));
//! ```
//!
//! The [`Colorize`](trait.Colorize.html) trait:
//!
//! ```no_run
//! use crossterm::Colorize;
//!
//! println!("{}", "Red foreground color & blue background.".red().on_blue());
//! ```
//!
//! ### Attributes
//!
//! The command API:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//!
//! use crossterm::{execute, Result, Output};
//! use crossterm::{SetAttr, Attribute};
//!
//! fn main() -> Result<()> {
//! execute!(
//! stdout(),
//! // Set to bold
//! SetAttr(Attribute::Bold),
//! Output("Styled text here.".to_string()),
//! // Reset all attributes
//! SetAttr(Attribute::Reset)
//! )
//! }
//! ```
//!
//! The [`Styler`](trait.Styler.html) trait:
//!
//! ```no_run
//! use crossterm::Styler;
//!
//! println!("{}", "Bold".bold());
//! println!("{}", "Underlined".underlined());
//! println!("{}", "Negative".negative());
//! ```
//!
//! The [`Attribute`](enum.Attribute.html) enum:
//!
//! ```no_run
//! use crossterm::Attribute;
//!
//! println!(
//! "{} Underlined {} No Underline",
//! Attribute::Underlined,
//! Attribute::NoUnderline
//! );
//! ```
use std::env;
use std::fmt::Display;
use style::ansi::{self, AnsiColor};
#[cfg(windows)]
use style::winapi::WinApiColor;
use style::Style;
use crate::impl_display;
#[cfg(windows)]
use crate::utils::supports_ansi;
use crate::utils::{Command, Result};
pub use self::enums::{Attribute, Color, Colored};
pub use self::objectstyle::ObjectStyle;
pub use self::styledobject::StyledObject;
pub use self::traits::{Colorize, Styler};
#[macro_use]
mod macros;
mod enums;
mod objectstyle;
mod style;
mod styledobject;
mod traits;
/// Creates a `StyledObject`.
///
/// This could be used to style any type that implements `Display` with colors and text attributes.
///
/// See [`StyledObject`](struct.StyledObject.html) for more info.
///
/// # Examples
///
/// ```no_run
/// use crossterm::{style, Color};
///
/// let styled_object = style("Blue colored text on yellow background")
/// .with(Color::Blue)
/// .on(Color::Yellow);
///
/// println!("{}", styled_object);
/// ```
pub fn style<'a, D: 'a>(val: D) -> StyledObject<D>
where
D: Display + Clone,
{
ObjectStyle::new().apply_to(val)
}
impl Colorize<&'static str> for &'static str {
// foreground colors
def_str_color!(fg_color: black => Color::Black);
def_str_color!(fg_color: dark_grey => Color::DarkGrey);
def_str_color!(fg_color: red => Color::Red);
def_str_color!(fg_color: dark_red => Color::DarkRed);
def_str_color!(fg_color: green => Color::Green);
def_str_color!(fg_color: dark_green => Color::DarkGreen);
def_str_color!(fg_color: yellow => Color::Yellow);
def_str_color!(fg_color: dark_yellow => Color::DarkYellow);
def_str_color!(fg_color: blue => Color::Blue);
def_str_color!(fg_color: dark_blue => Color::DarkBlue);
def_str_color!(fg_color: magenta => Color::Magenta);
def_str_color!(fg_color: dark_magenta => Color::DarkMagenta);
def_str_color!(fg_color: cyan => Color::Cyan);
def_str_color!(fg_color: dark_cyan => Color::DarkCyan);
def_str_color!(fg_color: white => Color::White);
def_str_color!(fg_color: grey => Color::Grey);
// background colors
def_str_color!(bg_color: on_black => Color::Black);
def_str_color!(bg_color: on_dark_grey => Color::DarkGrey);
def_str_color!(bg_color: on_red => Color::Red);
def_str_color!(bg_color: on_dark_red => Color::DarkRed);
def_str_color!(bg_color: on_green => Color::Green);
def_str_color!(bg_color: on_dark_green => Color::DarkGreen);
def_str_color!(bg_color: on_yellow => Color::Yellow);
def_str_color!(bg_color: on_dark_yellow => Color::DarkYellow);
def_str_color!(bg_color: on_blue => Color::Blue);
def_str_color!(bg_color: on_dark_blue => Color::DarkBlue);
def_str_color!(bg_color: on_magenta => Color::Magenta);
def_str_color!(bg_color: on_dark_magenta => Color::DarkMagenta);
def_str_color!(bg_color: on_cyan => Color::Cyan);
def_str_color!(bg_color: on_dark_cyan => Color::DarkCyan);
def_str_color!(bg_color: on_white => Color::White);
def_str_color!(bg_color: on_grey => Color::Grey);
}
impl Styler<&'static str> for &'static str {
def_str_attr!(reset => Attribute::Reset);
def_str_attr!(bold => Attribute::Bold);
def_str_attr!(underlined => Attribute::Underlined);
def_str_attr!(reverse => Attribute::Reverse);
def_str_attr!(dim => Attribute::Dim);
def_str_attr!(italic => Attribute::Italic);
def_str_attr!(negative => Attribute::Reverse);
def_str_attr!(slow_blink => Attribute::SlowBlink);
def_str_attr!(rapid_blink => Attribute::RapidBlink);
def_str_attr!(hidden => Attribute::Hidden);
def_str_attr!(crossed_out => Attribute::CrossedOut);
}
/// A terminal color.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// // You can replace the following line with `use crossterm::TerminalColor;`
/// // if you're using the `crossterm` crate with the `style` feature enabled.
/// use crossterm::{Result, TerminalColor, Color};
///
/// fn main() -> Result<()> {
/// let color = TerminalColor::new();
/// // Set foreground color
/// color.set_fg(Color::Blue)?;
/// // Set background color
/// color.set_bg(Color::Red)?;
/// // Reset to the default colors
/// color.reset()
/// }
/// ```
pub struct TerminalColor {
#[cfg(windows)]
color: Box<(dyn Style + Sync + Send)>,
#[cfg(unix)]
color: AnsiColor,
}
impl TerminalColor {
/// Creates a new `TerminalColor`.
pub fn new() -> TerminalColor {
#[cfg(windows)]
let color = if supports_ansi() {
Box::from(AnsiColor::new()) as Box<(dyn Style + Sync + Send)>
} else {
WinApiColor::new() as Box<(dyn Style + Sync + Send)>
};
#[cfg(unix)]
let color = AnsiColor::new();
TerminalColor { color }
}
/// Sets the foreground color.
pub fn set_fg(&self, color: Color) -> Result<()> {
self.color.set_fg(color)
}
/// Sets the background color.
pub fn set_bg(&self, color: Color) -> Result<()> {
self.color.set_bg(color)
}
/// Resets the terminal colors and attributes to the default ones.
pub fn reset(&self) -> Result<()> {
self.color.reset()
}
/// Returns available color count.
///
/// # Notes
///
/// This does not always provide a good result.
pub fn available_color_count(&self) -> u16 {
env::var("TERM")
.map(|x| if x.contains("256color") { 256 } else { 8 })
.unwrap_or(8)
}
}
/// Creates a new `TerminalColor`.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{color, Color, Result};
///
/// fn main() -> Result<()> {
/// let color = color();
/// // Set foreground color
/// color.set_fg(Color::Blue)?;
/// // Set background color
/// color.set_bg(Color::Red)?;
/// // Reset to the default colors
/// color.reset()
/// }
/// ```
pub fn color() -> TerminalColor {
TerminalColor::new()
}
/// A command to set the foreground color.
///
/// See [`Color`](enum.Color.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct SetFg(pub Color);
impl Command for SetFg {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::set_fg_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiColor::new().set_fg(self.0)
}
}
/// A command to set the background color.
///
/// See [`Color`](enum.Color.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct SetBg(pub Color);
impl Command for SetBg {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::set_bg_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiColor::new().set_bg(self.0)
}
}
/// A command to set the text attribute.
///
/// See [`Attribute`](enum.Attribute.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct SetAttr(pub Attribute);
impl Command for SetAttr {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::set_attr_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
/// A command to print the styled object.
///
/// See [`StyledObject`](struct.StyledObject.html) for more info.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct PrintStyledFont<D: Display + Clone>(pub StyledObject<D>);
impl<D> Command for PrintStyledFont<D>
where
D: Display + Clone,
{
type AnsiType = StyledObject<D>;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// A command to reset the colors back to default ones.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct ResetColor;
impl Command for ResetColor {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi::RESET_CSI_SEQUENCE.to_string()
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiColor::new().reset()
}
}
impl_display!(for SetFg);
impl_display!(for SetBg);
impl_display!(for SetAttr);
impl_display!(for PrintStyledFont<String>);
impl_display!(for PrintStyledFont<&'static str>);
impl_display!(for ResetColor);

5
src/style/enums.rs Normal file
View File

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

View File

@ -0,0 +1,121 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::super::SetAttr;
/// 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::Attribute;
///
/// println!(
/// "{} Underlined {} No Underline",
/// Attribute::Underlined,
/// Attribute::NoUnderline
/// );
/// ```
///
/// Style existing text:
///
/// ```no_run
/// use crossterm::Styler;
///
/// 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)]
pub enum 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,
/// 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.
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.
NoInverse = 27, // TODO Shouldn't we rename this to `NoReverse`? Or `Reverse` to `Inverse`?
/// 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,
#[doc(hidden)]
__Nonexhaustive,
}
impl Display for Attribute {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", SetAttr(*self))?;
Ok(())
}
}

156
src/style/enums/color.rs Normal file
View File

@ -0,0 +1,156 @@
use std::convert::AsRef;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// 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 |
/// | :--| :-- |
/// | `Grey` | `Black` |
/// | `Red` | `DarkRed` |
/// | `Green` | `DarkGreen` |
/// | `Yellow` | `DarkYellow` |
/// | `Blue` | `DarkBlue` |
/// | `Magenta` | `DarkMagenta` |
/// | `Cyan` | `DarkCyan` |
/// | `White` | `DarkWhite` |
///
/// Most UNIX terminals and Windows 10 consoles support additional colors.
/// See [`Color::Rgb`](enum.Color.html#variant.Rgb) or [`Color::AnsiValue`](enum.Color.html#variant.AnsiValue) for
/// more info.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[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 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) -> ::std::result::Result<Self, Self::Err> {
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),
_ => Ok(Color::White),
}
}
}
#[cfg(test)]
mod tests {
use super::Color;
#[test]
fn test_known_color_conversion() {
assert_eq!("black".parse(), Ok(Color::Black));
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!("grey".parse(), Ok(Color::Grey));
}
#[test]
fn test_unknown_color_conversion_yields_white() {
assert_eq!("foo".parse(), Ok(Color::White));
}
}

View File

@ -0,0 +1,46 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{super::color, color::Color};
/// Represents a foreground or a background color.
///
/// Can be used to easily change the text colors.
///
/// # Examples
///
/// `Colored` implements `Display` therefore you can use it in any `write` operation.
///
/// ```no_run
/// use crossterm::{Colored, Color};
///
/// println!("{} Red foreground color", Colored::Fg(Color::Red));
/// println!("{} Blue background color", Colored::Bg(Color::Blue));
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Colored {
/// A foreground color.
Fg(Color),
/// A background color.
Bg(Color),
}
impl Display for Colored {
fn fmt(&self, _f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
let colored_terminal = color();
match *self {
Colored::Fg(color) => colored_terminal
.set_fg(color)
.map_err(|_| std::fmt::Error)?,
Colored::Bg(color) => colored_terminal
.set_bg(color)
.map_err(|_| std::fmt::Error)?,
}
Ok(())
}
}

46
src/style/macros.rs Normal file
View File

@ -0,0 +1,46 @@
macro_rules! def_attr {
($name:ident => $attr:path) => {
fn $name(self) -> StyledObject<D> {
self.attr($attr)
}
};
}
macro_rules! def_color {
($side:ident: $name:ident => $color:path) => {
fn $name(self) -> StyledObject<D> {
StyledObject {
object_style: ObjectStyle {
$side: Some($color),
..self.object_style
},
..self
}
}
};
}
macro_rules! def_str_color {
($side:ident: $name:ident => $color:path) => {
fn $name(self) -> StyledObject< &'static str> {
StyledObject {
object_style: ObjectStyle {
$side: Some($color),
..Default::default()
},
content: self
}
}
};
}
macro_rules! def_str_attr {
($name:ident => $color:path) => {
fn $name(self) -> StyledObject<&'static str> {
StyledObject {
object_style: Default::default(),
content: self,
}
}
}
}

77
src/style/objectstyle.rs Normal file
View File

@ -0,0 +1,77 @@
//! This module contains the `object style` that can be applied to an `styled object`.
use std::fmt::Display;
use super::{Attribute, Color, StyledObject};
/// An object style.
#[derive(Debug, Clone, Default)]
pub struct ObjectStyle {
/// The foreground color.
pub fg_color: Option<Color>,
/// The background color.
pub bg_color: Option<Color>,
/// List of attributes.
pub attrs: Vec<Attribute>,
}
impl ObjectStyle {
/// Creates a `StyledObject` by applying the style to the given `val`.
pub fn apply_to<D: Display + Clone>(&self, val: D) -> StyledObject<D> {
StyledObject {
object_style: self.clone(),
content: val,
}
}
/// Creates a new `ObjectStyle`.
pub fn new() -> ObjectStyle {
ObjectStyle::default()
}
/// Sets the background color.
pub fn bg(mut self, color: Color) -> ObjectStyle {
self.bg_color = Some(color);
self
}
/// Sets the foreground color.
pub fn fg(mut self, color: Color) -> ObjectStyle {
self.fg_color = Some(color);
self
}
/// Adds the attribute.
///
/// You can add more attributes by calling this method multiple times.
pub fn add_attr(&mut self, attr: Attribute) {
self.attrs.push(attr);
}
}
#[cfg(test)]
mod tests {
use crate::{Attribute, Color, ObjectStyle};
#[test]
fn test_set_fg_bg_add_attr() {
let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red);
object_style.add_attr(Attribute::Reset);
assert_eq!(object_style.fg_color, Some(Color::Blue));
assert_eq!(object_style.bg_color, Some(Color::Red));
assert_eq!(object_style.attrs[0], Attribute::Reset);
}
#[test]
fn test_apply_object_style_to_text() {
let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red);
object_style.add_attr(Attribute::Reset);
let styled_object = object_style.apply_to("test");
assert_eq!(styled_object.object_style.fg_color, Some(Color::Blue));
assert_eq!(styled_object.object_style.bg_color, Some(Color::Red));
assert_eq!(styled_object.object_style.attrs[0], Attribute::Reset);
}
}

27
src/style/style.rs Normal file
View File

@ -0,0 +1,27 @@
//! A module that contains all the actions related to the styling of the terminal.
//! Like applying attributes to text and changing the foreground and background.
use crate::utils::Result;
use super::Color;
pub(crate) mod ansi;
#[cfg(windows)]
pub(crate) mod winapi;
/// This trait defines the actions that can be performed with terminal colors.
/// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill
/// the wishes to work on a specific platform.
///
/// ## For example:
///
/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
/// so that color-related actions can be performed on both UNIX and Windows systems.
pub(crate) trait Style: Sync + Send {
/// Set the foreground color to the given color.
fn set_fg(&self, fg_color: Color) -> Result<()>;
/// Set the background color to the given color.
fn set_bg(&self, fg_color: Color) -> Result<()>;
/// Reset the terminal color to default.
fn reset(&self) -> Result<()>;
}

148
src/style/style/ansi.rs Normal file
View File

@ -0,0 +1,148 @@
//! This is a ANSI specific implementation for styling related action.
//! This module is used for Windows 10 terminals and Unix terminals by default.
use crate::utils::Result;
use crate::{csi, write_cout};
use super::super::{Attribute, Color, Colored, Style};
pub(crate) fn set_fg_csi_sequence(fg_color: Color) -> String {
format!(csi!("{}m"), Into::<String>::into(Colored::Fg(fg_color)))
}
pub(crate) fn set_bg_csi_sequence(bg_color: Color) -> String {
format!(csi!("{}m"), Into::<String>::into(Colored::Bg(bg_color)))
}
pub(crate) fn set_attr_csi_sequence(attribute: Attribute) -> String {
format!(csi!("{}m"), attribute as i16)
}
pub(crate) static RESET_CSI_SEQUENCE: &'static str = csi!("0m");
/// This struct is an ANSI escape code implementation for color related actions.
pub(crate) struct AnsiColor;
impl AnsiColor {
pub fn new() -> AnsiColor {
AnsiColor
}
}
impl Style for AnsiColor {
fn set_fg(&self, fg_color: Color) -> Result<()> {
write_cout!(set_fg_csi_sequence(fg_color))?;
Ok(())
}
fn set_bg(&self, bg_color: Color) -> Result<()> {
write_cout!(set_bg_csi_sequence(bg_color))?;
Ok(())
}
fn reset(&self) -> Result<()> {
write_cout!(RESET_CSI_SEQUENCE)?;
Ok(())
}
}
impl From<Colored> for String {
fn from(colored: Colored) -> Self {
let mut ansi_value = String::new();
let color;
match colored {
Colored::Fg(new_color) => {
if new_color == Color::Reset {
ansi_value.push_str("39");
return ansi_value;
} else {
ansi_value.push_str("38;");
color = new_color;
}
}
Colored::Bg(new_color) => {
if new_color == Color::Reset {
ansi_value.push_str("49");
return ansi_value;
} else {
ansi_value.push_str("48;");
color = new_color;
}
}
}
let color_val = match color {
Color::Black => "5;0",
Color::DarkGrey => "5;8",
Color::Red => "5;9",
Color::DarkRed => "5;1",
Color::Green => "5;10",
Color::DarkGreen => "5;2",
Color::Yellow => "5;11",
Color::DarkYellow => "5;3",
Color::Blue => "5;12",
Color::DarkBlue => "5;4",
Color::Magenta => "5;13",
Color::DarkMagenta => "5;5",
Color::Cyan => "5;14",
Color::DarkCyan => "5;6",
Color::White => "5;15",
Color::Grey => "5;7",
Color::Rgb { r, g, b } => {
ansi_value.push_str(format!("2;{};{};{}", r, g, b).as_str());
""
}
Color::AnsiValue(val) => {
ansi_value.push_str(format!("5;{}", val).as_str());
""
}
_ => "",
};
ansi_value.push_str(color_val);
ansi_value
}
}
#[cfg(test)]
mod tests {
use crate::{Color, Colored};
#[test]
fn test_parse_fg_color() {
let colored = Colored::Fg(Color::Red);
assert_eq!(Into::<String>::into(colored), "38;5;9");
}
#[test]
fn test_parse_bg_color() {
let colored = Colored::Bg(Color::Red);
assert_eq!(Into::<String>::into(colored), "48;5;9");
}
#[test]
fn test_parse_reset_fg_color() {
let colored = Colored::Fg(Color::Reset);
assert_eq!(Into::<String>::into(colored), "39");
}
#[test]
fn test_parse_reset_bg_color() {
let colored = Colored::Bg(Color::Reset);
assert_eq!(Into::<String>::into(colored), "49");
}
#[test]
fn test_parse_fg_rgb_color() {
let colored = Colored::Bg(Color::Rgb { r: 1, g: 2, b: 3 });
assert_eq!(Into::<String>::into(colored), "48;2;1;2;3");
}
#[test]
fn test_parse_fg_ansi_color() {
let colored = Colored::Fg(Color::AnsiValue(255));
assert_eq!(Into::<String>::into(colored), "38;5;255");
}
}

220
src/style/style/winapi.rs Normal file
View File

@ -0,0 +1,220 @@
//! This is a `WinApi` specific implementation for styling related action.
//! This module is used for non supporting `ANSI` Windows terminals.
use std::sync::Mutex;
use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer};
use winapi::um::wincon;
use lazy_static::lazy_static;
use crate::utils::Result;
use super::super::{Color, Colored, Style};
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;
/// This struct is a WinApi implementation for color related actions.
pub(crate) struct WinApiColor;
impl WinApiColor {
pub fn new() -> Box<WinApiColor> {
init_console_color().unwrap();
Box::from(WinApiColor)
}
}
impl Style for WinApiColor {
fn set_fg(&self, fg_color: Color) -> Result<()> {
let color_value: u16 = Colored::Fg(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 mut color: u16;
let attrs = csbi.attributes();
let bg_color = attrs & 0x0070;
color = color_value | bg_color;
// background intensity is a separate value in attrs,
// wee need to check if this was applied to the current bg color.
if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 {
color = color | wincon::BACKGROUND_INTENSITY as u16;
}
Console::from(**screen_buffer.handle()).set_text_attribute(color)?;
Ok(())
}
fn set_bg(&self, bg_color: Color) -> Result<()> {
let color_value: u16 = Colored::Bg(bg_color).into();
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So wee need to use bitwise operators to check if the values exists or to get current console colors.
let mut color: u16;
let attrs = csbi.attributes();
let fg_color = attrs & 0x0007;
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 = color | wincon::FOREGROUND_INTENSITY as u16;
}
Console::from(**screen_buffer.handle()).set_text_attribute(color)?;
Ok(())
}
fn reset(&self) -> Result<()> {
let original_color = original_console_color();
Console::from(Handle::new(HandleType::CurrentOutputHandle)?)
.set_text_attribute(original_color)?;
Ok(())
}
}
impl From<Colored> for u16 {
/// Returns the WinApi color value (u16) from the `Colored` struct.
fn from(colored: Colored) -> Self {
match colored {
Colored::Fg(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 { r: _, g: _, b: _ } => 0,
Color::AnsiValue(_val) => 0,
}
}
Colored::Bg(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 { r: _, g: _, b: _ } => 0,
Color::AnsiValue(_val) => 0,
}
}
}
}
}
/// Initializes the default console color. It will will be skipped if it has already been initialized.
fn init_console_color() -> Result<()> {
let mut locked_pos = ORIGINAL_CONSOLE_COLOR.lock().unwrap();
if locked_pos.is_none() {
let screen_buffer = ScreenBuffer::current()?;
let attr = screen_buffer.info()?.attributes();
*locked_pos = Some(attr);
}
Ok(())
}
/// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic.
fn original_console_color() -> u16 {
// safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()`
ORIGINAL_CONSOLE_COLOR
.lock()
.unwrap()
.expect("Initial console color not set")
}
lazy_static! {
static ref ORIGINAL_CONSOLE_COLOR: Mutex<Option<u16>> = Mutex::new(None);
}
#[cfg(test)]
mod tests {
use super::{
Color, Colored, WinApiColor, BG_INTENSITY, BG_RED, FG_INTENSITY, FG_RED,
ORIGINAL_CONSOLE_COLOR,
};
#[test]
fn test_parse_fg_color() {
let colored = Colored::Fg(Color::Red);
assert_eq!(Into::<u16>::into(colored), FG_INTENSITY | FG_RED);
}
#[test]
fn test_parse_bg_color() {
let colored = Colored::Bg(Color::Red);
assert_eq!(Into::<u16>::into(colored), BG_INTENSITY | BG_RED);
}
#[test]
fn test_original_console_color_is_set() {
assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_none());
// will call `init_console_color`
let _ = WinApiColor::new();
assert!(ORIGINAL_CONSOLE_COLOR.lock().unwrap().is_some());
}
}

156
src/style/styledobject.rs Normal file
View File

@ -0,0 +1,156 @@
//! This module contains the logic to style an object that contains some 'content' which can be styled.
use std::fmt::{self, Display, Formatter};
use std::result;
use crate::queue;
use super::{Attribute, Color, Colorize, ObjectStyle, ResetColor, SetAttr, SetBg, SetFg, Styler};
/// A styled object.
///
/// # Examples
///
/// ```rust
/// use crossterm::{style, Color, Attribute};
///
/// let styled = style("Hello there")
/// .with(Color::Yellow)
/// .on(Color::Blue)
/// .attr(Attribute::Bold);
///
/// println!("{}", styled);
/// ```
#[derive(Clone)]
pub struct StyledObject<D: Display + Clone> {
/// The object style (colors, content attributes).
pub object_style: ObjectStyle,
/// An object to apply the style on.
pub content: D,
}
impl<'a, D: Display + 'a + Clone> StyledObject<D> {
/// Sets the foreground color.
pub fn with(mut self, foreground_color: Color) -> StyledObject<D> {
self.object_style = self.object_style.fg(foreground_color);
self
}
/// Sets the background color.
pub fn on(mut self, background_color: Color) -> StyledObject<D> {
self.object_style = self.object_style.bg(background_color);
self
}
/// Adds the attribute.
///
/// You can add more attributes by calling this method multiple times.
pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
self.object_style.add_attr(attr);
self
}
}
impl<D: Display + Clone> Display for StyledObject<D> {
fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> {
let mut reset = false;
if let Some(bg) = self.object_style.bg_color {
queue!(f, SetBg(bg)).map_err(|_| fmt::Error)?;
reset = true;
}
if let Some(fg) = self.object_style.fg_color {
queue!(f, SetFg(fg)).map_err(|_| fmt::Error)?;
reset = true;
}
for attr in self.object_style.attrs.iter() {
queue!(f, SetAttr(*attr)).map_err(|_| fmt::Error)?;
reset = true;
}
fmt::Display::fmt(&self.content, f)?;
if reset {
queue!(f, ResetColor).map_err(|_| fmt::Error)?;
}
Ok(())
}
}
impl<D: Display + Clone> Colorize<D> for StyledObject<D> {
// foreground colors
def_color!(fg_color: black => Color::Black);
def_color!(fg_color: dark_grey => Color::DarkGrey);
def_color!(fg_color: red => Color::Red);
def_color!(fg_color: dark_red => Color::DarkRed);
def_color!(fg_color: green => Color::Green);
def_color!(fg_color: dark_green => Color::DarkGreen);
def_color!(fg_color: yellow => Color::Yellow);
def_color!(fg_color: dark_yellow => Color::DarkYellow);
def_color!(fg_color: blue => Color::Blue);
def_color!(fg_color: dark_blue => Color::DarkBlue);
def_color!(fg_color: magenta => Color::Magenta);
def_color!(fg_color: dark_magenta => Color::DarkMagenta);
def_color!(fg_color: cyan => Color::Cyan);
def_color!(fg_color: dark_cyan => Color::DarkCyan);
def_color!(fg_color: white => Color::White);
def_color!(fg_color: grey => Color::Grey);
// background colors
def_color!(bg_color: on_black => Color::Black);
def_color!(bg_color: on_dark_grey => Color::DarkGrey);
def_color!(bg_color: on_red => Color::Red);
def_color!(bg_color: on_dark_red => Color::DarkRed);
def_color!(bg_color: on_green => Color::Green);
def_color!(bg_color: on_dark_green => Color::DarkGreen);
def_color!(bg_color: on_yellow => Color::Yellow);
def_color!(bg_color: on_dark_yellow => Color::DarkYellow);
def_color!(bg_color: on_blue => Color::Blue);
def_color!(bg_color: on_dark_blue => Color::DarkBlue);
def_color!(bg_color: on_magenta => Color::Magenta);
def_color!(bg_color: on_dark_magenta => Color::DarkMagenta);
def_color!(bg_color: on_cyan => Color::Cyan);
def_color!(bg_color: on_dark_cyan => Color::DarkCyan);
def_color!(bg_color: on_white => Color::White);
def_color!(bg_color: on_grey => Color::Grey);
}
impl<D: Display + Clone> Styler<D> for StyledObject<D> {
def_attr!(reset => Attribute::Reset);
def_attr!(bold => Attribute::Bold);
def_attr!(underlined => Attribute::Underlined);
def_attr!(reverse => Attribute::Reverse);
def_attr!(dim => Attribute::Dim);
def_attr!(italic => Attribute::Italic);
def_attr!(negative => Attribute::Reverse);
def_attr!(slow_blink => Attribute::SlowBlink);
def_attr!(rapid_blink => Attribute::RapidBlink);
def_attr!(hidden => Attribute::Hidden);
def_attr!(crossed_out => Attribute::CrossedOut);
}
#[cfg(test)]
mod tests {
use super::{Attribute, Color, ObjectStyle};
#[test]
fn test_set_fg_bg_add_attr() {
let mut object_style = ObjectStyle::new().fg(Color::Blue).bg(Color::Red);
object_style.add_attr(Attribute::Reset);
let mut styled_object = object_style.apply_to("test");
styled_object = styled_object
.with(Color::Green)
.on(Color::Magenta)
.attr(Attribute::NoItalic);
assert_eq!(styled_object.object_style.fg_color, Some(Color::Green));
assert_eq!(styled_object.object_style.bg_color, Some(Color::Magenta));
assert_eq!(styled_object.object_style.attrs.len(), 2);
assert_eq!(styled_object.object_style.attrs[0], Attribute::Reset);
assert_eq!(styled_object.object_style.attrs[1], Attribute::NoItalic);
}
}

81
src/style/traits.rs Normal file
View File

@ -0,0 +1,81 @@
use std::fmt::Display;
use super::StyledObject;
/// Provides a set of methods to set the colors.
///
/// Every method with the `on_` prefix sets the background color. All other methods set
/// the foreground color.
///
/// Method names correspond to the [`Color`](enum.Color.html) enum variants.
///
/// # Examples
///
/// ```no_run
/// use crossterm::Colorize;
///
/// let styled_text = "Red foreground color on blue background.".red().on_blue();
/// println!("{}", styled_text);
/// ```
pub trait Colorize<D: Display + Clone> {
fn black(self) -> StyledObject<D>;
fn dark_grey(self) -> StyledObject<D>;
fn red(self) -> StyledObject<D>;
fn dark_red(self) -> StyledObject<D>;
fn green(self) -> StyledObject<D>;
fn dark_green(self) -> StyledObject<D>;
fn yellow(self) -> StyledObject<D>;
fn dark_yellow(self) -> StyledObject<D>;
fn blue(self) -> StyledObject<D>;
fn dark_blue(self) -> StyledObject<D>;
fn magenta(self) -> StyledObject<D>;
fn dark_magenta(self) -> StyledObject<D>;
fn cyan(self) -> StyledObject<D>;
fn dark_cyan(self) -> StyledObject<D>;
fn white(self) -> StyledObject<D>;
fn grey(self) -> StyledObject<D>;
fn on_black(self) -> StyledObject<D>;
fn on_dark_grey(self) -> StyledObject<D>;
fn on_red(self) -> StyledObject<D>;
fn on_dark_red(self) -> StyledObject<D>;
fn on_green(self) -> StyledObject<D>;
fn on_dark_green(self) -> StyledObject<D>;
fn on_yellow(self) -> StyledObject<D>;
fn on_dark_yellow(self) -> StyledObject<D>;
fn on_blue(self) -> StyledObject<D>;
fn on_dark_blue(self) -> StyledObject<D>;
fn on_magenta(self) -> StyledObject<D>;
fn on_dark_magenta(self) -> StyledObject<D>;
fn on_cyan(self) -> StyledObject<D>;
fn on_dark_cyan(self) -> StyledObject<D>;
fn on_white(self) -> StyledObject<D>;
fn on_grey(self) -> StyledObject<D>;
}
/// Provides a set of methods to set the text attributes.
///
/// Method names correspond to the [`Attribute`](enum.Attribute.html) enum variants.
///
/// # Examples
///
/// ```no_run
/// use crossterm::Styler;
///
/// println!("{}", "Bold text".bold());
/// println!("{}", "Underlined text".underlined());
/// println!("{}", "Negative text".negative());
/// ```
pub trait Styler<D: Display + Clone> {
fn reset(self) -> StyledObject<D>;
fn bold(self) -> StyledObject<D>;
fn underlined(self) -> StyledObject<D>;
fn reverse(self) -> StyledObject<D>;
fn dim(self) -> StyledObject<D>;
fn italic(self) -> StyledObject<D>;
fn negative(self) -> StyledObject<D>;
fn slow_blink(self) -> StyledObject<D>;
fn rapid_blink(self) -> StyledObject<D>;
fn hidden(self) -> StyledObject<D>;
fn crossed_out(self) -> StyledObject<D>;
}

298
src/terminal.rs Normal file
View File

@ -0,0 +1,298 @@
//! # Terminal
//!
//! The `terminal` module provides a 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/examples) repository
//! to demonstrate the capabilities.
//!
//! ## Examples
//!
//! ```no_run
//! use crossterm::{Result, Terminal};
//!
//! fn main() -> Result<()> {
//! // Get a terminal, save size
//! let terminal = Terminal::new();
//! let (cols, rows) = terminal.size()?;
//!
//! // Do something with the terminal
//! terminal.set_size(10, 10)?;
//! terminal.scroll_up(5)?;
//!
//! // Be a good citizen, cleanup
//! terminal.set_size(cols, rows)
//! }
//! ```
//!
//! Commands:
//!
//! ```no_run
//! use std::io::{stdout, Write};
//! use crossterm::{execute, Result, ScrollUp, SetSize, Terminal};
//!
//! fn main() -> Result<()> {
//! // Get a terminal, save size
//! let terminal = Terminal::new();
//! let (cols, rows) = terminal.size()?;
//!
//! // Do something with the terminal
//! execute!(
//! stdout(),
//! SetSize(10, 10),
//! ScrollUp(5)
//! )?;
//!
//! // Be a good citizen, cleanup
//! terminal.set_size(cols, rows)
//! }
//! ```
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(windows)]
use crate::utils::supports_ansi;
#[doc(no_inline)]
use crate::utils::{Command, Result};
use crate::{impl_display, write_cout};
use self::terminal::ansi::AnsiTerminal;
#[cfg(windows)]
use self::terminal::winapi::WinApiTerminal;
use self::terminal::Terminal as TerminalTrait;
mod sys;
mod terminal;
/// Represents different options how to clear the terminal.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ClearType {
/// All cells.
All,
/// 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 terminal.
///
/// The `Terminal` instance is stateless and does not hold any data.
/// You can create as many instances as you want and they will always refer to the
/// same terminal.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{Result, Terminal};
///
/// fn main() -> Result<()> {
/// let terminal = Terminal::new();
/// let (cols, rows) = terminal.size()?;
///
/// terminal.set_size(10, 10)?;
/// terminal.scroll_up(5)?;
///
/// terminal.set_size(cols, rows)
/// }
/// ```
pub struct Terminal {
#[cfg(windows)]
terminal: Box<(dyn TerminalTrait + Sync + Send)>,
#[cfg(unix)]
terminal: AnsiTerminal,
}
impl Terminal {
/// Creates a new `Terminal`.
pub fn new() -> Terminal {
#[cfg(windows)]
let terminal = if supports_ansi() {
Box::from(AnsiTerminal::new()) as Box<(dyn TerminalTrait + Sync + Send)>
} else {
WinApiTerminal::new() as Box<(dyn TerminalTrait + Sync + Send)>
};
#[cfg(unix)]
let terminal = AnsiTerminal::new();
Terminal { terminal }
}
/// Clears the terminal.
///
/// See the [`ClearType`](enum.ClearType.html) enum to learn about
/// all ways how the terminal can be cleared.
pub fn clear(&self, clear_type: ClearType) -> Result<()> {
self.terminal.clear(clear_type)
}
/// Returns the terminal size (`(columns, rows)`).
pub fn size(&self) -> Result<(u16, u16)> {
self.terminal.size()
}
/// Scrolls the terminal `row_count` rows up.
pub fn scroll_up(&self, row_count: u16) -> Result<()> {
self.terminal.scroll_up(row_count)
}
/// Scrolls the terminal `row_count` rows down.
pub fn scroll_down(&self, row_count: u16) -> Result<()> {
self.terminal.scroll_down(row_count)
}
/// Sets the terminal size.
pub fn set_size(&self, columns: u16, rows: u16) -> Result<()> {
self.terminal.set_size(columns, rows)
}
/// Exits the current process.
///
/// # Platform-specific Behavior
///
/// [`std::process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html) is
/// called internally with platform specific exit codes.
///
/// **Unix**: exit code 0.
///
/// **Windows**: exit code 256.
pub fn exit(&self) {
crate::terminal::sys::exit();
}
/// Writes any displayable content to the current terminal and flushes
/// the standard output.
pub fn write<D: fmt::Display>(&self, value: D) -> Result<usize> {
write_cout!(format!("{}", value))
}
}
/// Creates a new `Terminal`.
///
/// # Examples
///
/// Basic usage:
///
/// ```no_run
/// use crossterm::{terminal, Result};
///
/// fn main() -> Result<()> {
/// // Get a terminal, save size
/// let terminal = terminal();
/// let (cols, rows) = terminal.size()?;
///
/// // Do something with the terminal
/// terminal.set_size(10, 10)?;
/// terminal.scroll_up(5)?;
///
/// // Be a good citizen, cleanup
/// terminal.set_size(cols, rows)
/// }
/// ```
pub fn terminal() -> Terminal {
Terminal::new()
}
/// A command to scroll the terminal given rows up.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct ScrollUp(pub u16);
impl Command for ScrollUp {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
terminal::ansi::scroll_up_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().scroll_up(self.0)
}
}
/// A command to scroll the terminal given rows down.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct ScrollDown(pub u16);
impl Command for ScrollDown {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
terminal::ansi::scroll_down_csi_sequence(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().scroll_down(self.0)
}
}
/// A command to clear the terminal.
///
/// See the [`ClearType`](enum.ClearType.html) enum.
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct Clear(pub ClearType);
impl Command for Clear {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
match self.0 {
ClearType::All => terminal::ansi::CLEAR_ALL_CSI_SEQUENCE,
ClearType::FromCursorDown => terminal::ansi::CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE,
ClearType::FromCursorUp => terminal::ansi::CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE,
ClearType::CurrentLine => terminal::ansi::CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE,
ClearType::UntilNewLine => terminal::ansi::CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE,
}
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().clear(self.0.clone())
}
}
/// A command to set the terminal size (rows, columns).
///
/// # Notes
///
/// Commands must be executed/queued for execution otherwise they do nothing.
pub struct SetSize(pub u16, pub u16);
impl Command for SetSize {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
terminal::ansi::set_size_csi_sequence(self.0, self.1)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().set_size(self.0, self.1)
}
}
impl_display!(for ScrollUp);
impl_display!(for ScrollDown);
impl_display!(for SetSize);
impl_display!(for Clear);

10
src/terminal/sys.rs Normal file
View File

@ -0,0 +1,10 @@
#[cfg(unix)]
pub(crate) use self::unix::{exit, get_terminal_size};
#[cfg(windows)]
pub(crate) use self::winapi::{exit, get_terminal_size};
#[cfg(windows)]
pub(crate) mod winapi;
#[cfg(unix)]
pub(crate) mod unix;

24
src/terminal/sys/unix.rs Normal file
View File

@ -0,0 +1,24 @@
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
use crate::utils::Result;
pub(crate) fn exit() {
::std::process::exit(0);
}
pub(crate) fn get_terminal_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 r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) };
if r == 0 {
Ok((size.ws_col, size.ws_row))
} else {
Err(std::io::Error::last_os_error().into())
}
}

View File

@ -0,0 +1,16 @@
use crossterm_winapi::ScreenBuffer;
use crate::utils::Result;
pub(crate) fn exit() {
::std::process::exit(256);
}
pub(crate) fn get_terminal_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,
))
}

30
src/terminal/terminal.rs Normal file
View File

@ -0,0 +1,30 @@
//! A module that contains all the actions related to the terminal. like clearing, resizing, pausing
//! and scrolling the terminal.
use crate::utils::Result;
use super::ClearType;
pub(crate) mod ansi;
#[cfg(windows)]
pub(crate) mod winapi;
/// This trait defines the actions that can be performed with the terminal color.
/// This trait can be implemented so that an concrete implementation of the ITerminalColor can fulfill.
/// the wishes to work on an specific platform.
///
/// ## For example:
///
/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
/// so that terminal related actions can be performed on both Unix and Windows systems.
pub(crate) trait Terminal {
/// Clear the current cursor by specifying the clear type
fn clear(&self, clear_type: ClearType) -> Result<()>;
/// Get the terminal size (x,y)
fn size(&self) -> Result<(u16, u16)>;
/// Scroll `n` lines up in the current terminal.
fn scroll_up(&self, count: u16) -> Result<()>;
/// Scroll `n` lines down in the current terminal.
fn scroll_down(&self, count: u16) -> Result<()>;
/// Resize terminal to the given width and height.
fn set_size(&self, width: u16, height: u16) -> Result<()>;
}

View File

@ -0,0 +1,118 @@
//! This is an `ANSI escape code` specific implementation for terminal related action.
//! This module is used for windows 10 terminals and unix terminals by default.
use crate::cursor::TerminalCursor;
use crate::utils::Result;
use crate::{csi, write_cout};
use super::{super::sys::get_terminal_size, ClearType, Terminal};
pub(crate) static CLEAR_ALL_CSI_SEQUENCE: &'static str = csi!("2J");
pub(crate) static CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE: &'static str = csi!("J");
pub(crate) static CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE: &'static str = csi!("1J");
pub(crate) static CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE: &'static str = csi!("2K");
pub(crate) static CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE: &'static str = csi!("K");
pub(crate) fn scroll_up_csi_sequence(count: u16) -> String {
format!(csi!("{}S"), count)
}
pub(crate) fn scroll_down_csi_sequence(count: u16) -> String {
format!(csi!("{}T"), count)
}
pub(crate) fn set_size_csi_sequence(width: u16, height: u16) -> String {
format!(csi!("8;{};{}t"), height, width)
}
/// This struct is an ansi escape code implementation for terminal related actions.
pub(crate) struct AnsiTerminal;
impl AnsiTerminal {
pub(crate) fn new() -> AnsiTerminal {
AnsiTerminal
}
}
impl Terminal for AnsiTerminal {
fn clear(&self, clear_type: ClearType) -> Result<()> {
match clear_type {
ClearType::All => write_cout!(CLEAR_ALL_CSI_SEQUENCE)?,
ClearType::FromCursorDown => write_cout!(CLEAR_FROM_CURSOR_DOWN_CSI_SEQUENCE)?,
ClearType::FromCursorUp => write_cout!(CLEAR_FROM_CURSOR_UP_CSI_SEQUENCE)?,
ClearType::CurrentLine => write_cout!(CLEAR_FROM_CURRENT_LINE_CSI_SEQUENCE)?,
ClearType::UntilNewLine => write_cout!(CLEAR_UNTIL_NEW_LINE_CSI_SEQUENCE)?,
};
if clear_type == ClearType::All {
TerminalCursor::new().goto(0, 0)?;
}
Ok(())
}
fn size(&self) -> Result<(u16, u16)> {
get_terminal_size()
}
fn scroll_up(&self, count: u16) -> Result<()> {
write_cout!(scroll_up_csi_sequence(count))?;
Ok(())
}
fn scroll_down(&self, count: u16) -> Result<()> {
write_cout!(scroll_down_csi_sequence(count))?;
Ok(())
}
fn set_size(&self, width: u16, height: u16) -> Result<()> {
write_cout!(set_size_csi_sequence(width, height))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{thread, time};
use super::{AnsiTerminal, Terminal};
// TODO - Test is disabled, because it's failing on Travis CI
#[test]
#[ignore]
fn test_resize_ansi() {
if try_enable_ansi() {
let terminal = AnsiTerminal::new();
let (width, height) = terminal.size().unwrap();
terminal.set_size(35, 35).unwrap();
// see issue: https://github.com/eminence/terminal-size/issues/11
thread::sleep(time::Duration::from_millis(30));
assert_eq!((35, 35), terminal.size().unwrap());
// reset to previous size
terminal.set_size(width, height).unwrap();
// see issue: https://github.com/eminence/terminal-size/issues/11
thread::sleep(time::Duration::from_millis(30));
assert_eq!((width, height), terminal.size().unwrap());
}
}
fn try_enable_ansi() -> bool {
#[cfg(windows)]
{
if cfg!(target_os = "windows") {
use crate::utils::sys::winapi::ansi::set_virtual_terminal_processing;
// if it is not listed we should try with WinApi to check if we do support ANSI-codes.
match set_virtual_terminal_processing(true) {
Ok(_) => return true,
Err(_) => return false,
}
}
}
true
}
}

View File

@ -0,0 +1,285 @@
//! This is a `WINAPI` specific implementation for terminal related action.
//! This module is used for non supporting `ANSI` windows terminals.
//!
//! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions
//! will use this implementation instead.
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
use crate::cursor::TerminalCursor;
use crate::utils::{ErrorKind, Result};
use super::{super::sys::winapi::get_terminal_size, ClearType, Terminal};
/// This struct is a winapi implementation for terminal related actions.
pub(crate) struct WinApiTerminal;
impl WinApiTerminal {
pub(crate) fn new() -> Box<WinApiTerminal> {
Box::from(WinApiTerminal {})
}
}
impl Terminal for WinApiTerminal {
fn clear(&self, 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)?,
};
Ok(())
}
fn size(&self) -> Result<(u16, u16)> {
get_terminal_size()
}
fn scroll_up(&self, 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 = count as i16;
if window.top >= count {
window.top -= count; // move top down
window.bottom = count; // move bottom down
Console::new()?.set_console_info(false, window)?;
}
Ok(())
}
fn scroll_down(&self, 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 = count as i16;
if window.bottom < buffer_size.height - count {
window.top += count; // move top down
window.bottom += count; // move bottom down
Console::new()?.set_console_info(false, window)?;
}
Ok(())
}
/// Set the current terminal size
fn set_size(&self, width: u16, height: u16) -> Result<()> {
if width <= 0 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Cannot set the terminal width lower than 1",
)));
}
if height <= 0 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Cannot set the terminal height lower then 1",
)));
}
// Get the position of the current console window
let screen_buffer = ScreenBuffer::current()?;
let console = Console::from(**screen_buffer.handle());
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::ResizingTerminalFailure(String::from(
"Argument out of range when setting terminal width.",
)));
}
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::ResizingTerminalFailure(String::from(
"Argument out of range when setting terminal height.",
)));
}
new_size.height = window.top + height;
resize_buffer = true;
}
if resize_buffer {
if let Err(_) = screen_buffer.set_size(new_size.width - 1, new_size.height - 1) {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Something went wrong when setting screen buffer size.",
)));
}
}
let mut window = window.clone();
// 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 {
if let Err(_) = screen_buffer.set_size(current_size.width - 1, current_size.height - 1)
{
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Something went wrong when setting screen buffer size.",
)));
}
}
let bounds = console.largest_window_size();
if width > bounds.x {
return Err(ErrorKind::ResizingTerminalFailure(format!(
"Argument width: {} out of range when setting terminal width.",
width
)));
}
if height > bounds.y {
return Err(ErrorKind::ResizingTerminalFailure(format!(
"Argument height: {} out of range when setting terminal height",
width
)));
}
Ok(())
}
}
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(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(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(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 0,0
let cursor = TerminalCursor::new();
cursor.goto(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(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 1 on current row
let cursor = TerminalCursor::new();
cursor.goto(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(start_location, cells_to_write, current_attribute)?;
// put the cursor back at original cursor position before we did the clearing
let cursor = TerminalCursor::new();
cursor.goto(x as u16, y as u16)?;
Ok(())
}
fn clear(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 super::{Terminal, WinApiTerminal};
#[test]
fn test_resize_winapi() {
let terminal = WinApiTerminal::new();
let (width, height) = terminal.size().unwrap();
terminal.set_size(30, 30).unwrap();
assert_eq!((30, 30), terminal.size().unwrap());
// reset to previous size
terminal.set_size(width, height).unwrap();
assert_eq!((width, height), terminal.size().unwrap());
}
}

12
src/utils.rs Normal file
View File

@ -0,0 +1,12 @@
//! # Utils
pub use self::command::{Command, ExecutableCommand, Output, QueueableCommand};
pub use self::error::{ErrorKind, Result};
#[cfg(windows)]
pub use self::functions::supports_ansi;
mod command;
mod error;
mod functions;
pub(crate) mod macros;
pub(crate) mod sys;

115
src/utils/command.rs Normal file
View File

@ -0,0 +1,115 @@
use std::fmt::Display;
use std::io::Write;
use crate::{execute, impl_display, queue, write_cout};
use super::error::Result;
/// A command is an action that can be performed on the terminal.
///
/// crossterm already delivers a number of commands.
/// There is no need to implement them yourself.
/// Also, you don't have to execute the commands yourself by calling a function.
pub trait Command {
type AnsiType: Display;
/// Returns the ANSI code representation of this command.
/// You can manipulate the terminal behaviour by writing an ANSI escape code to the terminal.
/// You are able to use ANSI escape codes only for windows 10 and UNIX systems.
///
/// **This method is mainly used internally by crossterm!**
fn ansi_code(&self) -> Self::AnsiType;
/// Execute this command.
///
/// On operating systems that do not support ANSI escape codes ( < Windows 10) we need to call WinApi to execute this command.
///
/// **This method is mainly used internally by crossterm!**
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()>;
}
/// A trait that defines behaviour for a command that can be used to be executed at a later time point.
/// This can be used in order to get more performance.
pub trait QueueableCommand<T: Display>: Sized {
/// Queues the given command for later execution.
fn queue(&mut self, command: impl Command<AnsiType = T>) -> Result<&mut Self>;
}
/// A trait that defines behaviour for a command that will be executed immediately.
pub trait ExecutableCommand<T: Display>: Sized {
/// Execute the given command directly.
fn execute(&mut self, command: impl Command<AnsiType = T>) -> Result<&mut Self>;
}
impl<T, A> QueueableCommand<A> for T
where
A: Display,
T: Write,
{
/// Queue the given command for later execution.
///
/// Queued commands will be executed in the following cases:
/// - When you manually call `flush` on the given writer.
/// - When the buffer is to full, then the terminal will flush for you.
/// - Incase of `stdout` each line, because `stdout` is line buffered.
///
/// # Parameters
/// - [Command](./trait.Command.html)
///
/// The command that you want to queue for later execution.
///
/// # Remarks
/// - 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.
/// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer.
/// Because of that there is no difference between `execute` and `queue` for those windows versions.
/// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal.
fn queue(&mut self, command: impl Command<AnsiType = A>) -> Result<&mut Self> {
queue!(self, command)?;
Ok(self)
}
}
impl<T, A> ExecutableCommand<A> for T
where
A: Display,
T: Write,
{
/// Execute the given command directly.
/// This function will `write` the ANSI escape code to this type and call `flush`.
///
/// In case you have many executions after on and another you can use `queue(command)` to get some better performance.
/// The `queue` function will not call `flush`.
///
/// # Remarks
/// - 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.
/// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer.
/// Because of that there is no difference between `execute` and `queue` for those windows versions.
fn execute(&mut self, command: impl Command<AnsiType = A>) -> Result<&mut Self> {
execute!(self, command)?;
Ok(self)
}
}
/// When executed, this command will output the given string to the terminal.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Output(pub String);
impl Command for Output {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
return self.0.clone();
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
print!("{}", self.0);
Ok(())
}
}
impl_display!(for Output);

51
src/utils/error.rs Normal file
View File

@ -0,0 +1,51 @@
//! Module containing error handling logic.
use std::{
fmt::{self, Display, Formatter},
io,
};
use crate::impl_from;
/// The `crossterm` result type.
pub type Result<T> = std::result::Result<T, ErrorKind>;
/// Wrapper for all errors that can occur in `crossterm`.
#[derive(Debug)]
pub enum ErrorKind {
IoError(io::Error),
FmtError(fmt::Error),
Utf8Error(std::string::FromUtf8Error),
ParseIntError(std::num::ParseIntError),
ResizingTerminalFailure(String),
#[doc(hidden)]
__Nonexhaustive,
}
impl std::error::Error for ErrorKind {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ErrorKind::IoError(e) => Some(e),
ErrorKind::FmtError(e) => Some(e),
ErrorKind::Utf8Error(e) => Some(e),
ErrorKind::ParseIntError(e) => Some(e),
_ => None,
}
}
}
impl Display for ErrorKind {
fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
match *self {
ErrorKind::IoError(_) => write!(fmt, "IO-error occurred"),
ErrorKind::ResizingTerminalFailure(_) => write!(fmt, "Cannot resize the terminal"),
_ => write!(fmt, "Some error has occurred"),
}
}
}
impl_from!(io::Error, ErrorKind::IoError);
impl_from!(fmt::Error, ErrorKind::FmtError);
impl_from!(std::string::FromUtf8Error, ErrorKind::Utf8Error);
impl_from!(std::num::ParseIntError, ErrorKind::ParseIntError);

42
src/utils/functions.rs Normal file
View File

@ -0,0 +1,42 @@
#[cfg(windows)]
use super::sys::winapi::ansi::set_virtual_terminal_processing;
#[cfg(windows)]
pub fn supports_ansi() -> bool {
// Some terminals on windows like GitBash can't use WinaApi 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.
if is_specific_term() {
return true;
}
// if it is not listed we should try with WinApi to check if we do support ANSI-codes.
set_virtual_terminal_processing(true)
.map(|_| true)
.unwrap_or(false)
}
// checks if the 'TERM' environment variable is set to check if the terminal supports ANSI-codes.
// I got the list of terminals from here: https://github.com/keqingrong/supports-ansi/blob/master/index.js
#[cfg(windows)]
fn is_specific_term() -> bool {
const TERMS: [&'static str; 15] = [
"xterm", // xterm, PuTTY, Mintty
"rxvt", // RXVT
"eterm", // Eterm
"screen", // GNU screen, tmux
"tmux", // tmux
"vt100", "vt102", "vt220", "vt320", // DEC VT series
"ansi", // ANSI
"scoansi", // SCO ANSI
"cygwin", // Cygwin, MinGW
"linux", // Linux console
"konsole", // Konsole
"bvterm", // Bitvise SSH Client
];
match std::env::var("TERM") {
Ok(val) => val != "dumb" || TERMS.contains(&val.as_str()),
Err(_) => false,
}
}

199
src/utils/macros.rs Normal file
View File

@ -0,0 +1,199 @@
/// Append a the first few characters of an ANSI escape code to the given string.
#[macro_export]
macro_rules! csi {
($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) };
}
/// Write a string to standard output whereafter the stdout will be flushed.
#[macro_export]
macro_rules! write_cout {
($write:expr, $string:expr) => {{
use $crate::ErrorKind;
let fmt = format!("{}", $string);
let bytes = fmt.as_bytes();
$write
.write_all(bytes)
.and_then(|_| $write.flush().map(|_| bytes.len()))
.map_err(ErrorKind::IoError)
}};
($string:expr) => {{
// Bring Write into the scope and ignore unused imports if it's
// already imported by the user
#[allow(unused_imports)]
use std::io::Write;
write_cout!(::std::io::stdout(), $string)
}};
}
/// Queue one or more command(s) for execution in the near future.
///
/// Queued commands will be executed in the following cases:
/// - When you manually call `flush` on the given writer.
/// - When the buffer is to full, then the terminal will flush for you.
/// - Incase of `stdout` each line, because `stdout` is line buffered.
///
/// # Parameters
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// Crossterm will write the ANSI escape codes to this given writer (No flush will be done).
/// - [Command](./trait.Command.html)
///
/// Give one or more commands that you want to queue for execution
///
/// # Example
/// ```rust
///
/// use std::io::{Write, stdout};
///
/// use crossterm::{queue, Output};
///
/// let mut stdout = stdout();
///
/// // will be executed when flush is called
/// queue!(stdout, Output("foo".to_string()));
///
/// // some other code (no execution happening here) ...
///
/// // when calling flush on stdout, all commands will be written to the stdout and therefor executed.
/// stdout.flush();
/// ```
///
/// # Remarks
/// - 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.
/// This is happening because windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer.
/// Because of that there is no difference between `execute` and `queue` for those windows versions.
/// - Queuing might sound that there is some scheduling going on, however, this means that we write to the stdout without flushing which will cause commands to be stored in the buffer without them being written to the terminal.
#[macro_export]
macro_rules! queue {
($write:expr, $($command:expr), *) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::Command;
let mut error = None;
$(
#[cfg(windows)]
{
if $crate::utils::supports_ansi() {
match write!($write, "{}", $command.ansi_code()) {
Err(e) => {
error = Some(Err($crate::ErrorKind::from(e)));
}
_ => {}
};
} else {
match $command.execute_winapi() {
Err(e) => {
error = Some(Err($crate::ErrorKind::from(e)));
}
_ => {}
};
};
}
#[cfg(unix)]
match write!($write, "{}", $command.ansi_code()) {
Err(e) => {
error = Some(Err($crate::ErrorKind::from(e)));
}
_ => {}
};
)*
if let Some(error) = error {
error
} else {
Ok(())
}
}}
}
/// Execute one or more command(s)
///
/// # Parameters
/// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html)
///
/// Crossterm will write the ANSI escape codes to this given. (A flush will be done)
/// - [Command](./trait.Command.html)
///
/// Give one or more commands that you want to execute
///
/// # Example
/// ```rust
/// use std::io::Write;
///
/// use crossterm::{execute, Output};
///
/// // will be executed directly
/// execute!(std::io::stdout(), Output("foo".to_string()));
///
/// // will be executed directly
/// execute!(std::io::stdout(), Output("foo".to_string()), Output("bar".to_string()));
/// ```
///
/// # Remarks
/// - 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.
/// This is happening because Windows versions lower then 10 do not support ANSI codes, and thus they can't be written to the given buffer.
/// Because of that there is no difference between `execute` and `queue` for those windows versions.
#[macro_export]
macro_rules! execute {
($write:expr, $($command:expr), *) => {{
// Silent warning when the macro is used inside the `command` module
#[allow(unused_imports)]
use $crate::{Command, write_cout};
let mut error = None;
$(
#[cfg(windows)]
{
if $crate::utils::supports_ansi() {
if let Err(e) = write_cout!($write, $command.ansi_code()) {
error = Some($crate::ErrorKind::from(e));
};
} else {
if let Err(e) = $command.execute_winapi() {
error = Some($crate::ErrorKind::from(e));
};
};
}
#[cfg(unix)]
{
if let Err(e) = write_cout!($write, $command.ansi_code()) {
error = Some($crate::ErrorKind::from(e));
}
}
)*
if let Some(error) = error {
Err(error)
} else {
Ok(())
}
}}
}
#[macro_export]
macro_rules! impl_display {
(for $($t:ty),+) => {
$(impl ::std::fmt::Display for $t {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> {
use $crate::Command;
write!(f, "{}", self.ansi_code())
}
})*
}
}
#[macro_export]
macro_rules! impl_from {
($from:path, $to:expr) => {
impl From<$from> for ErrorKind {
fn from(e: $from) -> Self {
$to(e)
}
}
};
}

5
src/utils/sys.rs Normal file
View File

@ -0,0 +1,5 @@
#[cfg(windows)]
pub mod winapi;
#[cfg(unix)]
pub mod unix;

77
src/utils/sys/unix.rs Normal file
View File

@ -0,0 +1,77 @@
//! This module contains all `unix` specific terminal related logic.
use std::sync::Mutex;
use std::{io, mem};
pub use libc::termios as Termios;
use libc::{cfmakeraw, tcgetattr, tcsetattr, STDIN_FILENO, TCSANOW};
use lazy_static::lazy_static;
use super::super::error::{ErrorKind, Result};
lazy_static! {
// Some(Termios) -> we're in the raw mode and this is the previous mode
// None -> we're not in the raw mode
static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex<Option<Termios>> = Mutex::new(None);
}
pub fn is_raw_mode_enabled() -> bool {
TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some()
}
fn wrap_with_result(t: i32) -> Result<()> {
if t == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error()))
} else {
Ok(())
}
}
/// Transform the given mode into an raw mode (non-canonical) mode.
pub fn raw_terminal_attr(termios: &mut Termios) {
unsafe { cfmakeraw(termios) }
}
pub fn get_terminal_attr() -> Result<Termios> {
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?;
Ok(termios)
}
}
pub fn set_terminal_attr(termios: &Termios) -> Result<()> {
wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) })
}
pub fn enable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if original_mode.is_some() {
return Ok(());
}
let mut ios = get_terminal_attr()?;
let original_mode_ios = ios;
raw_terminal_attr(&mut ios);
set_terminal_attr(&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(())
}
pub fn disable_raw_mode() -> Result<()> {
let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap();
if let Some(original_mode_ios) = original_mode.as_ref() {
set_terminal_attr(original_mode_ios)?;
// Keep it last - remove the original mode only if we were able to switch back
*original_mode = None;
}
Ok(())
}

34
src/utils/sys/winapi.rs Normal file
View File

@ -0,0 +1,34 @@
pub mod ansi {
use crossterm_winapi::ConsoleMode;
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
use super::super::super::error::Result;
/// Toggle virtual terminal processing.
///
/// This method attempts to toggle virtual terminal processing for this
/// console. If there was a problem toggling it, then an error returned.
/// On success, the caller may assume that toggling 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.
pub fn set_virtual_terminal_processing(yes: bool) -> Result<()> {
let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
let console_mode = ConsoleMode::new()?;
let old_mode = console_mode.mode()?;
let new_mode = if yes {
old_mode | mask
} else {
old_mode & !mask
};
if old_mode != new_mode {
console_mode.set_mode(new_mode)?;
}
Ok(())
}
}