Sub-crates separatation (#251)
This commit is contained in:
parent
e600eabaed
commit
675c8f5e10
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,5 +1,5 @@
|
||||
target/
|
||||
.idea/
|
||||
.vscode/
|
||||
**/target/
|
||||
**/.idea/
|
||||
**/.vscode/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
**/Cargo.lock
|
||||
|
@ -1,4 +1,3 @@
|
||||
# Set up the Rust toolchain.
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
@ -28,5 +27,4 @@ script:
|
||||
- rustc --version
|
||||
- if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi
|
||||
- cargo build
|
||||
- cargo test --all -- --nocapture --test-threads 1
|
||||
- scripts/test-examples.sh
|
||||
- cargo test --all-features -- --nocapture --test-threads 1
|
||||
|
@ -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'.
|
||||
|
||||
### 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]
|
||||
- Examples: [example-cleanup_1], [example-cleanup_2], [example-fix], [commandbar-fix], [snake-game-improved]
|
||||
- Fixed all broken tests and added tests
|
||||
|
||||
### Important Changes
|
||||
|
||||
- Return written bytes: [return-written-bytes]
|
||||
- Added derives: `Debug` for `ObjectStyle` [debug-derive], Serialize/Deserialize for key events [serde]
|
||||
- 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
|
||||
[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)
|
||||
- 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)
|
||||
- Made terminal size 1-based [PR](https://github.com/crossterm-rs/crossterm/commit/d689d7e8ed46a335474b8262bd76f21feaaf0c50)
|
||||
- Added some derive implementation
|
||||
|
||||
# Changes crossterm 0.9.6
|
||||
# Version 0.9.6
|
||||
|
||||
- Copy for KeyEvent
|
||||
- CTRL + 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# Changes crossterm 0.9.4
|
||||
# Version 0.9.4
|
||||
|
||||
- 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)
|
||||
- Corrected white/grey and added dark grey.
|
||||
- Fixed getting cursor position with raw screen enabled. [PR](https://github.com/crossterm-rs/crossterm/pull/134)
|
||||
- Removed one redundant stdout lock
|
||||
|
||||
# Changes crossterm 0.9.3
|
||||
# Version 0.9.3
|
||||
|
||||
- Removed println from `SyncReader`
|
||||
|
||||
## Changes crossterm 0.9.2
|
||||
## Version 0.9.2
|
||||
|
||||
- Terminal size linux was not 0-based
|
||||
- Windows mouse input event position was 0-based ans should be 1-based
|
||||
- Result, ErrorKind are made re-exported
|
||||
- Fixed some special key combination detections for UNIX systems
|
||||
- Made FreeBSD compile
|
||||
|
||||
## Changes crossterm 0.9.1
|
||||
## Version 0.9.1
|
||||
|
||||
- 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.
|
||||
|
||||
- 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
|
||||
- Added `NoItalic` attribute
|
||||
|
||||
## Changes crossterm to 0.8.2
|
||||
## Version 0.8.2
|
||||
|
||||
- Bug fix for sync reader UNIX.
|
||||
|
||||
## Changes crossterm to 0.8.1
|
||||
## Version 0.8.1
|
||||
|
||||
- Added public re-exports for input.
|
||||
|
||||
# Changes crossterm 0.8.0
|
||||
# Version 0.8.0
|
||||
|
||||
- Introduced KeyEvents
|
||||
- Introduced MouseEvents
|
||||
- Upgraded crossterm_winapi 0.2
|
||||
|
||||
# Changes crossterm 0.7.0
|
||||
# Version 0.7.0
|
||||
|
||||
- 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 0.6.0
|
||||
# Version 0.6.0
|
||||
|
||||
- Introduced feature flags; input, cursor, style, terminal, screen.
|
||||
- All modules are moved to their own crate.
|
||||
- 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)
|
||||
|
||||
# Changes crossterm 0.5.5
|
||||
# Version 0.5.5
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
@ -159,31 +181,37 @@ This release is all about moving to a stabilized API for 1.0.
|
||||
- General refactoring, all warnings removed.
|
||||
- Documentation improvement.
|
||||
|
||||
# Changes crossterm 0.5.1
|
||||
# Version 0.5.1
|
||||
|
||||
- Documentation refactor.
|
||||
- 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)
|
||||
- RGB support for Windows 10 systems
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
# 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 unit tests.
|
||||
- Bugfix with getting terminal size unix.
|
||||
- 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`
|
||||
|
||||
# Bug fix crossterm to 0.4.1
|
||||
# Version 0.4.1
|
||||
|
||||
- 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)
|
||||
- Styling module improved
|
||||
- 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 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.
|
||||
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.
|
||||
|
||||
## Alternate screen
|
||||
|
||||
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.
|
||||
@ -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.
|
||||
|
||||
## Raw screen
|
||||
|
||||
This crate now supports raw screen for both windows and unix systems.
|
||||
What exactly is raw state:
|
||||
- No line buffering.
|
||||
@ -232,6 +262,7 @@ What exactly is raw state:
|
||||
With these modes you can easier design the terminal screen.
|
||||
|
||||
## Some functionalities added
|
||||
|
||||
- Hiding and showing terminal cursor
|
||||
- Enable or disabling blinking of the cursor for unix systems (this is not widely supported)
|
||||
- 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,
|
||||
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)
|
||||
|
||||
# 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.
|
||||
- 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)
|
||||
- Get position unix fixed [issue 8](https://github.com/crossterm-rs/crossterm/issues/8)
|
||||
|
||||
# Features crossterm 0.2
|
||||
# Version 0.2
|
||||
|
||||
- 256 color support.
|
||||
- Text Attributes like: bold, italic, underscore and crossed word ect.
|
32
Cargo.toml
32
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "crossterm"
|
||||
version = "0.11.0"
|
||||
version = "0.11.1"
|
||||
authors = ["T. Post"]
|
||||
description = "An crossplatform terminal library for manipulating terminals."
|
||||
repository = "https://github.com/crossterm-rs/crossterm"
|
||||
@ -12,7 +12,7 @@ readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["cursor", "style","terminal","screen","input"]
|
||||
default = ["cursor", "style", "terminal", "screen", "input"]
|
||||
|
||||
cursor = ["crossterm_cursor"]
|
||||
style = ["crossterm_style"]
|
||||
@ -20,29 +20,13 @@ terminal = ["crossterm_terminal"]
|
||||
screen = ["crossterm_screen"]
|
||||
input = ["crossterm_input"]
|
||||
|
||||
[workspace]
|
||||
|
||||
members = [
|
||||
"crossterm_winapi",
|
||||
"crossterm_utils",
|
||||
"crossterm_cursor",
|
||||
"crossterm_style",
|
||||
"crossterm_terminal",
|
||||
"crossterm_input",
|
||||
"crossterm_screen"
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"examples/program_examples"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
crossterm_screen = { path = "./crossterm_screen", version = "0.3.0" , optional = true }
|
||||
crossterm_cursor = { path = "./crossterm_cursor", version = "0.3.0" , optional = true }
|
||||
crossterm_terminal = { path = "./crossterm_terminal", version = "0.3.0", optional = true }
|
||||
crossterm_style = { path = "./crossterm_style", version = "0.5.0" , optional = true }
|
||||
crossterm_input = { path = "./crossterm_input", version = "0.4.0" , optional = true }
|
||||
crossterm_utils = { path = "./crossterm_utils", version = "0.3.0" , optional = false }
|
||||
crossterm_screen = { version = "0.3.1" , optional = true }
|
||||
crossterm_cursor = { version = "0.3.1" , optional = true }
|
||||
crossterm_terminal = { version = "0.3.1", optional = true }
|
||||
crossterm_style = { version = "0.5.1" , optional = true }
|
||||
crossterm_input = { version = "0.4.1" , optional = true }
|
||||
crossterm_utils = { version = "0.3.1" , optional = false }
|
||||
|
||||
[lib]
|
||||
name = "crossterm"
|
||||
|
129
README.md
129
README.md
@ -1,81 +1,66 @@
|
||||
<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
|
||||
[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
|
||||
# cross-platform terminal manipulating library.
|
||||
|
||||
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.
|
||||
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.
|
||||
- [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 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.
|
||||
|
||||
- [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)
|
||||
|
||||
## Table of contents:
|
||||
|
||||
- [Getting started](#getting-started)
|
||||
- [Useful links](#useful-links)
|
||||
- [Features](#features)
|
||||
- [Examples](#examples)
|
||||
- [Crossterm Type](#crossterm-type)
|
||||
- [Styled Text](#styled-text)
|
||||
- [Cursor](#cursor)
|
||||
- [Terminal](#terminal)
|
||||
- [Input Reading](#input-reading)
|
||||
- [Tested Terminals](#tested-terminals)
|
||||
- [Notice](#notice)
|
||||
- [Todo](#todo)
|
||||
- [Contributing](#contributing)
|
||||
- [Authors](#authors)
|
||||
- [License](#license)
|
||||
|
||||
## 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.
|
||||
|
||||
```
|
||||
[dependencies]
|
||||
crossterm = "^0.11"
|
||||
crossterm = "0.11"
|
||||
```
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [Book](https://crossterm-rs.github.io/crossterm/docs//)
|
||||
- [Book](https://crossterm-rs.github.io/crossterm/docs/)
|
||||
- [Documentation](https://docs.rs/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/crossterm/tree/master/examples)
|
||||
- [Examples](https://github.com/crossterm-rs/examples)
|
||||
|
||||
## Features
|
||||
|
||||
- Cross-platform
|
||||
- Multithreaded (send, sync)
|
||||
- Multi-threaded (send, sync)
|
||||
- Detailed Documentation
|
||||
- Few Dependencies
|
||||
- Cursor
|
||||
@ -104,19 +89,25 @@ crossterm = "^0.11"
|
||||
- Read mouse input events (press, release, position, button)
|
||||
|
||||
## Examples
|
||||
These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/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
|
||||
|
||||
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.
|
||||
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. It is more convenient, faster, and easier to use.
|
||||
|
||||
### Styled 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_
|
||||
|
||||
```rust
|
||||
use crossterm::{Colored, Color, Colorize, Styler, Attribute};
|
||||
|
||||
@ -129,6 +120,7 @@ println!("{}", styled_text);
|
||||
```
|
||||
|
||||
_style text with colors_
|
||||
|
||||
```rust
|
||||
println!("{} Red foreground color", Colored::Fg(Color::Red));
|
||||
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();
|
||||
println!("{}", styled_text);
|
||||
```
|
||||
|
||||
_style text with RGB and ANSI Value_
|
||||
|
||||
```rust
|
||||
// custom rgb value (Windows 10 and UNIX systems)
|
||||
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)));
|
||||
```
|
||||
|
||||
|
||||
### 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
|
||||
use crossterm::cursor;
|
||||
@ -194,9 +189,11 @@ cursor.blink(true)
|
||||
```
|
||||
|
||||
### Terminal
|
||||
|
||||
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
|
||||
use crossterm::{terminal,ClearType};
|
||||
@ -233,9 +230,12 @@ terminal.write("Some text\n Some text on new line");
|
||||
```
|
||||
|
||||
### Input Reading
|
||||
|
||||
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_
|
||||
```rust
|
||||
@ -245,6 +245,7 @@ use crossterm_input::{
|
||||
```
|
||||
|
||||
_Simple Readings_
|
||||
|
||||
```rust
|
||||
let mut input = input();
|
||||
|
||||
@ -260,8 +261,10 @@ let mut input = input();
|
||||
```
|
||||
|
||||
_Read input events synchronously or asynchronously._
|
||||
|
||||
```rust
|
||||
// make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you.
|
||||
// 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();
|
||||
@ -281,6 +284,7 @@ if let Some(key_event) = stdin.next() {
|
||||
```
|
||||
|
||||
_Enable mouse input events._
|
||||
|
||||
```rust
|
||||
let input = input();
|
||||
|
||||
@ -292,9 +296,13 @@ input.disable_mouse_mode().unwrap();
|
||||
```
|
||||
|
||||
### 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/)
|
||||
- [Cursive](https://github.com/gyscos/Cursive)
|
||||
- [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
|
||||
- Linux Mint
|
||||
|
||||
This crate supports all Unix terminals and Windows terminals down to Windows 7; however, not all of the 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!
|
||||
This crate supports all Unix terminals and Windows terminals down to Windows 7; however, not all of the
|
||||
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
|
||||
|
||||
@ -332,4 +341,24 @@ Would you like crossterm to be even more gorgeous and beautiful? You can help wi
|
||||
|
||||
## 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
|
||||
|
2
crossterm_cursor/.gitignore
vendored
2
crossterm_cursor/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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.
|
@ -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"}
|
@ -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.
|
@ -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
|
@ -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<()>;
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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))
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
2
crossterm_input/.gitignore
vendored
2
crossterm_input/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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.
|
@ -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 }
|
@ -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.
|
@ -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
|
@ -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,
|
||||
}
|
@ -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()
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
}
|
||||
}
|
@ -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;
|
@ -1,2 +0,0 @@
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
@ -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)
|
||||
}
|
2
crossterm_screen/.gitignore
vendored
2
crossterm_screen/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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`
|
@ -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" }
|
@ -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.
|
@ -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
|
@ -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;
|
@ -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;
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<()>;
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
2
crossterm_style/.gitignore
vendored
2
crossterm_style/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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.
|
@ -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 }
|
@ -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.
|
@ -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
|
@ -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
|
||||
}
|
@ -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>);
|
@ -1,5 +0,0 @@
|
||||
pub use self::{attribute::Attribute, color::Color, colored::Colored};
|
||||
|
||||
mod attribute;
|
||||
mod color;
|
||||
mod colored;
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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>;
|
||||
}
|
@ -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;
|
2
crossterm_terminal/.gitignore
vendored
2
crossterm_terminal/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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.
|
@ -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 }
|
@ -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.
|
@ -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
|
@ -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;
|
@ -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;
|
@ -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())
|
||||
}
|
||||
}
|
@ -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,
|
||||
))
|
||||
}
|
@ -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<()>;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
@ -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());
|
||||
}
|
||||
}
|
2
crossterm_utils/.gitignore
vendored
2
crossterm_utils/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
@ -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"
|
@ -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.
|
@ -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
|
@ -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);
|
@ -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);
|
@ -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,
|
||||
}
|
||||
}
|
@ -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;
|
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#[cfg(windows)]
|
||||
pub mod winapi;
|
||||
|
||||
#[cfg(unix)]
|
||||
pub mod unix;
|
@ -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(())
|
||||
}
|
@ -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(())
|
||||
}
|
||||
}
|
4
crossterm_winapi/.gitignore
vendored
4
crossterm_winapi/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
target/
|
||||
.idea/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
@ -1,2 +0,0 @@
|
||||
# Changes crossterm_winapi 0.2.0
|
||||
- `Console::get_handle` to `Console::handle`
|
@ -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"
|
@ -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.
|
@ -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();
|
||||
}
|
||||
```
|
@ -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.");
|
||||
}
|
@ -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.");
|
||||
}
|
@ -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.");
|
||||
}
|
@ -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.");
|
||||
}
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user