Sub-crates separatation (#251)

This commit is contained in:
Zrzka 2019-09-25 16:09:16 +02:00 committed by Timon
parent e600eabaed
commit 675c8f5e10
131 changed files with 165 additions and 9883 deletions

8
.gitignore vendored
View File

@ -1,5 +1,5 @@
target/ **/target/
.idea/ **/.idea/
.vscode/ **/.vscode/
**/*.rs.bk **/*.rs.bk
Cargo.lock **/Cargo.lock

View File

@ -1,4 +1,3 @@
# Set up the Rust toolchain.
language: rust language: rust
rust: rust:
@ -28,5 +27,4 @@ script:
- rustc --version - rustc --version
- if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi - if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi
- cargo build - cargo build
- cargo test --all -- --nocapture --test-threads 1 - cargo test --all-features -- --nocapture --test-threads 1
- scripts/test-examples.sh

View File

@ -1,12 +1,20 @@
# Changes crossterm 0.11.0 # Version 0.11.1
- Maintenance release
- All sub-crates were moved to their own repositories in the `crossterm-rs` organization
# Version 0.11.0
As a preparation for crossterm 0.1.0 we have moved crossterm to an organisation called 'crossterm-rs'. As a preparation for crossterm 0.1.0 we have moved crossterm to an organisation called 'crossterm-rs'.
### Code Quality ### Code Quality
- Code Cleanup: [warning-cleanup], [crossterm_style-cleanup], [crossterm_screen-cleanup], [crossterm_terminal-cleanup], [crossterm_utils-cleanup], [2018-cleanup], [api-cleanup-1], [api-cleanup-2], [api-cleanup-3] - Code Cleanup: [warning-cleanup], [crossterm_style-cleanup], [crossterm_screen-cleanup], [crossterm_terminal-cleanup], [crossterm_utils-cleanup], [2018-cleanup], [api-cleanup-1], [api-cleanup-2], [api-cleanup-3]
- Examples: [example-cleanup_1], [example-cleanup_2], [example-fix], [commandbar-fix], [snake-game-improved] - Examples: [example-cleanup_1], [example-cleanup_2], [example-fix], [commandbar-fix], [snake-game-improved]
- Fixed all broken tests and added tests - Fixed all broken tests and added tests
### Important Changes ### Important Changes
- Return written bytes: [return-written-bytes] - Return written bytes: [return-written-bytes]
- Added derives: `Debug` for `ObjectStyle` [debug-derive], Serialize/Deserialize for key events [serde] - Added derives: `Debug` for `ObjectStyle` [debug-derive], Serialize/Deserialize for key events [serde]
- Improved error handling: - Improved error handling:
@ -65,45 +73,52 @@ As a preparation for crossterm 0.1.0 we have moved crossterm to an organisation
[added-key-event-enter]: https://github.com/crossterm-rs/crossterm/pull/236 [added-key-event-enter]: https://github.com/crossterm-rs/crossterm/pull/236
[fixed-get-set-terminal-size]: https://github.com/crossterm-rs/crossterm/pull/242 [fixed-get-set-terminal-size]: https://github.com/crossterm-rs/crossterm/pull/242
# Changes crossterm 0.10.1 # Version 0.10.1
# Changes crossterm 0.10.0 ~ yanked # Version 0.10.0 ~ yanked
- Implemented command API, to have better performance and more control over how and when commands are executed. [PR](https://github.com/crossterm-rs/crossterm/commit/1a60924abd462ab169b6706aab68f4cca31d7bc2), [issue](https://github.com/crossterm-rs/crossterm/issues/171) - Implemented command API, to have better performance and more control over how and when commands are executed. [PR](https://github.com/crossterm-rs/crossterm/commit/1a60924abd462ab169b6706aab68f4cca31d7bc2), [issue](https://github.com/crossterm-rs/crossterm/issues/171)
- Fixed showing, hiding cursor windows implementation - Fixed showing, hiding cursor windows implementation
- Removed some of the parsing logic from windows keys to ansi codes to key events [PR](https://github.com/crossterm-rs/crossterm/commit/762c3a9b8e3d1fba87acde237f8ed09e74cd9ecd) - Removed some of the parsing logic from windows keys to ansi codes to key events [PR](https://github.com/crossterm-rs/crossterm/commit/762c3a9b8e3d1fba87acde237f8ed09e74cd9ecd)
- Made terminal size 1-based [PR](https://github.com/crossterm-rs/crossterm/commit/d689d7e8ed46a335474b8262bd76f21feaaf0c50) - Made terminal size 1-based [PR](https://github.com/crossterm-rs/crossterm/commit/d689d7e8ed46a335474b8262bd76f21feaaf0c50)
- Added some derive implementation - Added some derive implementation
# Changes crossterm 0.9.6 # Version 0.9.6
- Copy for KeyEvent - Copy for KeyEvent
- CTRL + Left, Down, Up, Right key support - CTRL + Left, Down, Up, Right key support
- SHIFT + Left, Down, Up, Right key support - SHIFT + Left, Down, Up, Right key support
- Fixed UNIX cursor position bug [issue](https://github.com/crossterm-rs/crossterm/issues/140), [PR](https://github.com/crossterm-rs/crossterm/pull/152) - Fixed UNIX cursor position bug [issue](https://github.com/crossterm-rs/crossterm/issues/140), [PR](https://github.com/crossterm-rs/crossterm/pull/152)
# Changes crossterm 0.9.5 # Version 0.9.5
- Prefetching buffer size for more efficient windows input reads. [PR](https://github.com/crossterm-rs/crossterm/pull/144) - Prefetching buffer size for more efficient windows input reads. [PR](https://github.com/crossterm-rs/crossterm/pull/144)
# Changes crossterm 0.9.4 # Version 0.9.4
- Reset foreground and background color individually. [PR](https://github.com/crossterm-rs/crossterm/pull/138) - Reset foreground and background color individually. [PR](https://github.com/crossterm-rs/crossterm/pull/138)
- Backtap input support. [PR](https://github.com/crossterm-rs/crossterm/pull/129) - Backtap input support. [PR](https://github.com/crossterm-rs/crossterm/pull/129)
- Corrected white/grey and added dark grey. - Corrected white/grey and added dark grey.
- Fixed getting cursor position with raw screen enabled. [PR](https://github.com/crossterm-rs/crossterm/pull/134) - Fixed getting cursor position with raw screen enabled. [PR](https://github.com/crossterm-rs/crossterm/pull/134)
- Removed one redundant stdout lock - Removed one redundant stdout lock
# Changes crossterm 0.9.3 # Version 0.9.3
- Removed println from `SyncReader` - Removed println from `SyncReader`
## Changes crossterm 0.9.2 ## Version 0.9.2
- Terminal size linux was not 0-based - Terminal size linux was not 0-based
- Windows mouse input event position was 0-based ans should be 1-based - Windows mouse input event position was 0-based ans should be 1-based
- Result, ErrorKind are made re-exported - Result, ErrorKind are made re-exported
- Fixed some special key combination detections for UNIX systems - Fixed some special key combination detections for UNIX systems
- Made FreeBSD compile - Made FreeBSD compile
## Changes crossterm 0.9.1 ## Version 0.9.1
- Fixed libc compile error - Fixed libc compile error
## Changes crossterm 0.9.0 (yanked) ## Version 0.9.0 (yanked)
This release is all about moving to a stabilized API for 1.0. This release is all about moving to a stabilized API for 1.0.
- Major refactor and cleanup. - Major refactor and cleanup.
@ -123,23 +138,28 @@ This release is all about moving to a stabilized API for 1.0.
- Raw modes UNIX systems improved - Raw modes UNIX systems improved
- Added `NoItalic` attribute - Added `NoItalic` attribute
## Changes crossterm to 0.8.2 ## Version 0.8.2
- Bug fix for sync reader UNIX. - Bug fix for sync reader UNIX.
## Changes crossterm to 0.8.1 ## Version 0.8.1
- Added public re-exports for input. - Added public re-exports for input.
# Changes crossterm 0.8.0 # Version 0.8.0
- Introduced KeyEvents - Introduced KeyEvents
- Introduced MouseEvents - Introduced MouseEvents
- Upgraded crossterm_winapi 0.2 - Upgraded crossterm_winapi 0.2
# Changes crossterm 0.7.0 # Version 0.7.0
- Introduced more `Attributes` - Introduced more `Attributes`
- Introduced easier ways to style text [issue 87](https://github.com/crossterm-rs/crossterm/issues/87). - Introduced easier ways to style text [issue 87](https://github.com/crossterm-rs/crossterm/issues/87).
- Removed `ColorType` since it was unnecessary. - Removed `ColorType` since it was unnecessary.
# Changes crossterm 0.6.0 # Version 0.6.0
- Introduced feature flags; input, cursor, style, terminal, screen. - Introduced feature flags; input, cursor, style, terminal, screen.
- All modules are moved to their own crate. - All modules are moved to their own crate.
- Introduced crossterm workspace - Introduced crossterm workspace
@ -148,10 +168,12 @@ This release is all about moving to a stabilized API for 1.0.
[PR 84](https://github.com/crossterm-rs/crossterm/pull/84) [PR 84](https://github.com/crossterm-rs/crossterm/pull/84)
# Changes crossterm 0.5.5 # Version 0.5.5
- Error module is made public [PR 78](https://github.com/crossterm-rs/crossterm/pull/78). - Error module is made public [PR 78](https://github.com/crossterm-rs/crossterm/pull/78).
# Changes crossterm 0.5.4 # Version 0.5.4
- WinApi rewrite and correctly error handled [PR 67](https://github.com/crossterm-rs/crossterm/pull/67) - WinApi rewrite and correctly error handled [PR 67](https://github.com/crossterm-rs/crossterm/pull/67)
- Windows attribute support [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) - Windows attribute support [PR 62](https://github.com/crossterm-rs/crossterm/pull/62)
- Readline bug fix windows systems [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) - Readline bug fix windows systems [PR 62](https://github.com/crossterm-rs/crossterm/pull/62)
@ -159,31 +181,37 @@ This release is all about moving to a stabilized API for 1.0.
- General refactoring, all warnings removed. - General refactoring, all warnings removed.
- Documentation improvement. - Documentation improvement.
# Changes crossterm 0.5.1 # Version 0.5.1
- Documentation refactor. - Documentation refactor.
- Fixed broken API documentation [PR 53](https://github.com/crossterm-rs/crossterm/pull/53). - Fixed broken API documentation [PR 53](https://github.com/crossterm-rs/crossterm/pull/53).
# Changes crossterm 0.5.0 # Version 0.5.0
- Added ability to pause the terminal [issue](https://github.com/crossterm-rs/crossterm/issues/39) - Added ability to pause the terminal [issue](https://github.com/crossterm-rs/crossterm/issues/39)
- RGB support for Windows 10 systems - RGB support for Windows 10 systems
- ANSI color value (255) color support - ANSI color value (255) color support
- More convenient API, no need to care about `Screen` unless working with when working with alternate or raw screen [PR](https://github.com/crossterm-rs/crossterm/pull/44) - More convenient API, no need to care about `Screen` unless working with when working with alternate or raw screen [PR](https://github.com/crossterm-rs/crossterm/pull/44)
- Implemented Display for styled object - Implemented Display for styled object
# Changes crossterm to 0.4.3 # Version 0.4.3
- Fixed bug [issue 41](https://github.com/crossterm-rs/crossterm/issues/41) - Fixed bug [issue 41](https://github.com/crossterm-rs/crossterm/issues/41)
# Changes crossterm to 0.4.2 # Version 0.4.2
- Added functionality to make a styled object writable to screen [issue 33](https://github.com/crossterm-rs/crossterm/issues/33) - Added functionality to make a styled object writable to screen [issue 33](https://github.com/crossterm-rs/crossterm/issues/33)
- Added unit tests. - Added unit tests.
- Bugfix with getting terminal size unix. - Bugfix with getting terminal size unix.
- Bugfix with returning written bytes [pull request 31](https://github.com/crossterm-rs/crossterm/pull/31) - Bugfix with returning written bytes [pull request 31](https://github.com/crossterm-rs/crossterm/pull/31)
- removed methods calls: `as_any()` and `as_any_mut()` from `TerminalOutput` - removed methods calls: `as_any()` and `as_any_mut()` from `TerminalOutput`
# Bug fix crossterm to 0.4.1 # Version 0.4.1
- Fixed resizing of ansi terminal with and height where in the wrong order. - Fixed resizing of ansi terminal with and height where in the wrong order.
# Features / Fixes in crossterm 0.4.0 # Version 0.4.0
- Input support (read_line, read_char, read_async, read_until_async) - Input support (read_line, read_char, read_async, read_until_async)
- Styling module improved - Styling module improved
- Everything is multithreaded (`Send`, `Sync`) - Everything is multithreaded (`Send`, `Sync`)
@ -194,7 +222,7 @@ This release is all about moving to a stabilized API for 1.0.
- Overall commend improvement. - Overall commend improvement.
- Overall refactor of code. - Overall refactor of code.
# Features in crossterm 0.3.0 # Version 0.3.0
This version has some braking changes check [upgrade manual](UPGRADE%20Manual.md) for more information about what is changed. This version has some braking changes check [upgrade manual](UPGRADE%20Manual.md) for more information about what is changed.
I think you should not switch to version `0.3.0` if you aren't going to use the AlternateScreen feature. I think you should not switch to version `0.3.0` if you aren't going to use the AlternateScreen feature.
@ -209,6 +237,7 @@ Some Features crossterm 0.3.0
- exit the current process. - exit the current process.
## Alternate screen ## Alternate screen
This create supports alternate screen for both windows and unix systems. You can use This create supports alternate screen for both windows and unix systems. You can use
*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. *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.
@ -219,6 +248,7 @@ Vim uses the entirety of the screen to edit the file, then returning to bash lea
I Highly recommend you to check the `examples/program_examples/first_depth_search` for seeing this in action. I Highly recommend you to check the `examples/program_examples/first_depth_search` for seeing this in action.
## Raw screen ## Raw screen
This crate now supports raw screen for both windows and unix systems. This crate now supports raw screen for both windows and unix systems.
What exactly is raw state: What exactly is raw state:
- No line buffering. - No line buffering.
@ -232,6 +262,7 @@ What exactly is raw state:
With these modes you can easier design the terminal screen. With these modes you can easier design the terminal screen.
## Some functionalities added ## Some functionalities added
- Hiding and showing terminal cursor - Hiding and showing terminal cursor
- Enable or disabling blinking of the cursor for unix systems (this is not widely supported) - Enable or disabling blinking of the cursor for unix systems (this is not widely supported)
- Restoring the terminal to original modes. - Restoring the terminal to original modes.
@ -319,10 +350,11 @@ like demonstrated above, to get the functionalities of `cursor(), color(), termi
You need to pass it the same `Context` as you have passed to the previous three called functions, You need to pass it the same `Context` as you have passed to the previous three called functions,
If you don't use the same `Context` in `cursor(), color(), terminal()` than these modules will be using the main screen and you will not see anything at the alternate screen. If you use the [Crossterm](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) type you can get the `Context` from it by calling the crossterm.get_context() whereafter you can create the AlternateScreen from it. If you don't use the same `Context` in `cursor(), color(), terminal()` than these modules will be using the main screen and you will not see anything at the alternate screen. If you use the [Crossterm](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) type you can get the `Context` from it by calling the crossterm.get_context() whereafter you can create the AlternateScreen from it.
# Fixes in crossterm 0.2.2 # Version 0.2.2
- Bug see [issue 15](https://github.com/crossterm-rs/crossterm/issues/15) - Bug see [issue 15](https://github.com/crossterm-rs/crossterm/issues/15)
# Fixes in crossterm 0.2.1 # Version 0.2.1
- Default ANSI escape codes for windows machines, if windows does not support ANSI switch back to WinApi. - Default ANSI escape codes for windows machines, if windows does not support ANSI switch back to WinApi.
- method grammar mistake fixed [Issue 3](https://github.com/crossterm-rs/crossterm/issues/3) - method grammar mistake fixed [Issue 3](https://github.com/crossterm-rs/crossterm/issues/3)
@ -330,7 +362,7 @@ If you don't use the same `Context` in `cursor(), color(), terminal()` than thes
- Removed bin reference from crate [Issue 6](https://github.com/crossterm-rs/crossterm/issues/6) - Removed bin reference from crate [Issue 6](https://github.com/crossterm-rs/crossterm/issues/6)
- Get position unix fixed [issue 8](https://github.com/crossterm-rs/crossterm/issues/8) - Get position unix fixed [issue 8](https://github.com/crossterm-rs/crossterm/issues/8)
# Features crossterm 0.2 # Version 0.2
- 256 color support. - 256 color support.
- Text Attributes like: bold, italic, underscore and crossed word ect. - Text Attributes like: bold, italic, underscore and crossed word ect.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "crossterm" name = "crossterm"
version = "0.11.0" version = "0.11.1"
authors = ["T. Post"] authors = ["T. Post"]
description = "An crossplatform terminal library for manipulating terminals." description = "An crossplatform terminal library for manipulating terminals."
repository = "https://github.com/crossterm-rs/crossterm" repository = "https://github.com/crossterm-rs/crossterm"
@ -20,29 +20,13 @@ terminal = ["crossterm_terminal"]
screen = ["crossterm_screen"] screen = ["crossterm_screen"]
input = ["crossterm_input"] input = ["crossterm_input"]
[workspace]
members = [
"crossterm_winapi",
"crossterm_utils",
"crossterm_cursor",
"crossterm_style",
"crossterm_terminal",
"crossterm_input",
"crossterm_screen"
]
exclude = [
"examples/program_examples"
]
[dependencies] [dependencies]
crossterm_screen = { path = "./crossterm_screen", version = "0.3.0" , optional = true } crossterm_screen = { version = "0.3.1" , optional = true }
crossterm_cursor = { path = "./crossterm_cursor", version = "0.3.0" , optional = true } crossterm_cursor = { version = "0.3.1" , optional = true }
crossterm_terminal = { path = "./crossterm_terminal", version = "0.3.0", optional = true } crossterm_terminal = { version = "0.3.1", optional = true }
crossterm_style = { path = "./crossterm_style", version = "0.5.0" , optional = true } crossterm_style = { version = "0.5.1" , optional = true }
crossterm_input = { path = "./crossterm_input", version = "0.4.0" , optional = true } crossterm_input = { version = "0.4.1" , optional = true }
crossterm_utils = { path = "./crossterm_utils", version = "0.3.0" , optional = false } crossterm_utils = { version = "0.3.1" , optional = false }
[lib] [lib]
name = "crossterm" name = "crossterm"

127
README.md
View File

@ -1,81 +1,66 @@
<h1 align="center"><img width="440" src="docs/crossterm_full.png" /></h1> <h1 align="center"><img width="440" src="docs/crossterm_full.png" /></h1>
# cross-platform terminal manipulating library.
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z8QK6XU749JB2) ![Travis][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] [![Join us on Discord][s5]][l5] [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z8QK6XU749JB2) ![Travis][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 # cross-platform terminal manipulating library.
[l1]: https://crates.io/crates/crossterm
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: crossterm/LICENSE
[s3]: https://docs.rs/crossterm/badge.svg
[l3]: https://docs.rs/crossterm/
[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/crossterm-rs/crossterm?category=code
[s7]: https://travis-ci.org/crossterm-rs/crossterm.svg?branch=master
Have you ever been disappointed when a terminal library for rust was only written for UNIX systems? Have you ever been disappointed when a terminal library for rust was only written for UNIX systems?
Crossterm provides clearing, input handling, styling, cursor movement, and terminal actions for both Windows and UNIX systems. Crossterm provides clearing, input handling, styling, cursor movement, and terminal actions for both
Windows and UNIX systems.
Crossterm aims to be simple and easy to call in code. Crossterm aims to be simple and easy to call in code.
Through the simplicity of Crossterm, you do not have to worry about the platform you are working with. Through the simplicity of Crossterm, you do not have to worry about the platform you are working with.
This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info). This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested
see [Tested Terminals](#tested-terminals) for more info).
This crate consists of five modules that are provided behind [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html) so that you can define which features you'd like to have; by default, all features are enabled. This crate consists of five modules that are provided behind
- [Crossterm Style](https://crates.io/crates/crossterm_style) [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html) so that you can define
- [Crossterm Input](https://crates.io/crates/crossterm_input) which features you'd like to have; by default, all features are enabled.
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor) - [crossterm_style](https://crates.io/crates/crossterm_style)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal) - [crossterm_input](https://crates.io/crates/crossterm_input)
- [crossterm_screen](https://crates.io/crates/crossterm_screen)
- [crossterm_cursor](https://crates.io/crates/crossterm_cursor)
- [crossterm_terminal](https://crates.io/crates/crossterm_terminal)
## Table of contents: ## Table of contents:
- [Getting started](#getting-started) - [Getting started](#getting-started)
- [Useful links](#useful-links) - [Useful links](#useful-links)
- [Features](#features) - [Features](#features)
- [Examples](#examples) - [Examples](#examples)
- [Crossterm Type](#crossterm-type)
- [Styled Text](#styled-text) - [Styled Text](#styled-text)
- [Cursor](#cursor) - [Cursor](#cursor)
- [Terminal](#terminal) - [Terminal](#terminal)
- [Input Reading](#input-reading) - [Input Reading](#input-reading)
- [Tested Terminals](#tested-terminals) - [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Todo](#todo)
- [Contributing](#contributing) - [Contributing](#contributing)
- [Authors](#authors) - [Authors](#authors)
- [License](#license) - [License](#license)
## Getting Started ## Getting Started
All [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) of how crossterm works can be found in the example directory. All [examples](https://github.com/crossterm-rs/examples) of how crossterm works can be found in the example repository.
Add the Crossterm package to your `Cargo.toml` file. Add the Crossterm package to your `Cargo.toml` file.
``` ```
[dependencies] [dependencies]
crossterm = "^0.11" crossterm = "0.11"
``` ```
### Useful Links ### Useful Links
- [Book](https://crossterm-rs.github.io/crossterm/docs//) - [Book](https://crossterm-rs.github.io/crossterm/docs/)
- [Documentation](https://docs.rs/crossterm/) - [Documentation](https://docs.rs/crossterm/)
- [Crates.io](https://crates.io/crates/crossterm) - [Crates.io](https://crates.io/crates/crossterm)
- [Program Examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/program_examples) - [Examples](https://github.com/crossterm-rs/examples)
- [Examples](https://github.com/crossterm-rs/crossterm/tree/master/examples)
## Features ## Features
- Cross-platform - Cross-platform
- Multithreaded (send, sync) - Multi-threaded (send, sync)
- Detailed Documentation - Detailed Documentation
- Few Dependencies - Few Dependencies
- Cursor - Cursor
@ -104,19 +89,25 @@ crossterm = "^0.11"
- Read mouse input events (press, release, position, button) - Read mouse input events (press, release, position, button)
## Examples ## Examples
These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/crossterm-rs/crossterm/blob/master/examples/) for more.
These are some basic examples demonstrating how to use this crate. See the
[examples](https://github.com/crossterm-rs/examples) repository.
### Command API ### Command API
My first recommendation is to use the [command API](https://crossterm-rs.github.io/crossterm/docs/command.html) because this might replace some of the existing API in the future. My first recommendation is to use the [command API](https://crossterm-rs.github.io/crossterm/docs/command.html)
Because it is more convenient, faster, and easier to use. because this might replace some of the existing API in the future. It is more convenient, faster, and easier to use.
### Styled Text ### Styled Text
This module enables you to style the terminal text. This module enables you to style the terminal text.
Good documentation can be found at the following places: [docs](https://docs.rs/crossterm_style/), [book](https://crossterm-rs.github.io/crossterm/docs/styling.html), [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/key_events.rs) Good documentation can be found at the following places: [docs](https://docs.rs/crossterm_style/),
[book](https://crossterm-rs.github.io/crossterm/docs/styling.html)
and [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/key_events.rs).
_style text with attributes_ _style text with attributes_
```rust ```rust
use crossterm::{Colored, Color, Colorize, Styler, Attribute}; use crossterm::{Colored, Color, Colorize, Styler, Attribute};
@ -129,6 +120,7 @@ println!("{}", styled_text);
``` ```
_style text with colors_ _style text with colors_
```rust ```rust
println!("{} Red foreground color", Colored::Fg(Color::Red)); println!("{} Red foreground color", Colored::Fg(Color::Red));
println!("{} Blue background color", Colored::Bg(Color::Blue)); println!("{} Blue background color", Colored::Bg(Color::Blue));
@ -137,7 +129,9 @@ println!("{} Blue background color", Colored::Bg(Color::Blue));
let styled_text = "Bold Underlined".red().on_blue(); let styled_text = "Bold Underlined".red().on_blue();
println!("{}", styled_text); println!("{}", styled_text);
``` ```
_style text with RGB and ANSI Value_ _style text with RGB and ANSI Value_
```rust ```rust
// custom rgb value (Windows 10 and UNIX systems) // custom rgb value (Windows 10 and UNIX systems)
println!("{} some colored text", Colored::Fg(Color::Rgb { println!("{} some colored text", Colored::Fg(Color::Rgb {
@ -150,11 +144,12 @@ println!("{} some colored text", Colored::Fg(Color::Rgb {
println!("{} some colored text", Colored::Fg(Color::AnsiValue(10))); println!("{} some colored text", Colored::Fg(Color::AnsiValue(10)));
``` ```
### Cursor ### Cursor
This module enables you to work with the terminal cursor. This module enables you to work with the terminal cursor.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_cursor/), [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/cursor.rs) Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_cursor/) and
[examples](https://github.com/crossterm-rs/examples).
```rust ```rust
use crossterm::cursor; use crossterm::cursor;
@ -194,9 +189,11 @@ cursor.blink(true)
``` ```
### Terminal ### Terminal
This module enables you to work with the terminal in general. This module enables you to work with the terminal in general.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_terminal/), [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/terminal.rs). Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_terminal/) and
[examples](https://github.com/crossterm-rs/examples).
```rust ```rust
use crossterm::{terminal,ClearType}; use crossterm::{terminal,ClearType};
@ -233,9 +230,12 @@ terminal.write("Some text\n Some text on new line");
``` ```
### Input Reading ### Input Reading
This module enables you to read user input events. This module enables you to read user input events.
Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_input/), [book](https://crossterm-rs.github.io/crossterm/docs/input.html), [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/key_events.rs) Good documentation could be found on the following places: [docs](https://docs.rs/crossterm_input/),
[book](https://crossterm-rs.github.io/crossterm/docs/input.html) and
[examples](https://github.com/crossterm-rs/examples).
_available imports_ _available imports_
```rust ```rust
@ -245,6 +245,7 @@ use crossterm_input::{
``` ```
_Simple Readings_ _Simple Readings_
```rust ```rust
let mut input = input(); let mut input = input();
@ -260,8 +261,10 @@ let mut input = input();
``` ```
_Read input events synchronously or asynchronously._ _Read input events synchronously or asynchronously._
```rust ```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. // 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 = RawScreen::into_raw_mode(); let screen = RawScreen::into_raw_mode();
let mut input = input(); let mut input = input();
@ -281,6 +284,7 @@ if let Some(key_event) = stdin.next() {
``` ```
_Enable mouse input events._ _Enable mouse input events._
```rust ```rust
let input = input(); let input = input();
@ -292,9 +296,13 @@ input.disable_mouse_mode().unwrap();
``` ```
### Alternate and Raw Screen ### Alternate and Raw Screen
These concepts are a little more complex and would take over the README, please check out the [docs](https://docs.rs/crossterm_screen/), [book](https://crossterm-rs.github.io/crossterm/docs/screen.html), and [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples).
## Used By These concepts are a little more complex and would take over the README, please check out
the [docs](https://docs.rs/crossterm_screen/), [book](https://crossterm-rs.github.io/crossterm/docs/screen.html)
and [examples](https://github.com/crossterm-rs/examples).
## Used by
- [Broot](https://dystroy.org/broot/) - [Broot](https://dystroy.org/broot/)
- [Cursive](https://github.com/gyscos/Cursive) - [Cursive](https://github.com/gyscos/Cursive)
- [TUI](https://github.com/fdehau/tui-rs) - [TUI](https://github.com/fdehau/tui-rs)
@ -312,8 +320,9 @@ These concepts are a little more complex and would take over the README, please
- (Arch, Manjaro) KDE Konsole - (Arch, Manjaro) KDE Konsole
- Linux Mint - Linux Mint
This crate supports all Unix terminals and Windows terminals down to Windows 7; however, not all of the terminals have been tested. This crate supports all Unix terminals and Windows terminals down to Windows 7; however, not all of the
If you have used this library for a terminal other than the above list without issues, then feel free to add it to the above list - I really would appreciate it! terminals have been tested. If you have used this library for a terminal other than the above list without
issues, then feel free to add it to the above list - I really would appreciate it!
## Contributing ## Contributing
@ -332,4 +341,24 @@ Would you like crossterm to be even more gorgeous and beautiful? You can help wi
## License ## License
This project, crossterm and all it's sub-modules: crossterm_screen, crossterm_cursor, crossterm_style, crossterm_input, crossterm_terminal, crossterm_winapi, crossterm_utils are licensed under the MIT License - see the [LICENSE.md](https://github.com/crossterm-rs/crossterm/blob/master/LICENSE) file for details This project, crossterm and all it's sub-modules: crossterm_screen, crossterm_cursor, crossterm_style,
crossterm_input, crossterm_terminal, crossterm_winapi, crossterm_utils are licensed under the MIT License - see
the [LICENSE](https://github.com/crossterm-rs/crossterm/blob/master/LICENSE) file for details
[s1]: https://img.shields.io/crates/v/crossterm.svg
[l1]: https://crates.io/crates/crossterm
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: crossterm/LICENSE
[s3]: https://docs.rs/crossterm/badge.svg
[l3]: https://docs.rs/crossterm/
[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/crossterm-rs/crossterm?category=code
[s7]: https://travis-ci.org/crossterm-rs/crossterm.svg?branch=master

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,15 +0,0 @@
# Changes crossterm_cursor 0.3
- `TerminalCursor::pos()` returns `crossterm::Result<(u16, u16)>`
- `TerminalCursor::move_*` returns `crossterm::Result`
- `TerminalCursor::reset_position()` to `restore_position()`
- All `i16` values for indexing: set/get cursor pos synced to `u16` values
- `Command::get_anis_code()` to `ansi_code()`
- `ExecutableCommand::queue` returns `crossterm::Result`
- `QueueableCommand::queue` returns `crossterm::Result`
- Command API takes mutable self instead of self
# Changes crossterm_cursor 0.2
- Removed `TerminalCursor::from_output()`
# Changes crossterm_cursor 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,19 +0,0 @@
[package]
name = "crossterm_cursor"
version = "0.3.0"
authors = ["T. Post"]
description = "A cross-platform library for moving the terminal cursor."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_cursor/"
license = "MIT"
keywords = ["cursor", "cli", "crossterm", "crossplatform", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["wincon","winnt","minwindef"] }
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0"}
[dependencies]
crossterm_utils = { path="../crossterm_utils", version = "0.3.0"}

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,141 +0,0 @@
# Crossterm Cursor | cross-platform cursor movement.
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate allows you to move the terminal cursor cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to move the cursor, and can be use individually.
Other sub-crates are:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal)
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Input](https://crates.io/crates/crossterm_input)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
- [Useful links](#useful-links)
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Authors](#authors)
- [License](#license)
## Getting Started
All examples of how `crossterm_cursor` works can be found in the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) directory.
Add the `crossterm_cursor` package to your `Cargo.toml` file.
```
[dependencies]
crossterm_cursor = "0.2"
```
Import the `crossterm_cursor` modules you want to use.
```rust
pub use crossterm_cursor::{cursor, TerminalCursor};
```
### Useful Links
- [Documentation](https://docs.rs/crossterm_cursor/)
- [Crates.io](https://crates.io/crates/crossterm_cursor)
- [Examples](/examples)
## Features
These are the features of this crate:
- Cross-platform
- 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)
## Command API
My first recommendation is to use the [command API](https://crossterm-rs.github.io/crossterm/docs/command.html) because this might replace some of the existing API in the future.
Because it is more convenient, faster, and easier to use.
## Examples
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
```rust
use crossterm_cursor::cursor;
let mut cursor = cursor();
/// Moving the cursor
// Set the cursor to position X: 10, Y: 5 in the terminal
cursor.goto(10,5);
// Move the cursor up,right,down,left 3 cells.
cursor.move_up(3);
cursor.move_right(3);
cursor.move_down(3);
cursor.move_left(3);
/// Safe the current cursor position to recall later
// Goto X: 5 Y: 5
cursor.goto(5,5);
// Safe cursor position: X: 5 Y: 5
cursor.save_position();
// Goto X: 5 Y: 20
cursor.goto(5,20);
// Print at X: 5 Y: 20.
print!("Yea!");
// Reset back to X: 5 Y: 5.
cursor.restore_position();
// Print 'Back' at X: 5 Y: 5.
print!("Back");
// hide cursor
cursor.hide();
// show cursor
cursor.show();
// blink or not blinking of the cursor (not widely supported)
cursor.blink(true)
```
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
- Windows 10 (pro)
- Windows 8.1 (N)
- Ubuntu Desktop Terminal
- Ubuntu 17.10
- (Arch, Manjaro) KDE Konsole
- Linux Mint
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.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

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

View File

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

View File

@ -1,342 +0,0 @@
//! A module that contains all the actions related to cursor movement in the terminal.
//! Like: moving the cursor position; saving and resetting the cursor position; hiding showing and control the blinking of the cursor.
#[cfg(windows)]
use crossterm_utils::supports_ansi;
use crossterm_utils::{impl_display, Command, Result};
use super::ansi_cursor::{self, AnsiCursor};
#[cfg(windows)]
use super::winapi_cursor::WinApiCursor;
use super::ITerminalCursor;
/// Allows you to preform actions with the terminal cursor.
///
/// # Features:
///
/// - 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 and showing the cursor
/// - Control over blinking of the terminal cursor (only some terminals are supporting this)
///
/// 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.
pub struct TerminalCursor {
#[cfg(windows)]
cursor: Box<(dyn ITerminalCursor + Sync + Send)>,
#[cfg(unix)]
cursor: AnsiCursor,
}
impl TerminalCursor {
/// Create new `TerminalCursor` instance whereon cursor related actions can be performed.
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(unix)]
let cursor = AnsiCursor::new();
TerminalCursor { cursor }
}
/// Goto some position (x,y) in the terminal.
///
/// # Remarks
/// position is 0-based, which means we start counting at 0.
pub fn goto(&self, x: u16, y: u16) -> Result<()> {
self.cursor.goto(x, y)
}
/// Get current cursor position (x,y) in the terminal.
///
/// # Remarks
/// position is 0-based, which means we start counting at 0.
pub fn pos(&self) -> Result<(u16, u16)> {
self.cursor.pos()
}
/// Move the current cursor position `n` times up.
pub fn move_up(&mut self, count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_up(count)?;
Ok(self)
}
/// Move the current cursor position `n` times right.
pub fn move_right(&mut self, count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_right(count)?;
Ok(self)
}
/// Move the current cursor position `n` times down.
pub fn move_down(&mut self, count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_down(count)?;
Ok(self)
}
/// Move the current cursor position `n` times left.
pub fn move_left(&mut self, count: u16) -> Result<&mut TerminalCursor> {
self.cursor.move_left(count)?;
Ok(self)
}
/// Save cursor position for recall later.
///
/// Note that this position is stored program based not per instance of the `Cursor` struct.
pub fn save_position(&self) -> Result<()> {
self.cursor.save_position()
}
/// Return to saved cursor position
pub fn restore_position(&self) -> Result<()> {
self.cursor.restore_position()
}
/// Hide de cursor in the console.
pub fn hide(&self) -> Result<()> {
self.cursor.hide()
}
/// Show the cursor in the console.
pub fn show(&self) -> Result<()> {
self.cursor.show()
}
/// Enable or disable blinking of the terminal.
///
/// # 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.cursor.blink(blink)
}
}
/// Get a `TerminalCursor` instance whereon cursor related actions can be performed.
pub fn cursor() -> TerminalCursor {
TerminalCursor::new()
}
/// When executed, this command will move the cursor position to the given `x` and `y` in the terminal window.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Goto(pub u16, pub u16);
impl Command for Goto {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::get_goto_ansi(self.0, self.1)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().goto(self.0, self.1)
}
}
/// When executed, this command will move the current cursor position `n` times up.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Up(pub u16);
impl Command for Up {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::get_move_up_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_up(self.0)
}
}
/// When executed, this command will move the current cursor position `n` times down.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Down(pub u16);
impl Command for Down {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::get_move_down_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_down(self.0)
}
}
/// When executed, this command will move the current cursor position `n` times left.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Left(pub u16);
impl Command for Left {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::get_move_left_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_left(self.0)
}
}
/// When executed, this command will move the current cursor position `n` times right.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Right(pub u16);
impl Command for Right {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::get_move_right_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().move_right(self.0)
}
}
/// When executed, this command will save the cursor position for recall later.
///
/// Note that this position is stored program based not per instance of the `Cursor` struct.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct SavePos;
impl Command for SavePos {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::SAVE_POS_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().save_position()
}
}
/// When executed, this command will return the cursor position to the saved cursor position
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct ResetPos;
impl Command for ResetPos {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::RESTORE_POS_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().restore_position()
}
}
/// When executed, this command will hide de cursor in the console.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Hide;
impl Command for Hide {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::HIDE_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().hide()
}
}
/// When executed, this command will show de cursor in the console.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Show;
impl Command for Show {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::SHOW_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiCursor::new().show()
}
}
/// When executed, this command will enable cursor blinking.
///
/// # Remarks
/// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct BlinkOn;
impl Command for BlinkOn {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::BLINK_ON_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
/// When executed, this command will disable cursor blinking.
///
/// # Remarks
/// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct BlinkOff;
impl Command for BlinkOff {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
ansi_cursor::BLINK_OFF_ANSI
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Ok(())
}
}
impl_display!(for Goto);
impl_display!(for Up);
impl_display!(for Down);
impl_display!(for Left);
impl_display!(for Right);
impl_display!(for SavePos);
impl_display!(for ResetPos);
impl_display!(for Hide);
impl_display!(for Show);
impl_display!(for BlinkOn);
impl_display!(for BlinkOff);

View File

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

View File

@ -1,13 +0,0 @@
#![deny(unused_imports)]
pub use crossterm_utils::{
execute, queue, Command, ErrorKind, ExecutableCommand, Output, QueueableCommand, Result,
};
pub use self::cursor::{
cursor, BlinkOff, BlinkOn, Down, Goto, Hide, Left, ResetPos, Right, SavePos, Show,
TerminalCursor, Up,
};
mod cursor;
pub mod sys;

View File

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

View File

@ -1,61 +0,0 @@
use std::io::{self, BufRead, Write};
use crossterm_utils::{
csi,
sys::unix::{self, RAW_MODE_ENABLED},
write_cout, Result,
};
#[cfg(unix)]
pub fn get_cursor_position() -> Result<(u16, u16)> {
if unsafe { RAW_MODE_ENABLED } {
pos_raw()
} else {
pos()
}
}
#[cfg(unix)]
pub fn show_cursor(show_cursor: bool) -> Result<()> {
if show_cursor {
write_cout!(csi!("?25h"))?;
} else {
write_cout!(csi!("?25l"))?;
}
Ok(())
}
pub fn pos() -> Result<(u16, u16)> {
unix::enable_raw_mode()?;
let pos = pos_raw();
unix::disable_raw_mode()?;
pos
}
pub fn pos_raw() -> Result<(u16, u16)> {
// Where is the cursor?
// Use `ESC [ 6 n`.
let mut stdout = io::stdout();
let stdin = io::stdin();
// Write command
stdout.write_all(b"\x1B[6n")?;
stdout.flush()?;
stdin.lock().read_until(b'[', &mut vec![])?;
let mut rows = vec![];
stdin.lock().read_until(b';', &mut rows)?;
let mut cols = vec![];
stdin.lock().read_until(b'R', &mut cols)?;
// remove delimiter
rows.pop();
cols.pop();
let rows = String::from_utf8(rows)?.parse::<u16>()?;
let cols = String::from_utf8(cols)?.parse::<u16>()?;
Ok((cols - 1, rows - 1))
}

View File

@ -1,135 +0,0 @@
//! This module handles some logic for cursor interaction in the windows console.
use std::io;
use winapi::{
shared::minwindef::{FALSE, TRUE},
um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
um::winnt::HANDLE,
};
use crossterm_utils::Result;
pub use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer};
#[cfg(windows)]
pub fn get_cursor_position() -> Result<(u16, u16)> {
let cursor = Cursor::new()?;
Ok(cursor.position()?.into())
}
#[cfg(windows)]
pub fn show_cursor(show_cursor: bool) -> Result<()> {
Cursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
}
/// This stores the cursor pos, at program level. So it can be recalled later.
static mut SAVED_CURSOR_POS: (u16, u16) = (0, 0);
pub struct Cursor {
screen_buffer: ScreenBuffer,
}
impl Cursor {
pub fn new() -> Result<Cursor> {
Ok(Cursor {
screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
})
}
/// get the current cursor position.
pub fn position(&self) -> Result<Coord> {
Ok(self.screen_buffer.info()?.cursor_pos())
}
/// Set the cursor position to the given x and y. Note that this is 0 based.
pub fn goto(&self, x: i16, y: i16) -> Result<()> {
if x < 0 || x >= <i16>::max_value() {
Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Argument Out of Range Exception when setting cursor position to X: {}",
x
),
))?;
}
if y < 0 || y >= <i16>::max_value() {
Err(io::Error::new(
io::ErrorKind::Other,
format!(
"Argument Out of Range Exception when setting cursor position to Y: {}",
y
),
))?;
}
let position = COORD { X: x, Y: y };
unsafe {
if !is_true(SetConsoleCursorPosition(
**self.screen_buffer.handle(),
position,
)) {
Err(io::Error::last_os_error())?;
}
}
Ok(())
}
/// change the cursor visibility.
pub fn set_visibility(&self, visable: bool) -> Result<()> {
let cursor_info = CONSOLE_CURSOR_INFO {
dwSize: 100,
bVisible: if visable { TRUE } else { FALSE },
};
unsafe {
if !is_true(SetConsoleCursorInfo(
**self.screen_buffer.handle(),
&cursor_info,
)) {
Err(io::Error::last_os_error())?;
}
}
Ok(())
}
/// Reset to saved cursor position
pub fn restore_cursor_pos() -> Result<()> {
let cursor = Cursor::new()?;
unsafe {
cursor.goto(SAVED_CURSOR_POS.0 as i16, SAVED_CURSOR_POS.1 as i16)?;
}
Ok(())
}
/// Save current cursor position to recall later.
pub fn save_cursor_pos() -> Result<()> {
let cursor = Cursor::new()?;
let position = cursor.position()?;
unsafe {
SAVED_CURSOR_POS = (position.x as u16, position.y as u16);
}
Ok(())
}
}
impl From<Handle> for Cursor {
fn from(handle: Handle) -> Self {
Cursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}
impl From<HANDLE> for Cursor {
fn from(handle: HANDLE) -> Self {
Cursor {
screen_buffer: ScreenBuffer::from(handle),
}
}
}

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,39 +0,0 @@
# Changes crossterm_input 0.4.0
- `TerminalInput::read_line` returns `crossterm::Result` instead of `io::Result`
- `TerminalInput::read_char` returns `crossterm::Result` instead of `io::Result`
- `Command::get_anis_code()` to `ansi_code()`
- Added KeyEvent::Enter and KeyEvent::Tab: [added-key-event-enter], [added-key-event-tab]
- `ExecutableCommand::queue` returns `crossterm::Result`
- `QueueableCommand::queue` returns `crossterm::Result`
- Added derives: Serialize/Deserialize for key events [serde]
- Command API takes mutable self instead of self
[added-key-event-tab]: https://github.com/crossterm-rs/crossterm/pull/239
[added-key-event-enter]: https://github.com/crossterm-rs/crossterm/pull/236
[serde]: https://github.com/crossterm-rs/crossterm/pull/190
# Changes crossterm_input 0.3.3
- Removed println from `SyncReader`
# Changes crossterm_input 0.3.2
- Fixed some special key combination detections for UNIX systems
- Windows mouse input event position was 0-based and should be 1-based
# Changes crossterm_input 0.3.1
- Updated crossterm_utils
# 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,24 +0,0 @@
[package]
name = "crossterm_input"
version = "0.4.0"
authors = ["T. Post"]
description = "A cross-platform library for reading userinput."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_input/"
license = "MIT"
keywords = ["input", "keys", "crossterm", "events", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["winnt", "winuser"] }
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0"}
[target.'cfg(unix)'.dependencies]
libc = "0.2.51"
[dependencies]
crossterm_utils = { path="../crossterm_utils", version = "0.3.0"}
crossterm_screen = {path="../crossterm_screen", version = "0.3.0"}
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,151 +0,0 @@
# Crossterm Input | cross-platform input reading .
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate allows you to read the user input cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to read the user input and can be used individually.
Other sub-crates are:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal)
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
- [Useful links](#useful-links)
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Contributing](#contributing)
- [Authors](#authors)
- [License](#license)
## Getting Started
All examples of how `crossterm_input` works can be found in the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) directory.
Add the `crossterm_input` package to your `Cargo.toml` file.
```
[dependencies]
crossterm_input = "0.3"
```
Import the `crossterm_input` modules you want to use.
```rust
pub use crossterm_input::{input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput};
```
### Useful Links
- [Documentation](https://docs.rs/crossterm_input/)
- [Crates.io](https://crates.io/crates/crossterm_input)
- [Book](https://crossterm-rs.github.io/crossterm/docs/input.html)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Input
- Read character
- Read line
- Read key input events (async / sync)
- Read mouse input events (press, release, position, button)
- RawScreen (from `crossterm_screen`)
## Examples
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
_Simple Readings_
```rust
let mut input = input();
match input.read_char() {
Ok(s) => println!("char typed: {}", s),
Err(e) => println!("char error : {}", e),
}
match input.read_line() {
Ok(s) => println!("string typed: {}", s),
Err(e) => println!("error: {}", e),
}
```
_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 = RawScreen::into_raw_mode();
let mut input = input();
// either read the input synchronously
let stdin = input.read_sync();
// or asynchronously
let stdin = input.read_async();
if let Some(key_event) = stdin.next() {
match key_event {
InputEvent::Keyboard(event: KeyEvent) => match event { /* check key event */ }
InputEvent::Mouse(event: MouseEvent) => match event { /* check mouse event */ }
}
}
```
_Enable mouse input events._
```rust
let input = input();
// enable mouse events to be captured.
input.enable_mouse_mode().unwrap();
// disable mouse events to be captured.
input.disable_mouse_mode().unwrap();
```
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
- Windows 10 (pro)
- Windows 8.1 (N)
- Ubuntu Desktop Terminal
- Ubuntu 17.10
- (Arch, Manjaro) KDE Konsole
- Linux Mint
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.
## Authors
* **Timon Post** - *Project Owner & creator*
* **Dave Ho** - *Contributor*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,119 +0,0 @@
//! A module that contains all the actions related to reading input from the terminal.
//! Like reading a line, reading a character and reading asynchronously.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crossterm_utils::Result;
pub use self::input::{input, TerminalInput};
#[cfg(unix)]
pub use self::unix_input::{AsyncReader, SyncReader};
#[cfg(windows)]
pub use self::windows_input::{AsyncReader, SyncReader};
mod input;
#[cfg(unix)]
mod unix_input;
#[cfg(windows)]
mod windows_input;
/// This trait defines the actions that can be performed with the terminal input.
/// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill
/// the wishes to work on a specific platform.
///
/// ## For example:
///
/// This trait is implemented for Windows and UNIX systems.
/// Unix is using the 'TTY' and windows is using 'libc' C functions to read the input.
trait ITerminalInput {
/// Read one character from the user input
fn read_char(&self) -> Result<char>;
/// Read the input asynchronously from the user.
fn read_async(&self) -> AsyncReader;
/// Read the input asynchronously until a certain character is hit.
fn read_until_async(&self, delimiter: u8) -> AsyncReader;
/// Read the input synchronously from the user.
fn read_sync(&self) -> SyncReader;
fn enable_mouse_mode(&self) -> Result<()>;
fn disable_mouse_mode(&self) -> Result<()>;
}
/// Enum to specify which input event has occurred.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone)]
pub enum InputEvent {
/// A single key or a combination is pressed.
Keyboard(KeyEvent),
/// A mouse event occurred.
Mouse(MouseEvent),
/// A unsupported event has occurred.
Unsupported(Vec<u8>),
/// An unknown event has occurred.
Unknown,
}
/// Enum to specify which mouse event has occurred.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Copy)]
pub enum MouseEvent {
/// A mouse press has occurred, this contains the pressed button and the position of the press.
Press(MouseButton, u16, u16),
/// A mouse button was released.
Release(u16, u16),
/// A mouse button was hold.
Hold(u16, u16),
/// An unknown mouse event has occurred.
Unknown,
}
/// Enum to define mouse buttons.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Copy)]
pub enum MouseButton {
/// Left mouse button
Left,
/// Right mouse button
Right,
/// Middle mouse button
Middle,
/// Scroll up
WheelUp,
/// Scroll down
WheelDown,
}
/// Enum with different key or key combinations.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum KeyEvent {
Backspace,
Enter,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Tab,
BackTab,
Delete,
Insert,
F(u8),
Char(char),
Alt(char),
Ctrl(char),
Null,
Esc,
CtrlUp,
CtrlDown,
CtrlRight,
CtrlLeft,
ShiftUp,
ShiftDown,
ShiftRight,
ShiftLeft,
}

View File

@ -1,158 +0,0 @@
//! A module that contains all the actions related to reading input from the terminal.
//! Like reading a line, reading a character and reading asynchronously.
use std::io;
use crossterm_utils::Result;
#[cfg(unix)]
use super::unix_input::{AsyncReader, SyncReader, UnixInput};
#[cfg(windows)]
use super::windows_input::{AsyncReader, SyncReader, WindowsInput};
use super::ITerminalInput;
/// Allows you to read user input.
///
/// # Features:
///
/// - Read character
/// - Read line
/// - Read async
/// - Read async until
/// - Read sync
/// - Wait for key event (terminal pause)
///
/// Check `/examples/` in the library for more specific examples.
pub struct TerminalInput {
#[cfg(windows)]
input: WindowsInput,
#[cfg(unix)]
input: UnixInput,
}
impl TerminalInput {
/// Create a new instance of `TerminalInput` whereon input related actions could be performed.
pub fn new() -> TerminalInput {
#[cfg(windows)]
let input = WindowsInput::new();
#[cfg(unix)]
let input = UnixInput::new();
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`, `read_async_until` or `read_sync`.
/// Not sure what 'raw mode' is, checkout the 'crossterm_screen' crate.
///
/// # Example
/// ```ignore
/// let in = input();
/// match in.read_line() {
/// Ok(s) => println!("string typed: {}", s),
/// Err(e) => println!("error: {}", e),
/// }
/// ```
pub fn read_line(&self) -> Result<String> {
let mut rv = String::new();
io::stdin().read_line(&mut rv)?;
let len = rv.trim_end_matches(&['\r', '\n'][..]).len();
rv.truncate(len);
Ok(rv)
}
/// Read one character from the user input
///
/// ```ignore
/// let in = input();
/// match in.read_char() {
/// Ok(c) => println!("character pressed: {}", c),
/// Err(e) => println!("error: {}", e),
/// }
/// ```
pub fn read_char(&self) -> Result<char> {
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.
///
/// If you want a blocking, or less resource consuming read to happen use `read_sync()`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This is the same as `read_async()` but stops reading when a certain character is hit.
///
/// # Remarks
/// - Readings won't be blocking calls.
/// A thread will be fired to read input, on unix systems from TTY and on windows WinApi
/// `ReadConsoleW` will be used.
/// - Input events read from the user will be queued on a MPSC-channel.
/// - The reading thread will be cleaned up when it drops.
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is? Please checkout the 'crossterm_screen' crate.
///
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_async(&self) -> AsyncReader {
self.input.read_async()
}
/// Read the input asynchronously until a certain delimiter (character as byte) is hit, which means that input events are gathered on the background and will be queued for you to read.
///
/// If you want a blocking or less resource consuming read to happen, use `read_sync()`. This will leave alone the background thread and queues and will be a blocking read.
///
/// This is the same as `read_async()` but stops reading when a certain character is hit.
///
/// # Remarks
/// - Readings won't be blocking calls.
/// A thread will be fired to read input, on unix systems from TTY and on windows WinApi
/// `ReadConsoleW` will be used.
/// - Input events read from the user will be queued on a MPSC-channel.
/// - The reading thread will be cleaned up when it drops.
/// - Requires 'raw screen to be enabled'.
/// Not sure what this is? Please checkout the 'crossterm_screen' crate.
///
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_until_async(&self, delimiter: u8) -> AsyncReader {
self.input.read_until_async(delimiter)
}
/// Read the input synchronously from the user, which means that reading calls will block.
/// It also uses less resources than the `AsyncReader` because the background thread and queues are left alone.
///
/// Consider using `read_async` if you don't want the reading call to block your program.
///
/// # Remark
/// - Readings will be blocking calls.
///
/// # Examples
/// Please checkout the example folder in the repository.
pub fn read_sync(&self) -> SyncReader {
self.input.read_sync()
}
/// Enable mouse events to be captured.
///
/// When enabling mouse input, you will be able to capture mouse movements, pressed buttons, and locations.
///
/// # Remark
/// - Mouse events will be send over the reader created with `read_async`, `read_async_until`, `read_sync`.
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) -> Result<()> {
self.input.disable_mouse_mode()
}
}
/// Get a `TerminalInput` instance whereon input related actions can be performed.
pub fn input() -> TerminalInput {
TerminalInput::new()
}

View File

@ -1,506 +0,0 @@
//! This is a UNIX specific implementation for input related action.
use std::char;
use std::sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc,
};
use std::{
io::{self, Read},
str, thread,
};
use crossterm_utils::{csi, write_cout, ErrorKind, Result};
use crate::sys::unix::{get_tty, read_char_raw};
use super::{ITerminalInput, InputEvent, KeyEvent, MouseButton, MouseEvent};
pub struct UnixInput;
impl UnixInput {
pub fn new() -> UnixInput {
UnixInput {}
}
}
impl ITerminalInput for UnixInput {
fn read_char(&self) -> Result<char> {
read_char_raw()
}
fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| {
for i in get_tty().unwrap().bytes() {
if event_tx.send(i.unwrap()).is_err() {
return;
}
if cancellation_token.load(Ordering::SeqCst) {
return;
}
}
}))
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| {
for byte in get_tty().unwrap().bytes() {
let byte = byte.unwrap();
let end_of_stream = byte == delimiter;
let send_error = event_tx.send(byte).is_err();
if end_of_stream || send_error || cancellation_token.load(Ordering::SeqCst) {
return;
}
}
}))
}
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) -> Result<()> {
write_cout!(&format!(
"{}l{}l{}l{}l",
csi!("?1006"),
csi!("?1015"),
csi!("?1002"),
csi!("?1000")
))?;
Ok(())
}
}
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
///
/// **[SyncReader](./LINK)**
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This type is an iterator, and could be used to iterate over input events.
///
/// # Remarks
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
pub struct AsyncReader {
event_rx: Receiver<u8>,
shutdown: Arc<AtomicBool>,
}
impl AsyncReader {
/// Construct a new instance of the `AsyncReader`.
/// The reading will immediately start when calling this function.
pub fn new(function: Box<dyn Fn(&Sender<u8>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
let shutdown_handle = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx) = mpsc::channel();
let thread_shutdown = shutdown_handle.clone();
thread::spawn(move || loop {
function(&event_tx, &thread_shutdown);
});
AsyncReader {
event_rx,
shutdown: shutdown_handle,
}
}
/// Stop the input event reading.
///
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
///
/// # Remarks
/// - Background thread will be closed.
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
pub fn stop(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
}
}
impl Iterator for AsyncReader {
type Item = InputEvent;
/// Check if there are input events to read.
///
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
///
/// # Remark
/// - This is **not** a blocking call.
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.event_rx.try_iter();
match iterator.next() {
Some(char_value) => {
if let Ok(char_value) = parse_event(char_value, &mut iterator) {
Some(char_value)
} else {
None
}
}
None => None,
}
}
}
impl Drop for AsyncReader {
fn drop(&mut self) {
self.stop();
}
}
/// This type allows you to read input synchronously, which means that reading calls will block.
///
/// This type is an iterator, and can be used to iterate over input events.
///
/// 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 {
source: Box<std::fs::File>,
leftover: Option<u8>,
}
impl Iterator for SyncReader {
type Item = InputEvent;
/// Read input from the user.
///
/// If there are no keys pressed, this will be a blocking call until there is one.
/// 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> {
// TODO: Currently errors are consumed and converted to a `None`. Maybe we shouldn't 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 iter.flatten()) {
self.leftover = option_iter.next();
Some(e)
} else {
None
}
}
Ok(_) => unreachable!(),
Err(_) => return None, /* maybe we should not throw away the error?*/
};
res
}
}
/// Parse an Event from `item` and possibly subsequent bytes through `iter`.
pub(crate) fn parse_event<I>(item: u8, iter: &mut I) -> Result<InputEvent>
where
I: Iterator<Item = u8>,
{
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();
// This is an escape character, leading a control sequence.
match a {
Some(b'O') => {
match iter.next() {
// F1-F4
Some(val @ b'P'..=b'S') => {
InputEvent::Keyboard(KeyEvent::F(1 + val - b'P'))
}
_ => return Err(error),
}
}
Some(b'[') => {
// This is a CSI sequence.
parse_csi(iter)
}
Some(b'\x1B') => InputEvent::Keyboard(KeyEvent::Esc),
Some(c) => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Alt(ch?))
}
None => InputEvent::Keyboard(KeyEvent::Esc),
}
}
b'\r' | b'\n' => InputEvent::Keyboard(KeyEvent::Enter),
b'\t' => InputEvent::Keyboard(KeyEvent::Tab),
b'\x7F' => InputEvent::Keyboard(KeyEvent::Backspace),
c @ b'\x01'..=b'\x1A' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
}
c @ b'\x1C'..=b'\x1F' => {
InputEvent::Keyboard(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
}
b'\0' => InputEvent::Keyboard(KeyEvent::Null),
c => {
let ch = parse_utf8_char(c, iter);
InputEvent::Keyboard(KeyEvent::Char(ch?))
}
};
Ok(input_event)
}
/// Parses a CSI sequence, just after reading ^[
/// Returns Event::Unknown if an unrecognized sequence is found.
/// Most of this parsing code is been taken over from 'termion`.
fn parse_csi<I>(iter: &mut I) -> InputEvent
where
I: Iterator<Item = u8>,
{
match iter.next() {
Some(b'[') => match iter.next() {
// NOTE (@imdaveho): cannot find when this occurs;
// having another '[' after ESC[ not a likely scenario
Some(val @ b'A'..=b'E') => InputEvent::Keyboard(KeyEvent::F(1 + val - b'A')),
_ => InputEvent::Unknown,
},
Some(b'D') => InputEvent::Keyboard(KeyEvent::Left),
Some(b'C') => InputEvent::Keyboard(KeyEvent::Right),
Some(b'A') => InputEvent::Keyboard(KeyEvent::Up),
Some(b'B') => InputEvent::Keyboard(KeyEvent::Down),
Some(b'H') => InputEvent::Keyboard(KeyEvent::Home),
Some(b'F') => InputEvent::Keyboard(KeyEvent::End),
Some(b'Z') => InputEvent::Keyboard(KeyEvent::BackTab),
Some(b'M') => {
// X10 emulation mouse encoding: ESC [ CB Cx Cy (6 characters only).
// NOTE (@imdaveho): cannot find documentation on this
let mut next = || iter.next().unwrap();
let cb = next() as i8 - 32;
// (1, 1) are the coords for upper left.
let cx = next().saturating_sub(32) as u16;
let cy = next().saturating_sub(32) as u16;
InputEvent::Mouse(match cb & 0b11 {
0 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelUp, cx, cy)
} else {
MouseEvent::Press(MouseButton::Left, cx, cy)
}
}
1 => {
if cb & 0x40 != 0 {
MouseEvent::Press(MouseButton::WheelDown, cx, cy)
} else {
MouseEvent::Press(MouseButton::Middle, cx, cy)
}
}
2 => MouseEvent::Press(MouseButton::Right, cx, cy),
3 => MouseEvent::Release(cx, cy),
_ => MouseEvent::Unknown,
})
}
Some(b'<') => {
// xterm mouse handling:
// ESC [ < Cb ; Cx ; Cy (;) (M or m)
let mut buf = Vec::new();
let mut c = iter.next().unwrap();
while match c {
b'm' | b'M' => false,
_ => true,
} {
buf.push(c);
c = iter.next().unwrap();
}
let str_buf = String::from_utf8(buf).unwrap();
let nums = &mut str_buf.split(';');
let cb = nums.next().unwrap().parse::<u16>().unwrap();
let cx = nums.next().unwrap().parse::<u16>().unwrap();
let cy = nums.next().unwrap().parse::<u16>().unwrap();
match cb {
0..=2 | 64..=65 => {
let button = match cb {
0 => MouseButton::Left,
1 => MouseButton::Middle,
2 => MouseButton::Right,
64 => MouseButton::WheelUp,
65 => MouseButton::WheelDown,
_ => unreachable!(),
};
match c {
b'M' => InputEvent::Mouse(MouseEvent::Press(button, cx, cy)),
b'm' => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
32 => InputEvent::Mouse(MouseEvent::Hold(cx, cy)),
3 => InputEvent::Mouse(MouseEvent::Release(cx, cy)),
_ => InputEvent::Unknown,
}
}
Some(c @ b'0'..=b'9') => {
// Numbered escape code.
let mut buf = Vec::new();
buf.push(c);
let mut character = iter.next().unwrap();
// The final byte of a CSI sequence can be in the range 64-126, so
// let's keep reading anything else.
while character < 64 || character > 126 {
buf.push(character);
character = iter.next().unwrap();
}
match character {
// rxvt mouse encoding:
// ESC [ Cb ; Cx ; Cy ; M
b'M' => {
let str_buf = String::from_utf8(buf).unwrap();
let nums: Vec<u16> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
let cb = nums[0];
let cx = nums[1];
let cy = nums[2];
let event = match cb {
32 => MouseEvent::Press(MouseButton::Left, cx, cy),
33 => MouseEvent::Press(MouseButton::Middle, cx, cy),
34 => MouseEvent::Press(MouseButton::Right, cx, cy),
35 => MouseEvent::Release(cx, cy),
64 => MouseEvent::Hold(cx, cy),
96 | 97 => MouseEvent::Press(MouseButton::WheelUp, cx, cy),
_ => MouseEvent::Unknown,
};
InputEvent::Mouse(event)
}
// Special key code.
b'~' => {
let str_buf = String::from_utf8(buf).unwrap();
// This CSI sequence can be a list of semicolon-separated numbers.
let nums: Vec<u8> = str_buf.split(';').map(|n| n.parse().unwrap()).collect();
if nums.is_empty() {
return InputEvent::Unknown;
}
// TODO: handle multiple values for key modifiers (ex: values [3, 2] means Shift+Delete)
if nums.len() > 1 {
return InputEvent::Unknown;
}
match nums[0] {
1 | 7 => InputEvent::Keyboard(KeyEvent::Home),
2 => InputEvent::Keyboard(KeyEvent::Insert),
3 => InputEvent::Keyboard(KeyEvent::Delete),
4 | 8 => InputEvent::Keyboard(KeyEvent::End),
5 => InputEvent::Keyboard(KeyEvent::PageUp),
6 => InputEvent::Keyboard(KeyEvent::PageDown),
v @ 11..=15 => InputEvent::Keyboard(KeyEvent::F(v - 10)),
v @ 17..=21 => InputEvent::Keyboard(KeyEvent::F(v - 11)),
v @ 23..=24 => InputEvent::Keyboard(KeyEvent::F(v - 12)),
_ => InputEvent::Unknown,
}
}
e => match (buf.last().unwrap(), e) {
(53, 65) => InputEvent::Keyboard(KeyEvent::CtrlUp),
(53, 66) => InputEvent::Keyboard(KeyEvent::CtrlDown),
(53, 67) => InputEvent::Keyboard(KeyEvent::CtrlRight),
(53, 68) => InputEvent::Keyboard(KeyEvent::CtrlLeft),
(50, 65) => InputEvent::Keyboard(KeyEvent::ShiftUp),
(50, 66) => InputEvent::Keyboard(KeyEvent::ShiftDown),
(50, 67) => InputEvent::Keyboard(KeyEvent::ShiftRight),
(50, 68) => InputEvent::Keyboard(KeyEvent::ShiftLeft),
_ => InputEvent::Unknown,
},
}
}
_ => InputEvent::Unknown,
}
}
/// Parse `c` as either a single byte ASCII char or a variable size UTF-8 char.
fn parse_utf8_char<I>(c: u8, iter: &mut I) -> Result<char>
where
I: Iterator<Item = u8>,
{
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)
} else {
let mut bytes = Vec::new();
bytes.push(c);
while let Some(next) = iter.next() {
bytes.push(next);
if let Ok(st) = str::from_utf8(&bytes) {
return Ok(st.chars().next().unwrap());
}
if bytes.len() >= 4 {
return error;
}
}
return error;
}
}
#[cfg(test)]
mod tests {
use super::parse_utf8_char;
#[test]
fn test_parse_utf8() {
let st = "abcéŷ¤£€ù%323";
let ref mut bytes = st.bytes();
let chars = st.chars();
for c in chars {
let b = bytes.next().unwrap();
assert_eq!(c, parse_utf8_char(b, bytes).unwrap());
}
}
}

View File

@ -1,486 +0,0 @@
//! This is a WINDOWS specific implementation for input related action.
use std::sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
Arc,
};
use std::time::Duration;
use std::{char, io, thread};
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 crossterm_utils::Result;
use crossterm_winapi::{
ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord,
MouseEvent,
};
use super::{ITerminalInput, InputEvent, KeyEvent, MouseButton};
pub struct WindowsInput;
impl WindowsInput {
pub fn new() -> WindowsInput {
WindowsInput
}
}
const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008;
// NOTE (@imdaveho): this global var is terrible -> move it elsewhere...
static mut ORIG_MODE: u32 = 0;
impl ITerminalInput for WindowsInput {
fn read_char(&self) -> Result<char> {
// _getwch is without echo and _getwche is with echo
let pressed_char = unsafe { _getwche() };
// we could return error but maybe option to keep listening until valid character is inputted.
if pressed_char == 0 || pressed_char == 0xe0 {
Err(io::Error::new(
io::ErrorKind::Other,
"Given input char is not a valid char, mostly occurs when pressing special keys",
))?;
}
let ch = char::from_u32(pressed_char as u32).ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Could not parse given input to char")
})?;
Ok(ch)
}
fn read_async(&self) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for i in read_input_events().unwrap().1 {
if event_tx.send(i).is_err() {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
}
thread::sleep(Duration::from_millis(1));
}))
}
fn read_until_async(&self, delimiter: u8) -> AsyncReader {
AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop {
for event in read_input_events().unwrap().1 {
if let InputEvent::Keyboard(KeyEvent::Char(key)) = event {
if (key as u8) == delimiter {
return;
}
}
if cancellation_token.load(Ordering::SeqCst) {
return;
} else {
if event_tx.send(event).is_err() {
return;
}
}
thread::sleep(Duration::from_millis(1));
}
}))
}
fn read_sync(&self) -> SyncReader {
SyncReader
}
fn enable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
unsafe {
ORIG_MODE = mode.mode()?;
mode.set_mode(ENABLE_MOUSE_MODE)?;
}
Ok(())
}
fn disable_mouse_mode(&self) -> Result<()> {
let mode = ConsoleMode::from(Handle::current_in_handle()?);
mode.set_mode(unsafe { ORIG_MODE })?;
Ok(())
}
}
/// This type allows you to read input synchronously, which means that reading call will be blocking ones.
///
/// This type is an iterator, and could be used to iterate over input events.
///
/// 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;
impl Iterator for SyncReader {
type Item = InputEvent;
/// Read input from the user.
///
/// 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> {
read_single_event().unwrap()
}
}
/// This type allows you to read the input asynchronously which means that input events are gathered on the background and will be queued for you to read.
///
/// **[SyncReader](./LINK)**
/// If you want a blocking, or less resource consuming read to happen use `SyncReader`, this will leave a way all the thread and queueing and will be a blocking read.
///
/// This type is an iterator, and could be used to iterate over input events.
///
/// # Remarks
/// - Threads spawned will be disposed of as soon the `AsyncReader` goes out of scope.
/// - MPSC-channels are used to queue input events, this type implements an iterator of the rx side of the queue.
pub struct AsyncReader {
event_rx: Receiver<InputEvent>,
shutdown: Arc<AtomicBool>,
}
impl AsyncReader {
/// Construct a new instance of the `AsyncReader`.
/// The reading will immediately start when calling this function.
pub fn new(function: Box<dyn Fn(&Sender<InputEvent>, &Arc<AtomicBool>) + Send>) -> AsyncReader {
let shutdown_handle = Arc::new(AtomicBool::new(false));
let (event_tx, event_rx) = mpsc::channel();
let thread_shutdown = shutdown_handle.clone();
thread::spawn(move || loop {
function(&event_tx, &thread_shutdown);
});
AsyncReader {
event_rx,
shutdown: shutdown_handle,
}
}
/// Stop the input event reading.
///
/// You don't necessarily have to call this function because it will automatically be called when this reader goes out of scope.
///
/// # Remarks
/// - Background thread will be closed.
/// - This will consume the handle you won't be able to restart the reading with this handle, create a new `AsyncReader` instead.
pub fn stop(&mut self) {
self.shutdown.store(true, Ordering::SeqCst);
}
}
impl Drop for AsyncReader {
fn drop(&mut self) {
self.stop();
}
}
impl Iterator for AsyncReader {
type Item = InputEvent;
/// Check if there are input events to read.
///
/// It will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read.
///
/// # Remark
/// - This is **not** a blocking call.
/// - When calling this method to fast after each other the reader might not have read a full byte sequence of some pressed key.
/// Make sure that you have some delay of a few ms when calling this method.
fn next(&mut self) -> Option<Self::Item> {
let mut iterator = self.event_rx.try_iter();
iterator.next()
}
}
extern "C" {
fn _getwche() -> INT;
}
fn read_single_event() -> Result<Option<InputEvent>> {
let console = Console::from(Handle::current_in_handle()?);
let input = match console.read_single_input_event()? {
Some(event) => event,
None => return Ok(None),
};
match input.event_type {
InputEventType::KeyEvent => {
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
}
InputEventType::MouseEvent => {
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
}
// NOTE (@imdaveho): ignore below
InputEventType::WindowBufferSizeEvent => return Ok(None), // TODO implement terminal resize event
InputEventType::FocusEvent => Ok(None),
InputEventType::MenuEvent => Ok(None),
}
}
/// partially inspired by: https://github.com/retep998/wio-rs/blob/master/src/console.rs#L130
fn read_input_events() -> Result<(u32, Vec<InputEvent>)> {
let console = Console::from(Handle::current_in_handle()?);
let result = console.read_console_input()?;
let mut input_events = Vec::with_capacity(result.0 as usize);
for input in result.1 {
match input.event_type {
InputEventType::KeyEvent => {
if let Ok(Some(event)) =
handle_key_event(unsafe { KeyEventRecord::from(*input.event.KeyEvent()) })
{
input_events.push(event)
}
}
InputEventType::MouseEvent => {
if let Ok(Some(event)) =
handle_mouse_event(unsafe { MouseEvent::from(*input.event.MouseEvent()) })
{
input_events.push(event)
}
}
// NOTE (@imdaveho): ignore below
InputEventType::WindowBufferSizeEvent => (), // TODO implement terminal resize event
InputEventType::FocusEvent => (),
InputEventType::MenuEvent => (),
}
}
return Ok((result.0, input_events));
}
fn handle_mouse_event(mouse_event: MouseEvent) -> Result<Option<InputEvent>> {
if let Some(event) = parse_mouse_event_record(&mouse_event) {
return Ok(Some(InputEvent::Mouse(event)));
}
Ok(None)
}
fn handle_key_event(key_event: KeyEventRecord) -> Result<Option<InputEvent>> {
if key_event.key_down {
if let Some(event) = parse_key_event_record(&key_event) {
return Ok(Some(InputEvent::Keyboard(event)));
}
}
return Ok(None);
}
fn parse_key_event_record(key_event: &KeyEventRecord) -> Option<KeyEvent> {
let key_code = key_event.virtual_key_code as i32;
match key_code {
VK_SHIFT | VK_CONTROL | VK_MENU => None,
VK_BACK => Some(KeyEvent::Backspace),
VK_ESCAPE => Some(KeyEvent::Esc),
VK_RETURN => Some(KeyEvent::Enter),
VK_F1 | VK_F2 | VK_F3 | VK_F4 | VK_F5 | VK_F6 | VK_F7 | VK_F8 | VK_F9 | VK_F10 | VK_F11
| VK_F12 => Some(KeyEvent::F((key_event.virtual_key_code - 111) as u8)),
VK_LEFT | VK_UP | VK_RIGHT | VK_DOWN => {
// Modifier Keys (Ctrl, Shift) Support
let key_state = &key_event.control_key_state;
let ctrl_pressed = key_state.has_state(RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED);
let shift_pressed = key_state.has_state(SHIFT_PRESSED);
let event = match key_code {
VK_LEFT => {
if ctrl_pressed {
Some(KeyEvent::CtrlLeft)
} else if shift_pressed {
Some(KeyEvent::ShiftLeft)
} else {
Some(KeyEvent::Left)
}
}
VK_UP => {
if ctrl_pressed {
Some(KeyEvent::CtrlUp)
} else if shift_pressed {
Some(KeyEvent::ShiftUp)
} else {
Some(KeyEvent::Up)
}
}
VK_RIGHT => {
if ctrl_pressed {
Some(KeyEvent::CtrlRight)
} else if shift_pressed {
Some(KeyEvent::ShiftRight)
} else {
Some(KeyEvent::Right)
}
}
VK_DOWN => {
if ctrl_pressed {
Some(KeyEvent::CtrlDown)
} else if shift_pressed {
Some(KeyEvent::ShiftDown)
} else {
Some(KeyEvent::Down)
}
}
_ => None,
};
event
}
VK_PRIOR | VK_NEXT => {
if key_code == VK_PRIOR {
Some(KeyEvent::PageUp)
} else if key_code == VK_NEXT {
Some(KeyEvent::PageDown)
} else {
None
}
}
VK_END | VK_HOME => {
if key_code == VK_HOME {
Some(KeyEvent::Home)
} else if key_code == VK_END {
Some(KeyEvent::End)
} else {
None
}
}
VK_DELETE => Some(KeyEvent::Delete),
VK_INSERT => Some(KeyEvent::Insert),
_ => {
// Modifier Keys (Ctrl, Alt, Shift) Support
let character_raw = { (unsafe { *key_event.u_char.UnicodeChar() } as u16) };
if character_raw < 255 {
let character = character_raw as u8 as char;
let key_state = &key_event.control_key_state;
if key_state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) {
// If the ALT key is held down, pressing the A key produces ALT+A, which the system does not treat as a character at all, but rather as a system command.
// The pressed command is stored in `virtual_key_code`.
let command = key_event.virtual_key_code as u8 as char;
if (command).is_alphabetic() {
Some(KeyEvent::Alt(command))
} else {
None
}
} else if key_state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) {
match character_raw as u8 {
c @ b'\x01'..=b'\x1A' => {
Some(KeyEvent::Ctrl((c as u8 - 0x1 + b'a') as char))
}
c @ b'\x1C'..=b'\x1F' => {
Some(KeyEvent::Ctrl((c as u8 - 0x1C + b'4') as char))
}
_ => None,
}
} else if key_state.has_state(SHIFT_PRESSED) {
// Shift + key press, essentially the same as single key press
// Separating to be explicit about the Shift press.
if character == '\t' {
Some(KeyEvent::BackTab)
} else {
Some(KeyEvent::Tab)
}
} else {
Some(KeyEvent::Char(character))
}
} else {
None
}
}
}
}
fn parse_mouse_event_record(event: &MouseEvent) -> Option<super::MouseEvent> {
// NOTE (@imdaveho): xterm emulation takes the digits of the coords and passes them
// individually as bytes into a buffer; the below cxbs and cybs replicates that and
// mimicks the behavior; additionally, in xterm, mouse move is only handled when a
// mouse button is held down (ie. mouse drag)
let xpos = event.mouse_position.x + 1;
let ypos = event.mouse_position.y + 1;
// TODO (@imdaveho): check if linux only provides coords for visible terminal window vs the total buffer
match event.event_flags {
EventFlags::PressOrRelease => {
// Single click
match event.button_state {
ButtonState::Release => Some(super::MouseEvent::Release(xpos as u16, ypos as u16)),
ButtonState::FromLeft1stButtonPressed => {
// left click
Some(super::MouseEvent::Press(
MouseButton::Left,
xpos as u16,
ypos as u16,
))
}
ButtonState::RightmostButtonPressed => {
// right click
Some(super::MouseEvent::Press(
MouseButton::Right,
xpos as u16,
ypos as u16,
))
}
ButtonState::FromLeft2ndButtonPressed => {
// middle click
Some(super::MouseEvent::Press(
MouseButton::Middle,
xpos as u16,
ypos as u16,
))
}
_ => None,
}
}
EventFlags::MouseMoved => {
// Click + Move
// NOTE (@imdaveho) only register when mouse is not released
if event.button_state != ButtonState::Release {
Some(super::MouseEvent::Hold(xpos as u16, ypos as u16))
} else {
None
}
}
EventFlags::MouseWheeled => {
// Vertical scroll
// NOTE (@imdaveho) from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str
// if `button_state` is negative then the wheel was rotated backward, toward the user.
if event.button_state != ButtonState::Negative {
Some(super::MouseEvent::Press(
MouseButton::WheelUp,
xpos as u16,
ypos as u16,
))
} else {
Some(super::MouseEvent::Press(
MouseButton::WheelDown,
xpos as u16,
ypos as u16,
))
}
}
EventFlags::DoubleClick => None, // NOTE (@imdaveho): double click not supported by unix terminals
EventFlags::MouseHwheeled => None, // NOTE (@imdaveho): horizontal scroll not supported by unix terminals
// TODO: Handle Ctrl + Mouse, Alt + Mouse, etc.
}
}

