Refactor and API stabilization (#115)

- Major refactor and cleanup.
- Improved performance; 
    - No locking when writing to stdout. 
    - UNIX doesn't have any dynamic dispatch anymore. 
    - Windows has improved the way to check if ANSI modes are enabled.
    - Removed lot's of complex API calls: `from_screen`, `from_output`
    - Removed `Arc<TerminalOutput>` from all internal Api's. 
- Removed termios dependency for UNIX systems.
- Upgraded deps.
- Removed about 1000 lines of code
    - `TerminalOutput` 
    - `Screen`
    - unsafe code
    - Some duplicated code introduced by a previous refactor.
- Raw modes UNIX systems improved     
- Added `NoItalic` attribute
This commit is contained in:
Timon 2019-04-10 23:46:30 +02:00 committed by GitHub
parent 77188d6d2c
commit 1e332daaed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 1182 additions and 2651 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm"
version = "0.8.2"
version = "0.9.0"
authors = ["T. Post"]
description = "An crossplatform terminal library for manipulating terminals."
repository = "https://github.com/TimonPost/crossterm"
@ -32,21 +32,13 @@ members = [
]
[dependencies]
crossterm_screen = { optional = true, version = "0.1.0" }
crossterm_cursor = { optional = true, version = "0.1.0" }
crossterm_terminal = { optional = true, version = "0.1.0" }
crossterm_style = { optional = true, version = "0.2.0" }
crossterm_input = { optional = true, version = "0.2.1" }
crossterm_utils = { version = "0.1.0" }
crossterm_screen = { optional = true, path = "./crossterm_screen" }
crossterm_cursor = { optional = true, path = "./crossterm_cursor" }
crossterm_terminal = { optional = true, path = "./crossterm_terminal" }
crossterm_style = { optional = true, path = "./crossterm_style" }
crossterm_input = { optional = true, path = "./crossterm_input" }
crossterm_utils = { optional = false, path = "./crossterm_utils" }
[lib]
name = "crossterm"
path = "src/lib.rs"
[[example]]
name = "logging"
path = "examples/program_examples/logging.rs"
[[example]]
name = "command_bar"
path = "examples/program_examples/command_bar.rs"

View File

@ -1,5 +1,5 @@
# Crossterm | cross-platform terminal manipulating library.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm.svg
[l1]: https://crates.io/crates/crossterm
@ -13,6 +13,9 @@
[s3]: https://docs.rs/crossterm/badge.svg
[l3]: https://docs.rs/crossterm/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s6]: https://tokei.rs/b1/github/TimonPost/crossterm?category=code
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -50,13 +53,13 @@ This crate is exists out of five modules who are behind [feature flags](http://a
## Getting Started
This documentation is only for Crossterm version `0.8` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for Crossterm version `0.9` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
Add the Crossterm package to your `Cargo.toml` file.
```
[dependencies]
crossterm = "0.8"
crossterm = "0.9"
```
### Useful Links
@ -71,35 +74,33 @@ crossterm = "0.8"
These are the features from this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Cursor.
- Moving _n_ times Up, Down, Left, Right
- Goto a certain position
- Get cursor position
- Storing the current cursor position and resetting to that stored cursor position later
- Hiding an showing the cursor
- Control over blinking of the terminal cursor (only some terminals are supporting this)
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Cursor
- Moving _n_ times (up, down, left, right)
- Position (set/get)
- Store cursor position and resetting to that later
- Hiding/Showing
- Blinking Cursor (only some terminals are supporting this)
- Styled output
- Foreground color (16 base colors)
- Background color (16 base colors)
- 256 color support (Windows 10 and UNIX only)
- RGB support (Windows 10 and UNIX only)
- Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only)
- Foreground Color (16 base colors)
- Background Color (16 base colors)
- 256 (ANSI) Color Support (Windows 10 and UNIX Only)
- RGB Color Support (Windows 10 and UNIX only)
- Text Attributes: bold, italic, underscore and crossed word and [more](http://atcentra.com/crossterm/styling.html#attributes) (Windows 10 and UNIX only)
- Terminal
- Clearing (all lines, current line, from cursor down and up, until new line)
- Scrolling (Up, down)
- Get the size of the terminal
- Set the size of the terminal
- Alternate screen
- Raw screen
- Exit the current process
- Scrolling (up, down)
- Terminal Size (get/set)
- Alternate Screen
- Raw Screen
- Exit Current Process
- Input
- Read character
- Read line
- Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more)
- Read mouse input events (Press, Release, Position, Button)
- Read key input events (async / sync)
- Read mouse input events (press, release, position, button)
## Examples
These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/TimonPost/crossterm/blob/master/examples/) for more.
@ -280,7 +281,7 @@ let mut input = input();
_Read input events synchronously or asynchronously._
```rust
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let screen = RawScreen::into_raw_mode();
let mut input = input();

View File

@ -0,0 +1,5 @@
# Changes crossterm_cursor 0.2
- Removed `TerminalCursor::from_output()`
# Changes crossterm_cursor 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_cursor"
version = "0.1.0"
version = "0.2.0"
authors = ["T. Post"]
description = "A cross-platform library for moving the terminal cursor."
repository = "https://github.com/TimonPost/crossterm"
@ -12,11 +12,11 @@ readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.1"
winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = "0.1.2"
[dependencies]
crossterm_utils = "0.1.0"
crossterm_utils = { path = "../crossterm_utils" }
[[example]]
name = "cursor"

View File

@ -1,5 +1,5 @@
# Crossterm Cursor | cross-platform cursor movement.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_cursor.svg
[l1]: https://crates.io/crates/crossterm_cursor
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_cursor/badge.svg
[l3]: https://docs.rs/crossterm_cursor/
[s3]: https://docs.rs/crossterm_cursor/badge.svg
[l3]: https://docs.rs/crossterm_cursor/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -34,23 +34,20 @@ When you want to use other modules as well you might want to use crossterm with
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Contributing](#contributing)
- [Authors](#authors)
- [License](#license)
## Getting Started
This documentation is only for `crossterm_cursor` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_cursor` version `0.2`. Also, check out the [examples](examples/cursor.rs) folders with detailed examples for all functionality of this crate.
Add the `crossterm_cursor` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_cursor` = "0.1"
crossterm_cursor = "0.2"
```
And import the crossterm_input modules you want to use.
Import the `crossterm_cursor` modules you want to use.
```rust
extern crate crossterm_cursor;
@ -68,19 +65,18 @@ pub use crossterm_cursor::{cursor, TerminalCursor};
These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Cursor.
- Moving _n_ times Up, Down, Left, Right
- Goto a certain position
- Get cursor position
- Storing the current cursor position and resetting to that stored cursor position later
- Hiding an showing the cursor
- Control over blinking of the terminal cursor (only some terminals are supporting this)
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Cursor
- Moving _n_ times (up, down, left, right)
- Position (set/get)
- Store cursor position and resetting to that later
- Hiding/Showing
- Blinking Cursor (only some terminals are supporting this)
## Examples
Check out the [examples](/examples/) for more information about how to use this crate.
The [examples](./examples) folder has more complete and verbose examples.
```rust
use crossterm_cursor::cursor;
@ -134,21 +130,9 @@ cursor.blink(true)
This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested.
If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it.
## Notice
This library is average stable now, I don't expect it to not to change that much.
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -4,7 +4,7 @@
extern crate crossterm_cursor;
use crossterm_cursor::{cursor, TerminalCursor};
use crossterm_cursor::cursor;
/// Set the cursor to position X: 10, Y: 5 in the terminal.
pub fn goto() {
@ -48,7 +48,7 @@ pub fn move_down() {
}
/// Save and reset cursor position | demonstration..
pub fn safe_and_reset_position() {
pub fn save_and_reset_position() {
let cursor = cursor();
// Goto X: 5 Y: 5

View File

@ -4,22 +4,22 @@
use super::ITerminalCursor;
use crate::sys::get_cursor_position;
use std::io::Write;
use crossterm_utils::{write, write_str, Result, TerminalOutput};
use std::sync::Arc;
use crossterm_utils::Result;
/// This struct is an ANSI implementation for cursor related actions.
pub struct AnsiCursor {}
pub struct AnsiCursor;
impl AnsiCursor {
pub fn new() -> Box<AnsiCursor> {
Box::from(AnsiCursor {})
pub fn new() -> AnsiCursor {
AnsiCursor
}
}
impl ITerminalCursor for AnsiCursor {
fn goto(&self, x: u16, y: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(stdout, format!(csi!("{};{}H"), y + 1, x + 1))?;
fn goto(&self, x: u16, y: u16) -> Result<()> {
write_cout!(format!(csi!("{};{}H"), y + 1, x + 1))?;
Ok(())
}
@ -27,51 +27,51 @@ impl ITerminalCursor for AnsiCursor {
get_cursor_position()
}
fn move_up(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(stdout, format!(csi!("{}A"), count))?;
fn move_up(&self, count: u16) -> Result<()> {
write_cout!(&format!(csi!("{}A"), count))?;
Ok(())
}
fn move_right(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(stdout, format!(csi!("{}C"), count))?;
fn move_right(&self, count: u16) -> Result<()> {
write_cout!(&format!(csi!("{}C"), count))?;
Ok(())
}
fn move_down(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(stdout, format!(csi!("{}B"), count))?;
fn move_down(&self, count: u16) -> Result<()> {
write_cout!(&format!(csi!("{}B"), count))?;
Ok(())
}
fn move_left(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(stdout, format!(csi!("{}D"), count))?;
fn move_left(&self, count: u16) -> Result<()> {
write_cout!(&format!(csi!("{}D"), count))?;
Ok(())
}
fn save_position(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write_str(stdout, csi!("s"))?;
fn save_position(&self) -> Result<()> {
write_cout!(csi!("s"))?;
Ok(())
}
fn reset_position(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write_str(stdout, csi!("u"))?;
fn reset_position(&self) -> Result<()> {
write_cout!(csi!("u"))?;
Ok(())
}
fn hide(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write_str(stdout, csi!("?25l"))?;
fn hide(&self) -> Result<()> {
write_cout!(csi!("?25l"))?;
Ok(())
}
fn show(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write_str(stdout, csi!("?25h"))?;
fn show(&self) -> Result<()> {
write_cout!(csi!("?25h"))?;
Ok(())
}
fn blink(&self, blink: bool, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn blink(&self, blink: bool) -> Result<()> {
if blink {
write_str(stdout, csi!("?12h"))?;
write_cout!(csi!("?12h"))?;
} else {
write_str(stdout, csi!("?12l"))?;
write_cout!(csi!("?12l"))?;
}
Ok(())
}

View File

@ -2,12 +2,11 @@
//! Like: moving the cursor position; saving and resetting the cursor position; hiding showing and control the blinking of the cursor.
use super::*;
use std::sync::Arc;
use crossterm_utils::{Result, TerminalOutput};
use crossterm_utils::Result;
#[cfg(windows)]
use crossterm_utils::get_module;
use crossterm_utils::supports_ansi;
/// Allows you to preform actions with the terminal cursor.
///
@ -23,67 +22,27 @@ use crossterm_utils::get_module;
/// Note that positions of the cursor are 0 -based witch means that the coordinates (cells) starts counting from 0
///
/// Check `/examples/cursor` in the library for more specific examples.
///
/// # Remarks
///
/// When you want to use 'cursor' on 'alternate screen' use the 'crossterm_screen' crate.
pub struct TerminalCursor<'stdout> {
terminal_cursor: Box<ITerminalCursor + Sync + Send>,
stdout: Option<&'stdout Arc<TerminalOutput>>,
pub struct TerminalCursor {
#[cfg(windows)]
cursor: Box<(dyn ITerminalCursor + Sync + Send)>,
#[cfg(unix)]
cursor: AnsiCursor,
}
impl<'stdout> TerminalCursor<'stdout> {
impl TerminalCursor {
/// Create new `TerminalCursor` instance whereon cursor related actions can be performed.
pub fn new() -> TerminalCursor<'stdout> {
#[cfg(target_os = "windows")]
let cursor = get_module::<Box<ITerminalCursor + Sync + Send>>(
WinApiCursor::new(),
AnsiCursor::new(),
)
.unwrap();
pub fn new() -> TerminalCursor {
#[cfg(windows)]
let cursor = if supports_ansi() {
Box::from(AnsiCursor::new()) as Box<(dyn ITerminalCursor + Sync + Send)>
} else {
WinApiCursor::new() as Box<(dyn ITerminalCursor + Sync + Send)>
};
#[cfg(not(target_os = "windows"))]
let cursor = AnsiCursor::new() as Box<ITerminalCursor + Sync + Send>;
#[cfg(unix)]
let cursor = AnsiCursor::new();
TerminalCursor {
terminal_cursor: cursor,
stdout: None,
}
}
/// Create a new instance of `TerminalCursor` whereon cursor related actions could be preformed on the given output.
///
/// # Remarks
///
/// Use this function when you want your terminal to operate with a specific output.
/// This could be useful when you have a screen which is in 'alternate mode',
/// and you want your actions from the `TerminalCursor`, created by this function, to operate on the 'alternate screen'.
///
/// You should checkout the 'crossterm_screen' crate for more information about this.
///
/// # Example
/// ```
/// let screen = Screen::default();
//
/// if let Ok(alternate) = screen.enable_alternate_modes(false) {
/// let terminal = TerminalCursor::from_output(&alternate.screen.stdout);
/// }
/// ```
pub fn from_output(stdout: &'stdout Arc<TerminalOutput>) -> TerminalCursor<'stdout> {
#[cfg(target_os = "windows")]
let cursor = get_module::<Box<ITerminalCursor + Sync + Send>>(
WinApiCursor::new(),
AnsiCursor::new(),
)
.unwrap();
#[cfg(not(target_os = "windows"))]
let cursor = AnsiCursor::new() as Box<ITerminalCursor + Sync + Send>;
TerminalCursor {
terminal_cursor: cursor,
stdout: Some(stdout),
}
TerminalCursor { cursor }
}
/// Goto some position (x,y) in the terminal.
@ -91,7 +50,7 @@ impl<'stdout> TerminalCursor<'stdout> {
/// # Remarks
/// position is 0-based, which means we start counting at 0.
pub fn goto(&self, x: u16, y: u16) -> Result<()> {
self.terminal_cursor.goto(x, y, &self.stdout)
self.cursor.goto(x, y)
}
/// Get current cursor position (x,y) in the terminal.
@ -99,32 +58,30 @@ impl<'stdout> TerminalCursor<'stdout> {
/// # Remarks
/// position is 0-based, which means we start counting at 0.
pub fn pos(&self) -> (u16, u16) {
self.terminal_cursor.pos()
self.cursor.pos()
}
/// Move the current cursor position `n` times up.
pub fn move_up(&mut self, count: u16) -> &mut TerminalCursor<'stdout> {
self.terminal_cursor.move_up(count, &self.stdout).unwrap();
pub fn move_up(&mut self, count: u16) -> &mut TerminalCursor {
self.cursor.move_up(count).unwrap();
self
}
/// Move the current cursor position `n` times right.
pub fn move_right(&mut self, count: u16) -> &mut TerminalCursor<'stdout> {
self.terminal_cursor
.move_right(count, &self.stdout)
.unwrap();
pub fn move_right(&mut self, count: u16) -> &mut TerminalCursor {
self.cursor.move_right(count).unwrap();
self
}
/// Move the current cursor position `n` times down.
pub fn move_down(&mut self, count: u16) -> &mut TerminalCursor<'stdout> {
self.terminal_cursor.move_down(count, &self.stdout).unwrap();
pub fn move_down(&mut self, count: u16) -> &mut TerminalCursor {
self.cursor.move_down(count).unwrap();
self
}
/// Move the current cursor position `n` times left.
pub fn move_left(&mut self, count: u16) -> &mut TerminalCursor<'stdout> {
self.terminal_cursor.move_left(count, &self.stdout).unwrap();
pub fn move_left(&mut self, count: u16) -> &mut TerminalCursor {
self.cursor.move_left(count).unwrap();
self
}
@ -132,22 +89,22 @@ impl<'stdout> TerminalCursor<'stdout> {
///
/// Note that this position is stored program based not per instance of the `Cursor` struct.
pub fn save_position(&self) -> Result<()> {
self.terminal_cursor.save_position(&self.stdout)
self.cursor.save_position()
}
/// Return to saved cursor position
pub fn reset_position(&self) -> Result<()> {
self.terminal_cursor.reset_position(&self.stdout)
self.cursor.reset_position()
}
/// Hide de cursor in the console.
pub fn hide(&self) -> Result<()> {
self.terminal_cursor.hide(&self.stdout)
self.cursor.hide()
}
/// Show the cursor in the console.
pub fn show(&self) -> Result<()> {
self.terminal_cursor.show(&self.stdout)
self.cursor.show()
}
/// Enable or disable blinking of the terminal.
@ -155,11 +112,11 @@ impl<'stdout> TerminalCursor<'stdout> {
/// # Remarks
/// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version.
pub fn blink(&self, blink: bool) -> Result<()> {
self.terminal_cursor.blink(blink, &self.stdout)
self.cursor.blink(blink)
}
}
/// Get a `TerminalCursor` instance whereon cursor related actions can be performed.
pub fn cursor() -> TerminalCursor<'static> {
pub fn cursor() -> TerminalCursor {
TerminalCursor::new()
}

View File

@ -9,16 +9,15 @@ mod cursor;
mod test;
mod ansi_cursor;
#[cfg(target_os = "windows")]
#[cfg(windows)]
mod winapi_cursor;
use self::ansi_cursor::AnsiCursor;
#[cfg(target_os = "windows")]
#[cfg(windows)]
use self::winapi_cursor::WinApiCursor;
pub use self::cursor::{cursor, TerminalCursor};
use crossterm_utils::{Result, TerminalOutput};
use std::sync::Arc;
use crossterm_utils::Result;
///! 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
@ -30,25 +29,25 @@ use std::sync::Arc;
///! so that cursor related actions can be performed on both UNIX and Windows systems.
trait ITerminalCursor: Sync + Send {
/// Goto some location (x,y) in the context.
fn goto(&self, x: u16, y: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn goto(&self, x: u16, y: u16) -> Result<()>;
/// Get the location (x,y) of the current cursor in the context
fn pos(&self) -> (u16, u16);
/// Move cursor n times up
fn move_up(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn move_up(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times to the right.
fn move_right(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn move_right(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times down.
fn move_down(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn move_down(&self, count: u16) -> Result<()>;
/// Move the cursor `n` times left.
fn move_left(&self, count: u16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
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, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn save_position(&self) -> Result<()>;
/// Return to saved cursor position
fn reset_position(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn reset_position(&self) -> Result<()>;
/// Hide the terminal cursor.
fn hide(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn hide(&self) -> Result<()>;
/// Show the terminal cursor
fn show(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn show(&self) -> Result<()>;
/// Enable or disable the blinking of the cursor.
fn blink(&self, blink: bool, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn blink(&self, blink: bool) -> Result<()>;
}

View File

@ -11,7 +11,7 @@ mod winapi_tests {
fn goto_winapi() {
let cursor = WinApiCursor::new();
cursor.goto(5, 5, &None);
cursor.goto(5, 5);
let (x, y) = cursor.pos();
assert_eq!(x, 5);
@ -23,9 +23,9 @@ mod winapi_tests {
let cursor = WinApiCursor::new();
let (x, y) = cursor.pos();
cursor.save_position(&None);
cursor.goto(5, 5, &None);
cursor.reset_position(&None);
cursor.save_position();
cursor.goto(5, 5);
cursor.reset_position();
let (x_saved, y_saved) = cursor.pos();
@ -41,9 +41,9 @@ fn reset_safe_ansi() {
let cursor = AnsiCursor::new();
let (x, y) = cursor.pos();
cursor.save_position(&None);
cursor.goto(5, 5, &None);
cursor.reset_position(&None);
cursor.save_position();
cursor.goto(5, 5);
cursor.reset_position();
let (x_saved, y_saved) = cursor.pos();
@ -56,7 +56,7 @@ fn reset_safe_ansi() {
fn goto_ansi() {
if try_enable_ansi() {
let cursor = AnsiCursor::new();
cursor.goto(5, 5, &None);
cursor.goto(5, 5);
let (x, y) = cursor.pos();
assert_eq!(x, 5);

View File

@ -4,8 +4,7 @@
use super::ITerminalCursor;
use crate::sys::winapi::{Cursor, Handle};
use crossterm_utils::{Result, TerminalOutput};
use std::sync::Arc;
use crossterm_utils::Result;
/// This struct is a windows implementation for cursor related actions.
pub struct WinApiCursor;
@ -17,7 +16,7 @@ impl WinApiCursor {
}
impl ITerminalCursor for WinApiCursor {
fn goto(&self, x: u16, y: u16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn goto(&self, x: u16, y: u16) -> Result<()> {
let cursor = Cursor::new()?;
cursor.goto(x as i16, y as i16)?;
Ok(())
@ -28,51 +27,51 @@ impl ITerminalCursor for WinApiCursor {
cursor.position().unwrap().into()
}
fn move_up(&self, count: u16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn move_up(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos();
self.goto(xpos, ypos - count, _stdout)?;
self.goto(xpos, ypos - count)?;
Ok(())
}
fn move_right(&self, count: u16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn move_right(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos();
self.goto(xpos + count, ypos, _stdout)?;
self.goto(xpos + count, ypos)?;
Ok(())
}
fn move_down(&self, count: u16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn move_down(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos();
self.goto(xpos, ypos + count, _stdout)?;
self.goto(xpos, ypos + count)?;
Ok(())
}
fn move_left(&self, count: u16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn move_left(&self, count: u16) -> Result<()> {
let (xpos, ypos) = self.pos();
self.goto(xpos - count, ypos, _stdout)?;
self.goto(xpos - count, ypos)?;
Ok(())
}
fn save_position(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn save_position(&self) -> Result<()> {
Cursor::save_cursor_pos()?;
Ok(())
}
fn reset_position(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn reset_position(&self) -> Result<()> {
Cursor::reset_to_saved_position()?;
Ok(())
}
fn hide(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn hide(&self) -> Result<()> {
Cursor::from(Handle::current_out_handle()?).set_visibility(false)?;
Ok(())
}
fn show(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn show(&self) -> Result<()> {
Cursor::from(Handle::current_out_handle()?).set_visibility(true)?;
Ok(())
}
fn blink(&self, _blink: bool, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn blink(&self, _blink: bool) -> Result<()> {
Ok(())
}
}

View File

@ -12,7 +12,7 @@ pub fn get_cursor_position() -> (u16, u16) {
}
pub fn pos() -> io::Result<(u16, u16)> {
// if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition.
// if we enable raw modes with screen, this could cause problems if raw mode is already enabled in application.
// I am not completely happy with this approach so feel free to find an other way.
unsafe {

View File

@ -0,0 +1,15 @@
# Changes crossterm_input 0.3
- Removed `TerminalInput::from_output()`
# Changes crossterm_input 0.2.1
- Fixed SyncReade bug.
# Changes crossterm_input 0.2.1
- Introduced SyncReader
# Changes crossterm_input 0.2
- Introduced KeyEvents
- Introduced MouseEvents
# Changes crossterm_input 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_input"
version = "0.2.1"
version = "0.3.0"
authors = ["T. Post"]
description = "A cross-platform library for reading userinput."
repository = "https://github.com/TimonPost/crossterm"
@ -12,20 +12,12 @@ readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["winnt", "winuser"] }
winapi = { version = "0.3.7", features = ["winnt", "winuser"] }
crossterm_winapi = "0.1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2.43"
libc = "0.2.51"
[dependencies]
crossterm_utils = "0.1.0"
crossterm_screen = "0.1.0"
[[example]]
name = "input"
path = "examples/input.rs"
[[example]]
name = "key_events"
path = "examples/key_events.rs"
crossterm_utils = { path = "../crossterm_utils" }
crossterm_screen = { path = "../crossterm_screen" }

View File

@ -1,5 +1,5 @@
# Crossterm Input | cross-platform input reading .
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_input.svg
[l1]: https://crates.io/crates/crossterm_input
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_input/badge.svg
[l3]: https://docs.rs/crossterm_input/
[s3]: https://docs.rs/crossterm_input/badge.svg
[l3]: https://docs.rs/crossterm_input/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -41,20 +41,20 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_input` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_input/examples) folders with detailed examples for all functionalities of this crate.
This documentation is only for `crossterm_input` version `0.3` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_input/examples) folders with detailed examples for all functionalities of this crate.
Add the `crossterm_input` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_input` = "0.2"
crossterm_input = "0.3"
```
And import the `crossterm_input` modules you want to use.
Import the `crossterm_input` modules you want to use.
```rust
extern crate crossterm_input;
pub use crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput};
pub use crossterm_input::{input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput};
```
### Useful Links
@ -62,31 +62,24 @@ pub use crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput};
- [Documentation](https://docs.rs/crossterm_input/)
- [Crates.io](https://crates.io/crates/crossterm_input)
- [Book](http://atcentra.com/crossterm/input.html)
- [Examples](/examples)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Input
- Read character
- Read line
- Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more)
- Read mouse input events (Press, Release, Position, Button)
- Read key input events (async / sync)
- Read mouse input events (press, release, position, button)
- RawScreen (from `crossterm_screen`)
## Examples
The examples folder has more complete and verbose examples, please have a look at that as well.
Good documentation could be found in the [docs][l3] as well in the [book](http://atcentra.com/crossterm/input.html).
_available imports_
```rust
use crossterm_input::{
input, InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, AsyncReader, SyncReader, Screen
};
```
The [examples](./examples) folder has more complete and verbose examples.
_Simple Readings_
```rust
@ -106,7 +99,7 @@ let mut input = input();
_Read input events synchronously or asynchronously._
```rust
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let screen = RawScreen::into_raw_mode();
let mut input = input();
@ -150,17 +143,6 @@ input.disable_mouse_mode().unwrap();
This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested.
If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it.
## Notice
This library is average stable now, I don't expect it to not to change that much.
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
@ -168,4 +150,4 @@ So improving this by correcting these mistakes will help both me and the reader
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -23,7 +23,6 @@ pub fn read_line() {
fn main() {
// un-comment below and run with
// `cargo run --example input`:
read_char();
read_line();
}

View File

@ -1,119 +1,74 @@
extern crate crossterm_input;
extern crate crossterm_screen;
extern crate crossterm_utils;
use crossterm_input::{InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput};
use crossterm_screen::Screen;
use std::{thread, time::Duration};
fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
fn process_input_event(key_event: InputEvent) -> bool {
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Char(c) => match c {
'q' => {
screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap();
return true;
InputEvent::Keyboard(k) => {
match k {
KeyEvent::Char(c) => match c {
'q' => {
println!("The 'q' key is hit and the program is not listening to input anymore.\n\n");
return true;
}
_ => {
println!("{}", format!("'{}' pressed\n\n", c));
}
},
KeyEvent::Alt(c) => {
println!("{}", format!("ALT +'{}' pressed\n\n", c));
}
KeyEvent::Ctrl(c) => {
println!("{}", format!("CTRL +'{}' Pressed\n\n", c));
}
KeyEvent::Esc => {
println!("{}", format!("ESC pressed\n\n"));
}
KeyEvent::F(number) => {
println!("{}", format!("F{} key pressed\n\n", number));
}
KeyEvent::PageUp => {
println!("{}", format!("Page Up\n\n"));
}
KeyEvent::PageDown => {
println!("{}", format!("Page Down\n\n"));
}
KeyEvent::Delete => {
println!("{}", format!("Delete\n\n"));
}
_ => {
screen
.stdout
.write_string(format!("'{}' pressed\n\n", c))
.unwrap();
println!("{}", format!("OTHER: {:?}\n\n", k));
()
}
},
KeyEvent::Alt(c) => {
screen
.stdout
.write_string(format!("ALT +'{}' pressed\n\n", c))
.unwrap();
}
KeyEvent::Ctrl(c) => {
screen
.stdout
.write_string(format!("CTRL +'{}' Pressed\n\n", c))
.unwrap();
}
KeyEvent::Esc => {
screen
.stdout
.write_string(format!("ESC pressed\n\n"))
.unwrap();
}
KeyEvent::F(number) => {
screen
.stdout
.write_string(format!("F{} key pressed\n\n", number))
.unwrap();
}
KeyEvent::PageUp => {
screen.stdout.write_string(format!("Page Up\n\n")).unwrap();
}
KeyEvent::PageDown => {
screen
.stdout
.write_string(format!("Page Down\n\n"))
.unwrap();
}
KeyEvent::Delete => {
screen.stdout.write_string(format!("Delete\n\n")).unwrap();
}
_ => {
screen
.stdout
.write_string(format!("OTHER: {:?}\n\n", k))
.unwrap();
()
}
},
}
InputEvent::Mouse(m) => match m {
MouseEvent::Press(b, x, y) => match b {
MouseButton::Left => {
screen
.stdout
.write_string(format!("left mouse press @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("left mouse press @ {}, {}\n\n", x, y));
}
MouseButton::Right => {
screen
.stdout
.write_string(format!("right mouse press @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("right mouse press @ {}, {}\n\n", x, y));
}
MouseButton::Middle => {
screen
.stdout
.write_string(format!("mid mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelUp => {
screen
.stdout
.write_string(format!("wheel up @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("mid mouse press @ {}, {}\n\n", x, y));
}
MouseButton::WheelUp => println!("{}", format!("wheel up @ {}, {}\n\n", x, y)),
MouseButton::WheelDown => {
screen
.stdout
.write_string(format!("wheel down @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("wheel down @ {}, {}\n\n", x, y));
}
},
MouseEvent::Release(x, y) => {
screen
.stdout
.write_string(format!("mouse released @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("mouse released @ {}, {}\n\n", x, y));
}
MouseEvent::Hold(x, y) => {
screen
.stdout
.write_string(format!("dragging @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("dragging @ {}, {}\n\n", x, y));
}
_ => {
screen.stdout.write_str("Unknown mouse event").unwrap();
println!("{}", "Unknown mouse event");
}
},
_ => println!("Unknown!"),
@ -124,18 +79,18 @@ fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
pub fn read_asynchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let _ = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
let input = input();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut async_stdin = input.read_async();
let mut stdin = input.read_async();
loop {
if let Some(key_event) = async_stdin.next() {
if process_input_event(key_event, &screen) {
if let Some(key_event) = stdin.next() {
if process_input_event(key_event) {
break;
}
}
@ -148,9 +103,9 @@ pub fn read_asynchronously() {
pub fn read_synchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
let _ = Screen::new(true);
let input = TerminalInput::from_output(&screen.stdout);
let input = input();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
@ -161,7 +116,7 @@ pub fn read_synchronously() {
let event = sync_stdin.next();
if let Some(key_event) = event {
if process_input_event(key_event, &screen) {
if process_input_event(key_event) {
break;
}
}
@ -174,7 +129,6 @@ pub fn read_synchronously() {
fn main() {
// un-comment below and run with
// `cargo run --example key_events`:
read_synchronously();
// read_asynchronously();
// read_synchronously();
// read_asynchronously();
}

View File

@ -2,23 +2,7 @@
//! Like reading a line, reading a character and reading asynchronously.
use super::*;
use std::io::{Error, ErrorKind};
use std::iter::Iterator;
use std::str;
use crossterm_utils::TerminalOutput;
/// Allows you to preform actions with the < option >.
///
/// # Features:
///
/// - features
///
/// Check `/examples/` in the library for more specific examples.
///
/// # Remarks
///
/// When you want to use '< name >' on 'alternate screen' use the 'crossterm_screen' crate.
use std::{io, str};
/// Allows you to read user input.
///
@ -28,68 +12,34 @@ use crossterm_utils::TerminalOutput;
/// - Read line
/// - Read async
/// - Read async until
/// - Read sync
/// - Wait for key event (terminal pause)
///
/// Check `/examples/` in the library for more specific examples.
///
/// # Remarks
///
/// When you want to use 'input' on 'alternate screen' use the 'crossterm_screen' crate.
pub struct TerminalInput<'stdout> {
terminal_input: Box<ITerminalInput + Sync + Send>,
stdout: Option<&'stdout Arc<TerminalOutput>>,
pub struct TerminalInput {
#[cfg(windows)]
input: WindowsInput,
#[cfg(unix)]
input: UnixInput,
}
impl<'stdout> TerminalInput<'stdout> {
impl TerminalInput {
/// Create a new instance of `TerminalInput` whereon input related actions could be preformed.
pub fn new() -> TerminalInput<'stdout> {
#[cfg(target_os = "windows")]
let input = Box::from(WindowsInput::new());
pub fn new() -> TerminalInput {
#[cfg(windows)]
let input = WindowsInput::new();
#[cfg(not(target_os = "windows"))]
let input = Box::from(UnixInput::new());
#[cfg(unix)]
let input = UnixInput::new();
TerminalInput {
terminal_input: input,
stdout: None,
}
}
/// Create a new instance of `TerminalInput` whereon input related actions could be preformed.
///
/// # Remarks
///
/// Use this function when you want your terminal to operate with a specific output.
/// This could be useful when you have a screen which is in 'alternate mode',
/// and you want your actions from the `TerminalInput`, created by this function, to operate on the 'alternate screen'.
///
/// You should checkout the 'crossterm_screen' crate for more information about this.
/// # Example
/// ```rust
/// let screen = Screen::default();
//
/// if let Ok(alternate) = screen.enable_alternate_modes(false) {
/// let terminal = TerminalInput::from_output(&alternate.screen.stdout);
/// }
/// ```
pub fn from_output(stdout: &'stdout Arc<TerminalOutput>) -> TerminalInput<'stdout> {
#[cfg(target_os = "windows")]
let input = Box::from(WindowsInput::new());
#[cfg(not(target_os = "windows"))]
let input = Box::from(UnixInput::new());
TerminalInput {
terminal_input: input,
stdout: Some(stdout),
}
TerminalInput { input }
}
/// Read one line from the user input.
///
/// # Remark
/// This function is not work when raw screen is turned on.
/// When you do want to read a line in raw mode please, checkout `read_async` or `read_async_until`.
/// When you do want to read a line in raw mode please, checkout `read_async`, `read_async_until` or `read_sync`.
/// Not sure what 'raw mode' is, checkout the 'crossterm_screen' crate.
///
/// # Example
@ -101,12 +51,6 @@ impl<'stdout> TerminalInput<'stdout> {
/// }
/// ```
pub fn read_line(&self) -> io::Result<String> {
if let Some(stdout) = self.stdout {
if stdout.is_in_raw_mode {
return Err(Error::new(ErrorKind::Other, "Crossterm does not support readline in raw mode this should be done instead whit `read_async` or `read_async_until`"));
}
}
let mut rv = String::new();
io::stdin().read_line(&mut rv)?;
let len = rv.trim_right_matches(&['\r', '\n'][..]).len();
@ -125,7 +69,7 @@ impl<'stdout> TerminalInput<'stdout> {
/// }
/// ```
pub fn read_char(&self) -> io::Result<char> {
self.terminal_input.read_char(&self.stdout)
self.input.read_char()
}
/// Read the input asynchronously, which means that input events are gathered on the background and will be queued for you to read.
@ -146,7 +90,7 @@ impl<'stdout> TerminalInput<'stdout> {
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_async(&self) -> AsyncReader {
self.terminal_input.read_async()
self.input.read_async()
}
/// Read the input asynchronously until a certain character is hit, which means that input events are gathered on the background and will be queued for you to read.
@ -167,7 +111,7 @@ impl<'stdout> TerminalInput<'stdout> {
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_until_async(&self, delimiter: u8) -> AsyncReader {
self.terminal_input.read_until_async(delimiter)
self.input.read_until_async(delimiter)
}
/// Read the input synchronously from the user, which means that reading call wil be blocking ones.
@ -181,7 +125,7 @@ impl<'stdout> TerminalInput<'stdout> {
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_sync(&self) -> SyncReader {
self.terminal_input.read_sync()
self.input.read_sync()
}
/// Enable mouse events to be captured.
@ -190,20 +134,20 @@ impl<'stdout> TerminalInput<'stdout> {
///
/// # Remark
/// - Mouse events will be send over the reader created with `read_async`, `read_async_until`, `read_sync`.
pub fn enable_mouse_mode(&self) -> io::Result<()> {
self.terminal_input.enable_mouse_mode(&self.stdout)
pub fn enable_mouse_mode(&self) -> Result<()> {
self.input.enable_mouse_mode()
}
/// Disable mouse events to be captured.
///
/// When disabling mouse input you won't be able to capture, mouse movements, pressed buttons and locations anymore.
pub fn disable_mouse_mode(&self) -> io::Result<()> {
self.terminal_input.disable_mouse_mode(&self.stdout)
pub fn disable_mouse_mode(&self) -> Result<()> {
self.input.disable_mouse_mode()
}
}
/// Get a `TerminalInput` instance whereon input related actions can be performed.
pub fn input<'stdout>() -> TerminalInput<'stdout> {
pub fn input() -> TerminalInput {
TerminalInput::new()
}
@ -212,7 +156,10 @@ pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
where
I: Iterator<Item = u8>,
{
let error = Error::new(ErrorKind::Other, "Could not parse an event");
let error = ErrorKind::IoError(io::Error::new(
io::ErrorKind::Other,
"Could not parse an event",
));
let input_event = match item {
b'\x1B' => {
let a = iter.next();
@ -426,10 +373,10 @@ fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
where
I: Iterator<Item = u8>,
{
let error = Err(Error::new(
ErrorKind::Other,
let error = Err(ErrorKind::IoError(io::Error::new(
io::ErrorKind::Other,
"Input character is not valid UTF-8",
));
)));
if c.is_ascii() {
Ok(c as char)

View File

@ -3,28 +3,27 @@
mod input;
#[cfg(not(target_os = "windows"))]
#[cfg(unix)]
mod unix_input;
#[cfg(target_os = "windows")]
#[cfg(windows)]
mod windows_input;
#[cfg(not(target_os = "windows"))]
#[cfg(unix)]
pub use self::unix_input::SyncReader;
#[cfg(not(target_os = "windows"))]
#[cfg(unix)]
use self::unix_input::UnixInput;
#[cfg(target_os = "windows")]
#[cfg(windows)]
pub use self::windows_input::SyncReader;
#[cfg(target_os = "windows")]
#[cfg(windows)]
use self::windows_input::WindowsInput;
use self::input::parse_event;
pub use self::input::{input, TerminalInput};
use std::io::{self, Result};
use crossterm_utils::{ErrorKind, Result};
use std::io;
use std::sync::{mpsc, Arc};
use crossterm_utils::TerminalOutput;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender};
use std::thread;
@ -39,15 +38,15 @@ use std::thread;
/// Unix is using the 'TTY' and windows is using 'libc' C functions to read the input.
trait ITerminalInput {
/// Read one character from the user input
fn read_char(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<char>;
fn read_char(&self) -> io::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;
fn enable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()>;
fn disable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()>;
fn enable_mouse_mode(&self) -> Result<()>;
fn disable_mouse_mode(&self) -> Result<()>;
}
/// Enum to specify which input event has occurred.

View File

@ -1,11 +1,11 @@
//! This is a UNIX specific implementation for input related action.
use super::*;
use crate::sys::unix::{get_tty, read_char, read_char_raw};
use crate::sys::unix::{get_tty, read_char_raw};
use crossterm_utils::{csi, write, TerminalOutput};
use crossterm_utils::{csi, write_cout, Result};
use std::char;
use std::io::Read;
use std::io::{Read, Write};
pub struct UnixInput;
@ -16,17 +16,8 @@ impl UnixInput {
}
impl ITerminalInput for UnixInput {
fn read_char(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<char> {
let is_raw_screen = match stdout {
Some(output) => output.is_in_raw_mode,
None => false,
};
if is_raw_screen {
read_char_raw()
} else {
read_char()
}
fn read_char(&self) -> io::Result<char> {
read_char_raw()
}
fn read_async(&self) -> AsyncReader {
@ -43,12 +34,6 @@ impl ITerminalInput for UnixInput {
}))
}
fn read_sync(&self) -> SyncReader {
SyncReader {
bytes: Box::new(get_tty().unwrap().bytes().flatten()),
}
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| {
for byte in get_tty().unwrap().bytes() {
@ -63,31 +48,32 @@ impl ITerminalInput for UnixInput {
}))
}
fn enable_mouse_mode(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
write(
stdout,
format!(
"{}h{}h{}h{}h",
csi!("?1000"),
csi!("?1002"),
csi!("?1015"),
csi!("?1006")
),
)?;
fn read_sync(&self) -> SyncReader {
SyncReader {
source: Box::from(get_tty().unwrap()),
leftover: None,
}
}
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, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
write(
stdout,
format!(
"{}l{}l{}l{}l",
csi!("?1006"),
csi!("?1015"),
csi!("?1002"),
csi!("?1000")
),
)?;
fn disable_mouse_mode(&self) -> Result<()> {
write_cout!(&format!(
"{}l{}l{}l{}l",
csi!("?1006"),
csi!("?1015"),
csi!("?1002"),
csi!("?1000")
))?;
Ok(())
}
}
@ -98,7 +84,8 @@ impl ITerminalInput for UnixInput {
///
/// If you don't want to block your calls use [AsyncReader](./LINK), which will read input on the background and queue it for you to read.
pub struct SyncReader {
bytes: Box<Iterator<Item = u8>>,
source: Box<std::fs::File>,
leftover: Option<u8>,
}
impl Iterator for SyncReader {
@ -108,16 +95,50 @@ impl Iterator for SyncReader {
/// If there are no keys pressed this will be a blocking call until there are.
/// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.`
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.bytes.as_mut();
match iterator.next() {
Some(byte) => {
if let Ok(event) = parse_event(byte, &mut iterator) {
Some(event)
// TODO: Currently errors are consumed and converted to a `NONE` maybe we should'nt be doing this?
let source = &mut self.source;
if let Some(c) = self.leftover {
// we have a leftover byte, use it
self.leftover = None;
if let Ok(e) = parse_event(c, &mut source.bytes().flatten()) {
return Some(e);
} else {
return None;
}
}
// Here we read two bytes at a time. We need to distinguish between single ESC key presses,
// and escape sequences (which start with ESC or a x1B byte). The idea is that if this is
// an escape sequence, we will read multiple bytes (the first byte being ESC) but if this
// is a single ESC keypress, we will only read a single byte.
let mut buf = [0u8; 2];
let res = match source.read(&mut buf) {
Ok(0) => return None,
Ok(1) => match buf[0] {
b'\x1B' => return Some(InputEvent::Keyboard(KeyEvent::Esc)),
c => {
if let Ok(e) = parse_event(c, &mut source.bytes().flatten()) {
return Some(e);
} else {
return None;
}
}
},
Ok(2) => {
let option_iter = &mut Some(buf[1]).into_iter();
let iter = option_iter.map(|c| Ok(c)).chain(source.bytes());
if let Ok(e) = parse_event(buf[0], &mut source.bytes().flatten()) {
self.leftover = option_iter.next();
Some(e)
} else {
None
}
}
None => None,
}
Ok(_) => unreachable!(),
Err(_) => return None, /* maybe we should not throw away the error?*/
};
res
}
}

View File

@ -2,26 +2,26 @@
use super::*;
use crossterm_utils::TerminalOutput;
use crossterm_winapi::{
ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
MouseEvent,
};
use winapi::um::wincon::{
LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED,
};
use winapi::um::winnt::INT;
use winapi::um::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 std::thread;
use std::{char, io};
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 std::sync::atomic::Ordering;
use std::time::Duration;
use std::{char, io, thread};
pub struct WindowsInput;
@ -37,20 +37,9 @@ const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
static mut ORIG_MODE: u32 = 0;
impl ITerminalInput for WindowsInput {
fn read_char(&self, stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<char> {
let is_raw_screen = match stdout {
Some(output) => output.is_in_raw_mode,
None => false,
};
fn read_char(&self) -> io::Result<char> {
// _getwch is without echo and _getwche is with echo
let pressed_char = unsafe {
if is_raw_screen {
_getwch()
} else {
_getwche()
}
};
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 {
@ -87,10 +76,6 @@ impl ITerminalInput for WindowsInput {
}))
}
fn read_sync(&self) -> SyncReader {
SyncReader {}
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in into_virtual_terminal_sequence().unwrap().1 {
@ -107,7 +92,11 @@ impl ITerminalInput for WindowsInput {
}))
}
fn enable_mouse_mode(&self, __stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
fn read_sync(&self) -> SyncReader {
SyncReader
}
fn enable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
unsafe {
@ -117,9 +106,10 @@ impl ITerminalInput for WindowsInput {
Ok(())
}
fn disable_mouse_mode(&self, __stdout: &Option<&Arc<TerminalOutput>>) -> io::Result<()> {
fn disable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
mode.set_mode(unsafe { ORIG_MODE })
mode.set_mode(unsafe { ORIG_MODE })?;
Ok(())
}
}

View File

@ -11,4 +11,4 @@ pub use self::input::{
input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput,
};
pub use self::crossterm_screen::Screen;
pub use self::crossterm_screen::{IntoRawMode, RawScreen};

View File

@ -7,66 +7,10 @@ use std::os::unix::io::AsRawFd;
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> io::Result<fs::File> {
let mut tty_f: fs::File = unsafe { ::std::mem::zeroed() };
let _fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::File::open("/dev/tty")?;
tty_f.as_raw_fd()
}
};
Ok(tty_f)
}
pub fn read_char() -> io::Result<char> {
let mut buf = [0u8; 20];
let fd = unix::into_raw_mode()?;
// read input and convert it to char
let rv = unsafe {
let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20);
if read < 0 {
Err(io::Error::last_os_error())
} else if buf[0] == b'\x03' {
Err(io::Error::new(
io::ErrorKind::Interrupted,
"read interrupted",
))
} else {
let mut pressed_char = Ok(' ');
if let Ok(s) = ::std::str::from_utf8(&buf[..read as usize]) {
if let Some(c) = s.chars().next() {
pressed_char = Ok(c);
}
} else {
pressed_char = Err(io::Error::new(
io::ErrorKind::Interrupted,
"Could not parse char to utf8 char",
));
}
pressed_char
}
};
unix::disable_raw_mode()?;
// if the user hit ^C we want to signal SIGINT to outselves.
if let Err(ref err) = rv {
if err.kind() == io::ErrorKind::Interrupted {
unsafe {
libc::raise(libc::SIGINT);
}
}
}
rv
fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
}
fn get_tty_fd() -> io::Result<i32> {

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_screen"
version = "0.1.0"
version = "0.2.0"
authors = ["T. Post"]
description = "A cross-platform library for raw and alternate screen."
repository = "https://github.com/TimonPost/crossterm"
@ -12,8 +12,8 @@ readme = "README.md"
edition = "2018"
[dependencies]
crossterm_utils = "0.1.0"
crossterm_utils = { path = "../crossterm_utils" }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["minwindef", "wincon"] }
crossterm_winapi = "0.1.1"
winapi = { version = "0.3.7", features = ["minwindef", "wincon"] }
crossterm_winapi = "0.1.2"

View File

@ -1,5 +1,5 @@
# Crossterm Screen | cross-platform alternate, raw screen.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_screen.svg
[l1]: https://crates.io/crates/crossterm_screen
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_screen/badge.svg
[l3]: https://docs.rs/crossterm_screen/
[s3]: https://docs.rs/crossterm_screen/badge.svg
[l3]: https://docs.rs/crossterm_screen/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -43,23 +43,23 @@ In case you are wondering what 'alternate' or 'raw' screen is, you could checkou
## Getting Started
This documentation is only for `crossterm_screen` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md).
Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_screen/examples) folders with detailed examples for all functionality of this crate
This documentation is only for `crossterm_screen` version `0.2`.
Also, check out the [examples](./examples) folders with detailed examples for all functionality of this crate
and the [book](http://atcentra.com/crossterm/screen.html) for more information about how to use the alternate or raw screen options.
Add the `crossterm_screen` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_screen` = "0.1"
crossterm_screen = "0.2"
```
And import the `crossterm_screen` modules you want to use.
```rust
extern crate crossterm_screen;
pub use crossterm_screen::{AlternateScreen, RawScreen, Screen};
pub use crossterm_screen::{AlternateScreen, RawScreen};
```
### Useful Links
@ -67,15 +67,15 @@ pub use crossterm_screen::{AlternateScreen, RawScreen, Screen};
- [Documentation](https://docs.rs/crossterm_screen/)
- [Crates.io](https://crates.io/crates/crossterm_screen)
- [Book](http://atcentra.com/crossterm/screen.html)
- [Examples](/examples)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Alternate screen
- Raw screen
@ -83,10 +83,9 @@ Planned features:
- make is possible to switch between multiple buffers.
## Examples
Check out the [examples](/examples/) for more information about how to use this crate.
The [examples](./examples) folder has more complete and verbose examples.
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
@ -100,21 +99,8 @@ Check out the [examples](/examples/) for more information about how to use this
This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested.
If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it.
## Notice
This library is average stable now, I don't expect it to not to change that much.
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,16 +1,14 @@
extern crate crossterm_screen;
use crossterm_screen::Screen;
use crossterm_screen::AlternateScreen;
use std::io::{stdout, Write};
use std::{thread, time};
/// print wait screen on alternate screen, then switch back.
pub fn print_wait_screen_on_alternate_window() {
let screen = Screen::default();
// move to alternate screen, 'false' means if the alternate screen should be in raw modes.
if let Ok(alternate) = screen.enable_alternate_modes(false) {
if let Ok(alternate) = AlternateScreen::to_alternate(false) {
// do some stuff on the alternate screen.
} // <- alternate screen will be disabled when dropped.
}

View File

@ -1,16 +1,14 @@
extern crate crossterm_screen;
use crossterm_screen::Screen;
use crossterm_screen::{IntoRawMode, RawScreen};
use std::io::{stdout, Write};
use std::{thread, time};
pub fn raw_modes() {
// create a Screen instance who operates on the default output; io::stdout().
let screen = Screen::default();
// create a Screen instance who operates on the default output; io::stdout(). By passing in 'true' we make this screen 'raw'
let screen = Screen::new(true);
let screen = RawScreen::into_raw_mode();
let screen = stdout().into_raw_mode();
drop(screen); // <-- by dropping the screen raw modes will be disabled.
// raw screen will be disabled when it goes out of scope.
}

View File

@ -12,4 +12,4 @@ extern crate crossterm_winapi;
mod screen;
mod sys;
pub use self::screen::{AlternateScreen, RawScreen, Screen};
pub use self::screen::{AlternateScreen, IntoRawMode, RawScreen};

View File

@ -8,12 +8,11 @@
#[cfg(windows)]
use crate::sys::winapi::ToAlternateScreenCommand;
#[cfg(windows)]
use crossterm_utils::get_module;
use crossterm_utils::supports_ansi;
use crate::sys::{self, IAlternateScreenCommand};
use super::{RawScreen, Screen, TerminalOutput};
use std::convert::From;
use super::RawScreen;
use std::io;
/// With this type you will be able to switch to alternate screen and back to main screen.
@ -21,16 +20,14 @@ use std::io;
///
/// Although this type is available for you to use I would recommend using `Screen` instead.
pub struct AlternateScreen {
command: Box<IAlternateScreenCommand + Sync + Send>,
pub screen: Screen,
#[cfg(windows)]
command: Box<(dyn IAlternateScreenCommand + Sync + Send)>,
#[cfg(unix)]
command: sys::ToAlternateScreenCommand,
raw_screen: Option<RawScreen>,
}
impl AlternateScreen {
/// Create new instance of alternate screen.
pub fn new(command: Box<IAlternateScreenCommand + Sync + Send>, screen: Screen) -> Self {
AlternateScreen { command, screen }
}
/// Switch to alternate screen. This function will return an `AlternateScreen` instance if everything went well this type will give you control over the `AlternateScreen`.
///
/// The bool specifies whether the screen should be in raw mode or not.
@ -40,35 +37,38 @@ impl AlternateScreen {
/// The alternate buffer is exactly the dimensions of the window, without any scrollback region.
/// For an example of this behavior, consider when vim is launched from bash.
/// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged.
pub fn to_alternate_screen(
stdout: TerminalOutput,
raw_mode: bool,
) -> io::Result<AlternateScreen> {
#[cfg(target_os = "windows")]
let command = get_module::<Box<IAlternateScreenCommand + Sync + Send>>(
Box::from(ToAlternateScreenCommand::new()),
Box::from(sys::ToAlternateScreenCommand::new()),
)
.unwrap();
pub fn to_alternate(raw_mode: bool) -> io::Result<AlternateScreen> {
#[cfg(windows)]
let command = if supports_ansi() {
Box::from(ToAlternateScreenCommand::new())
as Box<(dyn IAlternateScreenCommand + Sync + Send)>
} else {
Box::from(sys::ToAlternateScreenCommand::new())
as Box<(dyn IAlternateScreenCommand + Sync + Send)>
};
#[cfg(not(target_os = "windows"))]
let command = Box::from(sys::ToAlternateScreenCommand::new());
#[cfg(unix)]
let command = sys::ToAlternateScreenCommand::new();
let mut stdout = stdout;
command.enable(&mut stdout)?;
let screen = Screen::from(stdout);
command.enable()?;
if raw_mode {
RawScreen::into_raw_mode()?;
let raw_screen = RawScreen::into_raw_mode()?;
return Ok(AlternateScreen {
command,
raw_screen: Some(raw_screen),
});
}
Ok(AlternateScreen::new(command, screen))
Ok(AlternateScreen {
command,
raw_screen: None,
})
}
/// Switch the alternate screen back to main screen.
pub fn to_main_screen(&self) -> io::Result<()> {
self.command.disable(&self.screen.stdout)?;
pub fn to_main(&self) -> io::Result<()> {
self.command.disable()?;
Ok(())
}
}
@ -76,6 +76,6 @@ impl AlternateScreen {
impl Drop for AlternateScreen {
/// This will switch back to main screen on drop.
fn drop(&mut self) {
self.to_main_screen().unwrap();
self.to_main().unwrap();
}
}

View File

@ -3,10 +3,6 @@
mod alternate;
mod raw;
mod screen;
use crossterm_utils::TerminalOutput;
pub use self::alternate::AlternateScreen;
pub use self::raw::RawScreen;
pub use self::screen::Screen;
pub use self::raw::{IntoRawMode, RawScreen};

View File

@ -15,36 +15,72 @@
//! With these modes you can easier design the terminal screen.
use crate::sys;
use std::io;
use std::io::{self, Stdout, Write};
/// A wrapper for the raw terminal state. Which can be used to write to.
///
/// Although this type is available for you to use I would recommend using `Screen` instead.
/// Note that when you want to use input and raw mode you should use `Screen`.
pub struct RawScreen;
/// Please take in mind that if this type drops the raw screen will be undone, to prevent this behaviour call `disable_drop`.
pub struct RawScreen {
drop: bool,
}
impl RawScreen {
/// Put terminal in raw mode.
pub fn into_raw_mode() -> io::Result<()> {
#[cfg(not(target_os = "windows"))]
pub fn into_raw_mode() -> io::Result<RawScreen> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(target_os = "windows")]
#[cfg(windows)]
let mut command = sys::winapi::RawModeCommand::new();
let _result = command.enable();
command.enable()?;
Ok(())
Ok(RawScreen { drop: true })
}
/// Put terminal back in original modes.
pub fn disable_raw_modes() -> io::Result<()> {
#[cfg(not(target_os = "windows"))]
pub fn disable_raw_mode() -> io::Result<()> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(target_os = "windows")]
#[cfg(windows)]
let command = sys::winapi::RawModeCommand::new();
command.disable()?;
Ok(())
}
/// This will disable the drop logic of this type, which means that the rawscreen will not be disabled when this instance goes out of scope.
pub fn disable_drop(&mut self) {
self.drop = false;
}
}
/// Types which can be converted into "raw mode".
///
/// # Why is this type defined on writers and not readers?
///
/// 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.
pub trait IntoRawMode: Write + Sized {
/// Switch to raw mode.
///
/// Raw mode means that stdin won't be printed (it will instead have to be written manually by
/// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can
/// read from stdin one byte of a time). The output is neither modified in any way.
fn into_raw_mode(self) -> io::Result<RawScreen>;
}
impl IntoRawMode for Stdout {
fn into_raw_mode(self) -> io::Result<RawScreen> {
RawScreen::into_raw_mode()?;
// this make's sure that raw screen will be disabled when it goes out of scope.
Ok(RawScreen { drop: true })
}
}
impl Drop for RawScreen {
fn drop(&mut self) {
if self.drop == true {
RawScreen::disable_raw_mode().unwrap();
}
}
}

View File

@ -1,182 +0,0 @@
use super::{AlternateScreen, RawScreen};
use crossterm_utils::TerminalOutput;
use std::io::Result;
use std::io::Write;
use std::sync::Arc;
/// This type represents a screen which could be in normal, raw and alternate modes.
///
/// Let's talk about the different modes a bit:
///
/// - Alternate modes:
///
/// *Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them.
/// The alternate buffer is exactly the dimensions of the window, without any scrollback region.
/// For an example of this behavior, consider when vim is launched from bash.
/// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged.
///
/// - RawModes
/// - No line buffering.
/// Normally the terminals use line buffering. This means that the input will be sent to the terminal line by line.
/// With raw mode the input will send one byte at a time.
/// - Input
/// All input has to be written manually by the programmer.
/// - Characters
/// The characters are not processed by the terminal driver but are sent straight through.
/// Special character have no meaning, like backspace will not be interpreted as backspace but instead will be directly sent to the terminal.
/// - Escape characters
/// Note that in raw modes `\n` `\r` will move to the new line but the cursor will be at the same position as before on the new line therefor use `\n\r` to start at the new line at the first cell.
///
/// You have to make sure that you pass the correct `Screen` to the modules `cursor, terminal, color, input, style`.
/// If you switch to alternate screen modes you will get some `Screen` handle back. This `Screen` handle represents the alternate screen.
/// Once you want to do coloring or such you need to pass the `Screen` handle the library so that it could be used for coloring on the right screen.
///
/// # Example
/// ```rust
/// // create default screen (not raw).
/// let screen = Screen::default();
///
/// // create raw screen.
/// let mut screen = Screen::new(true);
///
/// // create a `Screen` with raw modes disabled.
/// let screen = Screen::new(false);
///
/// // create 'raw alternate screen' from normal screen.
/// if let Ok(alternate_screen) = screen.enable_alternate_modes(true)
/// {
/// // 'alternate screen' is an instance which you should use when you want your actions like: coloring and cursor movement happening at the alternate screen.
/// // For that you can use `Crossterm::from_screen(alternate.screen)` so that all modules like: cursor, input, terminal will be executed on alternate screen.
/// let crossterm = Crossterm::from_screen(&alternate_screen.screen);
/// crossterm.cursor();
/// crossterm.terminal();
///
/// // If you want access modules directly without the `Crossterm` type. You should do the following:
/// let cursor = crossterm::cursor::from_screen(&alternate_screen.screen);
/// let terminal = crossterm::terminal::from_screen(&alternate_screen.screen);
/// let input = crossterm::input::from_screen(&alternate_screen.screen);
/// }
/// ```
/// # Remarks
/// Note that using `Screen` is preferred over manually using `AlternateScreen` or `RawScreen`.
pub struct Screen {
buffer: Vec<u8>,
pub stdout: Arc<TerminalOutput>,
drop: bool,
}
impl Screen {
/// Create a new instance of the Screen also specify if the current screen should be in raw mode or normal mode.
/// If you are not sure what raw mode is then passed false or use the `Screen::default()` to create an instance.
pub fn new(raw_mode: bool) -> Screen {
if raw_mode {
let screen = Screen {
stdout: Arc::new(TerminalOutput::new(true)),
buffer: Vec::new(),
drop: true,
};
RawScreen::into_raw_mode().unwrap();
return screen;
}
Screen::default()
}
/// Switch to alternate screen. This function will return an `AlternateScreen` instance. If everything went well this type will give you control over the `AlternateScreen`.
///
/// The bool 'raw_mode' specifies whether the alternate screen should be raw mode or not.
///
/// # What is Alternate screen?
/// *Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them.
/// The alternate buffer is exactly the dimensions of the window, without any scrollback region.
/// For an example of this behavior, consider when vim is launched from bash.
/// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged.
pub fn enable_alternate_modes(&self, raw_mode: bool) -> Result<AlternateScreen> {
let stdout = TerminalOutput::new(raw_mode);
let alternate_screen = AlternateScreen::to_alternate_screen(stdout, raw_mode)?;
Ok(alternate_screen)
}
/// Write buffer to an internal buffer. When you want to write the buffer to screen use `flush_buf()`.
///
/// This function is useful if you want to build up some output and when you are ready you could flush the output to the screen.
///
/// # Example
/// ```
/// // write some text to the internal buffer of this type. Note that this will not be printed until you call `flush_buf`
/// let screen = Screen::default();
/// screen.write_buf(b"Some text");
/// screen.write_buf(b"Some more text");
/// screen.write_buf(b"Some more text");
/// ```
pub fn write_buf(&mut self, buf: &[u8]) -> Result<usize> {
self.buffer.write(buf)
}
/// Flush the internal buffer to the screen.
pub fn flush_buf(&mut self) -> Result<()> {
self.stdout.write_buf(&self.buffer)?;
self.stdout.flush()?;
self.buffer.clear();
Ok(())
}
/// This will disable the drop which will cause raw modes not to be undone on the drop of `Screen`.
pub fn disable_drop(&mut self) {
self.drop = false;
}
}
impl From<TerminalOutput> for Screen {
/// Create a screen with the given `Stdout`
fn from(stdout: TerminalOutput) -> Self {
Screen {
stdout: Arc::new(stdout),
buffer: Vec::new(),
drop: true,
}
}
}
impl From<Arc<TerminalOutput>> for Screen {
/// Create a screen with the given 'Arc<Stdout>'
fn from(stdout: Arc<TerminalOutput>) -> Self {
Screen {
stdout,
buffer: Vec::new(),
drop: true,
}
}
}
impl Default for Screen {
/// Create a new screen which will not be in raw mode or alternate mode.
fn default() -> Self {
Screen {
stdout: Arc::new(TerminalOutput::new(false)),
buffer: Vec::new(),
drop: true,
}
}
}
impl Drop for Screen {
/// If the current screen is in raw mode we need to disable it when the instance goes out of scope.
fn drop(&mut self) {
if self.stdout.is_in_raw_mode && self.drop {
RawScreen::disable_raw_modes().unwrap();
}
}
}
impl Write for Screen {
fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.stdout.write_buf(buf)
}
fn flush(&mut self) -> Result<()> {
self.stdout.flush()
}
}

View File

@ -4,9 +4,8 @@ pub mod unix;
#[cfg(windows)]
pub mod winapi;
use crossterm_utils::TerminalOutput;
use std::io;
use crossterm_utils::Result;
use std::io::Write;
/// This command is used for switching to alternate screen and back to main screen.
pub struct ToAlternateScreenCommand;
@ -19,37 +18,20 @@ impl ToAlternateScreenCommand {
impl IAlternateScreenCommand for ToAlternateScreenCommand {
/// enable alternate screen.
fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()> {
stdout.write_str(csi!("?1049h"))?;
fn enable(&self) -> Result<()> {
write_cout!(csi!("?1049h")).unwrap();
Ok(())
}
/// disable alternate screen.
fn disable(&self, stdout: &TerminalOutput) -> io::Result<()> {
stdout.write_str(csi!("?1049l"))?;
fn disable(&self) -> Result<()> {
write_cout!(csi!("?1049l"))?;
Ok(())
}
}
/// This trait provides a way to execute some state changing commands.
pub trait IStateCommand {
fn execute(&mut self) -> io::Result<()>;
fn undo(&mut self) -> io::Result<()>;
}
pub trait IEnableAnsiCommand {
fn enable(&self) -> io::Result<bool>;
fn disable(&self) -> io::Result<()>;
}
// This trait provides an interface for switching to alternate screen and back.
pub trait IAlternateScreenCommand: Sync + Send {
fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>;
fn disable(&self, stdout: &TerminalOutput) -> io::Result<()>;
}
// This trait provides an interface for switching to raw mode and back.
pub trait IRawScreenCommand: Sync + Send {
fn enable(&mut self) -> io::Result<()>;
fn disable(&self) -> io::Result<()>;
fn enable(&self) -> Result<()>;
fn disable(&self) -> Result<()>;
}

View File

@ -11,18 +11,12 @@ impl RawModeCommand {
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
crossterm_utils::sys::unix::into_raw_mode()?;
// will be removed in 6.1
unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = true }
Ok(())
}
/// Disables raw mode.
pub fn disable(&mut self) -> Result<()> {
crossterm_utils::sys::unix::disable_raw_mode()?;
// will be removed in 6.1
unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = false }
Ok(())
}
}

View File

@ -1,5 +1,5 @@
use super::IAlternateScreenCommand;
use crossterm_utils::TerminalOutput;
use crossterm_utils::Result;
use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
use std::io;
use winapi::shared::minwindef::DWORD;
@ -59,13 +59,13 @@ impl ToAlternateScreenCommand {
}
impl IAlternateScreenCommand for ToAlternateScreenCommand {
fn enable(&self, _stdout: &mut TerminalOutput) -> io::Result<()> {
fn enable(&self) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
fn disable(&self, _stdout: &TerminalOutput) -> io::Result<()> {
fn disable(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::output_handle()?);
screen_buffer.show()?;
Ok(())

View File

@ -1,3 +1,7 @@
# Changes crossterm_style 0.3
- Removed `TerminalColor::from_output()`
- Added `NoItalic` attribute
# Changes crossterm_style 0.2
- Introduced more `Attributes`
- Introduced easier ways to style text [issue 87](https://github.com/TimonPost/crossterm/issues/87).

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_style"
version = "0.2.0"
version = "0.3.0"
authors = ["T. Post"]
description = "A cross-platform library styling the terminal output."
repository = "https://github.com/TimonPost/crossterm"
@ -12,12 +12,8 @@ readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.5", features = ["wincon"] }
crossterm_winapi = "0.1.1"
winapi = { version = "0.3.7", features = ["wincon"] }
crossterm_winapi = "0.1.2"
[dependencies]
crossterm_utils = "0.1.0"
[[example]]
name = "style"
path = "examples/style.rs"
crossterm_utils = { path = "../crossterm_utils" }

View File

@ -1,5 +1,5 @@
# Crossterm Style | cross-platform styling.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_style.svg
[l1]: https://crates.io/crates/crossterm_style
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_style/badge.svg
[l3]: https://docs.rs/crossterm_style/
[s3]: https://docs.rs/crossterm_style/badge.svg
[l3]: https://docs.rs/crossterm_style/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -41,21 +41,21 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_style` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_style/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_style` version `0.3` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_style/examples) folders with detailed examples for all functionality of this crate.
Add the `crossterm_style` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_style` = "0.2"
crossterm_style = "0.3"
```
And import the `crossterm_style` modules you want to use.
```rust
extern crate crossterm_style;
pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor};
pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor, Colorize, Styler};
```
### Useful Links
@ -63,24 +63,24 @@ pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle
- [Documentation](https://docs.rs/crossterm_input/)
- [Crates.io](https://crates.io/crates/crossterm_input)
- [Book](http://atcentra.com/crossterm/styling.html)
- [Examples](/examples)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Styled output
- Foreground color (16 base colors)
- Background color (16 base colors)
- 256 color support (Windows 10 and UNIX only)
- RGB support (Windows 10 and UNIX only)
- Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only)
- Foreground Color (16 base colors)
- Background Color (16 base colors)
- 256 (ANSI) Color Support (Windows 10 and UNIX Only)
- RGB Color Support (Windows 10 and UNIX only)
- Text Attributes: bold, italic, underscore and crossed word and [more](http://atcentra.com/crossterm/styling.html#attributes) (Windows 10 and UNIX only)
## Examples
Check out the [examples](/examples/) for more information about how to use this crate.
The [examples](./examples) folder has more complete and verbose examples.
_style font with attributes_
```rust
@ -101,7 +101,6 @@ _style font with colors_
```rust
use crossterm_style::{Colored, Color, Colorize};
println!("{} Red foreground color", Colored::Fg(Color::Red));
println!("{} Blue background color", Colored::Bg(Color::Blue));
@ -123,8 +122,8 @@ println!("{} some colored text", Colored::Fg(Color::Rgb {
// custom ansi color value (Windows 10 and UNIX systems)
println!("{} some colored text", Colored::Fg(Color::AnsiValue(10)));
```
## Tested terminals
- Windows Powershell
@ -140,21 +139,8 @@ println!("{} some colored text", Colored::Fg(Color::AnsiValue(10)));
This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested.
If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it.
## Notice
This library is average stable now, I don't expect it to not to change that much.
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -2,10 +2,10 @@
//! This module is used for Windows 10 terminals and Unix terminals by default.
use crate::{Color, ITerminalColor};
use crossterm_utils::{write, write_str, Result, TerminalOutput};
use crossterm_utils::Result;
use crate::Colored;
use std::sync::Arc;
use std::io::Write;
/// This struct is an ANSI escape code implementation for color related actions.
pub struct AnsiColor;
@ -17,24 +17,24 @@ impl AnsiColor {
}
impl ITerminalColor for AnsiColor {
fn set_fg(&self, fg_color: Color, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(
stdout,
format!(csi!("{}m"), self.color_value(Colored::Fg(fg_color))),
)?;
fn set_fg(&self, fg_color: Color) -> Result<()> {
write_cout!(&format!(
csi!("{}m"),
self.color_value(Colored::Fg(fg_color))
))?;
Ok(())
}
fn set_bg(&self, bg_color: Color, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(
stdout,
format!(csi!("{}m"), self.color_value(Colored::Bg(bg_color))),
)?;
fn set_bg(&self, bg_color: Color) -> Result<()> {
write_cout!(&format!(
csi!("{}m"),
self.color_value(Colored::Bg(bg_color))
))?;
Ok(())
}
fn reset(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write_str(stdout, csi!("0m"))?;
fn reset(&self) -> Result<()> {
write_cout!(csi!("0m"))?;
Ok(())
}

View File

@ -5,12 +5,10 @@ use std::io;
use super::*;
use crate::{Color, ITerminalColor};
use crossterm_utils::{Result, TerminalOutput};
use crossterm_utils::Result;
#[cfg(windows)]
use crossterm_utils::get_module;
use std::sync::Arc;
use crossterm_utils::supports_ansi;
/// Allows you to style the terminal.
///
@ -23,82 +21,42 @@ use std::sync::Arc;
/// - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only)
///
/// Check `/examples/` in the library for more specific examples.
///
/// # Remarks
///
/// When you want to 'style' on 'alternate screen' use the 'crossterm_screen' crate.
pub struct TerminalColor<'stdout> {
color: Box<ITerminalColor + Sync + Send>,
stdout: Option<&'stdout Arc<TerminalOutput>>,
pub struct TerminalColor {
#[cfg(windows)]
color: Box<(dyn ITerminalColor + Sync + Send)>,
#[cfg(unix)]
color: AnsiColor,
}
impl<'stdout> TerminalColor<'stdout> {
impl TerminalColor {
/// Create new instance whereon color related actions can be performed.
pub fn new() -> TerminalColor<'stdout> {
#[cfg(target_os = "windows")]
let color = get_module::<Box<ITerminalColor + Sync + Send>>(
Box::from(WinApiColor::new()),
Box::from(AnsiColor::new()),
)
.expect("could not extract module");
pub fn new() -> TerminalColor {
#[cfg(windows)]
let color = if supports_ansi() {
Box::from(AnsiColor::new()) as Box<(dyn ITerminalColor + Sync + Send)>
} else {
WinApiColor::new() as Box<(dyn ITerminalColor + Sync + Send)>
};
#[cfg(not(target_os = "windows"))]
let color = Box::from(AnsiColor::new()) as Box<ITerminalColor + Sync + Send>;
#[cfg(unix)]
let color = AnsiColor::new();
TerminalColor {
color,
stdout: None,
}
}
/// Create a new instance of `TerminalColor` whereon coloring could be preformed on the given output.
///
/// # Remarks
///
/// Use this function when you want your terminal to operate with a specific output.
/// This could be useful when you have a screen which is in 'alternate mode',
/// and you want your actions from the `TerminalColor`, created by this function, to operate on the 'alternate screen'.
///
/// You should checkout the 'crossterm_screen' crate for more information about this.
///
/// # Example
/// ```
/// let screen = Screen::default();
//
/// if let Ok(alternate) = screen.enable_alternate_modes(false) {
/// let terminal = TerminalColor::from_output(&alternate.screen.stdout);
/// }
/// ```
pub fn from_output(stdout: &'stdout Arc<TerminalOutput>) -> TerminalColor<'stdout> {
#[cfg(target_os = "windows")]
let color = get_module::<Box<ITerminalColor + Sync + Send>>(
Box::from(WinApiColor::new()),
Box::from(AnsiColor::new()),
)
.unwrap();
#[cfg(not(target_os = "windows"))]
let color = Box::from(AnsiColor::new()) as Box<ITerminalColor + Sync + Send>;
TerminalColor {
color,
stdout: Some(stdout),
}
TerminalColor { color }
}
/// Set the foreground color to the given color.
pub fn set_fg(&self, color: Color) -> Result<()> {
self.color.set_fg(color, &self.stdout)
self.color.set_fg(color)
}
/// Set the background color to the given color.
pub fn set_bg(&self, color: Color) -> Result<()> {
self.color.set_bg(color, &self.stdout)
self.color.set_bg(color)
}
/// Reset the terminal colors and attributes to default.
pub fn reset(&self) -> Result<()> {
self.color.reset(&self.stdout)
self.color.reset()
}
/// Get available color count.
@ -119,6 +77,6 @@ impl<'stdout> TerminalColor<'stdout> {
}
/// Get a `TerminalColor` implementation whereon color related actions can be performed.
pub fn color<'stdout>() -> TerminalColor<'stdout> {
pub fn color() -> TerminalColor {
TerminalColor::new()
}

View File

@ -84,6 +84,12 @@ pub enum Attribute {
/// - Opposite of `Bold`(1)
/// [Supportability]: not widely supported
NoBold = 21,
/// This will turn off the italic attribute.
/// [info]:
/// - Not italic, not Fraktur
/// - Opposite of `Italic`(3)
/// [Supportability]: Windows, UNIX
NoItalic = 23,
/// This will turn off the underline attribute.
/// [info]:
/// - Not singly or doubly underlined will be turned off.
@ -137,7 +143,7 @@ pub enum Attribute {
impl Display for Attribute {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", format!(csi!("{}m"), *self as i16))?;
stdout().flush();
stdout().flush().unwrap();
Ok(())
}
}

View File

@ -28,15 +28,15 @@ pub enum Colored {
}
impl Display for Colored {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
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);
colored_terminal.set_fg(color).unwrap();
}
Colored::Bg(color) => {
colored_terminal.set_bg(color);
colored_terminal.set_bg(color).unwrap();
}
}

View File

@ -3,7 +3,7 @@
#[macro_use]
extern crate crossterm_utils;
#[cfg(target_os = "windows")]
#[cfg(windows)]
extern crate crossterm_winapi;
#[macro_use]
@ -15,27 +15,21 @@ pub mod styledobject;
mod traits;
mod ansi_color;
#[cfg(target_os = "windows")]
#[cfg(windows)]
mod winapi_color;
use self::ansi_color::AnsiColor;
#[cfg(target_os = "windows")]
#[cfg(windows)]
use self::winapi_color::WinApiColor;
use std::convert::From;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::Arc;
pub use self::color::{color, TerminalColor};
pub use self::enums::{Attribute, Color, Colored};
pub use self::objectstyle::ObjectStyle;
pub use self::styledobject::DisplayableObject;
pub use self::styledobject::StyledObject;
pub use self::traits::{Colorize, Styler};
use crossterm_utils::{Result, TerminalOutput};
use std::io::stdout;
use std::io::Write;
use crossterm_utils::Result;
/// This trait defines the actions that can be preformed with terminal color.
/// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill
@ -47,11 +41,11 @@ use std::io::Write;
/// so that color-related actions can be performed on both UNIX and Windows systems.
trait ITerminalColor {
/// Set the foreground color to the given color.
fn set_fg(&self, fg_color: Color, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn set_fg(&self, fg_color: Color) -> Result<()>;
/// Set the background color to the given color.
fn set_bg(&self, fg_color: Color, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn set_bg(&self, fg_color: Color) -> Result<()>;
/// Reset the terminal color to default.
fn reset(&self, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn reset(&self) -> Result<()>;
/// Gets an value that represents an color from the given `Color` and `ColorType`.
fn color_value(&self, cored: Colored) -> String;
}

View File

@ -1,9 +1,7 @@
use crate::{ObjectStyle, StyledObject};
macro_rules! def_attr {
($name: ident => $attr: path) => {
fn $name(self) -> StyledObject<D> {
let mut so = self;
let so = self;
so.attr($attr)
}

View File

@ -11,15 +11,14 @@ use super::Attribute;
pub struct ObjectStyle {
pub fg_color: Option<Color>,
pub bg_color: Option<Color>,
pub attrs: Vec<Attribute>,
}
impl Default for ObjectStyle {
fn default() -> ObjectStyle {
ObjectStyle {
fg_color: Some(Color::White),
bg_color: Some(Color::Black),
fg_color: None,
bg_color: None,
attrs: Vec::new(),
}
}

View File

@ -1,18 +1,14 @@
//! This module contains the logic to style an object that contains some 'content' which can be styled.
use super::{color, Color, ObjectStyle};
//use Screen;
use crossterm_utils::{Result, TerminalOutput};
use std::fmt::{self, Display, Formatter};
use std::io::Write;
use std::result;
use std::sync::Arc;
use super::Attribute;
use crate::Colorize;
use crate::Styler;
use crate::{Colorize, Styler};
/// Struct that contains both the style and the content wits can be styled.
/// Contains both the style and the content wits can be styled.
pub struct StyledObject<D: Display> {
pub object_style: ObjectStyle,
pub content: D,
@ -51,70 +47,12 @@ impl<'a, D: Display + 'a> StyledObject<D> {
self.object_style.add_attr(attr);
self
}
/// This converts an styled object into an `DisplayableObject` witch implements: `Display` and could be used inside the write function of the standard library.
///
/// _StyledObject already implements `Display` right?_
///
/// This is true, however there are some complex issues why this won't work on alternate screen.
/// That is the reason why this functions exists.
/// You could just pass in the 'screen' from your alternate screen to this method and your `StyledObject` will be printed to the alternate screen just fine.
///
/// ```
/// let screen = Screen::default(); /* represents the alternate screen */
/// let styled_object = style("test").with(Color::Yellow);
/// let display_object = styled_object.into_displayable(&screen);
/// println!("Colored text: {}. Default color", display_object);
/// ```
pub fn into_displayable(self, stdout: &'a Arc<TerminalOutput>) -> DisplayableObject<'a, D> {
DisplayableObject::new(stdout, self)
}
/// This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting.
///
/// ``` rust
/// style("Some colored text")
/// .with(Color::Blue)
/// .on(Color::Black)
/// .paint(&screen);
/// ```
///
/// You should take not that `StyledObject` implements `Display`. You don't need to call paint unless you are on alternate screen.
/// Checkout `into_displayable()` for more information about this.
pub fn paint(&self, stdout: &Arc<TerminalOutput>) -> Result<()> {
let colored_terminal = super::TerminalColor::from_output(stdout);
let mut reset = true;
if let Some(bg) = self.object_style.bg_color {
colored_terminal.set_bg(bg)?;
reset = true;
}
if let Some(fg) = self.object_style.fg_color {
colored_terminal.set_fg(fg)?;
reset = true;
}
for attr in self.object_style.attrs.iter() {
stdout.write_string(format!(csi!("{}m"), *attr as i16))?;
reset = true;
}
use std::fmt::Write;
let mut content = String::new();
write!(content, "{}", self.content)?;
stdout.write_string(content)?;
stdout.flush()?;
if reset {
colored_terminal.reset()?;
}
Ok(())
}
}
impl<D: Display> Display for StyledObject<D> {
fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> {
let colored_terminal = color();
let mut reset = true;
let mut reset = false;
if let Some(bg) = self.object_style.bg_color {
colored_terminal.set_bg(bg).unwrap();
@ -191,33 +129,3 @@ impl<D: Display> Styler<D> for StyledObject<D> {
def_attr!(hidden => Attribute::Hidden);
def_attr!(crossed_out => Attribute::CrossedOut);
}
/// This is a wrapper for a styled object on 'alternate screen' so that the styled object could be printed on the 'alternate screen' with the standard write functions in rust.
///
/// ```
/// write! ("some normal text, {} <- some colored text", DisplayableObject::new(&screen, styled_object));
/// println! ("some normal text, {} <- some colored text", DisplayableObject::new(&screen, styled_object));
/// ```
pub struct DisplayableObject<'a, D: Display + 'a> {
styled_object: StyledObject<D>,
output: &'a Arc<TerminalOutput>,
}
impl<'a, D: Display + 'a> DisplayableObject<'a, D> {
pub fn new(
screen: &'a Arc<TerminalOutput>,
styled_object: StyledObject<D>,
) -> DisplayableObject<'a, D> {
DisplayableObject {
output: screen,
styled_object,
}
}
}
impl<'a, D: Display + 'a> Display for DisplayableObject<'a, D> {
fn fmt(&self, _f: &mut Formatter) -> result::Result<(), fmt::Error> {
self.styled_object.paint(self.output).unwrap();
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use crate::{ObjectStyle, StyledObject};
use crate::StyledObject;
use std::fmt::Display;
/// Provides a set of methods to color any type implementing `Display` with attributes.

View File

@ -2,10 +2,9 @@
//! This module is used for non supporting `ANSI` Windows terminals.
use crate::{Color, Colored, ITerminalColor};
use crossterm_utils::{Result, TerminalOutput};
use crossterm_utils::Result;
use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer};
use std::io;
use std::sync::Arc;
use std::sync::{Once, ONCE_INIT};
use winapi::um::wincon;
@ -13,13 +12,13 @@ use winapi::um::wincon;
pub struct WinApiColor;
impl WinApiColor {
pub fn new() -> WinApiColor {
WinApiColor
pub fn new() -> Box<WinApiColor> {
Box::from(WinApiColor)
}
}
impl ITerminalColor for WinApiColor {
fn set_fg(&self, fg_color: Color, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn set_fg(&self, fg_color: Color) -> Result<()> {
// init the original color in case it is not set.
let _ = init_console_color()?;
@ -46,7 +45,7 @@ impl ITerminalColor for WinApiColor {
Ok(())
}
fn set_bg(&self, bg_color: Color, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn set_bg(&self, bg_color: Color) -> Result<()> {
// init the original color in case it is not set.
let _ = init_console_color()?;
@ -73,7 +72,7 @@ impl ITerminalColor for WinApiColor {
Ok(())
}
fn reset(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn reset(&self) -> Result<()> {
// init the original color in case it is not set.
let original_color = original_console_color();
Console::from(Handle::new(HandleType::CurrentOutputHandle)?)

View File

@ -0,0 +1,5 @@
# Changes crossterm_style 0.2
- Removed `Terminal:from_output()`
# Changes crossterm_terminal 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,6 +1,6 @@
[package]
name = "crossterm_terminal"
version = "0.1.0"
version = "0.2.0"
authors = ["T. Post"]
description = "A cross-platform library for doing terminal related actions."
repository = "https://github.com/TimonPost/crossterm"
@ -12,15 +12,11 @@ readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.1.1"
crossterm_winapi = "0.1.2"
[target.'cfg(unix)'.dependencies]
libc = "0.2.43"
libc = "0.2.51"
[dependencies]
crossterm_utils = "0.1.0"
crossterm_cursor = "0.1.0"
[[example]]
name = "terminal"
path = "examples/terminal.rs"
crossterm_utils = { path = "../crossterm_utils" }
crossterm_cursor = { path = "../crossterm_cursor" }

View File

@ -1,5 +1,5 @@
# Crossterm Terminal | cross-platform terminal actions.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_terminal.svg
[l1]: https://crates.io/crates/crossterm_terminal
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_terminal/badge.svg
[l3]: https://docs.rs/crossterm_terminal/
[s3]: https://docs.rs/crossterm_terminal/badge.svg
[l3]: https://docs.rs/crossterm_terminal/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -41,13 +41,13 @@ When you want to use other modules as well you might want to use crossterm with
## Getting Started
This documentation is only for `crossterm_terminal` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_terminal/examples) folders with detailed examples for all functionality of this crate.
This documentation is only for `crossterm_terminal` version `0.2` check the [examples](./examples) folders with detailed examples for all functionality of this crate.
Add the `crossterm_terminal` package to your `Cargo.toml` file.
```
[dependencies]
`crossterm_terminal` = "0.1"
crossterm_terminal = "0.2"
```
And import the `crossterm_terminal` modules you want to use.
@ -68,20 +68,17 @@ pub use crossterm_terminal::{terminal, Terminal, ClearType};
These are the features of this crate:
- Cross-platform
- Everything is multithreaded (Send, Sync)
- Detailed documentation on every item
- Very few dependenties.
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Terminal
- Clearing (all lines, current line, from cursor down and up, until new line)
- Scrolling (Up, down)
- Get the size of the terminal
- Set the size of the terminal
- Alternate screen
- Raw screen
- Exit the current process
- Scrolling (up, down)
- Terminal Size (get/set)
- Exit Current Process
## Examples
Check out the [examples](/examples/) for more information about how to use this crate.
The [examples](./examples) folder has more complete and verbose examples.
```rust
use crossterm::terminal::{terminal,ClearType};
@ -132,21 +129,8 @@ terminal.write("Some text\n Some text on new line");
This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested.
If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it.
## Notice
This library is average stable now, I don't expect it to not to change that much.
If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -4,62 +4,57 @@
use super::ITerminal;
use crate::{sys::get_terminal_size, ClearType};
use crossterm_cursor::TerminalCursor;
use crossterm_utils::{write, write_str, Result, TerminalOutput};
use std::sync::Arc;
use crossterm_utils::Result;
use std::io::Write;
/// This struct is an ansi escape code implementation for terminal related actions.
pub struct AnsiTerminal;
impl AnsiTerminal {
pub fn new() -> AnsiTerminal {
AnsiTerminal {}
AnsiTerminal
}
}
impl ITerminal for AnsiTerminal {
fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn clear(&self, clear_type: ClearType) -> Result<()> {
match clear_type {
ClearType::All => {
write_str(&stdout, csi!("2J"))?;
write_cout!(csi!("2J"))?;
TerminalCursor::new().goto(0, 0)?;
}
ClearType::FromCursorDown => {
write_str(&stdout, csi!("J"))?;
write_cout!(csi!("J"))?;
}
ClearType::FromCursorUp => {
write_str(&stdout, csi!("1J"))?;
write_cout!(csi!("1J"))?;
}
ClearType::CurrentLine => {
write_str(&stdout, csi!("2K"))?;
write_cout!(csi!("2K"))?;
}
ClearType::UntilNewLine => {
write_str(&stdout, csi!("K"))?;
write_cout!(csi!("K"))?;
}
};
Ok(())
}
fn terminal_size(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> (u16, u16) {
fn terminal_size(&self) -> (u16, u16) {
get_terminal_size()
}
fn scroll_up(&self, count: i16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(&stdout, format!(csi!("{}S"), count))?;
fn scroll_up(&self, count: i16) -> Result<()> {
write_cout!(&format!(csi!("{}S"), count))?;
Ok(())
}
fn scroll_down(&self, count: i16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
write(&stdout, format!(csi!("{}T"), count))?;
fn scroll_down(&self, count: i16) -> Result<()> {
write_cout!(&format!(csi!("{}T"), count))?;
Ok(())
}
fn set_size(
&self,
width: i16,
height: i16,
stdout: &Option<&Arc<TerminalOutput>>,
) -> Result<()> {
write(&stdout, format!(csi!("8;{};{}t"), height, width))?;
fn set_size(&self, width: i16, height: i16) -> Result<()> {
write_cout!(&format!(csi!("8;{};{}t"), height, width))?;
Ok(())
}
}

View File

@ -5,18 +5,16 @@ mod test;
mod terminal;
mod ansi_terminal;
#[cfg(target_os = "windows")]
#[cfg(windows)]
mod winapi_terminal;
use self::ansi_terminal::AnsiTerminal;
#[cfg(target_os = "windows")]
#[cfg(windows)]
use self::winapi_terminal::WinApiTerminal;
pub use self::terminal::{terminal, Terminal};
use crossterm_utils::{Result, TerminalOutput};
use std::sync::Arc;
use crossterm_utils::Result;
/// Enum that specifies a way of clearing.
pub enum ClearType {
@ -42,18 +40,13 @@ pub enum ClearType {
/// so that terminal related actions can be preformed on both Unix and Windows systems.
trait ITerminal {
/// Clear the current cursor by specifying the clear type
fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn clear(&self, clear_type: ClearType) -> Result<()>;
/// Get the terminal size (x,y)
fn terminal_size(&self, stdout: &Option<&Arc<TerminalOutput>>) -> (u16, u16);
fn terminal_size(&self) -> (u16, u16);
/// Scroll `n` lines up in the current terminal.
fn scroll_up(&self, count: i16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn scroll_up(&self, count: i16) -> Result<()>;
/// Scroll `n` lines down in the current terminal.
fn scroll_down(&self, count: i16, stdout: &Option<&Arc<TerminalOutput>>) -> Result<()>;
fn scroll_down(&self, count: i16) -> Result<()>;
/// Resize terminal to the given width and height.
fn set_size(
&self,
width: i16,
height: i16,
stdout: &Option<&Arc<TerminalOutput>>,
) -> Result<()>;
fn set_size(&self, width: i16, height: i16) -> Result<()>;
}

View File

@ -2,15 +2,15 @@
//! Like clearing and scrolling in the terminal or getting the window size from the terminal.
use super::{AnsiTerminal, ClearType, ITerminal};
use crossterm_utils::{write, Result, TerminalOutput};
use crossterm_utils::Result;
#[cfg(windows)]
use super::WinApiTerminal;
#[cfg(windows)]
use crossterm_utils::get_module;
use crossterm_utils::supports_ansi;
use std::fmt;
use std::sync::Arc;
use std::io::Write;
/// Allows you to preform actions on the terminal.
///
@ -25,67 +25,27 @@ use std::sync::Arc;
/// - Exit the current process
///
/// Check `/examples/` in the library for more specific examples.
///
/// # Remarks
///
/// When you want to perform terminal actions on 'alternate screen' use the 'crossterm_screen' crate.
pub struct Terminal<'stdout> {
terminal: Box<ITerminal + Sync + Send>,
stdout: Option<&'stdout Arc<TerminalOutput>>,
pub struct Terminal {
#[cfg(windows)]
terminal: Box<(dyn ITerminal + Sync + Send)>,
#[cfg(unix)]
terminal: AnsiTerminal,
}
impl<'stdout> Terminal<'stdout> {
impl Terminal {
/// Create new terminal instance whereon terminal related actions can be performed.
pub fn new() -> Terminal<'stdout> {
#[cfg(target_os = "windows")]
let terminal = get_module::<Box<ITerminal + Sync + Send>>(
Box::new(WinApiTerminal::new()),
Box::new(AnsiTerminal::new()),
)
.unwrap();
pub fn new() -> Terminal {
#[cfg(windows)]
let terminal = if supports_ansi() {
Box::from(AnsiTerminal::new()) as Box<(dyn ITerminal + Sync + Send)>
} else {
WinApiTerminal::new() as Box<(dyn ITerminal + Sync + Send)>
};
#[cfg(not(target_os = "windows"))]
let terminal = Box::from(AnsiTerminal::new()) as Box<ITerminal + Sync + Send>;
#[cfg(unix)]
let terminal = AnsiTerminal::new();
Terminal {
terminal,
stdout: None,
}
}
/// Create a new instance of `Terminal` whereon terminal related actions could be preformed on the given output.
///
/// # Remarks
///
/// Use this function when you want your terminal to operate with a specific output.
/// This could be useful when you have a screen which is in 'alternate mode',
/// and you want your actions from the `Terminal`, created by this function, to operate on the 'alternate screen'.
///
/// You should checkout the 'crossterm_screen' crate for more information about this.
///
/// # Example
/// ```
/// let screen = Screen::default();
//
/// if let Ok(alternate) = screen.enable_alternate_modes(false) {
/// let terminal = Terminal::from_output(&alternate.screen.stdout);
/// }
/// ```
pub fn from_output(stdout: &'stdout Arc<TerminalOutput>) -> Terminal<'stdout> {
#[cfg(target_os = "windows")]
let terminal = get_module::<Box<ITerminal + Sync + Send>>(
Box::new(WinApiTerminal::new()),
Box::new(AnsiTerminal::new()),
)
.unwrap();
#[cfg(not(target_os = "windows"))]
let terminal = Box::from(AnsiTerminal::new()) as Box<ITerminal + Sync + Send>;
Terminal {
terminal,
stdout: Some(stdout),
}
Terminal { terminal }
}
/// Clear the current cursor by specifying the `ClearType`.
@ -106,7 +66,7 @@ impl<'stdout> Terminal<'stdout> {
/// term.clear(terminal::ClearType::UntilNewLine);
/// ```
pub fn clear(&self, clear_type: ClearType) -> Result<()> {
self.terminal.clear(clear_type, &self.stdout)
self.terminal.clear(clear_type)
}
/// Get the terminal size (x,y).
@ -114,7 +74,7 @@ impl<'stdout> Terminal<'stdout> {
/// # Remark
/// This will return a tuple of (x: u16, y: u16)
pub fn terminal_size(&self) -> (u16, u16) {
self.terminal.terminal_size(&self.stdout)
self.terminal.terminal_size()
}
/// Scroll `n` lines up in the current terminal.
@ -122,7 +82,7 @@ impl<'stdout> Terminal<'stdout> {
/// # Parameter
/// - `count`: the number of rows should be shifted up.
pub fn scroll_up(&self, count: i16) -> Result<()> {
self.terminal.scroll_up(count, &self.stdout)
self.terminal.scroll_up(count)
}
/// Scroll `n` lines down in the current terminal.
@ -130,7 +90,7 @@ impl<'stdout> Terminal<'stdout> {
/// # Parameter
/// - `count`: the number of rows should be shifted down.
pub fn scroll_down(&self, count: i16) -> Result<()> {
self.terminal.scroll_down(count, &self.stdout)
self.terminal.scroll_down(count)
}
/// Set the terminal size. Note that not all terminals can be set to a very small scale.
@ -142,7 +102,7 @@ impl<'stdout> Terminal<'stdout> {
/// let size = term.set_size(10,10);
/// ```
pub fn set_size(&self, width: i16, height: i16) -> Result<()> {
self.terminal.set_size(width, height, &self.stdout)
self.terminal.set_size(width, height)
}
/// Exit the current process.
@ -163,16 +123,15 @@ impl<'stdout> Terminal<'stdout> {
///
/// let size = term.write("Some text \n Some text on new line");
/// ```
///
/// This will also flush the standard output.
pub fn write<D: fmt::Display>(&self, value: D) -> Result<usize> {
use std::fmt::Write;
let mut string = String::new();
write!(string, "{}", value)?;
let size = write(&self.stdout, string)?;
Ok(size)
write_cout!(value)?;
Ok(0)
}
}
/// Get a `Terminal` instance whereon terminal related actions could performed.
pub fn terminal<'stdout>() -> Terminal<'stdout> {
pub fn terminal() -> Terminal {
Terminal::new()
}

View File

@ -9,9 +9,9 @@ mod winapi_tests {
fn resize_winapi() {
let terminal = WinApiTerminal::new();
terminal.set_size(30, 30, &None);
terminal.set_size(30, 30);
let (x, y) = terminal.terminal_size(&None);
let (x, y) = terminal.terminal_size();
assert_eq!(x, 30);
assert_eq!(y, 30);
@ -25,12 +25,12 @@ fn resize_ansi() {
if try_enable_ansi() {
let terminal = AnsiTerminal::new();
terminal.set_size(50, 50, &None).unwrap();
terminal.set_size(50, 50).unwrap();
// see issue: https://github.com/eminence/terminal-size/issues/11
thread::sleep(time::Duration::from_millis(30));
let (x, y) = terminal.terminal_size(&None);
let (x, y) = terminal.terminal_size();
assert_eq!(x, 50);
assert_eq!(y, 50);

View File

@ -5,20 +5,20 @@
use super::*;
use crossterm_cursor::sys::winapi::Cursor;
use crossterm_utils::{ErrorKind, Result, TerminalOutput};
use crossterm_utils::{ErrorKind, Result};
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
/// This struct is an winapi implementation for terminal related actions.
pub struct WinApiTerminal;
impl WinApiTerminal {
pub fn new() -> WinApiTerminal {
WinApiTerminal {}
pub fn new() -> Box<WinApiTerminal> {
Box::from(WinApiTerminal {})
}
}
impl ITerminal for WinApiTerminal {
fn clear(&self, clear_type: ClearType, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn clear(&self, clear_type: ClearType) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
@ -38,12 +38,12 @@ impl ITerminal for WinApiTerminal {
Ok(())
}
fn terminal_size(&self, _stdout: &Option<&Arc<TerminalOutput>>) -> (u16, u16) {
fn terminal_size(&self) -> (u16, u16) {
let csbi = ScreenBuffer::current().unwrap();
csbi.info().unwrap().terminal_size().into()
}
fn scroll_up(&self, count: i16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn scroll_up(&self, count: i16) -> Result<()> {
let csbi = ScreenBuffer::current()?;
let mut window = csbi.info()?.terminal_window();
@ -57,7 +57,7 @@ impl ITerminal for WinApiTerminal {
Ok(())
}
fn scroll_down(&self, count: i16, _stdout: &Option<&Arc<TerminalOutput>>) -> Result<()> {
fn scroll_down(&self, count: i16) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
let mut window = csbi.terminal_window();
@ -74,12 +74,7 @@ impl ITerminal for WinApiTerminal {
}
/// Set the current terminal size
fn set_size(
&self,
width: i16,
height: i16,
_stdout: &Option<&Arc<TerminalOutput>>,
) -> Result<()> {
fn set_size(&self, width: i16, height: i16) -> Result<()> {
if width <= 0 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Cannot set the terminal width lower than 1",

View File

@ -1,13 +1,12 @@
[package]
name = "crossterm_utils"
version = "0.1.0"
version = "0.2.0"
authors = ["Timon Post <timonpost@hotmail.nl>"]
edition = "2018"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = "0.1.1"
winapi = { version = "0.3.5", features = ["wincon"] }
winapi = { version = "0.3.7", features = ["wincon"] }
[target.'cfg(unix)'.dependencies]
libc = "0.2.43"
termios = "0.3.1"
libc = "0.2.51"

View File

@ -1,5 +1,5 @@
# Crossterm Utils | crossterm common used code.
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5]
[s1]: https://img.shields.io/crates/v/crossterm_utils.svg
[l1]: https://crates.io/crates/crossterm_utils
@ -10,8 +10,8 @@
[s3]: https://docs.rs/crossterm_utils/badge.svg
[l3]: https://docs.rs/crossterm_utils/
[s3]: https://docs.rs/crossterm_utils/badge.svg
[l3]: https://docs.rs/crossterm_utils/
[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
[l5]: https://discord.gg/K4nyTDB.
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
@ -24,16 +24,10 @@ This crate is a utilities crate used by the following [crossterm](https://crates
This crate is not meant for standalone use and is really a library with some common used code for crossterm and the above named modules.
## Contributing
I highly appreciate it when you are contributing to this crate.
Also Since my native language is not English my grammar and sentence order will not be perfect.
So improving this by correcting these mistakes will help both me and the reader of the docs.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,30 +0,0 @@
//! This module contains some commands that could be executed for a specific task. A `Command` is just a little wrapper.
use crate::output::TerminalOutput;
use std::io;
//pub mod shared_commands;
//
//#[cfg(not(target_os = "windows"))]
//pub mod unix_command;
#[cfg(target_os = "windows")]
pub mod win_commands;
/// This trait provides a way to execute some state changing commands.
pub trait IStateCommand {
fn execute(&mut self) -> io::Result<()>;
fn undo(&mut self) -> io::Result<()>;
}
// This trait provides an interface for switching to alternate screen and back.
pub trait IAlternateScreenCommand: Sync + Send {
fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>;
fn disable(&self, stdout: &TerminalOutput) -> io::Result<()>;
}
// This trait provides an interface for switching to raw mode and back.
pub trait IRawScreenCommand: Sync + Send {
fn enable(&mut self) -> io::Result<()>;
fn disable(&self) -> io::Result<()>;
}

View File

@ -1,76 +0,0 @@
//! A module which contains the commands that can be used for windows systems.
use super::{IAlternateScreenCommand, TerminalOutput};
use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
use winapi::shared::minwindef::DWORD;
use winapi::um::wincon;
use std::io::Result;
/// 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,
}
use self::wincon::{ENABLE_LINE_INPUT, ENABLE_WRAP_AT_EOL_OUTPUT};
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand {
mask: ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LINE_INPUT,
}
}
}
impl RawModeCommand {
/// Enables raw mode.
pub fn enable(&mut self) -> Result<()> {
let console_mode = ConsoleMode::new()?;
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::new()?;
let dw_mode = console_mode.mode()?;
let new_mode = dw_mode | self.mask;
console_mode.set_mode(new_mode)?;
return Ok(());
}
}
/// This command is used for switching to alternate screen and back to main screen.
/// check https://docs.microsoft.com/en-us/windows/console/reading-and-writing-blocks-of-characters-and-attributes for more info
pub struct ToAlternateScreenCommand;
impl ToAlternateScreenCommand {
pub fn new() -> ToAlternateScreenCommand {
return ToAlternateScreenCommand {};
}
}
impl IAlternateScreenCommand for ToAlternateScreenCommand {
fn enable(&self, _stdout: &mut TerminalOutput) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
fn disable(&self, _stdout: &TerminalOutput) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::output_handle()?);
screen_buffer.show()?;
Ok(())
}
}

View File

@ -49,3 +49,12 @@ impl From<fmt::Error> for ErrorKind {
ErrorKind::FmtError(e)
}
}
impl From<ErrorKind> for io::Error {
fn from(e: ErrorKind) -> io::Error {
match e {
ErrorKind::IoError(io) => return io,
_ => io::Error::new(io::ErrorKind::Other, "can not convert error to IO error"),
}
}
}

View File

@ -1,76 +1,33 @@
use crate::output::TerminalOutput;
use std::io::{self, Write};
use std::sync::Arc;
#[cfg(windows)]
use crate::sys::winapi::ansi::set_virtual_terminal_processing;
#[cfg(windows)]
/// Get an module specific implementation of a the generic given type based on the current platform.
/// If the current platform is windows and it supports ansi escape codes it will return the ansi implementation and if not it will return the winapi implementation.
/// If the current platform is unix it will return the ansi implementation.
pub fn get_module<T>(winapi_impl: T, ansi_impl: T) -> Option<T> {
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.
let supports_ansi = is_specific_term();
match supports_ansi {
true => {
return Some(ansi_impl);
return true;
}
false => {
// 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 Some(ansi_impl);
return true;
}
Err(_) => {
return Some(winapi_impl);
return false;
}
}
}
}
}
/// This function is used by 'ANSI' modules. Those modules are using an `Option` of `TerminalOutput`.
/// Because it is an option it could be either 'None' or 'Some'.
/// When the `TerminalOutput` is 'None' we write our 'ANSI' escape codes to the default `stdout()` if it is a `Some`
/// - which means we are in alternate screen modes or we have raw screen enabled - we should write to the screen passed by the user.
/// This way our commands or our writes will be done with the passed `TerminalOutput`.
pub fn write(stdout: &Option<&Arc<TerminalOutput>>, string: String) -> io::Result<usize> {
match stdout {
None => {
print!("{}", string.as_str());
match io::stdout().flush() {
Ok(_) => Ok(string.len()),
Err(e) => Err(e),
}
}
Some(output) => output.write_string(string),
}
}
/// This function is used by 'ANSI' modules. Those modules are using an `Option` of `TerminalOutput`.
/// Because it is an option it could be either 'None' or 'Some'.
/// When the `TerminalOutput` is 'None' we write our 'ANSI' escape codes to the default `stdout()` if it is a `Some`
/// - which means we are in alternate screen modes or we have raw screen enabled - we should write to the screen passed by the user.
/// This way our commands or our writes will be done with the passed `TerminalOutput`.
pub fn write_str(stdout: &Option<&Arc<TerminalOutput>>, string: &str) -> io::Result<usize> {
match stdout {
None => match io::stdout().flush() {
Ok(_) => {
write!(io::stdout(), "{}", string)?;
Ok(string.len())
}
Err(e) => Err(e),
},
Some(output) => output.write_str(string),
}
}
// 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

View File

@ -3,20 +3,13 @@ extern crate crossterm_winapi;
#[cfg(windows)]
extern crate winapi;
#[cfg(unix)]
extern crate termios;
pub mod commands;
pub mod error;
pub mod macros;
pub mod sys;
mod functions;
mod output;
pub use self::error::{ErrorKind, Result};
pub use self::output::TerminalOutput;
#[cfg(windows)]
pub use self::functions::get_module;
pub use self::functions::{write, write_str};
pub use self::functions::supports_ansi;

View File

@ -1,4 +1,25 @@
/// 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 screen will be flushed.
#[macro_export]
macro_rules! write_cout {
($string:expr) => {{
let mut size = 0;
let result = write!(::std::io::stdout(), "{}", $string);
match result {
Ok(size) => size,
Err(e) => return Err(crossterm_utils::ErrorKind::IoError(e)),
};
match ::std::io::stdout().flush() {
Ok(_) => Ok(size),
Err(e) => Err(crossterm_utils::ErrorKind::IoError(e)),
}
}};
}

View File

@ -1,40 +0,0 @@
//! This is an ANSI specific implementation for the screen write
//! This module is used for Windows 10 terminals and UNIX terminals by default.
//! This module uses the stdout to write to the console.
use super::IStdout;
use std::io::{self, stdout, Stdout, Write};
/// This struct is a wrapper for `Stdout`
pub struct AnsiOutput {
pub handle: Stdout,
}
impl IStdout for AnsiOutput {
fn write_str(&self, string: &str) -> io::Result<usize> {
let out = &self.handle;
let mut handle = out.lock();
let amt = handle.write(string.as_bytes())?;
handle.flush()?;
Ok(amt)
}
fn write(&self, buf: &[u8]) -> io::Result<usize> {
let out = &self.handle;
let mut handle = out.lock();
handle.write(buf)
}
fn flush(&self) -> io::Result<()> {
let out = &self.handle;
let mut handle = out.lock();
handle.flush()
}
}
impl AnsiOutput {
pub fn new() -> Self {
AnsiOutput { handle: stdout() }
}
}

View File

@ -1,34 +0,0 @@
//! A module that provides a uniformed way to write to the output no matter if it is in main, alternate or raw mode.
mod output;
#[cfg(test)]
mod test;
mod ansi_output;
#[cfg(target_os = "windows")]
mod winapi_output;
use self::ansi_output::AnsiOutput;
#[cfg(target_os = "windows")]
use self::winapi_output::WinApiOutput;
pub use self::output::TerminalOutput;
use std::io;
/// This trait defines represents an stdout of an screen.
/// This trait can be implemented so that an concrete implementation of the IStdout can forfill
/// the wishes to work on an specific platform.
///
/// ## For example:
///
/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
/// so that output related actions can be preformed on both unix and windows systems.
trait IStdout {
/// Write an &str to the current stdout and flush the screen.
fn write_str(&self, string: &str) -> io::Result<usize>;
/// Write [u8] buffer to console.
fn write(&self, buf: &[u8]) -> io::Result<usize>;
/// Flush the current output.
fn flush(&self) -> io::Result<()>;
}

View File

@ -1,96 +0,0 @@
//! This module provides one place to work with the screen.
//!
//! In Rust we can call `stdout()` to get a handle to the current default console handle.
//! However, we can't use `stdout()` to access the alternate screen handle therefore we also won't be able to use `print!(), println!(), or write!()`.
//! The same goes for coloring, cursor movement, input, and terminal actions.
//! All of those functions are writing to the standard output and not to our alternate screen we are currently on.
//!
//! To get the handle to the `alternate screen` we first need to store this handle so that we are able to call it later on.
//! Through this stored handle, crossterm can write to or execute commands at the current screen whether it be an alternate screen or main screen.
//!
//! For UNIX and Windows10 systems, we store the handle gotten from `stdout()`. For Windows systems who are not supporting ANSI escape codes, we can call `CONOUT$` to get the current screen `HANDLE`.
use super::*;
use crate::functions;
use std::default::Default;
use std::io::Write;
/// Struct that is a handle to the current terminal screen.
///
/// For UNIX and Windows 10 `stdout()` will be used as handle. And for Windows systems, not supporting ANSI escape codes, will use WinApi's `HANDLE` as handle.
pub struct TerminalOutput {
stdout: Box<IStdout + Send + Sync>,
/// checks if this output is in raw mode.
pub is_in_raw_mode: bool,
}
impl TerminalOutput {
/// Create a new screen write instance whereon screen related actions can be performed.
pub fn new(raw_mode: bool) -> Self {
#[cfg(target_os = "windows")]
let stdout: Box<IStdout + Send + Sync> =
functions::get_module::<Box<IStdout + Send + Sync>>(
Box::from(WinApiOutput::new()),
Box::from(AnsiOutput::new()),
)
.unwrap();
#[cfg(not(target_os = "windows"))]
let stdout = Box::from(AnsiOutput::new()) as Box<IStdout + Send + Sync>;
TerminalOutput {
stdout,
is_in_raw_mode: raw_mode,
}
}
/// Write String to the current screen.
pub fn write_string(&self, string: String) -> io::Result<usize> {
self.stdout.write_str(string.as_str())
}
/// Flush the current screen.
pub fn flush(&self) -> io::Result<()> {
self.stdout.flush()
}
/// Write &str to the current screen.
pub fn write_str(&self, string: &str) -> io::Result<usize> {
self.stdout.write_str(string)
}
/// Write buffer to the screen
pub fn write_buf(&self, buf: &[u8]) -> io::Result<usize> {
self.stdout.write(buf)
}
}
impl Write for TerminalOutput {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_buf(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.stdout.flush()
}
}
impl Default for TerminalOutput {
/// Get the default handle to the current screen.
fn default() -> Self {
#[cfg(target_os = "windows")]
let stdout = functions::get_module::<Box<IStdout + Send + Sync>>(
Box::from(WinApiOutput::new()),
Box::from(AnsiOutput::new()),
)
.unwrap();
#[cfg(not(target_os = "windows"))]
let stdout = Box::from(AnsiOutput::new()) as Box<IStdout + Send + Sync>;
TerminalOutput {
stdout,
is_in_raw_mode: false,
}
}
}

View File

@ -1 +0,0 @@
pub mod winapi;

View File

@ -1,13 +0,0 @@
//! This module contains the logic to write to the terminal.
use winapi::ctypes::c_void;
use winapi::shared::ntdef::NULL;
use winapi::um::consoleapi::WriteConsoleW;
use winapi::um::wincon::{WriteConsoleOutputA, CHAR_INFO, COORD, PSMALL_RECT};
use winapi::um::winnt::HANDLE;
use crossterm_winapi::{is_true, ScreenBuffer};
use std::io::{self, Result};
use std::str;

View File

@ -1,74 +0,0 @@
use super::{AnsiOutput, IStdout, WinApiOutput};
#[cfg(windows)]
mod winapi_tests {
use super::*;
/* ======================== WinApi =========================== */
#[test]
fn write_winapi() {
let output = WinApiOutput::new();
let bytes = "test".as_bytes();
let result = output.write(bytes);
is_valid_write(result, bytes.len());
}
#[test]
fn write_str_winapi() {
let output = WinApiOutput::new();
let bytes = "test".as_bytes();
let result = output.write_str("test");
is_valid_write(result, bytes.len());
}
}
/* ======================== ANSI =========================== */
#[test]
fn write_ansi() {
let output = AnsiOutput::new();
let bytes = "test".as_bytes();
let result = output.write(bytes);
is_valid_write(result, bytes.len());
}
#[test]
fn write_str_ansi() {
let output = AnsiOutput::new();
let bytes = "test".as_bytes();
let result = output.write_str("test");
is_valid_write(result, bytes.len());
}
fn is_valid_write(result: ::std::io::Result<usize>, str_length: usize) {
match result {
Err(_) => assert!(false),
Ok(length) => {
if str_length == length {
assert!(true)
} else {
assert!(false)
}
}
};
}
fn try_enable_ansi() -> bool {
#[cfg(windows)]
{
if cfg!(target_os = "windows") {
use crate::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(e) => return false,
}
}
}
return true;
}

View File

@ -1,33 +0,0 @@
use super::IStdout;
use crossterm_winapi::{Console, Handle};
use std::io;
/// This struct is a wrapper for WinApi output.
pub struct WinApiOutput;
impl WinApiOutput {
pub fn new() -> WinApiOutput {
WinApiOutput
}
}
impl IStdout for WinApiOutput {
fn write_str(&self, string: &str) -> io::Result<usize> {
self.write(string.as_bytes())
}
fn write(&self, buf: &[u8]) -> io::Result<usize> {
let handle = Handle::current_out_handle()?;
let console = Console::from(handle);
console.write_char_buffer(buf)
}
fn flush(&self) -> io::Result<()> {
Ok(())
}
}
unsafe impl Send for WinApiOutput {}
unsafe impl Sync for WinApiOutput {}

View File

@ -1,65 +1,67 @@
//! This module contains all `unix` specific terminal related logic.
use libc::{self, TCSADRAIN};
pub use libc::{self, c_int, termios as Termios};
use crate::termios::{tcsetattr, Termios};
use std::fs;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::{io, mem};
static mut ORIGINAL_TERMINAL_MODE: Option<Termios> = None;
pub static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false;
pub static mut RAW_MODE_ENABLED_BY_USER: bool = false;
fn unwrap(t: i32) -> io::Result<()> {
if t == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
/// Transform the given mode into an raw mode (non-canonical) mode.
pub fn make_raw(termios: &mut Termios) {
pub fn raw_terminal_attr(termios: &mut Termios) {
extern "C" {
pub fn cfmakeraw(termptr: *mut Termios);
}
unsafe { cfmakeraw(termios) }
}
pub fn into_raw_mode() -> io::Result<RawFd> {
let tty_f;
pub fn get_terminal_attr() -> io::Result<Termios> {
extern "C" {
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
}
unsafe {
let mut termios = mem::zeroed();
unwrap(tcgetattr(0, &mut termios))?;
Ok(termios)
}
}
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::File::open("/dev/tty")?;
tty_f.as_raw_fd()
}
};
pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> {
extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
}
unwrap(unsafe { tcsetattr(0, 0, termios) }).and(Ok(()))
}
let mut termios = Termios::from_fd(fd)?;
let original = termios.clone();
pub fn into_raw_mode() -> io::Result<()> {
let mut ios = get_terminal_attr()?;
let prev_ios = ios;
unsafe {
if ORIGINAL_TERMINAL_MODE.is_none() {
ORIGINAL_TERMINAL_MODE = Some(original.clone())
ORIGINAL_TERMINAL_MODE = Some(prev_ios.clone())
}
}
make_raw(&mut termios);
tcsetattr(fd, TCSADRAIN, &termios)?;
Ok(fd)
raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
Ok(())
}
pub fn disable_raw_mode() -> io::Result<()> {
let tty_f;
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
tty_f = fs::File::open("/dev/tty")?;
tty_f.as_raw_fd()
unsafe {
if ORIGINAL_TERMINAL_MODE.is_some() {
set_terminal_attr(&ORIGINAL_TERMINAL_MODE.unwrap())?;
}
};
if let Some(original) = unsafe { ORIGINAL_TERMINAL_MODE } {
tcsetattr(fd, TCSADRAIN, &original)?;
}
Ok(())
}

View File

@ -11,4 +11,4 @@ exclude = ["target", "Cargo.lock"]
readme = "README.md"
[dependencies]
winapi = { version = "0.3.5", features = ["winbase","consoleapi","processenv", "handleapi"] }
winapi = { version = "0.3.7", features = ["winbase","consoleapi","processenv", "handleapi"] }

View File

@ -13,8 +13,8 @@
[s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master
This crate provides some wrappers aground common used WinApi functions.
The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm),
and it is very unstable right because of that some changes could be expected.
The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm), but could be used apart from it.
Although, notice that it unstable right because some changes to the API could be expected.
# Features
This crate provides some abstractions over reading input, console screen buffer, and handle.
@ -31,8 +31,7 @@ _The following WinApi calls_
- ReadConsoleW
# Example
Here are some examples do demonstrate how to work whit this crate.
Please see [examples](https://github.com/TimonPost/crossterm_winapi) for more
The [examples](./examples) folder has more complete and verbose examples.
## Screenbuffer information
```rust
@ -61,9 +60,3 @@ fn get_different_handle_types() {
let curr_out_put_handle = Handle::new(HandleType::CurrentInputHandle).unwrap();
}
```
### Inspiration
I wanted to expose some of the api crossterm uses for WinApi.
1. I thought it would be helpful for other people to, to have a small rust seemable abstraction over the WinApi bindings.
2. I have some future plans for crossterm wherefore I needed to seperate the WinAPi logic out of the currenbt librarie.

View File

@ -1,3 +1,29 @@
## Changes crossterm 0.9.0
This release is all about moving to a stabilized API for 1.0.
- Major refactor and cleanup.
- Improved performance;
- No locking when writing to stdout.
- UNIX doesn't have any dynamic dispatch anymore.
- Windows has improved the way to check if ANSI modes are enabled.
- Removed lot's of complex API calls: `from_screen`, `from_output`
- Removed `Arc<TerminalOutput>` from all internal Api's.
- Removed termios dependency for UNIX systems.
- Upgraded deps.
- Removed about 1000 lines of code
- `TerminalOutput`
- `Screen`
- unsafe code
- Some duplicated code introduced by a previous refactor.
- Raw modes UNIX systems improved
- Added `NoItalic` attribute
## Changes crossterm to 0.8.2
- Bug fix for sync reader UNIX.
## Changes crossterm to 0.8.1
- Added public re-exports for input.
# Changes crossterm 0.8.0
- Upgraded to `crossterm_input 0.2.0`; Input key, mouse events support.
- Upgraded crossterm_winapi 0.2

View File

@ -1,3 +1,86 @@
## Upgrade crossterm to 0.9.0
This release is all about moving to a stabilized API for 1.0. It has a lot of changes to the API however it has become much better.
### Removed functions
First you don't have to pass any screens or output's to the crossterm API. This makes the API much more easier to use.
_**old**_
_"Use this function when you want your terminal to operate with a specific output.
This could be useful when you have a screen which is in 'alternate mode', and you want your actions from the TerminalCursor, created by this function, to operate on the 'alternate screen'."_
Because crosstrem does not have to keep track of the output and you don't have to pass an `TerminalOutput` anymore those functions are removed.
```rust
use crossterm::{Screen, Terminal, TerminalCursor, TerminalColor, TerminalInput, Crossterm};
let screen = Screen::new(false);
Terminal::from_output(&screen.stdout);
TerminalCursor::from_output(&screen.stdout);
TerminalColor::from_output(&screen.stdout);
TerminalInput::from_output(&screen.stdout);
Crossterm::from_screen(&screen.stdout);
```
_**new**_
```rust
Terminal::new();
TerminalCursor::new();
TerminalColor::new();
TerminalInput::new();
Crossterm::new();
```
_"This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting"_
Because crosstrem does not have to keep track of the output anymore those functions are removed.
_**old**_
```rust
use crossterm::{Crossterm, Screen, style};
let screen = Screen::new(false);
style("Some colored text")
.with(Color::Blue)
.on(Color::Black)
.paint(&screen);
let crossterm = Crossterm::new();
crossterm.style("Some colored text")
.with(Color::Blue)
.on(Color::Black)
.paint(&screen);
```
_**new**_
```rust
print!("{}", "Some colored text".blue().on_black());
```
### Removed Types
`Screen` was removed because it hadn't any purpose anymore.
_**old**_
use crossterm::Screen;
```rust
// create raw screen
let screen = Screen::new(true);
// create alternate raw screen
let screen = Screen::new(true);
let alternate = screen.enable_raw_modes(true);
```
_**new**_
```rust
use crossterm::{AlternateScreen, RawScreen, IntoRawModes};
let raw_screen = RawScreen::into_raw_mode();
let raw_screen = stdout().into_raw_mode();
let alternate = AlternateScreen::to_alternate(true);
```
### Renamed Functions
```rust
RawScreen::disable_raw_modes => RawScreen::disable_raw_mode
AlternateScreen::to_alternate_screen => Alternate::to_alternate
AlternateScreen::to_main_screen => Alternate::to_main
```
## Upgrade crossterm to 0.8.0
This update will cause problems with `read_async`. `read_async` first returned a type implementing `Read` it returns an `Iterator` of input events now.
See the examples for details on how this works.

View File

@ -1,9 +1,8 @@
# Summary
This book will cover styling and user input. It will also give a detailed description about working with alternate and raw screen.
This book will cover styling, user input, terminal modes, and feature flags.
- [Feature Flags](feature_flags.md)
- [Styling Output](styling.md)
- [example](styling_example.md)
- [Async Input](input.md)
- [Reading Input Events](input.md)
- [Alternate, Raw Screen](screen.md)
- [example](screen_example.md)

View File

@ -1,9 +1,9 @@
From `crossterm 0.6` you are allowed to use feature flags.
From `crossterm 0.6` you are able to use feature flags.
With feature flags you can pick the features you want which reduces the size of the library and could prevent you from having unnecessary dependencies.
Crossterm provides the following feature flags:
- input ; reading input
- input ; reading input events
- terminal ; terminal actions like resizing
- style ; styling of the terminal
- cursor ; moving the terminal cursor
@ -15,7 +15,17 @@ _Cargo.toml_
```
[dependencies]
crossterm = { version="0.6", default-features = false, features = ["screen", "terminal", "cursor", "style", "input"] }
crossterm = { version="0.9", default-features = false, features = ["screen", "terminal", "cursor", "style", "input"] }
```
By default all flags are enabled, the types and functions available to use depend on the specified flags.
```rust
"cursor" => cursor, TerminalCursor
"input" => input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput
"screen" => AlternateScreen, IntoRawMode, RawScreen
"style" => color, style, Attribute, Color, Colored, Colorize, ObjectStyle, StyledObject, Styler, TerminalColor,
"terminal" => terminal, ClearType, Terminal
```
You can also use all the crossterm modules individually by directly referencing the crate.

View File

@ -2,21 +2,28 @@ Crossterm provides a way to work with the terminal input. We will not cover the
Please check out these [examples](https://github.com/TimonPost/crossterm/blob/master/examples/input/keyboard/input.rs) for reading a line or a character from the user.
## Differences Synchronous and Asynchronous
Crossterm provides two ways to read user input, synchronous and asynchronous.
Crossterm provides two ways to read user input, synchronous and 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."
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.
You can get an synchronous event reader by calling: `TerminalInput::read_sync`.
### Asynchronous reading
Read the input asynchronously, input events are gathered on the background and will be 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.
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.
You can get an synchronous event reader by calling: `TerminalInput::read_async`, `TerminalInput::read_async_until`.
### 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 for the user to iterate over.
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 raw mode, raw mode prevents the input of the user to be displayed on the terminal screen, see [screen](./screen.md) for more info.
# Example
In the following example, we will create a small program that will listen for mouse and keyboard input.
@ -26,7 +33,7 @@ So let's start by setting up the basics.
```
use std::{thread, time::Duration};
use crossterm::{TerminalInput, Screen, InputEvent, KeyEvent};
use crossterm::{input, InputEvent, KeyEvent};
fn main() {
println!("Press 'ESC' to quit.");
@ -38,19 +45,17 @@ fn main() {
Next, we need to put the terminal into raw mode. We do this because we don't want the user input to be printed to the terminal screen.
```rust
// enable raw modes by passing in 'true'
let screen = Screen::new(true);
// enable raw mode
let screen = RawScreen::into_raw_mode();
// create a input from our screen.
let input = TerminalInput::from_output(&screen.stdout);
// create a input from our screen
let input = input();
/* next code here */
```
Take note that we need to use our `Screen` to create a `TerminalInput` instance, check [this](screen.md#important-notice) out for more information about why that is.
Now that we have constructed a `TerminalInput` instance we can go ahead an start the reading.
Do this by calling `input.read_async()`, which returns an [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html).
Now that we constructed a `TerminalInput` instance we can go ahead an start the reading.
Do this by calling `input.read_async()`, which returns an [AsyncReader](https://docs.rs/crossterm/0.8.0/crossterm/struct.AsyncReader.html).
This is an iterator over the input events that you could as any other iterator.
```rust
@ -64,22 +69,23 @@ loop {
}
```
The [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html) iterator will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
The [AsyncReader](https://docs.rs/crossterm/0.8.0/crossterm/struct.AsyncReader.html) iterator will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
I use a thread delay to prevent spamming the iterator.
Next up we can start pattern matching to see if there are input events we'd like to catch.
In our case, we want to catch the `Escape Key`.
```rust
match key_event {
InputEvent::Keyboard(k) => match k {
match key_event {
InputEvent::Keyboard(event) => match event {
KeyEvent::Esc => {
println!("Program closing ...");
break
}
_ => println!("Key {:?} was pressed!", k)
_ => println!("Key {:?} was pressed!", event)
}
_ => { /* Mouse Event */ }
InputEvent::Mouse(event) => { /* Mouse Event */ }
_ => { }
}
```
@ -87,35 +93,39 @@ As you see, we check if the `KeyEvent::Esc` was pressed, if that's true we stop
_final code_
```rust
use std::{thread, time::Duration};
use crossterm::{input, InputEvent, KeyEvent};
fn main() {
println!("Press 'ESC' to quit.");
// enable raw modes by passing in 'true'
let screen = Screen::new(true);
// enable raw mode
let screen = RawScreen::into_raw_mode();
// create a input from our screen.
let input = TerminalInput::from_output(&screen.stdout);
let input = input();
// create async reader
let mut async_stdin = input.read_async();
loop {
// try to get the next input event.
if let Some(key_event) = async_stdin.next() {
match key_event {
InputEvent::Keyboard(k) => match k {
InputEvent::Keyboard(event) => match event {
KeyEvent::Esc => {
println!("Program closing ...");
break;
break
}
_ => println!("Key {:?} was pressed!", k)
_ => println!("Key {:?} was pressed!", event)
}
InputEvent::Mouse(event) => { /* Mouse Event */ }
_ => { }
}
}
thread::sleep(Duration::from_millis(50));
}
} // <=== background reader will be disposed when dropped.
} // <=== background reader will be disposed when dropped.s
```
---------------------------------------------------------------------------------------------------------------------------------------------
More robust and complete examples on all input aspects like mouse, keys could be found [here](https://github.com/TimonPost/crossterm/tree/master/examples/).

View File

@ -1,12 +1,9 @@
# Basic Usage of Screen
As you may have seen crossterm has a type called `Screen`. This type should be used when working with the alternate or raw screen.
Before we continue, I'll explain what those concepts are.
## Screen Buffer
A screen buffer is a two-dimensional array of characters and color data to be output in a console window. An terminal can have multiple of those screen buffers, and the active screen buffer is the one that is displayed on the screen.
A screen buffer is a two-dimensional array of characters and color data to be output in a console window.
An 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'.
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.
@ -14,7 +11,8 @@ For example, it has the exact dimensions of the terminal window, without any scr
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.
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 screen
To understand the concept of a 'raw screen' let's look at the following points:
@ -41,51 +39,5 @@ _example of what I mean_
To start at the beginning of the next line, use `\n\r`.
# Crossterm's implementation
When we want to print some text to the alternate screen we can't just write on it using `print!(), println!(), or write!()`.
This is because those functions are writing to the standard output and not to our alternate screen we are currently on.
The same goes for coloring, cursor movement, input, and terminal actions.
Crossterm provides a solution for that by introducing the `Screen` type.
You can use the 'alternate' or 'raw' screen functionalities by either using the [crossterm](https://crates.io/crates/crossterm) or [crossterm_screen](https://crates.io/crates/crossterm_screen) crate.
Please checkout this [example](screen_example.md) for more information on how to use it.
_Cargo.toml_
```rust
crossterm = { version = "0.6.0", features = ["screen","terminal","cursor", "style", "input"] }
```
```rust
use crossterm::{cursor, TerminalCursor};
use crossterm::{color, TerminalColor};
use crossterm::{input, TerminalInput};
use crossterm::{terminal, Terminal};
let screen = Screen::default();
if let Ok(alternate) = screen.enable_alternate_modes(false) {
// by calling 'from_screen' you force crossterm to use the screen of the alternate screen to perform actions on.
let crossterm = Crossterm::from_screen(&alternate.screen);
let cursor = crossterm.cursor();
let terminal =crossterm.terminal();
let input = crossterm.input();
let color = crossterm.color();
// you can also create instances directly without `Crossterm`
let screen = alternate.screen;
let terminal = Terminal::from_output(&screen.stdout);
let cursor = TerminalCursor::from_output(&screen.stdout);
let color = TerminalColor::from_output(&screen.stdout);
let input = TerminalInput::from_output(&screen.stdout);
}
```
The above modules will now all be executed at the 'alternate screen'.
---------------------------------------------------------------------------------------------------------------------------------------------
Next up: [Examples](screen_example.md)
More examples could be found [over here](https://github.com/TimonPost/crossterm/blob/master/examples/).

View File

@ -1,155 +0,0 @@
Let's build some basic programs who are using the alternate and raw screen.
# Raw Screen
_setup the basics_
```rust
extern crate crossterm;
use crossterm::Screen;
fn main() {
/* next code here */
}
```
We use the `Screen` type to enable raw mode for the terminal.
When creating a screen you have the option to pass it a boolean, this boolean specifies whether the screen will be in raw or normal mode.
Let's play around a bit to see what raw screen does.
```rust
// Create raw screen by passing in true.
let screen = Screen::new(true);
println!("Some text");
println!("Some text");
```
When you run this program you will directly see that the output is a little strange like:
```
Some text
Some text
```
Another fun thing to do is reading the input. This should not work since the input is not recorded by the terminal when in raw mode.
_Take note this will cause your terminal to freeze.
Since `read_line` expects some line input but it will never be recorded because of raw mode.
You should just restart the terminal when you are stuck_.
```rust
// Create raw screen by passing in true.
let screen = Screen::new(true);
let text = std::io::stdin().read_line(&mut string);
println!("{:?}", text);
```
Note that we spoke about the reason why this is [previously](screen.md#raw-screen).
However, if you want to read input in raw mode you should checkout [Async Input](input.md).
# Alternate Screen
_setup the basics_
```rust
extern crate crossterm;
use crossterm::Screen;
use std::{thread, time};
use std::io::Write;
fn main() {
/* next code here */
}
```
As we spoke of [previously](screen.md#alternate-screen), with `Screen` we can manage alternate screen and raw screen.
Let's make some simple program who is switching from the main to alternate screen whereafter it will wait for 2 seconds.
When those seconds are over we will go back to the main screen.
First off, create an instance of `Screen`. We can call `enable_alternate_modes()` on this screen, this will switch the screen buffer to the alternate buffer.
When we call this function we will get an `AlternateScreen` instance back which represents the alternate screen.
We should use this instance when we want to do any writing, styling, cursor, and terminal related things on the alternate screen ([Important Notice](screen.md##important-notice)).
```rust
let screen = Screen::default();
if let Ok(mut alternate) = screen.enable_alternate_modes(false) {
/* next code here */
}
```
Next, we use the instance we got back and write our message to it whereafter we wait for 2 seconds.
We wait 2 seconds to give us some time to see the alternate screen.
If the `AlternateScreen` goes out of scope it will automatically switch back to the main screen.
```rust
write!(alternate.screen, "{}", "Welcome to the wait screen.\n Please wait a 2 seconds until we arrive back at the main screen.");
thread::sleep(time::Duration::from_secs(2));
```
By now you should be able to execute the program, you will see that directly you are being redirected to another screen with no scrollback region.
You will see this screen open whereafter it closes after 2 seconds.
When the program finishes you will notice you are on the main screen again with it's contents unmodified.
# Perform Actions on Alternate Screen.
Now we have covered the basics of alternate screen let's make a program who styles some text on the 'raw' alternate screen.
_setup the basics_
```rust
extern crate crossterm;
use crossterm::Screen;
use std::{thread, time};
use std::io::Write;
fn main() {
// switch to alternate screen and make it raw by passing in true.
if let Ok(mut alternate) = screen.enable_alternate_modes(true) {
/* next code here */
}
}
```
Some basics steps the following code will do:
1. Create [Crossterm]() type to manage the cursor and styling
2. Set the position of the cursor to x: 0 and y: 0
3. Write the styled text, you can use the two described ways
4. Set the position of the cursor to x: 0 and y: 1
5. Write other text and flush it to the screen
6. Sleep two seconds to see the results
```rust
let alternate_screen = &mut alternate.screen;
// by calling 'from_screen' the cursor will be moved at the passed screen.
let crossterm = Crossterm::from_screen(alternate_screen);
let cursor = crossterm.cursor();
cursor.goto(0,0);
// 1) print the 'styled object' by converting it into a type that is displayable for alternate screen.
println!("{}", crossterm.style("Welcome to the wait screen.")
.with(Color::Red)
.on(Color::Blue)
.into_displayable(alternate_screen)
);
// 2) use the `paint` method to print it to the alternate screen.
crossterm.style("Welcome to the wait screen.")
.with(Color::Red)
.on(Color::Blue)
.paint(alternate_screen);
cursor.goto(0,1);
write!(alternate_screen, "{}", "Please wait a few seconds until we arrive back at the main screen.");
alternate_screen.flush();
thread::sleep(time::Duration::from_secs(2));
```
As you might have noticed, you need to to manually move the cursor, flush the buffer. This is because the terminal is in raw modes.
Also, by printing with `paint` or calling `into_displayable` you pass in a reference to the alternate screen.
Tip: Take a look at [how](screen.md#Crossterm's implementation) you should use other modules crossterm provides on the alternate screen.
---------------------------------------------------------------------------------------------------------------------------------------------
More examples could be found at this [link](https://github.com/TimonPost/crossterm/tree/master/examples/terminal).

View File

@ -21,6 +21,7 @@ There are 16 base colors which available for almost all terminals even windows 7
In addition to 16 colors, most UNIX terminals and Windows 10 consoles are also supporting more colors.
Those colors could be: [True color (24-bit)](https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)) coloring scheme, which allows you to use [RGB](https://nl.wikipedia.org/wiki/RGB-kleursysteem), and [256 (Xterm, 8-bit)](https://jonasjacek.github.io/colors/) colors.
Checkout the examples on how to use this feature.
## Attributes
Only UNIX and Windows 10 terminals are supporting attributes on top of the text. Crossterm allows you to add attributes to the text.
@ -46,7 +47,7 @@ Crossterm implements almost all attributes shown in this [Wikipedia-list](https:
| Encircled | Unknown | This will turn on the encircled attribute. |
| OverLined | Unknown | This will draw a line at the top of the font. |
(There are a few attributes who disable one of the above, I did not write those down in the above scheme).
(There are a few attributes who disable one of the above attributes, I did not write those down to keep the list short).
Now we have covered the basics of styling lets go some [examples](styling_example.md).

View File

@ -1,12 +1,10 @@
extern crate crossterm;
use crossterm::{ClearType, Color, Crossterm, Screen, style};
use std::io::{stdout, Write};
use crossterm::{style, AlternateScreen, ClearType, Color, Crossterm};
use std::{thread, time};
fn print_wait_screen(screen: &Screen) {
let crossterm = Crossterm::from_screen(screen);
fn print_wait_screen() {
let crossterm = Crossterm::new();
let terminal = crossterm.terminal();
let cursor = crossterm.cursor();
@ -22,10 +20,12 @@ fn print_wait_screen(screen: &Screen) {
for i in 1..5 {
// print the current counter at the line of `Seconds to Go: {counter}`
cursor.goto(10, 2);
style(format!("{} of the 5 items processed", i))
.with(Color::Red)
.on(Color::Blue)
.paint(&screen.stdout);
println!(
"{}",
style(format!("{} of the 5 items processed", i))
.with(Color::Red)
.on(Color::Blue)
);
// 1 second delay
thread::sleep(time::Duration::from_secs(1));
@ -34,13 +34,11 @@ fn print_wait_screen(screen: &Screen) {
/// print wait screen on alternate screen, then switch back.
pub fn print_wait_screen_on_alternate_window() {
let screen = Screen::default();
if let Ok(alternate) = screen.enable_alternate_modes(false) {
print_wait_screen(&alternate.screen);
if let Ok(alternate) = AlternateScreen::to_alternate(false) {
print_wait_screen();
}
}
fn main() {
print_wait_screen_on_alternate_window();
}
}

View File

@ -1,9 +1,9 @@
extern crate crossterm;
use crossterm::{Crossterm, Screen, Color};
use crossterm::{Color, Crossterm};
/// use the `Crossterm` to get an instance to the cursor module | demonstration.
pub fn crossterm() {
pub fn main() {
// Create the crossterm type to access different modules.
let crossterm = Crossterm::new();
@ -12,7 +12,10 @@ pub fn crossterm() {
let color = crossterm.color();
let terminal = crossterm.terminal();
let terminal = crossterm.input();
let style = crossterm.style("Black font on green background").with(Color::Black).on(Color::Green);
let style = crossterm
.style("Black font on green background")
.with(Color::Black)
.on(Color::Green);
// TODO: perform some actions with the instances above.
}

View File

@ -4,7 +4,7 @@
extern crate crossterm_cursor;
use crossterm_cursor::{cursor, TerminalCursor};
use crossterm_cursor::cursor;
/// Set the cursor to position X: 10, Y: 5 in the terminal.
pub fn goto() {
@ -87,6 +87,5 @@ pub fn blink_cursor() {
}
fn main() {
goto();
pos();
save_and_reset_position();
}

View File

@ -1,6 +1,6 @@
extern crate crossterm;
use self::crossterm::{KeyEvent, TerminalInput, input, Screen};
use self::crossterm::input;
pub fn read_char() {
let input = input();
@ -20,13 +20,7 @@ pub fn read_line() {
}
}
pub fn pause_terminal() {
println!("Press 'x' to quit...");
let screen = Screen::new(true);
let terminal_input = TerminalInput::from_output(&screen.stdout);
terminal_input.wait_until(KeyEvent::OnKeyPress(b'x'));
}
fn main(){
pause_terminal();
fn main() {
read_line();
read_char();
}

View File

@ -1,117 +1,72 @@
extern crate crossterm;
use crossterm::{
InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, Screen
};
use crossterm::{input, InputEvent, KeyEvent, MouseButton, MouseEvent, RawScreen};
use std::{thread, time::Duration};
fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
fn process_input_event(key_event: InputEvent) -> bool {
match key_event {
InputEvent::Keyboard(k) => match k {
KeyEvent::Char(c) => match c {
'q' => {
screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap();
return true;
InputEvent::Keyboard(k) => {
match k {
KeyEvent::Char(c) => match c {
'q' => {
println!("The 'q' key is hit and the program is not listening to input anymore.\n\n");
return true;
}
_ => {
println!("{}", format!("'{}' pressed\n\n", c));
}
},
KeyEvent::Alt(c) => {
println!("{}", format!("ALT +'{}' pressed\n\n", c));
}
KeyEvent::Ctrl(c) => {
println!("{}", format!("CTRL +'{}' Pressed\n\n", c));
}
KeyEvent::Esc => {
println!("{}", format!("ESC pressed\n\n"));
}
KeyEvent::F(number) => {
println!("{}", format!("F{} key pressed\n\n", number));
}
KeyEvent::PageUp => {
println!("{}", format!("Page Up\n\n"));
}
KeyEvent::PageDown => {
println!("{}", format!("Page Down\n\n"));
}
KeyEvent::Delete => {
println!("{}", format!("Delete\n\n"));
}
_ => {
screen
.stdout
.write_string(format!("'{}' pressed\n\n", c))
.unwrap();
println!("{}", format!("OTHER: {:?}\n\n", k));
()
}
},
KeyEvent::Alt(c) => {
screen
.stdout
.write_string(format!("ALT +'{}' pressed\n\n", c))
.unwrap();
}
KeyEvent::Ctrl(c) => {
screen
.stdout
.write_string(format!("CTRL +'{}' Pressed\n\n", c))
.unwrap();
}
KeyEvent::Esc => {
screen
.stdout
.write_string(format!("ESC pressed\n\n"))
.unwrap();
}
KeyEvent::F(number) => {
screen
.stdout
.write_string(format!("F{} key pressed\n\n", number))
.unwrap();
}
KeyEvent::PageUp => {
screen.stdout.write_string(format!("Page Up\n\n")).unwrap();
}
KeyEvent::PageDown => {
screen
.stdout
.write_string(format!("Page Down\n\n"))
.unwrap();
}
KeyEvent::Delete => {
screen.stdout.write_string(format!("Delete\n\n")).unwrap();
}
_ => {
screen
.stdout
.write_string(format!("OTHER: {:?}\n\n", k))
.unwrap();
()
}
},
}
InputEvent::Mouse(m) => match m {
MouseEvent::Press(b, x, y) => match b {
MouseButton::Left => {
screen
.stdout
.write_string(format!("left mouse press @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("left mouse press @ {}, {}\n\n", x, y));
}
MouseButton::Right => {
screen
.stdout
.write_string(format!("right mouse press @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("right mouse press @ {}, {}\n\n", x, y));
}
MouseButton::Middle => {
screen
.stdout
.write_string(format!("mid mouse press @ {}, {}\n\n", x, y))
.unwrap();
}
MouseButton::WheelUp => {
screen
.stdout
.write_string(format!("wheel up @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("mid mouse press @ {}, {}\n\n", x, y));
}
MouseButton::WheelUp => println!("{}", format!("wheel up @ {}, {}\n\n", x, y)),
MouseButton::WheelDown => {
screen
.stdout
.write_string(format!("wheel down @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("wheel down @ {}, {}\n\n", x, y));
}
},
MouseEvent::Release(x, y) => {
screen
.stdout
.write_string(format!("mouse released @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("mouse released @ {}, {}\n\n", x, y));
}
MouseEvent::Hold(x, y) => {
screen
.stdout
.write_string(format!("dragging @ {}, {}\n\n", x, y))
.unwrap();
println!("{}", format!("dragging @ {}, {}\n\n", x, y));
}
_ => {
screen.stdout.write_str("Unknown mouse event").unwrap();
println!("{}", "Unknown mouse event");
}
},
_ => println!("Unknown!"),
@ -122,56 +77,56 @@ fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool {
pub fn read_asynchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
if let Ok(_raw) = RawScreen::into_raw_mode() {
let input = input();
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut stdin = input.read_async();
let stdin = input.read_async();
loop {
if let Some(key_event) = stdin.next() {
if process_input_event(key_event, &screen) {
break;
loop {
if let Some(key_event) = stdin.next() {
if process_input_event(key_event) {
break;
}
}
thread::sleep(Duration::from_millis(50));
}
thread::sleep(Duration::from_millis(50));
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
} // <=== raw modes will be disabled here
} // <=== background reader will be disposed when dropped.
pub fn read_synchronously() {
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
let screen = Screen::new(true);
if let Ok(_raw) = RawScreen::into_raw_mode() {
let input = input();
let input = TerminalInput::from_output(&screen.stdout);
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
let mut sync_stdin = input.read_sync();
let mut sync_stdin = input.read_sync();
loop {
let event = sync_stdin.next();
loop {
let event = sync_stdin.next();
if let Some(key_event) = event {
if process_input_event(key_event, &screen) {
break;
if let Some(key_event) = event {
if process_input_event(key_event) {
break;
}
}
}
}
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
} // <=== raw modes will be disabled here
}
fn main() {
// un-comment below and run with
// `cargo run --example key_events`:
// read_synchronously();
read_asynchronously();
}
read_synchronously();
// read_asynchronously();
}

View File

@ -1,24 +1,19 @@
extern crate crossterm;
use crossterm::{
input, ClearType, Crossterm, InputEvent, KeyEvent, Screen, Terminal, TerminalCursor,
input, cursor, terminal, ClearType, Crossterm, InputEvent, KeyEvent, AlternateScreen, RawScreen, Terminal, TerminalCursor
};
use std::io::Read;
use std::sync::{Arc, Mutex};
use std::{thread, time};
fn main() {
use crossterm::color;
let screen = Screen::new(true);
let crossterm = Crossterm::from_screen(&screen);
let cursor = crossterm.cursor();
cursor.hide();
let screen = RawScreen::into_raw_mode();
cursor().hide();
let input_buf = Arc::new(Mutex::new(String::new()));
let threads = log(input_buf.clone(), &screen);
let threads = log(input_buf.clone());
let mut count = 0;
@ -47,19 +42,18 @@ fn main() {
thread.join();
}
cursor.show();
cursor().show();
}
fn log(input_buf: Arc<Mutex<String>>, screen: &Screen) -> Vec<thread::JoinHandle<()>> {
fn log(input_buf: Arc<Mutex<String>>) -> Vec<thread::JoinHandle<()>> {
let mut threads = Vec::with_capacity(10);
let (_, term_height) = Terminal::from_output(&screen.stdout).terminal_size();
let (_, term_height) = terminal().terminal_size();
for i in 0..1 {
let input_buffer = input_buf.clone();
let _clone_stdout = screen.stdout.clone();
let crossterm = Crossterm::from(screen.stdout.clone());
let crossterm = Crossterm::new();
let join = thread::spawn(move || {
let cursor = crossterm.cursor();
@ -93,5 +87,5 @@ pub fn swap_write(
cursor.goto(0, term_height);
terminal.clear(ClearType::CurrentLine);
terminal.write(format!("{}\r\n", msg));
terminal.write(format!(">{}", input_buf));
terminal.write(format!("> {}", input_buf));
}

View File

@ -2,37 +2,29 @@
use super::map::Map;
use super::variables::{Direction, Position};
use crossterm::{Color, Crossterm, Screen};
use crossterm::{Color, Colored, Colorize, Crossterm};
use std::io::{stdout, Write};
use super::rand;
use super::rand::distributions::{IndependentSample, Range};
use std::io::Write;
use std::{thread, time};
pub struct FirstDepthSearch<'screen> {
pub struct FirstDepthSearch {
direction: Direction,
map: Map,
stack: Vec<Position>,
root_pos: Position,
is_terminated: bool,
screen: &'screen Screen,
}
impl<'screen> FirstDepthSearch<'screen> {
pub fn new(
map: Map,
start_pos: Position,
crossterm: &'screen Screen,
) -> FirstDepthSearch<'screen> {
impl FirstDepthSearch {
pub fn new(map: Map, start_pos: Position) -> FirstDepthSearch {
FirstDepthSearch {
direction: Direction::Up,
map,
stack: Vec::new(),
root_pos: start_pos,
is_terminated: false,
screen: crossterm,
}
}
@ -42,10 +34,18 @@ impl<'screen> FirstDepthSearch<'screen> {
// push first position on the stack
self.stack.push(self.root_pos);
let crossterm = Crossterm::from_screen(&self.screen);
let mut cursor = crossterm.cursor();
let crossterm = Crossterm::new();
let cursor = crossterm.cursor();
cursor.hide();
write!(
::std::io::stdout(),
"{}{}",
Colored::Fg(Color::Green),
Colored::Bg(Color::Black)
);
::std::io::stdout().flush();
// loop until there are now items left in the stack.
loop {
if self.stack.len() == 0 {
@ -60,16 +60,14 @@ impl<'screen> FirstDepthSearch<'screen> {
self.update_position();
let cell = crossterm.style(" ").on(Color::Blue);
let pos = self.root_pos.clone();
let x = pos.x as u16;
let y = pos.y as u16;
cursor.goto(x, y);
cell.paint(&self.screen.stdout);
self.screen.stdout.flush();
write!(stdout(), "{}", " ".on_yellow());
stdout().flush();
thread::sleep(time::Duration::from_millis(1));
}

View File

@ -6,12 +6,12 @@ mod map;
mod messages;
mod variables;
use self::crossterm::{ClearType, Color, Crossterm, InputEvent, KeyEvent, Screen};
use self::messages::WELCOME_MESSAGE;
use self::crossterm::{
color, cursor, input, terminal, AlternateScreen, ClearType, Color, Colored, Crossterm,
InputEvent, KeyEvent, RawScreen,
};
use self::variables::{Position, Size};
use std::io::Read;
use std::iter::Iterator;
use std::{thread, time};
@ -21,40 +21,38 @@ fn main() {
/// run the program
pub fn run() {
// let screen = RawScreen::into_raw_mode().expect("failed to enable raw modes");
print_welcome_screen();
// This is represents the current screen.
let mut screen = Screen::new(true);
start_algorithm(&screen);
start_algorithm();
exit();
}
fn start_algorithm(screen: &Screen) {
fn start_algorithm() {
// we first want to switch to alternate screen. On the alternate screen we are going to run or firstdepthsearch algorithm
if let Ok(ref alternate_screen) = screen.enable_alternate_modes(true) {
if let Ok(ref alternate_screen) = AlternateScreen::to_alternate(true) {
// setup the map size and the position to start searching for a path.
let map_size = Size::new(50, 40);
let start_pos = Position::new(10, 10);
// create and render the map. Or map border is going to have an █ look and inside the map is just a space.
let mut map = map::Map::new(map_size, '█', ' ');
map.render_map(&alternate_screen.screen);
map.render_map();
// create the algorithm and start it on the alternate screen. Make sure to pass the refrence to the AlternateScreen screen.
let mut algorithm =
algorithm::FirstDepthSearch::new(map, start_pos, &alternate_screen.screen);
let mut algorithm = algorithm::FirstDepthSearch::new(map, start_pos);
algorithm.start();
}
}
fn print_welcome_screen() {
let mut screen = Screen::new(true);
let crossterm = Crossterm::from_screen(&screen);
// we have to keep this screen arround to prevent te
let _screen = RawScreen::into_raw_mode();
let crossterm = Crossterm::new();
// create the handle for the cursor and terminal.
let terminal = crossterm.terminal();
let cursor = crossterm.cursor();
let input = crossterm.input();
let terminal = terminal();
let cursor = cursor();
let input = input();
// set size of terminal so the map we are going to draw is fitting the screen.
terminal.set_size(110, 60);
@ -63,10 +61,12 @@ fn print_welcome_screen() {
terminal.clear(ClearType::All);
cursor.goto(0, 0);
crossterm
.style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r")))
.with(Color::Cyan)
.paint(&screen.stdout);
print!(
"{}",
crossterm
.style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r")))
.with(Color::Cyan)
);
cursor.hide();
cursor.goto(0, 10);
@ -80,20 +80,29 @@ fn print_welcome_screen() {
// print some progress example.
for i in (1..5).rev() {
if let Some(InputEvent::Keyboard(KeyEvent::Char('q'))) = stdin.next() {
drop(screen);
exit();
terminal.exit();
break;
} else {
// print the current counter at the line of `Seconds to Go: {counter}`
cursor.goto(48, 10);
crossterm
.style(format!("{}", i))
.with(Color::Red)
.on(Color::Blue)
.paint(&screen.stdout);
print!(
"{}{}{}",
Colored::Fg(Color::Red),
Colored::Bg(Color::Blue),
i
);
}
color().reset();
// 1 second delay
thread::sleep(time::Duration::from_secs(1));
}
}
fn exit() {
RawScreen::disable_raw_mode().expect("failed to disable raw modes.");
cursor().show();
color().reset();
}

View File

@ -1,5 +1,6 @@
use super::variables::{Cell, Position, Size};
use crossterm::{cursor, Color, Crossterm, Screen};
use crossterm::{cursor, Color, Crossterm};
use std::io::{stdout, Write};
pub struct Map {
pub map: Vec<Vec<Cell>>,
@ -42,8 +43,8 @@ impl Map {
}
// render the map on the screen.
pub fn render_map(&mut self, screen: &Screen) {
let crossterm = Crossterm::from_screen(screen);
pub fn render_map(&mut self) {
let crossterm = Crossterm::new();
for row in self.map.iter_mut() {
for column in row.iter_mut() {
@ -51,9 +52,13 @@ impl Map {
if (column.position.y == 0 || column.position.y == self.size.height - 1)
|| (column.position.x == 0 || column.position.x == self.size.width - 1)
{
let cell_style = crossterm.style(column.look).on(column.color);
cursor().goto(column.position.x as u16, column.position.y as u16);
cell_style.paint(&screen.stdout);
write!(
stdout(),
"{}",
crossterm.style(column.look).on(column.color)
);
stdout().flush();
}
}
}

View File

@ -8,13 +8,3 @@ pub const WELCOME_MESSAGE: [&str; 6] = [
" \\__/\\ / \\___ >____/__|_ \\____/|__|_| /\\___ > ",
" \\/ \\/ \\/ \\/ \\/ ",
];
pub const END_MESSAGE: [&str; 5] = [
"-----------------------",
" ",
" No routes (DONE) ",
" ",
"-----------------------",
];
pub fn print_stack_count(position: Position) {}

View File

@ -1,6 +1,6 @@
extern crate crossterm;
use self::crossterm::{Color, ObjectStyle, StyledObject};
use self::crossterm::Color;
#[derive(Copy, Clone, Debug)]
pub enum Direction {

View File

@ -1,152 +0,0 @@
extern crate crossterm;
use crossterm::Screen;
use std::collections::VecDeque;
use std::sync::{
mpsc::{self, Receiver, Sender},
Arc, Mutex,
};
use std::thread::{self, JoinHandle};
/// This is an que that could be shared between threads safely.
#[derive(Clone)]
struct WorkQueue<T: Send + Clone> {
inner: Arc<Mutex<VecDeque<T>>>,
}
impl<T: Send + Clone> WorkQueue<T> {
fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(VecDeque::new())),
}
}
// get an item from the que if exists
fn get_work(&self) -> Option<T> {
let maybe_queue = self.inner.lock();
if let Ok(mut queue) = maybe_queue {
queue.pop_front()
} else {
panic!("WorkQueue::get_work() tried to lock a poisoned mutex");
}
}
// add an item to the que
fn add_work(&self, work: T) -> usize {
if let Ok(mut queue) = self.inner.lock() {
queue.push_back(work);
queue.len()
} else {
panic!("WorkQueue::add_work() tried to lock a poisoned mutex");
}
}
}
#[derive(Clone)]
struct SyncFlagTx {
inner: Arc<Mutex<bool>>,
}
impl SyncFlagTx {
pub fn set(&mut self, state: bool) -> Result<(), ()> {
if let Ok(mut v) = self.inner.lock() {
*v = state;
Ok(())
} else {
Err(())
}
}
}
#[derive(Clone)]
struct SyncFlagRx {
inner: Arc<Mutex<bool>>,
}
impl SyncFlagRx {
pub fn get(&self) -> Result<bool, ()> {
if let Ok(v) = self.inner.lock() {
Ok(*v)
} else {
Err(())
}
}
}
fn new_sync_flag(initial_state: bool) -> (SyncFlagTx, SyncFlagRx) {
let state = Arc::new(Mutex::new(initial_state));
let tx = SyncFlagTx {
inner: state.clone(),
};
let rx = SyncFlagRx {
inner: state.clone(),
};
return (tx, rx);
}
fn main() {
let (_results_tx, _results_rx): (Sender<String>, Receiver<String>) = mpsc::channel();
let (more_jobs_tx, more_jobs_rx) = new_sync_flag(true);
// queue with all log entry's.
let queue = WorkQueue::new();
// queue x logs with different threads.
let _thread_handles = log_with_different_threads(more_jobs_tx.clone(), queue.clone());
// a thread that will log all logs in the queue.
handle_incoming_logs(more_jobs_rx.clone(), queue.clone());
}
fn handle_incoming_logs(more_jobs_rx: SyncFlagRx, queue: WorkQueue<String>) {
thread::spawn(move || {
let screen: Screen = Screen::default();
// Loop while there's expected to be work, looking for work.
while more_jobs_rx.get().unwrap() {
// If work is available, do that work.
if let Some(work) = queue.get_work() {
let mut log = work;
log.push('\n');
// write the log
screen.stdout.write_string(log);
}
std::thread::yield_now();
}
})
.join();
}
// start different threads that log contiguously.
fn log_with_different_threads(
more_jobs_tx: SyncFlagTx,
queue: WorkQueue<String>,
) -> Vec<JoinHandle<()>> {
// one vector that will have the thread handles in it.
let mut threads = Vec::new();
for thread_num in 1..5 {
let mut more_jobs = more_jobs_tx.clone();
let thread_queue = queue.clone();
// create new thread
let thread = thread::spawn(move || {
// log 400 messages
for log_entry_count in 1..400 {
thread_queue.add_work(format!(
"Log {} from thread {} ",
log_entry_count, thread_num
));
more_jobs.set(true);
}
});
threads.push(thread);
}
println!("All logging threads started");
return threads;
}

View File

@ -7,7 +7,7 @@ mod snake;
mod variables;
use self::crossterm::{
AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, Screen,
AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, RawScreen,
};
use map::Map;
@ -15,7 +15,6 @@ use snake::Snake;
use variables::{Direction, Position, Size};
use std::collections::HashMap;
use std::io::Write;
use std::iter::Iterator;
use std::{thread, time};
@ -23,8 +22,8 @@ fn main() {
let map_size = ask_size();
// screen has to be in raw mode in order for the key presses not to be printed to the screen.
let screen = Screen::new(true);
let crossterm = Crossterm::from_screen(&screen);
let raw = RawScreen::into_raw_mode();
let crossterm = Crossterm::new();
crossterm.cursor().hide();
@ -34,7 +33,7 @@ fn main() {
// render the map
let mut map = Map::new(map_size);
map.render_map(&screen, &mut free_positions);
map.render_map(&mut free_positions);
let mut snake = Snake::new();
@ -43,7 +42,7 @@ fn main() {
free_positions.remove_entry(format!("{},{}", part.position.x, part.position.y).as_str());
}
map.spawn_food(&free_positions, &screen);
map.spawn_food(&free_positions);
let mut stdin = crossterm.input().read_async();
let mut snake_direction = Direction::Right;
@ -54,16 +53,16 @@ fn main() {
snake_direction = new_direction;
}
snake.move_snake(&snake_direction, &screen, &mut free_positions);
snake.move_snake(&snake_direction, &mut free_positions);
if map.is_out_of_bounds(snake.snake_parts[0].position) {
break;
}
snake.draw_snake(&screen);
snake.draw_snake();
if snake.has_eaten_food(map.foot_pos) {
map.spawn_food(&free_positions, &screen);
map.spawn_food(&free_positions);
}
thread::sleep(time::Duration::from_millis(400));

View File

@ -1,6 +1,6 @@
use super::variables::{Position, Size};
use crossterm::{style, Color, Colorize, Crossterm, Screen, TerminalCursor};
use crossterm::{cursor, Colorize, TerminalCursor};
use rand::distributions::{IndependentSample, Range};
@ -22,16 +22,12 @@ impl Map {
}
// render the map on the screen.
pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap<String, Position>) {
let crossterm = Crossterm::from_screen(screen);
let cursor = crossterm.cursor();
let terminal = crossterm.terminal();
pub fn render_map(&mut self, free_positions: &mut HashMap<String, Position>) {
for y in 0..self.size.height {
for x in 0..self.size.height {
if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) {
cursor.goto(x as u16, y as u16);
"".magenta().paint(&screen.stdout);
cursor().goto(x as u16, y as u16);
print!("{}", "".magenta());
} else {
free_positions.insert(format!("{},{}", x, y), Position::new(x, y));
}
@ -53,15 +49,15 @@ impl Map {
snake_head.x == self.foot_pos.x && snake_head.y == self.foot_pos.y
}
pub fn spawn_food(&mut self, free_positions: &HashMap<String, Position>, screen: &Screen) {
pub fn spawn_food(&mut self, free_positions: &HashMap<String, Position>) {
let index = Range::new(0, free_positions.len()).ind_sample(&mut rand::thread_rng());
self.foot_pos = free_positions.values().skip(index).next().unwrap().clone();
self.draw_food(screen);
self.draw_food();
}
fn draw_food(&self, screen: &Screen) {
let cursor = TerminalCursor::from_output(&screen.stdout);
fn draw_food(&self) {
let cursor = TerminalCursor::new();
cursor.goto(self.foot_pos.x as u16, self.foot_pos.y as u16);
"$".green().paint(&screen.stdout);
print!("{}", "$".green());
}
}

View File

@ -1,5 +1,5 @@
use super::variables::{Direction, Position, Size};
use crossterm::{Crossterm, Screen};
use super::variables::{Direction, Position};
use crossterm::Crossterm;
use std::collections::HashMap;
@ -31,18 +31,13 @@ impl Snake {
pub fn move_snake(
&mut self,
direction: &Direction,
screen: &Screen,
free_positions: &mut HashMap<String, Position>,
) {
let crossterm = Crossterm::from_screen(screen);
let cursor = crossterm.cursor();
let terminal = crossterm.terminal();
let count = self.snake_parts.len();
for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() {
if index == count - 1 {
snake_part.position.remove(screen);
snake_part.position.remove();
free_positions.insert(
format!("{},{}", snake_part.position.x, snake_part.position.y),
snake_part.position,
@ -70,9 +65,9 @@ impl Snake {
}
}
pub fn draw_snake(&mut self, screen: &Screen) {
pub fn draw_snake(&mut self) {
for snake_part in self.snake_parts.iter_mut() {
snake_part.position.draw("", screen);
snake_part.position.draw("");
}
}

View File

@ -1,6 +1,8 @@
extern crate crossterm;
use self::crossterm::{style, Color, Crossterm, Screen, TerminalCursor};
use self::crossterm::{style, Color, Crossterm, TerminalCursor};
use std::io::stdout;
use std::io::Write;
#[derive(Copy, Clone, Debug)]
pub enum Direction {
@ -21,16 +23,16 @@ impl Position {
Position { x, y }
}
pub fn draw(&self, val: &str, screen: &Screen) {
let cursor = TerminalCursor::from_output(&screen.stdout);
pub fn draw(&self, val: &str) {
let cursor = TerminalCursor::new();
cursor.goto(self.x as u16, self.y as u16);
style(val).with(Color::Red).paint(&screen.stdout);
screen.stdout.flush();
print!("{}", style(val).with(Color::Red));
stdout().flush();
}
pub fn remove(&self, screen: &Screen) {
let crossterm = Crossterm::from_screen(screen);
pub fn remove(&self) {
let crossterm = Crossterm::new();
crossterm.cursor().goto(self.x as u16, self.y as u16);
crossterm.terminal().write(" ");

View File

@ -1,12 +1,12 @@
extern crate crossterm;
use crossterm::{Crossterm, Screen, terminal, ClearType, Color, style};
use crossterm::{style, terminal, AlternateScreen, ClearType, Color, Crossterm, RawScreen};
use std::io::{stdout, Write};
use std::{thread, time};
fn print_wait_screen(screen: &mut Screen) {
let crossterm = Crossterm::from_screen(screen);
fn print_wait_screen() {
let crossterm = Crossterm::new();
let terminal = crossterm.terminal();
let cursor = crossterm.cursor();
@ -14,22 +14,25 @@ fn print_wait_screen(screen: &mut Screen) {
cursor.hide();
cursor.goto(0, 0);
screen.write(b"Welcome to the wait screen.");
println!("Welcome to the wait screen.");
cursor.goto(0, 1);
screen.write(b"Please wait a few seconds until we arrive back at the main screen.");
println!("Please wait a few seconds until we arrive back at the main screen.");
cursor.goto(0, 2);
screen.write(b"Progress:");
println!("Progress:");
cursor.goto(0, 3);
// print some progress example.
for i in 1..5 {
// print the current counter at the line of `Seconds to Go: {counter}`
cursor.goto(10, 2);
style(format!("{} of the 5 items processed", i))
.with(Color::Red)
.on(Color::Blue)
.paint(&screen.stdout);
screen.stdout.flush();
print!(
"{}",
style(format!("{} of the 5 items processed", i))
.with(Color::Red)
.on(Color::Blue)
);
stdout().flush();
// 1 second delay
thread::sleep(time::Duration::from_secs(1));
@ -37,18 +40,12 @@ fn print_wait_screen(screen: &mut Screen) {
}
pub fn print_wait_screen_on_alternate_window() {
let screen = Screen::default();
// by passing in 'true' the alternate screen will be in raw modes.
if let Ok(ref mut alternate) = screen.enable_alternate_modes(true) {
print_wait_screen(&mut alternate.screen);
if let Ok(alternate) = AlternateScreen::to_alternate(true) {
print_wait_screen();
} // <- drop alternate screen; this will cause the alternate screen to drop.
drop(screen); // <- drop screen; this will cause raw mode to be turned off.
println!("Whe are back at the main screen");
}
fn main() {
print_wait_screen_on_alternate_window();
}
}

View File

@ -1,12 +1,9 @@
//!
//! Examples of coloring the terminal.
//!
#[macro_use]
extern crate crosstterm;
extern crate crossterm;
use self::crossterm_style::{
color, style, Attribute, Color, Colored, Colorize, Styler, TerminalColor,
};
use self::crossterm::{color, style, Attribute, Color, Colored, Colorize, Styler};
/// print some red font | demonstration.
pub fn paint_foreground() {

Some files were not shown because too many files have changed in this diff Show More