diff --git a/.gitignore b/.gitignore index a6e0d6c..b28425c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ target/ .idea/ **/*.rs.bk -Cargo.lock +Cargo.lock \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 70ec4de..471aaaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,22 +1,22 @@ # Set up the Rust toolchain. language: rust rust: -- stable -- nightly + - stable + - nightly before_script: -- export PATH=$PATH:/home/travis/.cargo/bin -- rustup component add rustfmt-preview + - export PATH=$PATH:/home/travis/.cargo/bin + - rustup component add rustfmt-preview os: -- linux -- osx -- windows + - linux + - osx + - windows branches: only: - - master + - master script: -- cargo build -- cargo fmt -- --check + - cargo build + - cargo fmt -- --check \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 1ad00dd..a2f172a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm" -version = "0.5.5" +version = "0.5.4" authors = ["T. Post"] description = "An crossplatform terminal library for manipulating terminals." repository = "https://github.com/TimonPost/crossterm" @@ -10,26 +10,43 @@ keywords = ["console", "color", "cursor", "input", "terminal"] exclude = ["target", "Cargo.lock"] readme = "README.md" -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["winbase","winuser","consoleapi","processenv","wincon", "handleapi","errhandlingapi"] } -crossterm_winapi = "0.1.0" +[features] +default = ["cursor", "style","terminal","screen","input"] -[target.'cfg(unix)'.dependencies] -libc = "0.2.43" -termios = "0.3.0" +cursor = ["crossterm_cursor"] +style = ["crossterm_style"] +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", +] + +[dependencies] +crossterm_screen = { path = "./crossterm_screen", optional = true, version = "0.1.0" } +crossterm_cursor = { path = "./crossterm_cursor", optional = true, version = "0.1.0" } +crossterm_terminal = { path = "./crossterm_terminal", optional = true, version = "0.1.0" } +crossterm_style = { path = "./crossterm_style", optional = true, version = "0.1.0" } +crossterm_input = { path = "./crossterm_input", optional = true, version = "0.1.0" } +crossterm_utils = { path = "./crossterm_utils", version = "0.1.0" } [lib] name = "crossterm" path = "src/lib.rs" -[[example]] -name = "examples" -path = "examples/examples.rs" - [[example]] name = "logging" path = "examples/program_examples/logging.rs" [[example]] name = "command_bar" -path = "examples/program_examples/command_bar.rs" +path = "examples/program_examples/command_bar.rs" \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b8792d7..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 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. diff --git a/README.md b/README.md index 8d1f028..586315c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,10 @@ [l1]: https://crates.io/crates/crossterm [s2]: https://img.shields.io/badge/license-MIT-blue.svg -[l2]: ./LICENSE +[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/ @@ -21,6 +24,13 @@ Through the simplicity of Crossterm, you do not have to worry about the platform 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 is exists out of six modules who are behind feature flags so that you can define which features you'd like to have: +- [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) @@ -40,30 +50,16 @@ This crate supports all UNIX and windows terminals down to windows 7 (not all te ## Getting Started -This documentation is only for Crossterm version `0.5.^` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. +This documentation is only for Crossterm version `0.5` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. Add the Crossterm package to your `Cargo.toml` file. ``` [dependencies] -crossterm = "0.5.4" - +crossterm = "0.6" ``` -And import the Crossterm modules you want to use. -```rust -extern crate crossterm; -// this module is used for styling the terminal -use crossterm::style::*; -// this module is used for cursor related actions -use crossterm::cursor::*; -// this module is used for terminal related actions -use crossterm::terminal::*; -// this module is used for input related actions -use crossterm::input::*; - -``` ### Useful Links @@ -79,7 +75,7 @@ These are the features from this crate: - Cross-platform - Everything is multithreaded (Send, Sync) - Detailed documentation on every item -- Very few dependencies. +- Very few dependenties. - Cursor. - Moving _n_ times Up, Down, Left, Right - Goto a certain position @@ -130,13 +126,13 @@ println!("{}", crossterm.style("Black font on Green background color").with(Colo ### Styled Font | [see more](http://atcentra.com/crossterm/styling.html) This module provides the functionalities to style the terminal. ```rust -use crossterm::style::{Color, style}; +use crossterm::{Color, style}; // store objcets so it could be painted later to the screen. let style1 = style("Some Blue font on Black background").with(Color::Blue).on(Color::Black); let style2 = style("Some Red font on Yellow background").with(Color::Red).on(Color::Yellow); -// styling font with (Windows 10 and UNIX systems) +// syling font with (Windows 10 and UNIX systems) let normal = style("Normal text"); let bold = style("Bold text").bold(); let italic = style("Italic text").italic(); @@ -155,7 +151,7 @@ println!("{}", bold); println!("{}", hidden); ... -// custom rgb value (Windows 10 and UNIX systems) +// cursom rgb value (Windows 10 and UNIX systems) style("RGB color (10,10,10) ").with(Color::Rgb { r: 10, g: 10, @@ -207,31 +203,12 @@ cursor.blink(true) ``` -### Input | [see more](http://atcentra.com/crossterm/input.html) -This module provides the functionalities to work with terminal input. - -```rust -use crossterm::input; - -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), - } - -``` ### Terminal | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/terminal/terminal.rs) This module provides the functionalities to work with the terminal in general. ```rust -use crossterm::terminal::{terminal,ClearType}; +use crossterm::{terminal,ClearType}; let mut terminal = terminal(); @@ -283,17 +260,17 @@ This crate supports all Unix terminals and windows terminals down to Windows 7 b If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. ## Notice -This library is quite stable now, changes could be expected but they will probably be not that big. +This library is average stable now but I don't expect it to not to change that much. If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. ## Todo I still have some things in mind to implement. -- Handling mouse events: +- Handling mouse events I want to be able to do something based on the clicks the user has done with its mouse. -- Handling key events: +- Handling key events I want to be able to read key combination inputs. -- Tests: +- Tests Find a way to test: color, alternate screen, rawscreen ## Contributing diff --git a/crossterm.iml b/crossterm.iml deleted file mode 100644 index 7fe828a..0000000 --- a/crossterm.iml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/crossterm_cursor/.gitignore b/crossterm_cursor/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_cursor/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_cursor/Cargo.toml b/crossterm_cursor/Cargo.toml new file mode 100644 index 0000000..542e775 --- /dev/null +++ b/crossterm_cursor/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "crossterm_cursor" +version = "0.1.0" +authors = ["T. Post"] +description = "A cross-platform library for moving the terminal cursor." +repository = "https://github.com/TimonPost/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.5", features = ["wincon","winnt","minwindef"] } +crossterm_winapi = { path = "../crossterm_winapi" } + +[dependencies] +crossterm_utils = { path = "../crossterm_utils" } + +[[example]] +name = "cursor" +path = "examples/cursor.rs" \ No newline at end of file diff --git a/crossterm_cursor/README.md b/crossterm_cursor/README.md new file mode 100644 index 0000000..1e0c851 --- /dev/null +++ b/crossterm_cursor/README.md @@ -0,0 +1,155 @@ +# Crossterm Cursor | cross-platform cursor movement. + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_cursor/badge.svg +[l3]: https://docs.rs/crossterm_cursor/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_cursor?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_cursor.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://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.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 + +This documentation is only for `crossterm_cursor` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. + +Add the `crossterm_cursor` package to your `Cargo.toml` file. + +``` +[dependencies] +`crossterm_cursor` = "0.1" + +``` +And import the crossterm_input modules you want to use. + +```rust +extern crate crossterm_cursor; + +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 +- Everything is multithreaded (Send, Sync) +- Detailed documentation on every item +- Very few dependenties. +- Cursor. + - Moving _n_ times Up, Down, Left, Right + - Goto a certain position + - Get cursor position + - Storing the current cursor position and resetting to that stored cursor position later + - Hiding an showing the cursor + - Control over blinking of the terminal cursor (only some terminals are supporting this) + +## Examples +Check out the [examples](/examples/) for more information about how to use this crate. + +```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.reset_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. + +## Notice + +This library is average stable now, I don't expect it to not to change that much. +If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/examples/cursor/mod.rs b/crossterm_cursor/examples/cursor.rs similarity index 61% rename from examples/cursor/mod.rs rename to crossterm_cursor/examples/cursor.rs index ead5d9e..310c9dc 100644 --- a/examples/cursor/mod.rs +++ b/crossterm_cursor/examples/cursor.rs @@ -2,9 +2,9 @@ //! Examples of actions that could be performed with te cursor. //! -extern crate crossterm; -use self::crossterm::cursor::{cursor, TerminalCursor}; -use self::crossterm::Screen; +extern crate crossterm_cursor; + +use crossterm_cursor::{cursor, TerminalCursor}; /// Set the cursor to position X: 10, Y: 5 in the terminal. pub fn goto() { @@ -47,40 +47,6 @@ pub fn move_down() { cursor.move_down(3); } -/// Move the cursor 3 to the left | demonstration. -pub fn move_left() { - let mut cursor = cursor(); - - // Move the cursor to position 3 times to the left in the terminal - cursor.move_left(3); -} - -///// Print character at X: 10 Y: 5 | demonstration. -//pub fn print() { -// let context = Context::new(); -// -// // To print an some displayable content on an certain position. -// -// // Get the cursor -// let mut cursor = cursor(&context); -// // Set the cursor to position X: 10, Y: 5 in the terminal -// cursor.goto(10, 5); -// // Print the @ symbol at position X: 10, Y: 5 in the terminal -// print!("@"); -// // Rust is line buffered inorder to print at an certain position we need to clear the buffer first. -// use std; -// use std::io::Write; -// std::io::stdout().flush(); -// -// /* Because the above method is a little to much code, -// you can use the `print()` method for printing an value at an certain position in the terminal. -// -// Crossterm provides method chaining so that the above points can be inlined. -// */ -// -// cursor.goto(10, 5).print("@"); -//} - /// Save and reset cursor position | demonstration.. pub fn safe_and_reset_position() { let cursor = cursor(); @@ -119,3 +85,8 @@ pub fn blink_cursor() { cursor.blink(false); cursor.blink(false); } + +fn main() { + goto(); + pos(); +} diff --git a/src/modules/cursor/ansi_cursor.rs b/crossterm_cursor/src/cursor/ansi_cursor.rs similarity index 69% rename from src/modules/cursor/ansi_cursor.rs rename to crossterm_cursor/src/cursor/ansi_cursor.rs index c94ba12..8939b9d 100644 --- a/src/modules/cursor/ansi_cursor.rs +++ b/crossterm_cursor/src/cursor/ansi_cursor.rs @@ -2,8 +2,11 @@ //! 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 super::*; -use common::error::Result; +use super::ITerminalCursor; +use crate::sys::get_cursor_position; + +use crossterm_utils::{write, write_str, Result, TerminalOutput}; +use std::sync::Arc; /// This struct is an ANSI implementation for cursor related actions. pub struct AnsiCursor {} @@ -16,59 +19,59 @@ impl AnsiCursor { impl ITerminalCursor for AnsiCursor { fn goto(&self, x: u16, y: u16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(stdout, format!(csi!("{};{}H"), y + 1, x + 1))?; + write(stdout, format!(csi!("{};{}H"), y + 1, x + 1))?; Ok(()) } fn pos(&self) -> (u16, u16) { - functions::get_cursor_position() + get_cursor_position() } fn move_up(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(stdout, format!(csi!("{}A"), count))?; + write(stdout, format!(csi!("{}A"), count))?; Ok(()) } fn move_right(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(stdout, format!(csi!("{}C"), count))?; + write(stdout, format!(csi!("{}C"), count))?; Ok(()) } fn move_down(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(stdout, format!(csi!("{}B"), count))?; + write(stdout, format!(csi!("{}B"), count))?; Ok(()) } fn move_left(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(stdout, format!(csi!("{}D"), count))?; + write(stdout, format!(csi!("{}D"), count))?; Ok(()) } fn save_position(&self, stdout: &Option<&Arc>) -> Result<()> { - functions::write_str(stdout, csi!("s"))?; + write_str(stdout, csi!("s"))?; Ok(()) } fn reset_position(&self, stdout: &Option<&Arc>) -> Result<()> { - functions::write_str(stdout, csi!("u"))?; + write_str(stdout, csi!("u"))?; Ok(()) } fn hide(&self, stdout: &Option<&Arc>) -> Result<()> { - functions::write_str(stdout, csi!("?25l"))?; + write_str(stdout, csi!("?25l"))?; Ok(()) } fn show(&self, stdout: &Option<&Arc>) -> Result<()> { - functions::write_str(stdout, csi!("?25h"))?; + write_str(stdout, csi!("?25h"))?; Ok(()) } fn blink(&self, blink: bool, stdout: &Option<&Arc>) -> Result<()> { if blink { - functions::write_str(stdout, csi!("?12h"))?; + write_str(stdout, csi!("?12h"))?; } else { - functions::write_str(stdout, csi!("?12l"))?; + write_str(stdout, csi!("?12l"))?; } Ok(()) } diff --git a/src/modules/cursor/cursor.rs b/crossterm_cursor/src/cursor/cursor.rs similarity index 61% rename from src/modules/cursor/cursor.rs rename to crossterm_cursor/src/cursor/cursor.rs index 7c69c20..f804c72 100644 --- a/src/modules/cursor/cursor.rs +++ b/crossterm_cursor/src/cursor/cursor.rs @@ -1,34 +1,32 @@ //! 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 super::*; -use common::error::Result; -use Screen; +use std::sync::Arc; -/// Struct that stores a platform-specific implementation for cursor related actions. +use crossterm_utils::{Result, TerminalOutput}; + +#[cfg(windows)] +use crossterm_utils::get_module; + +/// 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 an 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. /// -/// ```rust -/// extern crate crossterm; -/// use self::crossterm::cursor; -/// use self::crossterm::Screen; +/// # Remarks /// -/// let mut cursor = cursor(); -/// -/// // Get cursor and goto pos X: 5, Y: 10 -/// cursor.goto(5,10); -/// -/// cursor.show(); -/// cursor.hide(); -/// cursor.blink(true); -/// cursor.move_left(2); -/// ``` -/// -/// When you want to use 'cursor' on 'alternate screen' use the `Screen` type instead and pass it to the `cursor::from_screen()` function. -/// By doing that cursor actions will be performed on the alternate screen. +/// When you want to use 'cursor' on 'alternate screen' use the 'crossterm_screen' crate. pub struct TerminalCursor<'stdout> { terminal_cursor: Box, stdout: Option<&'stdout Arc>, @@ -38,7 +36,7 @@ impl<'stdout> TerminalCursor<'stdout> { /// Create new `TerminalCursor` instance whereon cursor related actions can be performed. pub fn new() -> TerminalCursor<'stdout> { #[cfg(target_os = "windows")] - let cursor = functions::get_module::>( + let cursor = get_module::>( WinApiCursor::new(), AnsiCursor::new(), ) @@ -55,11 +53,13 @@ impl<'stdout> TerminalCursor<'stdout> { /// Create a new instance of `TerminalCursor` whereon cursor related actions could be preformed on the given output. /// - /// **Note** + /// # Remarks /// /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode'. - /// And you want your actions from the `TerminalCursor`, created by this function, to operate on the 'alternate screen'. + /// This could be useful when you have a screen which is in 'alternate mode', + /// and you want your actions from the `TerminalCursor`, created by this function, to operate on the 'alternate screen'. + /// + /// You should checkout the 'crossterm_screen' crate for more information about this. /// /// # Example /// ``` @@ -71,7 +71,7 @@ impl<'stdout> TerminalCursor<'stdout> { /// ``` pub fn from_output(stdout: &'stdout Arc) -> TerminalCursor<'stdout> { #[cfg(target_os = "windows")] - let cursor = functions::get_module::>( + let cursor = get_module::>( WinApiCursor::new(), AnsiCursor::new(), ) @@ -88,50 +88,27 @@ impl<'stdout> TerminalCursor<'stdout> { /// Goto some position (x,y) in the terminal. /// - /// ```rust - /// let cursor = cursor(); - /// - /// // change the cursor to position, x: 4 and y: 5 - /// cursor.goto(4,5); - /// - /// ``` + /// # Remarks + /// position is 0-based, which means we start counting at 0. pub fn goto(&self, x: u16, y: u16) -> Result<()> { self.terminal_cursor.goto(x, y, &self.stdout) } /// Get current cursor position (x,y) in the terminal. /// - /// ```rust - /// let cursor = cursor(); - /// - /// // get the current cursor pos - /// let (x,y) = cursor.pos(); - /// ``` + /// # Remarks + /// position is 0-based, which means we start counting at 0. pub fn pos(&self) -> (u16, u16) { self.terminal_cursor.pos() } /// Move the current cursor position `n` times up. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// // Move the cursor to position 3 times to the up in the terminal - /// cursor.move_up(3); - /// ``` pub fn move_up(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { self.terminal_cursor.move_up(count, &self.stdout).unwrap(); self } /// Move the current cursor position `n` times right. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// // Move the cursor to position 3 times to the right in the terminal - /// cursor.move_right(3); - /// ``` pub fn move_right(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { self.terminal_cursor .move_right(count, &self.stdout) @@ -140,26 +117,12 @@ impl<'stdout> TerminalCursor<'stdout> { } /// Move the current cursor position `n` times down. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// // Move the cursor to position 3 times to the down in the terminal - /// cursor.move_down(3); - /// ``` pub fn move_down(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { self.terminal_cursor.move_down(count, &self.stdout).unwrap(); self } /// Move the current cursor position `n` times left. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// // Move the cursor to position 3 times to the left in the terminal - /// cursor.move_left(3); - /// ``` pub fn move_left(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { self.terminal_cursor.move_left(count, &self.stdout).unwrap(); self @@ -168,60 +131,29 @@ impl<'stdout> TerminalCursor<'stdout> { /// Save cursor position for recall later. /// /// Note that this position is stored program based not per instance of the `Cursor` struct. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// cursor.safe_position(); - /// ``` pub fn save_position(&self) -> Result<()> { self.terminal_cursor.save_position(&self.stdout) } /// Return to saved cursor position - /// - /// Note that this method reset to the position set by `save_position()` and that this position is stored program based not per instance of the `Cursor` struct. - /// - /// ```rust - /// let cursor = cursor(); - /// - /// cursor.reset_position(); - /// ``` pub fn reset_position(&self) -> Result<()> { self.terminal_cursor.reset_position(&self.stdout) } /// Hide de cursor in the console. - /// - /// ```rust - /// let cursor = cursor(); - /// cursor.hide(); - /// ``` pub fn hide(&self) -> Result<()> { self.terminal_cursor.hide(&self.stdout) } /// Show the cursor in the console. - /// - /// ```rust - /// - /// let cursor = cursor(); - /// cursor.show(); - /// - /// ``` pub fn show(&self) -> Result<()> { self.terminal_cursor.show(&self.stdout) } /// 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. - /// - /// ```rust - /// let cursor = cursor(); - /// cursor.blink(true); - /// cursor.blink(false); - /// ``` pub fn blink(&self, blink: bool) -> Result<()> { self.terminal_cursor.blink(blink, &self.stdout) } @@ -231,9 +163,3 @@ impl<'stdout> TerminalCursor<'stdout> { pub fn cursor() -> TerminalCursor<'static> { TerminalCursor::new() } - -/// Get a `TerminalCursor` instance whereon cursor related actions can be performed. -/// Pass the reference to any `Screen` you want this type to perform actions on. -pub fn from_screen(screen: &Screen) -> TerminalCursor { - TerminalCursor::from_output(&screen.stdout) -} diff --git a/src/modules/cursor/mod.rs b/crossterm_cursor/src/cursor/mod.rs similarity index 95% rename from src/modules/cursor/mod.rs rename to crossterm_cursor/src/cursor/mod.rs index b80b491..ede65ee 100644 --- a/src/modules/cursor/mod.rs +++ b/crossterm_cursor/src/cursor/mod.rs @@ -16,11 +16,9 @@ use self::ansi_cursor::AnsiCursor; #[cfg(target_os = "windows")] use self::winapi_cursor::WinApiCursor; -pub use self::cursor::{cursor, from_screen, TerminalCursor}; -use super::functions; -use common::error::Result; +pub use self::cursor::{cursor, TerminalCursor}; +use crossterm_utils::{Result, TerminalOutput}; use std::sync::Arc; -use TerminalOutput; ///! 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 diff --git a/src/modules/cursor/test.rs b/crossterm_cursor/src/cursor/test.rs similarity index 55% rename from src/modules/cursor/test.rs rename to crossterm_cursor/src/cursor/test.rs index ce135e2..06ee7f2 100644 --- a/src/modules/cursor/test.rs +++ b/crossterm_cursor/src/cursor/test.rs @@ -1,22 +1,17 @@ -use modules::cursor::ansi_cursor::AnsiCursor; - -use modules::cursor::ITerminalCursor; - -use Screen; +use super::AnsiCursor; +use super::ITerminalCursor; /* ======================== WinApi =========================== */ #[cfg(windows)] mod winapi_tests { - use super::*; - use modules::cursor::winapi_cursor::WinApiCursor; + use super::super::WinApiCursor; + use super::*; #[test] fn goto_winapi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let cursor = WinApiCursor::new(); - cursor.goto(5, 5, &stdout); + cursor.goto(5, 5, &None); let (x, y) = cursor.pos(); assert_eq!(x, 5); @@ -25,14 +20,12 @@ mod winapi_tests { #[test] fn reset_safe_winapi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let cursor = WinApiCursor::new(); let (x, y) = cursor.pos(); - cursor.save_position(&stdout); - cursor.goto(5, 5, &stdout); - cursor.reset_position(&stdout); + cursor.save_position(&None); + cursor.goto(5, 5, &None); + cursor.reset_position(&None); let (x_saved, y_saved) = cursor.pos(); @@ -45,14 +38,12 @@ mod winapi_tests { #[test] fn reset_safe_ansi() { if try_enable_ansi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let cursor = AnsiCursor::new(); let (x, y) = cursor.pos(); - cursor.save_position(&stdout); - cursor.goto(5, 5, &stdout); - cursor.reset_position(&stdout); + cursor.save_position(&None); + cursor.goto(5, 5, &None); + cursor.reset_position(&None); let (x_saved, y_saved) = cursor.pos(); @@ -64,11 +55,8 @@ fn reset_safe_ansi() { #[test] fn goto_ansi() { if try_enable_ansi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let cursor = AnsiCursor::new(); - - cursor.goto(5, 5, &stdout); + cursor.goto(5, 5, &None); let (x, y) = cursor.pos(); assert_eq!(x, 5); @@ -80,10 +68,12 @@ fn try_enable_ansi() -> bool { #[cfg(windows)] { if cfg!(target_os = "windows") { - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + use crossterm_utils::sys::winapi::ansi::set_virtual_terminal_processing; - if !try_enable_ansi_support() { - return false; + // if it is not listed we should try with WinApi to check if we do support ANSI-codes. + match set_virtual_terminal_processing(true) { + Ok(_) => return true, + Err(e) => return false, } } } diff --git a/src/modules/cursor/winapi_cursor.rs b/crossterm_cursor/src/cursor/winapi_cursor.rs similarity index 94% rename from src/modules/cursor/winapi_cursor.rs rename to crossterm_cursor/src/cursor/winapi_cursor.rs index fdf5157..e9ea293 100644 --- a/src/modules/cursor/winapi_cursor.rs +++ b/crossterm_cursor/src/cursor/winapi_cursor.rs @@ -2,9 +2,10 @@ //! 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 super::*; -use common::error::Result; -use kernel::windows_kernel::{Cursor, Handle}; +use super::ITerminalCursor; +use crate::sys::winapi::{Cursor, Handle}; +use crossterm_utils::{Result, TerminalOutput}; +use std::sync::Arc; /// This struct is a windows implementation for cursor related actions. pub struct WinApiCursor; diff --git a/crossterm_cursor/src/lib.rs b/crossterm_cursor/src/lib.rs new file mode 100644 index 0000000..4c99b11 --- /dev/null +++ b/crossterm_cursor/src/lib.rs @@ -0,0 +1,10 @@ +#[macro_use] +extern crate crossterm_utils; + +#[cfg(windows)] +extern crate winapi; + +mod cursor; +pub mod sys; + +pub use self::cursor::{cursor, TerminalCursor}; diff --git a/crossterm_cursor/src/sys/mod.rs b/crossterm_cursor/src/sys/mod.rs new file mode 100644 index 0000000..6cc3a17 --- /dev/null +++ b/crossterm_cursor/src/sys/mod.rs @@ -0,0 +1,10 @@ +#[cfg(unix)] +pub mod unix; + +#[cfg(windows)] +pub mod winapi; + +#[cfg(unix)] +pub use self::unix::get_cursor_position; +#[cfg(windows)] +pub use self::winapi::get_cursor_position; diff --git a/crossterm_cursor/src/sys/unix.rs b/crossterm_cursor/src/sys/unix.rs new file mode 100644 index 0000000..3c4f559 --- /dev/null +++ b/crossterm_cursor/src/sys/unix.rs @@ -0,0 +1,88 @@ +use crossterm_utils::sys::unix; +use std::io::{self, Error, ErrorKind, Read, Write}; + +/// Get the cursor position based on the current platform. +#[cfg(unix)] +pub fn get_cursor_position() -> (u16, u16) { + if let Ok(pos) = pos() { + pos + } else { + (0, 0) + } +} + +pub fn pos() -> io::Result<(u16, u16)> { + // if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition. + // I am not completely happy with this approach so feel free to find an other way. + + unsafe { + if !unix::RAW_MODE_ENABLED_BY_USER || !unix::RAW_MODE_ENABLED_BY_SYSTEM { + // set this boolean so that we know that the systems has enabled raw mode. + unix::RAW_MODE_ENABLED_BY_SYSTEM = true; + unix::into_raw_mode()?; + } + } + + // Where is the cursor? + // Use `ESC [ 6 n`. + let mut stdout = io::stdout(); + + // Write command + stdout.write_all(b"\x1B[6n")?; + stdout.flush()?; + + let mut buf = [0u8; 2]; + + // Expect `ESC[` + io::stdin().read_exact(&mut buf)?; + if buf[0] != 0x1B || buf[1] as char != '[' { + return Err(Error::new(ErrorKind::Other, "test")); + } + + // Read rows and cols through a ad-hoc integer parsing function + let read_num: fn() -> Result<(i32, char), Error> = || -> Result<(i32, char), Error> { + let mut num = 0; + let mut c: char; + + loop { + let mut buf = [0u8; 1]; + io::stdin().read_exact(&mut buf)?; + c = buf[0] as char; + if let Some(d) = c.to_digit(10) { + num = if num == 0 { 0 } else { num * 10 }; + num += d as i32; + } else { + break; + } + } + + Ok((num, c)) + }; + + // Read rows and expect `;` + let (rows, c) = read_num()?; + if c != ';' { + return Err(Error::new(ErrorKind::Other, "test")); + } + + // Read cols + let (cols, c) = read_num()?; + + // Expect `R` + let res = if c == 'R' { + Ok(((cols - 1) as u16, (rows - 1) as u16)) + } else { + return Err(Error::new(ErrorKind::Other, "test")); + }; + + // If raw mode is enabled from else where in the application (by the user) we do not want to disable raw modes. + // I am not completely happy with this approach so feel free to find an other way. + unsafe { + if unix::RAW_MODE_ENABLED_BY_SYSTEM && !unix::RAW_MODE_ENABLED_BY_USER { + unix::RAW_MODE_ENABLED_BY_SYSTEM = false; + unix::disable_raw_mode()?; + } + } + + res +} diff --git a/src/kernel/windows_kernel/cursor.rs b/crossterm_cursor/src/sys/winapi.rs similarity index 92% rename from src/kernel/windows_kernel/cursor.rs rename to crossterm_cursor/src/sys/winapi.rs index f549065..9787526 100644 --- a/src/kernel/windows_kernel/cursor.rs +++ b/crossterm_cursor/src/sys/winapi.rs @@ -1,6 +1,15 @@ //! This module handles some logic for cursor interaction in the windows console. -use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer}; +#[cfg(windows)] +pub fn get_cursor_position() -> (u16, u16) { + if let Ok(cursor) = Cursor::new() { + cursor.position().unwrap().into() + } else { + (0, 0) + } +} + +pub use crossterm_winapi::{is_true, Coord, Handle, HandleType, ScreenBuffer}; use winapi::{ shared::minwindef::{FALSE, TRUE}, diff --git a/crossterm_input/.gitignore b/crossterm_input/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_input/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_input/Cargo.toml b/crossterm_input/Cargo.toml new file mode 100644 index 0000000..bbd0973 --- /dev/null +++ b/crossterm_input/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "crossterm_input" +version = "0.1.0" +authors = ["T. Post"] +description = "A cross-platform library for reading userinput." +repository = "https://github.com/TimonPost/crossterm" +documentation = "https://docs.rs/crossterm_input/" +license = "MIT" +keywords = ["input", "keys", "crossterm", "crossplatform", "terminal"] +exclude = ["target", "Cargo.lock"] +readme = "README.md" +edition = "2018" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.5", features = ["winnt"] } +crossterm_winapi = { path = "../crossterm_winapi" } + +[dependencies] +crossterm_utils = { path = "../crossterm_utils" } + +[[example]] +name = "input" +path = "examples/input.rs" + +[[example]] +name = "async_input" +path = "examples/async_input.rs" \ No newline at end of file diff --git a/crossterm_input/README.md b/crossterm_input/README.md new file mode 100644 index 0000000..3789755 --- /dev/null +++ b/crossterm_input/README.md @@ -0,0 +1,138 @@ +# Crossterm Input | cross-platform input reading . + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_input/badge.svg +[l3]: https://docs.rs/crossterm_input/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_input?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_input.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 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 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://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.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 + +This documentation is only for `crossterm_input` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. + +Add the `crossterm_input` package to your `Cargo.toml` file. + +``` +[dependencies] +`crossterm_input` = "0.1" + +``` +And import the `crossterm_input` modules you want to use. + +```rust +extern crate crossterm_input; + +pub use crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput}; +``` + +### Useful Links + +- [Documentation](https://docs.rs/crossterm_input/) +- [Crates.io](https://crates.io/crates/crossterm_input) +- [Book](http://atcentra.com/crossterm/input.html) +- [Examples](/examples) + +## Features +These are the features of this crate: + +- Cross-platform +- Everything is multithreaded (Send, Sync) +- Detailed documentation on every item +- Very few dependenties. +- Input + - Read character + - Read line + - Read async + - Read async until + - Wait for key event (terminal pause) + + Planned features: + - Read mouse events + - Read special keys events + +## Examples +Check out the [examples](/examples/) for more information about how to use this crate. + +```rust +use crossterm_input::input; + +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), + } + +``` +## 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. + +## Notice + +This library is average stable now, I don't expect it to not to change that much. +If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/crossterm_input/examples/async_input.rs b/crossterm_input/examples/async_input.rs new file mode 100644 index 0000000..b23d6c3 --- /dev/null +++ b/crossterm_input/examples/async_input.rs @@ -0,0 +1,131 @@ +extern crate crossterm_input; + +use self::crossterm_input::input; + +use std::io::{stdout, Read, Write}; +use std::time::Duration; +use std::{thread, time}; + +/// this will capture the input until the given key. +/// TODO: make sure terminal is in raw mode before this function is called. +/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag. +pub fn read_async_until() { + // TODO: make sure terminal is in raw mode. + // for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag. + + // init some modules we use for this demo + let input = input(); + + let mut stdin = input.read_until_async(b'\r').bytes(); + + for _i in 0..100 { + let a = stdin.next(); + + println!("pressed key: {:?}", a); + + if let Some(Ok(b'\r')) = a { + println!("The enter key is hit and program is not listening to input anymore."); + break; + } + + if let Some(Ok(b'x')) = a { + println!("The key: x was pressed and program is terminated."); + break; + } + + thread::sleep(time::Duration::from_millis(100)); + } +} + +/// this will read pressed characters async until `x` is typed. +/// TODO: make sure terminal is in raw mode before this function is called. +/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag. +pub fn read_async() { + let input = input(); + + let mut stdin = input.read_async().bytes(); + + for _i in 0..100 { + let a = stdin.next(); + + println!("pressed key: {:?}", a); + + if let Some(Ok(b'x')) = a { + println!("The key: `x` was pressed and program is terminated."); + break; + } + + thread::sleep(time::Duration::from_millis(50)); + } +} + +/// TODO: make sure terminal is in raw mode before this function is called. +/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag. +pub fn read_async_demo() { + // init some modules we use for this demo + let input = input(); + + // this will setup the async reading. + let mut stdin = input.read_async().bytes(); + + // clear terminal and reset the cursor. + terminal.clear(ClearType::All); + cursor.goto(1, 1); + + // loop until the enter key (\r) is pressed. + loop { + terminal.clear(ClearType::All); + cursor.goto(1, 1); + + // get the next pressed key + let pressed_key = stdin.next(); + terminal.write(format!("{:?} <- Character pressed", pressed_key)); + + // check if pressed key is enter (\r) + if let Some(Ok(b'\r')) = pressed_key { + break; + } + + // wait 200 ms and reset cursor write + thread::sleep(Duration::from_millis(200)); + } +} + +/// TODO: make sure terminal is in raw mode before this function is called. +/// for more information checkout `crossterm_screen` or use crossterm with the `screen` feature flag. +pub fn async_reading_on_alternate_screen() { + let screen = Screen::new(false); + + // switch to alternate screen + if let Ok(alternate) = screen.enable_alternate_modes(true) { + let crossterm = Crossterm::from_screen(&alternate.screen); + // init some modules we use for this demo + let input = crossterm.input(); + let terminal = crossterm.terminal(); + let mut cursor = crossterm.cursor(); + + // this will setup the async reading. + let mut stdin = input.read_async().bytes(); + + // loop until the enter key (\r) is pressed. + loop { + terminal.clear(ClearType::All); + cursor.goto(1, 1); + + // get the next pressed key + let pressed_key = stdin.next(); + + terminal.write(format!("{:?} <- Character pressed", pressed_key)); + + // check if pressed key is enter (\r) + if let Some(Ok(b'\r')) = pressed_key { + break; + } + + // wait 200 ms and reset cursor write + thread::sleep(Duration::from_millis(200)); + } + } +} + +fn main() {} diff --git a/examples/input/keyboard/input.rs b/crossterm_input/examples/input.rs similarity index 66% rename from examples/input/keyboard/input.rs rename to crossterm_input/examples/input.rs index d176521..217b830 100644 --- a/examples/input/keyboard/input.rs +++ b/crossterm_input/examples/input.rs @@ -1,7 +1,6 @@ -extern crate crossterm; +extern crate crossterm_input; -use self::crossterm::input::{input, KeyEvent, TerminalInput}; -use self::crossterm::Screen; +use self::crossterm_input::{input, KeyEvent, Screen, TerminalInput}; pub fn read_char() { let input = input(); @@ -23,5 +22,8 @@ pub fn read_line() { pub fn pause_terminal() { println!("Press 'x' to quit..."); - TerminalInput::wait_until(KeyEvent::OnKeyPress(b'x')); + let terminal_input = TerminalInput::new(); + terminal_input.wait_until(KeyEvent::OnKeyPress(b'x')); } + +fn main() {} diff --git a/src/modules/input/input.rs b/crossterm_input/src/input/input.rs similarity index 76% rename from src/modules/input/input.rs rename to crossterm_input/src/input/input.rs index 4faf531..dc6e792 100644 --- a/src/modules/input/input.rs +++ b/crossterm_input/src/input/input.rs @@ -3,30 +3,36 @@ use super::*; use std::{thread, time::Duration}; -use Screen; -/// Struct that stores a platform-specific implementation for input related actions. +use crossterm_utils::TerminalOutput; + +/// Allows you to preform actions with the < option >. /// -/// Check `/examples/input` the examples folder on github for more info. +/// # Features: /// -/// ```rust -/// extern crate crossterm; -/// use self::crossterm::Screen; -/// use self::crossterm::input; +/// - features /// -/// let input = input(); -/// let result = input.read_line(); -/// let pressed_char = input.read_char(); -///``` +/// Check `/examples/` in the library for more specific examples. /// -/// **!! Take note when using input with raw mode you should use the `Screen` type. !!** +/// # Remarks /// -/// ``` -/// let screen = Screen::new(true); -/// let input = crossterm::input::from_screen(&screen); -/// ``` -/// When you want to use 'input' related actions on 'alternate screen' use the `Screen` type instead, and pass it to the `terminal::from_screen()` function. -/// By doing that terminal actions will be performed on the alternate screen. +/// When you want to use '< name >' on 'alternate screen' use the 'crossterm_screen' crate. + +/// Allows you to read user input. +/// +/// # Features: +/// +/// - Read character +/// - Read line +/// - Read async +/// - Read async until +/// - Wait for key event (terminal pause) +/// +/// Check `/examples/` in the library for more specific examples. +/// +/// # Remarks +/// +/// When you want to use 'input' on 'alternate screen' use the 'crossterm_screen' crate. pub struct TerminalInput<'stdout> { terminal_input: Box, stdout: Option<&'stdout Arc>, @@ -49,14 +55,15 @@ impl<'stdout> TerminalInput<'stdout> { /// Create a new instance of `TerminalInput` whereon input related actions could be preformed. /// - /// **Note** + /// # Remarks /// /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode'. - /// And you want your actions from the `TerminalInput`, created by this function, to operate on the 'alternate screen'. + /// This could be useful when you have a screen which is in 'alternate mode', + /// and you want your actions from the `TerminalInput`, created by this function, to operate on the 'alternate screen'. /// + /// You should checkout the 'crossterm_screen' crate for more information about this. /// # Example - /// ``` + /// ```rust /// let screen = Screen::default(); // /// if let Ok(alternate) = screen.enable_alternate_modes(false) { @@ -78,9 +85,12 @@ impl<'stdout> TerminalInput<'stdout> { /// Read one line from the user input. /// - /// Note that this function only works when rawscreen is not turned on. - /// When you do want to read a line in raw mode please checkout `read_async` or `read_async_until`. + /// # Remark + /// This function is not work when raw screen is turned on. + /// When you do want to read a line in raw mode please, checkout `read_async` or `read_async_until`. + /// Not sure what 'raw mode' is, checkout the 'crossterm_screen' crate. /// + /// # Example /// ```rust /// let input = input(); /// match input.read_line() { @@ -118,8 +128,11 @@ impl<'stdout> TerminalInput<'stdout> { /// Read the input asynchronously from the user. /// - /// This call will not block the current thread. - /// Under the hood a thread is fired which will read input on unix systems from TTY and on windows systems with '_getwch' and '_getwche' + /// # Remarks + /// - This call will not block the current thread. + /// A thread will be fired to read input, on unix systems from TTY and on windows systems with '_getwch' and '_getwche'. + /// - Requires 'raw screen to be enabled'. + /// Not sure what this is, please checkout the 'crossterm_screen' crate. /// /// ```rust /// // we need to enable raw mode otherwise the characters will be outputted by default before we are able to read them. @@ -152,6 +165,13 @@ impl<'stdout> TerminalInput<'stdout> { /// /// This is the same as `read_async()` but stops reading when a certain character is hit. /// + /// # Remarks + /// - This call will not block the current thread. + /// A thread will be fired to read input, on unix systems from TTY and on windows systems with '_getwch' and '_getwche'. + /// - Requires 'raw screen to be enabled'. + /// Not sure what this is, please checkout the 'crossterm_screen' crate. + /// - Thread is automatically destroyed when the 'delimiter' is hit. + /// /// ```rust /// // we need to enable raw mode otherwise the characters will be outputted by default before we are able to read them. /// let screen = Screen::new(true); @@ -191,7 +211,9 @@ impl<'stdout> TerminalInput<'stdout> { /// This will prevent the current thread from continuing until the passed `KeyEvent` has happened. /// - /// This function will put the terminal into raw mode so that any key presses will not be shown at the screen. + /// # Remark + /// - Requires 'raw screen to be enabled'. + /// Not sure what this is, please checkout the 'crossterm_screen' crate. /// /// ``` /// use crossterm::input::{TerminalInput, KeyEvent}; @@ -201,11 +223,8 @@ impl<'stdout> TerminalInput<'stdout> { /// TerminalInput::wait_until(KeyEvent::OnKeyPress(b'x')); /// } /// ``` - pub fn wait_until(key_event: KeyEvent) { - let screen = Screen::new(true); - let input = from_screen(&screen); - - let mut stdin = input.read_async().bytes(); + pub fn wait_until(&self, key_event: KeyEvent) { + let mut stdin = self.read_async().bytes(); loop { let pressed_key: Option> = stdin.next(); @@ -239,9 +258,3 @@ impl<'stdout> TerminalInput<'stdout> { pub fn input<'stdout>() -> TerminalInput<'stdout> { TerminalInput::new() } - -/// Get a `TerminalInput` instance whereon input related actions can be performed. -/// Pass the reference to any `Screen` you want this type to perform actions on. -pub fn from_screen(screen: &Screen) -> TerminalInput { - TerminalInput::from_output(&screen.stdout) -} diff --git a/src/modules/input/mod.rs b/crossterm_input/src/input/mod.rs similarity index 97% rename from src/modules/input/mod.rs rename to crossterm_input/src/input/mod.rs index a965125..44f10e2 100644 --- a/src/modules/input/mod.rs +++ b/crossterm_input/src/input/mod.rs @@ -13,12 +13,12 @@ use self::unix_input::UnixInput; #[cfg(target_os = "windows")] use self::windows_input::WindowsInput; -pub use self::input::{from_screen, input, TerminalInput}; +pub use self::input::{input, TerminalInput}; use std::io::{self, Error, ErrorKind, Read}; use std::sync::{mpsc, Arc}; -use TerminalOutput; +use crossterm_utils::TerminalOutput; /// This trait defines the actions that can be preformed with the terminal input. /// This trait can be implemented so that a concrete implementation of the ITerminalInput can fulfill diff --git a/src/modules/input/unix_input.rs b/crossterm_input/src/input/unix_input.rs similarity index 94% rename from src/modules/input/unix_input.rs rename to crossterm_input/src/input/unix_input.rs index 5c16297..b7b5cfe 100644 --- a/src/modules/input/unix_input.rs +++ b/crossterm_input/src/input/unix_input.rs @@ -1,8 +1,9 @@ //! This is a UNIX specific implementation for input related action. use super::*; -use kernel::unix_kernel::terminal::{get_tty, read_char}; +use crate::sys::unix::{get_tty, read_char}; +use crossterm_utils::TerminalOutput; use std::char; use std::thread; diff --git a/src/modules/input/windows_input.rs b/crossterm_input/src/input/windows_input.rs similarity index 99% rename from src/modules/input/windows_input.rs rename to crossterm_input/src/input/windows_input.rs index 5829d02..00dd443 100644 --- a/src/modules/input/windows_input.rs +++ b/crossterm_input/src/input/windows_input.rs @@ -2,6 +2,7 @@ use super::*; +use crossterm_utils::TerminalOutput; use std::char; use std::thread; use winapi::um::winnt::INT; diff --git a/crossterm_input/src/lib.rs b/crossterm_input/src/lib.rs new file mode 100644 index 0000000..64608df --- /dev/null +++ b/crossterm_input/src/lib.rs @@ -0,0 +1,6 @@ +extern crate crossterm_utils; + +mod input; +mod sys; + +pub use self::input::{input, AsyncReader, KeyEvent, TerminalInput}; diff --git a/crossterm_input/src/sys/mod.rs b/crossterm_input/src/sys/mod.rs new file mode 100644 index 0000000..b13064b --- /dev/null +++ b/crossterm_input/src/sys/mod.rs @@ -0,0 +1,2 @@ +#[cfg(unix)] +pub mod unix; diff --git a/crossterm_input/src/sys/unix.rs b/crossterm_input/src/sys/unix.rs new file mode 100644 index 0000000..e9c45c6 --- /dev/null +++ b/crossterm_input/src/sys/unix.rs @@ -0,0 +1,70 @@ +use crossterm_utils::sys::unix; +use std::fs; +use std::io; +use std::os::unix::io::AsRawFd; + +/// Get the TTY device. +/// +/// This allows for getting stdio representing _only_ the TTY, and not other streams. +pub fn get_tty() -> io::Result { + let mut tty_f: fs::File = unsafe { ::std::mem::zeroed() }; + + let _fd = unsafe { + if libc::isatty(libc::STDIN_FILENO) == 1 { + libc::STDIN_FILENO + } else { + tty_f = fs::File::open("/dev/tty")?; + tty_f.as_raw_fd() + } + }; + + Ok(tty_f) +} + +pub fn read_char() -> io::Result { + let mut buf = [0u8; 20]; + + let fd = unix::into_raw_mode()?; + + // read input and convert it to char + let rv = unsafe { + let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20); + + if read < 0 { + Err(io::Error::last_os_error()) + } else if buf[0] == b'\x03' { + Err(io::Error::new( + io::ErrorKind::Interrupted, + "read interrupted", + )) + } else { + let mut pressed_char = Ok(' '); + + if let Ok(s) = ::std::str::from_utf8(&buf[..read as usize]) { + if let Some(c) = s.chars().next() { + pressed_char = Ok(c); + } + } else { + pressed_char = Err(io::Error::new( + io::ErrorKind::Interrupted, + "Could not parse char to utf8 char", + )); + } + + pressed_char + } + }; + + unix::disable_raw_mode()?; + + // if the user hit ^C we want to signal SIGINT to outselves. + if let Err(ref err) = rv { + if err.kind() == io::ErrorKind::Interrupted { + unsafe { + libc::raise(libc::SIGINT); + } + } + } + + rv +} diff --git a/crossterm_screen/.gitignore b/crossterm_screen/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_screen/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_screen/Cargo.toml b/crossterm_screen/Cargo.toml new file mode 100644 index 0000000..0f275a1 --- /dev/null +++ b/crossterm_screen/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "crossterm_screen" +version = "0.1.0" +authors = ["T. Post"] +description = "A cross-platform library for raw and alternate screen." +repository = "https://github.com/TimonPost/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" } + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.5", features = ["minwindef", "wincon"] } +crossterm_winapi = { path = "../crossterm_winapi" } \ No newline at end of file diff --git a/crossterm_screen/README.md b/crossterm_screen/README.md new file mode 100644 index 0000000..9d77cc2 --- /dev/null +++ b/crossterm_screen/README.md @@ -0,0 +1,121 @@ +# Crossterm Screen | cross-platform alternate, raw screen. + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_screen/badge.svg +[l3]: https://docs.rs/crossterm_screen/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_screen?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_screen.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://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.html) + +In case you are wondering what 'alternate' or 'raw' screen is, you could checkout the [book](http://atcentra.com/crossterm/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 + +This documentation is only for `crossterm_screen` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). +Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate +and the [book](http://atcentra.com/crossterm/screen.html) for more information about how to use the alternate or raw screen options. + +Add the `crossterm_screen` package to your `Cargo.toml` file. + +``` +[dependencies] +`crossterm_screen` = "0.1" + +``` +And import the `crossterm_screen` modules you want to use. + +```rust +extern crate crossterm_screen; + +pub use crossterm_screen::{AlternateScreen, RawScreen, Screen}; +``` + +### Useful Links + +- [Documentation](https://docs.rs/crossterm_screen/) +- [Crates.io](https://crates.io/crates/crossterm_screen) +- [Book](http://atcentra.com/crossterm/screen.html) +- [Examples](/examples) + +## Features +These are the features of this crate: + +- Cross-platform +- Everything is multithreaded (Send, Sync) +- Detailed documentation on every item +- Very few dependenties. +- Alternate screen +- Raw screen + +Planned features: +- make is possible to switch between multiple buffers. + +## Examples +Check out the [examples](/examples/) for more information about how to use this crate. + +## 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. + +## Notice + +This library is average stable now, I don't expect it to not to change that much. +If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/crossterm_screen/examples/alternate_screen.rs b/crossterm_screen/examples/alternate_screen.rs new file mode 100644 index 0000000..96862ad --- /dev/null +++ b/crossterm_screen/examples/alternate_screen.rs @@ -0,0 +1,16 @@ +extern crate crossterm_screen; + +use crossterm_screen::Screen; + +use std::io::{stdout, Write}; +use std::{thread, time}; + +/// print wait screen on alternate screen, then switch back. +pub fn print_wait_screen_on_alternate_window() { + let screen = Screen::default(); + + // move to alternate screen, 'false' means if the alternate screen should be in raw modes. + if let Ok(alternate) = screen.enable_alternate_modes(false) { + // do some stuff on the alternate screen. + } // <- alternate screen will be disabled when dropped. +} diff --git a/crossterm_screen/examples/raw_mode.rs b/crossterm_screen/examples/raw_mode.rs new file mode 100644 index 0000000..c6270f1 --- /dev/null +++ b/crossterm_screen/examples/raw_mode.rs @@ -0,0 +1,16 @@ +extern crate crossterm_screen; + +use crossterm_screen::Screen; + +use std::io::{stdout, Write}; +use std::{thread, time}; + +pub fn raw_modes() { + // create a Screen instance who operates on the default output; io::stdout(). + let screen = Screen::default(); + + // create a Screen instance who operates on the default output; io::stdout(). By passing in 'true' we make this screen 'raw' + let screen = Screen::new(true); + + drop(screen); // <-- by dropping the screen raw modes will be disabled. +} diff --git a/crossterm_screen/src/lib.rs b/crossterm_screen/src/lib.rs new file mode 100644 index 0000000..512f295 --- /dev/null +++ b/crossterm_screen/src/lib.rs @@ -0,0 +1,18 @@ +//! 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. +#[macro_use] +extern crate crossterm_utils; + +#[cfg(windows)] +extern crate winapi; + +#[cfg(windows)] +extern crate crossterm_winapi; + +#[cfg(unix)] +extern crate libc; + +mod screen; +mod sys; + +pub use self::screen::{AlternateScreen, RawScreen, Screen}; diff --git a/src/common/screen/alternate.rs b/crossterm_screen/src/screen/alternate.rs similarity index 85% rename from src/common/screen/alternate.rs rename to crossterm_screen/src/screen/alternate.rs index af4b0ad..a031375 100644 --- a/src/common/screen/alternate.rs +++ b/crossterm_screen/src/screen/alternate.rs @@ -5,9 +5,14 @@ //! 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. -use super::commands::{self, IAlternateScreenCommand}; +#[cfg(windows)] +use crate::sys::winapi::ToAlternateScreenCommand; +#[cfg(windows)] +use crossterm_utils::get_module; + +use crate::sys::{self, IAlternateScreenCommand}; + use super::{RawScreen, Screen, TerminalOutput}; -use common::functions; use std::convert::From; use std::io; @@ -40,15 +45,14 @@ impl AlternateScreen { raw_mode: bool, ) -> io::Result { #[cfg(target_os = "windows")] - let command = - functions::get_module::>( - Box::from(commands::win_commands::ToAlternateScreenCommand::new()), - Box::from(commands::shared_commands::ToAlternateScreenCommand::new()), - ) - .unwrap(); + let command = get_module::>( + Box::from(ToAlternateScreenCommand::new()), + Box::from(sys::ToAlternateScreenCommand::new()), + ) + .unwrap(); #[cfg(not(target_os = "windows"))] - let command = Box::from(commands::shared_commands::ToAlternateScreenCommand::new()); + let command = Box::from(sys::ToAlternateScreenCommand::new()); let mut stdout = stdout; command.enable(&mut stdout)?; diff --git a/src/common/screen/mod.rs b/crossterm_screen/src/screen/mod.rs similarity index 89% rename from src/common/screen/mod.rs rename to crossterm_screen/src/screen/mod.rs index 64936c1..098989c 100644 --- a/src/common/screen/mod.rs +++ b/crossterm_screen/src/screen/mod.rs @@ -5,7 +5,7 @@ mod alternate; mod raw; mod screen; -use super::{commands, TerminalOutput}; +use crossterm_utils::TerminalOutput; pub use self::alternate::AlternateScreen; pub use self::raw::RawScreen; diff --git a/src/common/screen/raw.rs b/crossterm_screen/src/screen/raw.rs similarity index 86% rename from src/common/screen/raw.rs rename to crossterm_screen/src/screen/raw.rs index 8fb5c2a..c1d31b5 100644 --- a/src/common/screen/raw.rs +++ b/crossterm_screen/src/screen/raw.rs @@ -14,7 +14,7 @@ //! //! With these modes you can easier design the terminal screen. -use super::commands::*; +use crate::sys; use std::io; @@ -28,9 +28,9 @@ impl RawScreen { /// Put terminal in raw mode. pub fn into_raw_mode() -> io::Result<()> { #[cfg(not(target_os = "windows"))] - let mut command = unix_command::RawModeCommand::new(); + let mut command = sys::unix::RawModeCommand::new(); #[cfg(target_os = "windows")] - let mut command = win_commands::RawModeCommand::new(); + let mut command = sys::winapi::RawModeCommand::new(); let _result = command.enable(); @@ -40,9 +40,9 @@ impl RawScreen { /// Put terminal back in original modes. pub fn disable_raw_modes() -> io::Result<()> { #[cfg(not(target_os = "windows"))] - let mut command = unix_command::RawModeCommand::new(); + let mut command = sys::unix::RawModeCommand::new(); #[cfg(target_os = "windows")] - let command = win_commands::RawModeCommand::new(); + let command = sys::winapi::RawModeCommand::new(); command.disable()?; Ok(()) diff --git a/src/common/screen/screen.rs b/crossterm_screen/src/screen/screen.rs similarity index 98% rename from src/common/screen/screen.rs rename to crossterm_screen/src/screen/screen.rs index dbe3a8d..86d87d5 100644 --- a/src/common/screen/screen.rs +++ b/crossterm_screen/src/screen/screen.rs @@ -1,5 +1,5 @@ use super::{AlternateScreen, RawScreen}; -use TerminalOutput; +use crossterm_utils::TerminalOutput; use std::io::Result; use std::io::Write; @@ -58,7 +58,8 @@ use std::sync::Arc; /// let input = crossterm::input::from_screen(&alternate_screen.screen); /// } /// ``` -/// +/// # Remarks +/// Note that using `Screen` is preferred over manually using `AlternateScreen` or `RawScreen`. pub struct Screen { buffer: Vec, pub stdout: Arc, diff --git a/crossterm_screen/src/sys/mod.rs b/crossterm_screen/src/sys/mod.rs new file mode 100644 index 0000000..fe02951 --- /dev/null +++ b/crossterm_screen/src/sys/mod.rs @@ -0,0 +1,55 @@ +#[cfg(unix)] +pub mod unix; + +#[cfg(windows)] +pub mod winapi; + +use crossterm_utils::TerminalOutput; + +use std::io; + +/// This command is used for switching to alternate screen and back to main screen. +pub struct ToAlternateScreenCommand; + +impl ToAlternateScreenCommand { + pub fn new() -> ToAlternateScreenCommand { + ToAlternateScreenCommand + } +} + +impl IAlternateScreenCommand for ToAlternateScreenCommand { + /// enable alternate screen. + fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()> { + stdout.write_str(csi!("?1049h"))?; + Ok(()) + } + + /// disable alternate screen. + fn disable(&self, stdout: &TerminalOutput) -> io::Result<()> { + stdout.write_str(csi!("?1049l"))?; + Ok(()) + } +} + +/// This trait provides a way to execute some state changing commands. +pub trait IStateCommand { + fn execute(&mut self) -> io::Result<()>; + fn undo(&mut self) -> io::Result<()>; +} + +pub trait IEnableAnsiCommand { + fn enable(&self) -> io::Result; + fn disable(&self) -> io::Result<()>; +} + +// This trait provides an interface for switching to alternate screen and back. +pub trait IAlternateScreenCommand: Sync + Send { + fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>; + fn disable(&self, stdout: &TerminalOutput) -> io::Result<()>; +} + +// This trait provides an interface for switching to raw mode and back. +pub trait IRawScreenCommand: Sync + Send { + fn enable(&mut self) -> io::Result<()>; + fn disable(&self) -> io::Result<()>; +} diff --git a/src/common/commands/unix_command.rs b/crossterm_screen/src/sys/unix.rs similarity index 54% rename from src/common/commands/unix_command.rs rename to crossterm_screen/src/sys/unix.rs index e37c40e..e17dc41 100644 --- a/src/common/commands/unix_command.rs +++ b/crossterm_screen/src/sys/unix.rs @@ -1,6 +1,3 @@ -//! A module which contains the commands that can be used for UNIX systems. -use kernel::unix_kernel::terminal; - use std::io::Result; /// This command is used for enabling and disabling raw mode for the terminal. @@ -13,17 +10,19 @@ impl RawModeCommand { /// Enables raw mode. pub fn enable(&mut self) -> Result<()> { - terminal::into_raw_mode()?; + crossterm_utils::sys::unix::into_raw_mode()?; - unsafe { terminal::RAW_MODE_ENABLED_BY_USER = true } + // will be removed in 6.1 + unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = true } Ok(()) } /// Disables raw mode. pub fn disable(&mut self) -> Result<()> { - terminal::disable_raw_mode()?; + crossterm_utils::sys::unix::disable_raw_mode()?; - unsafe { terminal::RAW_MODE_ENABLED_BY_USER = false } + // will be removed in 6.1 + unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = false } Ok(()) } } diff --git a/crossterm_screen/src/sys/winapi.rs b/crossterm_screen/src/sys/winapi.rs new file mode 100644 index 0000000..21a01d1 --- /dev/null +++ b/crossterm_screen/src/sys/winapi.rs @@ -0,0 +1,73 @@ +use super::IAlternateScreenCommand; +use crossterm_utils::TerminalOutput; +use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; +use std::io; +use winapi::shared::minwindef::DWORD; +use winapi::um::wincon; + +/// This command is used for enabling and disabling raw mode for windows systems. +/// For more info check: https://docs.microsoft.com/en-us/windows/console/high-level-console-modes. +#[derive(Clone, Copy)] +pub struct RawModeCommand { + mask: DWORD, +} +use self::wincon::{ENABLE_LINE_INPUT, ENABLE_WRAP_AT_EOL_OUTPUT}; +impl RawModeCommand { + pub fn new() -> Self { + RawModeCommand { + mask: ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LINE_INPUT, + } + } +} + +impl RawModeCommand { + /// Enables raw mode. + pub fn enable(&mut self) -> io::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) -> io::Result<()> { + let console_mode = ConsoleMode::new()?; + + let dw_mode = console_mode.mode()?; + + let new_mode = dw_mode | self.mask; + + console_mode.set_mode(new_mode)?; + + return Ok(()); + } +} + +/// This command is used for switching to alternate screen and back to main screen. +/// check https://docs.microsoft.com/en-us/windows/console/reading-and-writing-blocks-of-characters-and-attributes for more info +pub struct ToAlternateScreenCommand; + +impl ToAlternateScreenCommand { + pub fn new() -> ToAlternateScreenCommand { + return ToAlternateScreenCommand {}; + } +} + +impl IAlternateScreenCommand for ToAlternateScreenCommand { + fn enable(&self, _stdout: &mut TerminalOutput) -> io::Result<()> { + let alternate_screen = ScreenBuffer::create(); + alternate_screen.show()?; + Ok(()) + } + + fn disable(&self, _stdout: &TerminalOutput) -> io::Result<()> { + let screen_buffer = ScreenBuffer::from(Handle::output_handle()?); + screen_buffer.show()?; + Ok(()) + } +} diff --git a/crossterm_style/.gitignore b/crossterm_style/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_style/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_style/Cargo.toml b/crossterm_style/Cargo.toml new file mode 100644 index 0000000..854b156 --- /dev/null +++ b/crossterm_style/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "crossterm_style" +version = "0.1.0" +authors = ["T. Post"] +description = "A cross-platform library styling the terminal output." +repository = "https://github.com/TimonPost/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.5", features = ["wincon"] } +crossterm_winapi = { path = "../crossterm_winapi" } + +[dependencies] +crossterm_utils = { path = "../crossterm_utils" } + +[[example]] +name = "style" +path = "examples/style.rs" \ No newline at end of file diff --git a/crossterm_style/README.md b/crossterm_style/README.md new file mode 100644 index 0000000..713be75 --- /dev/null +++ b/crossterm_style/README.md @@ -0,0 +1,157 @@ +# Crossterm Style | cross-platform styling. + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_style/badge.svg +[l3]: https://docs.rs/crossterm_style/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_style?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_style.svg?branch=master + +This crate allows you to style te 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 te 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://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.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 + +This documentation is only for `crossterm_style` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. + +Add the `crossterm_style` package to your `Cargo.toml` file. + +``` +[dependencies] +`crossterm_style` = "0.1" + +``` +And import the `crossterm_style` modules you want to use. + +```rust +extern crate crossterm_style; + +pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor}; +``` + +### Useful Links + +- [Documentation](https://docs.rs/crossterm_input/) +- [Crates.io](https://crates.io/crates/crossterm_input) +- [Book](http://atcentra.com/crossterm/styling.html) +- [Examples](/examples) + +## Features +These are the features of this crate: + +- Cross-platform +- Everything is multithreaded (Send, Sync) +- Detailed documentation on every item +- Very few dependenties. +- Styled output + - Foreground color (16 base colors) + - Background color (16 base colors) + - 256 color support (Windows 10 and UNIX only) + - RGB support (Windows 10 and UNIX only) + - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) + +Planned features: +- Easier usage; e.g. `println!("{}Bold{}Blue", Attribute::Bold, Color::Blue)` + +## Examples +Check out the [examples](/examples/) for more information about how to use this crate. +```rust +use crossterm::style::{Color, style}; + +// store objcets so it could be painted later to the screen. +let style1 = style("Some Blue font on Black background").with(Color::Blue).on(Color::Black); +let style2 = style("Some Red font on Yellow background").with(Color::Red).on(Color::Yellow); + +// syling font with (Windows 10 and UNIX systems) +let normal = style("Normal text"); +let bold = style("Bold text").bold(); +let italic = style("Italic text").italic(); +let slow_blink = style("Slow blinking text").slow_blink(); +let rapid_blink = style("Rapid blinking text").rapid_blink(); +let hidden = style("Hidden text").hidden(); +let underlined = style("Underlined text").underlined(); +let reversed = style("Reversed text").reverse(); +let dimmed = style("Dim text").dim(); +let crossed_out = style("Crossed out font").crossed_out(); + +// paint styled text to screen (this could also be called inline) +println!("{}", style1); +println!("{}", style2); +println!("{}", bold); +println!("{}", hidden); +... + +// cursom rgb value (Windows 10 and UNIX systems) +style("RGB color (10,10,10) ").with(Color::Rgb { + r: 10, + g: 10, + b: 10 +})); + +// custom ansi color value (Windows 10 and UNIX systems) +style("ANSI color value (50) ").with(Color::AnsiValue(50)); + +``` +## 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. + +## Notice + +This library is average stable now, I don't expect it to not to change that much. +If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/crossterm_style/examples/style.rs b/crossterm_style/examples/style.rs new file mode 100644 index 0000000..a6c308e --- /dev/null +++ b/crossterm_style/examples/style.rs @@ -0,0 +1,236 @@ +//! +//! Examples of coloring the terminal. +//! +extern crate crossterm_style; + +use self::crossterm_style::{color, style, Color}; + +/// print some red font | demonstration. +pub fn paint_foreground() { + // Create a styled object. + // Call the method `with()` on the object given by `style()` and pass in any Color from the Color enum. + let styledobject = style("Red foreground").with(Color::Red); + + // Print the object to the given screen and. + println!("Colored text: {}", styledobject); + + // Or print inline + println!( + "Colored text: {}", + style("Blue foreground").with(Color::Blue) + ); +} + +/// print some font on red background | demonstration. +pub fn paint_background() { + // Create a styled object. + // Call the method `with()` on the object given by `style()` and pass in any Color from the Color enum. + let styledobject = style("Red foreground").on(Color::Red); + + // Print the object to the given screen and. + println!("Colored text: {}", styledobject); + + // Or print inline + println!("Colored text: {}", style("Red foreground").on(Color::Blue)); +} + +/// Print all available foreground colors | demonstration. +pub fn print_all_foreground_colors() { + println!( + "{}", + style(format!("Black : \t\t {} \n", "■")).with(Color::Black) + ); + println!( + "{}", + style(format!("Red : \t\t {} \n", "■")).with(Color::Red) + ); + println!( + "{}", + style(format!("Cyan : \t\t {} \n", "■")).with(Color::Cyan) + ); + println!( + "{}", + style(format!("DarkCyan : \t {} \n", "■")).with(Color::DarkCyan) + ); + println!( + "{}", + style(format!("DarkRed : \t {} \n", "■")).with(Color::DarkRed) + ); + println!( + "{}", + style(format!("Green : \t {} \n", "■")).with(Color::Green) + ); + println!( + "{}", + style(format!("DarkGreen : \t {} \n", "■")).with(Color::DarkGreen) + ); + println!( + "{}", + style(format!("Blue : \t\t {} \n", "■")).with(Color::Blue) + ); + println!( + "{}", + style(format!("DarkBlue : \t {} \n", "■")).with(Color::DarkBlue) + ); + println!( + "{}", + style(format!("Magenta : \t {} \n", "■")).with(Color::Magenta) + ); + println!( + "{}", + style(format!("DarkMagenta : \t {} \n", "■")).with(Color::DarkMagenta) + ); + println!( + "{}", + style(format!("Yellow : \t {} \n", "■")).with(Color::Yellow) + ); + println!( + "{}", + style(format!("DarkYellow : \t {} \n", "■")).with(Color::DarkYellow) + ); + println!( + "{}", + style(format!("Grey : \t\t {} \n", "■")).with(Color::Grey) + ); + println!( + "{}", + style(format!("White : \t {} \n", "■")).with(Color::White) + ); + + // supported by Unix and < Windows 10 terminals + println!( + "{}", + style("RGB color (10,10,10) ").with(Color::Rgb { + r: 10, + g: 10, + b: 10 + }) + ); + + // supported by Unix and < Windows 10 terminals + println!( + "{}", + style("RGB color (10,10,10) ").with(Color::AnsiValue(50)) + ); +} + +/// Print all available foreground colors | demonstration. +pub fn print_all_background_colors() { + println!( + "{}", + style(format!("Black : \t {} \n", "■")).on(Color::Black) + ); + println!( + "{}", + style(format!("Red : \t\t {} \n", "■")).on(Color::Red) + ); + println!( + "{}", + style(format!("Cyan : \t\t {} \n", "■")).on(Color::Cyan) + ); + println!( + "{}", + style(format!("DarkCyan : \t {} \n", "■")).on(Color::DarkCyan) + ); + println!( + "{}", + style(format!("DarkRed : \t {} \n", "■")).on(Color::DarkRed) + ); + println!( + "{}", + style(format!("Green : \t {} \n", "■")).on(Color::Green) + ); + println!( + "{}", + style(format!("DarkGreen : \t {} \n", "■")).on(Color::DarkGreen) + ); + println!( + "{}", + style(format!("Blue : \t\t {} \n", "■")).on(Color::Blue) + ); + println!( + "{}", + style(format!("DarkBlue : \t {} \n", "■")).on(Color::DarkBlue) + ); + println!( + "{}", + style(format!("Magenta : \t {} \n", "■")).on(Color::Magenta) + ); + println!( + "{}", + style(format!("DarkMagenta : \t {} \n", "■")).on(Color::DarkMagenta) + ); + println!( + "{}", + style(format!("Yellow : \t {} \n", "■")).on(Color::Yellow) + ); + println!( + "{}", + style(format!("DarkYellow : \t {} \n", "■")).on(Color::DarkYellow) + ); + println!( + "{}", + style(format!("Grey : \t\t {} \n", "■")).on(Color::Grey) + ); + println!( + "{}", + style(format!("White : \t {} \n", "■")).on(Color::White) + ); + + // supported by Unix and < Windows 10 terminals + println!( + "{}", + style("RGB color (10,10,10) ").on(Color::Rgb { + r: 10, + g: 10, + b: 10 + }) + ); + // supported by Unix and < Windows 10 terminals + println!( + "{}", + style("RGB color (10,10,10) ").on(Color::AnsiValue(50)) + ); +} + +/// Print font with all available attributes. Note that this can only be used at unix systems and that some are not supported widely | demonstration.. +#[cfg(unix)] +pub fn print_font_with_attributes() { + println!("{}", style("Normal text")); + println!("{}", style("Bold text").bold()); + println!("{}", style("Italic text").italic()); + println!("{}", style("Slow blinking text").slow_blink()); + println!("{}", style("Rapid blinking text").rapid_blink()); + println!("{}", style("Hidden text").hidden()); + println!("{}", style("Underlined text").underlined()); + println!("{}", style("Reversed text").reverse()); + println!("{}", style("Dim text").dim()); + println!("{}", style("Crossed out font").crossed_out()); +} + +/// Print font with all available attributes. Note that this can only be used at unix systems and that some are not supported widely | demonstration.. +#[cfg(windows)] +pub fn print_font_with_attributes() { + println!("{}", style("Normal text")); + println!("{}", style("Bold text").bold()); + println!("{}", style("Underlined text").underlined()); + println!("{}", style("Negative text").negative()); +} + +/// Print all supported RGB colors, not supported for Windows systems < 10 | demonstration. +pub fn print_supported_colors() { + let count = color().get_available_color_count().unwrap(); + + for i in 0..count { + println!( + "{}", + style(format!("White : \t {}", i)).on(Color::AnsiValue(i as u8)) + ); + } +} + +fn main() { + print_all_background_colors(); + print_all_foreground_colors(); + print_font_with_attributes(); +} diff --git a/src/modules/style/ansi_color.rs b/crossterm_style/src/ansi_color.rs similarity index 91% rename from src/modules/style/ansi_color.rs rename to crossterm_style/src/ansi_color.rs index 28724e8..b1a867c 100644 --- a/src/modules/style/ansi_color.rs +++ b/crossterm_style/src/ansi_color.rs @@ -1,8 +1,10 @@ //! This is a ANSI specific implementation for styling related action. //! This module is used for Windows 10 terminals and Unix terminals by default. -use super::*; -use common::error::Result; +use crate::{Color, ColorType, ITerminalColor}; +use crossterm_utils::{write, write_str, Result, TerminalOutput}; + +use std::sync::Arc; /// This struct is an ANSI escape code implementation for color related actions. pub struct AnsiColor; @@ -15,7 +17,7 @@ impl AnsiColor { impl ITerminalColor for AnsiColor { fn set_fg(&self, fg_color: Color, stdout: &Option<&Arc>) -> Result<()> { - functions::write( + write( stdout, format!( csi!("{}m"), @@ -26,7 +28,7 @@ impl ITerminalColor for AnsiColor { } fn set_bg(&self, bg_color: Color, stdout: &Option<&Arc>) -> Result<()> { - functions::write( + write( stdout, format!( csi!("{}m"), @@ -37,7 +39,7 @@ impl ITerminalColor for AnsiColor { } fn reset(&self, stdout: &Option<&Arc>) -> Result<()> { - functions::write_str(stdout, csi!("0m"))?; + write_str(stdout, csi!("0m"))?; Ok(()) } diff --git a/src/modules/style/color.rs b/crossterm_style/src/color.rs similarity index 58% rename from src/modules/style/color.rs rename to crossterm_style/src/color.rs index 8aadaa9..7eeb266 100644 --- a/src/modules/style/color.rs +++ b/crossterm_style/src/color.rs @@ -1,32 +1,32 @@ //! A module that contains all the actions related to the styling of the terminal. //! Like applying attributes to font and changing the foreground and background. -use super::*; use std::io; -use Screen; -/// Struct that stores a platform-specific implementation for color related actions. +use super::*; +use crate::{Color, ITerminalColor}; +use crossterm_utils::{Result, TerminalOutput}; + +#[cfg(windows)] +use crossterm_utils::get_module; + +use std::sync::Arc; + +/// Allows you to style the terminal. /// -/// For styling text use the `::crossterm::style()` function. `TerminalColor` will set the colors of the screen permanently and the `style()` will only style the text given. +/// # Features: /// -/// Check `/examples/color` in the library for more specific examples. +/// - 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. /// -/// ```rust -/// use crossterm::style::color; +/// # Remarks /// -/// let colored_terminal = color(); -/// -/// // set foreground color -/// colored_terminal.set_fg(Color::Red); -/// // set background color -/// colored_terminal.set_bg(Color::Red); -/// // reset color to default -/// colored_terminal.reset(); -/// ``` -/// -/// When you want to use 'color' on 'alternate screen' use the `Screen` type instead and pass it to the `color::from_screen()` function. -/// By doing that styling actions will be performed on the alternate screen. +/// When you want to 'style' on 'alternate screen' use the 'crossterm_screen' crate. pub struct TerminalColor<'stdout> { color: Box, stdout: Option<&'stdout Arc>, @@ -36,11 +36,11 @@ impl<'stdout> TerminalColor<'stdout> { /// Create new instance whereon color related actions can be performed. pub fn new() -> TerminalColor<'stdout> { #[cfg(target_os = "windows")] - let color = functions::get_module::>( + let color = get_module::>( Box::from(WinApiColor::new()), Box::from(AnsiColor::new()), ) - .unwrap(); + .expect("could not extract module"); #[cfg(not(target_os = "windows"))] let color = Box::from(AnsiColor::new()) as Box; @@ -53,11 +53,13 @@ impl<'stdout> TerminalColor<'stdout> { /// Create a new instance of `TerminalColor` whereon coloring could be preformed on the given output. /// - /// **Note** + /// # Remarks /// /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode'. - /// And you want your actions from the `TerminalColor`, created by this function, to operate on the 'alternate screen'. + /// This could be useful when you have a screen which is in 'alternate mode', + /// and you want your actions from the `TerminalColor`, created by this function, to operate on the 'alternate screen'. + /// + /// You should checkout the 'crossterm_screen' crate for more information about this. /// /// # Example /// ``` @@ -69,7 +71,7 @@ impl<'stdout> TerminalColor<'stdout> { /// ``` pub fn from_output(stdout: &'stdout Arc) -> TerminalColor<'stdout> { #[cfg(target_os = "windows")] - let color = functions::get_module::>( + let color = get_module::>( Box::from(WinApiColor::new()), Box::from(AnsiColor::new()), ) @@ -85,39 +87,16 @@ impl<'stdout> TerminalColor<'stdout> { } /// Set the foreground color to the given color. - /// - /// ```rust - /// let colored_terminal = color(); - /// - /// // Set foreground color of the font - /// colored_terminal.set_fg(Color::Red); - /// // crossterm provides to set the background from &str or String - /// colored_terminal.set_fg(Color::from("Red")); - /// ``` pub fn set_fg(&self, color: Color) -> Result<()> { self.color.set_fg(color, &self.stdout) } /// Set the background color to the given color. - /// - /// ```rust - /// let colored_terminal = color(); - /// - /// // Set background color of the font - /// colored_terminal.set_bg(Color::Red); - /// // crossterm provides to set the background from &str or String - /// colored_terminal.set_bg(Color::from("Red")); - /// ``` pub fn set_bg(&self, color: Color) -> Result<()> { self.color.set_bg(color, &self.stdout) } /// Reset the terminal colors and attributes to default. - /// - /// ```rust - /// let colored_terminal = color(); - /// colored_terminal.reset(); - /// ``` pub fn reset(&self) -> Result<()> { self.color.reset(&self.stdout) } @@ -143,9 +122,3 @@ impl<'stdout> TerminalColor<'stdout> { pub fn color<'stdout>() -> TerminalColor<'stdout> { TerminalColor::new() } - -/// Get a `TerminalColor` instance whereon color related actions can be performed. -/// Pass the reference to any `Screen` you want this type to perform actions on. -pub fn from_screen(screen: &Screen) -> TerminalColor { - TerminalColor::from_output(&screen.stdout) -} diff --git a/src/modules/style/mod.rs b/crossterm_style/src/lib.rs similarity index 89% rename from src/modules/style/mod.rs rename to crossterm_style/src/lib.rs index 6b4fdd3..eeadd9a 100644 --- a/src/modules/style/mod.rs +++ b/crossterm_style/src/lib.rs @@ -1,7 +1,12 @@ //! A module that contains all the actions related to the styling of the terminal. //! Like applying attributes to font and changing the foreground and background. -pub mod color; +#[macro_use] +extern crate crossterm_utils; +#[cfg(target_os = "windows")] +extern crate crossterm_winapi; + +mod color; pub mod objectstyle; pub mod styledobject; @@ -18,13 +23,11 @@ use std::fmt::Display; use std::str::FromStr; use std::sync::Arc; -pub use self::color::{color, from_screen, TerminalColor}; +pub use self::color::{color, TerminalColor}; pub use self::objectstyle::ObjectStyle; pub use self::styledobject::DisplayableObject; pub use self::styledobject::StyledObject; -use common::{error::Result, functions}; - -use TerminalOutput; +use crossterm_utils::{Result, TerminalOutput}; /// This trait defines the actions that can be preformed with terminal color. /// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill @@ -45,16 +48,12 @@ trait ITerminalColor { fn color_value(&self, color: Color, color_type: ColorType) -> String; } -/// This could be used to style an `Displayable` type with colors and attributes. +/// This could be used to style a type who is implementing `Display` with colors and attributes. /// +/// # Example /// ```rust -/// extern crate crossterm; -/// use crossterm::Crossterm; -/// -/// let crossterm = Crossterm::new(); -/// /// // get an styled object which could be painted to the terminal. -/// let styled_object = crossterm.style("Some Blue colored text on black background") +/// let styled_object = style("Some Blue colored text on black background") /// .with(Color::Blue) /// .on(Color::Black); /// @@ -123,8 +122,15 @@ pub enum Color { Grey, White, - - Rgb { r: u8, g: u8, b: u8 }, + /// Color representing RGB-colors; + /// r = red + /// g = green + /// b = blue + Rgb { + r: u8, + g: u8, + b: u8, + }, AnsiValue(u8), } diff --git a/src/modules/style/objectstyle.rs b/crossterm_style/src/objectstyle.rs similarity index 100% rename from src/modules/style/objectstyle.rs rename to crossterm_style/src/objectstyle.rs diff --git a/src/modules/style/styledobject.rs b/crossterm_style/src/styledobject.rs similarity index 75% rename from src/modules/style/styledobject.rs rename to crossterm_style/src/styledobject.rs index 5d0ae92..92ea03b 100644 --- a/src/modules/style/styledobject.rs +++ b/crossterm_style/src/styledobject.rs @@ -1,12 +1,12 @@ //! This module contains the logic to style an object that contains some 'content' which can be styled. -use super::{color, from_screen, Color, ObjectStyle}; -use Screen; - -use common::error::Result; +use super::{color, Color, ObjectStyle}; +//use Screen; +use crossterm_utils::{Result, TerminalOutput}; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::result; +use std::sync::Arc; use super::Attribute; @@ -19,21 +19,10 @@ pub struct StyledObject { impl<'a, D: Display + 'a> StyledObject { /// Set the foreground of the styled object to the passed `Color`. /// - /// ```rust - /// use self::crossterm::style::{style,Color}; + /// # Remarks /// - /// // create an styled object with the foreground color red. - /// let styledobject = style("Some colored text").with(Color::Red); - /// // create an styled object with the foreground color blue. - /// let styledobject1 = style("Some colored text").with(Color::Blue); - /// - /// // print the styledobject to see the result - /// println!("{}", styledobject); - /// println!("{}", styledobject1); - /// - /// // print an styled object directly. - /// println!("{}", style("Some colored text").on(Color::Blue)); - /// ``` + /// 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 { self.object_style = self.object_style.fg(foreground_color); self @@ -41,23 +30,10 @@ impl<'a, D: Display + 'a> StyledObject { /// Set the background of the styled object to the passed `Color`. /// - /// #Example + /// # Remarks /// - /// ```rust - /// use self::crossterm::style::{style,Color}; - /// - /// // create an styled object with the background color red. - /// let styledobject = style("Some colored text").on(Color::Red); - /// // create an styled object with the foreground color blue. - /// let styledobject1 = style("Some colored text").on(Color::Blue); - /// - /// // print the styledobject to see the result - /// println!("{}", styledobject); - /// println!("{}", styledobject1); - /// - /// // print an styled object directly. - /// println!("{}", style("Some colored text").on(Color::Blue)); - /// ``` + /// 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 { self.object_style = self.object_style.bg(background_color); self @@ -65,14 +41,10 @@ impl<'a, D: Display + 'a> StyledObject { /// Set the attribute of an styled object to the passed `Attribute`. /// - /// #Example + /// # Remarks /// - /// ```rust - /// extern crate crossterm; - /// use self::crossterm::style::{style,Attribute}; - /// - /// println!("{}", style("Some bold text").attr(Attribute::Bold); - /// ``` + /// 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 { self.object_style.add_attr(attr); self @@ -137,50 +109,6 @@ impl<'a, D: Display + 'a> StyledObject { self.attr(Attribute::CrossedOut) } - /// This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting. - /// - /// ``` rust - /// style("Some colored text") - /// .with(Color::Blue) - /// .on(Color::Black) - /// .paint(&screen); - /// ``` - /// - /// You should take not that `StyledObject` implements `Display`. You don't need to call paint unless you are on alternate screen. - /// Checkout `into_displayable()` for more information about this. - pub fn paint(&self, screen: &Screen) -> Result<()> { - let colored_terminal = from_screen(&screen); - let mut reset = true; - - if let Some(bg) = self.object_style.bg_color { - colored_terminal.set_bg(bg)?; - reset = true; - } - - if let Some(fg) = self.object_style.fg_color { - colored_terminal.set_fg(fg)?; - reset = true; - } - - for attr in self.object_style.attrs.iter() { - screen - .stdout - .write_string(format!(csi!("{}m"), *attr as i16))?; - reset = true; - } - - use std::fmt::Write; - let mut content = String::new(); - write!(content, "{}", self.content)?; - screen.stdout.write_string(content)?; - screen.stdout.flush()?; - - if reset { - colored_terminal.reset()?; - } - Ok(()) - } - /// This converts an styled object into an `DisplayableObject` witch implements: `Display` and could be used inside the write function of the standard library. /// /// _StyledObject already implements `Display` right?_ @@ -195,8 +123,48 @@ impl<'a, D: Display + 'a> StyledObject { /// let display_object = styled_object.into_displayable(&screen); /// println!("Colored text: {}. Default color", display_object); /// ``` - pub fn into_displayable(self, screen: &'a Screen) -> DisplayableObject<'a, D> { - DisplayableObject::new(screen, self) + pub fn into_displayable(self, stdout: &'a Arc) -> DisplayableObject<'a, D> { + DisplayableObject::new(stdout, self) + } + + /// This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting. + /// + /// ``` rust + /// style("Some colored text") + /// .with(Color::Blue) + /// .on(Color::Black) + /// .paint(&screen); + /// ``` + /// + /// You should take not that `StyledObject` implements `Display`. You don't need to call paint unless you are on alternate screen. + /// Checkout `into_displayable()` for more information about this. + pub fn paint(&self, stdout: &Arc) -> Result<()> { + let colored_terminal = super::TerminalColor::from_output(stdout); + + let mut reset = true; + + if let Some(bg) = self.object_style.bg_color { + colored_terminal.set_bg(bg)?; + reset = true; + } + + if let Some(fg) = self.object_style.fg_color { + colored_terminal.set_fg(fg)?; + reset = true; + } + for attr in self.object_style.attrs.iter() { + stdout.write_string(format!(csi!("{}m"), *attr as i16))?; + reset = true; + } + use std::fmt::Write; + let mut content = String::new(); + write!(content, "{}", self.content)?; + stdout.write_string(content)?; + stdout.flush()?; + if reset { + colored_terminal.reset()?; + } + Ok(()) } } @@ -226,6 +194,7 @@ impl Display for StyledObject { colored_terminal.reset().unwrap(); std::io::stdout().flush().unwrap(); } + Ok(()) } } @@ -238,13 +207,16 @@ impl Display for StyledObject { /// ``` pub struct DisplayableObject<'a, D: Display + 'a> { styled_object: StyledObject, - screen: &'a Screen, + output: &'a Arc, } impl<'a, D: Display + 'a> DisplayableObject<'a, D> { - pub fn new(screen: &'a Screen, styled_object: StyledObject) -> DisplayableObject<'a, D> { + pub fn new( + screen: &'a Arc, + styled_object: StyledObject, + ) -> DisplayableObject<'a, D> { DisplayableObject { - screen, + output: screen, styled_object, } } @@ -252,7 +224,7 @@ impl<'a, D: Display + 'a> DisplayableObject<'a, D> { impl<'a, D: Display + 'a> Display for DisplayableObject<'a, D> { fn fmt(&self, _f: &mut Formatter) -> result::Result<(), fmt::Error> { - self.styled_object.paint(&self.screen).unwrap(); + self.styled_object.paint(self.output).unwrap(); Ok(()) } } diff --git a/src/modules/style/winapi_color.rs b/crossterm_style/src/winapi_color.rs similarity index 97% rename from src/modules/style/winapi_color.rs rename to crossterm_style/src/winapi_color.rs index 3eb458a..976daf3 100644 --- a/src/modules/style/winapi_color.rs +++ b/crossterm_style/src/winapi_color.rs @@ -1,10 +1,11 @@ //! This is an `WinApi` specific implementation for styling related action. //! This module is used for non supporting `ANSI` Windows terminals. -use super::*; -use common::error::Result; -use kernel::windows_kernel::{Console, Handle, HandleType, ScreenBuffer}; +use crate::{Color, ColorType, ITerminalColor}; +use crossterm_utils::{Result, TerminalOutput}; +use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; use std::io; +use std::sync::Arc; use std::sync::{Once, ONCE_INIT}; use winapi::um::wincon; @@ -83,8 +84,6 @@ impl ITerminalColor for WinApiColor { /// This will get the winapi color value from the Color and ColorType struct fn color_value(&self, color: Color, color_type: ColorType) -> String { - use style::{Color, ColorType}; - let winapi_color: u16; let fg_green = wincon::FOREGROUND_GREEN; diff --git a/crossterm_terminal/.gitignore b/crossterm_terminal/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_terminal/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_terminal/Cargo.toml b/crossterm_terminal/Cargo.toml new file mode 100644 index 0000000..1b9d69c --- /dev/null +++ b/crossterm_terminal/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "crossterm_terminal" +version = "0.1.0" +authors = ["T. Post"] +description = "A cross-platform library for doing terminal related actions." +repository = "https://github.com/TimonPost/crossterm" +documentation = "https://docs.rs/crossterm_terminal/" +license = "MIT" +keywords = ["terminal", "clear", "crossplatform", "crossterm", "terminal size"] +exclude = ["target", "Cargo.lock"] +readme = "README.md" +edition = "2018" + +[target.'cfg(windows)'.dependencies] +crossterm_winapi = { path = "../crossterm_winapi" } + +[target.'cfg(unix)'.dependencies] +libc = "0.2.43" + +[dependencies] +crossterm_utils = { path = "../crossterm_utils" } +crossterm_cursor = { path = "../crossterm_cursor" } + +[[example]] +name = "terminal" +path = "examples/terminal.rs" \ No newline at end of file diff --git a/crossterm_terminal/README.md b/crossterm_terminal/README.md new file mode 100644 index 0000000..7cd7768 --- /dev/null +++ b/crossterm_terminal/README.md @@ -0,0 +1,153 @@ +# Crossterm Terminal | cross-platform terminal actions. + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_terminal/badge.svg +[l3]: https://docs.rs/crossterm_terminal/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_terminal?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_terminal.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://doc.rust-lang.org/1.30.0/book/first-edition/conditional-compilation.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 + +This documentation is only for `crossterm_terminal` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. + +Add the `crossterm_terminal` package to your `Cargo.toml` file. + +``` +[dependencies] +`crossterm_terminal` = "0.1" + +``` +And import the `crossterm_terminal` modules you want to use. + +```rust +extern crate crossterm_terminal; + +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 +- Everything is multithreaded (Send, Sync) +- Detailed documentation on every item +- Very few dependenties. +- Terminal + - Clearing (all lines, current line, from cursor down and up, until new line) + - Scrolling (Up, down) + - Get the size of the terminal + - Set the size of the terminal + - Alternate screen + - Raw screen + - Exit the current process + +## Examples +Check out the [examples](/examples/) for more information about how to use this crate. + +```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.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. + +## Notice + +This library is average stable now, I don't expect it to not to change that much. +If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/crossterm_terminal/examples/terminal.rs b/crossterm_terminal/examples/terminal.rs new file mode 100644 index 0000000..68b8668 --- /dev/null +++ b/crossterm_terminal/examples/terminal.rs @@ -0,0 +1,132 @@ +//! +//! Terminal Examples +//! +extern crate crossterm_cursor; +extern crate crossterm_terminal; + +use crossterm_cursor::cursor; +use crossterm_terminal::{terminal, ClearType}; + +fn print_test_data() { + for i in 0..100 { + println!("Test data to test terminal: {}", i); + } +} + +/// Clear all lines in terminal | demonstration +pub fn clear_all_lines() { + let terminal = terminal(); + + print_test_data(); + + // Clear all lines in terminal; + terminal.clear(ClearType::All); +} + +/// Clear all lines from cursor position X:4, Y:4 down | demonstration +pub fn clear_from_cursor_down() { + let terminal = terminal(); + + print_test_data(); + + // Set terminal cursor position (see example for more info). + cursor().goto(4, 8); + + // Clear all cells from current cursor position down. + terminal.clear(ClearType::FromCursorDown); +} + +/// Clear all lines from cursor position X:4, Y:4 up | demonstration +pub fn clear_from_cursor_up() { + let terminal = terminal(); + + print_test_data(); + + // Set terminal cursor position (see example for more info). + cursor().goto(4, 4); + + // Clear all cells from current cursor position down. + terminal.clear(ClearType::FromCursorUp); +} + +/// Clear all lines from cursor position X:4, Y:4 up | demonstration +pub fn clear_current_line() { + let terminal = terminal(); + + print_test_data(); + + // Set terminal cursor position (see example for more info). + cursor().goto(4, 3); + + // Clear current line cells. + terminal.clear(ClearType::CurrentLine); +} + +/// Clear all lines from cursor position X:4, Y:7 up | demonstration +pub fn clear_until_new_line() { + let terminal = terminal(); + + print_test_data(); + + // Set terminal cursor position (see example for more info). + cursor().goto(4, 20); + + // Clear all the cells until next line. + terminal.clear(ClearType::UntilNewLine); +} + +/// Print the the current terminal size | demonstration. +pub fn print_terminal_size() { + let terminal = terminal(); + + // Get terminal size + let (width, height) = terminal.terminal_size(); + + // Print results + print!("X: {}, y: {}", width, height); +} + +/// Set the terminal size to width 10, height: 10 | demonstration. +pub fn set_terminal_size() { + let terminal = terminal(); + + terminal.set_size(10, 10); +} + +/// Scroll down 10 lines | demonstration. +pub fn scroll_down() { + let terminal = terminal(); + + print_test_data(); + + // Scroll down 10 lines. + terminal.scroll_down(10); +} + +/// Scroll down 10 lines | demonstration. +pub fn scroll_up() { + let terminal = terminal(); + + print_test_data(); + + // Scroll up 10 lines. + terminal.scroll_up(5); +} + +/// Resize the terminal to X: 10, Y: 10 | demonstration. +pub fn resize_terminal() { + let terminal = terminal(); + + // Get terminal size + terminal.set_size(10, 10); +} + +/// exit the current proccess. +pub fn exit() { + let terminal = terminal(); + terminal.exit(); +} + +fn main() { + resize_terminal() +} diff --git a/crossterm_terminal/src/lib.rs b/crossterm_terminal/src/lib.rs new file mode 100644 index 0000000..f098076 --- /dev/null +++ b/crossterm_terminal/src/lib.rs @@ -0,0 +1,14 @@ +#[macro_use] +extern crate crossterm_utils; +extern crate crossterm_cursor; + +#[cfg(windows)] +extern crate crossterm_winapi; + +#[cfg(unix)] +extern crate libc; + +mod sys; +mod terminal; + +pub use self::terminal::{terminal, ClearType, Terminal}; diff --git a/crossterm_terminal/src/sys/mod.rs b/crossterm_terminal/src/sys/mod.rs new file mode 100644 index 0000000..2d71118 --- /dev/null +++ b/crossterm_terminal/src/sys/mod.rs @@ -0,0 +1,10 @@ +#[cfg(windows)] +pub mod winapi; + +#[cfg(unix)] +pub mod unix; + +#[cfg(unix)] +pub use self::unix::{exit, get_terminal_size}; +#[cfg(windows)] +pub use self::winapi::{exit, get_terminal_size}; diff --git a/crossterm_terminal/src/sys/unix.rs b/crossterm_terminal/src/sys/unix.rs new file mode 100644 index 0000000..cf96735 --- /dev/null +++ b/crossterm_terminal/src/sys/unix.rs @@ -0,0 +1,37 @@ +use libc::{c_ushort, ioctl, STDOUT_FILENO, TIOCGWINSZ}; + +pub fn exit() { + ::std::process::exit(0); +} + +/// A representation of the size of the current terminal. +#[repr(C)] +#[derive(Debug)] +pub struct UnixSize { + /// number of rows + pub rows: c_ushort, + /// number of columns + pub cols: c_ushort, + pub ws_xpixel: c_ushort, + pub ws_ypixel: c_ushort, +} + +/// Get the current terminal size. +pub fn get_terminal_size() -> (u16, u16) { + // http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc + let us = UnixSize { + rows: 0, + cols: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) }; + + if r == 0 { + // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results. + (us.cols, us.rows) + } else { + (0, 0) + } +} diff --git a/crossterm_terminal/src/sys/winapi.rs b/crossterm_terminal/src/sys/winapi.rs new file mode 100644 index 0000000..8c513e4 --- /dev/null +++ b/crossterm_terminal/src/sys/winapi.rs @@ -0,0 +1,16 @@ +use crossterm_winapi::ScreenBuffer; + +/// Exit the current process. +pub fn exit() { + ::std::process::exit(256); +} + +#[cfg(windows)] +pub fn get_terminal_size() -> (u16, u16) { + if let Ok(buffer) = ScreenBuffer::current() { + let size = buffer.info().unwrap().terminal_size(); + (size.width as u16, size.height as u16) + } else { + (0, 0) + } +} diff --git a/src/modules/terminal/ansi_terminal.rs b/crossterm_terminal/src/terminal/ansi_terminal.rs similarity index 58% rename from src/modules/terminal/ansi_terminal.rs rename to crossterm_terminal/src/terminal/ansi_terminal.rs index 5151dc3..f79ab6e 100644 --- a/src/modules/terminal/ansi_terminal.rs +++ b/crossterm_terminal/src/terminal/ansi_terminal.rs @@ -1,12 +1,14 @@ //! 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 super::*; -use common::error::Result; +use super::ITerminal; +use crate::{sys::get_terminal_size, ClearType}; +use crossterm_cursor::TerminalCursor; +use crossterm_utils::{write, write_str, Result, TerminalOutput}; +use std::sync::Arc; /// This struct is an ansi escape code implementation for terminal related actions. pub struct AnsiTerminal; -use cursor::TerminalCursor; impl AnsiTerminal { pub fn new() -> AnsiTerminal { @@ -18,36 +20,36 @@ impl ITerminal for AnsiTerminal { fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc>) -> Result<()> { match clear_type { ClearType::All => { - functions::write_str(&stdout, csi!("2J"))?; + write_str(&stdout, csi!("2J"))?; TerminalCursor::new().goto(0, 0)?; } ClearType::FromCursorDown => { - functions::write_str(&stdout, csi!("J"))?; + write_str(&stdout, csi!("J"))?; } ClearType::FromCursorUp => { - functions::write_str(&stdout, csi!("1J"))?; + write_str(&stdout, csi!("1J"))?; } ClearType::CurrentLine => { - functions::write_str(&stdout, csi!("2K"))?; + write_str(&stdout, csi!("2K"))?; } ClearType::UntilNewLine => { - functions::write_str(&stdout, csi!("K"))?; + write_str(&stdout, csi!("K"))?; } }; Ok(()) } fn terminal_size(&self, _stdout: &Option<&Arc>) -> (u16, u16) { - functions::get_terminal_size() + get_terminal_size() } fn scroll_up(&self, count: i16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(&stdout, format!(csi!("{}S"), count))?; + write(&stdout, format!(csi!("{}S"), count))?; Ok(()) } fn scroll_down(&self, count: i16, stdout: &Option<&Arc>) -> Result<()> { - functions::write(&stdout, format!(csi!("{}T"), count))?; + write(&stdout, format!(csi!("{}T"), count))?; Ok(()) } @@ -57,16 +59,7 @@ impl ITerminal for AnsiTerminal { height: i16, stdout: &Option<&Arc>, ) -> Result<()> { - functions::write(&stdout, format!(csi!("8;{};{}t"), height, width))?; + write(&stdout, format!(csi!("8;{};{}t"), height, width))?; Ok(()) } - - fn exit(&self, stdout: &Option<&Arc>) { - if let Some(output) = stdout { - // drop the screen with the current stdout. This will make sure when in raw mode this will be disabled first. - let screen = Screen::from(output.to_owned().clone()); - drop(screen); - functions::exit_terminal(); - } - } } diff --git a/src/modules/terminal/mod.rs b/crossterm_terminal/src/terminal/mod.rs similarity index 73% rename from src/modules/terminal/mod.rs rename to crossterm_terminal/src/terminal/mod.rs index 388cc2d..1cf8f3e 100644 --- a/src/modules/terminal/mod.rs +++ b/crossterm_terminal/src/terminal/mod.rs @@ -1,5 +1,4 @@ //! A module that contains all the actions related to the terminal. like clearing, resizing, pausing and scrolling the terminal. - #[cfg(test)] mod test; @@ -13,18 +12,23 @@ use self::ansi_terminal::AnsiTerminal; #[cfg(target_os = "windows")] use self::winapi_terminal::WinApiTerminal; -pub use self::terminal::{from_screen, terminal, Terminal}; +pub use self::terminal::{terminal, Terminal}; + +use crossterm_utils::{Result, TerminalOutput}; -use common::{error, functions}; use std::sync::Arc; -use {Screen, TerminalOutput}; /// Enum that specifies a way of clearing. 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, } @@ -38,24 +42,18 @@ pub enum ClearType { /// so that terminal related actions can be preformed on both Unix and Windows systems. trait ITerminal { /// Clear the current cursor by specifying the clear type - fn clear( - &self, - clear_type: ClearType, - stdout: &Option<&Arc>, - ) -> error::Result<()>; + fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc>) -> Result<()>; /// Get the terminal size (x,y) fn terminal_size(&self, stdout: &Option<&Arc>) -> (u16, u16); /// Scroll `n` lines up in the current terminal. - fn scroll_up(&self, count: i16, stdout: &Option<&Arc>) -> error::Result<()>; + fn scroll_up(&self, count: i16, stdout: &Option<&Arc>) -> Result<()>; /// Scroll `n` lines down in the current terminal. - fn scroll_down(&self, count: i16, stdout: &Option<&Arc>) -> error::Result<()>; + fn scroll_down(&self, count: i16, stdout: &Option<&Arc>) -> Result<()>; /// Resize terminal to the given width and height. fn set_size( &self, width: i16, height: i16, stdout: &Option<&Arc>, - ) -> error::Result<()>; - /// Close the current terminal - fn exit(&self, stdout: &Option<&Arc>); + ) -> Result<()>; } diff --git a/src/modules/terminal/terminal.rs b/crossterm_terminal/src/terminal/terminal.rs similarity index 64% rename from src/modules/terminal/terminal.rs rename to crossterm_terminal/src/terminal/terminal.rs index 078d88f..2099ccd 100644 --- a/src/modules/terminal/terminal.rs +++ b/crossterm_terminal/src/terminal/terminal.rs @@ -1,35 +1,44 @@ //! 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 super::*; -use common::error::Result; -use std::fmt; +use super::{AnsiTerminal, ClearType, ITerminal}; +use crossterm_utils::{write, Result, TerminalOutput}; -/// Struct that stores a platform-specific platform implementation for terminal related actions. +#[cfg(windows)] +use super::WinApiTerminal; +#[cfg(windows)] +use crossterm_utils::get_module; + +use std::fmt; +use std::sync::Arc; + +/// Allows you to preform actions on the terminal. /// -/// Check `/examples/terminal` in the library for more specific examples. +/// # Features: /// -/// ```rust -/// use crossterm::terminal; +/// - Clearing (all lines, current line, from cursor down and up, until new line) +/// - Scrolling (Up, down) +/// - Get the size of the terminal +/// - Set the size of the terminal +/// - Alternate screen +/// - Raw screen +/// - Exit the current process /// -/// let term = terminal(); +/// Check `/examples/` in the library for more specific examples. /// -/// term.scroll_down(5); -/// term.scroll_up(4); -/// let (with, height) = term.terminal_size(); -/// ``` -/// When you want to use 'terminal' related actions on 'alternate screen' use the `Screen` type instead, and pass it to the `terminal::from_screen()` function. -/// By doing that terminal actions will be performed on the alternate screen. +/// # Remarks +/// +/// When you want to perform terminal actions on 'alternate screen' use the 'crossterm_screen' crate. pub struct Terminal<'stdout> { terminal: Box, - screen: Option<&'stdout Arc>, + stdout: Option<&'stdout Arc>, } impl<'stdout> Terminal<'stdout> { /// Create new terminal instance whereon terminal related actions can be performed. pub fn new() -> Terminal<'stdout> { #[cfg(target_os = "windows")] - let terminal = functions::get_module::>( + let terminal = get_module::>( Box::new(WinApiTerminal::new()), Box::new(AnsiTerminal::new()), ) @@ -40,17 +49,19 @@ impl<'stdout> Terminal<'stdout> { Terminal { terminal, - screen: None, + stdout: None, } } /// Create a new instance of `Terminal` whereon terminal related actions could be preformed on the given output. /// - /// **Note** + /// # Remarks /// /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode'. - /// And you want your actions from the `Terminal`, created by this function, to operate on the 'alternate screen'. + /// This could be useful when you have a screen which is in 'alternate mode', + /// and you want your actions from the `Terminal`, created by this function, to operate on the 'alternate screen'. + /// + /// You should checkout the 'crossterm_screen' crate for more information about this. /// /// # Example /// ``` @@ -62,7 +73,7 @@ impl<'stdout> Terminal<'stdout> { /// ``` pub fn from_output(stdout: &'stdout Arc) -> Terminal<'stdout> { #[cfg(target_os = "windows")] - let terminal = functions::get_module::>( + let terminal = get_module::>( Box::new(WinApiTerminal::new()), Box::new(AnsiTerminal::new()), ) @@ -73,12 +84,13 @@ impl<'stdout> Terminal<'stdout> { Terminal { terminal, - screen: Some(stdout), + stdout: Some(stdout), } } - /// Clear the current cursor by specifying the clear type. + /// Clear the current cursor by specifying the `ClearType`. /// + /// # Example /// ```rust /// let mut term = terminal(); /// @@ -94,43 +106,31 @@ impl<'stdout> Terminal<'stdout> { /// term.clear(terminal::ClearType::UntilNewLine); /// ``` pub fn clear(&self, clear_type: ClearType) -> Result<()> { - self.terminal.clear(clear_type, &self.screen) + self.terminal.clear(clear_type, &self.stdout) } /// Get the terminal size (x,y). /// - /// ```rust - /// let mut term = terminal(); - /// - /// let size = term.terminal_size(); - /// println!("{:?}", size); - /// ``` + /// # Remark + /// This will return a tuple of (x: u16, y: u16) pub fn terminal_size(&self) -> (u16, u16) { - self.terminal.terminal_size(&self.screen) + self.terminal.terminal_size(&self.stdout) } /// Scroll `n` lines up in the current terminal. /// - /// ```rust - /// let mut term = terminal(); - /// - /// // scroll up by 5 lines - /// let size = term.scroll_up(5); - /// ``` + /// # Parameter + /// - `count`: the number of rows should be shifted up. pub fn scroll_up(&self, count: i16) -> Result<()> { - self.terminal.scroll_up(count, &self.screen) + self.terminal.scroll_up(count, &self.stdout) } - /// Scroll `n` lines up in the current terminal. + /// Scroll `n` lines down in the current terminal. /// - /// ```rust - /// let mut term = terminal(); - /// - /// // scroll down by 5 lines - /// let size = term.scroll_down(5); - /// ``` + /// # Parameter + /// - `count`: the number of rows should be shifted down. pub fn scroll_down(&self, count: i16) -> Result<()> { - self.terminal.scroll_down(count, &self.screen) + self.terminal.scroll_down(count, &self.stdout) } /// Set the terminal size. Note that not all terminals can be set to a very small scale. @@ -142,7 +142,7 @@ impl<'stdout> Terminal<'stdout> { /// let size = term.set_size(10,10); /// ``` pub fn set_size(&self, width: i16, height: i16) -> Result<()> { - self.terminal.set_size(width, height, &self.screen) + self.terminal.set_size(width, height, &self.stdout) } /// Exit the current process. @@ -153,7 +153,7 @@ impl<'stdout> Terminal<'stdout> { /// let size = term.exit(); /// ``` pub fn exit(&self) { - self.terminal.exit(&self.screen); + crate::sys::exit(); } /// Write any displayable content to the current terminal screen. @@ -167,7 +167,7 @@ impl<'stdout> Terminal<'stdout> { use std::fmt::Write; let mut string = String::new(); write!(string, "{}", value)?; - let size = functions::write(&self.screen, string)?; + let size = write(&self.stdout, string)?; Ok(size) } } @@ -176,9 +176,3 @@ impl<'stdout> Terminal<'stdout> { pub fn terminal<'stdout>() -> Terminal<'stdout> { Terminal::new() } - -/// Get a `Terminal` instance whereon terminal related actions can be performed. -/// Pass the reference to any `Screen` you want this type to perform actions on. -pub fn from_screen(screen: &Screen) -> Terminal { - Terminal::from_output(&screen.stdout) -} diff --git a/src/modules/terminal/test.rs b/crossterm_terminal/src/terminal/test.rs similarity index 50% rename from src/modules/terminal/test.rs rename to crossterm_terminal/src/terminal/test.rs index 65208f3..5112d36 100644 --- a/src/modules/terminal/test.rs +++ b/crossterm_terminal/src/terminal/test.rs @@ -1,27 +1,20 @@ -use modules::terminal::ansi_terminal::AnsiTerminal; - -use modules::terminal::ITerminal; - -use Screen; +use super::{AnsiTerminal, ITerminal, WinApiTerminal}; /* ======================== WinApi =========================== */ #[cfg(windows)] mod winapi_tests { use super::*; - use modules::terminal::winapi_terminal::WinApiTerminal; #[test] fn resize_winapi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let terminal = WinApiTerminal::new(); - terminal.set_size(20, 10, &stdout); + terminal.set_size(30, 30, &None); - let (x, y) = terminal.terminal_size(&stdout); + let (x, y) = terminal.terminal_size(&None); - assert_eq!(x, 20); - assert_eq!(y, 10); + assert_eq!(x, 30); + assert_eq!(y, 30); } } @@ -30,16 +23,14 @@ mod winapi_tests { fn resize_ansi() { use std::{thread, time}; if try_enable_ansi() { - let screen = Screen::default(); - let stdout = Some(&screen.stdout); let terminal = AnsiTerminal::new(); - terminal.set_size(50, 50, &stdout); + terminal.set_size(50, 50, &None).unwrap(); // see issue: https://github.com/eminence/terminal-size/issues/11 thread::sleep(time::Duration::from_millis(30)); - let (x, y) = terminal.terminal_size(&stdout); + let (x, y) = terminal.terminal_size(&None); assert_eq!(x, 50); assert_eq!(y, 50); @@ -50,10 +41,12 @@ fn try_enable_ansi() -> bool { #[cfg(windows)] { if cfg!(target_os = "windows") { - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + use crossterm_utils::sys::winapi::ansi::set_virtual_terminal_processing; - if !try_enable_ansi_support() { - return false; + // if it is not listed we should try with WinApi to check if we do support ANSI-codes. + match set_virtual_terminal_processing(true) { + Ok(_) => return true, + Err(e) => return false, } } } diff --git a/src/modules/terminal/winapi_terminal.rs b/crossterm_terminal/src/terminal/winapi_terminal.rs similarity index 94% rename from src/modules/terminal/winapi_terminal.rs rename to crossterm_terminal/src/terminal/winapi_terminal.rs index 81af720..8c07242 100644 --- a/src/modules/terminal/winapi_terminal.rs +++ b/crossterm_terminal/src/terminal/winapi_terminal.rs @@ -4,8 +4,9 @@ //! Windows versions lower then windows 10 are not supporting ANSI codes. Those versions will use this implementation instead. use super::*; -use common::error::{ErrorKind, Result}; -use kernel::windows_kernel::{Console, Coord, Cursor, Handle, ScreenBuffer, Size}; +use crossterm_cursor::sys::winapi::Cursor; +use crossterm_utils::{ErrorKind, Result, TerminalOutput}; +use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; /// This struct is an winapi implementation for terminal related actions. pub struct WinApiTerminal; @@ -166,15 +167,6 @@ impl ITerminal for WinApiTerminal { Ok(()) } - - fn exit(&self, stdout: &Option<&Arc>) { - if let Some(output) = stdout { - // drop the screen with the current stdout. This will make sure when in raw mode this will be disabled first. - let mut screen = Screen::from(output.to_owned().clone()); - drop(screen); - functions::exit_terminal(); - } - } } pub fn clear_after_cursor( diff --git a/crossterm_utils/.gitignore b/crossterm_utils/.gitignore new file mode 100644 index 0000000..53eaa21 --- /dev/null +++ b/crossterm_utils/.gitignore @@ -0,0 +1,2 @@ +/target +**/*.rs.bk diff --git a/crossterm_utils/Cargo.toml b/crossterm_utils/Cargo.toml new file mode 100644 index 0000000..cbd1a12 --- /dev/null +++ b/crossterm_utils/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "crossterm_utils" +version = "0.1.0" +authors = ["Timon Post "] +edition = "2018" + +[target.'cfg(windows)'.dependencies] +crossterm_winapi = { path = "../crossterm_winapi" } +winapi = { version = "0.3.5", features = ["wincon"] } + +[target.'cfg(unix)'.dependencies] +libc = "0.2.43" +termios = "0.3.1" \ No newline at end of file diff --git a/crossterm_utils/README.md b/crossterm_utils/README.md new file mode 100644 index 0000000..baedd7f --- /dev/null +++ b/crossterm_utils/README.md @@ -0,0 +1,40 @@ +# Crossterm Utils | crossterm common used code. + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s3]: https://docs.rs/crossterm_utils/badge.svg +[l3]: https://docs.rs/crossterm_utils/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_utils?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_utils.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. + +## Contributing + +I highly appreciate it when you are contributing to this crate. +Also Since my native language is not English my grammar and sentence order will not be perfect. +So improving this by correcting these mistakes will help both me and the reader of the docs. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details diff --git a/src/common/commands/mod.rs b/crossterm_utils/src/commands/mod.rs similarity index 77% rename from src/common/commands/mod.rs rename to crossterm_utils/src/commands/mod.rs index ac029b1..fd8e418 100644 --- a/src/common/commands/mod.rs +++ b/crossterm_utils/src/commands/mod.rs @@ -1,12 +1,12 @@ //! This module contains some commands that could be executed for a specific task. A `Command` is just a little wrapper. -use super::super::output::TerminalOutput; +use crate::output::TerminalOutput; use std::io; -pub mod shared_commands; - -#[cfg(not(target_os = "windows"))] -pub mod unix_command; +//pub mod shared_commands; +// +//#[cfg(not(target_os = "windows"))] +//pub mod unix_command; #[cfg(target_os = "windows")] pub mod win_commands; @@ -17,11 +17,6 @@ pub trait IStateCommand { fn undo(&mut self) -> io::Result<()>; } -pub trait IEnableAnsiCommand { - fn enable(&self) -> io::Result; - fn disable(&self) -> io::Result<()>; -} - // This trait provides an interface for switching to alternate screen and back. pub trait IAlternateScreenCommand: Sync + Send { fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>; diff --git a/src/common/commands/win_commands.rs b/crossterm_utils/src/commands/win_commands.rs similarity index 55% rename from src/common/commands/win_commands.rs rename to crossterm_utils/src/commands/win_commands.rs index 25b3208..17949e8 100644 --- a/src/common/commands/win_commands.rs +++ b/crossterm_utils/src/commands/win_commands.rs @@ -1,63 +1,13 @@ //! A module which contains the commands that can be used for windows systems. -use super::{IAlternateScreenCommand, IEnableAnsiCommand, TerminalOutput}; +use super::{IAlternateScreenCommand, TerminalOutput}; -use kernel::windows_kernel::{ansi_support, ConsoleMode, Handle, ScreenBuffer}; +use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; use winapi::shared::minwindef::DWORD; use winapi::um::wincon; -use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; use std::io::Result; -/// This command is used for enabling and disabling ANSI code support for windows systems, -/// For more info check: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences. -#[derive(Clone, Copy)] -pub struct EnableAnsiCommand { - mask: DWORD, -} - -impl EnableAnsiCommand { - pub fn new() -> EnableAnsiCommand { - let command = EnableAnsiCommand { - mask: ENABLE_VIRTUAL_TERMINAL_PROCESSING, - }; - command - } -} - -impl IEnableAnsiCommand for EnableAnsiCommand { - fn enable(&self) -> Result { - // we need to check whether we tried to enable ansi before. If we have we can just return if that had succeeded. - if ansi_support::has_been_tried_to_enable_ansi() && ansi_support::ansi_enabled() { - return Ok(ansi_support::windows_supportable()); - } else { - let console_mode = ConsoleMode::new()?; - - let mut dw_mode = console_mode.mode()?; - - dw_mode |= self.mask; - - console_mode.set_mode(dw_mode)?; - Ok(true) - } - } - - fn disable(&self) -> Result<()> { - if ansi_support::ansi_enabled() { - let console_mode = ConsoleMode::new()?; - - let mut dw_mode = console_mode.mode()?; - - dw_mode &= !self.mask; - - console_mode.set_mode(dw_mode)?; - - ansi_support::set_ansi_enabled(false); - } - Ok(()) - } -} - /// 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)] diff --git a/src/common/error.rs b/crossterm_utils/src/error.rs similarity index 100% rename from src/common/error.rs rename to crossterm_utils/src/error.rs diff --git a/src/common/functions.rs b/crossterm_utils/src/functions.rs similarity index 54% rename from src/common/functions.rs rename to crossterm_utils/src/functions.rs index bba74f5..cb63b6b 100644 --- a/src/common/functions.rs +++ b/crossterm_utils/src/functions.rs @@ -1,87 +1,35 @@ -//! Some actions need to preformed platform independently since they can not be solved `ANSI escape codes`. - -use super::TerminalOutput; +use crate::output::TerminalOutput; use std::io::{self, Write}; use std::sync::Arc; #[cfg(windows)] -use kernel::windows_kernel::ansi_support::{try_enable_ansi_support, windows_supportable}; - -#[cfg(windows)] -use kernel::windows_kernel::exit; -#[cfg(windows)] -use kernel::windows_kernel::ScreenBuffer; - -#[cfg(windows)] -use kernel::windows_kernel::Cursor; - -#[cfg(unix)] -use kernel::unix_kernel::terminal::{exit, pos, terminal_size}; - -/// Get the terminal size based on the current platform. -#[cfg(unix)] -pub fn get_terminal_size() -> (u16, u16) { - terminal_size() -} - -#[cfg(windows)] -pub fn get_terminal_size() -> (u16, u16) { - if let Ok(buffer) = ScreenBuffer::current() { - let size = buffer.info().unwrap().terminal_size(); - (size.width as u16, size.height as u16) - } else { - (0, 0) - } -} - -/// Get the cursor position based on the current platform. -#[cfg(unix)] -pub fn get_cursor_position() -> (u16, u16) { - if let Ok(pos) = pos() { - pos - } else { - (0, 0) - } -} - -#[cfg(windows)] -pub fn get_cursor_position() -> (u16, u16) { - if let Ok(cursor) = Cursor::new() { - cursor.position().unwrap().into() - } else { - (0, 0) - } -} - -/// exit the current terminal. -pub fn exit_terminal() { - exit(); -} +use crate::sys::winapi::ansi::set_virtual_terminal_processing; #[cfg(windows)] /// Get an module specific implementation of a the generic given type based on the current platform. /// If the current platform is windows and it supports ansi escape codes it will return the ansi implementation and if not it will return the winapi implementation. /// If the current platform is unix it will return the ansi implementation. -pub fn get_module(winapi_impl: T, unix_impl: T) -> Option { - let mut term: Option = None; - let mut does_support = true; +pub fn get_module(winapi_impl: T, ansi_impl: T) -> Option { + // Some terminals on windows like GitBash can't use WinaApi calls directly so when we try to enable the ANSI-flag for windows this won't work. + // Because of that we should check first if the TERM-variable is set and see if the current terminal is a terminal who does support ANSI. + let supports_ansi = is_specific_term(); - if !windows_supportable() { - // Try to enable ansi on windows if not than use WINAPI. - does_support = try_enable_ansi_support(); - // - // uncomment this line when you want to use the winapi implementation. - // does_support = false; - if !does_support { - term = Some(winapi_impl); + match supports_ansi { + true => { + return Some(ansi_impl); + } + false => { + // if it is not listed we should try with WinApi to check if we do support ANSI-codes. + match set_virtual_terminal_processing(true) { + Ok(_) => { + return Some(ansi_impl); + } + Err(_) => { + return Some(winapi_impl); + } + } } } - - if does_support { - term = Some(unix_impl); - } - - term } /// This function is used by 'ANSI' modules. Those modules are using an `Option` of `TerminalOutput`. @@ -120,3 +68,27 @@ pub fn write_str(stdout: &Option<&Arc>, string: &str) -> io::Res Some(output) => output.write_str(string), } } + +// checks if the 'TERM' environment variable is set to check if the terminal supports ANSI-codes. +// I got the list of terminals from here: https://github.com/keqingrong/supports-ansi/blob/master/index.js +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, + } +} diff --git a/crossterm_utils/src/lib.rs b/crossterm_utils/src/lib.rs new file mode 100644 index 0000000..3e6dc17 --- /dev/null +++ b/crossterm_utils/src/lib.rs @@ -0,0 +1,22 @@ +#[cfg(windows)] +extern crate crossterm_winapi; +#[cfg(windows)] +extern crate winapi; + +#[cfg(unix)] +extern crate termios; + +pub mod commands; +pub mod error; +pub mod macros; +pub mod sys; + +mod functions; +mod output; + +pub use self::error::{ErrorKind, Result}; +pub use self::output::TerminalOutput; + +#[cfg(windows)] +pub use self::functions::get_module; +pub use self::functions::{write, write_str}; diff --git a/crossterm_utils/src/macros.rs b/crossterm_utils/src/macros.rs new file mode 100644 index 0000000..fb12b8d --- /dev/null +++ b/crossterm_utils/src/macros.rs @@ -0,0 +1,4 @@ +#[macro_export] +macro_rules! csi { + ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; +} diff --git a/src/modules/output/ansi_output.rs b/crossterm_utils/src/output/ansi_output.rs similarity index 100% rename from src/modules/output/ansi_output.rs rename to crossterm_utils/src/output/ansi_output.rs diff --git a/src/modules/output/mod.rs b/crossterm_utils/src/output/mod.rs similarity index 97% rename from src/modules/output/mod.rs rename to crossterm_utils/src/output/mod.rs index e4b2b4f..0fb42ea 100644 --- a/src/modules/output/mod.rs +++ b/crossterm_utils/src/output/mod.rs @@ -1,5 +1,4 @@ //! A module that provides a uniformed way to write to the output no matter if it is in main, alternate or raw mode. - mod output; #[cfg(test)] @@ -15,7 +14,6 @@ use self::winapi_output::WinApiOutput; pub use self::output::TerminalOutput; -use super::functions; use std::io; /// This trait defines represents an stdout of an screen. diff --git a/src/modules/output/output.rs b/crossterm_utils/src/output/output.rs similarity index 68% rename from src/modules/output/output.rs rename to crossterm_utils/src/output/output.rs index 9cfcabf..59db256 100644 --- a/src/modules/output/output.rs +++ b/crossterm_utils/src/output/output.rs @@ -1,31 +1,26 @@ //! This module provides one place to work with the screen. //! -//! In Rust we can call `stdout()` to get a handle to the current default console handle. -//! When working with UNIX or Windows10 systems you could print some text to the screen by doing the following: +//! In Rust we can call `stdout()` to get a handle to the current default console handle. +//! However, we can't use `stdout()` to access the alternate screen handle therefore we also won't be able to use `print!(), println!(), or write!()`. +//! The same goes for coloring, cursor movement, input, and terminal actions. +//! All of those functions are writing to the standard output and not to our alternate screen we are currently on. //! -//! ``` -//! write!(std::io::stdout(), "{}", "some text"). -//! ``` +//! To get the handle to the `alternate screen` we first need to store this handle so that we are able to call it later on. +//! Through this stored handle, crossterm can write to or execute commands at the current screen whether it be an alternate screen or main screen. //! -//! But things change when we are in alternate screen modes. -//! We can not simply use `stdout()` to get a handle to the 'alternate screen', since this call returns the current default console handle (main screen). -//! -//! To get the handle to the `alternate screen` we first need to store this handle so that we are able to call it later on. -//! Through this stored handle, crossterm can write to or execute commands at the current screen whether it be an alternate screen or main screen. -//! -//! For UNIX and Windows10 systems, we store the handle gotten from `stdout()`. For Windows systems who are not supporting ANSI escape codes, we can call `CONOUT$` to get the current screen `HANDLE`. +//! For UNIX and Windows10 systems, we store the handle gotten from `stdout()`. For Windows systems who are not supporting ANSI escape codes, we can call `CONOUT$` to get the current screen `HANDLE`. use super::*; - +use crate::functions; use std::default::Default; use std::io::Write; -/// Struct that is a handle to a terminal screen. -/// This handle could be used to write to the current screen +/// Struct that is a handle to the current terminal screen. /// /// For UNIX and Windows 10 `stdout()` will be used as handle. And for Windows systems, not supporting ANSI escape codes, will use WinApi's `HANDLE` as handle. pub struct TerminalOutput { stdout: Box, + /// checks if this output is in raw mode. pub is_in_raw_mode: bool, } diff --git a/crossterm_utils/src/output/sys/mod.rs b/crossterm_utils/src/output/sys/mod.rs new file mode 100644 index 0000000..3a06e02 --- /dev/null +++ b/crossterm_utils/src/output/sys/mod.rs @@ -0,0 +1 @@ +pub mod winapi; \ No newline at end of file diff --git a/crossterm_utils/src/output/sys/winapi.rs b/crossterm_utils/src/output/sys/winapi.rs new file mode 100644 index 0000000..c8819d4 --- /dev/null +++ b/crossterm_utils/src/output/sys/winapi.rs @@ -0,0 +1,13 @@ +//! This module contains the logic to write to the terminal. + +use winapi::ctypes::c_void; +use winapi::shared::ntdef::NULL; +use winapi::um::consoleapi::WriteConsoleW; +use winapi::um::wincon::{WriteConsoleOutputA, CHAR_INFO, COORD, PSMALL_RECT}; +use winapi::um::winnt::HANDLE; + +use crossterm_winapi::{is_true, ScreenBuffer}; + +use std::io::{self, Result}; +use std::str; + diff --git a/src/modules/output/test.rs b/crossterm_utils/src/output/test.rs similarity index 76% rename from src/modules/output/test.rs rename to crossterm_utils/src/output/test.rs index bd53b8c..5e1641d 100644 --- a/src/modules/output/test.rs +++ b/crossterm_utils/src/output/test.rs @@ -1,17 +1,12 @@ -use modules::output::ansi_output::AnsiOutput; - -use modules::output::IStdout; - -use Screen; +use super::{AnsiOutput, IStdout, WinApiOutput}; #[cfg(windows)] mod winapi_tests { use super::*; - use modules::output::winapi_output::WinApiOutput; + /* ======================== WinApi =========================== */ #[test] fn write_winapi() { - let _screen = Screen::default(); let output = WinApiOutput::new(); let bytes = "test".as_bytes(); @@ -21,7 +16,6 @@ mod winapi_tests { #[test] fn write_str_winapi() { - let _screen = Screen::default(); let output = WinApiOutput::new(); let bytes = "test".as_bytes(); @@ -33,7 +27,6 @@ mod winapi_tests { /* ======================== ANSI =========================== */ #[test] fn write_ansi() { - let _screen = Screen::default(); let output = AnsiOutput::new(); let bytes = "test".as_bytes(); @@ -43,7 +36,6 @@ fn write_ansi() { #[test] fn write_str_ansi() { - let _screen = Screen::default(); let output = AnsiOutput::new(); let bytes = "test".as_bytes(); @@ -68,10 +60,12 @@ fn try_enable_ansi() -> bool { #[cfg(windows)] { if cfg!(target_os = "windows") { - use kernel::windows_kernel::ansi_support::try_enable_ansi_support; + use crate::sys::winapi::ansi::set_virtual_terminal_processing; - if !try_enable_ansi_support() { - return false; + // if it is not listed we should try with WinApi to check if we do support ANSI-codes. + match set_virtual_terminal_processing(true) { + Ok(_) => return true, + Err(e) => return false, } } } diff --git a/src/modules/output/winapi_output.rs b/crossterm_utils/src/output/winapi_output.rs similarity index 82% rename from src/modules/output/winapi_output.rs rename to crossterm_utils/src/output/winapi_output.rs index 5316c32..6ef0c95 100644 --- a/src/modules/output/winapi_output.rs +++ b/crossterm_utils/src/output/winapi_output.rs @@ -1,5 +1,5 @@ use super::IStdout; -use kernel::windows_kernel::{writing, Handle}; +use crossterm_winapi::{Console, Handle}; use std::io; @@ -19,7 +19,8 @@ impl IStdout for WinApiOutput { fn write(&self, buf: &[u8]) -> io::Result { let handle = Handle::current_out_handle()?; - writing::write_char_buffer(&handle, buf) + let console = Console::from(handle); + console.write_char_buffer(buf) } fn flush(&self) -> io::Result<()> { diff --git a/crossterm_utils/src/sys/mod.rs b/crossterm_utils/src/sys/mod.rs new file mode 100644 index 0000000..5ea3379 --- /dev/null +++ b/crossterm_utils/src/sys/mod.rs @@ -0,0 +1,5 @@ +#[cfg(windows)] +pub mod winapi; + +#[cfg(unix)] +pub mod unix; diff --git a/crossterm_utils/src/sys/unix.rs b/crossterm_utils/src/sys/unix.rs new file mode 100644 index 0000000..81e0048 --- /dev/null +++ b/crossterm_utils/src/sys/unix.rs @@ -0,0 +1,65 @@ +//! This module contains all `unix` specific terminal related logic. + +use libc::{self, TCSADRAIN}; + +use crate::termios::{tcsetattr, Termios}; +use std::fs; +use std::io; +use std::os::unix::io::{AsRawFd, RawFd}; + +static mut ORIGINAL_TERMINAL_MODE: Option = None; +pub static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false; +pub static mut RAW_MODE_ENABLED_BY_USER: bool = false; + +/// Transform the given mode into an raw mode (non-canonical) mode. +pub fn make_raw(termios: &mut Termios) { + extern "C" { + pub fn cfmakeraw(termptr: *mut Termios); + } + unsafe { cfmakeraw(termios) } +} + +pub fn into_raw_mode() -> io::Result { + let tty_f; + + let fd = unsafe { + if libc::isatty(libc::STDIN_FILENO) == 1 { + libc::STDIN_FILENO + } else { + tty_f = fs::File::open("/dev/tty")?; + tty_f.as_raw_fd() + } + }; + + let mut termios = Termios::from_fd(fd)?; + let original = termios.clone(); + + unsafe { + if ORIGINAL_TERMINAL_MODE.is_none() { + ORIGINAL_TERMINAL_MODE = Some(original.clone()) + } + } + + make_raw(&mut termios); + tcsetattr(fd, TCSADRAIN, &termios)?; + + Ok(fd) +} + +pub fn disable_raw_mode() -> io::Result<()> { + let tty_f; + + let fd = unsafe { + if libc::isatty(libc::STDIN_FILENO) == 1 { + libc::STDIN_FILENO + } else { + tty_f = fs::File::open("/dev/tty")?; + tty_f.as_raw_fd() + } + }; + + if let Some(original) = unsafe { ORIGINAL_TERMINAL_MODE } { + tcsetattr(fd, TCSADRAIN, &original)?; + } + Ok(()) +} diff --git a/crossterm_utils/src/sys/winapi.rs b/crossterm_utils/src/sys/winapi.rs new file mode 100644 index 0000000..b56c030 --- /dev/null +++ b/crossterm_utils/src/sys/winapi.rs @@ -0,0 +1,33 @@ +pub mod ansi { + use crossterm_winapi::ConsoleMode; + use std::io; + use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + /// 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) -> io::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 { + return Ok(()); + } + + console_mode.set_mode(new_mode)?; + Ok(()) + } +} diff --git a/crossterm_winapi/.gitignore b/crossterm_winapi/.gitignore new file mode 100644 index 0000000..b28425c --- /dev/null +++ b/crossterm_winapi/.gitignore @@ -0,0 +1,4 @@ +target/ +.idea/ +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/crossterm_winapi/.travis.yml b/crossterm_winapi/.travis.yml new file mode 100644 index 0000000..d78be24 --- /dev/null +++ b/crossterm_winapi/.travis.yml @@ -0,0 +1,19 @@ +language: rust +rust: +- stable +- nightly + +before_script: +- export PATH=$PATH:/home/travis/.cargo/bin +- rustup component add rustfmt-preview + +os: +- windows + +branches: +only: +- master + +script: +- cargo build +- cargo fmt -- --check diff --git a/crossterm_winapi/Cargo.toml b/crossterm_winapi/Cargo.toml new file mode 100644 index 0000000..7d8c5ca --- /dev/null +++ b/crossterm_winapi/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "crossterm_winapi" +version = "0.1.0" +authors = ["T. Post"] +description = "An WinApi wrapper that provides some basic simple abstractions aground common WinApi calls" +repository = "https://github.com/TimonPost/crossterm_winapi" +documentation = "https://docs.rs/crossterm_winapi/" +license = "MIT" +keywords = ["winapi", "abstractions", "crossterm", "windows", "screen_buffer"] +exclude = ["target", "Cargo.lock"] +readme = "README.md" + +[dependencies] +winapi = { version = "0.3.5", features = ["winbase","consoleapi","processenv", "handleapi"] } \ No newline at end of file diff --git a/crossterm_winapi/README.md b/crossterm_winapi/README.md new file mode 100644 index 0000000..2b28f7d --- /dev/null +++ b/crossterm_winapi/README.md @@ -0,0 +1,67 @@ +# Crossterm Winapi | Common WinApi Abstractions + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + +[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/ + +[s6]: https://tokei.rs/b1/github/TimonPost/crossterm_winapi?category=code +[s7]: https://travis-ci.org/TimonPost/crossterm_winapi.svg?branch=master + +This crate provides some wrappers aground common used WinApi functions. +The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm), +and it is very unstable right because of that some changes could be expected. + +# Features +This crate provides some abstractions over: + +- 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 + +# Example +Here are some examples do demonstrate how to work whit this crate. +Please see [examples](https://github.com/TimonPost/crossterm_winapi) for more +## 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(); +} +``` + + +### Inspiration +I wanted to expose some of the api crossterm uses for WinApi. +1. I thought it would be helpful for other people to, to have a small rust seemable abstraction over the WinApi bindings. +2. I have some future plans for crossterm wherefore I needed to seperate the WinAPi logic out of the currenbt librarie. diff --git a/crossterm_winapi/examples/coloring_example.rs b/crossterm_winapi/examples/coloring_example.rs new file mode 100644 index 0000000..8aa8b72 --- /dev/null +++ b/crossterm_winapi/examples/coloring_example.rs @@ -0,0 +1,51 @@ +extern crate crossterm_winapi; + +use crossterm_winapi::{Console, ScreenBuffer}; + +fn set_background_color() -> std::io::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 mut new_color = fg_color | BLUE_BACKGROUND; + + // set the console text attribute to the new color value. + Console::from(**screen_buffer.get_handle()).set_text_attribute(new_color)?; + + Ok(()) +} + +fn set_foreground_color() -> std::io::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.get_handle()).set_text_attribute(color)?; + + Ok(()) +} + +fn main() {} diff --git a/crossterm_winapi/examples/console.rs b/crossterm_winapi/examples/console.rs new file mode 100644 index 0000000..b3e22e5 --- /dev/null +++ b/crossterm_winapi/examples/console.rs @@ -0,0 +1,15 @@ +extern crate crossterm_winapi; + +use crossterm_winapi::ConsoleMode; + +pub fn change_console_mode() { + let console_mode = ConsoleMode::new().unwrap(); + + // get the current console mode: + let mode: u32 = console_mode.mode().unwrap(); + + // set the console mode (not sure if this is an actual value xp) + console_mode.set_mode(10); +} + +fn main() {} diff --git a/crossterm_winapi/examples/handle.rs b/crossterm_winapi/examples/handle.rs new file mode 100644 index 0000000..45d5704 --- /dev/null +++ b/crossterm_winapi/examples/handle.rs @@ -0,0 +1,19 @@ +extern crate crossterm_winapi; + +use crossterm_winapi::{Handle, HandleType}; + +fn main() { + /// see the description of the types to see what they do. + 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(); + + // 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 */ +} diff --git a/crossterm_winapi/examples/screen_buffer.rs b/crossterm_winapi/examples/screen_buffer.rs new file mode 100644 index 0000000..8fc181e --- /dev/null +++ b/crossterm_winapi/examples/screen_buffer.rs @@ -0,0 +1,25 @@ +extern crate crossterm_winapi; + +use crossterm_winapi::{Handle, ScreenBuffer}; + +fn main() {} + +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()); +} + +fn multiple_screen_buffers() { + // create new screen buffer + let screen_buffer = ScreenBuffer::create(); + + // which to this screen buffer + screen_buffer.show(); +} diff --git a/crossterm_winapi/src/console.rs b/crossterm_winapi/src/console.rs new file mode 100644 index 0000000..67e5b5d --- /dev/null +++ b/crossterm_winapi/src/console.rs @@ -0,0 +1,178 @@ +use super::{is_true, Coord, Handle, HandleType, WindowPositions}; +use std::io::{self, Error, Result}; +use std::str; + +use winapi::ctypes::c_void; +use winapi::shared::ntdef::NULL; +use winapi::um::{ + consoleapi::WriteConsoleW, + wincon::{ + FillConsoleOutputAttribute, FillConsoleOutputCharacterA, GetLargestConsoleWindowSize, + SetConsoleTextAttribute, SetConsoleWindowInfo, COORD, SMALL_RECT, + }, + winnt::HANDLE, +}; + +/// 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 { + 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 { + 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 { + 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 font 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 { + // 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 = 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()) + } +} + +impl From 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 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), + } + } +} diff --git a/crossterm_winapi/src/console_mode.rs b/crossterm_winapi/src/console_mode.rs new file mode 100644 index 0000000..31b6878 --- /dev/null +++ b/crossterm_winapi/src/console_mode.rs @@ -0,0 +1,93 @@ +use super::{is_true, Handle, HandleType}; +use std::io::{Error, Result}; +use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode}; +use winapi::um::winnt::HANDLE; + +/// 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 { + 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 { + 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 for ConsoleMode { + fn from(handle: HANDLE) -> Self { + ConsoleMode { + handle: Handle::from(handle), + } + } +} + +impl From for ConsoleMode { + fn from(handle: Handle) -> Self { + ConsoleMode { handle } + } +} + +#[cfg(test)] +mod test { + use super::ConsoleMode; + + #[test] + fn set_get_mode() { + let mode = ConsoleMode::new().unwrap(); + + let original_mode = mode.mode().unwrap(); + + mode.set_mode(0x0004); + let console_mode = mode.mode().unwrap(); + + assert!((console_mode & 0x0004) != 0); + + mode.set_mode(original_mode); + } +} diff --git a/crossterm_winapi/src/csbi.rs b/crossterm_winapi/src/csbi.rs new file mode 100644 index 0000000..1c034df --- /dev/null +++ b/crossterm_winapi/src/csbi.rs @@ -0,0 +1,61 @@ +use super::{Coord, Size, WindowPositions}; + +use winapi::um::wincon::CONSOLE_SCREEN_BUFFER_INFO; + +use std::mem::zeroed; + +/// 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 whit 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 for ScreenBufferInfo { + fn from(csbi: CONSOLE_SCREEN_BUFFER_INFO) -> Self { + ScreenBufferInfo(csbi) + } +} diff --git a/crossterm_winapi/src/handle.rs b/crossterm_winapi/src/handle.rs new file mode 100644 index 0000000..d2a5741 --- /dev/null +++ b/crossterm_winapi/src/handle.rs @@ -0,0 +1,188 @@ +//! This module contains some logic for working with the console handle. + +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}, +}; + +use std::io::{self, Result}; +use std::ops::Deref; +use std::ptr::null_mut; + +/// 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 { + 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 { + let utf16: Vec = "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 { + let utf16: Vec = "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 { + 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 { + 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) -> &::Target { + &self.handle + } +} + +impl From for Handle { + fn from(handle: HANDLE) -> Self { + Handle { handle } + } +} + +#[cfg(test)] +mod test { + use super::{Handle, HandleType}; + + #[test] + fn get_handle() { + 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(); + } +} diff --git a/crossterm_winapi/src/lib.rs b/crossterm_winapi/src/lib.rs new file mode 100644 index 0000000..560cae3 --- /dev/null +++ b/crossterm_winapi/src/lib.rs @@ -0,0 +1,28 @@ +extern crate winapi; + +mod console; +mod console_mode; +mod csbi; +mod handle; +mod screen_buffer; +mod structs; + +pub use self::{ + console::Console, + console_mode::ConsoleMode, + csbi::ScreenBufferInfo, + handle::{Handle, HandleType}, + screen_buffer::ScreenBuffer, + structs::{Coord, Size, WindowPositions}, +}; + +/// 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; + } +} diff --git a/crossterm_winapi/src/screen_buffer.rs b/crossterm_winapi/src/screen_buffer.rs new file mode 100644 index 0000000..af95441 --- /dev/null +++ b/crossterm_winapi/src/screen_buffer.rs @@ -0,0 +1,137 @@ +//! This contains the logic for working with the console buffer. + +use super::{is_true, Handle, HandleType, ScreenBufferInfo}; + +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 std::io::{Error, Result}; +use std::mem::size_of; + +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 { + 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::() 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 { + 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 get_handle(&self) -> &Handle { + return &self.handle; + } +} + +impl From for ScreenBuffer { + fn from(handle: Handle) -> Self { + ScreenBuffer { handle } + } +} + +impl From for ScreenBuffer { + fn from(handle: HANDLE) -> Self { + ScreenBuffer { + handle: Handle::from(handle), + } + } +} + +#[cfg(test)] +mod test { + use super::ScreenBuffer; + + #[test] + fn screen_buffer_info() { + let buffer = ScreenBuffer::current().unwrap(); + let info = buffer.info().unwrap(); + info.terminal_size(); + info.terminal_window(); + info.attributes(); + info.cursor_pos(); + } +} diff --git a/crossterm_winapi/src/structs/coord.rs b/crossterm_winapi/src/structs/coord.rs new file mode 100644 index 0000000..01b838a --- /dev/null +++ b/crossterm_winapi/src/structs/coord.rs @@ -0,0 +1,42 @@ +//! This module provides a type that represents some location/coordination. +//! For example, in WinAPi we have `COORD` which looks and feels inconvenient. +//! This module provides also some trait implementations who will make parsing and working whit `COORD` easier. + +use winapi::um::wincon::COORD; + +/// This is type represents the position of something on a certain 'x' and 'y'. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Coord { + /// the position on the x axis + pub x: i16, + /// the position on the y axis + pub y: i16, +} + +impl Coord { + /// Create a new size instance by passing in the width and height. + pub fn new(x: i16, y: i16) -> Coord { + Coord { x, y } + } +} + +impl From for Coord { + fn from(coord: COORD) -> Self { + Coord::new(coord.X, coord.Y) + } +} + +impl From for COORD { + fn from(location: Coord) -> Self { + COORD { + X: location.x, + Y: location.y, + } + } +} + +impl Into<(u16, u16)> for Coord { + fn into(self) -> (u16, u16) { + (self.x as u16, self.y as u16) + } +} diff --git a/crossterm_winapi/src/structs/mod.rs b/crossterm_winapi/src/structs/mod.rs new file mode 100644 index 0000000..d5721da --- /dev/null +++ b/crossterm_winapi/src/structs/mod.rs @@ -0,0 +1,7 @@ +mod coord; +mod size; +mod window_coords; + +pub use self::coord::Coord; +pub use self::size::Size; +pub use self::window_coords::WindowPositions; diff --git a/crossterm_winapi/src/structs/size.rs b/crossterm_winapi/src/structs/size.rs new file mode 100644 index 0000000..59e38b7 --- /dev/null +++ b/crossterm_winapi/src/structs/size.rs @@ -0,0 +1,31 @@ +//! This module provides a type that represents some size. +//! For example, in WinAPi we have `COORD` to represent screen/buffer size but this is a little inconvenient. +//! This module provides some trait implementations who will make parsing and working whit `COORD` easier. + +use winapi::um::wincon::COORD; + +/// This is type represents the size of something in width and height. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct Size { + pub width: i16, + pub height: i16, +} + +impl Size { + /// Create a new size instance by passing in the width and height. + pub fn new(width: i16, height: i16) -> Size { + Size { width, height } + } +} + +impl From for Size { + fn from(coord: COORD) -> Self { + Size::new(coord.X, coord.Y) + } +} + +impl Into<(u16, u16)> for Size { + fn into(self) -> (u16, u16) { + (self.width as u16, self.height as u16) + } +} diff --git a/crossterm_winapi/src/structs/window_coords.rs b/crossterm_winapi/src/structs/window_coords.rs new file mode 100644 index 0000000..3dbd264 --- /dev/null +++ b/crossterm_winapi/src/structs/window_coords.rs @@ -0,0 +1,38 @@ +//! This module provides a type that represents some rectangle. +//! For example, in WinAPi we have `SMALL_RECT` to represent a window size but this is a little inconvenient. +//! This module provides some trait implementations who will make parsing and working whit `COORD` easier. + +use winapi::um::wincon::{CONSOLE_SCREEN_BUFFER_INFO, SMALL_RECT}; + +/// This is a wrapper for the locations of a rectangle. +/// +/// It has left, right, bottom, top attributes. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub struct WindowPositions { + pub left: i16, + pub right: i16, + pub bottom: i16, + pub top: i16, +} + +impl From for WindowPositions { + fn from(csbi: CONSOLE_SCREEN_BUFFER_INFO) -> Self { + WindowPositions { + left: csbi.srWindow.Left, + right: csbi.srWindow.Right, + bottom: csbi.srWindow.Bottom, + top: csbi.srWindow.Top, + } + } +} + +impl From for SMALL_RECT { + fn from(positions: WindowPositions) -> Self { + SMALL_RECT { + Top: positions.top, + Right: positions.right, + Bottom: positions.bottom, + Left: positions.left, + } + } +} diff --git a/docs/Contributing.md b/docs/Contributing.md index cafec3b..094746a 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -1,46 +1,40 @@ -I would really appreciate any contributing to this crate. But there are some things that are handy to know. +I would really appreciate any contributing to this crate. However there are some things that are handy to know. -## Branch -- If you have small commits (e.g. bugfixes, grammar improvements, examples, comments) please create a pull request to the development branch. -- If you have a large feature you could better create a separate branch for that and pull request this one into development. - ## How it works Crossterm is using ANSI escape codes by default for both Unix and Windows systems. But for Windows, it is a bit more complicated since Windows versions 8 or lower are not supporting ANSI escape codes. This is why we use WinApi for those machines. ## Architecture -Here I will discuss the architecture of crossterm. At first we will discuss the five modules crossterm has like: cursor, input, style, terminal, write. +Here I will discuss the architecture of crossterm. +Crossterm wraps 5 crates: cursor, input, style, terminal, screen. -### The different modules +### The different crates If you would like to contribute to Crossterm, than please design the code as it is now. For example, a module like cursor has the following file structure: -- mod.rs - This file contains some trait, in this case, `ITerminalCursor`, for other modules to implement. So that it can work at a specific platform. +- module name + - mod.rs + + This file contains some trait, in this case, `ITerminalCursor`, for other modules to implement. So that it can work at a specific platform. + + - cursor.rs + + The end user will call this module to access the cursor functionalities. This module will decide which implementation to use based on the current platform. + - winapi_cursor + + This is the cursor trait (located in mod.rs) implementation with WinApi. + - ansi_cursor + + This is the cursor trait (located in mod.rs) implementation with ANSI escape codes. + - sys + + contains platform specific logic. -- cursor.rs - - The end user will call this module to access the cursor functionalities. This module will decide which implementation to use based on the current platform. -- winapi_cursor - - This is the cursor trait (located in mod.rs) implementation with WinApi. -- ansi_cursor - - This is the cursor trait (located in mod.rs) implementation with ANSI escape codes. - -The above structure is the same for the terminal, color, manager modules. +The above structure is the same for the other modules. Why I have chosen for this design: - Because you can easily extend to multiple platforms by implementing the trait int the mod.rs. - You keep the functionalities for different platforms separated in different files. -- Also, you have one API the user can call like in the `cursor.rs` above. This file should be avoided to change that much. All the other code could change a lot because it has no impact on the user side. - -### Kernel -The kernel is divided into to modules one containing all the windows specific logic and the other containing all the unix specific code. -Here we will do all the unsafe system/C calls. - -### Common -Here is the code located that could be used everywhere. An example is the `Screen` type. -The user can call this but also the different modules and the kernel are using this type. \ No newline at end of file +- Also, you have one API the user can call like in the `cursor.rs` above. This file should be avoided to change that much. All the other code could change a lot because it has no impact on the user side. \ No newline at end of file diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index e3be622..6b5f50a 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -1,10 +1,4 @@ # Changes crossterm 0.5.4 -- Windows panic bug when trying to enable ANSI flag windows console [PR](https://github.com/TimonPost/crossterm/pull/73). - -# Changes crossterm 0.5.3 -- Fixed crucial bug for windows systems, this was accidentally introduced when working on 0.5.2 - -# Changes crossterm 0.5.2 (yanked) - WinApi rewrite and correctly error handled [PR 67](https://github.com/TimonPost/crossterm/pull/67) - Windows attribute support [PR 62](https://github.com/TimonPost/crossterm/pull/62) - Readline bug fix windows systems [PR 62](https://github.com/TimonPost/crossterm/pull/62) diff --git a/docs/mdbook/src/SUMMARY.md b/docs/mdbook/src/SUMMARY.md index 7255bd4..d77f77a 100644 --- a/docs/mdbook/src/SUMMARY.md +++ b/docs/mdbook/src/SUMMARY.md @@ -1,6 +1,7 @@ # Summary This book will cover styling and user input. It will also give a detailed description about working with alternate and raw screen. +- [Feature Flags](feature_flags.md) - [Styling Output](styling.md) - [example](styling_example.md) - [Async Input](input.md) diff --git a/docs/mdbook/src/feature_flags.md b/docs/mdbook/src/feature_flags.md new file mode 100644 index 0000000..a05be82 --- /dev/null +++ b/docs/mdbook/src/feature_flags.md @@ -0,0 +1,29 @@ +From `crossterm 0.6` you are allowed to use feature flags. + +With feature flags you can pick the features you want which reduces the size of the library and could prevent you from having unnecessary dependencies. + +Crossterm provides the following feature flags: +- input ; reading input +- terminal ; terminal actions like resizing +- style ; styling of the terminal +- cursor ; moving the terminal cursor +- screen ; alternate and raw screen + +By default all of those will be enabled. + +_Cargo.toml_ + +``` +[dependencies] +crossterm = { version="0.6", default-features = false, features = ["screen", "terminal", "cursor", "style", "input"] } +``` + +You can also use all the crossterm modules individually by directly referencing the crate. + +- [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) + + diff --git a/docs/mdbook/src/screen.md b/docs/mdbook/src/screen.md index e15f77c..34bda85 100644 --- a/docs/mdbook/src/screen.md +++ b/docs/mdbook/src/screen.md @@ -36,40 +36,53 @@ Note that in raw mode `\n` `\r` will move the cursor to a new line but it will b _example of what I mean_ ``` some text - some text + some text ``` To start at the beginning of the next line, use `\n\r`. -# Important Notice +# Crossterm's implementation + When we want to print some text to the alternate screen we can't just write on it using `print!(), println!(), or write!()`. -The same goes for coloring, cursor movement, input, and terminal actions. Crossterm provides a solution for that by introducing the `Screen` type. + +This is because those functions are writing to the standard output and not to our alternate screen we are currently on. +The same goes for coloring, cursor movement, input, and terminal actions. + +Crossterm provides a solution for that by introducing the `Screen` type. +You can use the 'alternate' or 'raw' screen functionalities by either using the [crossterm](https://crates.io/crates/crossterm) or [crossterm_screen](https://crates.io/crates/crossterm_screen) crate. + Please checkout this [example](screen_example.md) for more information on how to use it. - +_Cargo.toml_ +```rust +crossterm = { version = "0.6.0", features = ["screen","terminal","cursor", "style", "input"] } ``` -use crossterm::cursor; -use crossterm::color; -use crossterm::input; -use crossterm::terminal; + +```rust +use crossterm::{cursor, TerminalCursor}; +use crossterm::{color, TerminalColor}; +use crossterm::{input, TerminalInput}; +use crossterm::{terminal, Terminal}; let screen = Screen::default(); if let Ok(alternate) = screen.enable_alternate_modes(false) { + + // by calling 'from_screen' you force crossterm to use the screen of the alternate screen to perform actions on. + let crossterm = Crossterm::from_screen(&alternate.screen); + let cursor = crossterm.cursor(); + let terminal =crossterm.terminal(); + let input = crossterm.input(); + let color = crossterm.color(); + + // you can also create instances directly without `Crossterm` let screen = alternate.screen; - let color = color::from_screen(&screen); - let cursor = cursor::from_screen(&screen); - let input = input::from_screen(&screen); - let terminal = terminal::from_screen(&screen); - let crossterm = Crossterm::from_screen(&screen); - let terminal = Terminal::from_output(&screen.stdout); let cursor = TerminalCursor::from_output(&screen.stdout); let color = TerminalColor::from_output(&screen.stdout); let input = TerminalInput::from_output(&screen.stdout); } - ``` The above modules will now all be executed at the 'alternate screen'. diff --git a/docs/mdbook/src/screen_example.md b/docs/mdbook/src/screen_example.md index d967cc6..bf424e2 100644 --- a/docs/mdbook/src/screen_example.md +++ b/docs/mdbook/src/screen_example.md @@ -42,7 +42,7 @@ let text = std::io::stdin().read_line(&mut string); println!("{:?}", text); ``` -Note that we spoke about the reason why this [previously](screen.md#raw-screen). +Note that we spoke about the reason why this is [previously](screen.md#raw-screen). However, if you want to read input in raw mode you should checkout [Async Input](input.md). # Alternate Screen @@ -118,7 +118,7 @@ Some basics steps the following code will do: ```rust let alternate_screen = &mut alternate.screen; -// could manage styling and cursor movement. +// by calling 'from_screen' the cursor will be moved at the passed screen. let crossterm = Crossterm::from_screen(alternate_screen); let cursor = crossterm.cursor(); @@ -148,7 +148,7 @@ thread::sleep(time::Duration::from_secs(2)); As you might have noticed, you need to to manually move the cursor, flush the buffer. This is because the terminal is in raw modes. Also, by printing with `paint` or calling `into_displayable` you pass in a reference to the alternate screen. -Tip: Take a look at [how](screen.md#important-notice) you should use other modules crossterm provides on the alternate screen. +Tip: Take a look at [how](screen.md#Crossterm's implementation) you should use other modules crossterm provides on the alternate screen. --------------------------------------------------------------------------------------------------------------------------------------------- More examples could be found at this [link](https://github.com/TimonPost/crossterm/tree/master/examples/terminal). diff --git a/docs/mdbook/src/styling.md b/docs/mdbook/src/styling.md index 35b5641..b9c4946 100644 --- a/docs/mdbook/src/styling.md +++ b/docs/mdbook/src/styling.md @@ -21,14 +21,16 @@ In addition to 16 colours, most UNIX terminals and Windows 10 consoles are also Those colors could be: [True color (24-bit)](https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)) coloring scheme, which allows you to use [RGB](https://nl.wikipedia.org/wiki/RGB-kleursysteem), and [256 (Xterm, 8-bit)](https://jonasjacek.github.io/colors/) colors. ## Attributes -Only UNIX terminals are supporting attributes on top of text. Crossterm allows you to add attributes to the text. +Only UNIX and Windows 10 terminals are supporting attributes on top of text. Crossterm allows you to add attributes to the text. Not all attributes are widely supported for all terminals, keep that in mind when working with this. +**Unix Attributes** + | Attribute | Note | | :-------------: | :-------------: | -| Bold | | -| Underlined | | -| Dim | | +| Bold | _ | +| Underlined | _| +| Dim | _| | SlowBlink | less than 150 per minute | | CrosseOut | characters legible, but marked for deletion. | | Italic | not widely supported; Sometimes treated as inverse | @@ -36,6 +38,15 @@ Not all attributes are widely supported for all terminals, keep that in mind whe | Reverse | not widely supported | | Hidden | not widely supported | +**Windows Attributes** + +| Attribute | Note | +| :-------------: | :-------------: | +| Reset | _ | +| Underlined | _ | +| NoUnderline | _ | +| Negative | _ | +| Positive | _ | Now we have covered the basics of styling lets go some [examples](styling_example.md). diff --git a/docs/mdbook/src/styling_example.md b/docs/mdbook/src/styling_example.md index 39d8527..e9e042e 100644 --- a/docs/mdbook/src/styling_example.md +++ b/docs/mdbook/src/styling_example.md @@ -4,7 +4,7 @@ _setup the basics_ ```rust extern crate crossterm; -use crossterm::style::{style, Color, Attribute}; +use crossterm::{style, Color, Attribute}; fn main() { /* your code here */ @@ -18,7 +18,8 @@ let styled_object = style("This is some text converted into a styled object"); ``` The function `style()` takes in any type that implement `Display` -and returns a `StyledObject`. A `StyledObject` is just a wrapper crossterm uses to store the text and style together. +and returns a `StyledObject`. +A `StyledObject` is just a wrapper crossterm uses to store the text and style together. The above code will not do any coloring magic yet. Lets play around with some colors to see it in working. @@ -62,8 +63,8 @@ println!("{}", styled_object); ``` ## Attributes -When working with UNIX terminals you could also use attributes to style your font. For example you could cross your text with a line and make it bold. -See [above](styling.md#Attributes) for more information. +When working with UNIX or Windows 10 terminals you could also use attributes to style your font. For example you could cross your text with a line and make it bold. +See [this](styling.md#Attributes) for more information. ``` let styled_object = style("'Red' text on 'White' background") @@ -74,4 +75,4 @@ println!("{}", styled_object); ``` --------------------------------------------------------------------------------------------------------------------------------------------- -More examples could be found at this [link](https://github.com/TimonPost/crossterm/blob/master/examples/color/mod.rs). \ No newline at end of file +More examples could be found at this [link](https://github.com/TimonPost/crossterm/blob/master/examples/style.rs). \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index cb319b0..4dcd3f4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,5 +5,8 @@ It has 4 modules: - cursor (this is about all the actions you can perform with the cursor) - terminal (this is about all the actions you can perform on the terminal) - input (this is about all input actions you can perform on with terminal) -- crossterm_type (this is about the struct `Crossterm`) +- async_input (this is about reading async input) +- crossterm (this is about the struct `Crossterm`) +- alternate_screen (this is about switching to an alternate screen buffer) +- raw_screen (this is about enabling raw screen) - program examples (this folder will contain some real life examples) \ No newline at end of file diff --git a/examples/terminal/alternate_screen.rs b/examples/alternate_screen.rs similarity index 87% rename from examples/terminal/alternate_screen.rs rename to examples/alternate_screen.rs index 422ccf7..6920e91 100644 --- a/examples/terminal/alternate_screen.rs +++ b/examples/alternate_screen.rs @@ -1,8 +1,6 @@ extern crate crossterm; -use crossterm::style::{style, Color}; -use crossterm::terminal::{self, ClearType}; -use crossterm::{Crossterm, Screen}; +use crossterm::{ClearType, Color, Crossterm, Screen, style}; use std::io::{stdout, Write}; use std::{thread, time}; @@ -15,13 +13,11 @@ fn print_wait_screen(screen: &Screen) { terminal.clear(ClearType::All); cursor.goto(0, 0); cursor.hide(); - terminal.write( "Welcome to the wait screen.\n\ Please wait a few seconds until we arrive back at the main screen.\n\ Progress: ", ); - // print some progress example. for i in 1..5 { // print the current counter at the line of `Seconds to Go: {counter}` @@ -29,7 +25,7 @@ fn print_wait_screen(screen: &Screen) { style(format!("{} of the 5 items processed", i)) .with(Color::Red) .on(Color::Blue) - .paint(&screen); + .paint(&screen.stdout); // 1 second delay thread::sleep(time::Duration::from_secs(1)); @@ -44,3 +40,7 @@ pub fn print_wait_screen_on_alternate_window() { print_wait_screen(&alternate.screen); } } + +fn main() { + print_wait_screen_on_alternate_window(); +} \ No newline at end of file diff --git a/examples/input/keyboard/async_input.rs b/examples/async_input.rs similarity index 86% rename from examples/input/keyboard/async_input.rs rename to examples/async_input.rs index 807dda4..0ba6cc8 100644 --- a/examples/input/keyboard/async_input.rs +++ b/examples/async_input.rs @@ -1,8 +1,6 @@ extern crate crossterm; -use self::crossterm::input::input; -use self::crossterm::terminal::ClearType; -use self::crossterm::{Crossterm, Screen}; +use self::crossterm::{input, Screen, TerminalInput, Crossterm, ClearType}; use std::io::{stdout, Read, Write}; use std::time::Duration; @@ -11,18 +9,13 @@ use std::{thread, time}; /// this will capture the input until the given key. pub fn read_async_until() { // create raw screen - let crossterm = Crossterm::new(); + let screen = Screen::new(true); - // init some modules we use for this demo - let input = crossterm.input(); - let terminal = crossterm.terminal(); - let cursor = crossterm.cursor(); + let input = TerminalInput::from_output(&screen.stdout); let mut stdin = input.read_until_async(b'\r').bytes(); for _i in 0..100 { - terminal.clear(ClearType::All); - cursor.goto(1, 1); let a = stdin.next(); println!("pressed key: {:?}", a); @@ -43,7 +36,10 @@ pub fn read_async_until() { /// this will read pressed characters async until `x` is typed. pub fn read_async() { - let input = input(); + // create raw screen + let screen = Screen::new(true); + + let input = TerminalInput::from_output(&screen.stdout); let mut stdin = input.read_async().bytes(); @@ -62,10 +58,11 @@ pub fn read_async() { } pub fn read_async_demo() { + // create raw screen let screen = Screen::new(true); + let crossterm = Crossterm::from_screen(&screen); - // init some modules we use for this demo let input = crossterm.input(); let terminal = crossterm.terminal(); let cursor = crossterm.cursor(); @@ -97,7 +94,10 @@ pub fn read_async_demo() { } pub fn async_reading_on_alternate_screen() { - let screen = Screen::new(false); + // create raw screen + let screen = Screen::new(true); + + let input = TerminalInput::from_output(&screen.stdout); // switch to alternate screen if let Ok(alternate) = screen.enable_alternate_modes(true) { @@ -130,3 +130,7 @@ pub fn async_reading_on_alternate_screen() { } } } + +fn main() { + async_reading_on_alternate_screen(); +} diff --git a/examples/some_types/mod.rs b/examples/crossterm.rs similarity index 61% rename from examples/some_types/mod.rs rename to examples/crossterm.rs index 826affa..4f2a2d6 100644 --- a/examples/some_types/mod.rs +++ b/examples/crossterm.rs @@ -1,7 +1,6 @@ extern crate crossterm; -use crossterm::style::Color; -use crossterm::{Crossterm, Screen}; +use crossterm::{Crossterm, Screen, Color}; /// use the `Crossterm` to get an instance to the cursor module | demonstration. pub fn crossterm() { @@ -12,7 +11,8 @@ pub fn crossterm() { let cursor = crossterm.cursor(); let color = crossterm.color(); let terminal = crossterm.terminal(); - let style = crossterm.style("").with(Color::Black).on(Color::Green); + let terminal = crossterm.input(); + let style = crossterm.style("Black font on green background").with(Color::Black).on(Color::Green); - // perform some actions with the instances above. + // TODO: perform some actions with the instances above. } diff --git a/examples/cursor.rs b/examples/cursor.rs new file mode 100644 index 0000000..310c9dc --- /dev/null +++ b/examples/cursor.rs @@ -0,0 +1,92 @@ +//! +//! Examples of actions that could be performed with te cursor. +//! + +extern crate crossterm_cursor; + +use crossterm_cursor::{cursor, TerminalCursor}; + +/// Set the cursor to position X: 10, Y: 5 in the terminal. +pub fn goto() { + // Get the cursor + let cursor = cursor(); + // Set the cursor to position X: 10, Y: 5 in the terminal + cursor.goto(10, 5); +} + +/// get the cursor position +pub fn pos() { + // Get the cursor + let cursor = cursor(); + // get the cursor position. + let (x, y) = cursor.pos(); + + println!("{} {}", x, y); +} + +/// Move the cursor 3 up | demonstration. +pub fn move_up() { + // Get the cursor + let mut cursor = cursor(); + + // Move the cursor to position 3 times to the up in the terminal + cursor.move_up(10); +} + +/// Move the cursor 3 to the right | demonstration. +pub fn move_right() { + let mut cursor = cursor(); + // Move the cursor to position 3 times to the right in the terminal + cursor.move_right(3); +} + +/// Move the cursor 3 down | demonstration. +pub fn move_down() { + let mut cursor = cursor(); + // Move the cursor to position 3 times to the down in the terminal + cursor.move_down(3); +} + +/// Save and reset cursor position | demonstration.. +pub fn safe_and_reset_position() { + let cursor = cursor(); + + // 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. + println!("Yea!"); + // Reset back to X: 5 Y: 5. + cursor.reset_position(); + // Print Back at X: 5 Y: 5. + println!("Back"); + + println!() +} + +/// Hide cursor display | demonstration. +pub fn hide_cursor() { + let cursor = cursor(); + cursor.hide(); +} + +/// Show cursor display | demonstration. +pub fn show_cursor() { + let cursor = cursor(); + cursor.show(); +} + +/// Show cursor display, only works on certain terminals.| demonstration +pub fn blink_cursor() { + let cursor = cursor(); + cursor.blink(false); + cursor.blink(false); +} + +fn main() { + goto(); + pos(); +} diff --git a/examples/examples.rs b/examples/examples.rs deleted file mode 100644 index fc3dcf8..0000000 --- a/examples/examples.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! This bin folder can be used to try the examples out located in the examples directory. -//! -//! All you need to do is: -//! -//! - Download the crossterm source code. -//! - Run program with: `cargo run --example examples` - -extern crate crossterm; - -// modules that could be test -mod color; -mod cursor; -mod input; -//mod some_types; -mod terminal; - -fn main() { - let cursor = crossterm::cursor(); - cursor.goto(5, 5); - - let integer = 10; - let float: f32 = integert as f32; - - println!("5.515151"); -} diff --git a/examples/input.rs b/examples/input.rs new file mode 100644 index 0000000..a1c6a22 --- /dev/null +++ b/examples/input.rs @@ -0,0 +1,32 @@ +extern crate crossterm; + +use self::crossterm::{KeyEvent, TerminalInput, input, Screen}; + +pub fn read_char() { + let input = input(); + + match input.read_char() { + Ok(s) => println!("char typed: {}", s), + Err(e) => println!("char error : {}", e), + } +} + +pub fn read_line() { + let input = input(); + + match input.read_line() { + Ok(s) => println!("string typed: {}", s), + Err(e) => println!("error: {}", e), + } +} + +pub fn pause_terminal() { + println!("Press 'x' to quit..."); + let screen = Screen::new(true); + let terminal_input = TerminalInput::from_output(&screen.stdout); + terminal_input.wait_until(KeyEvent::OnKeyPress(b'x')); +} + +fn main(){ + pause_terminal(); +} diff --git a/examples/input/keyboard/mod.rs b/examples/input/keyboard/mod.rs deleted file mode 100644 index ab07b5b..0000000 --- a/examples/input/keyboard/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod async_input; -pub mod input; diff --git a/examples/input/mod.rs b/examples/input/mod.rs deleted file mode 100644 index 45f5b56..0000000 --- a/examples/input/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod keyboard; diff --git a/examples/program_examples/command_bar.rs b/examples/program_examples/command_bar.rs index da0da85..c448fb5 100644 --- a/examples/program_examples/command_bar.rs +++ b/examples/program_examples/command_bar.rs @@ -1,9 +1,7 @@ extern crate crossterm; -use crossterm::cursor::{cursor, TerminalCursor}; -use crossterm::input::input; -use crossterm::terminal::{from_screen, ClearType, Terminal}; -use crossterm::{Crossterm, Screen}; +use crossterm::{cursor, input, ClearType, Crossterm, Screen, Terminal, TerminalCursor}; + use std::io::Read; use std::sync::{Arc, Mutex}; use std::{thread, time}; @@ -14,7 +12,6 @@ fn main() { let screen = Screen::new(true); let crossterm = Crossterm::from_screen(&screen); let cursor = crossterm.cursor(); - cursor.hide(); let input_buf = Arc::new(Mutex::new(String::new())); @@ -56,13 +53,14 @@ fn main() { fn log(input_buf: Arc>, screen: &Screen) -> Vec> { let mut threads = Vec::with_capacity(10); - let (_, term_height) = from_screen(screen).terminal_size(); + let (_, term_height) = Terminal::from_output(&screen.stdout).terminal_size(); for i in 0..1 { let input_buffer = input_buf.clone(); let _clone_stdout = screen.stdout.clone(); let crossterm = Crossterm::from(screen.stdout.clone()); + let join = thread::spawn(move || { let cursor = crossterm.cursor(); let terminal = crossterm.terminal(); diff --git a/examples/program_examples/first_depth_search/Cargo.toml b/examples/program_examples/first_depth_search/Cargo.toml index b03910c..9c33fee 100644 --- a/examples/program_examples/first_depth_search/Cargo.toml +++ b/examples/program_examples/first_depth_search/Cargo.toml @@ -8,4 +8,4 @@ rand = "0.4.2" [dependencies.crossterm] path = "../../../" -branch = "development" \ No newline at end of file +branch = "master" \ No newline at end of file diff --git a/examples/program_examples/first_depth_search/src/algorithm.rs b/examples/program_examples/first_depth_search/src/algorithm.rs index ff395d2..44d2fa3 100644 --- a/examples/program_examples/first_depth_search/src/algorithm.rs +++ b/examples/program_examples/first_depth_search/src/algorithm.rs @@ -1,10 +1,9 @@ //! Implementation of the first depth search algorithm -use super::variables::{Direction, Position}; -use super::messages::END_MESSAGE; use super::map::Map; +use super::messages::END_MESSAGE; +use super::variables::{Direction, Position}; -use crossterm::style::Color; -use crossterm::{Crossterm, Screen}; +use crossterm::{Color, Crossterm, Screen}; use super::rand; use super::rand::distributions::{IndependentSample, Range}; @@ -12,22 +11,22 @@ use super::rand::distributions::{IndependentSample, Range}; use std::io::{stdout, Write}; use std::{thread, time}; -pub struct FirstDepthSearch<'screen> -{ +pub struct FirstDepthSearch<'screen> { direction: Direction, map: Map, stack: Vec, root_pos: Position, is_terminated: bool, - screen: &'screen Screen + screen: &'screen Screen, } -impl<'screen> FirstDepthSearch<'screen> -{ - pub fn new(map: Map, start_pos: Position, crossterm: &'screen Screen) -> FirstDepthSearch<'screen> - { - FirstDepthSearch - { +impl<'screen> FirstDepthSearch<'screen> { + pub fn new( + map: Map, + start_pos: Position, + crossterm: &'screen Screen, + ) -> FirstDepthSearch<'screen> { + FirstDepthSearch { direction: Direction::Up, map: map, stack: Vec::new(), @@ -37,8 +36,7 @@ impl<'screen> FirstDepthSearch<'screen> } } - pub fn start(&mut self) - { + pub fn start(&mut self) { self.is_terminated = false; // push first position on the stack @@ -50,15 +48,13 @@ impl<'screen> FirstDepthSearch<'screen> // loop until there are now items left in the stack. loop { - if self.stack.len() == 0 - { + if self.stack.len() == 0 { break; } self.choose_random_neighbor(); - if self.is_terminated - { + if self.is_terminated { break; } @@ -71,37 +67,46 @@ impl<'screen> FirstDepthSearch<'screen> let x = pos.x as u16; let y = pos.y as u16; - cursor.goto(x,y); - cell.paint(&self.screen); - self.screen.stdout.flush(); + cursor.goto(x, y); + cell.paint(&self.screen.stdout); + self.screen.stdout.flush(); thread::sleep(time::Duration::from_millis(1)); } } /// With this function we are choosing an random neighbor that we havent visited yet. - fn choose_random_neighbor(&mut self) - { + fn choose_random_neighbor(&mut self) { let mut available_directions: Vec = Vec::with_capacity(4); // check every direction if the direction is not visited we can add it to the list. // note that if the y or x is 0 that we don't want to subtract because we get an subtract overflow. - if self.root_pos.y != 0 && !self.map.is_cell_visited(self.root_pos.x, self.root_pos.y - 1) + if self.root_pos.y != 0 + && !self + .map + .is_cell_visited(self.root_pos.x, self.root_pos.y - 1) { available_directions.push(Direction::Up) } - if !&self.map.is_cell_visited(self.root_pos.x, self.root_pos.y + 1) + if !&self + .map + .is_cell_visited(self.root_pos.x, self.root_pos.y + 1) { available_directions.push(Direction::Down) } - if self.root_pos.x != 0 && !self.map.is_cell_visited(self.root_pos.x - 1, self.root_pos.y) + if self.root_pos.x != 0 + && !self + .map + .is_cell_visited(self.root_pos.x - 1, self.root_pos.y) { available_directions.push(Direction::Left) } - if !&self.map.is_cell_visited(self.root_pos.x + 1, self.root_pos.y) + if !&self + .map + .is_cell_visited(self.root_pos.x + 1, self.root_pos.y) { available_directions.push(Direction::Right) } @@ -109,49 +114,41 @@ impl<'screen> FirstDepthSearch<'screen> let directions_count = available_directions.len(); // if there are no directions left we need to backtrack until we find directions to go to. - if directions_count != 0 - { + if directions_count != 0 { let step = Range::new(0, directions_count); let mut rng = rand::thread_rng(); let choice = step.ind_sample(&mut rng); // set the current direction to the new random generated direction. self.direction = available_directions[choice]; - } - else { + } else { self.find_first_possible_direction(); } } /// Find direction to go to if there is no direction pop the current position of the stack for back tracking to the previous position. - fn find_first_possible_direction(&mut self) - { + fn find_first_possible_direction(&mut self) { // if there are no elements left in the stack that means we have visited all cell and we van terminate the program. - if let &Some(previous_cell) = &self.stack.pop() - { + if let &Some(previous_cell) = &self.stack.pop() { // update root pos to previous cell and continue searching for new neighbours self.root_pos = previous_cell; self.choose_random_neighbor(); - } - else { + } else { self.is_terminated = true; } } /// update the root position to the new direction we went in - fn update_position(&mut self) - { - match self.direction - { + fn update_position(&mut self) { + match self.direction { Direction::Up => self.root_pos.y -= 1, Direction::Down => self.root_pos.y += 1, Direction::Left => self.root_pos.x -= 1, Direction::Right => self.root_pos.x += 1, - _ => panic!() + _ => panic!(), }; self.map.set_visited(self.root_pos.x, self.root_pos.y); self.stack.push(self.root_pos); } } - diff --git a/examples/program_examples/first_depth_search/src/main.rs b/examples/program_examples/first_depth_search/src/main.rs index 3d08ca0..f8e3e50 100644 --- a/examples/program_examples/first_depth_search/src/main.rs +++ b/examples/program_examples/first_depth_search/src/main.rs @@ -1,30 +1,26 @@ -extern crate rand; extern crate crossterm; +extern crate rand; -mod map; mod algorithm; +mod map; mod messages; mod variables; -use self::crossterm::{ Crossterm, Screen}; -use self::crossterm::terminal::{terminal, ClearType}; -use self::crossterm::style::Color; +use self::crossterm::{terminal, ClearType, Color, Crossterm, Screen}; -use self::variables::{Size, Position }; use self::messages::WELCOME_MESSAGE; +use self::variables::{Position, Size}; use std::io::Read; use std::iter::Iterator; use std::{thread, time}; -fn main() -{ +fn main() { run(); } /// run the program -pub fn run() -{ +pub fn run() { print_welcome_screen(); // This is represents the current screen. @@ -32,13 +28,11 @@ pub fn run() start_algorithm(&screen); } -fn start_algorithm(screen: &Screen) -{ +fn start_algorithm(screen: &Screen) { // we first want to switch to alternate screen. On the alternate screen we are going to run or firstdepthsearch algorithm - if let Ok(ref alternate_screen) = screen.enable_alternate_modes(true) - { + if let Ok(ref alternate_screen) = screen.enable_alternate_modes(true) { // setup the map size and the position to start searching for a path. - let map_size = Size::new(100, 40); + let map_size = Size::new(50, 40); let start_pos = Position::new(10, 10); // create and render the map. Or map border is going to have an █ look and inside the map is just a space. @@ -46,13 +40,13 @@ fn start_algorithm(screen: &Screen) map.render_map(&alternate_screen.screen); // create the algorithm and start it on the alternate screen. Make sure to pass the refrence to the AlternateScreen screen. - let mut algorithm = algorithm::FirstDepthSearch::new(map, start_pos, &alternate_screen.screen); + let mut algorithm = + algorithm::FirstDepthSearch::new(map, start_pos, &alternate_screen.screen); algorithm.start(); } } -fn print_welcome_screen() -{ +fn print_welcome_screen() { let mut screen = Screen::new(true); let crossterm = Crossterm::from_screen(&screen); @@ -63,7 +57,7 @@ fn print_welcome_screen() let input = crossterm.input(); // set size of terminal so the map we are going to draw is fitting the screen. - terminal.set_size(110,60); + terminal.set_size(110, 60); // clear the screen and print the welcome message. terminal.clear(ClearType::All); @@ -84,13 +78,20 @@ fn print_welcome_screen() let a = stdin.next(); if let Some(Ok(b'q')) = a { + drop(screen); terminal.exit(); + break; + } else { + // print the current counter at the line of `Seconds to Go: {counter}` + cursor.goto(48, 10); + crossterm + .style(format!("{}", i)) + .with(Color::Red) + .on(Color::Blue) + .paint(&screen.stdout); } - // print the current counter at the line of `Seconds to Go: {counter}` - cursor.goto(48, 10); - crossterm.style(format!("{}", i)).with(Color::Red).on(Color::Blue).paint(&screen); // 1 second delay thread::sleep(time::Duration::from_secs(1)); } -} \ No newline at end of file +} diff --git a/examples/program_examples/first_depth_search/src/map.rs b/examples/program_examples/first_depth_search/src/map.rs index de8c251..57e8524 100644 --- a/examples/program_examples/first_depth_search/src/map.rs +++ b/examples/program_examples/first_depth_search/src/map.rs @@ -1,72 +1,73 @@ -use super::variables::{Cell, Position, Size }; -use crossterm::{Crossterm, Screen}; -use crossterm::cursor::cursor; -use crossterm::style::{ObjectStyle, StyledObject, Color}; +use super::variables::{Cell, Position, Size}; +use crossterm::{cursor, Color, Crossterm, ObjectStyle, Screen, StyledObject}; use std::fmt::Display; -pub struct Map -{ +pub struct Map { pub map: Vec>, pub size: Size, } -impl Map -{ - pub fn new(map_size: Size, wall_cell_char: char, map_cell_char: char) -> Map - { +impl Map { + pub fn new(map_size: Size, wall_cell_char: char, map_cell_char: char) -> Map { let mut map: Vec> = Vec::new(); // initialize the map shown on the screen. Each cell of terminal should have a value that could be changed by the algorithm // create n rows with n cells. - for y in 0..map_size.height - { + for y in 0..map_size.height { let mut row: Vec = Vec::new(); - for x in 0..map_size.width - { - if (y == 0 || y == map_size.height - 1) || (x == 0 || x == map_size.width - 1) - { - row.push(Cell::new(Position::new(x, y), Color::Black, wall_cell_char, true)); - } else { - row.push(Cell::new(Position::new(x, y), Color::Black, map_cell_char, false)); + for x in 0..map_size.width { + if (y == 0 || y == map_size.height - 1) || (x == 0 || x == map_size.width - 1) { + row.push(Cell::new( + Position::new(x, y), + Color::Black, + wall_cell_char, + true, + )); + } else { + row.push(Cell::new( + Position::new(x, y), + Color::Black, + map_cell_char, + false, + )); } } map.push(row); } - Map { map: map, size: Size::new(map_size.width, map_size.height)} + Map { + map: map, + size: Size::new(map_size.width, map_size.height), + } } // render the map on the screen. - pub fn render_map(&mut self, screen: &Screen) - { + pub fn render_map(&mut self, screen: &Screen) { let crossterm = Crossterm::from_screen(screen); - for row in self.map.iter_mut() - { - for column in row.iter_mut() - { + for row in self.map.iter_mut() { + for column in row.iter_mut() { // we only have to render the walls - if (column.position.y == 0 || column.position.y == self.size.height - 1) || (column.position.x == 0 || column.position.x == self.size.width - 1) + if (column.position.y == 0 || column.position.y == self.size.height - 1) + || (column.position.x == 0 || column.position.x == self.size.width - 1) { let cell_style = crossterm.style(column.look).on(column.color); cursor().goto(column.position.x as u16, column.position.y as u16); - cell_style.paint(&screen); + cell_style.paint(&screen.stdout); } } } } // check if position in the map at the given coords is visted. - pub fn is_cell_visited(&self, x: usize, y: usize) -> bool - { + pub fn is_cell_visited(&self, x: usize, y: usize) -> bool { self.map[y][x].visited } // change an position in the map to visited. - pub fn set_visited(&mut self, x: usize, y: usize) - { + pub fn set_visited(&mut self, x: usize, y: usize) { self.map[y][x].visited = true; } -} \ No newline at end of file +} diff --git a/examples/program_examples/first_depth_search/src/messages.rs b/examples/program_examples/first_depth_search/src/messages.rs index 6ff44de..3cd0b56 100644 --- a/examples/program_examples/first_depth_search/src/messages.rs +++ b/examples/program_examples/first_depth_search/src/messages.rs @@ -1,17 +1,15 @@ use super::variables::Position; -pub const WELCOME_MESSAGE: [&str; 6] = -[ +pub const WELCOME_MESSAGE: [&str; 6] = [ "__ __ .__ __ ", "/ \\ / \\ ____ | | | | ______ _____ ____ ", "\\ \\/\\/ // __ \\| | | |/ / _ \\ / \\_/ __ \\ ", " \\ /\\ ___/| |_| < <_> ) Y Y \\ ___/ ", " \\__/\\ / \\___ >____/__|_ \\____/|__|_| /\\___ > ", - " \\/ \\/ \\/ \\/ \\/ " + " \\/ \\/ \\/ \\/ \\/ ", ]; -pub const END_MESSAGE: [&str; 5] = -[ +pub const END_MESSAGE: [&str; 5] = [ "-----------------------", " ", " No routes (DONE) ", @@ -19,7 +17,4 @@ pub const END_MESSAGE: [&str; 5] = "-----------------------", ]; -pub fn print_stack_count(position: Position) -{ - -} \ No newline at end of file +pub fn print_stack_count(position: Position) {} diff --git a/examples/program_examples/first_depth_search/src/variables.rs b/examples/program_examples/first_depth_search/src/variables.rs index 03c3fdb..2e745e4 100644 --- a/examples/program_examples/first_depth_search/src/variables.rs +++ b/examples/program_examples/first_depth_search/src/variables.rs @@ -1,62 +1,57 @@ extern crate crossterm; -use self::crossterm::terminal::{terminal, ClearType}; -use self::crossterm::style::{Color, StyledObject, ObjectStyle }; +use self::crossterm::{terminal, ClearType}; +use self::crossterm::{Color, ObjectStyle, StyledObject}; -use std::fmt::Debug; use std::fmt; +use std::fmt::Debug; -#[derive(Copy, Clone,Debug)] -pub enum Direction -{ +#[derive(Copy, Clone, Debug)] +pub enum Direction { Up = 0, Down = 1, Left = 2, - Right = 3 + Right = 3, } #[derive(Copy, Clone, Debug)] -pub struct Position -{ +pub struct Position { pub x: usize, - pub y: usize + pub y: usize, } -impl Position -{ - pub fn new(x: usize, y: usize) -> Position - { +impl Position { + pub fn new(x: usize, y: usize) -> Position { Position { x, y } } } #[derive(Copy, Clone)] -pub struct Size -{ +pub struct Size { pub width: usize, - pub height: usize + pub height: usize, } -impl Size -{ - pub fn new(width: usize, height: usize) -> Size - { - Size {width,height} +impl Size { + pub fn new(width: usize, height: usize) -> Size { + Size { width, height } } } -pub struct Cell -{ +pub struct Cell { pub position: Position, pub color: Color, pub look: char, - pub visited: bool + pub visited: bool, } -impl Cell -{ - pub fn new(position: Position, color: Color, look: char, visited: bool) -> Cell - { - Cell { position, color, look, visited } +impl Cell { + pub fn new(position: Position, color: Color, look: char, visited: bool) -> Cell { + Cell { + position, + color, + look, + visited, + } } -} \ No newline at end of file +} diff --git a/examples/program_examples/snake/Cargo.toml b/examples/program_examples/snake/Cargo.toml index b03910c..fdff37e 100644 --- a/examples/program_examples/snake/Cargo.toml +++ b/examples/program_examples/snake/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "first_depth_search" +name = "snake" version = "0.1.0" authors = ["TimonPost "] @@ -8,4 +8,3 @@ rand = "0.4.2" [dependencies.crossterm] path = "../../../" -branch = "development" \ No newline at end of file diff --git a/examples/program_examples/snake/src/main.rs b/examples/program_examples/snake/src/main.rs index 90b6c4c..b66913a 100644 --- a/examples/program_examples/snake/src/main.rs +++ b/examples/program_examples/snake/src/main.rs @@ -2,25 +2,21 @@ extern crate crossterm; extern crate rand; mod map; +mod messages; mod snake; mod variables; -mod messages; -use self::crossterm::input::input; -use self::crossterm::terminal::{terminal, ClearType}; -use self::crossterm::style::Color; - -use self::crossterm::{Screen, Crossterm}; +use self::crossterm::{input, terminal, ClearType, Color, Crossterm, Screen}; use map::Map; -use variables::{Size, Direction, Position}; use snake::Snake; +use variables::{Direction, Position, Size}; use std::collections::HashMap; -use std::{thread, time}; -use std::iter::Iterator; use std::io::Read; use std::io::Write; +use std::iter::Iterator; +use std::{thread, time}; fn main() { let map_size = title_screen(); @@ -28,14 +24,15 @@ fn main() { { let mut screen = Screen::new(true); let crossterm = Crossterm::from_screen(&screen); - let cursor = crossterm.cursor(); let mut input = crossterm.input(); cursor.hide(); + let mut stdin = input.read_async().bytes(); - let mut free_positions: HashMap = HashMap::with_capacity((map_size.width * map_size.height) as usize); + let mut free_positions: HashMap = + HashMap::with_capacity((map_size.width * map_size.height) as usize); let mut map = Map::new(map_size.clone()); map.render_map(&screen, &mut free_positions); @@ -45,7 +42,8 @@ fn main() { let mut snake = Snake::new(map_size.clone()); for part in snake.get_parts().iter() { - free_positions.remove_entry(format!("{},{}", part.position.x, part.position.y).as_str()); + free_positions + .remove_entry(format!("{},{}", part.position.x, part.position.y).as_str()); } map.spawn_food(&free_positions, &screen); @@ -66,15 +64,13 @@ fn main() { snake.move_snake(&direction, &screen, &mut free_positions); - if map.is_out_of_bounds(snake.snake_parts[0].position) - { + if map.is_out_of_bounds(snake.snake_parts[0].position) { break; } snake.draw_snake(&screen); - if snake.has_eaten_food(map.foot_pos) - { + if snake.has_eaten_food(map.foot_pos) { map.spawn_food(&free_positions, &screen); } } @@ -84,32 +80,29 @@ fn main() { game_over_screen(); } -fn title_screen() -> Size -{ +fn title_screen() -> Size { let crossterm = Crossterm::new(); let cursor = crossterm.cursor(); let terminal = crossterm.terminal().clear(ClearType::All); - println!("{}",messages::SNAKERS.join("\n\r")); + println!("{}", messages::SNAKERS.join("\n\r")); cursor.goto(0, 15); println!("Enter map width:"); cursor.goto(17, 15); let width = crossterm.input().read_line().unwrap(); println!("\r\nEnter map height:"); - cursor.goto(17, 16); + cursor.goto(17, 17); let height = crossterm.input().read_line().unwrap(); let parsed_width = width.parse::().unwrap(); let parsed_height = height.parse::().unwrap(); let terminal = crossterm.terminal().clear(ClearType::All); - return Size::new(parsed_width, parsed_height); } -fn print_game_stats(map_size: Size, snake_lenght: usize, food_aten: usize, screen: &mut Screen) -{ +fn print_game_stats(map_size: Size, snake_lenght: usize, food_aten: usize, screen: &mut Screen) { let crossterm = Crossterm::new(); let cursor = crossterm.cursor(); @@ -118,12 +111,11 @@ fn print_game_stats(map_size: Size, snake_lenght: usize, food_aten: usize, scree screen.write(format!("Snake Lenght: {}\n\r", snake_lenght).as_ref()); screen.write(format!("Food aten: {}\n\r", snake_lenght).as_ref()); - cursor.goto(0,map_size.height as u16); - cursor.goto(0,map_size.height as u16); + cursor.goto(0, map_size.height as u16); + cursor.goto(0, map_size.height as u16); } -fn game_over_screen() -{ +fn game_over_screen() { let crossterm = Crossterm::new(); let cursor = crossterm.cursor(); @@ -131,9 +123,12 @@ fn game_over_screen() terminal.clear(ClearType::All); - println!("{}", crossterm.style(format!("{}",messages::END_MESSAGE.join("\n\r"))).with(Color::Red)); -// cursor.goto() + println!( + "{}", + crossterm + .style(format!("{}", messages::END_MESSAGE.join("\n\r"))) + .with(Color::Red) + ); + // cursor.goto() cursor.show(); } - - diff --git a/examples/program_examples/snake/src/map.rs b/examples/program_examples/snake/src/map.rs index 3d93451..a1261bf 100644 --- a/examples/program_examples/snake/src/map.rs +++ b/examples/program_examples/snake/src/map.rs @@ -1,57 +1,53 @@ -use super::variables::{Position, Size, Direction }; use super::snake::Snake; +use super::variables::{Direction, Position, Size}; -use crossterm::{Crossterm, Screen}; -use crossterm::cursor::from_screen; -use crossterm::style::{ObjectStyle, StyledObject, Color, style}; +use crossterm::{ + style, Color, ColorType, Crossterm, ObjectStyle, Screen, StyledObject, TerminalCursor, +}; use rand::distributions::{IndependentSample, Range}; -use std::hash::Hash; use std::collections::HashMap; use std::fmt::Display; +use std::hash::Hash; use std::ops::Index; use rand; -pub struct Map -{ +pub struct Map { pub size: Size, pub foot_pos: Position, } -impl Map -{ - pub fn new(size: Size) -> Self - { - Map { size: size, foot_pos: Position::new(0,0) } - } +impl Map { + pub fn new(size: Size) -> Self { + Map { + size: size, + foot_pos: Position::new(0, 0), + } + } // render the map on the screen. - pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap) - { + pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap) { let crossterm = Crossterm::from_screen(screen); let mut cursor = crossterm.cursor(); let mut terminal = crossterm.terminal(); - for y in 0..self.size.height - { - for x in 0..self.size.height - { - if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) - { + for y in 0..self.size.height { + for x in 0..self.size.height { + if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) { cursor.goto(x as u16, y as u16); terminal.write("█"); - }else { - free_positions.insert(format!("{},{}",x,y), Position::new(x,y)); + } else { + free_positions.insert(format!("{},{}", x, y), Position::new(x, y)); } } } } - pub fn is_out_of_bounds(&self, new_pos: Position) -> bool - { - if (new_pos.x == 0 || new_pos.x == self.size.width) || (new_pos.y == 0 || new_pos.y == self.size.height) + pub fn is_out_of_bounds(&self, new_pos: Position) -> bool { + if (new_pos.x == 0 || new_pos.x == self.size.width) + || (new_pos.y == 0 || new_pos.y == self.size.height) { return true; } @@ -59,22 +55,20 @@ impl Map return false; } - pub fn is_food_eaten(&self, snake_head: Position) -> bool - { + pub fn is_food_eaten(&self, snake_head: Position) -> bool { snake_head.x == self.foot_pos.x && snake_head.y == self.foot_pos.y } - pub fn spawn_food(&mut self, free_positions: &HashMap, screen: &Screen) - { + pub fn spawn_food(&mut self, free_positions: &HashMap, screen: &Screen) { let index = Range::new(0, free_positions.len()).ind_sample(&mut rand::thread_rng()); self.foot_pos = free_positions.values().skip(index).next().unwrap().clone(); self.draw_food(screen); } - fn draw_food(&self, screen: &Screen) - { - from_screen(screen).goto(self.foot_pos.x as u16, self.foot_pos.y as u16); - style("$").with(Color::Green).paint(screen); + fn draw_food(&self, screen: &Screen) { + let cursor = TerminalCursor::from_output(&screen.stdout); + cursor.goto(self.foot_pos.x as u16, self.foot_pos.y as u16); + style("$").with(Color::Green).paint(&screen.stdout); screen.stdout.flush(); } -} \ No newline at end of file +} diff --git a/examples/program_examples/snake/src/messages.rs b/examples/program_examples/snake/src/messages.rs index f23f366..4890d8d 100644 --- a/examples/program_examples/snake/src/messages.rs +++ b/examples/program_examples/snake/src/messages.rs @@ -27,4 +27,4 @@ pub const END_MESSAGE: [&str; 11] = " ▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌ ", " ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ", -]; \ No newline at end of file +]; diff --git a/examples/program_examples/snake/src/snake.rs b/examples/program_examples/snake/src/snake.rs index b798161..54e70a3 100644 --- a/examples/program_examples/snake/src/snake.rs +++ b/examples/program_examples/snake/src/snake.rs @@ -1,38 +1,41 @@ -use crossterm::{Screen, Crossterm}; -use crossterm::style::Color; -use super::variables::{Position, Direction, Size}; +use super::variables::{Direction, Position, Size}; +use crossterm::{Color, Crossterm, Screen}; use std::collections::HashMap; -pub struct Part -{ +pub struct Part { pub position: Position, } -impl Part -{ - pub fn new(x: usize, y: usize) -> Part - { - Part { position: Position::new(x,y) } +impl Part { + pub fn new(x: usize, y: usize) -> Part { + Part { + position: Position::new(x, y), + } } } -pub struct Snake -{ +pub struct Snake { pub snake_parts: Vec, pub parent_pos: Position, - map_size: Size + map_size: Size, } -impl Snake -{ - pub fn new(map_size: Size) -> Snake - { - return Snake { map_size, snake_parts: vec![Part::new(9, 10), Part::new(8, 10)], parent_pos: Position::new(0,0) } +impl Snake { + pub fn new(map_size: Size) -> Snake { + return Snake { + map_size, + snake_parts: vec![Part::new(9, 10), Part::new(8, 10)], + parent_pos: Position::new(0, 0), + }; } - pub fn move_snake(&mut self, direction: &Direction, screen: &Screen, free_positions: &mut HashMap ) - { + pub fn move_snake( + &mut self, + direction: &Direction, + screen: &Screen, + free_positions: &mut HashMap, + ) { let crossterm = Crossterm::from_screen(screen); let cursor = crossterm.cursor(); let terminal = crossterm.terminal(); @@ -41,26 +44,28 @@ impl Snake let is_food_eaten = false; for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() { - if index == count - 1 - { + if index == count - 1 { snake_part.position.remove(screen); - free_positions.insert(format!("{},{}",snake_part.position.x,snake_part.position.y), snake_part.position); + free_positions.insert( + format!("{},{}", snake_part.position.x, snake_part.position.y), + snake_part.position, + ); } - if index == 0 - { - self.parent_pos = snake_part.position.clone(); + if index == 0 { + self.parent_pos = snake_part.position.clone(); - match direction { - &Direction::Up => { snake_part.position.y -= 1 }, - &Direction::Down => { snake_part.position.y += 1 }, - &Direction::Left => { snake_part.position.x -= 1 }, - &Direction::Right => { snake_part.position.x += 1 }, - } + match direction { + &Direction::Up => snake_part.position.y -= 1, + &Direction::Down => snake_part.position.y += 1, + &Direction::Left => snake_part.position.x -= 1, + &Direction::Right => snake_part.position.x += 1, + } - free_positions.remove_entry(format!("{},{}",snake_part.position.x,snake_part.position.y).as_str()); - - } else { + free_positions.remove_entry( + format!("{},{}", snake_part.position.x, snake_part.position.y).as_str(), + ); + } else { let new_pos = self.parent_pos.clone(); self.parent_pos = snake_part.position.clone(); snake_part.position = new_pos; @@ -68,33 +73,30 @@ impl Snake } } - pub fn draw_snake(&mut self, screen: &Screen) - { + pub fn draw_snake(&mut self, screen: &Screen) { for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() { snake_part.position.draw("■", screen); } } - pub fn has_eaten_food(&mut self, food_pos: Position) -> bool - { - if self.snake_parts[0].position.x == food_pos.x && self.snake_parts[0].position.y == food_pos.y + pub fn has_eaten_food(&mut self, food_pos: Position) -> bool { + if self.snake_parts[0].position.x == food_pos.x + && self.snake_parts[0].position.y == food_pos.y { - self.snake_parts.push(Part::new(1,1)); + self.snake_parts.push(Part::new(1, 1)); return true; } return false; } - pub fn get_parts(&self) -> &Vec - { + pub fn get_parts(&self) -> &Vec { return &self.snake_parts; } } -pub enum SnakeState -{ +pub enum SnakeState { MovedOutMap, Moved, - AteFood -} \ No newline at end of file + AteFood, +} diff --git a/examples/program_examples/snake/src/variables.rs b/examples/program_examples/snake/src/variables.rs index f227f48..ae5e97e 100644 --- a/examples/program_examples/snake/src/variables.rs +++ b/examples/program_examples/snake/src/variables.rs @@ -1,61 +1,55 @@ extern crate crossterm; -use self::crossterm::terminal::{self, ClearType}; -use self::crossterm::style::{Color, StyledObject, ObjectStyle, style }; -use self::crossterm::cursor; -use self::crossterm::Screen; +use self::crossterm::{ + cursor, style, ClearType, Color, Crossterm, ObjectStyle, Screen, StyledObject, TerminalCursor, +}; -use std::fmt::Debug; use std::fmt; +use std::fmt::Debug; -#[derive(Copy, Clone,Debug)] -pub enum Direction -{ +#[derive(Copy, Clone, Debug)] +pub enum Direction { Up = 0, Down = 1, Left = 2, - Right = 3 + Right = 3, } #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] -pub struct Position -{ +pub struct Position { pub x: usize, - pub y: usize + pub y: usize, } -impl Position -{ - pub fn new(x: usize, y: usize) -> Position - { +impl Position { + pub fn new(x: usize, y: usize) -> Position { Position { x, y } } - pub fn draw(&self, val: &str, screen: &Screen) - { - cursor::from_screen(screen).goto(self.x as u16, self.y as u16); - style(val).with(Color::Red).paint(&screen); + pub fn draw(&self, val: &str, screen: &Screen) { + let cursor = TerminalCursor::from_output(&screen.stdout); + cursor.goto(self.x as u16, self.y as u16); + + style(val).with(Color::Red).paint(&screen.stdout); screen.stdout.flush(); } - pub fn remove(&self, screen: &Screen) - { - cursor::from_screen(screen).goto(self.x as u16, self.y as u16); - terminal::from_screen(&screen).write(" "); + pub fn remove(&self, screen: &Screen) { + let crossterm = Crossterm::from_screen(screen); + + crossterm.cursor().goto(self.x as u16, self.y as u16); + crossterm.terminal().write(" "); } } #[derive(Copy, Clone)] -pub struct Size -{ +pub struct Size { pub width: usize, - pub height: usize + pub height: usize, } -impl Size -{ - pub fn new(width: usize, height: usize) -> Size - { - Size {width,height} +impl Size { + pub fn new(width: usize, height: usize) -> Size { + Size { width, height } } -} \ No newline at end of file +} diff --git a/examples/terminal/raw_mode.rs b/examples/raw_mode.rs similarity index 75% rename from examples/terminal/raw_mode.rs rename to examples/raw_mode.rs index 09cd308..c51a512 100644 --- a/examples/terminal/raw_mode.rs +++ b/examples/raw_mode.rs @@ -1,8 +1,6 @@ extern crate crossterm; -use crossterm::style::{style, Color}; -use crossterm::terminal::{self, ClearType}; -use crossterm::{Crossterm, Screen}; +use crossterm::{Crossterm, Screen, terminal, ClearType, Color, style}; use std::io::{stdout, Write}; use std::{thread, time}; @@ -30,7 +28,7 @@ fn print_wait_screen(screen: &mut Screen) { style(format!("{} of the 5 items processed", i)) .with(Color::Red) .on(Color::Blue) - .paint(&screen); + .paint(&screen.stdout); screen.stdout.flush(); // 1 second delay @@ -41,9 +39,16 @@ fn print_wait_screen(screen: &mut Screen) { pub fn print_wait_screen_on_alternate_window() { let screen = Screen::default(); + // by passing in 'true' the alternate screen will be in raw modes. if let Ok(ref mut alternate) = screen.enable_alternate_modes(true) { print_wait_screen(&mut alternate.screen); - } - drop(screen); + } // <- drop alternate screen; this will cause the alternate screen to drop. + + drop(screen); // <- drop screen; this will cause raw mode to be turned off. + println!("Whe are back at the main screen"); } + +fn main() { + print_wait_screen_on_alternate_window(); +} \ No newline at end of file diff --git a/examples/color/mod.rs b/examples/style.rs similarity index 97% rename from examples/color/mod.rs rename to examples/style.rs index feb027e..ebe8fc8 100644 --- a/examples/color/mod.rs +++ b/examples/style.rs @@ -3,8 +3,7 @@ //! extern crate crossterm; -use self::crossterm::style::{color, style, Color}; -use self::crossterm::{terminal, Screen}; +use self::crossterm::{style, Color}; /// print some red font | demonstration. pub fn paint_foreground() { @@ -231,3 +230,9 @@ pub fn print_supported_colors() { ); } } + +fn main() { + print_all_background_colors(); + print_all_foreground_colors(); + print_font_with_attributes(); +} diff --git a/examples/terminal/terminal.rs b/examples/terminal.rs similarity index 97% rename from examples/terminal/terminal.rs rename to examples/terminal.rs index 644b803..18d276f 100644 --- a/examples/terminal/terminal.rs +++ b/examples/terminal.rs @@ -4,8 +4,8 @@ extern crate crossterm; -use crossterm::cursor; -use crossterm::terminal::{terminal, ClearType}; +use crossterm::{cursor, terminal, ClearType}; + fn print_test_data() { for i in 0..100 { println!("Test data to test terminal: {}", i); @@ -125,3 +125,7 @@ pub fn exit() { let terminal = terminal(); terminal.exit(); } + +fn main() { + clear_all_lines(); +} diff --git a/examples/terminal/mod.rs b/examples/terminal/mod.rs deleted file mode 100644 index c6adf4b..0000000 --- a/examples/terminal/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -/// Examples of actions that could be performed on the alternatescreen. -pub mod alternate_screen; - -/// Examples of actions that could be performed on the terminal. -pub mod terminal; - -// Raw screen -pub mod raw_mode; diff --git a/src/common/commands/shared_commands.rs b/src/common/commands/shared_commands.rs deleted file mode 100644 index 9fcf864..0000000 --- a/src/common/commands/shared_commands.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! A module which contains the commands that can be used for both unix and windows 10 systems because they support ANSI escape codes - -use super::{IAlternateScreenCommand, TerminalOutput}; - -use std::io::Result; - -/// This command is used for switching to alternate screen and back to main screen. -pub struct ToAlternateScreenCommand; - -impl ToAlternateScreenCommand { - pub fn new() -> ToAlternateScreenCommand { - ToAlternateScreenCommand - } -} - -impl IAlternateScreenCommand for ToAlternateScreenCommand { - /// enable alternate screen. - fn enable(&self, stdout: &mut TerminalOutput) -> Result<()> { - stdout.write_str(csi!("?1049h"))?; - Ok(()) - } - - /// disable alternate screen. - fn disable(&self, stdout: &TerminalOutput) -> Result<()> { - stdout.write_str(csi!("?1049l"))?; - Ok(()) - } -} diff --git a/src/common/crossterm.rs b/src/common/crossterm.rs deleted file mode 100644 index 36e5217..0000000 --- a/src/common/crossterm.rs +++ /dev/null @@ -1,160 +0,0 @@ -use {Screen, TerminalOutput}; - -use super::super::cursor; -use super::super::input; -use super::super::style; -use super::super::terminal; - -use std::fmt::Display; -use std::sync::Arc; - -/// This type could be used to access the `cursor, terminal, color, input, styling` module more easily. -/// You need to pass a reference to the screen whereon you want to perform the actions to the `Crossterm` type. -/// -/// If you want to use the default screen you could do it like this: -/// -/// ```rust -/// extern crate crossterm; -/// use crossterm::{Crossterm, Screen}; -/// -/// let crossterm = Crossterm::new(&Screen::default()); -/// let cursor = crossterm.cursor(); -/// ``` -/// -/// If you want to perform actions on the `AlternateScreen` make sure to pass a reference to the screen of the `AlternateScreen`. -/// -/// ``` -/// extern crate crossterm; -/// use crossterm::{Crossterm, Screen}; -/// -/// let main_screen = Screen::default(); -/// -/// if let Ok(alternate_srceen) = main_screen.enable_alternate_modes(false) -/// { -/// let crossterm = Crossterm::new(&alternate_screen.screen); -/// let cursor = crossterm.cursor(); -/// } -/// ``` -pub struct Crossterm { - stdout: Option>, -} - -impl<'crossterm> Crossterm { - /// Create a new instance of `Crossterm` - pub fn new() -> Crossterm { - Crossterm { stdout: None } - } - - /// Create a new instance of `Crossterm` - pub fn from_screen(screen: &Screen) -> Crossterm { - Crossterm { - stdout: Some(screen.stdout.clone()), - } - } - - /// Get a `TerminalCursor` implementation whereon cursor related actions can be performed. - /// - /// ```rust - /// extern crate crossterm; - /// use crossterm::Crossterm; - /// - /// let crossterm = Crossterm::new(); - /// let cursor = crossterm.cursor(); - /// ``` - pub fn cursor(&self) -> cursor::TerminalCursor { - match &self.stdout { - None => cursor::TerminalCursor::new(), - Some(stdout) => cursor::TerminalCursor::from_output(&stdout), - } - } - - /// Get a `TerminalInput` implementation whereon terminal related actions can be performed. - /// - /// ```rust - /// extern crate crossterm; - /// use crossterm::Crossterm; - /// - /// let crossterm = Crossterm::new(); - /// let input = crossterm.input(); - /// ``` - pub fn input(&self) -> input::TerminalInput { - match &self.stdout { - None => input::TerminalInput::new(), - Some(stdout) => input::TerminalInput::from_output(&stdout), - } - } - - /// Get a `Terminal` implementation whereon terminal related actions can be performed. - /// - /// ```rust - /// extern crate crossterm; - /// use crossterm::Crossterm; - /// - /// let crossterm = Crossterm::new(); - /// let mut terminal = crossterm.terminal(); - /// ``` - pub fn terminal(&self) -> terminal::Terminal { - match &self.stdout { - None => terminal::Terminal::new(), - Some(stdout) => terminal::Terminal::from_output(&stdout), - } - } - - /// Get a `TerminalColor` implementation whereon color related actions can be performed. - /// - /// ```rust - /// extern crate crossterm; - /// use crossterm::Crossterm; - /// - /// let crossterm = Crossterm::new(); - /// let mut terminal = crossterm.color(); - /// ``` - pub fn color(&self) -> style::TerminalColor { - match &self.stdout { - None => style::TerminalColor::new(), - Some(stdout) => style::TerminalColor::from_output(&stdout), - } - } - - /// This could be used to style a `Displayable` type with colors and attributes. - /// - /// ```rust - /// extern crate crossterm; - /// use crossterm::Crossterm; - /// - /// let crossterm = Crossterm::new(); - /// - /// // get an styled object which could be painted to the terminal. - /// let styled_object = crossterm.style("Some Blue colored text on black background") - /// .with(Color::Blue) - /// .on(Color::Black); - /// - /// // print the styled font * times to the current screen. - /// for i in 1..10 - /// { - /// println!("{}", styled_object); - /// } - /// ``` - pub fn style(&self, val: D) -> style::StyledObject - where - D: Display, - { - style::ObjectStyle::new().apply_to(val) - } -} - -impl From> for Crossterm { - fn from(stdout: Arc) -> Self { - Crossterm { - stdout: Some(stdout), - } - } -} - -impl From for Crossterm { - fn from(screen: Screen) -> Self { - Crossterm { - stdout: Some(screen.stdout.clone()), - } - } -} diff --git a/src/common/macros.rs b/src/common/macros.rs deleted file mode 100644 index 3659217..0000000 --- a/src/common/macros.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// This macro will take an ANSI input and combines it with some default ANSI characters and returns the result -#[macro_export] -macro_rules! csi { - ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index 6387220..0000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! This module contains some code that can be used for all module in this library. - -#[macro_use] -pub mod macros; -pub mod commands; -pub mod error; -pub mod functions; -pub mod screen; -pub mod traits; - -mod crossterm; - -pub use self::crossterm::Crossterm; -use TerminalOutput; diff --git a/src/common/traits.rs b/src/common/traits.rs deleted file mode 100644 index 2357873..0000000 --- a/src/common/traits.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// This trait can be used to create an empty instance of an struct. -pub trait Empty { - fn empty() -> Self; -} diff --git a/src/crossterm.rs b/src/crossterm.rs new file mode 100644 index 0000000..694c755 --- /dev/null +++ b/src/crossterm.rs @@ -0,0 +1,207 @@ +use crossterm_utils::TerminalOutput; + +use std::fmt::Display; +use std::io::{self, Write}; +use std::sync::Arc; + +/// This type offers a easy way to use functionalities like `cursor, terminal, color, input, styling`. +/// +/// To get a cursor instance to perform cursor related actions, you can do the following: +/// +/// ```rust +/// let crossterm = Crossterm::new(); +/// let cursor = crossterm.cursor(); +/// ``` +/// +/// If you want to perform actions on the `AlternateScreen` make sure to pass a reference to the screen of the `AlternateScreen`. +/// If you don't do this you actions won't be performed on the alternate screen but on the main screen. +/// +/// ``` +/// let main_screen = Screen::default(); +/// +/// if let Ok(alternate_srceen) = main_screen.enable_alternate_modes(false) +/// { +/// let crossterm = Crossterm::new(&alternate_screen.screen); +/// let cursor = crossterm.cursor(); +/// } +/// ``` +/// +/// # Remark +/// - depending on the feature flags you've enabled you are able to call methods of this type. +/// - checkout the crossterm book for more information about feature flags or alternate screen. +pub struct Crossterm { + stdout: Option>, +} + +impl<'crossterm> Crossterm { + /// Create a new instance of `Crossterm` + pub fn new() -> Crossterm { + Crossterm { stdout: None } + } + + /// Create a new instance of `Crossterm` + #[cfg(feature = "screen")] + pub fn from_screen(screen: &crossterm_screen::Screen) -> Crossterm { + Crossterm { + stdout: Some(screen.stdout.clone()), + } + } + + /// Get a `TerminalCursor` implementation whereon cursor related actions can be performed. + /// + /// ```rust + /// let crossterm = Crossterm::new(); + /// let cursor = crossterm.cursor(); + /// ``` + #[cfg(feature = "cursor")] + pub fn cursor(&self) -> crossterm_cursor::TerminalCursor { + match &self.stdout { + None => crossterm_cursor::TerminalCursor::new(), + Some(stdout) => crossterm_cursor::TerminalCursor::from_output(&stdout), + } + } + + /// Get a `TerminalInput` implementation whereon terminal related actions can be performed. + /// + /// ```rust + /// let crossterm = Crossterm::new(); + /// let input = crossterm.input(); + /// ``` + #[cfg(feature = "input")] + pub fn input(&self) -> crossterm_input::TerminalInput { + match &self.stdout { + None => crossterm_input::TerminalInput::new(), + Some(stdout) => crossterm_input::TerminalInput::from_output(&stdout), + } + } + + /// Get a `Terminal` implementation whereon terminal related actions can be performed. + /// + /// ```rust + /// let crossterm = Crossterm::new(); + /// let mut terminal = crossterm.terminal(); + /// ``` + #[cfg(feature = "terminal")] + pub fn terminal(&self) -> crossterm_terminal::Terminal { + match &self.stdout { + None => crossterm_terminal::Terminal::new(), + Some(stdout) => crossterm_terminal::Terminal::from_output(&stdout), + } + } + + /// Get a `TerminalColor` implementation whereon color related actions can be performed. + /// + /// ```rust + /// let crossterm = Crossterm::new(); + /// let mut terminal = crossterm.color(); + /// ``` + #[cfg(feature = "style")] + pub fn color(&self) -> crossterm_style::TerminalColor { + match &self.stdout { + None => crossterm_style::TerminalColor::new(), + Some(stdout) => crossterm_style::TerminalColor::from_output(&stdout), + } + } + + /// This could be used to style any type implementing `Display` with colors and attributes. + /// + /// # Example + /// ```rust + /// let crossterm = Crossterm::new(); + /// + /// // get an styled object which could be painted to the terminal. + /// let styled_object = crossterm.style("Some Blue colored text on black background") + /// .with(Color::Blue) + /// .on(Color::Black); + /// + /// // print the styled font * times to the current screen. + /// for i in 1..10 + /// { + /// println!("{}", styled_object); + /// } + /// ``` + /// + /// # Remark + /// `val`: any type implementing Display e.g. string. + #[cfg(feature = "style")] + pub fn style(&self, val: D) -> crossterm_style::StyledObject + where + D: Display, + { + crossterm_style::ObjectStyle::new().apply_to(val) + } + + /// This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting. + /// + /// ``` rust + /// style("Some colored text") + /// .with(Color::Blue) + /// .on(Color::Black) + /// .paint(&screen); + /// ``` + /// + /// You should take note that `StyledObject` implements `Display`. You don't need to call paint unless you are on alternate screen. + /// Checkout `StyledObject::into_displayable()` for more information about this. + #[cfg(feature = "style")] + #[cfg(feature = "screen")] + pub fn paint<'a, D: Display + 'a>( + &self, + styled_object: crossterm_style::StyledObject, + ) -> super::crossterm_utils::Result<()> { + let colored_terminal = match &self.stdout { + Some(stdout) => super::TerminalColor::from_output(stdout), + None => super::TerminalColor::new(), + }; + + let mut reset = false; + + if let Some(bg) = styled_object.object_style.bg_color { + colored_terminal.set_bg(bg)?; + reset = true; + } + + if let Some(fg) = styled_object.object_style.fg_color { + colored_terminal.set_fg(fg)?; + reset = true; + } + + match self.stdout { + None => { + let mut stdout = io::stdout(); + + for attr in styled_object.object_style.attrs.iter() { + write!(stdout, "{}", format!(csi!("{}m"), *attr as i16))?; + reset = true; + } + + write!(stdout, "{}", styled_object.content)?; + } + Some(ref stdout) => { + for attr in styled_object.object_style.attrs.iter() { + stdout.write_string(format!(csi!("{}m"), *attr as i16))?; + reset = true; + } + + use std::fmt::Write; + let mut content = String::new(); + write!(content, "{}", styled_object.content)?; + stdout.write_string(content)?; + stdout.flush()?; + } + } + + if reset { + colored_terminal.reset()?; + } + + Ok(()) + } +} + +impl From> for Crossterm { + fn from(stdout: Arc) -> Self { + Crossterm { + stdout: Some(stdout), + } + } +} diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs deleted file mode 100644 index 570fc96..0000000 --- a/src/kernel/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! All platform specific (unsafe) code will be handled in this module. - -#[cfg(unix)] -pub mod unix_kernel; -#[cfg(windows)] -pub mod windows_kernel; diff --git a/src/kernel/unix_kernel/mod.rs b/src/kernel/unix_kernel/mod.rs deleted file mode 100644 index debd8b6..0000000 --- a/src/kernel/unix_kernel/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! This module contains all the `unix` (unsafe) code. - -pub mod terminal; diff --git a/src/kernel/unix_kernel/terminal.rs b/src/kernel/unix_kernel/terminal.rs deleted file mode 100644 index 09e8f38..0000000 --- a/src/kernel/unix_kernel/terminal.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! This module contains all `unix` specific terminal related logic. - -use self::libc::{c_ushort, ioctl, STDOUT_FILENO, TCSADRAIN, TIOCGWINSZ}; - -pub use libc::termios; -use {libc, Screen}; - -use std::fs; -use std::io::{self, Error, ErrorKind, Read, Write}; -use std::os::unix::io::AsRawFd; -use termios::{tcsetattr, Termios}; - -/// A representation of the size of the current terminal. -#[repr(C)] -#[derive(Debug)] -pub struct UnixSize { - /// number of rows - pub rows: c_ushort, - /// number of columns - pub cols: c_ushort, - pub ws_xpixel: c_ushort, - pub ws_ypixel: c_ushort, -} - -/// Get the current terminal size. -pub fn terminal_size() -> (u16, u16) { - // http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc - let us = UnixSize { - rows: 0, - cols: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - let r = unsafe { ioctl(STDOUT_FILENO, TIOCGWINSZ, &us) }; - - if r == 0 { - // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results. - (us.cols, us.rows) - } else { - (0, 0) - } -} - -// maybe this coudl be used over ANSI escape code -//pub fn set_terminal_size() -> io::Result<(u16,u16)> -//{ -// let new_size = UnixSize { -// rows: 40, -// cols: 40, -// ws_xpixel: 0, -// ws_ypixel: 0, -// }; -// -// let r = unsafe { ioctl(STDOUT_FILENO, TIOCSWINSZ, &new_size) }; -// -// if r == 0 { -// // because crossterm works starts counting at 0 and unix terminal starts at cell 1 you have subtract one to get 0-based results. -// (us.cols, us.rows) -// } else { -// Err(ErrorKind::Other, "Could not resize try ansi escape codes") -// (0, 0) -// } -//} - -pub fn pos() -> io::Result<(u16, u16)> { - let _screen = Screen::new(false); - - // if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition. - // I am not completely happy with this approach so feel free to find an other way. - - unsafe { - if !RAW_MODE_ENABLED_BY_USER || !RAW_MODE_ENABLED_BY_SYSTEM { - // set this boolean so that we know that the systems has enabled raw mode. - RAW_MODE_ENABLED_BY_SYSTEM = true; - into_raw_mode()?; - } - } - - // Where is the cursor? - // Use `ESC [ 6 n`. - let mut stdout = io::stdout(); - - // Write command - stdout.write_all(b"\x1B[6n")?; - stdout.flush()?; - - let mut buf = [0u8; 2]; - - // Expect `ESC[` - io::stdin().read_exact(&mut buf)?; - if buf[0] != 0x1B || buf[1] as char != '[' { - return Err(Error::new(ErrorKind::Other, "test")); - } - - // Read rows and cols through a ad-hoc integer parsing function - let read_num: fn() -> Result<(i32, char), Error> = || -> Result<(i32, char), Error> { - let mut num = 0; - let mut c: char; - - loop { - let mut buf = [0u8; 1]; - io::stdin().read_exact(&mut buf)?; - c = buf[0] as char; - if let Some(d) = c.to_digit(10) { - num = if num == 0 { 0 } else { num * 10 }; - num += d as i32; - } else { - break; - } - } - - Ok((num, c)) - }; - - // Read rows and expect `;` - let (rows, c) = read_num()?; - if c != ';' { - return Err(Error::new(ErrorKind::Other, "test")); - } - - // Read cols - let (cols, c) = read_num()?; - - // Expect `R` - let res = if c == 'R' { - Ok(((cols - 1) as u16, (rows - 1) as u16)) - } else { - return Err(Error::new(ErrorKind::Other, "test")); - }; - - // If raw mode is enabled from else where in the application (by the user) we do not want to disable raw modes. - // I am not completely happy with this approach so feel free to find an other way. - unsafe { - if RAW_MODE_ENABLED_BY_SYSTEM && !RAW_MODE_ENABLED_BY_USER { - RAW_MODE_ENABLED_BY_SYSTEM = false; - disable_raw_mode()?; - } - } - - res -} - -static mut ORIGINAL_TERMINAL_MODE: Option = None; -static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false; -pub static mut RAW_MODE_ENABLED_BY_USER: bool = false; - -/// Transform the given mode into an raw mode (non-canonical) mode. -pub fn make_raw(termios: &mut Termios) { - extern "C" { - pub fn cfmakeraw(termptr: *mut Termios); - } - unsafe { cfmakeraw(termios) } -} - -pub fn into_raw_mode() -> io::Result<()> { - let tty_f; - - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() - } - }; - - let mut termios = Termios::from_fd(fd)?; - let original = termios.clone(); - - unsafe { - if ORIGINAL_TERMINAL_MODE.is_none() { - ORIGINAL_TERMINAL_MODE = Some(original.clone()) - } - } - - make_raw(&mut termios); - tcsetattr(fd, TCSADRAIN, &termios)?; - - Ok(()) -} - -pub fn disable_raw_mode() -> io::Result<()> { - let tty_f; - - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() - } - }; - - if let Some(original) = unsafe { ORIGINAL_TERMINAL_MODE } { - tcsetattr(fd, TCSADRAIN, &original)?; - } - Ok(()) -} - -/// Get the TTY device. -/// -/// This allows for getting stdio representing _only_ the TTY, and not other streams. -pub fn get_tty() -> io::Result { - let mut tty_f: fs::File = unsafe { ::std::mem::zeroed() }; - - let _fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() - } - }; - - Ok(tty_f) -} - -pub fn read_char() -> io::Result { - let mut buf = [0u8; 20]; - - // get tty raw handle. - let tty_f; - - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() - } - }; - - let mut termios = Termios::from_fd(fd)?; - let original = termios; - - make_raw(&mut termios); - tcsetattr(fd, TCSADRAIN, &termios)?; - - // read input and convert it to char - let rv = unsafe { - let read = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, 20); - - if read < 0 { - Err(io::Error::last_os_error()) - } else if buf[0] == b'\x03' { - Err(io::Error::new( - io::ErrorKind::Interrupted, - "read interrupted", - )) - } else { - let mut pressed_char = Ok(' '); - - if let Ok(s) = ::std::str::from_utf8(&buf[..read as usize]) { - if let Some(c) = s.chars().next() { - pressed_char = Ok(c); - } - } else { - pressed_char = Err(io::Error::new( - io::ErrorKind::Interrupted, - "Could not parse char to utf8 char", - )); - } - - pressed_char - } - }; - - tcsetattr(fd, TCSADRAIN, &original)?; - - // if the user hit ^C we want to signal SIGINT to outselves. - if let Err(ref err) = rv { - if err.kind() == io::ErrorKind::Interrupted { - unsafe { - libc::raise(libc::SIGINT); - } - } - } - - rv -} - -pub fn exit() { - ::std::process::exit(0); -} diff --git a/src/kernel/windows_kernel/ansi_support.rs b/src/kernel/windows_kernel/ansi_support.rs deleted file mode 100644 index 9e73b89..0000000 --- a/src/kernel/windows_kernel/ansi_support.rs +++ /dev/null @@ -1,65 +0,0 @@ -//! This module handles the enabling `ANSI escape codes` for windows terminals. - -use std::sync::{Once, ONCE_INIT}; - -static mut HAS_BEEN_TRIED_TO_ENABLE: bool = false; -static mut IS_ANSI_ON_WINDOWS_ENABLED: Option = None; -static mut DOES_WINDOWS_SUPPORT_ANSI: Option = None; -use common::commands::win_commands::EnableAnsiCommand; -use common::commands::IEnableAnsiCommand; - -static ENABLE_ANSI: Once = ONCE_INIT; - -/// Try enable `ANSI escape codes` and return the result. -pub fn try_enable_ansi_support() -> bool { - ENABLE_ANSI.call_once(|| { - let command = EnableAnsiCommand::new(); - let success = match command.enable() { - Ok(success) => success, - Err(_) => false, - }; - - set_is_windows_ansi_supportable(success); - set_ansi_enabled(success); - has_been_tried_to_enable(true); - }); - windows_supportable() -} - -/// Get whether ansi has been enabled. -pub fn ansi_enabled() -> bool { - unsafe { IS_ANSI_ON_WINDOWS_ENABLED.unwrap_or_else(|| false) } -} - -/// Get whether windows supports ansi -pub fn windows_supportable() -> bool { - unsafe { DOES_WINDOWS_SUPPORT_ANSI.unwrap_or_else(|| false) } -} - -/// Get whether ansi has been tried to enable before. -pub fn has_been_tried_to_enable_ansi() -> bool { - unsafe { - return HAS_BEEN_TRIED_TO_ENABLE; - } -} - -/// Set the is ansi escape property enabled or disabled. So whe can determine if the ansi escape codes are enabled. -pub fn set_ansi_enabled(is_enabled: bool) { - unsafe { - IS_ANSI_ON_WINDOWS_ENABLED = Some(is_enabled); - } -} - -/// Set the is_windows_ansi_supportable property. So whe can determine whether windows supports ansi. -fn set_is_windows_ansi_supportable(is_enabled: bool) { - unsafe { - DOES_WINDOWS_SUPPORT_ANSI = Some(is_enabled); - } -} - -/// Set the has_been_tried_to_enable property. So we can determine whether ansi has been tried to enable before. -fn has_been_tried_to_enable(has_been_tried: bool) { - unsafe { - HAS_BEEN_TRIED_TO_ENABLE = has_been_tried; - } -} diff --git a/src/kernel/windows_kernel/mod.rs b/src/kernel/windows_kernel/mod.rs deleted file mode 100644 index 8deb32f..0000000 --- a/src/kernel/windows_kernel/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! This module contains the `windows` (unsafe) logic. - -pub mod ansi_support; -mod cursor; -#[allow(unused)] -mod reading; -pub mod writing; - -pub use self::cursor::Cursor; -pub use crossterm_winapi::{ - Console, ConsoleMode, Coord, Handle, HandleType, ScreenBuffer, ScreenBufferInfo, Size, - WindowPositions, -}; - -/// Exit the current process. -pub fn exit() { - ::std::process::exit(256); -} diff --git a/src/kernel/windows_kernel/reading.rs b/src/kernel/windows_kernel/reading.rs deleted file mode 100644 index b84fdd0..0000000 --- a/src/kernel/windows_kernel/reading.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crossterm_winapi::Handle; - -use std::{ - io::{self, Write}, - mem, -}; - -use winapi::{ - shared::minwindef::{LPVOID, ULONG}, - um::{ - consoleapi::ReadConsoleW, - wincon::{CONSOLE_READCONSOLE_CONTROL, PCONSOLE_READCONSOLE_CONTROL}, - }, -}; - -/// Could be used to read a line from the stdin. -/// Note that this is a blocking call and it continues when user pressed enter. -pub fn read_line(buf: &mut Vec) -> io::Result { - let handle = Handle::current_in_handle()?; - - let mut utf16 = vec![0u16; 0x1000]; - let mut num = 0; - let mut input_control = readconsole_input_control(CTRL_Z_MASK); - - unsafe { - ReadConsoleW( - handle, - utf16.as_mut_ptr() as LPVOID, - utf16.len() as u32, - &mut num, - &mut input_control as PCONSOLE_READCONSOLE_CONTROL, - ) - }; - - utf16.truncate(num as usize); - - let mut data = match String::from_utf16(&utf16) { - Ok(utf8) => utf8.into_bytes(), - Err(..) => return Err(invalid_encoding()), - }; - - if let Some(&last_byte) = data.last() { - if last_byte == CTRL_Z { - data.pop(); - } - }; - - let a = &data - .into_iter() - .filter(|&x| x != 10 || x != 13) - .collect::>(); - - buf.write(a)?; - Ok(num as usize) -} - -pub fn readconsole_input_control(wakeup_mask: ULONG) -> CONSOLE_READCONSOLE_CONTROL { - CONSOLE_READCONSOLE_CONTROL { - nLength: mem::size_of::() as ULONG, - nInitialChars: 0, - dwCtrlWakeupMask: wakeup_mask, - dwControlKeyState: 0, - } -} - -fn invalid_encoding() -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, "text was not valid unicode") -} - -const CTRL_Z: u8 = 0x1A; -const CTRL_Z_MASK: ULONG = 0x4000000; //1 << 0x1A diff --git a/src/kernel/windows_kernel/writing.rs b/src/kernel/windows_kernel/writing.rs deleted file mode 100644 index 977346e..0000000 --- a/src/kernel/windows_kernel/writing.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! This module contains the logic to write to the terminal. - -use winapi::ctypes::c_void; -use winapi::shared::ntdef::NULL; -use winapi::um::consoleapi::WriteConsoleW; -use winapi::um::wincon::{WriteConsoleOutputA, CHAR_INFO, COORD, PSMALL_RECT}; -use winapi::um::winnt::HANDLE; - -use crossterm_winapi::{is_true, ScreenBuffer}; - -use std::io::{self, Result}; -use std::str; - -/// Write console output. -#[allow(unused)] -pub fn write_console_output( - write_buffer: &HANDLE, - copy_buffer: &mut [CHAR_INFO; 160], - buffer_size: COORD, - buffer_coord: COORD, - source_buffer: PSMALL_RECT, -) -> Result<()> { - unsafe { - if !is_true( - WriteConsoleOutputA( - *write_buffer, // screen buffer to write to - copy_buffer.as_mut_ptr(), // buffer to copy into - buffer_size, // col-row size of chiBuffer - buffer_coord, // top left dest. cell in chiBuffer - source_buffer, - ), // screen buffer source rectangle - ) { - return Err(io::Error::last_os_error()); - } - } - - Ok(()) -} - -/// Write utf8 buffer to console. -pub fn write_char_buffer(handle: &HANDLE, buf: &[u8]) -> io::Result { - // 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 = utf8.encode_utf16().collect(); - let utf16_ptr: *const c_void = utf16.as_ptr() as *const _ as *const c_void; - - // get buffer info - match ScreenBuffer::from(*handle).info() { - Ok(csbi) => { - // get current position - let _current_pos = COORD { - X: csbi.cursor_pos().x, - Y: csbi.cursor_pos().y, - }; - - let mut cells_written: u32 = 0; - // write to console - unsafe { - if !is_true(WriteConsoleW( - *handle, - utf16_ptr, - utf16.len() as u32, - &mut cells_written, - NULL, - )) { - return Err(io::Error::last_os_error()); - } - } - Ok(utf8.as_bytes().len()) - } - Err(e) => Err(e), - } -} diff --git a/src/lib.rs b/src/lib.rs index 2c6ba1c..4414d7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,347 +1,31 @@ -/// # Crossterm | cross-platform terminal manipulating library. -/// ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] -/// -/// [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]: ./LICENSE -/// -/// [s3]: https://docs.rs/crossterm/badge.svg -/// [l3]: https://docs.rs/crossterm/ -/// -/// [s6]: https://tokei.rs/b1/github/TimonPost/crossterm?category=code -/// [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master -/// -/// Ever got disappointed when a terminal library for rust was only written for UNIX systems? -/// Crossterm provides the same core functionalities 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) -/// -/// ## Table of contents: -/// - [Getting started](#getting-started) -/// - [Useful links](#useful-links) -/// - [Features](#features) -/// - [Examples](#examples) -/// - [Crossterm Wrapper](#crossterm-type--see-more) -/// - [Styling](#crossterm-type--see-more) -/// - [Cursor](#cursor--see-more) -/// - [Input](#input--see-more) -/// - [Terminal](#terminal--see-more) -/// - [Tested Terminals](#tested-terminals) -/// - [Notice](#notice) -/// - [Todo](#todo) -/// - [Contributing](#contributing) -/// - [Authors](#authors) -/// - [License](#license) -/// -/// ## Getting Started -/// -/// This documentation is only for Crossterm version `0.5.^` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. -/// -/// Add the Crossterm package to your `Cargo.toml` file. -/// -/// ``` -/// [dependencies] -/// crossterm = "0.5.4" -/// -/// ``` -/// And import the Crossterm modules you want to use. -/// -/// ```rust -/// extern crate crossterm; -/// -/// // this module is used for styling the terminal -/// use crossterm::style::*; -/// // this module is used for cursor related actions -/// use crossterm::cursor::*; -/// // this module is used for terminal related actions -/// use crossterm::terminal::*; -/// // this module is used for input related actions -/// use crossterm::input::*; -/// -/// ``` -/// -/// ### Useful Links -/// -/// - [Book](http://atcentra.com/crossterm/) -/// - [Documentation](https://docs.rs/crossterm/) -/// - [Crates.io](https://crates.io/crates/crossterm) -/// - [Program Examples](https://github.com/TimonPost/crossterm/tree/master/examples/program_examples) -/// - [Examples](https://github.com/TimonPost/crossterm/tree/master/examples) -/// -/// ## Features -/// These are the features from this crate: -/// -/// - Cross-platform -/// - Everything is multithreaded (Send, Sync) -/// - Detailed documentation on every item -/// - Very few dependenties. -/// - Cursor. -/// - Moving _n_ times Up, Down, Left, Right -/// - Goto a certain position -/// - Get cursor position -/// - Storing the current cursor position and resetting to that stored cursor position later -/// - Hiding an showing the cursor -/// - Control over blinking of the terminal cursor (only some terminals are supporting this) -/// - Styled output -/// - Foreground color (16 base colors) -/// - Background color (16 base colors) -/// - 256 color support (Windows 10 and UNIX only) -/// - RGB support (Windows 10 and UNIX only) -/// - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) -/// - Terminal -/// - Clearing (all lines, current line, from cursor down and up, until new line) -/// - Scrolling (Up, down) -/// - Get the size of the terminal -/// - Set the size of the terminal -/// - Alternate screen -/// - Raw screen -/// - Exit the current process -/// - Input -/// - Read character -/// - Read line -/// - Read async -/// - Read async until -/// - Wait for key event (terminal pause) -/// -/// ## Examples -/// These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/TimonPost/crossterm/blob/master/examples/) for more. -/// -/// ### Crossterm Type | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/some_types/mod.rs) -/// This is a wrapper for all the modules crossterm provides like terminal, cursor, styling and input. -/// -/// ```rust -/// // screen wheron the `Crossterm` methods will be executed. -/// let crossterm = Crossterm::new(); -/// -/// // get instance of the modules, whereafter you can use the methods the particulary module provides. -/// let color = crossterm.color(); -/// let cursor = crossterm.cursor(); -/// let terminal = crossterm.terminal(); -/// -/// // styling -/// println!("{}", crossterm.style("Black font on Green background color").with(Color::Black).on(Color::Green)); -/// -/// ``` -/// ### Styled Font | [see more](http://atcentra.com/crossterm/styling.html) -/// This module provides the functionalities to style the terminal. -/// ```rust -/// use crossterm::style::{Color, style}; -/// -/// // store objcets so it could be painted later to the screen. -/// let style1 = style("Some Blue font on Black background").with(Color::Blue).on(Color::Black); -/// let style2 = style("Some Red font on Yellow background").with(Color::Red).on(Color::Yellow); -/// -/// // styling font with (Windows 10 and UNIX systems) -/// let normal = style("Normal text"); -/// let bold = style("Bold text").bold(); -/// let italic = style("Italic text").italic(); -/// let slow_blink = style("Slow blinking text").slow_blink(); -/// let rapid_blink = style("Rapid blinking text").rapid_blink(); -/// let hidden = style("Hidden text").hidden(); -/// let underlined = style("Underlined text").underlined(); -/// let reversed = style("Reversed text").reverse(); -/// let dimmed = style("Dim text").dim(); -/// let crossed_out = style("Crossed out font").crossed_out(); -/// -/// // paint styled text to screen (this could also be called inline) -/// println!("{}", style1); -/// println!("{}", style2); -/// println!("{}", bold); -/// println!("{}", hidden); -/// ... -/// -/// // custom rgb value (Windows 10 and UNIX systems) -/// style("RGB color (10,10,10) ").with(Color::Rgb { -/// r: 10, -/// g: 10, -/// b: 10 -/// })); -/// -/// // custom ansi color value (Windows 10 and UNIX systems) -/// style("ANSI color value (50) ").with(Color::AnsiValue(50)); -/// -/// ``` -/// ### Cursor | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/cursor/mod.rs) -/// This module provides the functionalities to work with the terminal cursor. -/// -/// ```rust -/// use crossterm::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.reset_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) -/// -/// ``` -/// -/// ### Input | [see more](http://atcentra.com/crossterm/input.html) -/// This module provides the functionalities to work with terminal input. -/// -/// ```rust -/// use crossterm::input; -/// -/// 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), -/// } -/// -/// ``` -/// -/// ### Terminal | [see more](https://github.com/TimonPost/crossterm/blob/master/examples/terminal/terminal.rs) -/// This module provides the functionalities to work with the terminal in general. -/// -/// ```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.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"); -/// ``` -/// -/// ### Alternate and Raw Screen -/// These concepts are a little more complex, please checkout the [book](http://atcentra.com/crossterm/screen.html) topics about these subjects. -/// -/// ## 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. -/// -/// ## Notice -/// This library is quite stable now, changes could be expected but they will probably be not that big. -/// If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UpgradeManual.md) what to change to upgrade. -/// -/// ## Todo -/// I still have some things in mind to implement. -/// -/// - Handling mouse events: -/// I want to be able to do something based on the clicks the user has done with its mouse. -/// - Handling key events: -/// I want to be able to read key combination inputs. -/// - Tests: -/// Find a way to test: color, alternate screen, rawscreen -/// -/// ## Contributing -/// -/// I highly appreciate it when you are contributing to this crate. -/// Also Since my native language is not English my grammar and sentence order will not be perfect. -/// So improving this by correcting these mistakes will help both me and the reader of the docs. -/// -/// Check [Contributing](https://github.com/TimonPost/crossterm/blob/master/docs/Contributing.md) for more info about branches and code architecture. -/// -/// ## Authors -/// -/// * **Timon Post** - *Project Owner & creator* -/// -/// ## License -/// -/// This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details - -#[cfg(unix)] -extern crate libc; -#[cfg(unix)] -extern crate termios; - -#[cfg(windows)] -extern crate crossterm_winapi; -#[cfg(windows)] -extern crate winapi; - #[macro_use] -mod common; +extern crate crossterm_utils; -mod kernel; -mod modules; +#[cfg(feature = "cursor")] +extern crate crossterm_cursor; +#[cfg(feature = "input")] +extern crate crossterm_input; +#[cfg(feature = "screen")] +extern crate crossterm_screen; +#[cfg(feature = "style")] +extern crate crossterm_style; +#[cfg(feature = "terminal")] +extern crate crossterm_terminal; -pub use modules::cursor; -pub use modules::input; -pub use modules::output; -pub use modules::style; -pub use modules::terminal; +mod crossterm; -pub use self::cursor::{cursor, TerminalCursor}; -pub use self::input::{input, AsyncReader, KeyEvent, TerminalInput}; -pub use self::output::TerminalOutput; -pub use self::style::{ - color, style, Attribute, Color, ColorType, DisplayableObject, ObjectStyle, StyledObject, - TerminalColor, +#[cfg(feature = "cursor")] +pub use self::crossterm_cursor::{cursor, TerminalCursor}; +#[cfg(feature = "input")] +pub use self::crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput}; +#[cfg(feature = "screen")] +pub use self::crossterm_screen::{AlternateScreen, Screen}; +#[cfg(feature = "style")] +pub use self::crossterm_style::{ + color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor, }; -pub use self::terminal::{terminal, Terminal}; -pub use common::error; -pub use common::screen::{AlternateScreen, Screen}; -pub use common::Crossterm; +#[cfg(feature = "terminal")] +pub use self::crossterm_terminal::*; + +pub use self::crossterm::Crossterm; +pub use self::crossterm_utils::{error, TerminalOutput}; diff --git a/src/modules/mod.rs b/src/modules/mod.rs deleted file mode 100644 index 8e841e6..0000000 --- a/src/modules/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod cursor; -pub mod input; -pub mod output; -pub mod style; -pub mod terminal; - -use super::common::functions;