View File

@ -1,10 +0,0 @@
#![deny(unused_imports)]
pub use crossterm_screen::{IntoRawMode, RawScreen};
pub use self::input::{
input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput,
};
mod input;
mod sys;

View File

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

View File

@ -1,60 +0,0 @@
use std::os::unix::io::AsRawFd;
use std::{fs, io};
use crossterm_utils::Result;
/// Get the TTY device.
///
/// This allows for getting stdio representing _only_ the TTY, and not other streams.
pub fn get_tty() -> Result<fs::File> {
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")?;
Ok(file)
}
fn get_tty_fd() -> Result<i32> {
let fd = unsafe {
if libc::isatty(libc::STDIN_FILENO) == 1 {
libc::STDIN_FILENO
} else {
let tty_f = fs::File::open("/dev/tty")?;
tty_f.as_raw_fd()
}
};
Ok(fd)
}
pub fn read_char_raw() -> Result<char> {
let mut buf = [0u8; 20];
let fd = get_tty_fd()?;
// 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 {
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
}
}?;
Ok(rv)
}

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,7 +0,0 @@
# Changes crossterm_screen 0.3.0
- `RawScreen::into_raw_mode` returns `crossterm::Result` instead of `io::Result`
- `RawScreen::disable_raw_mode` returns `crossterm::Result` instead of `io::Result`
- `AlternateScreen::to_alternate` returns `crossterm::Result` instead of `io::Result`
- `AsyncReader::stop_reading()` to `stop()`
- `RawScreen::disable_raw_mode_on_drop` to `keep_raw_mode_on_drop`

View File

@ -1,19 +0,0 @@
[package]
name = "crossterm_screen"
version = "0.3.0"
authors = ["T. Post"]
description = "A cross-platform library for raw and alternate screen."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_screen/"
license = "MIT"
keywords = ["screen", "alternate", "raw", "crossterm", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[dependencies]
crossterm_utils = { path="../crossterm_utils", version = "0.3.0"}
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["minwindef", "wincon"] }
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0" }

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,103 +0,0 @@
# Crossterm Screen | cross-platform alternate, raw screen.
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate allows you to work with alternate and raw screen cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to move between screen buffers and switch to raw-mode, it can be use individually.
Other sub-crates are:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal)
- [Crossterm Input](https://crates.io/crates/crossterm_input)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html).
In case you are wondering what 'alternate' or 'raw' screen is, you could checkout the [book](https://crossterm-rs.github.io/crossterm/docs/screen.html) describing this in more detail.
## Table of contents:
- [Getting started](#getting-started)
- [Useful links](#useful-links)
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Contributing](#contributing)
- [Authors](#authors)
- [License](#license)
## Getting Started
All examples of how `crossterm_input` works can be found in the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) directory.
And you might consider reading the [book](https://crossterm-rs.github.io/crossterm/docs/screen.html) which has a dedicated section on alternate and raw modes.
Add the `crossterm_screen` package to your `Cargo.toml` file.
```
[dependencies]
crossterm_screen = "0.2"
```
And import the `crossterm_screen` modules you want to use.
```rust
pub use crossterm_screen::{AlternateScreen, RawScreen};
```
### Useful Links
- [Documentation](https://docs.rs/crossterm_screen/)
- [Crates.io](https://crates.io/crates/crossterm_screen)
- [Book](https://crossterm-rs.github.io/crossterm/docs/screen.html)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Alternate screen
- Raw screen
Planned features:
- make is possible to switch between multiple buffers.
## Examples
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
- Windows 10 (pro)
- Windows 8.1 (N)
- Ubuntu Desktop Terminal
- Ubuntu 17.10
- (Arch, Manjaro) KDE Konsole
- Linux Mint
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.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,8 +0,0 @@
#![deny(unused_imports)]
//! A module which provides some functionalities to work with the terminal screen.
//! Like allowing you to switch between the main and alternate screen or putting the terminal into raw mode.
pub use self::screen::{AlternateScreen, IntoRawMode, RawScreen};
mod screen;
mod sys;

View File

@ -1,8 +0,0 @@
//! A module which provides some functionalities to work with the terminal screen.
//! Like allowing you to switch between main and alternate screen or putting the terminal into raw mode.
pub use self::alternate::AlternateScreen;
pub use self::raw::{IntoRawMode, RawScreen};
mod alternate;
mod raw;

View File

@ -1,80 +0,0 @@
//! This module contains all the logic for switching between alternate screen and main 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.
#[cfg(windows)]
use crossterm_utils::supports_ansi;
use crossterm_utils::Result;
#[cfg(windows)]
use crate::sys::winapi::ToAlternateScreenCommand;
use crate::sys::{self, IAlternateScreenCommand};
use super::RawScreen;
/// With this type you will be able to switch to the alternate screen and then back to the main screen.
/// Check also the Screen type for switching to alternate mode.
///
/// Although this type is available for you to use I would recommend using `Screen` instead.
pub struct AlternateScreen {
#[cfg(windows)]
command: Box<(dyn IAlternateScreenCommand + Sync + Send)>,
#[cfg(unix)]
command: sys::ToAlternateScreenCommand,
_raw_screen: Option<RawScreen>,
}
impl AlternateScreen {
/// Switch to the 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.
///
/// # 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 dimensions are exactly the same as 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(raw_mode: bool) -> Result<AlternateScreen> {
#[cfg(windows)]
let command = if supports_ansi() {
Box::from(sys::ToAlternateScreenCommand::new())
as Box<(dyn IAlternateScreenCommand + Sync + Send)>
} else {
Box::from(ToAlternateScreenCommand::new())
as Box<(dyn IAlternateScreenCommand + Sync + Send)>
};
#[cfg(unix)]
let command = sys::ToAlternateScreenCommand::new();
command.enable()?;
if raw_mode {
let raw_screen = RawScreen::into_raw_mode()?;
return Ok(AlternateScreen {
command,
_raw_screen: Some(raw_screen),
});
}
Ok(AlternateScreen {
command,
_raw_screen: None,
})
}
/// Switch the alternate screen back to the main screen.
pub fn to_main(&self) -> Result<()> {
self.command.disable()
}
}
impl Drop for AlternateScreen {
/// This will switch back to the main screen on drop.
fn drop(&mut self) {
let _ = self.to_main();
}
}

View File

@ -1,93 +0,0 @@
//! This module is used for enabling and disabling raw mode for the terminal.
//!
//! What exactly is raw state:
//! - No line buffering.
//! Normally the terminals uses line buffering. This means that the input will be send to the terminal line by line.
//! With raw mode the input will be 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 interpret as backspace but instead will be directly send 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.
//!
//! With these modes you can easier design the terminal screen.
use std::io::{Stdout, Write};
use crossterm_utils::Result;
use crate::sys;
/// A wrapper for the raw terminal state, which can be used to write to.
///
/// Please note that if this type drops, the raw screen will be undone. To prevent this behaviour call `disable_drop`.
pub struct RawScreen {
disable_raw_mode_on_drop: bool,
}
impl RawScreen {
/// Put terminal in raw mode.
pub fn into_raw_mode() -> Result<RawScreen> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let mut command = sys::winapi::RawModeCommand::new();
command.enable()?;
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
/// Put terminal back in original modes.
pub fn disable_raw_mode() -> Result<()> {
#[cfg(unix)]
let mut command = sys::unix::RawModeCommand::new();
#[cfg(windows)]
let command = sys::winapi::RawModeCommand::new();
command.disable()?;
Ok(())
}
/// Keeps the raw mode when the `RawMode` value is dropped.
pub fn keep_raw_mode_on_drop(&mut self) {
self.disable_raw_mode_on_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) -> Result<RawScreen>;
}
impl IntoRawMode for Stdout {
fn into_raw_mode(self) -> Result<RawScreen> {
RawScreen::into_raw_mode()?;
// this make's sure that raw screen will be disabled when it goes out of scope.
Ok(RawScreen {
disable_raw_mode_on_drop: true,
})
}
}
impl Drop for RawScreen {
fn drop(&mut self) {
if self.disable_raw_mode_on_drop {
let _ = RawScreen::disable_raw_mode();
}
}
}

View File

@ -1,36 +0,0 @@
use crossterm_utils::{csi, write_cout, Result};
#[cfg(unix)]
pub mod unix;
#[cfg(windows)]
pub mod winapi;
/// This command is used for switching to the alternate screen and back to the main screen.
pub struct ToAlternateScreenCommand;
impl ToAlternateScreenCommand {
pub fn new() -> ToAlternateScreenCommand {
ToAlternateScreenCommand
}
}
impl IAlternateScreenCommand for ToAlternateScreenCommand {
/// enable alternate screen.
fn enable(&self) -> Result<()> {
write_cout!(csi!("?1049h"))?;
Ok(())
}
/// disable alternate screen.
fn disable(&self) -> Result<()> {
write_cout!(csi!("?1049l"))?;
Ok(())
}
}
// This trait provides an interface for switching to the alternate screen and back.
pub trait IAlternateScreenCommand: Sync + Send {
fn enable(&self) -> Result<()>;
fn disable(&self) -> Result<()>;
}

View File

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

View File

@ -1,76 +0,0 @@
use winapi::shared::minwindef::DWORD;
use winapi::um::wincon;
use crossterm_utils::Result;
use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer};
use super::IAlternateScreenCommand;
use self::wincon::{ENABLE_LINE_INPUT, ENABLE_WRAP_AT_EOL_OUTPUT};
/// This command is used for enabling and disabling raw mode for Windows systems.
/// For more info check: https://docs.microsoft.com/en-us/windows/console/high-level-console-modes.
#[derive(Clone, Copy)]
pub struct RawModeCommand {
mask: DWORD,
}
impl RawModeCommand {
pub fn new() -> Self {
RawModeCommand {
mask: ENABLE_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 the alternate screen and back to the 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) -> Result<()> {
let alternate_screen = ScreenBuffer::create();
alternate_screen.show()?;
Ok(())
}
fn disable(&self) -> Result<()> {
let screen_buffer = ScreenBuffer::from(Handle::output_handle()?);
screen_buffer.show()?;
Ok(())
}
}

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,19 +0,0 @@
# Changes crossterm_style 0.4
- `get_available_color_count` returns no result
- `ExecutableCommand::queue` returns `crossterm::Result`
- `QueueableCommand::queue` returns `crossterm::Result`
- `available_color_count` to `available_color_count()`
- Added derives: `Debug` for `ObjectStyle` [debug-derive]
- Command API takes mutable self instead of self
# 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/crossterm-rs/crossterm/issues/87).
- Removed `ColorType` since it was unnecessary.
# Changes crossterm_style 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,20 +0,0 @@
[package]
name = "crossterm_style"
version = "0.5.0"
authors = ["T. Post"]
description = "A cross-platform library styling the terminal output."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_style/"
license = "MIT"
keywords = ["style", "color", "attributes", "crossterm", "terminal"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["wincon"] }
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0"}
[dependencies]
crossterm_utils = { path="../crossterm_utils", version = "0.3.0"}
serde = { version = "1.0.0", features = ["derive"], optional = true }

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,144 +0,0 @@
# Crossterm Style | cross-platform styling.
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate allows you to style the terminal cross-platform.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to style the terminal, and can be use individually.
Other sub-crates are:
- [Crossterm Input](https://crates.io/crates/crossterm_input)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal)
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
- [Useful links](#useful-links)
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Contributing](#contributing)
- [Authors](#authors)
- [License](#license)
## Getting Started
All examples of how `crossterm_style` works can be found in the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) directory.
Add the `crossterm_style` package to your `Cargo.toml` file.
```
[dependencies]
crossterm_style = "0.3"
```
And import the `crossterm_style` modules you want to use.
```rust
pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor, Colorize, Styler};
```
### Useful Links
- [Documentation](https://docs.rs/crossterm_input/)
- [Crates.io](https://crates.io/crates/crossterm_input)
- [Book](https://crossterm-rs.github.io/crossterm/docs/styling.html)
- [Examples](./examples)
## Features
These are the features of this crate:
- Cross-platform
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Styled output
- 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](https://crossterm-rs.github.io/crossterm/docs/styling.html#attributes) (Windows 10 and UNIX only)
## Examples
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
_style text with attributes_
```rust
use crossterm_style::{Colored, Color, Colorize, Styler, Attribute};
// pass any `Attribute` value to the formatting braces.
println!("{} Underlined {} No Underline", Attribute::Underlined, Attribute::NoUnderline);
// you could also call different attribute methods on a `&str` and keep on chaining if needed.
let styled_text = "Bold Underlined".bold().underlined();
println!("{}", styled_text);
// old-way but still usable
let styled_text = style("Bold Underlined").bold().underlined();
```
_style text 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));
// you can also call different coloring methods on a `&str`.
let styled_text = "Bold Underlined".red().on_blue();
println!("{}", styled_text);
// old-way but still usable
let styled_text = style("Bold Underlined").with(Color::Red).on(Color::Blue);
```
_style text with RGB and ANSI Value_
```rust
// custom rgb value (Windows 10 and UNIX systems)
println!("{} some colored text", Colored::Fg(Color::Rgb {
r: 10,
g: 10,
b: 10
}));
// custom ansi color value (Windows 10 and UNIX systems)
println!("{} some colored text", Colored::Fg(Color::AnsiValue(10)));
```
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
- Windows 10 (pro)
- Windows 8.1 (N)
- Ubuntu Desktop Terminal
- Ubuntu 17.10
- (Arch, Manjaro) KDE Konsole
- Linux Mint
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.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,106 +0,0 @@
//! This is a ANSI specific implementation for styling related action.
//! This module is used for Windows 10 terminals and Unix terminals by default.
use crossterm_utils::{csi, write_cout, Result};
use crate::{Attribute, Color, Colored, ITerminalColor};
pub fn get_set_fg_ansi(fg_color: Color) -> String {
format!(csi!("{}m"), color_value(Colored::Fg(fg_color)),)
}
pub fn get_set_bg_ansi(bg_color: Color) -> String {
format!(csi!("{}m"), color_value(Colored::Bg(bg_color)),)
}
pub fn get_set_attr_ansi(attribute: Attribute) -> String {
format!(csi!("{}m"), attribute as i16,)
}
pub static RESET_ANSI: &'static str = csi!("0m");
/// This struct is an ANSI escape code implementation for color related actions.
pub struct AnsiColor;
impl AnsiColor {
pub fn new() -> AnsiColor {
AnsiColor
}
}
impl ITerminalColor for AnsiColor {
fn set_fg(&self, fg_color: Color) -> Result<()> {
write_cout!(get_set_fg_ansi(fg_color))?;
Ok(())
}
fn set_bg(&self, bg_color: Color) -> Result<()> {
write_cout!(get_set_bg_ansi(bg_color))?;
Ok(())
}
fn reset(&self) -> Result<()> {
write_cout!(RESET_ANSI)?;
Ok(())
}
}
fn color_value(colored: Colored) -> String {
let mut ansi_value = String::new();
let color;
match colored {
Colored::Fg(new_color) => {
if new_color == Color::Reset {
ansi_value.push_str("39");
return ansi_value;
} else {
ansi_value.push_str("38;");
color = new_color;
}
}
Colored::Bg(new_color) => {
if new_color == Color::Reset {
ansi_value.push_str("49");
return ansi_value;
} else {
ansi_value.push_str("48;");
color = new_color;
}
}
}
let rgb_val: String;
let color_val = match color {
Color::Black => "5;0",
Color::DarkGrey => "5;8",
Color::Red => "5;9",
Color::DarkRed => "5;1",
Color::Green => "5;10",
Color::DarkGreen => "5;2",
Color::Yellow => "5;11",
Color::DarkYellow => "5;3",
Color::Blue => "5;12",
Color::DarkBlue => "5;4",
Color::Magenta => "5;13",
Color::DarkMagenta => "5;5",
Color::Cyan => "5;14",
Color::DarkCyan => "5;6",
Color::White => "5;15",
Color::Grey => "5;7",
Color::Rgb { r, g, b } => {
rgb_val = format!("2;{};{};{}", r, g, b);
rgb_val.as_str()
}
Color::AnsiValue(val) => {
rgb_val = format!("5;{}", val);
rgb_val.as_str()
}
_ => "",
};
ansi_value.push_str(color_val);
ansi_value
}

View File

@ -1,166 +0,0 @@
//! A module that contains all the actions related to the styling of the terminal.
//! Like applying attributes to text and changing the foreground and background.
use std::clone::Clone;
use std::env;
use std::fmt::Display;
#[cfg(windows)]
use crossterm_utils::supports_ansi;
use crossterm_utils::{impl_display, Command, Result};
use super::ansi_color::{self, AnsiColor};
use super::enums::{Attribute, Color};
use super::styledobject::StyledObject;
#[cfg(windows)]
use super::winapi_color::WinApiColor;
use super::ITerminalColor;
/// Allows you to style the terminal.
///
/// # Features:
///
/// - 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)
///
/// Check `/examples/` in the library for more specific examples.
pub struct TerminalColor {
#[cfg(windows)]
color: Box<(dyn ITerminalColor + Sync + Send)>,
#[cfg(unix)]
color: AnsiColor,
}
impl TerminalColor {
/// Create new instance whereon color related actions can be performed.
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(unix)]
let color = AnsiColor::new();
TerminalColor { color }
}
/// Set the foreground color to the given color.
pub fn set_fg(&self, color: Color) -> Result<()> {
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)
}
/// Reset the terminal colors and attributes to default.
pub fn reset(&self) -> Result<()> {
self.color.reset()
}
/// Get available color count.
///
/// # Remarks
///
/// This does not always provide a good result.
pub fn available_color_count(&self) -> u16 {
env::var("TERM")
.map(|x| if x.contains("256color") { 256 } else { 8 })
.unwrap_or(8)
}
}
/// Get a `TerminalColor` implementation whereon color related actions can be performed.
pub fn color() -> TerminalColor {
TerminalColor::new()
}
/// When executed, this command will set the foreground color of the terminal to the given color.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct SetFg(pub Color);
impl Command for SetFg {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_color::get_set_fg_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiColor::new().set_fg(self.0)
}
}
/// When executed, this command will set the background color of the terminal to the given color.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct SetBg(pub Color);
impl Command for SetBg {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_color::get_set_bg_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiColor::new().set_fg(self.0)
}
}
/// When executed, this command will set the given attribute to the terminal.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct SetAttr(pub Attribute);
impl Command for SetAttr {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
ansi_color::get_set_attr_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
/// When executed, this command will print the styled font to the terminal.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct PrintStyledFont<D: Display + Clone>(pub StyledObject<D>);
impl<D> Command for PrintStyledFont<D>
where
D: Display + Clone,
{
type AnsiType = StyledObject<D>;
fn ansi_code(&self) -> Self::AnsiType {
self.0.clone()
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
// attributes are not supported by WinAPI.
Ok(())
}
}
impl_display!(for SetFg);
impl_display!(for SetBg);
impl_display!(for SetAttr);
impl_display!(for PrintStyledFont<String>);
impl_display!(for PrintStyledFont<&'static str>);

View File

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

View File

@ -1,148 +0,0 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crossterm_utils::csi;
/// Enum with the different attributes to style your test.
///
/// There are few things to note:
/// - Not all attributes are supported, some of them are only supported on Windows some only on Unix,
/// and some are only very rarely supported.
/// - I got those attributes, descriptions, supportability from here: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
/// - Take note of the fact that when running your program cross-platform that some attributes might not work because of their support.
/// - When an attribute is not supported nothing will happen with the terminal state.
///
/// # Example
/// You can use an attribute in a write statement to apply the attribute to the terminal output.
///
/// ```ignore
/// println!(
/// "{} Underlined {} No Underline",
/// Attribute::Underlined,
/// Attribute::NoUnderline
/// );
/// ```
///
/// You can also call attribute functions on a `&'static str`:
/// ```ignore
/// use Colorizer;
///
/// println!("{}", style("Bold text").bold());
/// println!("{}", style("Underlined text").underlined());
/// println!("{}", style("Negative text").negative());
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Attribute {
/// All attributes off
/// [info]: This will reset all current set attributes.
/// [Supportability]: Windows, UNIX.
Reset = 0,
/// Increased Intensity
/// [info]: This will increase the text sensitivity also known as bold.
/// [Supportability]: Windows, UNIX.
Bold = 1,
/// Decreased Intensity
/// [info]: This will decrease the text sensitivity also known as bold.
/// [Supportability]: Windows, UNIX.
Dim = 2,
/// Italic Text
/// [info]: This will make the text italic.
/// [Supportability]: Not widely supported, sometimes treated as inverse.
Italic = 3,
/// This will draw a line under the text.
/// [info]: An line under a word, especially in order to show its importance.
/// [Supportability]: Windows, UNIX
Underlined = 4,
/// Slow Blinking Text
/// [info]: Blink Less than 150 per minute.
/// [Supportability]: UNIX
SlowBlink = 5,
/// Slow Blinking Text
/// [info]: MS-DOS ANSI.SYS; 150+ per minute;
/// [Supportability]: Not widely supported
RapidBlink = 6,
/// Swap foreground and background colors
/// [info]: swap foreground and background colors
/// [Supportability]: Windows, UNIX
Reverse = 7,
/// Hide text
/// [info]:
/// - This will make the text hidden.
/// - Also known as 'Conceal'
/// [Supportability]: Windows, UNIX
Hidden = 8,
/// Cross-out text
/// [info]: Characters legible, but marked for deletion.
/// [Supportability]: UNIX
CrossedOut = 9,
/// The Fraktur is a typeface belonging to the group of Gothic typefaces.
/// [info]: https://nl.wikipedia.org/wiki/Fraktur
/// [Supportability]: Rarely supported
Fraktur = 20,
/// This will turn off the bold attribute.
/// [info]:
/// - Double-underline per ECMA-48.
/// - WikiPedia: https://en.wikipedia.org/wiki/Talk:ANSI_escape_code#SGR_21%E2%80%94%60Bold_off%60_not_widely_supported
/// - Opposite of `Bold`(1)
/// [Supportability]: not widely supported
NoBold = 21,
/// Normal color or intensity
/// Neither bold nor faint
NormalIntensity = 22,
/// 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.
/// - Opposite of `Underlined.`(4)
/// [Supportability]: Windows, UNIX
NoUnderline = 24,
/// This will turn off the blinking attribute
/// [info]: Opposite of `Slow and Rapid blink.`(5,6)
/// [Supportability]: Unknown
NoBlink = 25,
/// This will turn off the reverse attribute.
/// [info]: Opposite of `Reverse`(7)
/// [Supportability]: Windows, unknown
NoInverse = 27,
/// This will make the text visible.
/// [info]: Opposite of `Hidden`(8)
/// [Supportability]: Unknown
NoHidden = 28,
/// This will turn off the crossed out attribute.
/// [info]: Opposite of `CrossedOut`(9)
/// [Supportability]: Not widely supported
NotCrossedOut = 29,
/// Framed text.
/// [Supportability]: Not widely supported
Framed = 51,
/// This will turn on the encircled attribute.
Encircled = 52,
/// This will draw a line at the top of the text.
/// [info]: Implementation defined (according to standard)
/// [Supportability]: Unknown
OverLined = 53,
/// This will turn off the framed or encircled attribute.
NotFramedOrEncircled = 54,
/// This will turn off the overLined attribute.
/// [info]: Opposite of `OverLined`(7)
/// [Supportability]: Windows, unknown
NotOverLined = 55,
#[doc(hidden)]
__Nonexhaustive,
}
impl Display for Attribute {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", format!(csi!("{}m"), *self as i16))?;
Ok(())
}
}

View File

@ -1,111 +0,0 @@
use std::convert::AsRef;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// Enum with the different colors to color your test and terminal.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Color {
// This resets the color.
Reset,
Black,
DarkGrey,
Red,
DarkRed,
Green,
DarkGreen,
Yellow,
DarkYellow,
Blue,
DarkBlue,
Magenta,
DarkMagenta,
Cyan,
DarkCyan,
White,
Grey,
/// Color representing RGB-colors;
/// r = red
/// g = green
/// b = blue
Rgb {
r: u8,
g: u8,
b: u8,
},
AnsiValue(u8),
}
impl FromStr for Color {
type Err = ();
/// Creates a `Color` from the string representation.
///
/// # Remarks
///
/// * `Color::White` is returned in case of an unknown color.
/// * This function does not return `Err` and you can safely unwrap.
fn from_str(src: &str) -> ::std::result::Result<Self, Self::Err> {
let src = src.to_lowercase();
match src.as_ref() {
"black" => Ok(Color::Black),
"dark_grey" => Ok(Color::DarkGrey),
"red" => Ok(Color::Red),
"dark_red" => Ok(Color::DarkRed),
"green" => Ok(Color::Green),
"dark_green" => Ok(Color::DarkGreen),
"yellow" => Ok(Color::Yellow),
"dark_yellow" => Ok(Color::DarkYellow),
"blue" => Ok(Color::Blue),
"dark_blue" => Ok(Color::DarkBlue),
"magenta" => Ok(Color::Magenta),
"dark_magenta" => Ok(Color::DarkMagenta),
"cyan" => Ok(Color::Cyan),
"dark_cyan" => Ok(Color::DarkCyan),
"white" => Ok(Color::White),
"grey" => Ok(Color::Grey),
_ => Ok(Color::White),
}
}
}
#[cfg(test)]
mod tests {
use super::Color;
#[test]
fn test_known_color_conversion() {
assert_eq!("black".parse(), Ok(Color::Black));
assert_eq!("dark_grey".parse(), Ok(Color::DarkGrey));
assert_eq!("red".parse(), Ok(Color::Red));
assert_eq!("dark_red".parse(), Ok(Color::DarkRed));
assert_eq!("green".parse(), Ok(Color::Green));
assert_eq!("dark_green".parse(), Ok(Color::DarkGreen));
assert_eq!("yellow".parse(), Ok(Color::Yellow));
assert_eq!("dark_yellow".parse(), Ok(Color::DarkYellow));
assert_eq!("blue".parse(), Ok(Color::Blue));
assert_eq!("dark_blue".parse(), Ok(Color::DarkBlue));
assert_eq!("magenta".parse(), Ok(Color::Magenta));
assert_eq!("dark_magenta".parse(), Ok(Color::DarkMagenta));
assert_eq!("cyan".parse(), Ok(Color::Cyan));
assert_eq!("dark_cyan".parse(), Ok(Color::DarkCyan));
assert_eq!("white".parse(), Ok(Color::White));
assert_eq!("grey".parse(), Ok(Color::Grey));
}
#[test]
fn test_unknown_color_conversion_yields_white() {
assert_eq!("foo".parse(), Ok(Color::White));
}
}

View File

@ -1,50 +0,0 @@
use std::fmt::Display;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::color::color;
use crate::enums::Color;
/// Could be used to color the foreground or background color.
///
/// `Colored::Fg` represents the foreground color.
/// `Color::Bg` represents the background color.
///
/// # Example
///
/// You can use `Colored` in a write statement to apply the attribute to the terminal output.
///
/// ```ignore
/// println!("{} Red foreground color", Colored::Fg(Color::Red));
/// println!("{} Blue background color", Colored::Bg(Color::Blue));
/// ```
///
/// You can also call coloring functions on a `&'static str`:
/// ```ignore
/// let styled_text = "Red forground color on blue background.".red().on_blue();
/// println!("{}", styled_text);
/// ```
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum Colored {
Fg(Color),
Bg(Color),
}
impl Display for Colored {
fn fmt(&self, _f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
let colored_terminal = color();
match *self {
Colored::Fg(color) => colored_terminal
.set_fg(color)
.map_err(|_| std::fmt::Error)?,
Colored::Bg(color) => colored_terminal
.set_bg(color)
.map_err(|_| std::fmt::Error)?,
}
Ok(())
}
}

View File

@ -1,121 +0,0 @@
//! A module that contains all the actions related to the styling of the terminal.
//! Like applying attributes to text and changing the foreground and background.
#![deny(unused_imports)]
use std::fmt::Display;
pub use crossterm_utils::{execute, queue, Command, ExecutableCommand, QueueableCommand, Result};
pub use self::color::{color, PrintStyledFont, SetAttr, SetBg, SetFg, TerminalColor};
pub use self::enums::{Attribute, Color, Colored};
pub use self::objectstyle::ObjectStyle;
pub use self::styledobject::StyledObject;
pub use self::traits::{Colorize, Styler};
#[macro_use]
mod macros;
mod color;
mod enums;
pub mod objectstyle;
pub mod styledobject;
mod traits;
mod ansi_color;
#[cfg(windows)]
mod winapi_color;
/// This trait defines the actions that can be performed with terminal colors.
/// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill
/// the wishes to work on a specific platform.
///
/// ## For example:
///
/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
/// so that color-related actions can be performed on both UNIX and Windows systems.
trait ITerminalColor {
/// Set the foreground color to the given color.
fn set_fg(&self, fg_color: Color) -> Result<()>;
/// Set the background color to the given color.
fn set_bg(&self, fg_color: Color) -> Result<()>;
/// Reset the terminal color to default.
fn reset(&self) -> Result<()>;
}
/// This could be used to style a type that implements `Display` with colors and attributes.
///
/// # Example
/// ```ignore
/// // get a styled object which could be painted to the terminal.
/// let styled_object = style("Some Blue colored text on black background")
/// .with(Color::Blue)
/// .on(Color::Black);
///
/// // print the styled text * times to the current screen.
/// for i in 1..10
/// {
/// println!("{}", styled_object);
/// }
/// ```
///
/// # Important Remark
///
/// - Please checkout the documentation for `Colorizer` or `Styler`.
/// Those types will make it a bit easier to style a string.
pub fn style<'a, D: 'a>(val: D) -> StyledObject<D>
where
D: Display + Clone,
{
ObjectStyle::new().apply_to(val)
}
impl Colorize<&'static str> for &'static str {
// foreground colors
def_str_color!(fg_color: black => Color::Black);
def_str_color!(fg_color: dark_grey => Color::DarkGrey);
def_str_color!(fg_color: red => Color::Red);
def_str_color!(fg_color: dark_red => Color::DarkRed);
def_str_color!(fg_color: green => Color::Green);
def_str_color!(fg_color: dark_green => Color::DarkGreen);
def_str_color!(fg_color: yellow => Color::Yellow);
def_str_color!(fg_color: dark_yellow => Color::DarkYellow);
def_str_color!(fg_color: blue => Color::Blue);
def_str_color!(fg_color: dark_blue => Color::DarkBlue);
def_str_color!(fg_color: magenta => Color::Magenta);
def_str_color!(fg_color: dark_magenta => Color::DarkMagenta);
def_str_color!(fg_color: cyan => Color::Cyan);
def_str_color!(fg_color: dark_cyan => Color::DarkCyan);
def_str_color!(fg_color: white => Color::White);
def_str_color!(fg_color: grey => Color::Grey);
// background colors
def_str_color!(bg_color: on_black => Color::Black);
def_str_color!(bg_color: on_dark_grey => Color::DarkGrey);
def_str_color!(bg_color: on_red => Color::Red);
def_str_color!(bg_color: on_dark_red => Color::DarkRed);
def_str_color!(bg_color: on_green => Color::Green);
def_str_color!(bg_color: on_dark_green => Color::DarkGreen);
def_str_color!(bg_color: on_yellow => Color::Yellow);
def_str_color!(bg_color: on_dark_yellow => Color::DarkYellow);
def_str_color!(bg_color: on_blue => Color::Blue);
def_str_color!(bg_color: on_dark_blue => Color::DarkBlue);
def_str_color!(bg_color: on_magenta => Color::Magenta);
def_str_color!(bg_color: on_dark_magenta => Color::DarkMagenta);
def_str_color!(bg_color: on_cyan => Color::Cyan);
def_str_color!(bg_color: on_dark_cyan => Color::DarkCyan);
def_str_color!(bg_color: on_white => Color::White);
def_str_color!(bg_color: on_grey => Color::Grey);
}
impl Styler<&'static str> for &'static str {
def_str_attr!(reset => Attribute::Reset);
def_str_attr!(bold => Attribute::Bold);
def_str_attr!(underlined => Attribute::Underlined);
def_str_attr!(reverse => Attribute::Reverse);
def_str_attr!(dim => Attribute::Dim);
def_str_attr!(italic => Attribute::Italic);
def_str_attr!(negative => Attribute::Reverse);
def_str_attr!(slow_blink => Attribute::SlowBlink);
def_str_attr!(rapid_blink => Attribute::RapidBlink);
def_str_attr!(hidden => Attribute::Hidden);
def_str_attr!(crossed_out => Attribute::CrossedOut);
}

View File

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

View File

@ -1,45 +0,0 @@
//! This module contains the `object style` that can be applied to an `styled object`.
use std::fmt::Display;
use super::{Attribute, Color, StyledObject};
/// Struct that contains the style properties that can be applied to a displayable object.
#[derive(Debug, Clone, Default)]
pub struct ObjectStyle {
pub fg_color: Option<Color>,
pub bg_color: Option<Color>,
pub attrs: Vec<Attribute>,
}
impl ObjectStyle {
/// Apply a `StyledObject` to the passed displayable object.
pub fn apply_to<D: Display + Clone>(&self, val: D) -> StyledObject<D> {
StyledObject {
object_style: self.clone(),
content: val,
}
}
/// Get a new instance of `ObjectStyle`
pub fn new() -> ObjectStyle {
ObjectStyle::default()
}
/// Set the background color of `ObjectStyle` to the passed color.
pub fn bg(mut self, color: Color) -> ObjectStyle {
self.bg_color = Some(color);
self
}
/// Set the foreground color of `ObjectStyle` to the passed color.
pub fn fg(mut self, color: Color) -> ObjectStyle {
self.fg_color = Some(color);
self
}
/// Add an `Attribute` to the current text. Like italic or bold.
pub fn add_attr(&mut self, attr: Attribute) {
self.attrs.push(attr);
}
}

View File

@ -1,131 +0,0 @@
//! This module contains the logic to style an object that contains some 'content' which can be styled.
use std::fmt::{self, Display, Formatter};
use std::result;
use crossterm_utils::{csi, queue};
use super::{color, Attribute, Color, Colorize, ObjectStyle, SetBg, SetFg, Styler};
/// Contains both the style and the content which can be styled.
#[derive(Clone)]
pub struct StyledObject<D: Display + Clone> {
pub object_style: ObjectStyle,
pub content: D,
}
impl<'a, D: Display + 'a + Clone> StyledObject<D> {
/// Set the foreground of the styled object to the passed `Color`.
///
/// # Remarks
///
/// This methods consumes 'self', and works like a builder.
/// By having this functionality you can do: `with().on().attr()`
pub fn with(mut self, foreground_color: Color) -> StyledObject<D> {
self.object_style = self.object_style.fg(foreground_color);
self
}
/// Set the background of the styled object to the passed `Color`.
///
/// # Remarks
///
/// This methods consumes 'self', and works like a builder.
/// By having this functionality you can do: `with().on().attr()`
pub fn on(mut self, background_color: Color) -> StyledObject<D> {
self.object_style = self.object_style.bg(background_color);
self
}
/// Set the attribute of an styled object to the passed `Attribute`.
///
/// # Remarks
///
/// This methods consumes 'self', and works like a builder.
/// By having this functionality you can do: `with().on().attr()`
pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
self.object_style.add_attr(attr);
self
}
}
impl<D: Display + Clone> Display for StyledObject<D> {
fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> {
let colored_terminal = color();
let mut reset = false;
if let Some(bg) = self.object_style.bg_color {
queue!(f, SetBg(bg)).map_err(|_| fmt::Error)?;
reset = true;
}
if let Some(fg) = self.object_style.fg_color {
queue!(f, SetFg(fg)).map_err(|_| fmt::Error)?;
reset = true;
}
for attr in self.object_style.attrs.iter() {
fmt::Display::fmt(&format!(csi!("{}m"), *attr as i16), f)?;
reset = true;
}
fmt::Display::fmt(&self.content, f)?;
if reset {
colored_terminal.reset().map_err(|_| fmt::Error)?;
}
Ok(())
}
}
impl<D: Display + Clone> Colorize<D> for StyledObject<D> {
// foreground colors
def_color!(fg_color: black => Color::Black);
def_color!(fg_color: dark_grey => Color::DarkGrey);
def_color!(fg_color: red => Color::Red);
def_color!(fg_color: dark_red => Color::DarkRed);
def_color!(fg_color: green => Color::Green);
def_color!(fg_color: dark_green => Color::DarkGreen);
def_color!(fg_color: yellow => Color::Yellow);
def_color!(fg_color: dark_yellow => Color::DarkYellow);
def_color!(fg_color: blue => Color::Blue);
def_color!(fg_color: dark_blue => Color::DarkBlue);
def_color!(fg_color: magenta => Color::Magenta);
def_color!(fg_color: dark_magenta => Color::DarkMagenta);
def_color!(fg_color: cyan => Color::Cyan);
def_color!(fg_color: dark_cyan => Color::DarkCyan);
def_color!(fg_color: white => Color::White);
def_color!(fg_color: grey => Color::Grey);
// background colors
def_color!(bg_color: on_black => Color::Black);
def_color!(bg_color: on_dark_grey => Color::DarkGrey);
def_color!(bg_color: on_red => Color::Red);
def_color!(bg_color: on_dark_red => Color::DarkRed);
def_color!(bg_color: on_green => Color::Green);
def_color!(bg_color: on_dark_green => Color::DarkGreen);
def_color!(bg_color: on_yellow => Color::Yellow);
def_color!(bg_color: on_dark_yellow => Color::DarkYellow);
def_color!(bg_color: on_blue => Color::Blue);
def_color!(bg_color: on_dark_blue => Color::DarkBlue);
def_color!(bg_color: on_magenta => Color::Magenta);
def_color!(bg_color: on_dark_magenta => Color::DarkMagenta);
def_color!(bg_color: on_cyan => Color::Cyan);
def_color!(bg_color: on_dark_cyan => Color::DarkCyan);
def_color!(bg_color: on_white => Color::White);
def_color!(bg_color: on_grey => Color::Grey);
}
impl<D: Display + Clone> Styler<D> for StyledObject<D> {
def_attr!(reset => Attribute::Reset);
def_attr!(bold => Attribute::Bold);
def_attr!(underlined => Attribute::Underlined);
def_attr!(reverse => Attribute::Reverse);
def_attr!(dim => Attribute::Dim);
def_attr!(italic => Attribute::Italic);
def_attr!(negative => Attribute::Reverse);
def_attr!(slow_blink => Attribute::SlowBlink);
def_attr!(rapid_blink => Attribute::RapidBlink);
def_attr!(hidden => Attribute::Hidden);
def_attr!(crossed_out => Attribute::CrossedOut);
}

View File

@ -1,76 +0,0 @@
use std::fmt::Display;
use crate::StyledObject;
/// Provides a set of methods to color any type implementing `Display` with attributes.
///
/// This trait is implemented for `&static str` and `StyledObject` and thus the methods of this trait could be called on them.
///
/// ```rust
/// use crossterm_style::Colorize;
///
/// let styled_text = "Red forground color on blue background.".red().on_blue();
/// println!("{}", styled_text);
/// ```
pub trait Colorize<D: Display + Clone> {
fn black(self) -> StyledObject<D>;
fn dark_grey(self) -> StyledObject<D>;
fn red(self) -> StyledObject<D>;
fn dark_red(self) -> StyledObject<D>;
fn green(self) -> StyledObject<D>;
fn dark_green(self) -> StyledObject<D>;
fn yellow(self) -> StyledObject<D>;
fn dark_yellow(self) -> StyledObject<D>;
fn blue(self) -> StyledObject<D>;
fn dark_blue(self) -> StyledObject<D>;
fn magenta(self) -> StyledObject<D>;
fn dark_magenta(self) -> StyledObject<D>;
fn cyan(self) -> StyledObject<D>;
fn dark_cyan(self) -> StyledObject<D>;
fn white(self) -> StyledObject<D>;
fn grey(self) -> StyledObject<D>;
fn on_black(self) -> StyledObject<D>;
fn on_dark_grey(self) -> StyledObject<D>;
fn on_red(self) -> StyledObject<D>;
fn on_dark_red(self) -> StyledObject<D>;
fn on_green(self) -> StyledObject<D>;
fn on_dark_green(self) -> StyledObject<D>;
fn on_yellow(self) -> StyledObject<D>;
fn on_dark_yellow(self) -> StyledObject<D>;
fn on_blue(self) -> StyledObject<D>;
fn on_dark_blue(self) -> StyledObject<D>;
fn on_magenta(self) -> StyledObject<D>;
fn on_dark_magenta(self) -> StyledObject<D>;
fn on_cyan(self) -> StyledObject<D>;
fn on_dark_cyan(self) -> StyledObject<D>;
fn on_white(self) -> StyledObject<D>;
fn on_grey(self) -> StyledObject<D>;
}
/// Provides a set of methods to style any type implementing `Display` with attributes.
///
/// This trait is implemented for `&static str` and `StyledObject` and thus the methods of this trait could be called on them.
///
/// # Example
///
/// ```rust
/// use crossterm_style::Styler;
///
/// println!("{}", "Bold text".bold());
/// println!("{}", "Underlined text".underlined());
/// println!("{}", "Negative text".negative());
/// ```
pub trait Styler<D: Display + Clone> {
fn reset(self) -> StyledObject<D>;
fn bold(self) -> StyledObject<D>;
fn underlined(self) -> StyledObject<D>;
fn reverse(self) -> StyledObject<D>;
fn dim(self) -> StyledObject<D>;
fn italic(self) -> StyledObject<D>;
fn negative(self) -> StyledObject<D>;
fn slow_blink(self) -> StyledObject<D>;
fn rapid_blink(self) -> StyledObject<D>;
fn hidden(self) -> StyledObject<D>;
fn crossed_out(self) -> StyledObject<D>;
}

View File

@ -1,191 +0,0 @@
//! This is a `WinApi` specific implementation for styling related action.
//! This module is used for non supporting `ANSI` Windows terminals.
use std::sync::Once;
use winapi::um::wincon;
use crossterm_utils::Result;
use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer};
use crate::{Color, Colored, ITerminalColor};
const FG_GREEN: u16 = wincon::FOREGROUND_GREEN;
const FG_RED: u16 = wincon::FOREGROUND_RED;
const FG_BLUE: u16 = wincon::FOREGROUND_BLUE;
const FG_INTENSITY: u16 = wincon::FOREGROUND_INTENSITY;
const BG_GREEN: u16 = wincon::BACKGROUND_GREEN;
const BG_RED: u16 = wincon::BACKGROUND_RED;
const BG_BLUE: u16 = wincon::BACKGROUND_BLUE;
const BG_INTENSITY: u16 = wincon::BACKGROUND_INTENSITY;
/// This struct is a WinApi implementation for color related actions.
pub struct WinApiColor;
impl WinApiColor {
pub fn new() -> Box<WinApiColor> {
Box::from(WinApiColor)
}
}
impl ITerminalColor for WinApiColor {
fn set_fg(&self, fg_color: Color) -> Result<()> {
// init the original color in case it is not set.
init_console_color()?;
let color_value = color_value(Colored::Fg(fg_color));
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So we need to use bitwise operators to check if the values exists or to get current console colors.
let mut color: u16;
let attrs = csbi.attributes();
let bg_color = attrs & 0x0070;
color = color_value.parse::<u16>()? | bg_color;
// background intensity is a separate value in attrs,
// wee need to check if this was applied to the current bg color.
if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 {
color = color | wincon::BACKGROUND_INTENSITY as u16;
}
Console::from(**screen_buffer.handle()).set_text_attribute(color)?;
Ok(())
}
fn set_bg(&self, bg_color: Color) -> Result<()> {
// init the original color in case it is not set.
init_console_color()?;
let color_value = color_value(Colored::Bg(bg_color));
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So wee need to use bitwise operators to check if the values exists or to get current console colors.
let mut color: u16;
let attrs = csbi.attributes();
let fg_color = attrs & 0x0007;
color = fg_color | color_value.parse::<u16>()?;
// Foreground intensity is a separate value in attrs,
// So we need to check if this was applied to the current fg color.
if (attrs & wincon::FOREGROUND_INTENSITY as u16) != 0 {
color = color | wincon::FOREGROUND_INTENSITY as u16;
}
Console::from(**screen_buffer.handle()).set_text_attribute(color)?;
Ok(())
}
fn reset(&self) -> Result<()> {
// init the original color in case it is not set.
let original_color = original_console_color();
Console::from(Handle::new(HandleType::CurrentOutputHandle)?)
.set_text_attribute(original_color)?;
Ok(())
}
}
/// This will get the winapi color value from the Color and ColorType struct
fn color_value(color: Colored) -> String {
let winapi_color: u16;
match color {
Colored::Fg(color) => {
winapi_color = match color {
Color::Black => 0,
Color::DarkGrey => FG_INTENSITY,
Color::Red => FG_INTENSITY | FG_RED,
Color::DarkRed => FG_RED,
Color::Green => FG_INTENSITY | FG_GREEN,
Color::DarkGreen => FG_GREEN,
Color::Yellow => FG_INTENSITY | FG_GREEN | FG_RED,
Color::DarkYellow => FG_GREEN | FG_RED,
Color::Blue => FG_INTENSITY | FG_BLUE,
Color::DarkBlue => FG_BLUE,
Color::Magenta => FG_INTENSITY | FG_RED | FG_BLUE,
Color::DarkMagenta => FG_RED | FG_BLUE,
Color::Cyan => FG_INTENSITY | FG_GREEN | FG_BLUE,
Color::DarkCyan => FG_GREEN | FG_BLUE,
Color::White => FG_RED | FG_GREEN | FG_BLUE,
Color::Grey => FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE,
Color::Reset => {
// init the original color in case it is not set.
let mut original_color = original_console_color();
const REMOVE_BG_MASK: u16 = BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE;
// remove all background values from the original color, we don't want to reset those.
original_color &= !(REMOVE_BG_MASK);
original_color
}
/* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/
Color::Rgb { r: _, g: _, b: _ } => 0,
Color::AnsiValue(_val) => 0,
};
}
Colored::Bg(color) => {
winapi_color = match color {
Color::Black => 0,
Color::DarkGrey => BG_INTENSITY,
Color::Red => BG_INTENSITY | BG_RED,
Color::DarkRed => BG_RED,
Color::Green => BG_INTENSITY | BG_GREEN,
Color::DarkGreen => BG_GREEN,
Color::Yellow => BG_INTENSITY | BG_GREEN | BG_RED,
Color::DarkYellow => BG_GREEN | BG_RED,
Color::Blue => BG_INTENSITY | BG_BLUE,
Color::DarkBlue => BG_BLUE,
Color::Magenta => BG_INTENSITY | BG_RED | BG_BLUE,
Color::DarkMagenta => BG_RED | BG_BLUE,
Color::Cyan => BG_INTENSITY | BG_GREEN | BG_BLUE,
Color::DarkCyan => BG_GREEN | BG_BLUE,
Color::White => BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE,
Color::Grey => BG_RED | BG_GREEN | BG_BLUE,
Color::Reset => {
// init the original color in case it is not set.
let mut original_color = original_console_color();
const REMOVE_FG_MASK: u16 = FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE;
// remove all foreground values from the original color, we don't want to reset those.
original_color &= !(REMOVE_FG_MASK);
original_color
}
/* WinApi will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/
Color::Rgb { r: _, g: _, b: _ } => 0,
Color::AnsiValue(_val) => 0,
};
}
};
winapi_color.to_string()
}
fn init_console_color() -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let attr = screen_buffer.info()?.attributes();
GET_ORIGINAL_CONSOLE_COLOR.call_once(|| {
unsafe { ORIGINAL_CONSOLE_COLOR = attr };
});
Ok(())
}
fn original_console_color() -> u16 {
return unsafe { ORIGINAL_CONSOLE_COLOR };
}
static GET_ORIGINAL_CONSOLE_COLOR: Once = Once::new();
static mut ORIGINAL_CONSOLE_COLOR: u16 = 0;

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,22 +0,0 @@
# Changes crossterm_terminal 0.3.0
- `Terminal::terminal_size` to `Terminal::size`
- `Terminal::size()` returns `Result<(u16, u16)>`
- Return written bytes: [return-written-bytes]
- Synced all `i16` values for indexing: set size, get size, scrolling to `u16` values
- Synced set/get terminal size behaviour: [fixed-get-set-terminal-size]
- `ExecutableCommand::queue` returns `crossterm::Result`
- `QueueableCommand::queue` returns `crossterm::Result`
- Command API takes mutable self instead of self
[return-written-bytes]: https://github.com/crossterm-rs/crossterm/pull/212
[fixed-get-set-terminal-size]: https://github.com/crossterm-rs/crossterm/pull/242
# Changes crossterm_terminal 0.2.2
- Terminal size Linux was not 0-based.
- Made FreeBSD compile
# Changes crossterm_terminal 0.2
- Removed `Terminal:from_output()`
# Changes crossterm_terminal 0.1
- Moved out of `crossterm` 5.4 crate.

View File

@ -1,23 +0,0 @@
[package]
name = "crossterm_terminal"
version = "0.3.0"
authors = ["T. Post"]
description = "A cross-platform library for doing terminal related actions."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_terminal/"
license = "MIT"
keywords = ["terminal", "clear", "console", "crossterm", "size"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0"}
[target.'cfg(unix)'.dependencies]
libc = "0.2.51"
[dependencies]
crossterm_utils = { path="../crossterm_utils", version = "0.3.0"}
crossterm_cursor = { path="../crossterm_cursor", version = "0.3.0"}
serde = { version = "1.0.0", features = ["derive"], optional = true }

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,140 +0,0 @@
# Crossterm Terminal | cross-platform terminal actions.
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate allows you to perform terminal related actions cross-platform e.g clearing, resizing etc.
It supports all UNIX and windows terminals down to windows 7 (not all terminals are tested see [Tested Terminals](#tested-terminals) for more info)
This crate is a sub-crate of [crossterm](https://crates.io/crates/crossterm) to perform terminal related actions, and can be use individually.
Other sub-crates are:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
- [Crossterm Input](https://crates.io/crates/crossterm_input)
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
When you want to use other modules as well you might want to use crossterm with [feature flags](https://crossterm-rs.github.io/crossterm/docs/feature_flags.html).
## Table of contents:
- [Getting started](#getting-started)
- [Useful links](#useful-links)
- [Features](#features)
- [Examples](#examples)
- [Tested Terminals](#tested-terminals)
- [Notice](#notice)
- [Contributing](#contributing)
- [Authors](#authors)
- [License](#license)
## Getting Started
All examples of how `crossterm_terminal` works can be found in the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) directory.
Add the `crossterm_terminal` package to your `Cargo.toml` file.
```
[dependencies]
crossterm_terminal = "0.2"
```
And import the `crossterm_terminal` modules you want to use.
```rust
pub use crossterm_terminal::{terminal, Terminal, ClearType};
```
### Useful Links
- [Documentation](https://docs.rs/crossterm_terminal/)
- [Crates.io](https://crates.io/crates/crossterm_terminal)
- [Examples](/examples)
## Features
These are the features of this crate:
- Cross-platform
- Multithreaded (send, sync)
- Detailed Documentation
- Few Dependencies
- Terminal
- Clearing (all lines, current line, from cursor down and up, until new line)
- Scrolling (up, down)
- Terminal Size (get/set)
- Exit Current Process
## Command API
My first recommendation is to use the [command API](https://crossterm-rs.github.io/crossterm/docs/command.html) because this might replace some of the existing API in the future.
Because it is more convenient, faster, and easier to use.
## Examples
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
```rust
use crossterm::terminal::{terminal,ClearType};
let mut terminal = terminal();
// Clear all lines in terminal;
terminal.clear(ClearType::All)?;
// Clear all cells from current cursor position down.
terminal.clear(ClearType::FromCursorDown)?;
// Clear all cells from current cursor position down.
terminal.clear(ClearType::FromCursorUp)?;
// Clear current line cells.
terminal.clear(ClearType::CurrentLine)?;
// Clear all the cells until next line.
terminal.clear(ClearType::UntilNewLine)?;
// Get terminal size
let (width, height) = terminal.size()?;
print!("X: {}, y: {}", width, height);
// Scroll down, up 10 lines.
terminal.scroll_down(10)?;
terminal.scroll_up(10)?;
// Set terminal size (width, height)
terminal.set_size(10,10)?;
// exit the current process.
terminal.exit();
// write to the terminal whether you are on the main screen or alternate screen.
terminal.write("Some text\n Some text on new line");
```
## Tested terminals
- Windows Powershell
- Windows 10 (pro)
- Windows CMD
- Windows 10 (pro)
- Windows 8.1 (N)
- Ubuntu Desktop Terminal
- Ubuntu 17.10
- (Arch, Manjaro) KDE Konsole
- Linux Mint
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.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

@ -1,8 +0,0 @@
#![deny(unused_imports)]
pub use crossterm_utils::{execute, queue, Command, ExecutableCommand, QueueableCommand, Result};
pub use self::terminal::{terminal, Clear, ClearType, ScrollDown, ScrollUp, SetSize, Terminal};
mod sys;
mod terminal;

View File

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

View File

@ -1,25 +0,0 @@
use libc::{ioctl, winsize, STDOUT_FILENO, TIOCGWINSZ};
use crossterm_utils::Result;
pub fn exit() {
::std::process::exit(0);
}
/// Get the current terminal size.
pub fn get_terminal_size() -> Result<(u16, u16)> {
// http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc
let mut size = winsize {
ws_row: 0,
ws_col: 0,
ws_xpixel: 0,
ws_ypixel: 0,
};
let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ.into(), &mut size) };
if r == 0 {
Ok((size.ws_col, size.ws_row))
} else {
Err(std::io::Error::last_os_error().into())
}
}

View File

@ -1,17 +0,0 @@
use crossterm_utils::Result;
use crossterm_winapi::ScreenBuffer;
/// Exit the current process.
pub fn exit() {
::std::process::exit(256);
}
#[cfg(windows)]
pub fn get_terminal_size() -> Result<(u16, u16)> {
let terminal_size = ScreenBuffer::current()?.info()?.terminal_size();
// windows starts counting at 0, unix at 1, add one to replicated unix behaviour.
Ok((
(terminal_size.width + 1) as u16,
(terminal_size.height + 1) as u16,
))
}

View File

@ -1,53 +0,0 @@
//! A module that contains all the actions related to the terminal. like clearing, resizing, pausing and scrolling the terminal.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crossterm_utils::Result;
use self::ansi_terminal::AnsiTerminal;
pub use self::terminal::{terminal, Clear, ScrollDown, ScrollUp, SetSize, Terminal};
#[cfg(windows)]
use self::winapi_terminal::WinApiTerminal;
mod terminal;
mod ansi_terminal;
#[cfg(windows)]
mod winapi_terminal;
/// Enum with the different values to clear the terminal.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub enum ClearType {
/// clear all cells in terminal.
All,
/// clear all cells from the cursor position downwards in terminal.
FromCursorDown,
/// clear all cells from the cursor position upwards in terminal.
FromCursorUp,
/// clear current line cells in terminal.
CurrentLine,
/// clear all cells from cursor position until new line in terminal.
UntilNewLine,
}
/// This trait defines the actions that can be performed with the terminal color.
/// This trait can be implemented so that an concrete implementation of the ITerminalColor can fulfill.
/// the wishes to work on an specific platform.
///
/// ## For example:
///
/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific),
/// so that terminal related actions can be performed on both Unix and Windows systems.
trait ITerminal {
/// Clear the current cursor by specifying the clear type
fn clear(&self, clear_type: ClearType) -> Result<()>;
/// Get the terminal size (x,y)
fn size(&self) -> Result<(u16, u16)>;
/// Scroll `n` lines up in the current terminal.
fn scroll_up(&self, count: u16) -> Result<()>;
/// Scroll `n` lines down in the current terminal.
fn scroll_down(&self, count: u16) -> Result<()>;
/// Resize terminal to the given width and height.
fn set_size(&self, width: u16, height: u16) -> Result<()>;
}

View File

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

View File

@ -1,227 +0,0 @@
//! A module that contains all the actions related to the terminal.
//! Like clearing and scrolling in the terminal or getting the window size from the terminal.
use std::fmt;
#[cfg(windows)]
use crossterm_utils::supports_ansi;
use crossterm_utils::{impl_display, write_cout, Command, Result};
#[cfg(windows)]
use super::WinApiTerminal;
use super::{AnsiTerminal, ClearType, ITerminal};
/// Allows you to preform actions on the terminal.
///
/// # Features:
///
/// - 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
///
/// Check `/examples/` in the library for more specific examples.
pub struct Terminal {
#[cfg(windows)]
terminal: Box<(dyn ITerminal + Sync + Send)>,
#[cfg(unix)]
terminal: AnsiTerminal,
}
impl Terminal {
/// Create new terminal instance whereon terminal related actions can be performed.
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(unix)]
let terminal = AnsiTerminal::new();
Terminal { terminal }
}
/// Clear the current cursor by specifying the `ClearType`.
///
/// # Example
/// ```rust
/// # use crossterm_terminal as crossterm;
/// # use crossterm_terminal::terminal;
/// let mut term = terminal();
///
/// // clear all cells in terminal.
/// term.clear(crossterm::ClearType::All);
/// // clear all cells from the cursor position downwards in terminal.
/// term.clear(crossterm::ClearType::FromCursorDown);
/// // clear all cells from the cursor position upwards in terminal.
/// term.clear(crossterm::ClearType::FromCursorUp);
/// // clear current line cells in terminal.
/// term.clear(crossterm::ClearType::CurrentLine);
/// // clear all cells from cursor position until new line in terminal.
/// term.clear(crossterm::ClearType::UntilNewLine);
/// ```
pub fn clear(&self, clear_type: ClearType) -> Result<()> {
self.terminal.clear(clear_type)
}
/// Get the terminal size `(x,y)`.
pub fn size(&self) -> Result<(u16, u16)> {
self.terminal.size()
}
/// Scroll `n` lines up in the current terminal.
///
/// # Parameter
/// - `count`: the number of rows should be shifted up.
pub fn scroll_up(&self, count: u16) -> Result<()> {
self.terminal.scroll_up(count)
}
/// Scroll `n` lines down in the current terminal.
///
/// # Parameter
/// - `count`: the number of rows should be shifted down.
pub fn scroll_down(&self, count: u16) -> Result<()> {
self.terminal.scroll_down(count)
}
/// Set the terminal size. Note that not all terminals can be set to a very small scale.
///
/// ```rust
/// # use crossterm_terminal::terminal;
/// let mut term = terminal();
///
/// // Set of the size to X: 10 and Y: 10
/// let size = term.set_size(10,10);
/// ```
pub fn set_size(&self, width: u16, height: u16) -> Result<()> {
self.terminal.set_size(width, height)
}
// TODO - Marked as no_run, because it's failing on Travis CI
/// Exit the current process.
///
/// ```no_run
/// # use crossterm_terminal::terminal;
/// let mut term = terminal();
///
/// let size = term.exit();
/// ```
pub fn exit(&self) {
crate::sys::exit();
}
/// Write any displayable content to the current terminal screen.
///
/// ```rust
/// # use crossterm_terminal::terminal;
/// let mut term = terminal();
///
/// 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> {
write_cout!(format!("{}", value))
}
}
/// Get a `Terminal` instance whereon terminal related actions can be performed.
pub fn terminal() -> Terminal {
Terminal::new()
}
/// When executed, this command will scroll up the terminal buffer by the given number of times.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct ScrollUp(pub u16);
impl Command for ScrollUp {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
super::ansi_terminal::get_scroll_up_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().scroll_up(self.0)
}
}
/// When executed, this command will scroll down the terminal buffer by the given number of times.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct ScrollDown(pub u16);
impl Command for ScrollDown {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
super::ansi_terminal::get_scroll_down_ansi(self.0)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().scroll_down(self.0)
}
}
/// When executed, this command will clear the terminal buffer based on the type provided.
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct Clear(pub ClearType);
impl Command for Clear {
type AnsiType = &'static str;
fn ansi_code(&self) -> Self::AnsiType {
match self.0 {
ClearType::All => {
return super::ansi_terminal::CLEAR_ALL;
}
ClearType::FromCursorDown => {
return super::ansi_terminal::CLEAR_FROM_CURSOR_DOWN;
}
ClearType::FromCursorUp => {
return super::ansi_terminal::CLEAR_FROM_CURSOR_UP;
}
ClearType::CurrentLine => return super::ansi_terminal::CLEAR_FROM_CURRENT_LINE,
ClearType::UntilNewLine => return super::ansi_terminal::CLEAR_UNTIL_NEW_LINE,
}
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().clear(self.0.clone())
}
}
/// When executed, this command will set the terminal sie to the given (`width` and `height`)
///
/// See `crossterm/examples/command.rs` for more information on how to execute commands.
pub struct SetSize(pub u16, pub u16);
impl Command for SetSize {
type AnsiType = String;
fn ansi_code(&self) -> Self::AnsiType {
super::ansi_terminal::get_set_size_ansi(self.0, self.1)
}
#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
WinApiTerminal::new().set_size(self.0, self.1)
}
}
impl_display!(for ScrollUp);
impl_display!(for ScrollDown);
impl_display!(for SetSize);
impl_display!(for Clear);

View File

@ -1,297 +0,0 @@
//! This is a `WINAPI` specific implementation for terminal related action.
//! This module is used for non supporting `ANSI` windows terminals.
//!
//! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead.
use crossterm_cursor::sys::winapi::Cursor;
use crossterm_utils::{ErrorKind, Result};
use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size};
use crate::sys::winapi::get_terminal_size;
use super::{ClearType, ITerminal};
/// This struct is a winapi implementation for terminal related actions.
pub struct WinApiTerminal;
impl WinApiTerminal {
pub fn new() -> Box<WinApiTerminal> {
Box::from(WinApiTerminal {})
}
}
impl ITerminal for WinApiTerminal {
fn clear(&self, clear_type: ClearType) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
let pos = csbi.cursor_pos();
let buffer_size = csbi.buffer_size();
let current_attribute = csbi.attributes();
match clear_type {
ClearType::All => {
clear_entire_screen(buffer_size, current_attribute)?;
}
ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?,
ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?,
ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?,
ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?,
};
Ok(())
}
fn size(&self) -> Result<(u16, u16)> {
get_terminal_size()
}
fn scroll_up(&self, count: u16) -> Result<()> {
let csbi = ScreenBuffer::current()?;
let mut window = csbi.info()?.terminal_window();
// Check whether the window is too close to the screen buffer top
let count = count as i16;
if window.top >= count {
window.top -= count; // move top down
window.bottom = count; // move bottom down
Console::new()?.set_console_info(false, window)?;
}
Ok(())
}
fn scroll_down(&self, count: u16) -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
let mut window = csbi.terminal_window();
let buffer_size = csbi.buffer_size();
// Check whether the window is too close to the screen buffer top
let count = count as i16;
if window.bottom < buffer_size.height - count {
window.top += count; // move top down
window.bottom += count; // move bottom down
Console::new()?.set_console_info(false, window)?;
}
Ok(())
}
/// Set the current terminal size
fn set_size(&self, width: u16, height: u16) -> Result<()> {
if width <= 0 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Cannot set the terminal width lower than 1",
)));
}
if height <= 0 {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Cannot set the terminal height lower then 1",
)));
}
// Get the position of the current console window
let screen_buffer = ScreenBuffer::current()?;
let console = Console::from(**screen_buffer.handle());
let csbi = screen_buffer.info()?;
let current_size = csbi.buffer_size();
let window = csbi.terminal_window();
let mut new_size = Size::new(current_size.width, current_size.height);
// If the buffer is smaller than this new window size, resize the
// buffer to be large enough. Include window position.
let mut resize_buffer = false;
let width = width as i16;
if current_size.width < window.left + width {
if window.left >= i16::max_value() - width {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Argument out of range when setting terminal width.",
)));
}
new_size.width = window.left + width;
resize_buffer = true;
}
let height = height as i16;
if current_size.height < window.top + height {
if window.top >= i16::max_value() - height {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Argument out of range when setting terminal height.",
)));
}
new_size.height = window.top + height;
resize_buffer = true;
}
if resize_buffer {
if let Err(_) = screen_buffer.set_size(new_size.width - 1, new_size.height - 1) {
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Something went wrong when setting screen buffer size.",
)));
}
}
let mut window = window.clone();
// Preserve the position, but change the size.
window.bottom = window.top + height - 1;
window.right = window.left + width - 1;
console.set_console_info(true, window)?;
// If we resized the buffer, un-resize it.
if resize_buffer {
if let Err(_) = screen_buffer.set_size(current_size.width - 1, current_size.height - 1)
{
return Err(ErrorKind::ResizingTerminalFailure(String::from(
"Something went wrong when setting screen buffer size.",
)));
}
}
let bounds = console.largest_window_size();
if width > bounds.x {
return Err(ErrorKind::ResizingTerminalFailure(format!(
"Argument width: {} out of range when setting terminal width.",
width
)));
}
if height > bounds.y {
return Err(ErrorKind::ResizingTerminalFailure(format!(
"Argument height: {} out of range when setting terminal height",
width
)));
}
Ok(())
}
}
pub fn clear_after_cursor(
location: Coord,
buffer_size: Size,
current_attribute: u16,
) -> Result<()> {
let (mut x, mut y) = (location.x, location.y);
// if cursor position is at the outer right position
if x as i16 > buffer_size.width {
y += 1;
x = 0;
}
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
clear(start_location, cells_to_write, current_attribute)
}
pub fn clear_before_cursor(
location: Coord,
buffer_size: Size,
current_attribute: u16,
) -> Result<()> {
let (xpos, ypos) = (location.x, location.y);
// one cell after cursor position
let x = 0;
// one at row of cursor position
let y = 0;
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1);
// clear everything before cursor position
clear(start_location, cells_to_write, current_attribute)
}
pub fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> {
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32;
// location where to start clearing
let start_location = Coord::new(0, 0);
// clear the entire screen
clear(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 0,0
let cursor = Cursor::new()?;
cursor.goto(0, 0)?;
Ok(())
}
pub fn clear_current_line(
location: Coord,
buffer_size: Size,
current_attribute: u16,
) -> Result<()> {
// location where to start clearing
let start_location = Coord::new(0, location.y);
// get sum cells before cursor
let cells_to_write = buffer_size.width as u32;
// clear the whole current line
clear(start_location, cells_to_write, current_attribute)?;
// put the cursor back at cell 1 on current row
let cursor = Cursor::new()?;
cursor.goto(0, location.y)?;
Ok(())
}
pub fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> {
let (x, y) = (location.x, location.y);
// location where to start clearing
let start_location = Coord::new(x, y);
// get sum cells before cursor
let cells_to_write = (buffer_size.width - x as i16) as u32;
// clear until the current line
clear(start_location, cells_to_write, current_attribute)?;
// put the cursor back at original cursor position before we did the clearing
let cursor = Cursor::new()?;
cursor.goto(x, y)?;
Ok(())
}
fn clear(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> {
let console = Console::from(Handle::current_out_handle()?);
console.fill_whit_character(start_location, cells_to_write, ' ')?;
console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{ITerminal, WinApiTerminal};
#[test]
fn test_resize_winapi() {
let terminal = WinApiTerminal::new();
let (width, height) = terminal.size().unwrap();
terminal.set_size(30, 30).unwrap();
assert_eq!((30, 30), terminal.size().unwrap());
// reset to previous size
terminal.set_size(width, height).unwrap();
assert_eq!((width, height), terminal.size().unwrap());
}
}

View File

@ -1,2 +0,0 @@
/target
**/*.rs.bk

View File

@ -1,19 +0,0 @@
[package]
name = "crossterm_utils"
version = "0.3.0"
authors = ["T. Post"]
description = "Common logic used by the crossterm crates."
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_utils/"
license = "MIT"
keywords = ["terminal", "abstractions", "crossterm", "windows", "screen_buffer"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["wincon"] }
crossterm_winapi = { path="../crossterm_winapi", version = "0.2.0"}
[target.'cfg(unix)'.dependencies]
libc = "0.2.51"

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,33 +0,0 @@
# Crossterm Utils | crossterm common used code.
![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
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: ./LICENSE
[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/crossterm-rs/crossterm.svg?branch=master
This crate is a utilities crate used by the following [crossterm](https://crates.io/crates/crossterm) modules:
- [Crossterm Style](https://crates.io/crates/crossterm_style)
- [Crossterm Input](https://crates.io/crates/crossterm_input)
- [Crossterm Screen](https://crates.io/crates/crossterm_screen)
- [Crossterm Cursor](https://crates.io/crates/crossterm_cursor)
- [Crossterm Terminal](https://crates.io/crates/crossterm_terminal)
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.
## Authors
* **Timon Post** - *Project Owner & creator*
## License
This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,70 +0,0 @@
//! This module contains all `unix` specific terminal related logic.
use std::{io, mem};
pub use libc::{c_int, termios as Termios};
use crate::{ErrorKind, Result};
static mut ORIGINAL_TERMINAL_MODE: Option<Termios> = None;
pub static mut RAW_MODE_ENABLED: bool = false;
fn wrap_with_result(t: i32) -> Result<()> {
if t == -1 {
Err(ErrorKind::IoError(io::Error::last_os_error()))
} else {
Ok(())
}
}
/// Transform the given mode into an raw mode (non-canonical) mode.
pub fn raw_terminal_attr(termios: &mut Termios) {
extern "C" {
pub fn cfmakeraw(termptr: *mut Termios);
}
unsafe { cfmakeraw(termios) }
}
pub fn get_terminal_attr() -> Result<Termios> {
extern "C" {
pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int;
}
unsafe {
let mut termios = mem::zeroed();
wrap_with_result(tcgetattr(0, &mut termios))?;
Ok(termios)
}
}
pub fn set_terminal_attr(termios: &Termios) -> Result<()> {
extern "C" {
pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int;
}
wrap_with_result(unsafe { tcsetattr(0, 0, termios) })
}
pub fn enable_raw_mode() -> Result<()> {
let mut ios = get_terminal_attr()?;
let prev_ios = ios;
unsafe {
if ORIGINAL_TERMINAL_MODE.is_none() {
ORIGINAL_TERMINAL_MODE = Some(prev_ios.clone());
}
RAW_MODE_ENABLED = true;
}
raw_terminal_attr(&mut ios);
set_terminal_attr(&ios)?;
Ok(())
}
pub fn disable_raw_mode() -> Result<()> {
unsafe {
if let Some(original_terminal_mode) = ORIGINAL_TERMINAL_MODE.as_ref() {
set_terminal_attr(original_terminal_mode)?;
RAW_MODE_ENABLED = false;
}
}
Ok(())
}

View File

@ -1,34 +0,0 @@
pub mod ansi {
use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
use crate::Result;
use crossterm_winapi::ConsoleMode;
/// Toggle virtual terminal processing.
///
/// This method attempts to toggle virtual terminal processing for this
/// console. If there was a problem toggling it, then an error returned.
/// On success, the caller may assume that toggling it was successful.
///
/// When virtual terminal processing is enabled, characters emitted to the
/// console are parsed for VT100 and similar control character sequences
/// that control color and other similar operations.
pub fn set_virtual_terminal_processing(yes: bool) -> Result<()> {
let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
let console_mode = ConsoleMode::new()?;
let old_mode = console_mode.mode()?;
let new_mode = if yes {
old_mode | mask
} else {
old_mode & !mask
};
if old_mode != new_mode {
console_mode.set_mode(new_mode)?;
}
Ok(())
}
}

View File

@ -1,4 +0,0 @@
target/
.idea/
**/*.rs.bk
Cargo.lock

View File

@ -1,2 +0,0 @@
# Changes crossterm_winapi 0.2.0
- `Console::get_handle` to `Console::handle`

View File

@ -1,17 +0,0 @@
[package]
name = "crossterm_winapi"
version = "0.2.0"
authors = ["T. Post"]
description = "An WinApi wrapper that provides some basic simple abstractions aground common WinApi calls"
repository = "https://github.com/crossterm-rs/crossterm"
documentation = "https://docs.rs/crossterm_winapi/"
license = "MIT"
keywords = ["winapi", "abstractions", "crossterm", "windows", "screen_buffer"]
exclude = ["target", "Cargo.lock"]
readme = "README.md"
edition = "2018"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.8", features = ["winbase","consoleapi","processenv", "handleapi"] }
[package.metadata.docs.rs]
default-target = "x86_64-pc-windows-msvc"

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Timon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,62 +0,0 @@
# Crossterm Winapi | Common WinApi Abstractions
![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3]
[s1]: https://img.shields.io/crates/v/crossterm_winapi.svg
[l1]: https://crates.io/crates/crossterm_winapi
[s2]: https://img.shields.io/badge/license-MIT-blue.svg
[l2]: LICENSE
[s3]: https://docs.rs/crossterm_winapi/badge.svg
[l3]: https://docs.rs/crossterm_winapi/
[s7]: https://travis-ci.org/crossterm-rs/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/crossterm-rs/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.
_The following WinApi calls_
- CONSOLE_SCREEN_BUFFER_INFO (used to extract information like cursor pos, terminal size etc.)
- HANDLE (the handle needed to run functions from WinApi)
- SetConsoleActiveScreenBuffer (activate an other screen buffer)
- Set/GetConsoleMode (e.g. console modes like disabling output)
- SetConsoleTextAttribute (eg. coloring)
- SetConsoleWindowInfo (changing the buffer location e.g. scrolling)
- FillConsoleOutputAttribute, FillConsoleOutputCharacter (used to replace some block of cells with a color or character.)
- SetConsoleInfo
- ReadConsoleW
# Example
The [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder has more complete and verbose examples.
## Screenbuffer information
```rust
use crossterm_winapi::{ScreenBuffer, Handle};
fn print_screen_buffer_information() {
let screen_buffer = ScreenBuffer::current().unwrap();
// get console screen buffer information
let csbi = screen_buffer.info().unwrap();
println!("cursor post: {:?}", csbi.cursor_pos());
println!("attributes: {:?}", csbi.attributes());
println!("terminal window dimentions {:?}", csbi.terminal_window());
println!("terminal size {:?}", csbi.terminal_size());
}
```
## Handle
```rust
use crossterm_winapi::{HandleType, Handle};
fn get_different_handle_types() {
let out_put_handle = Handle::new(HandleType::OutputHandle).unwrap();
let out_put_handle = Handle::new(HandleType::InputHandle).unwrap();
let curr_out_put_handle = Handle::new(HandleType::CurrentOutputHandle).unwrap();
let curr_out_put_handle = Handle::new(HandleType::CurrentInputHandle).unwrap();
}
```

View File

@ -1,64 +0,0 @@
#[cfg(windows)]
use std::io::Result;
#[cfg(windows)]
use crossterm_winapi::{Console, ScreenBuffer};
#[cfg(windows)]
fn set_background_color() -> Result<()> {
// background value
const BLUE_BACKGROUND: u16 = 0x0010;
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So wee need to use bitwise operators to check if the values exists or to get current console colors.
let attrs = csbi.attributes();
let fg_color = attrs & 0x0007;
// apply the blue background flag to the current attributes
let new_color = fg_color | BLUE_BACKGROUND;
// set the console text attribute to the new color value.
Console::from(**screen_buffer.handle()).set_text_attribute(new_color)?;
Ok(())
}
#[cfg(windows)]
fn set_foreground_color() -> Result<()> {
// background value
const BLUE_FOREGROUND: u16 = 0x0001;
let screen_buffer = ScreenBuffer::current()?;
let csbi = screen_buffer.info()?;
// Notice that the color values are stored in wAttribute.
// So we need to use bitwise operators to check if the values exists or to get current console colors.
let attrs = csbi.attributes();
let bg_color = attrs & 0x0070;
let mut color = BLUE_FOREGROUND | bg_color;
// background intensity is a separate value in attrs,
// wee need to check if this was applied to the current bg color.
if (attrs & 0x0080 as u16) != 0 {
color = color | 0x0080 as u16;
}
// set the console text attribute to the new color value.
Console::from(**screen_buffer.handle()).set_text_attribute(color)?;
Ok(())
}
#[cfg(windows)]
fn main() -> Result<()> {
set_background_color()?;
set_foreground_color()
}
#[cfg(not(windows))]
fn main() {
println!("This example is for the Windows platform only.");
}

View File

@ -1,26 +0,0 @@
#[cfg(windows)]
use std::io::Result;
#[cfg(windows)]
use crossterm_winapi::ConsoleMode;
#[cfg(windows)]
fn change_console_mode() -> Result<()> {
let console_mode = ConsoleMode::new()?;
// get the current console mode:
let _mode: u32 = console_mode.mode()?;
// set the console mode (not sure if this is an actual value xp)
console_mode.set_mode(10)
}
#[cfg(windows)]
fn main() -> Result<()> {
change_console_mode()
}
#[cfg(not(windows))]
fn main() {
println!("This example is for the Windows platform only.");
}

View File

@ -1,30 +0,0 @@
#[cfg(windows)]
use std::io::Result;
#[cfg(windows)]
use crossterm_winapi::{Handle, HandleType};
#[cfg(windows)]
#[allow(unused_variables)]
fn main() -> Result<()> {
// see the description of the types to see what they do.
let out_put_handle = Handle::new(HandleType::OutputHandle)?;
let out_put_handle = Handle::new(HandleType::InputHandle)?;
let curr_out_put_handle = Handle::new(HandleType::CurrentOutputHandle)?;
let curr_out_put_handle = Handle::new(HandleType::CurrentInputHandle)?;
// now you have this handle you might want to get the WinApi `HANDLE` it is wrapping.
// you can do this by defencing.
let handle /*:HANDLE*/ = *out_put_handle;
// you can also pass you own `HANDLE` to create an instance of `Handle`
let handle = Handle::from(handle); /* winapi::um::winnt::HANDLE */
Ok(())
}
#[cfg(not(windows))]
fn main() {
println!("This example is for the Windows platform only.");
}

View File

@ -1,41 +0,0 @@
#![allow(dead_code)]
#[cfg(windows)]
use std::io::Result;
#[cfg(windows)]
use crossterm_winapi::ScreenBuffer;
#[cfg(windows)]
fn print_screen_buffer_information() -> Result<()> {
let screen_buffer = ScreenBuffer::current()?;
// get console screen buffer information
let csbi = screen_buffer.info()?;
println!("cursor post: {:?}", csbi.cursor_pos());
println!("attributes: {:?}", csbi.attributes());
println!("terminal window dimentions {:?}", csbi.terminal_window());
println!("terminal size {:?}", csbi.terminal_size());
Ok(())
}
#[cfg(windows)]
fn multiple_screen_buffers() -> Result<()> {
// create new screen buffer
let screen_buffer = ScreenBuffer::create();
// which to this screen buffer
screen_buffer.show()
}
#[cfg(windows)]
fn main() -> Result<()> {
print_screen_buffer_information()
}
#[cfg(not(windows))]
fn main() {
println!("This example is for the Windows platform only.");
}

View File

@ -1,246 +0,0 @@
use std::borrow::ToOwned;
use std::io::{self, Error, Result};
use std::str;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::DWORD;
use winapi::shared::ntdef::NULL;
use winapi::um::consoleapi::{GetNumberOfConsoleInputEvents, ReadConsoleInputW, WriteConsoleW};
use winapi::um::{
wincon::{
FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetLargestConsoleWindowSize,
SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, INPUT_RECORD, SMALL_RECT,
},
winnt::HANDLE,
};
use super::{is_true, Coord, Handle, HandleType, InputRecord, WindowPositions};
/// Could be used to do some basic things with the console.
pub struct Console {
handle: Handle,
}
impl Console {
/// Create new instance of `Console`.
///
/// This created instance will use the default output handle (STD_OUTPUT_HANDLE) as handle for the function call it wraps.
pub fn new() -> Result<Console> {
Ok(Console {
handle: Handle::new(HandleType::OutputHandle)?,
})
}
/// Sets the attributes of characters written to the console screen buffer by the WriteFile or WriteConsole function, or echoed by the ReadFile or ReadConsole function.
/// This function affects text written after the function call.
///
/// parameter: [wAttributes]
/// Wraps the underlying function call: [SetConsoleTextAttribute]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute]
pub fn set_text_attribute(&self, value: u16) -> Result<()> {
unsafe {
if !is_true(SetConsoleTextAttribute(*self.handle, value)) {
return Err(Error::last_os_error());
}
}
Ok(())
}
/// Sets the current size and position of a console screen buffer's window.
///
/// Wraps the underlying function call: [SetConsoleTextAttribute]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsoletextattribute]
pub fn set_console_info(&self, absolute: bool, rect: WindowPositions) -> Result<()> {
let absolute = match absolute {
true => 1,
false => 0,
};
let a = SMALL_RECT::from(rect);
unsafe {
if !is_true(SetConsoleWindowInfo(*self.handle, absolute, &a)) {
return Err(Error::last_os_error());
}
}
Ok(())
}
/// Writes a character to the console screen buffer a specified number of times, beginning at the specified coordinates
///
/// Wraps the underlying function call: [FillConsoleOutputCharacterA]
/// link: [https://docs.microsoft.com/en-us/windows/console/fillconsoleoutputcharacter]
pub fn fill_whit_character(
&self,
start_location: Coord,
cells_to_write: u32,
filling_char: char,
) -> Result<u32> {
let mut chars_written = 0;
unsafe {
// fill the cells in console with blanks
if !is_true(FillConsoleOutputCharacterA(
*self.handle,
filling_char as i8,
cells_to_write,
COORD::from(start_location),
&mut chars_written,
)) {
return Err(Error::last_os_error());
}
Ok(chars_written)
}
}
/// Sets the character attributes for a specified number of character cells, beginning at the specified coordinates in a screen buffer.
///
/// Wraps the underlying function call: [FillConsoleOutputAttribute]
/// link: [https://docs.microsoft.com/en-us/windows/console/fillconsoleoutputattribute]
pub fn fill_whit_attribute(
&self,
start_location: Coord,
cells_to_write: u32,
dw_attribute: u16,
) -> Result<u32> {
let mut cells_written = 0;
// Get the position of the current console window
unsafe {
if !is_true(FillConsoleOutputAttribute(
*self.handle,
dw_attribute,
cells_to_write,
COORD::from(start_location),
&mut cells_written,
)) {
return Err(Error::last_os_error());
}
}
Ok(cells_written)
}
/// Retrieves the size of the largest possible console window, based on the current text and the size of the display.
///
/// Wraps the underlying function call: [GetLargestConsoleWindowSize]
/// link: [https://docs.microsoft.com/en-us/windows/console/getlargestconsolewindowsize]
pub fn largest_window_size(&self) -> Coord {
Coord::from(unsafe { GetLargestConsoleWindowSize(*self.handle) })
}
/// Writes a character string to a console screen buffer beginning at the current cursor location.
///
/// Wraps the underlying function call: [WriteConsoleW]
/// link: [https://docs.microsoft.com/en-us/windows/console/writeconsole]
pub fn write_char_buffer(&self, buf: &[u8]) -> Result<usize> {
// get string from u8[] and parse it to an c_str
let utf8 = match str::from_utf8(buf) {
Ok(string) => string,
Err(_) => {
return Err(io::Error::new(
io::ErrorKind::Other,
"Could not parse to utf8 string",
));
}
};
let utf16: Vec<u16> = utf8.encode_utf16().collect();
let utf16_ptr: *const c_void = utf16.as_ptr() as *const _ as *const c_void;
let mut cells_written: u32 = 0;
// write to console
unsafe {
if !is_true(WriteConsoleW(
*self.handle,
utf16_ptr,
utf16.len() as u32,
&mut cells_written,
NULL,
)) {
return Err(io::Error::last_os_error());
}
}
Ok(utf8.as_bytes().len())
}
pub fn read_single_input_event(&self) -> Result<Option<InputRecord>> {
let buf_len = self.number_of_console_input_events()?;
// Fast-skipping all the code below if there is nothing to read at all
if buf_len == 0 {
return Ok(None);
}
let mut buf: Vec<INPUT_RECORD> = Vec::with_capacity(1);
let mut size = 0;
let a = self.read_input(&mut buf, 1, &mut size)?.1[0].to_owned();
// read single input event
Ok(Some(a))
}
pub fn read_console_input(&self) -> Result<(u32, Vec<InputRecord>)> {
let buf_len = self.number_of_console_input_events()?;
// Fast-skipping all the code below if there is nothing to read at all
if buf_len == 0 {
return Ok((0, vec![]));
}
let mut buf: Vec<INPUT_RECORD> = Vec::with_capacity(buf_len as usize);
let mut size = 0;
self.read_input(&mut buf, buf_len, &mut size)
}
pub fn number_of_console_input_events(&self) -> Result<u32> {
let mut buf_len: DWORD = 0;
if !is_true(unsafe { GetNumberOfConsoleInputEvents(*self.handle, &mut buf_len) }) {
return Err(Error::last_os_error());
}
Ok(buf_len)
}
fn read_input(
&self,
buf: &mut Vec<INPUT_RECORD>,
buf_len: u32,
bytes_written: &mut u32,
) -> Result<(u32, Vec<InputRecord>)> {
if !is_true(unsafe {
ReadConsoleInputW(*self.handle, buf.as_mut_ptr(), buf_len, bytes_written)
}) {
return Err(Error::last_os_error());
} else {
unsafe {
buf.set_len(buf_len as usize);
}
}
Ok((
buf_len,
buf[..(buf_len as usize)]
.iter()
.map(|x| InputRecord::from(*x))
.collect::<Vec<InputRecord>>(),
))
}
}
impl From<Handle> for Console {
/// Create a `Console` instance who's functions will be executed on the the given `Handle`
fn from(handle: Handle) -> Self {
Console { handle }
}
}
impl From<HANDLE> for Console {
/// Create a `Console` instance who's functions will be executed on the the given `HANDLE`
fn from(handle: HANDLE) -> Self {
Console {
handle: Handle::from(handle),
}
}
}

View File

@ -1,96 +0,0 @@
use std::io::{Error, Result};
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::winnt::HANDLE;
use super::{is_true, Handle, HandleType};
/// This abstracts away some WinaApi calls to set and get the console mode.
///
/// Wraps the underlying function call: [SetConsoleMode]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsolemode]
///
/// Wraps the underlying function call: [GetConsoleMode]
/// link: [https://docs.microsoft.com/en-us/windows/console/getconsolemode]
pub struct ConsoleMode {
// the handle used for the functions of this type.
handle: Handle,
}
impl ConsoleMode {
/// Create a new `ConsoleMode` instance.
///
/// This will use the `STD_OUTPUT_HANDLE` as default handle.
/// When you explicitly want to specify the handle used for the function calls use `ConsoleMode::from(handle)` instead.
pub fn new() -> Result<ConsoleMode> {
Ok(ConsoleMode {
handle: Handle::new(HandleType::OutputHandle)?,
})
}
/// Set the console mode to the given console mode.
///
/// This function sets the `dwMode`.
///
/// Wraps the underlying function call: [SetConsoleMode]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsolemode]
pub fn set_mode(&self, console_mode: u32) -> Result<()> {
unsafe {
if !is_true(SetConsoleMode(*self.handle, console_mode)) {
return Err(Error::last_os_error());
}
}
Ok(())
}
/// Get the console mode.
///
/// This function returns the `lpMode`.
///
/// Wraps the underlying function call: [GetConsoleMode]
/// link: [https://docs.microsoft.com/en-us/windows/console/getconsolemode]
pub fn mode(&self) -> Result<u32> {
let mut console_mode = 0;
unsafe {
if !is_true(GetConsoleMode(*self.handle, &mut console_mode)) {
println!("Getting mode failed");
return Err(Error::last_os_error());
}
}
Ok(console_mode)
}
}
impl From<HANDLE> for ConsoleMode {
fn from(handle: HANDLE) -> Self {
ConsoleMode {
handle: Handle::from(handle),
}
}
}
impl From<Handle> for ConsoleMode {
fn from(handle: Handle) -> Self {
ConsoleMode { handle }
}
}
#[cfg(test)]
mod tests {
use super::ConsoleMode;
// TODO - Test is ignored, because it's failing on Travis CI
#[test]
#[ignore]
fn test_set_get_mode() {
let mode = ConsoleMode::new().unwrap();
let original_mode = mode.mode().unwrap();
mode.set_mode(0x0004).unwrap();
let console_mode = mode.mode().unwrap();
assert_eq!(console_mode & 0x0004, mode.mode().unwrap());
mode.set_mode(original_mode).unwrap();
}
}

View File

@ -1,61 +0,0 @@
use std::mem::zeroed;
use winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO;
use super::{Coord, Size, WindowPositions};
/// This type is a wrapper for `CONSOLE_SCREEN_BUFFER_INFO` and has some methods to extract information from it.
///
/// Wraps the underlying type: [CONSOLE_SCREEN_BUFFER_INFO]
/// link: [https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str]
pub struct ScreenBufferInfo(pub CONSOLE_SCREEN_BUFFER_INFO);
impl ScreenBufferInfo {
pub fn new() -> ScreenBufferInfo {
ScreenBufferInfo(unsafe { zeroed() })
}
/// This will return the buffer size.
///
/// Will take `dwSize` from the current screen buffer and convert it into the `Size`.
pub fn buffer_size(&self) -> Size {
Size::from(self.0.dwSize)
}
/// This will return the terminal size.
///
/// Will calculate the width and height from `srWindow` and convert it into a `Size`.
pub fn terminal_size(&self) -> Size {
(Size::new(
self.0.srWindow.Right - self.0.srWindow.Left,
self.0.srWindow.Bottom - self.0.srWindow.Top,
))
}
/// This will return the terminal window properties.
///
/// Will take `srWindow` and convert it into the `WindowPositions` type.
pub fn terminal_window(&self) -> WindowPositions {
WindowPositions::from(self.0)
}
/// This will return the terminal window properties.
///
/// Will take `wAttributes` from the current screen buffer.
pub fn attributes(&self) -> u16 {
self.0.wAttributes
}
/// This will return the current cursor position.
///
/// Will take `dwCursorPosition` from the current screen buffer.
pub fn cursor_pos(&self) -> Coord {
Coord::from(self.0.dwCursorPosition)
}
}
impl From<CONSOLE_SCREEN_BUFFER_INFO> for ScreenBufferInfo {
fn from(csbi: CONSOLE_SCREEN_BUFFER_INFO) -> Self {
ScreenBufferInfo(csbi)
}
}

View File

@ -1,188 +0,0 @@
//! This module contains some logic for working with the console handle.
use std::io::{self, Result};
use std::ops::Deref;
use std::ptr::null_mut;
use winapi::um::{
fileapi::{CreateFileW, OPEN_EXISTING},
handleapi::INVALID_HANDLE_VALUE,
processenv::GetStdHandle,
winbase::{STD_INPUT_HANDLE, STD_OUTPUT_HANDLE},
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE},
};
/// This enum represents the different handles that could be requested.
///
/// Some more details could be found [here](https://docs.microsoft.com/en-us/windows/console/getstdhandle#parameters)
pub enum HandleType {
/// This represents the `STD_OUTPUT_HANDLE`
OutputHandle,
/// This represents the `STD_INPUT_HANDLE`
InputHandle,
/// This represents the `CONOUT$` file handle
/// When using multiple screen buffers this will always point to the to the current screen output buffer.
CurrentOutputHandle,
/// This represents the `CONIN$` file handle.
/// When using multiple screen buffers this will always point to the to the current screen input buffer.
CurrentInputHandle,
}
/// This abstracts away some WinaApi calls to set and get some console handles.
///
// Wraps the underlying WinApi type: [HANDLE]
pub struct Handle {
handle: HANDLE,
}
impl Handle {
pub fn new(handle: HandleType) -> Result<Handle> {
let handle = match handle {
HandleType::OutputHandle => Handle::output_handle(),
HandleType::InputHandle => Handle::input_handle(),
HandleType::CurrentOutputHandle => Handle::current_out_handle(),
HandleType::CurrentInputHandle => Handle::current_in_handle(),
}?;
Ok(Handle { handle })
}
/// Get the handle of the active screen buffer.
/// When using multiple screen buffers this will always point to the to the current screen output buffer.
///
/// On success this function returns the `HANDLE` to `STD_OUTPUT_HANDLE`.
///
/// This function uses `CONOUT$` to create a file handle to the current output buffer.
///
/// Wraps the underlying function call: [CreateFileW]
/// link: [https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilew]
pub fn current_out_handle() -> Result<HANDLE> {
let utf16: Vec<u16> = "CONOUT$\0".encode_utf16().collect();
let utf16_ptr: *const u16 = utf16.as_ptr();
let handle = unsafe {
CreateFileW(
utf16_ptr,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
)
};
if !Handle::is_valid_handle(&handle) {
println!("invalid!!");
return Err(io::Error::last_os_error());
}
Ok(handle)
}
/// Get the handle of the active input screen buffer.
/// When using multiple screen buffers this will always point to the to the current screen input buffer.
///
/// On success this function returns the `HANDLE` to `STD_INPUT_HANDLE`.
///
/// This function uses `CONIN$` to create a file handle to the current input buffer.
///
/// Wraps the underlying function call: [CreateFileW]
/// link: [https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-createfilew]
pub fn current_in_handle() -> Result<HANDLE> {
let utf16: Vec<u16> = "CONIN$\0".encode_utf16().collect();
let utf16_ptr: *const u16 = utf16.as_ptr();
let handle = unsafe {
CreateFileW(
utf16_ptr,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
)
};
if !Handle::is_valid_handle(&handle) {
return Err(io::Error::last_os_error());
}
Ok(handle)
}
/// Get the handle of the output screen buffer.
///
/// On success this function returns the `HANDLE` to `STD_OUTPUT_HANDLE`.
///
/// Wraps the underlying function call: [GetStdHandle] whit argument `STD_OUTPUT_HANDLE`
/// link: [https://docs.microsoft.com/en-us/windows/console/getstdhandle]
pub fn output_handle() -> Result<HANDLE> {
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
if !Handle::is_valid_handle(&handle) {
return Err(io::Error::last_os_error());
}
Ok(handle)
}
}
/// Get the handle of the input screen buffer.
///
/// On success this function returns the `HANDLE` to `STD_INPUT_HANDLE`.
///
/// Wraps the underlying function call: [GetStdHandle] whit argument `STD_INPUT_HANDLE`
/// link: [https://docs.microsoft.com/en-us/windows/console/getstdhandle]
pub fn input_handle() -> Result<HANDLE> {
unsafe {
let handle = GetStdHandle(STD_INPUT_HANDLE);
if !Handle::is_valid_handle(&handle) {
return Err(io::Error::last_os_error());
}
Ok(handle)
}
}
/// Checks if the console handle is an invalid handle value.
///
/// This is done by checking if the passed `HANDLE` is equal to `INVALID_HANDLE_VALUE`
pub fn is_valid_handle(handle: &HANDLE) -> bool {
if *handle == INVALID_HANDLE_VALUE {
false
} else {
true
}
}
}
impl Deref for Handle {
type Target = HANDLE;
fn deref(&self) -> &<Self as Deref>::Target {
&self.handle
}
}
impl From<HANDLE> for Handle {
fn from(handle: HANDLE) -> Self {
Handle { handle }
}
}
#[cfg(test)]
mod tests {
use super::{Handle, HandleType};
#[test]
fn test_get_handle() {
assert!(Handle::new(HandleType::OutputHandle).is_ok());
assert!(Handle::new(HandleType::InputHandle).is_ok());
assert!(Handle::new(HandleType::CurrentOutputHandle).is_ok());
assert!(Handle::new(HandleType::CurrentInputHandle).is_ok());
}
}

View File

@ -1,32 +0,0 @@
#![cfg(windows)]
#![deny(unused_imports)]
pub use self::{
console::Console,
console_mode::ConsoleMode,
csbi::ScreenBufferInfo,
handle::{Handle, HandleType},
screen_buffer::ScreenBuffer,
structs::{
ButtonState, ControlKeyState, Coord, EventFlags, InputEventType, InputRecord,
KeyEventRecord, MouseEvent, Size, WindowPositions,
},
};
mod console;
mod console_mode;
mod csbi;
mod handle;
mod screen_buffer;
mod structs;
/// Parses the given integer to an bool by checking if the value is 0 or 1.
/// This is currently used for checking if a WinApi called succeeded, this might be moved into a macro at some time.
/// So please don't use this :(.
pub fn is_true(value: i32) -> bool {
if value == 0 {
return false;
} else {
return true;
}
}

View File

@ -1,137 +0,0 @@
//! This contains the logic for working with the console buffer.
use std::io::{Error, Result};
use std::mem::size_of;
use winapi::{
shared::minwindef::TRUE,
shared::ntdef::NULL,
um::{
minwinbase::SECURITY_ATTRIBUTES,
wincon::{
CreateConsoleScreenBuffer, GetConsoleScreenBufferInfo, SetConsoleActiveScreenBuffer,
SetConsoleScreenBufferSize, CONSOLE_TEXTMODE_BUFFER, COORD,
},
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE, HANDLE},
},
};
use super::{is_true, Handle, HandleType, ScreenBufferInfo};
pub struct ScreenBuffer {
handle: Handle,
}
impl ScreenBuffer {
/// Create an instance of `ScreenBuffer` where the `HANDLE`, used for the functions this type wraps, is the current output handle.
pub fn current() -> Result<ScreenBuffer> {
Ok(ScreenBuffer {
handle: Handle::new(HandleType::CurrentOutputHandle)?,
})
}
/// Create new console screen buffer.
///
/// Wraps the underlying function call: [CreateConsoleScreenBuffer]
/// link: [https://docs.microsoft.com/en-us/windows/console/createconsolescreenbuffer]
pub fn create() -> ScreenBuffer {
let mut security_attr: SECURITY_ATTRIBUTES = SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: NULL,
bInheritHandle: TRUE,
};
unsafe {
let new_screen_buffer = CreateConsoleScreenBuffer(
GENERIC_READ | // read/write access
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, // shared
&mut security_attr, // default security attributes
CONSOLE_TEXTMODE_BUFFER, // must be TEXTMODE
NULL,
);
ScreenBuffer {
handle: Handle::from(new_screen_buffer),
}
}
}
/// This will make this `ScreenBuffer` the active one.
///
/// Wraps the underlying function call: [SetConsoleActiveScreenBuffer]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsoleactivescreenbuffer]
pub fn show(&self) -> Result<()> {
unsafe {
if !is_true(SetConsoleActiveScreenBuffer(*self.handle)) {
return Err(Error::last_os_error());
}
}
Ok(())
}
/// Get the screen buffer information like terminal size, cursor position, buffer size.
///
/// Wraps the underlying function call: [GetConsoleScreenBufferInfo]
/// link: [https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo]
pub fn info(&self) -> Result<ScreenBufferInfo> {
let mut csbi = ScreenBufferInfo::new();
unsafe {
if !is_true(GetConsoleScreenBufferInfo(*self.handle, &mut csbi.0)) {
return Err(Error::last_os_error());
}
}
Ok(csbi)
}
/// Set the console screen buffer size to the given size.
///
/// Wraps the underlying function call: [SetConsoleScreenBufferSize]
/// link: [https://docs.microsoft.com/en-us/windows/console/setconsolescreenbuffersize]
pub fn set_size(&self, x: i16, y: i16) -> Result<()> {
unsafe {
if !is_true(SetConsoleScreenBufferSize(
*self.handle,
COORD { X: x, Y: y },
)) {
return Err(Error::last_os_error());
}
}
Ok(())
}
/// Get the underlining raw `HANDLE` used by this type to execute whit.
pub fn handle(&self) -> &Handle {
return &self.handle;
}
}
impl From<Handle> for ScreenBuffer {
fn from(handle: Handle) -> Self {
ScreenBuffer { handle }
}
}
impl From<HANDLE> for ScreenBuffer {
fn from(handle: HANDLE) -> Self {
ScreenBuffer {
handle: Handle::from(handle),
}
}
}
#[cfg(test)]
mod tests {
use super::ScreenBuffer;
#[test]
fn test_screen_buffer_info() {
let buffer = ScreenBuffer::current().unwrap();
let info = buffer.info().unwrap();
info.terminal_size();
info.terminal_window();
info.attributes();
info.cursor_pos();
}
}

View File

@ -1,12 +0,0 @@
pub use self::coord::Coord;
pub use self::input::{
ButtonState, ControlKeyState, EventFlags, InputEventType, InputRecord, KeyEventRecord,
MouseEvent,
};
pub use self::size::Size;
pub use self::window_coords::WindowPositions;
mod coord;
mod input;
mod size;
mod window_coords;

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