From 1e332daaedbf0895df40ebdedf6741abb0570c91 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 10 Apr 2019 23:46:30 +0200 Subject: [PATCH] Refactor and API stabilization (#115) - Major refactor and cleanup. - Improved performance; - No locking when writing to stdout. - UNIX doesn't have any dynamic dispatch anymore. - Windows has improved the way to check if ANSI modes are enabled. - Removed lot's of complex API calls: `from_screen`, `from_output` - Removed `Arc` from all internal Api's. - Removed termios dependency for UNIX systems. - Upgraded deps. - Removed about 1000 lines of code - `TerminalOutput` - `Screen` - unsafe code - Some duplicated code introduced by a previous refactor. - Raw modes UNIX systems improved - Added `NoItalic` attribute --- Cargo.toml | 22 +- README.md | 55 ++--- crossterm_cursor/CHANGELOG.md | 5 + crossterm_cursor/Cargo.toml | 8 +- crossterm_cursor/README.md | 50 ++--- crossterm_cursor/examples/cursor.rs | 4 +- crossterm_cursor/src/cursor/ansi_cursor.rs | 52 ++--- crossterm_cursor/src/cursor/cursor.rs | 111 +++------- crossterm_cursor/src/cursor/mod.rs | 27 ++- crossterm_cursor/src/cursor/test.rs | 16 +- crossterm_cursor/src/cursor/winapi_cursor.rs | 31 ++- crossterm_cursor/src/sys/unix.rs | 2 +- crossterm_input/CHANGELOG.md | 15 ++ crossterm_input/Cargo.toml | 18 +- crossterm_input/README.md | 52 ++--- crossterm_input/examples/input.rs | 1 - crossterm_input/examples/key_events.rs | 152 +++++--------- crossterm_input/src/input/input.rs | 115 +++-------- crossterm_input/src/input/mod.rs | 23 +-- crossterm_input/src/input/unix_input.rs | 121 ++++++----- crossterm_input/src/input/windows_input.rs | 54 ++--- crossterm_input/src/lib.rs | 2 +- crossterm_input/src/sys/unix.rs | 64 +----- crossterm_screen/Cargo.toml | 8 +- crossterm_screen/README.md | 42 ++-- crossterm_screen/examples/alternate_screen.rs | 6 +- crossterm_screen/examples/raw_mode.rs | 10 +- crossterm_screen/src/lib.rs | 2 +- crossterm_screen/src/screen/alternate.rs | 62 +++--- crossterm_screen/src/screen/mod.rs | 6 +- crossterm_screen/src/screen/raw.rs | 62 ++++-- crossterm_screen/src/screen/screen.rs | 182 ---------------- crossterm_screen/src/sys/mod.rs | 34 +-- crossterm_screen/src/sys/unix.rs | 6 - crossterm_screen/src/sys/winapi.rs | 6 +- crossterm_style/CHANGELOG.md | 4 + crossterm_style/Cargo.toml | 12 +- crossterm_style/README.md | 52 ++--- crossterm_style/src/ansi_color.rs | 28 +-- crossterm_style/src/color.rs | 86 ++------ crossterm_style/src/enums/attribute.rs | 8 +- crossterm_style/src/enums/colored.rs | 6 +- crossterm_style/src/lib.rs | 20 +- crossterm_style/src/macros.rs | 4 +- crossterm_style/src/objectstyle.rs | 5 +- crossterm_style/src/styledobject.rs | 98 +-------- crossterm_style/src/traits.rs | 2 +- crossterm_style/src/winapi_color.rs | 13 +- crossterm_terminal/CHANGELOG.md | 5 + crossterm_terminal/Cargo.toml | 14 +- crossterm_terminal/README.md | 42 ++-- .../src/terminal/ansi_terminal.rs | 37 ++-- crossterm_terminal/src/terminal/mod.rs | 23 +-- crossterm_terminal/src/terminal/terminal.rs | 99 +++------ crossterm_terminal/src/terminal/test.rs | 8 +- .../src/terminal/winapi_terminal.rs | 21 +- crossterm_utils/Cargo.toml | 7 +- crossterm_utils/README.md | 14 +- crossterm_utils/src/commands/mod.rs | 30 --- crossterm_utils/src/commands/win_commands.rs | 76 ------- crossterm_utils/src/error.rs | 9 + crossterm_utils/src/functions.rs | 53 +---- crossterm_utils/src/lib.rs | 9 +- crossterm_utils/src/macros.rs | 21 ++ crossterm_utils/src/output/ansi_output.rs | 40 ---- crossterm_utils/src/output/mod.rs | 34 --- crossterm_utils/src/output/output.rs | 96 --------- crossterm_utils/src/output/sys/mod.rs | 1 - crossterm_utils/src/output/sys/winapi.rs | 13 -- crossterm_utils/src/output/test.rs | 74 ------- crossterm_utils/src/output/winapi_output.rs | 33 --- crossterm_utils/src/sys/unix.rs | 72 +++---- crossterm_winapi/Cargo.toml | 2 +- crossterm_winapi/README.md | 13 +- docs/CHANGELOG.md | 26 +++ docs/UPGRADE.md | 83 ++++++++ docs/mdbook/src/SUMMARY.md | 5 +- docs/mdbook/src/feature_flags.md | 16 +- docs/mdbook/src/input.md | 64 +++--- docs/mdbook/src/screen.md | 62 +----- docs/mdbook/src/screen_example.md | 155 -------------- docs/mdbook/src/styling.md | 3 +- examples/alternate_screen.rs | 26 ++- examples/crossterm.rs | 9 +- examples/cursor.rs | 5 +- examples/input.rs | 14 +- examples/key_events.rs | 195 +++++++----------- examples/program_examples/command_bar.rs | 24 +-- .../first_depth_search/src/algorithm.rs | 36 ++-- .../first_depth_search/src/main.rs | 67 +++--- .../first_depth_search/src/map.rs | 15 +- .../first_depth_search/src/messages.rs | 10 - .../first_depth_search/src/variables.rs | 2 +- examples/program_examples/logging.rs | 152 -------------- examples/program_examples/snake/src/main.rs | 17 +- examples/program_examples/snake/src/map.rs | 22 +- examples/program_examples/snake/src/snake.rs | 15 +- .../program_examples/snake/src/variables.rs | 16 +- examples/raw_mode.rs | 37 ++-- examples/style.rs | 7 +- examples/terminal.rs | 2 +- src/crossterm.rs | 132 ++---------- src/lib.rs | 6 +- 103 files changed, 1182 insertions(+), 2651 deletions(-) create mode 100644 crossterm_cursor/CHANGELOG.md create mode 100644 crossterm_input/CHANGELOG.md delete mode 100644 crossterm_screen/src/screen/screen.rs create mode 100644 crossterm_terminal/CHANGELOG.md delete mode 100644 crossterm_utils/src/commands/mod.rs delete mode 100644 crossterm_utils/src/commands/win_commands.rs delete mode 100644 crossterm_utils/src/output/ansi_output.rs delete mode 100644 crossterm_utils/src/output/mod.rs delete mode 100644 crossterm_utils/src/output/output.rs delete mode 100644 crossterm_utils/src/output/sys/mod.rs delete mode 100644 crossterm_utils/src/output/sys/winapi.rs delete mode 100644 crossterm_utils/src/output/test.rs delete mode 100644 crossterm_utils/src/output/winapi_output.rs delete mode 100644 docs/mdbook/src/screen_example.md delete mode 100644 examples/program_examples/logging.rs diff --git a/Cargo.toml b/Cargo.toml index 9429e92..cd41c97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm" -version = "0.8.2" +version = "0.9.0" authors = ["T. Post"] description = "An crossplatform terminal library for manipulating terminals." repository = "https://github.com/TimonPost/crossterm" @@ -32,21 +32,13 @@ members = [ ] [dependencies] -crossterm_screen = { optional = true, version = "0.1.0" } -crossterm_cursor = { optional = true, version = "0.1.0" } -crossterm_terminal = { optional = true, version = "0.1.0" } -crossterm_style = { optional = true, version = "0.2.0" } -crossterm_input = { optional = true, version = "0.2.1" } -crossterm_utils = { version = "0.1.0" } +crossterm_screen = { optional = true, path = "./crossterm_screen" } +crossterm_cursor = { optional = true, path = "./crossterm_cursor" } +crossterm_terminal = { optional = true, path = "./crossterm_terminal" } +crossterm_style = { optional = true, path = "./crossterm_style" } +crossterm_input = { optional = true, path = "./crossterm_input" } +crossterm_utils = { optional = false, path = "./crossterm_utils" } [lib] name = "crossterm" path = "src/lib.rs" - -[[example]] -name = "logging" -path = "examples/program_examples/logging.rs" - -[[example]] -name = "command_bar" -path = "examples/program_examples/command_bar.rs" \ No newline at end of file diff --git a/README.md b/README.md index 28ab432..a2339e2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Crossterm | cross-platform terminal manipulating library. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm.svg [l1]: https://crates.io/crates/crossterm @@ -13,6 +13,9 @@ [s3]: https://docs.rs/crossterm/badge.svg [l3]: https://docs.rs/crossterm/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. + [s6]: https://tokei.rs/b1/github/TimonPost/crossterm?category=code [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -50,13 +53,13 @@ This crate is exists out of five modules who are behind [feature flags](http://a ## Getting Started -This documentation is only for Crossterm version `0.8` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. +This documentation is only for Crossterm version `0.9` if you have an older version of Crossterm I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. Add the Crossterm package to your `Cargo.toml` file. ``` [dependencies] -crossterm = "0.8" +crossterm = "0.9" ``` ### Useful Links @@ -71,35 +74,33 @@ crossterm = "0.8" These are the features from this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item -- Very few dependenties. -- Cursor. - - Moving _n_ times Up, Down, Left, Right - - Goto a certain position - - Get cursor position - - Storing the current cursor position and resetting to that stored cursor position later - - Hiding an showing the cursor - - Control over blinking of the terminal cursor (only some terminals are supporting this) +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies +- Cursor + - Moving _n_ times (up, down, left, right) + - Position (set/get) + - Store cursor position and resetting to that later + - Hiding/Showing + - Blinking Cursor (only some terminals are supporting this) - Styled output - - Foreground color (16 base colors) - - Background color (16 base colors) - - 256 color support (Windows 10 and UNIX only) - - RGB support (Windows 10 and UNIX only) - - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) + - Foreground Color (16 base colors) + - Background Color (16 base colors) + - 256 (ANSI) Color Support (Windows 10 and UNIX Only) + - RGB Color Support (Windows 10 and UNIX only) + - Text Attributes: bold, italic, underscore and crossed word and [more](http://atcentra.com/crossterm/styling.html#attributes) (Windows 10 and UNIX only) - Terminal - Clearing (all lines, current line, from cursor down and up, until new line) - - Scrolling (Up, down) - - Get the size of the terminal - - Set the size of the terminal - - Alternate screen - - Raw screen - - Exit the current process + - Scrolling (up, down) + - Terminal Size (get/set) + - Alternate Screen + - Raw Screen + - Exit Current Process - Input - Read character - Read line - - Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more) - - Read mouse input events (Press, Release, Position, Button) + - Read key input events (async / sync) + - Read mouse input events (press, release, position, button) ## Examples These are some basic examples demonstrating how to use this crate. See [examples](https://github.com/TimonPost/crossterm/blob/master/examples/) for more. @@ -280,7 +281,7 @@ let mut input = input(); _Read input events synchronously or asynchronously._ ```rust // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. -let screen = Screen::new(true); +let screen = RawScreen::into_raw_mode(); let mut input = input(); diff --git a/crossterm_cursor/CHANGELOG.md b/crossterm_cursor/CHANGELOG.md new file mode 100644 index 0000000..1e71fa7 --- /dev/null +++ b/crossterm_cursor/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes crossterm_cursor 0.2 +- Removed `TerminalCursor::from_output()` + +# Changes crossterm_cursor 0.1 +- Moved out of `crossterm` 5.4 crate. \ No newline at end of file diff --git a/crossterm_cursor/Cargo.toml b/crossterm_cursor/Cargo.toml index e6303d4..bc3e08e 100644 --- a/crossterm_cursor/Cargo.toml +++ b/crossterm_cursor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm_cursor" -version = "0.1.0" +version = "0.2.0" authors = ["T. Post"] description = "A cross-platform library for moving the terminal cursor." repository = "https://github.com/TimonPost/crossterm" @@ -12,11 +12,11 @@ readme = "README.md" edition = "2018" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["wincon","winnt","minwindef"] } -crossterm_winapi = "0.1.1" +winapi = { version = "0.3.7", features = ["wincon","winnt","minwindef"] } +crossterm_winapi = "0.1.2" [dependencies] -crossterm_utils = "0.1.0" +crossterm_utils = { path = "../crossterm_utils" } [[example]] name = "cursor" diff --git a/crossterm_cursor/README.md b/crossterm_cursor/README.md index 4b9a89f..cb5b5be 100644 --- a/crossterm_cursor/README.md +++ b/crossterm_cursor/README.md @@ -1,5 +1,5 @@ # Crossterm Cursor | cross-platform cursor movement. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_cursor.svg [l1]: https://crates.io/crates/crossterm_cursor @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_cursor/badge.svg [l3]: https://docs.rs/crossterm_cursor/ -[s3]: https://docs.rs/crossterm_cursor/badge.svg -[l3]: https://docs.rs/crossterm_cursor/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -34,23 +34,20 @@ When you want to use other modules as well you might want to use crossterm with - [Features](#features) - [Examples](#examples) - [Tested Terminals](#tested-terminals) -- [Notice](#notice) -- [Contributing](#contributing) - [Authors](#authors) - [License](#license) ## Getting Started -This documentation is only for `crossterm_cursor` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/examples) folders with detailed examples for all functionality of this crate. +This documentation is only for `crossterm_cursor` version `0.2`. Also, check out the [examples](examples/cursor.rs) folders with detailed examples for all functionality of this crate. Add the `crossterm_cursor` package to your `Cargo.toml` file. ``` [dependencies] -`crossterm_cursor` = "0.1" - +crossterm_cursor = "0.2" ``` -And import the crossterm_input modules you want to use. +Import the `crossterm_cursor` modules you want to use. ```rust extern crate crossterm_cursor; @@ -68,19 +65,18 @@ pub use crossterm_cursor::{cursor, TerminalCursor}; These are the features of this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item -- Very few dependenties. -- Cursor. - - Moving _n_ times Up, Down, Left, Right - - Goto a certain position - - Get cursor position - - Storing the current cursor position and resetting to that stored cursor position later - - Hiding an showing the cursor - - Control over blinking of the terminal cursor (only some terminals are supporting this) +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies +- Cursor + - Moving _n_ times (up, down, left, right) + - Position (set/get) + - Store cursor position and resetting to that later + - Hiding/Showing + - Blinking Cursor (only some terminals are supporting this) ## Examples -Check out the [examples](/examples/) for more information about how to use this crate. +The [examples](./examples) folder has more complete and verbose examples. ```rust use crossterm_cursor::cursor; @@ -134,21 +130,9 @@ cursor.blink(true) This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested. If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. -## Notice - -This library is average stable now, I don't expect it to not to change that much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors * **Timon Post** - *Project Owner & creator* ## License - -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_cursor/examples/cursor.rs b/crossterm_cursor/examples/cursor.rs index 310c9dc..f093f05 100644 --- a/crossterm_cursor/examples/cursor.rs +++ b/crossterm_cursor/examples/cursor.rs @@ -4,7 +4,7 @@ extern crate crossterm_cursor; -use crossterm_cursor::{cursor, TerminalCursor}; +use crossterm_cursor::cursor; /// Set the cursor to position X: 10, Y: 5 in the terminal. pub fn goto() { @@ -48,7 +48,7 @@ pub fn move_down() { } /// Save and reset cursor position | demonstration.. -pub fn safe_and_reset_position() { +pub fn save_and_reset_position() { let cursor = cursor(); // Goto X: 5 Y: 5 diff --git a/crossterm_cursor/src/cursor/ansi_cursor.rs b/crossterm_cursor/src/cursor/ansi_cursor.rs index 8939b9d..c7d7c4a 100644 --- a/crossterm_cursor/src/cursor/ansi_cursor.rs +++ b/crossterm_cursor/src/cursor/ansi_cursor.rs @@ -4,22 +4,22 @@ use super::ITerminalCursor; use crate::sys::get_cursor_position; +use std::io::Write; -use crossterm_utils::{write, write_str, Result, TerminalOutput}; -use std::sync::Arc; +use crossterm_utils::Result; /// This struct is an ANSI implementation for cursor related actions. -pub struct AnsiCursor {} +pub struct AnsiCursor; impl AnsiCursor { - pub fn new() -> Box { - Box::from(AnsiCursor {}) + pub fn new() -> AnsiCursor { + AnsiCursor } } impl ITerminalCursor for AnsiCursor { - fn goto(&self, x: u16, y: u16, stdout: &Option<&Arc>) -> Result<()> { - write(stdout, format!(csi!("{};{}H"), y + 1, x + 1))?; + fn goto(&self, x: u16, y: u16) -> Result<()> { + write_cout!(format!(csi!("{};{}H"), y + 1, x + 1))?; Ok(()) } @@ -27,51 +27,51 @@ impl ITerminalCursor for AnsiCursor { get_cursor_position() } - fn move_up(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - write(stdout, format!(csi!("{}A"), count))?; + fn move_up(&self, count: u16) -> Result<()> { + write_cout!(&format!(csi!("{}A"), count))?; Ok(()) } - fn move_right(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - write(stdout, format!(csi!("{}C"), count))?; + fn move_right(&self, count: u16) -> Result<()> { + write_cout!(&format!(csi!("{}C"), count))?; Ok(()) } - fn move_down(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - write(stdout, format!(csi!("{}B"), count))?; + fn move_down(&self, count: u16) -> Result<()> { + write_cout!(&format!(csi!("{}B"), count))?; Ok(()) } - fn move_left(&self, count: u16, stdout: &Option<&Arc>) -> Result<()> { - write(stdout, format!(csi!("{}D"), count))?; + fn move_left(&self, count: u16) -> Result<()> { + write_cout!(&format!(csi!("{}D"), count))?; Ok(()) } - fn save_position(&self, stdout: &Option<&Arc>) -> Result<()> { - write_str(stdout, csi!("s"))?; + fn save_position(&self) -> Result<()> { + write_cout!(csi!("s"))?; Ok(()) } - fn reset_position(&self, stdout: &Option<&Arc>) -> Result<()> { - write_str(stdout, csi!("u"))?; + fn reset_position(&self) -> Result<()> { + write_cout!(csi!("u"))?; Ok(()) } - fn hide(&self, stdout: &Option<&Arc>) -> Result<()> { - write_str(stdout, csi!("?25l"))?; + fn hide(&self) -> Result<()> { + write_cout!(csi!("?25l"))?; Ok(()) } - fn show(&self, stdout: &Option<&Arc>) -> Result<()> { - write_str(stdout, csi!("?25h"))?; + fn show(&self) -> Result<()> { + write_cout!(csi!("?25h"))?; Ok(()) } - fn blink(&self, blink: bool, stdout: &Option<&Arc>) -> Result<()> { + fn blink(&self, blink: bool) -> Result<()> { if blink { - write_str(stdout, csi!("?12h"))?; + write_cout!(csi!("?12h"))?; } else { - write_str(stdout, csi!("?12l"))?; + write_cout!(csi!("?12l"))?; } Ok(()) } diff --git a/crossterm_cursor/src/cursor/cursor.rs b/crossterm_cursor/src/cursor/cursor.rs index f804c72..184e365 100644 --- a/crossterm_cursor/src/cursor/cursor.rs +++ b/crossterm_cursor/src/cursor/cursor.rs @@ -2,12 +2,11 @@ //! Like: moving the cursor position; saving and resetting the cursor position; hiding showing and control the blinking of the cursor. use super::*; -use std::sync::Arc; -use crossterm_utils::{Result, TerminalOutput}; +use crossterm_utils::Result; #[cfg(windows)] -use crossterm_utils::get_module; +use crossterm_utils::supports_ansi; /// Allows you to preform actions with the terminal cursor. /// @@ -23,67 +22,27 @@ use crossterm_utils::get_module; /// Note that positions of the cursor are 0 -based witch means that the coordinates (cells) starts counting from 0 /// /// Check `/examples/cursor` in the library for more specific examples. -/// -/// # Remarks -/// -/// When you want to use 'cursor' on 'alternate screen' use the 'crossterm_screen' crate. -pub struct TerminalCursor<'stdout> { - terminal_cursor: Box, - stdout: Option<&'stdout Arc>, +pub struct TerminalCursor { + #[cfg(windows)] + cursor: Box<(dyn ITerminalCursor + Sync + Send)>, + #[cfg(unix)] + cursor: AnsiCursor, } -impl<'stdout> TerminalCursor<'stdout> { +impl TerminalCursor { /// Create new `TerminalCursor` instance whereon cursor related actions can be performed. - pub fn new() -> TerminalCursor<'stdout> { - #[cfg(target_os = "windows")] - let cursor = get_module::>( - WinApiCursor::new(), - AnsiCursor::new(), - ) - .unwrap(); + pub fn new() -> TerminalCursor { + #[cfg(windows)] + let cursor = if supports_ansi() { + Box::from(AnsiCursor::new()) as Box<(dyn ITerminalCursor + Sync + Send)> + } else { + WinApiCursor::new() as Box<(dyn ITerminalCursor + Sync + Send)> + }; - #[cfg(not(target_os = "windows"))] - let cursor = AnsiCursor::new() as Box; + #[cfg(unix)] + let cursor = AnsiCursor::new(); - TerminalCursor { - terminal_cursor: cursor, - stdout: None, - } - } - - /// Create a new instance of `TerminalCursor` whereon cursor related actions could be preformed on the given output. - /// - /// # Remarks - /// - /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode', - /// and you want your actions from the `TerminalCursor`, created by this function, to operate on the 'alternate screen'. - /// - /// You should checkout the 'crossterm_screen' crate for more information about this. - /// - /// # Example - /// ``` - /// let screen = Screen::default(); - // - /// if let Ok(alternate) = screen.enable_alternate_modes(false) { - /// let terminal = TerminalCursor::from_output(&alternate.screen.stdout); - /// } - /// ``` - pub fn from_output(stdout: &'stdout Arc) -> TerminalCursor<'stdout> { - #[cfg(target_os = "windows")] - let cursor = get_module::>( - WinApiCursor::new(), - AnsiCursor::new(), - ) - .unwrap(); - - #[cfg(not(target_os = "windows"))] - let cursor = AnsiCursor::new() as Box; - - TerminalCursor { - terminal_cursor: cursor, - stdout: Some(stdout), - } + TerminalCursor { cursor } } /// Goto some position (x,y) in the terminal. @@ -91,7 +50,7 @@ impl<'stdout> TerminalCursor<'stdout> { /// # Remarks /// position is 0-based, which means we start counting at 0. pub fn goto(&self, x: u16, y: u16) -> Result<()> { - self.terminal_cursor.goto(x, y, &self.stdout) + self.cursor.goto(x, y) } /// Get current cursor position (x,y) in the terminal. @@ -99,32 +58,30 @@ impl<'stdout> TerminalCursor<'stdout> { /// # Remarks /// position is 0-based, which means we start counting at 0. pub fn pos(&self) -> (u16, u16) { - self.terminal_cursor.pos() + self.cursor.pos() } /// Move the current cursor position `n` times up. - pub fn move_up(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { - self.terminal_cursor.move_up(count, &self.stdout).unwrap(); + pub fn move_up(&mut self, count: u16) -> &mut TerminalCursor { + self.cursor.move_up(count).unwrap(); self } /// Move the current cursor position `n` times right. - pub fn move_right(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { - self.terminal_cursor - .move_right(count, &self.stdout) - .unwrap(); + pub fn move_right(&mut self, count: u16) -> &mut TerminalCursor { + self.cursor.move_right(count).unwrap(); self } /// Move the current cursor position `n` times down. - pub fn move_down(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { - self.terminal_cursor.move_down(count, &self.stdout).unwrap(); + pub fn move_down(&mut self, count: u16) -> &mut TerminalCursor { + self.cursor.move_down(count).unwrap(); self } /// Move the current cursor position `n` times left. - pub fn move_left(&mut self, count: u16) -> &mut TerminalCursor<'stdout> { - self.terminal_cursor.move_left(count, &self.stdout).unwrap(); + pub fn move_left(&mut self, count: u16) -> &mut TerminalCursor { + self.cursor.move_left(count).unwrap(); self } @@ -132,22 +89,22 @@ impl<'stdout> TerminalCursor<'stdout> { /// /// Note that this position is stored program based not per instance of the `Cursor` struct. pub fn save_position(&self) -> Result<()> { - self.terminal_cursor.save_position(&self.stdout) + self.cursor.save_position() } /// Return to saved cursor position pub fn reset_position(&self) -> Result<()> { - self.terminal_cursor.reset_position(&self.stdout) + self.cursor.reset_position() } /// Hide de cursor in the console. pub fn hide(&self) -> Result<()> { - self.terminal_cursor.hide(&self.stdout) + self.cursor.hide() } /// Show the cursor in the console. pub fn show(&self) -> Result<()> { - self.terminal_cursor.show(&self.stdout) + self.cursor.show() } /// Enable or disable blinking of the terminal. @@ -155,11 +112,11 @@ impl<'stdout> TerminalCursor<'stdout> { /// # Remarks /// Not all terminals are supporting this functionality. Windows versions lower than windows 10 also are not supporting this version. pub fn blink(&self, blink: bool) -> Result<()> { - self.terminal_cursor.blink(blink, &self.stdout) + self.cursor.blink(blink) } } /// Get a `TerminalCursor` instance whereon cursor related actions can be performed. -pub fn cursor() -> TerminalCursor<'static> { +pub fn cursor() -> TerminalCursor { TerminalCursor::new() } diff --git a/crossterm_cursor/src/cursor/mod.rs b/crossterm_cursor/src/cursor/mod.rs index ede65ee..c855ab4 100644 --- a/crossterm_cursor/src/cursor/mod.rs +++ b/crossterm_cursor/src/cursor/mod.rs @@ -9,16 +9,15 @@ mod cursor; mod test; mod ansi_cursor; -#[cfg(target_os = "windows")] +#[cfg(windows)] mod winapi_cursor; use self::ansi_cursor::AnsiCursor; -#[cfg(target_os = "windows")] +#[cfg(windows)] use self::winapi_cursor::WinApiCursor; pub use self::cursor::{cursor, TerminalCursor}; -use crossterm_utils::{Result, TerminalOutput}; -use std::sync::Arc; +use crossterm_utils::Result; ///! This trait defines the actions that can be performed with the terminal cursor. ///! This trait can be implemented so that a concrete implementation of the ITerminalCursor can fulfill @@ -30,25 +29,25 @@ use std::sync::Arc; ///! so that cursor related actions can be performed on both UNIX and Windows systems. trait ITerminalCursor: Sync + Send { /// Goto some location (x,y) in the context. - fn goto(&self, x: u16, y: u16, stdout: &Option<&Arc>) -> Result<()>; + fn goto(&self, x: u16, y: u16) -> Result<()>; /// Get the location (x,y) of the current cursor in the context fn pos(&self) -> (u16, u16); /// Move cursor n times up - fn move_up(&self, count: u16, stdout: &Option<&Arc>) -> Result<()>; + fn move_up(&self, count: u16) -> Result<()>; /// Move the cursor `n` times to the right. - fn move_right(&self, count: u16, stdout: &Option<&Arc>) -> Result<()>; + fn move_right(&self, count: u16) -> Result<()>; /// Move the cursor `n` times down. - fn move_down(&self, count: u16, stdout: &Option<&Arc>) -> Result<()>; + fn move_down(&self, count: u16) -> Result<()>; /// Move the cursor `n` times left. - fn move_left(&self, count: u16, stdout: &Option<&Arc>) -> Result<()>; + fn move_left(&self, count: u16) -> Result<()>; /// Save cursor position so that its saved position can be recalled later. Note that this position is stored program based not per instance of the cursor struct. - fn save_position(&self, stdout: &Option<&Arc>) -> Result<()>; + fn save_position(&self) -> Result<()>; /// Return to saved cursor position - fn reset_position(&self, stdout: &Option<&Arc>) -> Result<()>; + fn reset_position(&self) -> Result<()>; /// Hide the terminal cursor. - fn hide(&self, stdout: &Option<&Arc>) -> Result<()>; + fn hide(&self) -> Result<()>; /// Show the terminal cursor - fn show(&self, stdout: &Option<&Arc>) -> Result<()>; + fn show(&self) -> Result<()>; /// Enable or disable the blinking of the cursor. - fn blink(&self, blink: bool, stdout: &Option<&Arc>) -> Result<()>; + fn blink(&self, blink: bool) -> Result<()>; } diff --git a/crossterm_cursor/src/cursor/test.rs b/crossterm_cursor/src/cursor/test.rs index 06ee7f2..a8758a1 100644 --- a/crossterm_cursor/src/cursor/test.rs +++ b/crossterm_cursor/src/cursor/test.rs @@ -11,7 +11,7 @@ mod winapi_tests { fn goto_winapi() { let cursor = WinApiCursor::new(); - cursor.goto(5, 5, &None); + cursor.goto(5, 5); let (x, y) = cursor.pos(); assert_eq!(x, 5); @@ -23,9 +23,9 @@ mod winapi_tests { let cursor = WinApiCursor::new(); let (x, y) = cursor.pos(); - cursor.save_position(&None); - cursor.goto(5, 5, &None); - cursor.reset_position(&None); + cursor.save_position(); + cursor.goto(5, 5); + cursor.reset_position(); let (x_saved, y_saved) = cursor.pos(); @@ -41,9 +41,9 @@ fn reset_safe_ansi() { let cursor = AnsiCursor::new(); let (x, y) = cursor.pos(); - cursor.save_position(&None); - cursor.goto(5, 5, &None); - cursor.reset_position(&None); + cursor.save_position(); + cursor.goto(5, 5); + cursor.reset_position(); let (x_saved, y_saved) = cursor.pos(); @@ -56,7 +56,7 @@ fn reset_safe_ansi() { fn goto_ansi() { if try_enable_ansi() { let cursor = AnsiCursor::new(); - cursor.goto(5, 5, &None); + cursor.goto(5, 5); let (x, y) = cursor.pos(); assert_eq!(x, 5); diff --git a/crossterm_cursor/src/cursor/winapi_cursor.rs b/crossterm_cursor/src/cursor/winapi_cursor.rs index e9ea293..ab92f63 100644 --- a/crossterm_cursor/src/cursor/winapi_cursor.rs +++ b/crossterm_cursor/src/cursor/winapi_cursor.rs @@ -4,8 +4,7 @@ use super::ITerminalCursor; use crate::sys::winapi::{Cursor, Handle}; -use crossterm_utils::{Result, TerminalOutput}; -use std::sync::Arc; +use crossterm_utils::Result; /// This struct is a windows implementation for cursor related actions. pub struct WinApiCursor; @@ -17,7 +16,7 @@ impl WinApiCursor { } impl ITerminalCursor for WinApiCursor { - fn goto(&self, x: u16, y: u16, _stdout: &Option<&Arc>) -> Result<()> { + fn goto(&self, x: u16, y: u16) -> Result<()> { let cursor = Cursor::new()?; cursor.goto(x as i16, y as i16)?; Ok(()) @@ -28,51 +27,51 @@ impl ITerminalCursor for WinApiCursor { cursor.position().unwrap().into() } - fn move_up(&self, count: u16, _stdout: &Option<&Arc>) -> Result<()> { + fn move_up(&self, count: u16) -> Result<()> { let (xpos, ypos) = self.pos(); - self.goto(xpos, ypos - count, _stdout)?; + self.goto(xpos, ypos - count)?; Ok(()) } - fn move_right(&self, count: u16, _stdout: &Option<&Arc>) -> Result<()> { + fn move_right(&self, count: u16) -> Result<()> { let (xpos, ypos) = self.pos(); - self.goto(xpos + count, ypos, _stdout)?; + self.goto(xpos + count, ypos)?; Ok(()) } - fn move_down(&self, count: u16, _stdout: &Option<&Arc>) -> Result<()> { + fn move_down(&self, count: u16) -> Result<()> { let (xpos, ypos) = self.pos(); - self.goto(xpos, ypos + count, _stdout)?; + self.goto(xpos, ypos + count)?; Ok(()) } - fn move_left(&self, count: u16, _stdout: &Option<&Arc>) -> Result<()> { + fn move_left(&self, count: u16) -> Result<()> { let (xpos, ypos) = self.pos(); - self.goto(xpos - count, ypos, _stdout)?; + self.goto(xpos - count, ypos)?; Ok(()) } - fn save_position(&self, _stdout: &Option<&Arc>) -> Result<()> { + fn save_position(&self) -> Result<()> { Cursor::save_cursor_pos()?; Ok(()) } - fn reset_position(&self, _stdout: &Option<&Arc>) -> Result<()> { + fn reset_position(&self) -> Result<()> { Cursor::reset_to_saved_position()?; Ok(()) } - fn hide(&self, _stdout: &Option<&Arc>) -> Result<()> { + fn hide(&self) -> Result<()> { Cursor::from(Handle::current_out_handle()?).set_visibility(false)?; Ok(()) } - fn show(&self, _stdout: &Option<&Arc>) -> Result<()> { + fn show(&self) -> Result<()> { Cursor::from(Handle::current_out_handle()?).set_visibility(true)?; Ok(()) } - fn blink(&self, _blink: bool, _stdout: &Option<&Arc>) -> Result<()> { + fn blink(&self, _blink: bool) -> Result<()> { Ok(()) } } diff --git a/crossterm_cursor/src/sys/unix.rs b/crossterm_cursor/src/sys/unix.rs index 3c4f559..d465a84 100644 --- a/crossterm_cursor/src/sys/unix.rs +++ b/crossterm_cursor/src/sys/unix.rs @@ -12,7 +12,7 @@ pub fn get_cursor_position() -> (u16, u16) { } pub fn pos() -> io::Result<(u16, u16)> { - // if we enable raw modes with screen, this could cause problems if raw mode is already enabled in applicaition. + // if we enable raw modes with screen, this could cause problems if raw mode is already enabled in application. // I am not completely happy with this approach so feel free to find an other way. unsafe { diff --git a/crossterm_input/CHANGELOG.md b/crossterm_input/CHANGELOG.md new file mode 100644 index 0000000..a087714 --- /dev/null +++ b/crossterm_input/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changes crossterm_input 0.3 +- Removed `TerminalInput::from_output()` + +# Changes crossterm_input 0.2.1 +- Fixed SyncReade bug. + +# Changes crossterm_input 0.2.1 +- Introduced SyncReader + +# Changes crossterm_input 0.2 +- Introduced KeyEvents +- Introduced MouseEvents + +# Changes crossterm_input 0.1 +- Moved out of `crossterm` 5.4 crate. \ No newline at end of file diff --git a/crossterm_input/Cargo.toml b/crossterm_input/Cargo.toml index 310c318..2cc5683 100644 --- a/crossterm_input/Cargo.toml +++ b/crossterm_input/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm_input" -version = "0.2.1" +version = "0.3.0" authors = ["T. Post"] description = "A cross-platform library for reading userinput." repository = "https://github.com/TimonPost/crossterm" @@ -12,20 +12,12 @@ readme = "README.md" edition = "2018" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["winnt", "winuser"] } +winapi = { version = "0.3.7", features = ["winnt", "winuser"] } crossterm_winapi = "0.1.2" [target.'cfg(unix)'.dependencies] -libc = "0.2.43" +libc = "0.2.51" [dependencies] -crossterm_utils = "0.1.0" -crossterm_screen = "0.1.0" - -[[example]] -name = "input" -path = "examples/input.rs" - -[[example]] -name = "key_events" -path = "examples/key_events.rs" \ No newline at end of file +crossterm_utils = { path = "../crossterm_utils" } +crossterm_screen = { path = "../crossterm_screen" } \ No newline at end of file diff --git a/crossterm_input/README.md b/crossterm_input/README.md index 6ae215c..b4ad1c3 100644 --- a/crossterm_input/README.md +++ b/crossterm_input/README.md @@ -1,5 +1,5 @@ # Crossterm Input | cross-platform input reading . - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_input.svg [l1]: https://crates.io/crates/crossterm_input @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_input/badge.svg [l3]: https://docs.rs/crossterm_input/ -[s3]: https://docs.rs/crossterm_input/badge.svg -[l3]: https://docs.rs/crossterm_input/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -41,20 +41,20 @@ When you want to use other modules as well you might want to use crossterm with ## Getting Started -This documentation is only for `crossterm_input` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_input/examples) folders with detailed examples for all functionalities of this crate. +This documentation is only for `crossterm_input` version `0.3` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_input/examples) folders with detailed examples for all functionalities of this crate. Add the `crossterm_input` package to your `Cargo.toml` file. ``` [dependencies] -`crossterm_input` = "0.2" +crossterm_input = "0.3" ``` -And import the `crossterm_input` modules you want to use. +Import the `crossterm_input` modules you want to use. ```rust extern crate crossterm_input; -pub use crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput}; +pub use crossterm_input::{input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput}; ``` ### Useful Links @@ -62,31 +62,24 @@ pub use crossterm_input::{input, AsyncReader, KeyEvent, TerminalInput}; - [Documentation](https://docs.rs/crossterm_input/) - [Crates.io](https://crates.io/crates/crossterm_input) - [Book](http://atcentra.com/crossterm/input.html) -- [Examples](/examples) +- [Examples](./examples) ## Features These are the features of this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies - Input - Read character - Read line - - Read key input events async / sync (ALT + Key, CTRL + Key, FN, Arrows, ESC, BackSpace, HOME, DELETE. INSERT, PAGEUP/DOWN, and more) - - Read mouse input events (Press, Release, Position, Button) + - Read key input events (async / sync) + - Read mouse input events (press, release, position, button) + - RawScreen (from `crossterm_screen`) ## Examples -The examples folder has more complete and verbose examples, please have a look at that as well. - -Good documentation could be found in the [docs][l3] as well in the [book](http://atcentra.com/crossterm/input.html). - -_available imports_ -```rust -use crossterm_input::{ - input, InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, AsyncReader, SyncReader, Screen -}; -``` +The [examples](./examples) folder has more complete and verbose examples. _Simple Readings_ ```rust @@ -106,7 +99,7 @@ let mut input = input(); _Read input events synchronously or asynchronously._ ```rust // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. -let screen = Screen::new(true); +let screen = RawScreen::into_raw_mode(); let mut input = input(); @@ -150,17 +143,6 @@ input.disable_mouse_mode().unwrap(); This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested. If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. -## Notice - -This library is average stable now, I don't expect it to not to change that much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors * **Timon Post** - *Project Owner & creator* @@ -168,4 +150,4 @@ So improving this by correcting these mistakes will help both me and the reader ## License -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_input/examples/input.rs b/crossterm_input/examples/input.rs index c7ec056..1515a2a 100644 --- a/crossterm_input/examples/input.rs +++ b/crossterm_input/examples/input.rs @@ -23,7 +23,6 @@ pub fn read_line() { fn main() { // un-comment below and run with // `cargo run --example input`: - read_char(); read_line(); } diff --git a/crossterm_input/examples/key_events.rs b/crossterm_input/examples/key_events.rs index d4e876a..6350624 100644 --- a/crossterm_input/examples/key_events.rs +++ b/crossterm_input/examples/key_events.rs @@ -1,119 +1,74 @@ extern crate crossterm_input; extern crate crossterm_screen; -extern crate crossterm_utils; use crossterm_input::{InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput}; - use crossterm_screen::Screen; - use std::{thread, time::Duration}; -fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool { +fn process_input_event(key_event: InputEvent) -> bool { match key_event { - InputEvent::Keyboard(k) => match k { - KeyEvent::Char(c) => match c { - 'q' => { - screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap(); - return true; + InputEvent::Keyboard(k) => { + match k { + KeyEvent::Char(c) => match c { + 'q' => { + println!("The 'q' key is hit and the program is not listening to input anymore.\n\n"); + return true; + } + _ => { + println!("{}", format!("'{}' pressed\n\n", c)); + } + }, + KeyEvent::Alt(c) => { + println!("{}", format!("ALT +'{}' pressed\n\n", c)); + } + KeyEvent::Ctrl(c) => { + println!("{}", format!("CTRL +'{}' Pressed\n\n", c)); + } + KeyEvent::Esc => { + println!("{}", format!("ESC pressed\n\n")); + } + KeyEvent::F(number) => { + println!("{}", format!("F{} key pressed\n\n", number)); + } + KeyEvent::PageUp => { + println!("{}", format!("Page Up\n\n")); + } + KeyEvent::PageDown => { + println!("{}", format!("Page Down\n\n")); + } + KeyEvent::Delete => { + println!("{}", format!("Delete\n\n")); } _ => { - screen - .stdout - .write_string(format!("'{}' pressed\n\n", c)) - .unwrap(); + println!("{}", format!("OTHER: {:?}\n\n", k)); + () } - }, - KeyEvent::Alt(c) => { - screen - .stdout - .write_string(format!("ALT +'{}' pressed\n\n", c)) - .unwrap(); } - KeyEvent::Ctrl(c) => { - screen - .stdout - .write_string(format!("CTRL +'{}' Pressed\n\n", c)) - .unwrap(); - } - KeyEvent::Esc => { - screen - .stdout - .write_string(format!("ESC pressed\n\n")) - .unwrap(); - } - KeyEvent::F(number) => { - screen - .stdout - .write_string(format!("F{} key pressed\n\n", number)) - .unwrap(); - } - KeyEvent::PageUp => { - screen.stdout.write_string(format!("Page Up\n\n")).unwrap(); - } - KeyEvent::PageDown => { - screen - .stdout - .write_string(format!("Page Down\n\n")) - .unwrap(); - } - KeyEvent::Delete => { - screen.stdout.write_string(format!("Delete\n\n")).unwrap(); - } - _ => { - screen - .stdout - .write_string(format!("OTHER: {:?}\n\n", k)) - .unwrap(); - () - } - }, + } InputEvent::Mouse(m) => match m { MouseEvent::Press(b, x, y) => match b { MouseButton::Left => { - screen - .stdout - .write_string(format!("left mouse press @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("left mouse press @ {}, {}\n\n", x, y)); } MouseButton::Right => { - screen - .stdout - .write_string(format!("right mouse press @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("right mouse press @ {}, {}\n\n", x, y)); } MouseButton::Middle => { - screen - .stdout - .write_string(format!("mid mouse press @ {}, {}\n\n", x, y)) - .unwrap(); - } - MouseButton::WheelUp => { - screen - .stdout - .write_string(format!("wheel up @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("mid mouse press @ {}, {}\n\n", x, y)); } + MouseButton::WheelUp => println!("{}", format!("wheel up @ {}, {}\n\n", x, y)), MouseButton::WheelDown => { - screen - .stdout - .write_string(format!("wheel down @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("wheel down @ {}, {}\n\n", x, y)); } }, MouseEvent::Release(x, y) => { - screen - .stdout - .write_string(format!("mouse released @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("mouse released @ {}, {}\n\n", x, y)); } MouseEvent::Hold(x, y) => { - screen - .stdout - .write_string(format!("dragging @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("dragging @ {}, {}\n\n", x, y)); } _ => { - screen.stdout.write_str("Unknown mouse event").unwrap(); + println!("{}", "Unknown mouse event"); } }, _ => println!("Unknown!"), @@ -124,18 +79,18 @@ fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool { pub fn read_asynchronously() { // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. - let screen = Screen::new(true); + let _ = Screen::new(true); - let input = TerminalInput::from_output(&screen.stdout); + let input = input(); // enable mouse events to be captured. input.enable_mouse_mode().unwrap(); - let mut async_stdin = input.read_async(); + let mut stdin = input.read_async(); loop { - if let Some(key_event) = async_stdin.next() { - if process_input_event(key_event, &screen) { + if let Some(key_event) = stdin.next() { + if process_input_event(key_event) { break; } } @@ -148,9 +103,9 @@ pub fn read_asynchronously() { pub fn read_synchronously() { // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. - let screen = Screen::new(true); + let _ = Screen::new(true); - let input = TerminalInput::from_output(&screen.stdout); + let input = input(); // enable mouse events to be captured. input.enable_mouse_mode().unwrap(); @@ -161,7 +116,7 @@ pub fn read_synchronously() { let event = sync_stdin.next(); if let Some(key_event) = event { - if process_input_event(key_event, &screen) { + if process_input_event(key_event) { break; } } @@ -174,7 +129,6 @@ pub fn read_synchronously() { fn main() { // un-comment below and run with // `cargo run --example key_events`: - - read_synchronously(); - // read_asynchronously(); + // read_synchronously(); + // read_asynchronously(); } diff --git a/crossterm_input/src/input/input.rs b/crossterm_input/src/input/input.rs index f225d18..3166a78 100644 --- a/crossterm_input/src/input/input.rs +++ b/crossterm_input/src/input/input.rs @@ -2,23 +2,7 @@ //! Like reading a line, reading a character and reading asynchronously. use super::*; -use std::io::{Error, ErrorKind}; -use std::iter::Iterator; -use std::str; - -use crossterm_utils::TerminalOutput; - -/// Allows you to preform actions with the < option >. -/// -/// # Features: -/// -/// - features -/// -/// Check `/examples/` in the library for more specific examples. -/// -/// # Remarks -/// -/// When you want to use '< name >' on 'alternate screen' use the 'crossterm_screen' crate. +use std::{io, str}; /// Allows you to read user input. /// @@ -28,68 +12,34 @@ use crossterm_utils::TerminalOutput; /// - Read line /// - Read async /// - Read async until +/// - Read sync /// - Wait for key event (terminal pause) /// /// Check `/examples/` in the library for more specific examples. -/// -/// # Remarks -/// -/// When you want to use 'input' on 'alternate screen' use the 'crossterm_screen' crate. -pub struct TerminalInput<'stdout> { - terminal_input: Box, - stdout: Option<&'stdout Arc>, +pub struct TerminalInput { + #[cfg(windows)] + input: WindowsInput, + #[cfg(unix)] + input: UnixInput, } -impl<'stdout> TerminalInput<'stdout> { +impl TerminalInput { /// Create a new instance of `TerminalInput` whereon input related actions could be preformed. - pub fn new() -> TerminalInput<'stdout> { - #[cfg(target_os = "windows")] - let input = Box::from(WindowsInput::new()); + pub fn new() -> TerminalInput { + #[cfg(windows)] + let input = WindowsInput::new(); - #[cfg(not(target_os = "windows"))] - let input = Box::from(UnixInput::new()); + #[cfg(unix)] + let input = UnixInput::new(); - TerminalInput { - terminal_input: input, - stdout: None, - } - } - - /// Create a new instance of `TerminalInput` whereon input related actions could be preformed. - /// - /// # Remarks - /// - /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode', - /// and you want your actions from the `TerminalInput`, created by this function, to operate on the 'alternate screen'. - /// - /// You should checkout the 'crossterm_screen' crate for more information about this. - /// # Example - /// ```rust - /// let screen = Screen::default(); - // - /// if let Ok(alternate) = screen.enable_alternate_modes(false) { - /// let terminal = TerminalInput::from_output(&alternate.screen.stdout); - /// } - /// ``` - pub fn from_output(stdout: &'stdout Arc) -> TerminalInput<'stdout> { - #[cfg(target_os = "windows")] - let input = Box::from(WindowsInput::new()); - - #[cfg(not(target_os = "windows"))] - let input = Box::from(UnixInput::new()); - - TerminalInput { - terminal_input: input, - stdout: Some(stdout), - } + TerminalInput { input } } /// Read one line from the user input. /// /// # Remark /// This function is not work when raw screen is turned on. - /// When you do want to read a line in raw mode please, checkout `read_async` or `read_async_until`. + /// When you do want to read a line in raw mode please, checkout `read_async`, `read_async_until` or `read_sync`. /// Not sure what 'raw mode' is, checkout the 'crossterm_screen' crate. /// /// # Example @@ -101,12 +51,6 @@ impl<'stdout> TerminalInput<'stdout> { /// } /// ``` pub fn read_line(&self) -> io::Result { - if let Some(stdout) = self.stdout { - if stdout.is_in_raw_mode { - return Err(Error::new(ErrorKind::Other, "Crossterm does not support readline in raw mode this should be done instead whit `read_async` or `read_async_until`")); - } - } - let mut rv = String::new(); io::stdin().read_line(&mut rv)?; let len = rv.trim_right_matches(&['\r', '\n'][..]).len(); @@ -125,7 +69,7 @@ impl<'stdout> TerminalInput<'stdout> { /// } /// ``` pub fn read_char(&self) -> io::Result { - self.terminal_input.read_char(&self.stdout) + self.input.read_char() } /// Read the input asynchronously, which means that input events are gathered on the background and will be queued for you to read. @@ -146,7 +90,7 @@ impl<'stdout> TerminalInput<'stdout> { /// # Examples /// Please checkout the example folder in the repository. pub fn read_async(&self) -> AsyncReader { - self.terminal_input.read_async() + self.input.read_async() } /// Read the input asynchronously until a certain character is hit, which means that input events are gathered on the background and will be queued for you to read. @@ -167,7 +111,7 @@ impl<'stdout> TerminalInput<'stdout> { /// # Examples /// Please checkout the example folder in the repository. pub fn read_until_async(&self, delimiter: u8) -> AsyncReader { - self.terminal_input.read_until_async(delimiter) + self.input.read_until_async(delimiter) } /// Read the input synchronously from the user, which means that reading call wil be blocking ones. @@ -181,7 +125,7 @@ impl<'stdout> TerminalInput<'stdout> { /// # Examples /// Please checkout the example folder in the repository. pub fn read_sync(&self) -> SyncReader { - self.terminal_input.read_sync() + self.input.read_sync() } /// Enable mouse events to be captured. @@ -190,20 +134,20 @@ impl<'stdout> TerminalInput<'stdout> { /// /// # Remark /// - Mouse events will be send over the reader created with `read_async`, `read_async_until`, `read_sync`. - pub fn enable_mouse_mode(&self) -> io::Result<()> { - self.terminal_input.enable_mouse_mode(&self.stdout) + pub fn enable_mouse_mode(&self) -> Result<()> { + self.input.enable_mouse_mode() } /// Disable mouse events to be captured. /// /// When disabling mouse input you won't be able to capture, mouse movements, pressed buttons and locations anymore. - pub fn disable_mouse_mode(&self) -> io::Result<()> { - self.terminal_input.disable_mouse_mode(&self.stdout) + pub fn disable_mouse_mode(&self) -> Result<()> { + self.input.disable_mouse_mode() } } /// Get a `TerminalInput` instance whereon input related actions can be performed. -pub fn input<'stdout>() -> TerminalInput<'stdout> { +pub fn input() -> TerminalInput { TerminalInput::new() } @@ -212,7 +156,10 @@ pub(crate) fn parse_event(item: u8, iter: &mut I) -> Result where I: Iterator, { - let error = Error::new(ErrorKind::Other, "Could not parse an event"); + let error = ErrorKind::IoError(io::Error::new( + io::ErrorKind::Other, + "Could not parse an event", + )); let input_event = match item { b'\x1B' => { let a = iter.next(); @@ -426,10 +373,10 @@ fn parse_utf8_char(c: u8, iter: &mut I) -> Result where I: Iterator, { - let error = Err(Error::new( - ErrorKind::Other, + let error = Err(ErrorKind::IoError(io::Error::new( + io::ErrorKind::Other, "Input character is not valid UTF-8", - )); + ))); if c.is_ascii() { Ok(c as char) diff --git a/crossterm_input/src/input/mod.rs b/crossterm_input/src/input/mod.rs index af4efde..4ab35e7 100644 --- a/crossterm_input/src/input/mod.rs +++ b/crossterm_input/src/input/mod.rs @@ -3,28 +3,27 @@ mod input; -#[cfg(not(target_os = "windows"))] +#[cfg(unix)] mod unix_input; -#[cfg(target_os = "windows")] +#[cfg(windows)] mod windows_input; -#[cfg(not(target_os = "windows"))] +#[cfg(unix)] pub use self::unix_input::SyncReader; -#[cfg(not(target_os = "windows"))] +#[cfg(unix)] use self::unix_input::UnixInput; -#[cfg(target_os = "windows")] +#[cfg(windows)] pub use self::windows_input::SyncReader; -#[cfg(target_os = "windows")] +#[cfg(windows)] use self::windows_input::WindowsInput; use self::input::parse_event; pub use self::input::{input, TerminalInput}; - -use std::io::{self, Result}; +use crossterm_utils::{ErrorKind, Result}; +use std::io; use std::sync::{mpsc, Arc}; -use crossterm_utils::TerminalOutput; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender}; use std::thread; @@ -39,15 +38,15 @@ use std::thread; /// Unix is using the 'TTY' and windows is using 'libc' C functions to read the input. trait ITerminalInput { /// Read one character from the user input - fn read_char(&self, stdout: &Option<&Arc>) -> io::Result; + fn read_char(&self) -> io::Result; /// Read the input asynchronously from the user. fn read_async(&self) -> AsyncReader; /// Read the input asynchronously until a certain character is hit. fn read_until_async(&self, delimiter: u8) -> AsyncReader; /// Read the input synchronously from the user. fn read_sync(&self) -> SyncReader; - fn enable_mouse_mode(&self, stdout: &Option<&Arc>) -> io::Result<()>; - fn disable_mouse_mode(&self, stdout: &Option<&Arc>) -> io::Result<()>; + fn enable_mouse_mode(&self) -> Result<()>; + fn disable_mouse_mode(&self) -> Result<()>; } /// Enum to specify which input event has occurred. diff --git a/crossterm_input/src/input/unix_input.rs b/crossterm_input/src/input/unix_input.rs index 9d4f7d9..c40f220 100644 --- a/crossterm_input/src/input/unix_input.rs +++ b/crossterm_input/src/input/unix_input.rs @@ -1,11 +1,11 @@ //! This is a UNIX specific implementation for input related action. use super::*; -use crate::sys::unix::{get_tty, read_char, read_char_raw}; +use crate::sys::unix::{get_tty, read_char_raw}; -use crossterm_utils::{csi, write, TerminalOutput}; +use crossterm_utils::{csi, write_cout, Result}; use std::char; -use std::io::Read; +use std::io::{Read, Write}; pub struct UnixInput; @@ -16,17 +16,8 @@ impl UnixInput { } impl ITerminalInput for UnixInput { - fn read_char(&self, stdout: &Option<&Arc>) -> io::Result { - let is_raw_screen = match stdout { - Some(output) => output.is_in_raw_mode, - None => false, - }; - - if is_raw_screen { - read_char_raw() - } else { - read_char() - } + fn read_char(&self) -> io::Result { + read_char_raw() } fn read_async(&self) -> AsyncReader { @@ -43,12 +34,6 @@ impl ITerminalInput for UnixInput { })) } - fn read_sync(&self) -> SyncReader { - SyncReader { - bytes: Box::new(get_tty().unwrap().bytes().flatten()), - } - } - fn read_until_async(&self, delimiter: u8) -> AsyncReader { AsyncReader::new(Box::new(move |event_tx, cancellation_token| { for byte in get_tty().unwrap().bytes() { @@ -63,31 +48,32 @@ impl ITerminalInput for UnixInput { })) } - fn enable_mouse_mode(&self, stdout: &Option<&Arc>) -> io::Result<()> { - write( - stdout, - format!( - "{}h{}h{}h{}h", - csi!("?1000"), - csi!("?1002"), - csi!("?1015"), - csi!("?1006") - ), - )?; + fn read_sync(&self) -> SyncReader { + SyncReader { + source: Box::from(get_tty().unwrap()), + leftover: None, + } + } + + fn enable_mouse_mode(&self) -> Result<()> { + write_cout!(&format!( + "{}h{}h{}h{}h", + csi!("?1000"), + csi!("?1002"), + csi!("?1015"), + csi!("?1006") + ))?; Ok(()) } - fn disable_mouse_mode(&self, stdout: &Option<&Arc>) -> io::Result<()> { - write( - stdout, - format!( - "{}l{}l{}l{}l", - csi!("?1006"), - csi!("?1015"), - csi!("?1002"), - csi!("?1000") - ), - )?; + fn disable_mouse_mode(&self) -> Result<()> { + write_cout!(&format!( + "{}l{}l{}l{}l", + csi!("?1006"), + csi!("?1015"), + csi!("?1002"), + csi!("?1000") + ))?; Ok(()) } } @@ -98,7 +84,8 @@ impl ITerminalInput for UnixInput { /// /// If you don't want to block your calls use [AsyncReader](./LINK), which will read input on the background and queue it for you to read. pub struct SyncReader { - bytes: Box>, + source: Box, + leftover: Option, } impl Iterator for SyncReader { @@ -108,16 +95,50 @@ impl Iterator for SyncReader { /// If there are no keys pressed this will be a blocking call until there are. /// This will return `None` in case of a failure and `Some(InputEvent) in case of an occurred input event.` fn next(&mut self) -> Option { - let mut iterator = self.bytes.as_mut(); - match iterator.next() { - Some(byte) => { - if let Ok(event) = parse_event(byte, &mut iterator) { - Some(event) + // TODO: Currently errors are consumed and converted to a `NONE` maybe we should'nt be doing this? + let source = &mut self.source; + + if let Some(c) = self.leftover { + // we have a leftover byte, use it + self.leftover = None; + if let Ok(e) = parse_event(c, &mut source.bytes().flatten()) { + return Some(e); + } else { + return None; + } + } + + // Here we read two bytes at a time. We need to distinguish between single ESC key presses, + // and escape sequences (which start with ESC or a x1B byte). The idea is that if this is + // an escape sequence, we will read multiple bytes (the first byte being ESC) but if this + // is a single ESC keypress, we will only read a single byte. + let mut buf = [0u8; 2]; + let res = match source.read(&mut buf) { + Ok(0) => return None, + Ok(1) => match buf[0] { + b'\x1B' => return Some(InputEvent::Keyboard(KeyEvent::Esc)), + c => { + if let Ok(e) = parse_event(c, &mut source.bytes().flatten()) { + return Some(e); + } else { + return None; + } + } + }, + Ok(2) => { + let option_iter = &mut Some(buf[1]).into_iter(); + let iter = option_iter.map(|c| Ok(c)).chain(source.bytes()); + if let Ok(e) = parse_event(buf[0], &mut source.bytes().flatten()) { + self.leftover = option_iter.next(); + Some(e) } else { None } } - None => None, - } + Ok(_) => unreachable!(), + Err(_) => return None, /* maybe we should not throw away the error?*/ + }; + + res } } diff --git a/crossterm_input/src/input/windows_input.rs b/crossterm_input/src/input/windows_input.rs index e66f5ad..69aa505 100644 --- a/crossterm_input/src/input/windows_input.rs +++ b/crossterm_input/src/input/windows_input.rs @@ -2,26 +2,26 @@ use super::*; -use crossterm_utils::TerminalOutput; use crossterm_winapi::{ ButtonState, Console, ConsoleMode, EventFlags, Handle, InputEventType, KeyEventRecord, MouseEvent, }; -use winapi::um::wincon::{ - LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED, -}; -use winapi::um::winnt::INT; -use winapi::um::winuser::{ - VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12, - VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT, VK_MENU, - VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, -}; -use std::thread; -use std::{char, io}; +use winapi::um::{ + wincon::{ + LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED, + }, + winnt::INT, + winuser::{ + VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F10, VK_F11, VK_F12, + VK_F2, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9, VK_HOME, VK_INSERT, VK_LEFT, + VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, + }, +}; use std::sync::atomic::Ordering; use std::time::Duration; +use std::{char, io, thread}; pub struct WindowsInput; @@ -37,20 +37,9 @@ const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008; static mut ORIG_MODE: u32 = 0; impl ITerminalInput for WindowsInput { - fn read_char(&self, stdout: &Option<&Arc>) -> io::Result { - let is_raw_screen = match stdout { - Some(output) => output.is_in_raw_mode, - None => false, - }; - + fn read_char(&self) -> io::Result { // _getwch is without echo and _getwche is with echo - let pressed_char = unsafe { - if is_raw_screen { - _getwch() - } else { - _getwche() - } - }; + let pressed_char = unsafe { _getwche() }; // we could return error but maybe option to keep listening until valid character is inputted. if pressed_char == 0 || pressed_char == 0xe0 { @@ -87,10 +76,6 @@ impl ITerminalInput for WindowsInput { })) } - fn read_sync(&self) -> SyncReader { - SyncReader {} - } - fn read_until_async(&self, delimiter: u8) -> AsyncReader { AsyncReader::new(Box::new(move |event_tx, cancellation_token| loop { for i in into_virtual_terminal_sequence().unwrap().1 { @@ -107,7 +92,11 @@ impl ITerminalInput for WindowsInput { })) } - fn enable_mouse_mode(&self, __stdout: &Option<&Arc>) -> io::Result<()> { + fn read_sync(&self) -> SyncReader { + SyncReader + } + + fn enable_mouse_mode(&self) -> Result<()> { let mode = ConsoleMode::from(Handle::current_in_handle()?); unsafe { @@ -117,9 +106,10 @@ impl ITerminalInput for WindowsInput { Ok(()) } - fn disable_mouse_mode(&self, __stdout: &Option<&Arc>) -> io::Result<()> { + fn disable_mouse_mode(&self) -> Result<()> { let mode = ConsoleMode::from(Handle::current_in_handle()?); - mode.set_mode(unsafe { ORIG_MODE }) + mode.set_mode(unsafe { ORIG_MODE })?; + Ok(()) } } diff --git a/crossterm_input/src/lib.rs b/crossterm_input/src/lib.rs index 827dae2..2dd47d0 100644 --- a/crossterm_input/src/lib.rs +++ b/crossterm_input/src/lib.rs @@ -11,4 +11,4 @@ pub use self::input::{ input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput, }; -pub use self::crossterm_screen::Screen; +pub use self::crossterm_screen::{IntoRawMode, RawScreen}; diff --git a/crossterm_input/src/sys/unix.rs b/crossterm_input/src/sys/unix.rs index 892ad2a..354d4b0 100644 --- a/crossterm_input/src/sys/unix.rs +++ b/crossterm_input/src/sys/unix.rs @@ -7,66 +7,10 @@ use std::os::unix::io::AsRawFd; /// /// This allows for getting stdio representing _only_ the TTY, and not other streams. pub fn get_tty() -> io::Result { - 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 + fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty") } fn get_tty_fd() -> io::Result { diff --git a/crossterm_screen/Cargo.toml b/crossterm_screen/Cargo.toml index 69391d3..5f4bc9b 100644 --- a/crossterm_screen/Cargo.toml +++ b/crossterm_screen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm_screen" -version = "0.1.0" +version = "0.2.0" authors = ["T. Post"] description = "A cross-platform library for raw and alternate screen." repository = "https://github.com/TimonPost/crossterm" @@ -12,8 +12,8 @@ readme = "README.md" edition = "2018" [dependencies] -crossterm_utils = "0.1.0" +crossterm_utils = { path = "../crossterm_utils" } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["minwindef", "wincon"] } -crossterm_winapi = "0.1.1" \ No newline at end of file +winapi = { version = "0.3.7", features = ["minwindef", "wincon"] } +crossterm_winapi = "0.1.2" \ No newline at end of file diff --git a/crossterm_screen/README.md b/crossterm_screen/README.md index 42dff77..900a3cd 100644 --- a/crossterm_screen/README.md +++ b/crossterm_screen/README.md @@ -1,5 +1,5 @@ # Crossterm Screen | cross-platform alternate, raw screen. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_screen.svg [l1]: https://crates.io/crates/crossterm_screen @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_screen/badge.svg [l3]: https://docs.rs/crossterm_screen/ -[s3]: https://docs.rs/crossterm_screen/badge.svg -[l3]: https://docs.rs/crossterm_screen/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -43,23 +43,23 @@ In case you are wondering what 'alternate' or 'raw' screen is, you could checkou ## Getting Started -This documentation is only for `crossterm_screen` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). -Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_screen/examples) folders with detailed examples for all functionality of this crate +This documentation is only for `crossterm_screen` version `0.2`. +Also, check out the [examples](./examples) folders with detailed examples for all functionality of this crate and the [book](http://atcentra.com/crossterm/screen.html) for more information about how to use the alternate or raw screen options. Add the `crossterm_screen` package to your `Cargo.toml` file. ``` [dependencies] -`crossterm_screen` = "0.1" - +crossterm_screen = "0.2" ``` + And import the `crossterm_screen` modules you want to use. ```rust extern crate crossterm_screen; -pub use crossterm_screen::{AlternateScreen, RawScreen, Screen}; +pub use crossterm_screen::{AlternateScreen, RawScreen}; ``` ### Useful Links @@ -67,15 +67,15 @@ pub use crossterm_screen::{AlternateScreen, RawScreen, Screen}; - [Documentation](https://docs.rs/crossterm_screen/) - [Crates.io](https://crates.io/crates/crossterm_screen) - [Book](http://atcentra.com/crossterm/screen.html) -- [Examples](/examples) +- [Examples](./examples) ## Features These are the features of this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item -- Very few dependenties. +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies - Alternate screen - Raw screen @@ -83,10 +83,9 @@ Planned features: - make is possible to switch between multiple buffers. ## Examples -Check out the [examples](/examples/) for more information about how to use this crate. +The [examples](./examples) folder has more complete and verbose examples. ## Tested terminals - - Windows Powershell - Windows 10 (pro) - Windows CMD @@ -100,21 +99,8 @@ Check out the [examples](/examples/) for more information about how to use this This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested. If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. -## Notice - -This library is average stable now, I don't expect it to not to change that much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors - * **Timon Post** - *Project Owner & creator* ## License - -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_screen/examples/alternate_screen.rs b/crossterm_screen/examples/alternate_screen.rs index 96862ad..9c2e6fd 100644 --- a/crossterm_screen/examples/alternate_screen.rs +++ b/crossterm_screen/examples/alternate_screen.rs @@ -1,16 +1,14 @@ extern crate crossterm_screen; -use crossterm_screen::Screen; +use crossterm_screen::AlternateScreen; use std::io::{stdout, Write}; use std::{thread, time}; /// print wait screen on alternate screen, then switch back. pub fn print_wait_screen_on_alternate_window() { - let screen = Screen::default(); - // move to alternate screen, 'false' means if the alternate screen should be in raw modes. - if let Ok(alternate) = screen.enable_alternate_modes(false) { + if let Ok(alternate) = AlternateScreen::to_alternate(false) { // do some stuff on the alternate screen. } // <- alternate screen will be disabled when dropped. } diff --git a/crossterm_screen/examples/raw_mode.rs b/crossterm_screen/examples/raw_mode.rs index c6270f1..39fd7ee 100644 --- a/crossterm_screen/examples/raw_mode.rs +++ b/crossterm_screen/examples/raw_mode.rs @@ -1,16 +1,14 @@ extern crate crossterm_screen; -use crossterm_screen::Screen; +use crossterm_screen::{IntoRawMode, RawScreen}; use std::io::{stdout, Write}; use std::{thread, time}; pub fn raw_modes() { - // create a Screen instance who operates on the default output; io::stdout(). - let screen = Screen::default(); - // create a Screen instance who operates on the default output; io::stdout(). By passing in 'true' we make this screen 'raw' - let screen = Screen::new(true); + let screen = RawScreen::into_raw_mode(); + let screen = stdout().into_raw_mode(); - drop(screen); // <-- by dropping the screen raw modes will be disabled. + // raw screen will be disabled when it goes out of scope. } diff --git a/crossterm_screen/src/lib.rs b/crossterm_screen/src/lib.rs index 9f3c738..898b272 100644 --- a/crossterm_screen/src/lib.rs +++ b/crossterm_screen/src/lib.rs @@ -12,4 +12,4 @@ extern crate crossterm_winapi; mod screen; mod sys; -pub use self::screen::{AlternateScreen, RawScreen, Screen}; +pub use self::screen::{AlternateScreen, IntoRawMode, RawScreen}; diff --git a/crossterm_screen/src/screen/alternate.rs b/crossterm_screen/src/screen/alternate.rs index a031375..87071e3 100644 --- a/crossterm_screen/src/screen/alternate.rs +++ b/crossterm_screen/src/screen/alternate.rs @@ -8,12 +8,11 @@ #[cfg(windows)] use crate::sys::winapi::ToAlternateScreenCommand; #[cfg(windows)] -use crossterm_utils::get_module; +use crossterm_utils::supports_ansi; use crate::sys::{self, IAlternateScreenCommand}; -use super::{RawScreen, Screen, TerminalOutput}; -use std::convert::From; +use super::RawScreen; use std::io; /// With this type you will be able to switch to alternate screen and back to main screen. @@ -21,16 +20,14 @@ use std::io; /// /// Although this type is available for you to use I would recommend using `Screen` instead. pub struct AlternateScreen { - command: Box, - pub screen: Screen, + #[cfg(windows)] + command: Box<(dyn IAlternateScreenCommand + Sync + Send)>, + #[cfg(unix)] + command: sys::ToAlternateScreenCommand, + raw_screen: Option, } impl AlternateScreen { - /// Create new instance of alternate screen. - pub fn new(command: Box, screen: Screen) -> Self { - AlternateScreen { command, screen } - } - /// Switch to alternate screen. This function will return an `AlternateScreen` instance if everything went well this type will give you control over the `AlternateScreen`. /// /// The bool specifies whether the screen should be in raw mode or not. @@ -40,35 +37,38 @@ impl AlternateScreen { /// The alternate buffer is exactly the dimensions of the window, without any scrollback region. /// For an example of this behavior, consider when vim is launched from bash. /// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged. - pub fn to_alternate_screen( - stdout: TerminalOutput, - raw_mode: bool, - ) -> io::Result { - #[cfg(target_os = "windows")] - let command = get_module::>( - Box::from(ToAlternateScreenCommand::new()), - Box::from(sys::ToAlternateScreenCommand::new()), - ) - .unwrap(); + pub fn to_alternate(raw_mode: bool) -> io::Result { + #[cfg(windows)] + let command = if supports_ansi() { + Box::from(ToAlternateScreenCommand::new()) + as Box<(dyn IAlternateScreenCommand + Sync + Send)> + } else { + Box::from(sys::ToAlternateScreenCommand::new()) + as Box<(dyn IAlternateScreenCommand + Sync + Send)> + }; - #[cfg(not(target_os = "windows"))] - let command = Box::from(sys::ToAlternateScreenCommand::new()); + #[cfg(unix)] + let command = sys::ToAlternateScreenCommand::new(); - let mut stdout = stdout; - command.enable(&mut stdout)?; - - let screen = Screen::from(stdout); + command.enable()?; if raw_mode { - RawScreen::into_raw_mode()?; + let raw_screen = RawScreen::into_raw_mode()?; + return Ok(AlternateScreen { + command, + raw_screen: Some(raw_screen), + }); } - Ok(AlternateScreen::new(command, screen)) + Ok(AlternateScreen { + command, + raw_screen: None, + }) } /// Switch the alternate screen back to main screen. - pub fn to_main_screen(&self) -> io::Result<()> { - self.command.disable(&self.screen.stdout)?; + pub fn to_main(&self) -> io::Result<()> { + self.command.disable()?; Ok(()) } } @@ -76,6 +76,6 @@ impl AlternateScreen { impl Drop for AlternateScreen { /// This will switch back to main screen on drop. fn drop(&mut self) { - self.to_main_screen().unwrap(); + self.to_main().unwrap(); } } diff --git a/crossterm_screen/src/screen/mod.rs b/crossterm_screen/src/screen/mod.rs index 098989c..6d0a635 100644 --- a/crossterm_screen/src/screen/mod.rs +++ b/crossterm_screen/src/screen/mod.rs @@ -3,10 +3,6 @@ mod alternate; mod raw; -mod screen; - -use crossterm_utils::TerminalOutput; pub use self::alternate::AlternateScreen; -pub use self::raw::RawScreen; -pub use self::screen::Screen; +pub use self::raw::{IntoRawMode, RawScreen}; diff --git a/crossterm_screen/src/screen/raw.rs b/crossterm_screen/src/screen/raw.rs index c1d31b5..437dd89 100644 --- a/crossterm_screen/src/screen/raw.rs +++ b/crossterm_screen/src/screen/raw.rs @@ -15,36 +15,72 @@ //! With these modes you can easier design the terminal screen. use crate::sys; - -use std::io; +use std::io::{self, Stdout, Write}; /// A wrapper for the raw terminal state. Which can be used to write to. /// -/// Although this type is available for you to use I would recommend using `Screen` instead. -/// Note that when you want to use input and raw mode you should use `Screen`. -pub struct RawScreen; +/// Please take in mind that if this type drops the raw screen will be undone, to prevent this behaviour call `disable_drop`. +pub struct RawScreen { + drop: bool, +} impl RawScreen { /// Put terminal in raw mode. - pub fn into_raw_mode() -> io::Result<()> { - #[cfg(not(target_os = "windows"))] + pub fn into_raw_mode() -> io::Result { + #[cfg(unix)] let mut command = sys::unix::RawModeCommand::new(); - #[cfg(target_os = "windows")] + #[cfg(windows)] let mut command = sys::winapi::RawModeCommand::new(); - let _result = command.enable(); + command.enable()?; - Ok(()) + Ok(RawScreen { drop: true }) } /// Put terminal back in original modes. - pub fn disable_raw_modes() -> io::Result<()> { - #[cfg(not(target_os = "windows"))] + pub fn disable_raw_mode() -> io::Result<()> { + #[cfg(unix)] let mut command = sys::unix::RawModeCommand::new(); - #[cfg(target_os = "windows")] + #[cfg(windows)] let command = sys::winapi::RawModeCommand::new(); command.disable()?; Ok(()) } + + /// This will disable the drop logic of this type, which means that the rawscreen will not be disabled when this instance goes out of scope. + pub fn disable_drop(&mut self) { + self.drop = false; + } +} + +/// Types which can be converted into "raw mode". +/// +/// # Why is this type defined on writers and not readers? +/// +/// TTYs has their state controlled by the writer, not the reader. You use the writer to clear the +/// screen, move the cursor and so on, so naturally you use the writer to change the mode as well. +pub trait IntoRawMode: Write + Sized { + /// Switch to raw mode. + /// + /// Raw mode means that stdin won't be printed (it will instead have to be written manually by + /// the program). Furthermore, the input isn't canonicalised or buffered (that is, you can + /// read from stdin one byte of a time). The output is neither modified in any way. + fn into_raw_mode(self) -> io::Result; +} + +impl IntoRawMode for Stdout { + fn into_raw_mode(self) -> io::Result { + RawScreen::into_raw_mode()?; + // this make's sure that raw screen will be disabled when it goes out of scope. + Ok(RawScreen { drop: true }) + } +} + +impl Drop for RawScreen { + fn drop(&mut self) { + if self.drop == true { + RawScreen::disable_raw_mode().unwrap(); + } + } } diff --git a/crossterm_screen/src/screen/screen.rs b/crossterm_screen/src/screen/screen.rs deleted file mode 100644 index 86d87d5..0000000 --- a/crossterm_screen/src/screen/screen.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::{AlternateScreen, RawScreen}; -use crossterm_utils::TerminalOutput; - -use std::io::Result; -use std::io::Write; -use std::sync::Arc; - -/// This type represents a screen which could be in normal, raw and alternate modes. -/// -/// Let's talk about the different modes a bit: -/// -/// - Alternate modes: -/// -/// *Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them. -/// The alternate buffer is exactly the dimensions of the window, without any scrollback region. -/// For an example of this behavior, consider when vim is launched from bash. -/// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged. -/// -/// - RawModes -/// - No line buffering. -/// Normally the terminals use line buffering. This means that the input will be sent to the terminal line by line. -/// With raw mode the input will send one byte at a time. -/// - Input -/// All input has to be written manually by the programmer. -/// - Characters -/// The characters are not processed by the terminal driver but are sent straight through. -/// Special character have no meaning, like backspace will not be interpreted as backspace but instead will be directly sent to the terminal. -/// - Escape characters -/// Note that in raw modes `\n` `\r` will move to the new line but the cursor will be at the same position as before on the new line therefor use `\n\r` to start at the new line at the first cell. -/// -/// You have to make sure that you pass the correct `Screen` to the modules `cursor, terminal, color, input, style`. -/// If you switch to alternate screen modes you will get some `Screen` handle back. This `Screen` handle represents the alternate screen. -/// Once you want to do coloring or such you need to pass the `Screen` handle the library so that it could be used for coloring on the right screen. -/// -/// # Example -/// ```rust -/// // create default screen (not raw). -/// let screen = Screen::default(); -/// -/// // create raw screen. -/// let mut screen = Screen::new(true); -/// -/// // create a `Screen` with raw modes disabled. -/// let screen = Screen::new(false); -/// -/// // create 'raw alternate screen' from normal screen. -/// if let Ok(alternate_screen) = screen.enable_alternate_modes(true) -/// { -/// // 'alternate screen' is an instance which you should use when you want your actions like: coloring and cursor movement happening at the alternate screen. -/// // For that you can use `Crossterm::from_screen(alternate.screen)` so that all modules like: cursor, input, terminal will be executed on alternate screen. -/// let crossterm = Crossterm::from_screen(&alternate_screen.screen); -/// crossterm.cursor(); -/// crossterm.terminal(); -/// -/// // If you want access modules directly without the `Crossterm` type. You should do the following: -/// let cursor = crossterm::cursor::from_screen(&alternate_screen.screen); -/// let terminal = crossterm::terminal::from_screen(&alternate_screen.screen); -/// let input = crossterm::input::from_screen(&alternate_screen.screen); -/// } -/// ``` -/// # Remarks -/// Note that using `Screen` is preferred over manually using `AlternateScreen` or `RawScreen`. -pub struct Screen { - buffer: Vec, - pub stdout: Arc, - drop: bool, -} - -impl Screen { - /// Create a new instance of the Screen also specify if the current screen should be in raw mode or normal mode. - /// If you are not sure what raw mode is then passed false or use the `Screen::default()` to create an instance. - pub fn new(raw_mode: bool) -> Screen { - if raw_mode { - let screen = Screen { - stdout: Arc::new(TerminalOutput::new(true)), - buffer: Vec::new(), - drop: true, - }; - RawScreen::into_raw_mode().unwrap(); - return screen; - } - - Screen::default() - } - - /// Switch to alternate screen. This function will return an `AlternateScreen` instance. If everything went well this type will give you control over the `AlternateScreen`. - /// - /// The bool 'raw_mode' specifies whether the alternate screen should be raw mode or not. - /// - /// # What is Alternate screen? - /// *Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them. - /// The alternate buffer is exactly the dimensions of the window, without any scrollback region. - /// For an example of this behavior, consider when vim is launched from bash. - /// Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged. - pub fn enable_alternate_modes(&self, raw_mode: bool) -> Result { - let stdout = TerminalOutput::new(raw_mode); - - let alternate_screen = AlternateScreen::to_alternate_screen(stdout, raw_mode)?; - Ok(alternate_screen) - } - - /// Write buffer to an internal buffer. When you want to write the buffer to screen use `flush_buf()`. - /// - /// This function is useful if you want to build up some output and when you are ready you could flush the output to the screen. - /// - /// # Example - /// ``` - /// // write some text to the internal buffer of this type. Note that this will not be printed until you call `flush_buf` - /// let screen = Screen::default(); - /// screen.write_buf(b"Some text"); - /// screen.write_buf(b"Some more text"); - /// screen.write_buf(b"Some more text"); - /// ``` - pub fn write_buf(&mut self, buf: &[u8]) -> Result { - self.buffer.write(buf) - } - - /// Flush the internal buffer to the screen. - pub fn flush_buf(&mut self) -> Result<()> { - self.stdout.write_buf(&self.buffer)?; - self.stdout.flush()?; - self.buffer.clear(); - Ok(()) - } - - /// This will disable the drop which will cause raw modes not to be undone on the drop of `Screen`. - pub fn disable_drop(&mut self) { - self.drop = false; - } -} - -impl From for Screen { - /// Create a screen with the given `Stdout` - fn from(stdout: TerminalOutput) -> Self { - Screen { - stdout: Arc::new(stdout), - buffer: Vec::new(), - drop: true, - } - } -} - -impl From> for Screen { - /// Create a screen with the given 'Arc' - fn from(stdout: Arc) -> Self { - Screen { - stdout, - buffer: Vec::new(), - drop: true, - } - } -} - -impl Default for Screen { - /// Create a new screen which will not be in raw mode or alternate mode. - fn default() -> Self { - Screen { - stdout: Arc::new(TerminalOutput::new(false)), - buffer: Vec::new(), - drop: true, - } - } -} - -impl Drop for Screen { - /// If the current screen is in raw mode we need to disable it when the instance goes out of scope. - fn drop(&mut self) { - if self.stdout.is_in_raw_mode && self.drop { - RawScreen::disable_raw_modes().unwrap(); - } - } -} - -impl Write for Screen { - fn write(&mut self, buf: &[u8]) -> Result { - self.stdout.write_buf(buf) - } - - fn flush(&mut self) -> Result<()> { - self.stdout.flush() - } -} diff --git a/crossterm_screen/src/sys/mod.rs b/crossterm_screen/src/sys/mod.rs index fe02951..5be831c 100644 --- a/crossterm_screen/src/sys/mod.rs +++ b/crossterm_screen/src/sys/mod.rs @@ -4,9 +4,8 @@ pub mod unix; #[cfg(windows)] pub mod winapi; -use crossterm_utils::TerminalOutput; - -use std::io; +use crossterm_utils::Result; +use std::io::Write; /// This command is used for switching to alternate screen and back to main screen. pub struct ToAlternateScreenCommand; @@ -19,37 +18,20 @@ impl ToAlternateScreenCommand { impl IAlternateScreenCommand for ToAlternateScreenCommand { /// enable alternate screen. - fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()> { - stdout.write_str(csi!("?1049h"))?; + fn enable(&self) -> Result<()> { + write_cout!(csi!("?1049h")).unwrap(); Ok(()) } /// disable alternate screen. - fn disable(&self, stdout: &TerminalOutput) -> io::Result<()> { - stdout.write_str(csi!("?1049l"))?; + fn disable(&self) -> Result<()> { + write_cout!(csi!("?1049l"))?; Ok(()) } } -/// This trait provides a way to execute some state changing commands. -pub trait IStateCommand { - fn execute(&mut self) -> io::Result<()>; - fn undo(&mut self) -> io::Result<()>; -} - -pub trait IEnableAnsiCommand { - fn enable(&self) -> io::Result; - fn disable(&self) -> io::Result<()>; -} - // This trait provides an interface for switching to alternate screen and back. pub trait IAlternateScreenCommand: Sync + Send { - fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>; - fn disable(&self, stdout: &TerminalOutput) -> io::Result<()>; -} - -// This trait provides an interface for switching to raw mode and back. -pub trait IRawScreenCommand: Sync + Send { - fn enable(&mut self) -> io::Result<()>; - fn disable(&self) -> io::Result<()>; + fn enable(&self) -> Result<()>; + fn disable(&self) -> Result<()>; } diff --git a/crossterm_screen/src/sys/unix.rs b/crossterm_screen/src/sys/unix.rs index e17dc41..046022c 100644 --- a/crossterm_screen/src/sys/unix.rs +++ b/crossterm_screen/src/sys/unix.rs @@ -11,18 +11,12 @@ impl RawModeCommand { /// Enables raw mode. pub fn enable(&mut self) -> Result<()> { crossterm_utils::sys::unix::into_raw_mode()?; - - // will be removed in 6.1 - unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = true } Ok(()) } /// Disables raw mode. pub fn disable(&mut self) -> Result<()> { crossterm_utils::sys::unix::disable_raw_mode()?; - - // will be removed in 6.1 - unsafe { crossterm_utils::sys::unix::RAW_MODE_ENABLED_BY_USER = false } Ok(()) } } diff --git a/crossterm_screen/src/sys/winapi.rs b/crossterm_screen/src/sys/winapi.rs index 21a01d1..49605b4 100644 --- a/crossterm_screen/src/sys/winapi.rs +++ b/crossterm_screen/src/sys/winapi.rs @@ -1,5 +1,5 @@ use super::IAlternateScreenCommand; -use crossterm_utils::TerminalOutput; +use crossterm_utils::Result; use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; use std::io; use winapi::shared::minwindef::DWORD; @@ -59,13 +59,13 @@ impl ToAlternateScreenCommand { } impl IAlternateScreenCommand for ToAlternateScreenCommand { - fn enable(&self, _stdout: &mut TerminalOutput) -> io::Result<()> { + fn enable(&self) -> Result<()> { let alternate_screen = ScreenBuffer::create(); alternate_screen.show()?; Ok(()) } - fn disable(&self, _stdout: &TerminalOutput) -> io::Result<()> { + fn disable(&self) -> Result<()> { let screen_buffer = ScreenBuffer::from(Handle::output_handle()?); screen_buffer.show()?; Ok(()) diff --git a/crossterm_style/CHANGELOG.md b/crossterm_style/CHANGELOG.md index 0a8ff51..a3fbae0 100644 --- a/crossterm_style/CHANGELOG.md +++ b/crossterm_style/CHANGELOG.md @@ -1,3 +1,7 @@ +# Changes crossterm_style 0.3 +- Removed `TerminalColor::from_output()` +- Added `NoItalic` attribute + # Changes crossterm_style 0.2 - Introduced more `Attributes` - Introduced easier ways to style text [issue 87](https://github.com/TimonPost/crossterm/issues/87). diff --git a/crossterm_style/Cargo.toml b/crossterm_style/Cargo.toml index e30eddc..f6d61d6 100644 --- a/crossterm_style/Cargo.toml +++ b/crossterm_style/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm_style" -version = "0.2.0" +version = "0.3.0" authors = ["T. Post"] description = "A cross-platform library styling the terminal output." repository = "https://github.com/TimonPost/crossterm" @@ -12,12 +12,8 @@ readme = "README.md" edition = "2018" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.5", features = ["wincon"] } -crossterm_winapi = "0.1.1" +winapi = { version = "0.3.7", features = ["wincon"] } +crossterm_winapi = "0.1.2" [dependencies] -crossterm_utils = "0.1.0" - -[[example]] -name = "style" -path = "examples/style.rs" \ No newline at end of file +crossterm_utils = { path = "../crossterm_utils" } \ No newline at end of file diff --git a/crossterm_style/README.md b/crossterm_style/README.md index 149c3e9..6a6b47c 100644 --- a/crossterm_style/README.md +++ b/crossterm_style/README.md @@ -1,5 +1,5 @@ # Crossterm Style | cross-platform styling. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_style.svg [l1]: https://crates.io/crates/crossterm_style @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_style/badge.svg [l3]: https://docs.rs/crossterm_style/ -[s3]: https://docs.rs/crossterm_style/badge.svg -[l3]: https://docs.rs/crossterm_style/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -41,21 +41,21 @@ When you want to use other modules as well you might want to use crossterm with ## Getting Started -This documentation is only for `crossterm_style` version `0.2` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_style/examples) folders with detailed examples for all functionality of this crate. +This documentation is only for `crossterm_style` version `0.3` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_style/examples) folders with detailed examples for all functionality of this crate. Add the `crossterm_style` package to your `Cargo.toml` file. ``` [dependencies] -`crossterm_style` = "0.2" - +crossterm_style = "0.3" ``` + And import the `crossterm_style` modules you want to use. ```rust extern crate crossterm_style; -pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor}; +pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle, StyledObject, TerminalColor, Colorize, Styler}; ``` ### Useful Links @@ -63,24 +63,24 @@ pub use crossterm_style::{color, style, Attribute, Color, ColorType, ObjectStyle - [Documentation](https://docs.rs/crossterm_input/) - [Crates.io](https://crates.io/crates/crossterm_input) - [Book](http://atcentra.com/crossterm/styling.html) -- [Examples](/examples) +- [Examples](./examples) ## Features These are the features of this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item -- Very few dependenties. +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies - Styled output - - Foreground color (16 base colors) - - Background color (16 base colors) - - 256 color support (Windows 10 and UNIX only) - - RGB support (Windows 10 and UNIX only) - - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) + - Foreground Color (16 base colors) + - Background Color (16 base colors) + - 256 (ANSI) Color Support (Windows 10 and UNIX Only) + - RGB Color Support (Windows 10 and UNIX only) + - Text Attributes: bold, italic, underscore and crossed word and [more](http://atcentra.com/crossterm/styling.html#attributes) (Windows 10 and UNIX only) ## Examples -Check out the [examples](/examples/) for more information about how to use this crate. +The [examples](./examples) folder has more complete and verbose examples. _style font with attributes_ ```rust @@ -101,7 +101,6 @@ _style font with colors_ ```rust use crossterm_style::{Colored, Color, Colorize}; - println!("{} Red foreground color", Colored::Fg(Color::Red)); println!("{} Blue background color", Colored::Bg(Color::Blue)); @@ -123,8 +122,8 @@ println!("{} some colored text", Colored::Fg(Color::Rgb { // custom ansi color value (Windows 10 and UNIX systems) println!("{} some colored text", Colored::Fg(Color::AnsiValue(10))); - ``` + ## Tested terminals - Windows Powershell @@ -140,21 +139,8 @@ println!("{} some colored text", Colored::Fg(Color::AnsiValue(10))); This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested. If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. -## Notice - -This library is average stable now, I don't expect it to not to change that much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors - * **Timon Post** - *Project Owner & creator* ## License - -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_style/src/ansi_color.rs b/crossterm_style/src/ansi_color.rs index f7981fb..eb6cc3f 100644 --- a/crossterm_style/src/ansi_color.rs +++ b/crossterm_style/src/ansi_color.rs @@ -2,10 +2,10 @@ //! This module is used for Windows 10 terminals and Unix terminals by default. use crate::{Color, ITerminalColor}; -use crossterm_utils::{write, write_str, Result, TerminalOutput}; +use crossterm_utils::Result; use crate::Colored; -use std::sync::Arc; +use std::io::Write; /// This struct is an ANSI escape code implementation for color related actions. pub struct AnsiColor; @@ -17,24 +17,24 @@ impl AnsiColor { } impl ITerminalColor for AnsiColor { - fn set_fg(&self, fg_color: Color, stdout: &Option<&Arc>) -> Result<()> { - write( - stdout, - format!(csi!("{}m"), self.color_value(Colored::Fg(fg_color))), - )?; + fn set_fg(&self, fg_color: Color) -> Result<()> { + write_cout!(&format!( + csi!("{}m"), + self.color_value(Colored::Fg(fg_color)) + ))?; Ok(()) } - fn set_bg(&self, bg_color: Color, stdout: &Option<&Arc>) -> Result<()> { - write( - stdout, - format!(csi!("{}m"), self.color_value(Colored::Bg(bg_color))), - )?; + fn set_bg(&self, bg_color: Color) -> Result<()> { + write_cout!(&format!( + csi!("{}m"), + self.color_value(Colored::Bg(bg_color)) + ))?; Ok(()) } - fn reset(&self, stdout: &Option<&Arc>) -> Result<()> { - write_str(stdout, csi!("0m"))?; + fn reset(&self) -> Result<()> { + write_cout!(csi!("0m"))?; Ok(()) } diff --git a/crossterm_style/src/color.rs b/crossterm_style/src/color.rs index 07f2fba..400bb1d 100644 --- a/crossterm_style/src/color.rs +++ b/crossterm_style/src/color.rs @@ -5,12 +5,10 @@ use std::io; use super::*; use crate::{Color, ITerminalColor}; -use crossterm_utils::{Result, TerminalOutput}; +use crossterm_utils::Result; #[cfg(windows)] -use crossterm_utils::get_module; - -use std::sync::Arc; +use crossterm_utils::supports_ansi; /// Allows you to style the terminal. /// @@ -23,82 +21,42 @@ use std::sync::Arc; /// - Text Attributes like: bold, italic, underscore and crossed word ect (Windows 10 and UNIX only) /// /// Check `/examples/` in the library for more specific examples. -/// -/// # Remarks -/// -/// When you want to 'style' on 'alternate screen' use the 'crossterm_screen' crate. -pub struct TerminalColor<'stdout> { - color: Box, - stdout: Option<&'stdout Arc>, +pub struct TerminalColor { + #[cfg(windows)] + color: Box<(dyn ITerminalColor + Sync + Send)>, + #[cfg(unix)] + color: AnsiColor, } -impl<'stdout> TerminalColor<'stdout> { +impl TerminalColor { /// Create new instance whereon color related actions can be performed. - pub fn new() -> TerminalColor<'stdout> { - #[cfg(target_os = "windows")] - let color = get_module::>( - Box::from(WinApiColor::new()), - Box::from(AnsiColor::new()), - ) - .expect("could not extract module"); + pub fn new() -> TerminalColor { + #[cfg(windows)] + let color = if supports_ansi() { + Box::from(AnsiColor::new()) as Box<(dyn ITerminalColor + Sync + Send)> + } else { + WinApiColor::new() as Box<(dyn ITerminalColor + Sync + Send)> + }; - #[cfg(not(target_os = "windows"))] - let color = Box::from(AnsiColor::new()) as Box; + #[cfg(unix)] + let color = AnsiColor::new(); - TerminalColor { - color, - stdout: None, - } - } - - /// Create a new instance of `TerminalColor` whereon coloring could be preformed on the given output. - /// - /// # Remarks - /// - /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode', - /// and you want your actions from the `TerminalColor`, created by this function, to operate on the 'alternate screen'. - /// - /// You should checkout the 'crossterm_screen' crate for more information about this. - /// - /// # Example - /// ``` - /// let screen = Screen::default(); - // - /// if let Ok(alternate) = screen.enable_alternate_modes(false) { - /// let terminal = TerminalColor::from_output(&alternate.screen.stdout); - /// } - /// ``` - pub fn from_output(stdout: &'stdout Arc) -> TerminalColor<'stdout> { - #[cfg(target_os = "windows")] - let color = get_module::>( - Box::from(WinApiColor::new()), - Box::from(AnsiColor::new()), - ) - .unwrap(); - - #[cfg(not(target_os = "windows"))] - let color = Box::from(AnsiColor::new()) as Box; - - TerminalColor { - color, - stdout: Some(stdout), - } + TerminalColor { color } } /// Set the foreground color to the given color. pub fn set_fg(&self, color: Color) -> Result<()> { - self.color.set_fg(color, &self.stdout) + self.color.set_fg(color) } /// Set the background color to the given color. pub fn set_bg(&self, color: Color) -> Result<()> { - self.color.set_bg(color, &self.stdout) + self.color.set_bg(color) } /// Reset the terminal colors and attributes to default. pub fn reset(&self) -> Result<()> { - self.color.reset(&self.stdout) + self.color.reset() } /// Get available color count. @@ -119,6 +77,6 @@ impl<'stdout> TerminalColor<'stdout> { } /// Get a `TerminalColor` implementation whereon color related actions can be performed. -pub fn color<'stdout>() -> TerminalColor<'stdout> { +pub fn color() -> TerminalColor { TerminalColor::new() } diff --git a/crossterm_style/src/enums/attribute.rs b/crossterm_style/src/enums/attribute.rs index bd0e0f0..1c00fed 100644 --- a/crossterm_style/src/enums/attribute.rs +++ b/crossterm_style/src/enums/attribute.rs @@ -84,6 +84,12 @@ pub enum Attribute { /// - Opposite of `Bold`(1) /// [Supportability]: not widely supported NoBold = 21, + /// This will turn off the italic attribute. + /// [info]: + /// - Not italic, not Fraktur + /// - Opposite of `Italic`(3) + /// [Supportability]: Windows, UNIX + NoItalic = 23, /// This will turn off the underline attribute. /// [info]: /// - Not singly or doubly underlined will be turned off. @@ -137,7 +143,7 @@ pub enum Attribute { impl Display for Attribute { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", format!(csi!("{}m"), *self as i16))?; - stdout().flush(); + stdout().flush().unwrap(); Ok(()) } } diff --git a/crossterm_style/src/enums/colored.rs b/crossterm_style/src/enums/colored.rs index a3f5c47..521482d 100644 --- a/crossterm_style/src/enums/colored.rs +++ b/crossterm_style/src/enums/colored.rs @@ -28,15 +28,15 @@ pub enum Colored { } impl Display for Colored { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { + fn fmt(&self, _f: &mut ::std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { let colored_terminal = color(); match *self { Colored::Fg(color) => { - colored_terminal.set_fg(color); + colored_terminal.set_fg(color).unwrap(); } Colored::Bg(color) => { - colored_terminal.set_bg(color); + colored_terminal.set_bg(color).unwrap(); } } diff --git a/crossterm_style/src/lib.rs b/crossterm_style/src/lib.rs index bdd18dc..31b418b 100644 --- a/crossterm_style/src/lib.rs +++ b/crossterm_style/src/lib.rs @@ -3,7 +3,7 @@ #[macro_use] extern crate crossterm_utils; -#[cfg(target_os = "windows")] +#[cfg(windows)] extern crate crossterm_winapi; #[macro_use] @@ -15,27 +15,21 @@ pub mod styledobject; mod traits; mod ansi_color; -#[cfg(target_os = "windows")] +#[cfg(windows)] mod winapi_color; use self::ansi_color::AnsiColor; -#[cfg(target_os = "windows")] +#[cfg(windows)] use self::winapi_color::WinApiColor; -use std::convert::From; use std::fmt::Display; -use std::str::FromStr; -use std::sync::Arc; pub use self::color::{color, TerminalColor}; pub use self::enums::{Attribute, Color, Colored}; pub use self::objectstyle::ObjectStyle; -pub use self::styledobject::DisplayableObject; pub use self::styledobject::StyledObject; pub use self::traits::{Colorize, Styler}; -use crossterm_utils::{Result, TerminalOutput}; -use std::io::stdout; -use std::io::Write; +use crossterm_utils::Result; /// This trait defines the actions that can be preformed with terminal color. /// This trait can be implemented so that a concrete implementation of the ITerminalColor can fulfill @@ -47,11 +41,11 @@ use std::io::Write; /// so that color-related actions can be performed on both UNIX and Windows systems. trait ITerminalColor { /// Set the foreground color to the given color. - fn set_fg(&self, fg_color: Color, stdout: &Option<&Arc>) -> Result<()>; + fn set_fg(&self, fg_color: Color) -> Result<()>; /// Set the background color to the given color. - fn set_bg(&self, fg_color: Color, stdout: &Option<&Arc>) -> Result<()>; + fn set_bg(&self, fg_color: Color) -> Result<()>; /// Reset the terminal color to default. - fn reset(&self, stdout: &Option<&Arc>) -> Result<()>; + fn reset(&self) -> Result<()>; /// Gets an value that represents an color from the given `Color` and `ColorType`. fn color_value(&self, cored: Colored) -> String; } diff --git a/crossterm_style/src/macros.rs b/crossterm_style/src/macros.rs index 0abbf84..e2de61e 100644 --- a/crossterm_style/src/macros.rs +++ b/crossterm_style/src/macros.rs @@ -1,9 +1,7 @@ -use crate::{ObjectStyle, StyledObject}; - macro_rules! def_attr { ($name: ident => $attr: path) => { fn $name(self) -> StyledObject { - let mut so = self; + let so = self; so.attr($attr) } diff --git a/crossterm_style/src/objectstyle.rs b/crossterm_style/src/objectstyle.rs index 8179dda..4a91ddd 100644 --- a/crossterm_style/src/objectstyle.rs +++ b/crossterm_style/src/objectstyle.rs @@ -11,15 +11,14 @@ use super::Attribute; pub struct ObjectStyle { pub fg_color: Option, pub bg_color: Option, - pub attrs: Vec, } impl Default for ObjectStyle { fn default() -> ObjectStyle { ObjectStyle { - fg_color: Some(Color::White), - bg_color: Some(Color::Black), + fg_color: None, + bg_color: None, attrs: Vec::new(), } } diff --git a/crossterm_style/src/styledobject.rs b/crossterm_style/src/styledobject.rs index 06770f8..cab8262 100644 --- a/crossterm_style/src/styledobject.rs +++ b/crossterm_style/src/styledobject.rs @@ -1,18 +1,14 @@ //! This module contains the logic to style an object that contains some 'content' which can be styled. use super::{color, Color, ObjectStyle}; -//use Screen; -use crossterm_utils::{Result, TerminalOutput}; use std::fmt::{self, Display, Formatter}; use std::io::Write; use std::result; -use std::sync::Arc; use super::Attribute; -use crate::Colorize; -use crate::Styler; +use crate::{Colorize, Styler}; -/// Struct that contains both the style and the content wits can be styled. +/// Contains both the style and the content wits can be styled. pub struct StyledObject { pub object_style: ObjectStyle, pub content: D, @@ -51,70 +47,12 @@ impl<'a, D: Display + 'a> StyledObject { self.object_style.add_attr(attr); self } - - /// This converts an styled object into an `DisplayableObject` witch implements: `Display` and could be used inside the write function of the standard library. - /// - /// _StyledObject already implements `Display` right?_ - /// - /// This is true, however there are some complex issues why this won't work on alternate screen. - /// That is the reason why this functions exists. - /// You could just pass in the 'screen' from your alternate screen to this method and your `StyledObject` will be printed to the alternate screen just fine. - /// - /// ``` - /// let screen = Screen::default(); /* represents the alternate screen */ - /// let styled_object = style("test").with(Color::Yellow); - /// let display_object = styled_object.into_displayable(&screen); - /// println!("Colored text: {}. Default color", display_object); - /// ``` - pub fn into_displayable(self, stdout: &'a Arc) -> 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(()) - } } impl Display for StyledObject { fn fmt(&self, f: &mut Formatter) -> result::Result<(), fmt::Error> { let colored_terminal = color(); - let mut reset = true; + let mut reset = false; if let Some(bg) = self.object_style.bg_color { colored_terminal.set_bg(bg).unwrap(); @@ -191,33 +129,3 @@ impl Styler for StyledObject { def_attr!(hidden => Attribute::Hidden); def_attr!(crossed_out => Attribute::CrossedOut); } - -/// This is a wrapper for a styled object on 'alternate screen' so that the styled object could be printed on the 'alternate screen' with the standard write functions in rust. -/// -/// ``` -/// write! ("some normal text, {} <- some colored text", DisplayableObject::new(&screen, styled_object)); -/// println! ("some normal text, {} <- some colored text", DisplayableObject::new(&screen, styled_object)); -/// ``` -pub struct DisplayableObject<'a, D: Display + 'a> { - styled_object: StyledObject, - output: &'a Arc, -} - -impl<'a, D: Display + 'a> DisplayableObject<'a, D> { - pub fn new( - screen: &'a Arc, - styled_object: StyledObject, - ) -> DisplayableObject<'a, D> { - DisplayableObject { - output: screen, - styled_object, - } - } -} - -impl<'a, D: Display + 'a> Display for DisplayableObject<'a, D> { - fn fmt(&self, _f: &mut Formatter) -> result::Result<(), fmt::Error> { - self.styled_object.paint(self.output).unwrap(); - Ok(()) - } -} diff --git a/crossterm_style/src/traits.rs b/crossterm_style/src/traits.rs index 51bfc30..94fac65 100644 --- a/crossterm_style/src/traits.rs +++ b/crossterm_style/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{ObjectStyle, StyledObject}; +use crate::StyledObject; use std::fmt::Display; /// Provides a set of methods to color any type implementing `Display` with attributes. diff --git a/crossterm_style/src/winapi_color.rs b/crossterm_style/src/winapi_color.rs index 5b08142..e6f4f24 100644 --- a/crossterm_style/src/winapi_color.rs +++ b/crossterm_style/src/winapi_color.rs @@ -2,10 +2,9 @@ //! This module is used for non supporting `ANSI` Windows terminals. use crate::{Color, Colored, ITerminalColor}; -use crossterm_utils::{Result, TerminalOutput}; +use crossterm_utils::Result; use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; use std::io; -use std::sync::Arc; use std::sync::{Once, ONCE_INIT}; use winapi::um::wincon; @@ -13,13 +12,13 @@ use winapi::um::wincon; pub struct WinApiColor; impl WinApiColor { - pub fn new() -> WinApiColor { - WinApiColor + pub fn new() -> Box { + Box::from(WinApiColor) } } impl ITerminalColor for WinApiColor { - fn set_fg(&self, fg_color: Color, _stdout: &Option<&Arc>) -> Result<()> { + fn set_fg(&self, fg_color: Color) -> Result<()> { // init the original color in case it is not set. let _ = init_console_color()?; @@ -46,7 +45,7 @@ impl ITerminalColor for WinApiColor { Ok(()) } - fn set_bg(&self, bg_color: Color, _stdout: &Option<&Arc>) -> Result<()> { + fn set_bg(&self, bg_color: Color) -> Result<()> { // init the original color in case it is not set. let _ = init_console_color()?; @@ -73,7 +72,7 @@ impl ITerminalColor for WinApiColor { Ok(()) } - fn reset(&self, _stdout: &Option<&Arc>) -> Result<()> { + fn reset(&self) -> Result<()> { // init the original color in case it is not set. let original_color = original_console_color(); Console::from(Handle::new(HandleType::CurrentOutputHandle)?) diff --git a/crossterm_terminal/CHANGELOG.md b/crossterm_terminal/CHANGELOG.md new file mode 100644 index 0000000..0db4e14 --- /dev/null +++ b/crossterm_terminal/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changes crossterm_style 0.2 +- Removed `Terminal:from_output()` + +# Changes crossterm_terminal 0.1 +- Moved out of `crossterm` 5.4 crate. \ No newline at end of file diff --git a/crossterm_terminal/Cargo.toml b/crossterm_terminal/Cargo.toml index 8e6a950..5bea6b6 100644 --- a/crossterm_terminal/Cargo.toml +++ b/crossterm_terminal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crossterm_terminal" -version = "0.1.0" +version = "0.2.0" authors = ["T. Post"] description = "A cross-platform library for doing terminal related actions." repository = "https://github.com/TimonPost/crossterm" @@ -12,15 +12,11 @@ readme = "README.md" edition = "2018" [target.'cfg(windows)'.dependencies] -crossterm_winapi = "0.1.1" +crossterm_winapi = "0.1.2" [target.'cfg(unix)'.dependencies] -libc = "0.2.43" +libc = "0.2.51" [dependencies] -crossterm_utils = "0.1.0" -crossterm_cursor = "0.1.0" - -[[example]] -name = "terminal" -path = "examples/terminal.rs" \ No newline at end of file +crossterm_utils = { path = "../crossterm_utils" } +crossterm_cursor = { path = "../crossterm_cursor" } \ No newline at end of file diff --git a/crossterm_terminal/README.md b/crossterm_terminal/README.md index 04bb490..455323a 100644 --- a/crossterm_terminal/README.md +++ b/crossterm_terminal/README.md @@ -1,5 +1,5 @@ # Crossterm Terminal | cross-platform terminal actions. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_terminal.svg [l1]: https://crates.io/crates/crossterm_terminal @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_terminal/badge.svg [l3]: https://docs.rs/crossterm_terminal/ -[s3]: https://docs.rs/crossterm_terminal/badge.svg -[l3]: https://docs.rs/crossterm_terminal/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -41,13 +41,13 @@ When you want to use other modules as well you might want to use crossterm with ## Getting Started -This documentation is only for `crossterm_terminal` version `0.1` if you have an older version I suggest you check the [Upgrade Manual](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md). Also, check out the [examples](https://github.com/TimonPost/crossterm/tree/master/crossterm_terminal/examples) folders with detailed examples for all functionality of this crate. +This documentation is only for `crossterm_terminal` version `0.2` check the [examples](./examples) folders with detailed examples for all functionality of this crate. Add the `crossterm_terminal` package to your `Cargo.toml` file. ``` [dependencies] -`crossterm_terminal` = "0.1" +crossterm_terminal = "0.2" ``` And import the `crossterm_terminal` modules you want to use. @@ -68,20 +68,17 @@ pub use crossterm_terminal::{terminal, Terminal, ClearType}; These are the features of this crate: - Cross-platform -- Everything is multithreaded (Send, Sync) -- Detailed documentation on every item -- Very few dependenties. +- Multithreaded (send, sync) +- Detailed Documentation +- Few Dependencies - Terminal - Clearing (all lines, current line, from cursor down and up, until new line) - - Scrolling (Up, down) - - Get the size of the terminal - - Set the size of the terminal - - Alternate screen - - Raw screen - - Exit the current process + - Scrolling (up, down) + - Terminal Size (get/set) + - Exit Current Process ## Examples -Check out the [examples](/examples/) for more information about how to use this crate. +The [examples](./examples) folder has more complete and verbose examples. ```rust use crossterm::terminal::{terminal,ClearType}; @@ -132,21 +129,8 @@ terminal.write("Some text\n Some text on new line"); This crate supports all Unix terminals and windows terminals down to Windows 7 but not all of them have been tested. If you have used this library for a terminal other than the above list without issues feel free to add it to the above list, I really would appreciate it. -## Notice - -This library is average stable now, I don't expect it to not to change that much. -If there are any changes that will affect previous versions I will [describe](https://github.com/TimonPost/crossterm/blob/master/docs/UPGRADE.md) what to change to upgrade. - -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors - * **Timon Post** - *Project Owner & creator* ## License - -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_terminal/src/terminal/ansi_terminal.rs b/crossterm_terminal/src/terminal/ansi_terminal.rs index f79ab6e..07d45b9 100644 --- a/crossterm_terminal/src/terminal/ansi_terminal.rs +++ b/crossterm_terminal/src/terminal/ansi_terminal.rs @@ -4,62 +4,57 @@ use super::ITerminal; use crate::{sys::get_terminal_size, ClearType}; use crossterm_cursor::TerminalCursor; -use crossterm_utils::{write, write_str, Result, TerminalOutput}; -use std::sync::Arc; +use crossterm_utils::Result; +use std::io::Write; /// This struct is an ansi escape code implementation for terminal related actions. pub struct AnsiTerminal; impl AnsiTerminal { pub fn new() -> AnsiTerminal { - AnsiTerminal {} + AnsiTerminal } } impl ITerminal for AnsiTerminal { - fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc>) -> Result<()> { + fn clear(&self, clear_type: ClearType) -> Result<()> { match clear_type { ClearType::All => { - write_str(&stdout, csi!("2J"))?; + write_cout!(csi!("2J"))?; TerminalCursor::new().goto(0, 0)?; } ClearType::FromCursorDown => { - write_str(&stdout, csi!("J"))?; + write_cout!(csi!("J"))?; } ClearType::FromCursorUp => { - write_str(&stdout, csi!("1J"))?; + write_cout!(csi!("1J"))?; } ClearType::CurrentLine => { - write_str(&stdout, csi!("2K"))?; + write_cout!(csi!("2K"))?; } ClearType::UntilNewLine => { - write_str(&stdout, csi!("K"))?; + write_cout!(csi!("K"))?; } }; Ok(()) } - fn terminal_size(&self, _stdout: &Option<&Arc>) -> (u16, u16) { + fn terminal_size(&self) -> (u16, u16) { get_terminal_size() } - fn scroll_up(&self, count: i16, stdout: &Option<&Arc>) -> Result<()> { - write(&stdout, format!(csi!("{}S"), count))?; + fn scroll_up(&self, count: i16) -> Result<()> { + write_cout!(&format!(csi!("{}S"), count))?; Ok(()) } - fn scroll_down(&self, count: i16, stdout: &Option<&Arc>) -> Result<()> { - write(&stdout, format!(csi!("{}T"), count))?; + fn scroll_down(&self, count: i16) -> Result<()> { + write_cout!(&format!(csi!("{}T"), count))?; Ok(()) } - fn set_size( - &self, - width: i16, - height: i16, - stdout: &Option<&Arc>, - ) -> Result<()> { - write(&stdout, format!(csi!("8;{};{}t"), height, width))?; + fn set_size(&self, width: i16, height: i16) -> Result<()> { + write_cout!(&format!(csi!("8;{};{}t"), height, width))?; Ok(()) } } diff --git a/crossterm_terminal/src/terminal/mod.rs b/crossterm_terminal/src/terminal/mod.rs index 1cf8f3e..d9c4078 100644 --- a/crossterm_terminal/src/terminal/mod.rs +++ b/crossterm_terminal/src/terminal/mod.rs @@ -5,18 +5,16 @@ mod test; mod terminal; mod ansi_terminal; -#[cfg(target_os = "windows")] +#[cfg(windows)] mod winapi_terminal; use self::ansi_terminal::AnsiTerminal; -#[cfg(target_os = "windows")] +#[cfg(windows)] use self::winapi_terminal::WinApiTerminal; pub use self::terminal::{terminal, Terminal}; -use crossterm_utils::{Result, TerminalOutput}; - -use std::sync::Arc; +use crossterm_utils::Result; /// Enum that specifies a way of clearing. pub enum ClearType { @@ -42,18 +40,13 @@ pub enum ClearType { /// so that terminal related actions can be preformed on both Unix and Windows systems. trait ITerminal { /// Clear the current cursor by specifying the clear type - fn clear(&self, clear_type: ClearType, stdout: &Option<&Arc>) -> Result<()>; + fn clear(&self, clear_type: ClearType) -> Result<()>; /// Get the terminal size (x,y) - fn terminal_size(&self, stdout: &Option<&Arc>) -> (u16, u16); + fn terminal_size(&self) -> (u16, u16); /// Scroll `n` lines up in the current terminal. - fn scroll_up(&self, count: i16, stdout: &Option<&Arc>) -> Result<()>; + fn scroll_up(&self, count: i16) -> Result<()>; /// Scroll `n` lines down in the current terminal. - fn scroll_down(&self, count: i16, stdout: &Option<&Arc>) -> Result<()>; + fn scroll_down(&self, count: i16) -> Result<()>; /// Resize terminal to the given width and height. - fn set_size( - &self, - width: i16, - height: i16, - stdout: &Option<&Arc>, - ) -> Result<()>; + fn set_size(&self, width: i16, height: i16) -> Result<()>; } diff --git a/crossterm_terminal/src/terminal/terminal.rs b/crossterm_terminal/src/terminal/terminal.rs index 2099ccd..7faaec2 100644 --- a/crossterm_terminal/src/terminal/terminal.rs +++ b/crossterm_terminal/src/terminal/terminal.rs @@ -2,15 +2,15 @@ //! Like clearing and scrolling in the terminal or getting the window size from the terminal. use super::{AnsiTerminal, ClearType, ITerminal}; -use crossterm_utils::{write, Result, TerminalOutput}; +use crossterm_utils::Result; #[cfg(windows)] use super::WinApiTerminal; #[cfg(windows)] -use crossterm_utils::get_module; +use crossterm_utils::supports_ansi; use std::fmt; -use std::sync::Arc; +use std::io::Write; /// Allows you to preform actions on the terminal. /// @@ -25,67 +25,27 @@ use std::sync::Arc; /// - Exit the current process /// /// Check `/examples/` in the library for more specific examples. -/// -/// # Remarks -/// -/// When you want to perform terminal actions on 'alternate screen' use the 'crossterm_screen' crate. -pub struct Terminal<'stdout> { - terminal: Box, - stdout: Option<&'stdout Arc>, +pub struct Terminal { + #[cfg(windows)] + terminal: Box<(dyn ITerminal + Sync + Send)>, + #[cfg(unix)] + terminal: AnsiTerminal, } -impl<'stdout> Terminal<'stdout> { +impl Terminal { /// Create new terminal instance whereon terminal related actions can be performed. - pub fn new() -> Terminal<'stdout> { - #[cfg(target_os = "windows")] - let terminal = get_module::>( - Box::new(WinApiTerminal::new()), - Box::new(AnsiTerminal::new()), - ) - .unwrap(); + pub fn new() -> Terminal { + #[cfg(windows)] + let terminal = if supports_ansi() { + Box::from(AnsiTerminal::new()) as Box<(dyn ITerminal + Sync + Send)> + } else { + WinApiTerminal::new() as Box<(dyn ITerminal + Sync + Send)> + }; - #[cfg(not(target_os = "windows"))] - let terminal = Box::from(AnsiTerminal::new()) as Box; + #[cfg(unix)] + let terminal = AnsiTerminal::new(); - Terminal { - terminal, - stdout: None, - } - } - - /// Create a new instance of `Terminal` whereon terminal related actions could be preformed on the given output. - /// - /// # Remarks - /// - /// Use this function when you want your terminal to operate with a specific output. - /// This could be useful when you have a screen which is in 'alternate mode', - /// and you want your actions from the `Terminal`, created by this function, to operate on the 'alternate screen'. - /// - /// You should checkout the 'crossterm_screen' crate for more information about this. - /// - /// # Example - /// ``` - /// let screen = Screen::default(); - // - /// if let Ok(alternate) = screen.enable_alternate_modes(false) { - /// let terminal = Terminal::from_output(&alternate.screen.stdout); - /// } - /// ``` - pub fn from_output(stdout: &'stdout Arc) -> Terminal<'stdout> { - #[cfg(target_os = "windows")] - let terminal = get_module::>( - Box::new(WinApiTerminal::new()), - Box::new(AnsiTerminal::new()), - ) - .unwrap(); - - #[cfg(not(target_os = "windows"))] - let terminal = Box::from(AnsiTerminal::new()) as Box; - - Terminal { - terminal, - stdout: Some(stdout), - } + Terminal { terminal } } /// Clear the current cursor by specifying the `ClearType`. @@ -106,7 +66,7 @@ impl<'stdout> Terminal<'stdout> { /// term.clear(terminal::ClearType::UntilNewLine); /// ``` pub fn clear(&self, clear_type: ClearType) -> Result<()> { - self.terminal.clear(clear_type, &self.stdout) + self.terminal.clear(clear_type) } /// Get the terminal size (x,y). @@ -114,7 +74,7 @@ impl<'stdout> Terminal<'stdout> { /// # Remark /// This will return a tuple of (x: u16, y: u16) pub fn terminal_size(&self) -> (u16, u16) { - self.terminal.terminal_size(&self.stdout) + self.terminal.terminal_size() } /// Scroll `n` lines up in the current terminal. @@ -122,7 +82,7 @@ impl<'stdout> Terminal<'stdout> { /// # Parameter /// - `count`: the number of rows should be shifted up. pub fn scroll_up(&self, count: i16) -> Result<()> { - self.terminal.scroll_up(count, &self.stdout) + self.terminal.scroll_up(count) } /// Scroll `n` lines down in the current terminal. @@ -130,7 +90,7 @@ impl<'stdout> Terminal<'stdout> { /// # Parameter /// - `count`: the number of rows should be shifted down. pub fn scroll_down(&self, count: i16) -> Result<()> { - self.terminal.scroll_down(count, &self.stdout) + self.terminal.scroll_down(count) } /// Set the terminal size. Note that not all terminals can be set to a very small scale. @@ -142,7 +102,7 @@ impl<'stdout> Terminal<'stdout> { /// let size = term.set_size(10,10); /// ``` pub fn set_size(&self, width: i16, height: i16) -> Result<()> { - self.terminal.set_size(width, height, &self.stdout) + self.terminal.set_size(width, height) } /// Exit the current process. @@ -163,16 +123,15 @@ impl<'stdout> Terminal<'stdout> { /// /// let size = term.write("Some text \n Some text on new line"); /// ``` + /// + /// This will also flush the standard output. pub fn write(&self, value: D) -> Result { - use std::fmt::Write; - let mut string = String::new(); - write!(string, "{}", value)?; - let size = write(&self.stdout, string)?; - Ok(size) + write_cout!(value)?; + Ok(0) } } /// Get a `Terminal` instance whereon terminal related actions could performed. -pub fn terminal<'stdout>() -> Terminal<'stdout> { +pub fn terminal() -> Terminal { Terminal::new() } diff --git a/crossterm_terminal/src/terminal/test.rs b/crossterm_terminal/src/terminal/test.rs index 5112d36..ca75d88 100644 --- a/crossterm_terminal/src/terminal/test.rs +++ b/crossterm_terminal/src/terminal/test.rs @@ -9,9 +9,9 @@ mod winapi_tests { fn resize_winapi() { let terminal = WinApiTerminal::new(); - terminal.set_size(30, 30, &None); + terminal.set_size(30, 30); - let (x, y) = terminal.terminal_size(&None); + let (x, y) = terminal.terminal_size(); assert_eq!(x, 30); assert_eq!(y, 30); @@ -25,12 +25,12 @@ fn resize_ansi() { if try_enable_ansi() { let terminal = AnsiTerminal::new(); - terminal.set_size(50, 50, &None).unwrap(); + terminal.set_size(50, 50).unwrap(); // see issue: https://github.com/eminence/terminal-size/issues/11 thread::sleep(time::Duration::from_millis(30)); - let (x, y) = terminal.terminal_size(&None); + let (x, y) = terminal.terminal_size(); assert_eq!(x, 50); assert_eq!(y, 50); diff --git a/crossterm_terminal/src/terminal/winapi_terminal.rs b/crossterm_terminal/src/terminal/winapi_terminal.rs index 8c07242..2c38ba2 100644 --- a/crossterm_terminal/src/terminal/winapi_terminal.rs +++ b/crossterm_terminal/src/terminal/winapi_terminal.rs @@ -5,20 +5,20 @@ use super::*; use crossterm_cursor::sys::winapi::Cursor; -use crossterm_utils::{ErrorKind, Result, TerminalOutput}; +use crossterm_utils::{ErrorKind, Result}; use crossterm_winapi::{Console, Coord, Handle, ScreenBuffer, Size}; /// This struct is an winapi implementation for terminal related actions. pub struct WinApiTerminal; impl WinApiTerminal { - pub fn new() -> WinApiTerminal { - WinApiTerminal {} + pub fn new() -> Box { + Box::from(WinApiTerminal {}) } } impl ITerminal for WinApiTerminal { - fn clear(&self, clear_type: ClearType, _stdout: &Option<&Arc>) -> Result<()> { + fn clear(&self, clear_type: ClearType) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; @@ -38,12 +38,12 @@ impl ITerminal for WinApiTerminal { Ok(()) } - fn terminal_size(&self, _stdout: &Option<&Arc>) -> (u16, u16) { + fn terminal_size(&self) -> (u16, u16) { let csbi = ScreenBuffer::current().unwrap(); csbi.info().unwrap().terminal_size().into() } - fn scroll_up(&self, count: i16, _stdout: &Option<&Arc>) -> Result<()> { + fn scroll_up(&self, count: i16) -> Result<()> { let csbi = ScreenBuffer::current()?; let mut window = csbi.info()?.terminal_window(); @@ -57,7 +57,7 @@ impl ITerminal for WinApiTerminal { Ok(()) } - fn scroll_down(&self, count: i16, _stdout: &Option<&Arc>) -> Result<()> { + fn scroll_down(&self, count: i16) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; let mut window = csbi.terminal_window(); @@ -74,12 +74,7 @@ impl ITerminal for WinApiTerminal { } /// Set the current terminal size - fn set_size( - &self, - width: i16, - height: i16, - _stdout: &Option<&Arc>, - ) -> Result<()> { + fn set_size(&self, width: i16, height: i16) -> Result<()> { if width <= 0 { return Err(ErrorKind::ResizingTerminalFailure(String::from( "Cannot set the terminal width lower than 1", diff --git a/crossterm_utils/Cargo.toml b/crossterm_utils/Cargo.toml index b5b745c..b1a36ff 100644 --- a/crossterm_utils/Cargo.toml +++ b/crossterm_utils/Cargo.toml @@ -1,13 +1,12 @@ [package] name = "crossterm_utils" -version = "0.1.0" +version = "0.2.0" authors = ["Timon Post "] edition = "2018" [target.'cfg(windows)'.dependencies] crossterm_winapi = "0.1.1" -winapi = { version = "0.3.5", features = ["wincon"] } +winapi = { version = "0.3.7", features = ["wincon"] } [target.'cfg(unix)'.dependencies] -libc = "0.2.43" -termios = "0.3.1" \ No newline at end of file +libc = "0.2.51" \ No newline at end of file diff --git a/crossterm_utils/README.md b/crossterm_utils/README.md index f64aaf0..675a36a 100644 --- a/crossterm_utils/README.md +++ b/crossterm_utils/README.md @@ -1,5 +1,5 @@ # Crossterm Utils | crossterm common used code. - ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] + ![Lines of Code][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] [![Join us on Discord][s5]][l5] [s1]: https://img.shields.io/crates/v/crossterm_utils.svg [l1]: https://crates.io/crates/crossterm_utils @@ -10,8 +10,8 @@ [s3]: https://docs.rs/crossterm_utils/badge.svg [l3]: https://docs.rs/crossterm_utils/ -[s3]: https://docs.rs/crossterm_utils/badge.svg -[l3]: https://docs.rs/crossterm_utils/ +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB. [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master @@ -24,16 +24,10 @@ This crate is a utilities crate used by the following [crossterm](https://crates This crate is not meant for standalone use and is really a library with some common used code for crossterm and the above named modules. -## Contributing - -I highly appreciate it when you are contributing to this crate. -Also Since my native language is not English my grammar and sentence order will not be perfect. -So improving this by correcting these mistakes will help both me and the reader of the docs. - ## Authors * **Timon Post** - *Project Owner & creator* ## License -This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/TimonPost/crossterm/blob/master/LICENSE) file for details +This project is licensed under the MIT License - see the [LICENSE.md](./LICENSE) file for details diff --git a/crossterm_utils/src/commands/mod.rs b/crossterm_utils/src/commands/mod.rs deleted file mode 100644 index fd8e418..0000000 --- a/crossterm_utils/src/commands/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! This module contains some commands that could be executed for a specific task. A `Command` is just a little wrapper. - -use crate::output::TerminalOutput; -use std::io; - -//pub mod shared_commands; -// -//#[cfg(not(target_os = "windows"))] -//pub mod unix_command; - -#[cfg(target_os = "windows")] -pub mod win_commands; - -/// This trait provides a way to execute some state changing commands. -pub trait IStateCommand { - fn execute(&mut self) -> io::Result<()>; - fn undo(&mut self) -> io::Result<()>; -} - -// This trait provides an interface for switching to alternate screen and back. -pub trait IAlternateScreenCommand: Sync + Send { - fn enable(&self, stdout: &mut TerminalOutput) -> io::Result<()>; - fn disable(&self, stdout: &TerminalOutput) -> io::Result<()>; -} - -// This trait provides an interface for switching to raw mode and back. -pub trait IRawScreenCommand: Sync + Send { - fn enable(&mut self) -> io::Result<()>; - fn disable(&self) -> io::Result<()>; -} diff --git a/crossterm_utils/src/commands/win_commands.rs b/crossterm_utils/src/commands/win_commands.rs deleted file mode 100644 index 17949e8..0000000 --- a/crossterm_utils/src/commands/win_commands.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! A module which contains the commands that can be used for windows systems. - -use super::{IAlternateScreenCommand, TerminalOutput}; - -use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; -use winapi::shared::minwindef::DWORD; -use winapi::um::wincon; - -use std::io::Result; - -/// This command is used for enabling and disabling raw mode for windows systems. -/// For more info check: https://docs.microsoft.com/en-us/windows/console/high-level-console-modes. -#[derive(Clone, Copy)] -pub struct RawModeCommand { - mask: DWORD, -} -use self::wincon::{ENABLE_LINE_INPUT, ENABLE_WRAP_AT_EOL_OUTPUT}; -impl RawModeCommand { - pub fn new() -> Self { - RawModeCommand { - mask: ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_LINE_INPUT, - } - } -} - -impl RawModeCommand { - /// Enables raw mode. - pub fn enable(&mut self) -> Result<()> { - let console_mode = ConsoleMode::new()?; - - let dw_mode = console_mode.mode()?; - - let new_mode = dw_mode & !self.mask; - - console_mode.set_mode(new_mode)?; - - Ok(()) - } - - /// Disables raw mode. - pub fn disable(&self) -> Result<()> { - let console_mode = ConsoleMode::new()?; - - let dw_mode = console_mode.mode()?; - - let new_mode = dw_mode | self.mask; - - console_mode.set_mode(new_mode)?; - - return Ok(()); - } -} - -/// This command is used for switching to alternate screen and back to main screen. -/// check https://docs.microsoft.com/en-us/windows/console/reading-and-writing-blocks-of-characters-and-attributes for more info -pub struct ToAlternateScreenCommand; - -impl ToAlternateScreenCommand { - pub fn new() -> ToAlternateScreenCommand { - return ToAlternateScreenCommand {}; - } -} - -impl IAlternateScreenCommand for ToAlternateScreenCommand { - fn enable(&self, _stdout: &mut TerminalOutput) -> Result<()> { - let alternate_screen = ScreenBuffer::create(); - alternate_screen.show()?; - Ok(()) - } - - fn disable(&self, _stdout: &TerminalOutput) -> Result<()> { - let screen_buffer = ScreenBuffer::from(Handle::output_handle()?); - screen_buffer.show()?; - Ok(()) - } -} diff --git a/crossterm_utils/src/error.rs b/crossterm_utils/src/error.rs index a85c3f2..86efdcc 100644 --- a/crossterm_utils/src/error.rs +++ b/crossterm_utils/src/error.rs @@ -49,3 +49,12 @@ impl From for ErrorKind { ErrorKind::FmtError(e) } } + +impl From for io::Error { + fn from(e: ErrorKind) -> io::Error { + match e { + ErrorKind::IoError(io) => return io, + _ => io::Error::new(io::ErrorKind::Other, "can not convert error to IO error"), + } + } +} diff --git a/crossterm_utils/src/functions.rs b/crossterm_utils/src/functions.rs index cb63b6b..838be4b 100644 --- a/crossterm_utils/src/functions.rs +++ b/crossterm_utils/src/functions.rs @@ -1,76 +1,33 @@ -use crate::output::TerminalOutput; -use std::io::{self, Write}; -use std::sync::Arc; - #[cfg(windows)] use crate::sys::winapi::ansi::set_virtual_terminal_processing; #[cfg(windows)] -/// Get an module specific implementation of a the generic given type based on the current platform. -/// If the current platform is windows and it supports ansi escape codes it will return the ansi implementation and if not it will return the winapi implementation. -/// If the current platform is unix it will return the ansi implementation. -pub fn get_module(winapi_impl: T, ansi_impl: T) -> Option { +pub fn supports_ansi() -> bool { // Some terminals on windows like GitBash can't use WinaApi calls directly so when we try to enable the ANSI-flag for windows this won't work. // Because of that we should check first if the TERM-variable is set and see if the current terminal is a terminal who does support ANSI. let supports_ansi = is_specific_term(); match supports_ansi { true => { - return Some(ansi_impl); + return true; } false => { // if it is not listed we should try with WinApi to check if we do support ANSI-codes. match set_virtual_terminal_processing(true) { Ok(_) => { - return Some(ansi_impl); + return true; } Err(_) => { - return Some(winapi_impl); + return false; } } } } } -/// This function is used by 'ANSI' modules. Those modules are using an `Option` of `TerminalOutput`. -/// Because it is an option it could be either 'None' or 'Some'. -/// When the `TerminalOutput` is 'None' we write our 'ANSI' escape codes to the default `stdout()` if it is a `Some` -/// - which means we are in alternate screen modes or we have raw screen enabled - we should write to the screen passed by the user. -/// This way our commands or our writes will be done with the passed `TerminalOutput`. -pub fn write(stdout: &Option<&Arc>, string: String) -> io::Result { - match stdout { - None => { - print!("{}", string.as_str()); - - match io::stdout().flush() { - Ok(_) => Ok(string.len()), - Err(e) => Err(e), - } - } - Some(output) => output.write_string(string), - } -} - -/// This function is used by 'ANSI' modules. Those modules are using an `Option` of `TerminalOutput`. -/// Because it is an option it could be either 'None' or 'Some'. -/// When the `TerminalOutput` is 'None' we write our 'ANSI' escape codes to the default `stdout()` if it is a `Some` -/// - which means we are in alternate screen modes or we have raw screen enabled - we should write to the screen passed by the user. -/// This way our commands or our writes will be done with the passed `TerminalOutput`. -pub fn write_str(stdout: &Option<&Arc>, string: &str) -> io::Result { - match stdout { - None => match io::stdout().flush() { - Ok(_) => { - write!(io::stdout(), "{}", string)?; - Ok(string.len()) - } - Err(e) => Err(e), - }, - Some(output) => output.write_str(string), - } -} - // checks if the 'TERM' environment variable is set to check if the terminal supports ANSI-codes. // I got the list of terminals from here: https://github.com/keqingrong/supports-ansi/blob/master/index.js +#[cfg(windows)] fn is_specific_term() -> bool { const TERMS: [&'static str; 15] = [ "xterm", // xterm, PuTTY, Mintty diff --git a/crossterm_utils/src/lib.rs b/crossterm_utils/src/lib.rs index 3e6dc17..fa2cae3 100644 --- a/crossterm_utils/src/lib.rs +++ b/crossterm_utils/src/lib.rs @@ -3,20 +3,13 @@ extern crate crossterm_winapi; #[cfg(windows)] extern crate winapi; -#[cfg(unix)] -extern crate termios; - -pub mod commands; pub mod error; pub mod macros; pub mod sys; mod functions; -mod output; pub use self::error::{ErrorKind, Result}; -pub use self::output::TerminalOutput; #[cfg(windows)] -pub use self::functions::get_module; -pub use self::functions::{write, write_str}; +pub use self::functions::supports_ansi; diff --git a/crossterm_utils/src/macros.rs b/crossterm_utils/src/macros.rs index fb12b8d..c2fbcf2 100644 --- a/crossterm_utils/src/macros.rs +++ b/crossterm_utils/src/macros.rs @@ -1,4 +1,25 @@ +/// Append a the first few characters of an ANSI escape code to the given string. #[macro_export] macro_rules! csi { ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; } + +/// Write a string to standard output whereafter the screen will be flushed. +#[macro_export] +macro_rules! write_cout { + ($string:expr) => {{ + let mut size = 0; + + let result = write!(::std::io::stdout(), "{}", $string); + + match result { + Ok(size) => size, + Err(e) => return Err(crossterm_utils::ErrorKind::IoError(e)), + }; + + match ::std::io::stdout().flush() { + Ok(_) => Ok(size), + Err(e) => Err(crossterm_utils::ErrorKind::IoError(e)), + } + }}; +} diff --git a/crossterm_utils/src/output/ansi_output.rs b/crossterm_utils/src/output/ansi_output.rs deleted file mode 100644 index c3afb56..0000000 --- a/crossterm_utils/src/output/ansi_output.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! This is an ANSI specific implementation for the screen write -//! This module is used for Windows 10 terminals and UNIX terminals by default. -//! This module uses the stdout to write to the console. - -use super::IStdout; - -use std::io::{self, stdout, Stdout, Write}; - -/// This struct is a wrapper for `Stdout` -pub struct AnsiOutput { - pub handle: Stdout, -} - -impl IStdout for AnsiOutput { - fn write_str(&self, string: &str) -> io::Result { - let out = &self.handle; - let mut handle = out.lock(); - let amt = handle.write(string.as_bytes())?; - handle.flush()?; - Ok(amt) - } - - fn write(&self, buf: &[u8]) -> io::Result { - let out = &self.handle; - let mut handle = out.lock(); - handle.write(buf) - } - - fn flush(&self) -> io::Result<()> { - let out = &self.handle; - let mut handle = out.lock(); - handle.flush() - } -} - -impl AnsiOutput { - pub fn new() -> Self { - AnsiOutput { handle: stdout() } - } -} diff --git a/crossterm_utils/src/output/mod.rs b/crossterm_utils/src/output/mod.rs deleted file mode 100644 index 0fb42ea..0000000 --- a/crossterm_utils/src/output/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! A module that provides a uniformed way to write to the output no matter if it is in main, alternate or raw mode. -mod output; - -#[cfg(test)] -mod test; - -mod ansi_output; -#[cfg(target_os = "windows")] -mod winapi_output; - -use self::ansi_output::AnsiOutput; -#[cfg(target_os = "windows")] -use self::winapi_output::WinApiOutput; - -pub use self::output::TerminalOutput; - -use std::io; - -/// This trait defines represents an stdout of an screen. -/// This trait can be implemented so that an concrete implementation of the IStdout can forfill -/// the wishes to work on an specific platform. -/// -/// ## For example: -/// -/// This trait is implemented for `WinApi` (Windows specific) and `ANSI` (Unix specific), -/// so that output related actions can be preformed on both unix and windows systems. -trait IStdout { - /// Write an &str to the current stdout and flush the screen. - fn write_str(&self, string: &str) -> io::Result; - /// Write [u8] buffer to console. - fn write(&self, buf: &[u8]) -> io::Result; - /// Flush the current output. - fn flush(&self) -> io::Result<()>; -} diff --git a/crossterm_utils/src/output/output.rs b/crossterm_utils/src/output/output.rs deleted file mode 100644 index 59db256..0000000 --- a/crossterm_utils/src/output/output.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! This module provides one place to work with the screen. -//! -//! In Rust we can call `stdout()` to get a handle to the current default console handle. -//! However, we can't use `stdout()` to access the alternate screen handle therefore we also won't be able to use `print!(), println!(), or write!()`. -//! The same goes for coloring, cursor movement, input, and terminal actions. -//! All of those functions are writing to the standard output and not to our alternate screen we are currently on. -//! -//! To get the handle to the `alternate screen` we first need to store this handle so that we are able to call it later on. -//! Through this stored handle, crossterm can write to or execute commands at the current screen whether it be an alternate screen or main screen. -//! -//! For UNIX and Windows10 systems, we store the handle gotten from `stdout()`. For Windows systems who are not supporting ANSI escape codes, we can call `CONOUT$` to get the current screen `HANDLE`. - -use super::*; -use crate::functions; -use std::default::Default; -use std::io::Write; - -/// Struct that is a handle to the current terminal screen. -/// -/// For UNIX and Windows 10 `stdout()` will be used as handle. And for Windows systems, not supporting ANSI escape codes, will use WinApi's `HANDLE` as handle. -pub struct TerminalOutput { - stdout: Box, - /// checks if this output is in raw mode. - pub is_in_raw_mode: bool, -} - -impl TerminalOutput { - /// Create a new screen write instance whereon screen related actions can be performed. - pub fn new(raw_mode: bool) -> Self { - #[cfg(target_os = "windows")] - let stdout: Box = - functions::get_module::>( - Box::from(WinApiOutput::new()), - Box::from(AnsiOutput::new()), - ) - .unwrap(); - - #[cfg(not(target_os = "windows"))] - let stdout = Box::from(AnsiOutput::new()) as Box; - - TerminalOutput { - stdout, - is_in_raw_mode: raw_mode, - } - } - - /// Write String to the current screen. - pub fn write_string(&self, string: String) -> io::Result { - self.stdout.write_str(string.as_str()) - } - - /// Flush the current screen. - pub fn flush(&self) -> io::Result<()> { - self.stdout.flush() - } - - /// Write &str to the current screen. - pub fn write_str(&self, string: &str) -> io::Result { - self.stdout.write_str(string) - } - - /// Write buffer to the screen - pub fn write_buf(&self, buf: &[u8]) -> io::Result { - self.stdout.write(buf) - } -} - -impl Write for TerminalOutput { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.write_buf(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.stdout.flush() - } -} - -impl Default for TerminalOutput { - /// Get the default handle to the current screen. - fn default() -> Self { - #[cfg(target_os = "windows")] - let stdout = functions::get_module::>( - Box::from(WinApiOutput::new()), - Box::from(AnsiOutput::new()), - ) - .unwrap(); - - #[cfg(not(target_os = "windows"))] - let stdout = Box::from(AnsiOutput::new()) as Box; - - TerminalOutput { - stdout, - is_in_raw_mode: false, - } - } -} diff --git a/crossterm_utils/src/output/sys/mod.rs b/crossterm_utils/src/output/sys/mod.rs deleted file mode 100644 index 3a06e02..0000000 --- a/crossterm_utils/src/output/sys/mod.rs +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index c8819d4..0000000 --- a/crossterm_utils/src/output/sys/winapi.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! This module contains the logic to write to the terminal. - -use winapi::ctypes::c_void; -use winapi::shared::ntdef::NULL; -use winapi::um::consoleapi::WriteConsoleW; -use winapi::um::wincon::{WriteConsoleOutputA, CHAR_INFO, COORD, PSMALL_RECT}; -use winapi::um::winnt::HANDLE; - -use crossterm_winapi::{is_true, ScreenBuffer}; - -use std::io::{self, Result}; -use std::str; - diff --git a/crossterm_utils/src/output/test.rs b/crossterm_utils/src/output/test.rs deleted file mode 100644 index 5e1641d..0000000 --- a/crossterm_utils/src/output/test.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::{AnsiOutput, IStdout, WinApiOutput}; - -#[cfg(windows)] -mod winapi_tests { - use super::*; - - /* ======================== WinApi =========================== */ - #[test] - fn write_winapi() { - let output = WinApiOutput::new(); - - let bytes = "test".as_bytes(); - let result = output.write(bytes); - is_valid_write(result, bytes.len()); - } - - #[test] - fn write_str_winapi() { - let output = WinApiOutput::new(); - - let bytes = "test".as_bytes(); - let result = output.write_str("test"); - is_valid_write(result, bytes.len()); - } -} - -/* ======================== ANSI =========================== */ -#[test] -fn write_ansi() { - let output = AnsiOutput::new(); - - let bytes = "test".as_bytes(); - let result = output.write(bytes); - is_valid_write(result, bytes.len()); -} - -#[test] -fn write_str_ansi() { - let output = AnsiOutput::new(); - - let bytes = "test".as_bytes(); - let result = output.write_str("test"); - is_valid_write(result, bytes.len()); -} - -fn is_valid_write(result: ::std::io::Result, str_length: usize) { - match result { - Err(_) => assert!(false), - Ok(length) => { - if str_length == length { - assert!(true) - } else { - assert!(false) - } - } - }; -} - -fn try_enable_ansi() -> bool { - #[cfg(windows)] - { - if cfg!(target_os = "windows") { - use crate::sys::winapi::ansi::set_virtual_terminal_processing; - - // if it is not listed we should try with WinApi to check if we do support ANSI-codes. - match set_virtual_terminal_processing(true) { - Ok(_) => return true, - Err(e) => return false, - } - } - } - - return true; -} diff --git a/crossterm_utils/src/output/winapi_output.rs b/crossterm_utils/src/output/winapi_output.rs deleted file mode 100644 index 6ef0c95..0000000 --- a/crossterm_utils/src/output/winapi_output.rs +++ /dev/null @@ -1,33 +0,0 @@ -use super::IStdout; -use crossterm_winapi::{Console, Handle}; - -use std::io; - -/// This struct is a wrapper for WinApi output. -pub struct WinApiOutput; - -impl WinApiOutput { - pub fn new() -> WinApiOutput { - WinApiOutput - } -} - -impl IStdout for WinApiOutput { - fn write_str(&self, string: &str) -> io::Result { - self.write(string.as_bytes()) - } - - fn write(&self, buf: &[u8]) -> io::Result { - let handle = Handle::current_out_handle()?; - let console = Console::from(handle); - console.write_char_buffer(buf) - } - - fn flush(&self) -> io::Result<()> { - Ok(()) - } -} - -unsafe impl Send for WinApiOutput {} - -unsafe impl Sync for WinApiOutput {} diff --git a/crossterm_utils/src/sys/unix.rs b/crossterm_utils/src/sys/unix.rs index 81e0048..12e86c5 100644 --- a/crossterm_utils/src/sys/unix.rs +++ b/crossterm_utils/src/sys/unix.rs @@ -1,65 +1,67 @@ //! This module contains all `unix` specific terminal related logic. -use libc::{self, TCSADRAIN}; +pub use libc::{self, c_int, termios as Termios}; -use crate::termios::{tcsetattr, Termios}; -use std::fs; -use std::io; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::{io, mem}; static mut ORIGINAL_TERMINAL_MODE: Option = None; pub static mut RAW_MODE_ENABLED_BY_SYSTEM: bool = false; pub static mut RAW_MODE_ENABLED_BY_USER: bool = false; +fn unwrap(t: i32) -> io::Result<()> { + if t == -1 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } +} + /// Transform the given mode into an raw mode (non-canonical) mode. -pub fn make_raw(termios: &mut Termios) { +pub fn raw_terminal_attr(termios: &mut Termios) { extern "C" { pub fn cfmakeraw(termptr: *mut Termios); } unsafe { cfmakeraw(termios) } } -pub fn into_raw_mode() -> io::Result { - let tty_f; +pub fn get_terminal_attr() -> io::Result { + extern "C" { + pub fn tcgetattr(fd: c_int, termptr: *mut Termios) -> c_int; + } + unsafe { + let mut termios = mem::zeroed(); + unwrap(tcgetattr(0, &mut termios))?; + Ok(termios) + } +} - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() - } - }; +pub fn set_terminal_attr(termios: &Termios) -> io::Result<()> { + extern "C" { + pub fn tcsetattr(fd: c_int, opt: c_int, termptr: *const Termios) -> c_int; + } + unwrap(unsafe { tcsetattr(0, 0, termios) }).and(Ok(())) +} - let mut termios = Termios::from_fd(fd)?; - let original = termios.clone(); +pub fn into_raw_mode() -> io::Result<()> { + let mut ios = get_terminal_attr()?; + let prev_ios = ios; unsafe { if ORIGINAL_TERMINAL_MODE.is_none() { - ORIGINAL_TERMINAL_MODE = Some(original.clone()) + ORIGINAL_TERMINAL_MODE = Some(prev_ios.clone()) } } - make_raw(&mut termios); - tcsetattr(fd, TCSADRAIN, &termios)?; - - Ok(fd) + raw_terminal_attr(&mut ios); + set_terminal_attr(&ios)?; + Ok(()) } pub fn disable_raw_mode() -> io::Result<()> { - let tty_f; - - let fd = unsafe { - if libc::isatty(libc::STDIN_FILENO) == 1 { - libc::STDIN_FILENO - } else { - tty_f = fs::File::open("/dev/tty")?; - tty_f.as_raw_fd() + unsafe { + if ORIGINAL_TERMINAL_MODE.is_some() { + set_terminal_attr(&ORIGINAL_TERMINAL_MODE.unwrap())?; } - }; - - if let Some(original) = unsafe { ORIGINAL_TERMINAL_MODE } { - tcsetattr(fd, TCSADRAIN, &original)?; } Ok(()) } diff --git a/crossterm_winapi/Cargo.toml b/crossterm_winapi/Cargo.toml index e05d64c..1eb5906 100644 --- a/crossterm_winapi/Cargo.toml +++ b/crossterm_winapi/Cargo.toml @@ -11,4 +11,4 @@ exclude = ["target", "Cargo.lock"] readme = "README.md" [dependencies] -winapi = { version = "0.3.5", features = ["winbase","consoleapi","processenv", "handleapi"] } \ No newline at end of file +winapi = { version = "0.3.7", features = ["winbase","consoleapi","processenv", "handleapi"] } \ No newline at end of file diff --git a/crossterm_winapi/README.md b/crossterm_winapi/README.md index bdb691c..4413a9e 100644 --- a/crossterm_winapi/README.md +++ b/crossterm_winapi/README.md @@ -13,8 +13,8 @@ [s7]: https://travis-ci.org/TimonPost/crossterm.svg?branch=master This crate provides some wrappers aground common used WinApi functions. -The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm), -and it is very unstable right because of that some changes could be expected. +The purpose of this library is originally meant for [crossterm](https://github.com/TimonPost/crossterm), but could be used apart from it. +Although, notice that it unstable right because some changes to the API could be expected. # Features This crate provides some abstractions over reading input, console screen buffer, and handle. @@ -31,8 +31,7 @@ _The following WinApi calls_ - ReadConsoleW # Example -Here are some examples do demonstrate how to work whit this crate. -Please see [examples](https://github.com/TimonPost/crossterm_winapi) for more +The [examples](./examples) folder has more complete and verbose examples. ## Screenbuffer information ```rust @@ -61,9 +60,3 @@ fn get_different_handle_types() { let curr_out_put_handle = Handle::new(HandleType::CurrentInputHandle).unwrap(); } ``` - - -### Inspiration -I wanted to expose some of the api crossterm uses for WinApi. -1. I thought it would be helpful for other people to, to have a small rust seemable abstraction over the WinApi bindings. -2. I have some future plans for crossterm wherefore I needed to seperate the WinAPi logic out of the currenbt librarie. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ebd9f9b..d27934b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,29 @@ +## Changes crossterm 0.9.0 +This release is all about moving to a stabilized API for 1.0. + +- Major refactor and cleanup. +- Improved performance; + - No locking when writing to stdout. + - UNIX doesn't have any dynamic dispatch anymore. + - Windows has improved the way to check if ANSI modes are enabled. + - Removed lot's of complex API calls: `from_screen`, `from_output` + - Removed `Arc` from all internal Api's. +- Removed termios dependency for UNIX systems. +- Upgraded deps. +- Removed about 1000 lines of code + - `TerminalOutput` + - `Screen` + - unsafe code + - Some duplicated code introduced by a previous refactor. +- Raw modes UNIX systems improved +- Added `NoItalic` attribute + +## Changes crossterm to 0.8.2 +- Bug fix for sync reader UNIX. + +## Changes crossterm to 0.8.1 +- Added public re-exports for input. + # Changes crossterm 0.8.0 - Upgraded to `crossterm_input 0.2.0`; Input key, mouse events support. - Upgraded crossterm_winapi 0.2 diff --git a/docs/UPGRADE.md b/docs/UPGRADE.md index d6f2880..a9fc853 100644 --- a/docs/UPGRADE.md +++ b/docs/UPGRADE.md @@ -1,3 +1,86 @@ +## Upgrade crossterm to 0.9.0 +This release is all about moving to a stabilized API for 1.0. It has a lot of changes to the API however it has become much better. + +### Removed functions +First you don't have to pass any screens or output's to the crossterm API. This makes the API much more easier to use. + +_**old**_ + +_"Use this function when you want your terminal to operate with a specific output. +This could be useful when you have a screen which is in 'alternate mode', and you want your actions from the TerminalCursor, created by this function, to operate on the 'alternate screen'."_ + + Because crosstrem does not have to keep track of the output and you don't have to pass an `TerminalOutput` anymore those functions are removed. +```rust +use crossterm::{Screen, Terminal, TerminalCursor, TerminalColor, TerminalInput, Crossterm}; +let screen = Screen::new(false); +Terminal::from_output(&screen.stdout); +TerminalCursor::from_output(&screen.stdout); +TerminalColor::from_output(&screen.stdout); +TerminalInput::from_output(&screen.stdout); +Crossterm::from_screen(&screen.stdout); +``` + +_**new**_ +```rust +Terminal::new(); +TerminalCursor::new(); +TerminalColor::new(); +TerminalInput::new(); +Crossterm::new(); +``` + +_"This could be used to paint the styled object onto the given screen. You have to pass a reference to the screen whereon you want to perform the painting"_ + Because crosstrem does not have to keep track of the output anymore those functions are removed. + +_**old**_ +```rust +use crossterm::{Crossterm, Screen, style}; +let screen = Screen::new(false); + +style("Some colored text") + .with(Color::Blue) + .on(Color::Black) + .paint(&screen); + +let crossterm = Crossterm::new(); +crossterm.style("Some colored text") + .with(Color::Blue) + .on(Color::Black) + .paint(&screen); +``` + +_**new**_ +```rust +print!("{}", "Some colored text".blue().on_black()); +``` + +### Removed Types +`Screen` was removed because it hadn't any purpose anymore. + +_**old**_ +use crossterm::Screen; +```rust +// create raw screen +let screen = Screen::new(true); +// create alternate raw screen +let screen = Screen::new(true); +let alternate = screen.enable_raw_modes(true); +``` +_**new**_ +```rust +use crossterm::{AlternateScreen, RawScreen, IntoRawModes}; +let raw_screen = RawScreen::into_raw_mode(); +let raw_screen = stdout().into_raw_mode(); +let alternate = AlternateScreen::to_alternate(true); +``` + +### Renamed Functions +```rust +RawScreen::disable_raw_modes => RawScreen::disable_raw_mode +AlternateScreen::to_alternate_screen => Alternate::to_alternate +AlternateScreen::to_main_screen => Alternate::to_main +``` + ## Upgrade crossterm to 0.8.0 This update will cause problems with `read_async`. `read_async` first returned a type implementing `Read` it returns an `Iterator` of input events now. See the examples for details on how this works. diff --git a/docs/mdbook/src/SUMMARY.md b/docs/mdbook/src/SUMMARY.md index d77f77a..0394ce7 100644 --- a/docs/mdbook/src/SUMMARY.md +++ b/docs/mdbook/src/SUMMARY.md @@ -1,9 +1,8 @@ # Summary -This book will cover styling and user input. It will also give a detailed description about working with alternate and raw screen. +This book will cover styling, user input, terminal modes, and feature flags. - [Feature Flags](feature_flags.md) - [Styling Output](styling.md) - [example](styling_example.md) -- [Async Input](input.md) +- [Reading Input Events](input.md) - [Alternate, Raw Screen](screen.md) - - [example](screen_example.md) diff --git a/docs/mdbook/src/feature_flags.md b/docs/mdbook/src/feature_flags.md index a05be82..e7d2bed 100644 --- a/docs/mdbook/src/feature_flags.md +++ b/docs/mdbook/src/feature_flags.md @@ -1,9 +1,9 @@ -From `crossterm 0.6` you are allowed to use feature flags. +From `crossterm 0.6` you are able to use feature flags. With feature flags you can pick the features you want which reduces the size of the library and could prevent you from having unnecessary dependencies. Crossterm provides the following feature flags: -- input ; reading input +- input ; reading input events - terminal ; terminal actions like resizing - style ; styling of the terminal - cursor ; moving the terminal cursor @@ -15,7 +15,17 @@ _Cargo.toml_ ``` [dependencies] -crossterm = { version="0.6", default-features = false, features = ["screen", "terminal", "cursor", "style", "input"] } +crossterm = { version="0.9", default-features = false, features = ["screen", "terminal", "cursor", "style", "input"] } +``` + +By default all flags are enabled, the types and functions available to use depend on the specified flags. + +```rust +"cursor" => cursor, TerminalCursor +"input" => input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput +"screen" => AlternateScreen, IntoRawMode, RawScreen +"style" => color, style, Attribute, Color, Colored, Colorize, ObjectStyle, StyledObject, Styler, TerminalColor, +"terminal" => terminal, ClearType, Terminal ``` You can also use all the crossterm modules individually by directly referencing the crate. diff --git a/docs/mdbook/src/input.md b/docs/mdbook/src/input.md index 0694ebc..bc6303e 100644 --- a/docs/mdbook/src/input.md +++ b/docs/mdbook/src/input.md @@ -2,21 +2,28 @@ Crossterm provides a way to work with the terminal input. We will not cover the Please check out these [examples](https://github.com/TimonPost/crossterm/blob/master/examples/input/keyboard/input.rs) for reading a line or a character from the user. ## Differences Synchronous and Asynchronous -Crossterm provides two ways to read user input, synchronous and asynchronous. +Crossterm provides two ways to read user input, synchronous and asynchronous. ### Synchronous reading Read the input synchronously from the user, the reads performed will be blocking calls. -Using synchronous over asynchronous reading has the benefit that it is using fewer resources than the asynchronous because background thread and queues are left away." +Using synchronous over asynchronous reading has the benefit that it is using fewer resources than the asynchronous because background thread and queues are left away. + +You can get an synchronous event reader by calling: `TerminalInput::read_sync`. ### Asynchronous reading Read the input asynchronously, input events are gathered on the background and will be queued for you to read. -Using asynchronous reading has the benefit that input events are queued until you read them. You can poll for occurred events, and the reads won't block your program. +Using asynchronous reading has the benefit that input events are queued until you read them. You can poll for occurred events, and the reads won't block your program. + +You can get an synchronous event reader by calling: `TerminalInput::read_async`, `TerminalInput::read_async_until`. ### Technical details -On UNIX systems crossterm reads from the TTY, on Windows, it uses `ReadConsoleInputW`. For asynchronous reading, a background thread will be fired up to read input events. -Occurred events will be queued on an MPSC-channel for the user to iterate over. +On UNIX systems crossterm reads from the TTY, on Windows, it uses `ReadConsoleInputW`. +For asynchronous reading, a background thread will be fired up to read input events, +occurred events will be queued on an MPSC-channel, and the user can iterate over those events. + +The terminal has to be in raw mode, raw mode prevents the input of the user to be displayed on the terminal screen, see [screen](./screen.md) for more info. # Example In the following example, we will create a small program that will listen for mouse and keyboard input. @@ -26,7 +33,7 @@ So let's start by setting up the basics. ``` use std::{thread, time::Duration}; -use crossterm::{TerminalInput, Screen, InputEvent, KeyEvent}; +use crossterm::{input, InputEvent, KeyEvent}; fn main() { println!("Press 'ESC' to quit."); @@ -38,19 +45,17 @@ fn main() { Next, we need to put the terminal into raw mode. We do this because we don't want the user input to be printed to the terminal screen. ```rust -// enable raw modes by passing in 'true' -let screen = Screen::new(true); +// enable raw mode +let screen = RawScreen::into_raw_mode(); -// create a input from our screen. -let input = TerminalInput::from_output(&screen.stdout); +// create a input from our screen +let input = input(); /* next code here */ ``` -Take note that we need to use our `Screen` to create a `TerminalInput` instance, check [this](screen.md#important-notice) out for more information about why that is. - -Now that we have constructed a `TerminalInput` instance we can go ahead an start the reading. -Do this by calling `input.read_async()`, which returns an [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html). +Now that we constructed a `TerminalInput` instance we can go ahead an start the reading. +Do this by calling `input.read_async()`, which returns an [AsyncReader](https://docs.rs/crossterm/0.8.0/crossterm/struct.AsyncReader.html). This is an iterator over the input events that you could as any other iterator. ```rust @@ -64,22 +69,23 @@ loop { } ``` -The [AsyncReader](https://docs.rs/crossterm/0.7.0/crossterm/struct.AsyncReader.html) iterator will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read. +The [AsyncReader](https://docs.rs/crossterm/0.8.0/crossterm/struct.AsyncReader.html) iterator will return `None` when nothing is there to read, `Some(InputEvent)` if there are events to read. I use a thread delay to prevent spamming the iterator. Next up we can start pattern matching to see if there are input events we'd like to catch. In our case, we want to catch the `Escape Key`. ```rust - match key_event { - InputEvent::Keyboard(k) => match k { + match key_event { + InputEvent::Keyboard(event) => match event { KeyEvent::Esc => { println!("Program closing ..."); break } - _ => println!("Key {:?} was pressed!", k) + _ => println!("Key {:?} was pressed!", event) } - _ => { /* Mouse Event */ } + InputEvent::Mouse(event) => { /* Mouse Event */ } + _ => { } } ``` @@ -87,35 +93,39 @@ As you see, we check if the `KeyEvent::Esc` was pressed, if that's true we stop _final code_ ```rust +use std::{thread, time::Duration}; +use crossterm::{input, InputEvent, KeyEvent}; + fn main() { println!("Press 'ESC' to quit."); - // enable raw modes by passing in 'true' - let screen = Screen::new(true); + // enable raw mode + let screen = RawScreen::into_raw_mode(); // create a input from our screen. - let input = TerminalInput::from_output(&screen.stdout); + let input = input(); // create async reader let mut async_stdin = input.read_async(); - + loop { // try to get the next input event. if let Some(key_event) = async_stdin.next() { match key_event { - InputEvent::Keyboard(k) => match k { + InputEvent::Keyboard(event) => match event { KeyEvent::Esc => { println!("Program closing ..."); - break; + break } - _ => println!("Key {:?} was pressed!", k) + _ => println!("Key {:?} was pressed!", event) } + InputEvent::Mouse(event) => { /* Mouse Event */ } _ => { } } } thread::sleep(Duration::from_millis(50)); } -} // <=== background reader will be disposed when dropped. +} // <=== background reader will be disposed when dropped.s ``` --------------------------------------------------------------------------------------------------------------------------------------------- More robust and complete examples on all input aspects like mouse, keys could be found [here](https://github.com/TimonPost/crossterm/tree/master/examples/). \ No newline at end of file diff --git a/docs/mdbook/src/screen.md b/docs/mdbook/src/screen.md index 34bda85..6c0573f 100644 --- a/docs/mdbook/src/screen.md +++ b/docs/mdbook/src/screen.md @@ -1,12 +1,9 @@ -# Basic Usage of Screen -As you may have seen crossterm has a type called `Screen`. This type should be used when working with the alternate or raw screen. - -Before we continue, I'll explain what those concepts are. - ## Screen Buffer -A screen buffer is a two-dimensional array of characters and color data to be output in a console window. An terminal can have multiple of those screen buffers, and the active screen buffer is the one that is displayed on the screen. +A screen buffer is a two-dimensional array of characters and color data to be output in a console window. +An terminal can have multiple of those screen buffers, and the active screen buffer is the one that is displayed on the screen. -Crossterm allows you to switch between those buffers; the screen you are working in is called the 'main screen'. We call the other screen the 'alternate screen'. +Crossterm allows you to switch between those buffers; the screen you are working in is called the 'main screen'. We call the other screen the 'alternate screen'. +One note to take is that crossterm does not support the creation and switching between several buffers. ### Alternate Screen Normally you are working on the main screen but an alternate screen is somewhat different from a normal screen. @@ -14,7 +11,8 @@ For example, it has the exact dimensions of the terminal window, without any scr Vim uses the entirety of the screen to edit the file, then exits to bash leaving the original buffer unchanged. -Crossterm provides the ability to switch to the alternate screen, make some changes, and then go back to the main screen. The main screen will still have its original data since we made all the edits on the alternate screen. +Crossterm provides the ability to switch to the alternate screen, make some changes, and then go back to the main screen. +The main screen will still have its original data since we made all the edits on the alternate screen. ## Raw screen To understand the concept of a 'raw screen' let's look at the following points: @@ -41,51 +39,5 @@ _example of what I mean_ To start at the beginning of the next line, use `\n\r`. -# Crossterm's implementation - -When we want to print some text to the alternate screen we can't just write on it using `print!(), println!(), or write!()`. - -This is because those functions are writing to the standard output and not to our alternate screen we are currently on. -The same goes for coloring, cursor movement, input, and terminal actions. - -Crossterm provides a solution for that by introducing the `Screen` type. -You can use the 'alternate' or 'raw' screen functionalities by either using the [crossterm](https://crates.io/crates/crossterm) or [crossterm_screen](https://crates.io/crates/crossterm_screen) crate. - -Please checkout this [example](screen_example.md) for more information on how to use it. - -_Cargo.toml_ -```rust -crossterm = { version = "0.6.0", features = ["screen","terminal","cursor", "style", "input"] } -``` - -```rust -use crossterm::{cursor, TerminalCursor}; -use crossterm::{color, TerminalColor}; -use crossterm::{input, TerminalInput}; -use crossterm::{terminal, Terminal}; - -let screen = Screen::default(); - -if let Ok(alternate) = screen.enable_alternate_modes(false) { - - // by calling 'from_screen' you force crossterm to use the screen of the alternate screen to perform actions on. - let crossterm = Crossterm::from_screen(&alternate.screen); - let cursor = crossterm.cursor(); - let terminal =crossterm.terminal(); - let input = crossterm.input(); - let color = crossterm.color(); - - // you can also create instances directly without `Crossterm` - let screen = alternate.screen; - - let terminal = Terminal::from_output(&screen.stdout); - let cursor = TerminalCursor::from_output(&screen.stdout); - let color = TerminalColor::from_output(&screen.stdout); - let input = TerminalInput::from_output(&screen.stdout); -} -``` - -The above modules will now all be executed at the 'alternate screen'. - --------------------------------------------------------------------------------------------------------------------------------------------- -Next up: [Examples](screen_example.md) \ No newline at end of file +More examples could be found [over here](https://github.com/TimonPost/crossterm/blob/master/examples/). \ No newline at end of file diff --git a/docs/mdbook/src/screen_example.md b/docs/mdbook/src/screen_example.md deleted file mode 100644 index bf424e2..0000000 --- a/docs/mdbook/src/screen_example.md +++ /dev/null @@ -1,155 +0,0 @@ -Let's build some basic programs who are using the alternate and raw screen. - -# Raw Screen -_setup the basics_ -```rust -extern crate crossterm; - -use crossterm::Screen; - -fn main() { - /* next code here */ -} -``` - -We use the `Screen` type to enable raw mode for the terminal. -When creating a screen you have the option to pass it a boolean, this boolean specifies whether the screen will be in raw or normal mode. - -Let's play around a bit to see what raw screen does. - -```rust -// Create raw screen by passing in true. -let screen = Screen::new(true); - -println!("Some text"); -println!("Some text"); -``` -When you run this program you will directly see that the output is a little strange like: -``` -Some text - Some text -``` - -Another fun thing to do is reading the input. This should not work since the input is not recorded by the terminal when in raw mode. - -_Take note this will cause your terminal to freeze. -Since `read_line` expects some line input but it will never be recorded because of raw mode. -You should just restart the terminal when you are stuck_. -```rust -// Create raw screen by passing in true. -let screen = Screen::new(true); -let text = std::io::stdin().read_line(&mut string); -println!("{:?}", text); -``` - -Note that we spoke about the reason why this is [previously](screen.md#raw-screen). -However, if you want to read input in raw mode you should checkout [Async Input](input.md). - -# Alternate Screen -_setup the basics_ -```rust -extern crate crossterm; - -use crossterm::Screen; -use std::{thread, time}; -use std::io::Write; - -fn main() { - /* next code here */ -} -``` - -As we spoke of [previously](screen.md#alternate-screen), with `Screen` we can manage alternate screen and raw screen. -Let's make some simple program who is switching from the main to alternate screen whereafter it will wait for 2 seconds. -When those seconds are over we will go back to the main screen. - -First off, create an instance of `Screen`. We can call `enable_alternate_modes()` on this screen, this will switch the screen buffer to the alternate buffer. -When we call this function we will get an `AlternateScreen` instance back which represents the alternate screen. -We should use this instance when we want to do any writing, styling, cursor, and terminal related things on the alternate screen ([Important Notice](screen.md##important-notice)). - -```rust -let screen = Screen::default(); - -if let Ok(mut alternate) = screen.enable_alternate_modes(false) { - /* next code here */ -} -``` - -Next, we use the instance we got back and write our message to it whereafter we wait for 2 seconds. -We wait 2 seconds to give us some time to see the alternate screen. -If the `AlternateScreen` goes out of scope it will automatically switch back to the main screen. - -```rust -write!(alternate.screen, "{}", "Welcome to the wait screen.\n Please wait a 2 seconds until we arrive back at the main screen."); -thread::sleep(time::Duration::from_secs(2)); -``` - - -By now you should be able to execute the program, you will see that directly you are being redirected to another screen with no scrollback region. -You will see this screen open whereafter it closes after 2 seconds. -When the program finishes you will notice you are on the main screen again with it's contents unmodified. - -# Perform Actions on Alternate Screen. -Now we have covered the basics of alternate screen let's make a program who styles some text on the 'raw' alternate screen. - -_setup the basics_ -```rust -extern crate crossterm; - -use crossterm::Screen; -use std::{thread, time}; -use std::io::Write; - -fn main() { - // switch to alternate screen and make it raw by passing in true. - if let Ok(mut alternate) = screen.enable_alternate_modes(true) { - /* next code here */ - } -} -``` -Some basics steps the following code will do: -1. Create [Crossterm]() type to manage the cursor and styling -2. Set the position of the cursor to x: 0 and y: 0 -3. Write the styled text, you can use the two described ways -4. Set the position of the cursor to x: 0 and y: 1 -5. Write other text and flush it to the screen -6. Sleep two seconds to see the results - -```rust -let alternate_screen = &mut alternate.screen; - -// by calling 'from_screen' the cursor will be moved at the passed screen. -let crossterm = Crossterm::from_screen(alternate_screen); -let cursor = crossterm.cursor(); - -cursor.goto(0,0); - -// 1) print the 'styled object' by converting it into a type that is displayable for alternate screen. -println!("{}", crossterm.style("Welcome to the wait screen.") - .with(Color::Red) - .on(Color::Blue) - .into_displayable(alternate_screen) -); - -// 2) use the `paint` method to print it to the alternate screen. -crossterm.style("Welcome to the wait screen.") - .with(Color::Red) - .on(Color::Blue) - .paint(alternate_screen); - -cursor.goto(0,1); - -write!(alternate_screen, "{}", "Please wait a few seconds until we arrive back at the main screen."); -alternate_screen.flush(); - -thread::sleep(time::Duration::from_secs(2)); - -``` - -As you might have noticed, you need to to manually move the cursor, flush the buffer. This is because the terminal is in raw modes. -Also, by printing with `paint` or calling `into_displayable` you pass in a reference to the alternate screen. -Tip: Take a look at [how](screen.md#Crossterm's implementation) you should use other modules crossterm provides on the alternate screen. - ---------------------------------------------------------------------------------------------------------------------------------------------- -More examples could be found at this [link](https://github.com/TimonPost/crossterm/tree/master/examples/terminal). - diff --git a/docs/mdbook/src/styling.md b/docs/mdbook/src/styling.md index f0a3e3e..3bedfe4 100644 --- a/docs/mdbook/src/styling.md +++ b/docs/mdbook/src/styling.md @@ -21,6 +21,7 @@ There are 16 base colors which available for almost all terminals even windows 7 In addition to 16 colors, most UNIX terminals and Windows 10 consoles are also supporting more colors. Those colors could be: [True color (24-bit)](https://en.wikipedia.org/wiki/Color_depth#True_color_(24-bit)) coloring scheme, which allows you to use [RGB](https://nl.wikipedia.org/wiki/RGB-kleursysteem), and [256 (Xterm, 8-bit)](https://jonasjacek.github.io/colors/) colors. +Checkout the examples on how to use this feature. ## Attributes Only UNIX and Windows 10 terminals are supporting attributes on top of the text. Crossterm allows you to add attributes to the text. @@ -46,7 +47,7 @@ Crossterm implements almost all attributes shown in this [Wikipedia-list](https: | Encircled | Unknown | This will turn on the encircled attribute. | | OverLined | Unknown | This will draw a line at the top of the font. | -(There are a few attributes who disable one of the above, I did not write those down in the above scheme). +(There are a few attributes who disable one of the above attributes, I did not write those down to keep the list short). Now we have covered the basics of styling lets go some [examples](styling_example.md). diff --git a/examples/alternate_screen.rs b/examples/alternate_screen.rs index 6920e91..059938a 100644 --- a/examples/alternate_screen.rs +++ b/examples/alternate_screen.rs @@ -1,12 +1,10 @@ extern crate crossterm; -use crossterm::{ClearType, Color, Crossterm, Screen, style}; - -use std::io::{stdout, Write}; +use crossterm::{style, AlternateScreen, ClearType, Color, Crossterm}; use std::{thread, time}; -fn print_wait_screen(screen: &Screen) { - let crossterm = Crossterm::from_screen(screen); +fn print_wait_screen() { + let crossterm = Crossterm::new(); let terminal = crossterm.terminal(); let cursor = crossterm.cursor(); @@ -22,10 +20,12 @@ fn print_wait_screen(screen: &Screen) { for i in 1..5 { // print the current counter at the line of `Seconds to Go: {counter}` cursor.goto(10, 2); - style(format!("{} of the 5 items processed", i)) - .with(Color::Red) - .on(Color::Blue) - .paint(&screen.stdout); + println!( + "{}", + style(format!("{} of the 5 items processed", i)) + .with(Color::Red) + .on(Color::Blue) + ); // 1 second delay thread::sleep(time::Duration::from_secs(1)); @@ -34,13 +34,11 @@ fn print_wait_screen(screen: &Screen) { /// print wait screen on alternate screen, then switch back. pub fn print_wait_screen_on_alternate_window() { - let screen = Screen::default(); - - if let Ok(alternate) = screen.enable_alternate_modes(false) { - print_wait_screen(&alternate.screen); + if let Ok(alternate) = AlternateScreen::to_alternate(false) { + print_wait_screen(); } } fn main() { print_wait_screen_on_alternate_window(); -} \ No newline at end of file +} diff --git a/examples/crossterm.rs b/examples/crossterm.rs index 4f2a2d6..0e91f2d 100644 --- a/examples/crossterm.rs +++ b/examples/crossterm.rs @@ -1,9 +1,9 @@ extern crate crossterm; -use crossterm::{Crossterm, Screen, Color}; +use crossterm::{Color, Crossterm}; /// use the `Crossterm` to get an instance to the cursor module | demonstration. -pub fn crossterm() { +pub fn main() { // Create the crossterm type to access different modules. let crossterm = Crossterm::new(); @@ -12,7 +12,10 @@ pub fn crossterm() { let color = crossterm.color(); let terminal = crossterm.terminal(); let terminal = crossterm.input(); - let style = crossterm.style("Black font on green background").with(Color::Black).on(Color::Green); + let style = crossterm + .style("Black font on green background") + .with(Color::Black) + .on(Color::Green); // TODO: perform some actions with the instances above. } diff --git a/examples/cursor.rs b/examples/cursor.rs index 4d758a0..404ce3d 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -4,7 +4,7 @@ extern crate crossterm_cursor; -use crossterm_cursor::{cursor, TerminalCursor}; +use crossterm_cursor::cursor; /// Set the cursor to position X: 10, Y: 5 in the terminal. pub fn goto() { @@ -87,6 +87,5 @@ pub fn blink_cursor() { } fn main() { - goto(); - pos(); + save_and_reset_position(); } diff --git a/examples/input.rs b/examples/input.rs index a1c6a22..549c2ae 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -1,6 +1,6 @@ extern crate crossterm; -use self::crossterm::{KeyEvent, TerminalInput, input, Screen}; +use self::crossterm::input; pub fn read_char() { let input = input(); @@ -20,13 +20,7 @@ pub fn read_line() { } } -pub fn pause_terminal() { - println!("Press 'x' to quit..."); - let screen = Screen::new(true); - let terminal_input = TerminalInput::from_output(&screen.stdout); - terminal_input.wait_until(KeyEvent::OnKeyPress(b'x')); -} - -fn main(){ - pause_terminal(); +fn main() { + read_line(); + read_char(); } diff --git a/examples/key_events.rs b/examples/key_events.rs index 4c575eb..71e2017 100644 --- a/examples/key_events.rs +++ b/examples/key_events.rs @@ -1,117 +1,72 @@ extern crate crossterm; -use crossterm::{ - InputEvent, KeyEvent, MouseButton, MouseEvent, TerminalInput, Screen -}; - +use crossterm::{input, InputEvent, KeyEvent, MouseButton, MouseEvent, RawScreen}; use std::{thread, time::Duration}; -fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool { +fn process_input_event(key_event: InputEvent) -> bool { match key_event { - InputEvent::Keyboard(k) => match k { - KeyEvent::Char(c) => match c { - 'q' => { - screen.stdout.write_str("The 'q' key is hit and the program is not listening to input anymore.\n\n").unwrap(); - return true; + InputEvent::Keyboard(k) => { + match k { + KeyEvent::Char(c) => match c { + 'q' => { + println!("The 'q' key is hit and the program is not listening to input anymore.\n\n"); + return true; + } + _ => { + println!("{}", format!("'{}' pressed\n\n", c)); + } + }, + KeyEvent::Alt(c) => { + println!("{}", format!("ALT +'{}' pressed\n\n", c)); + } + KeyEvent::Ctrl(c) => { + println!("{}", format!("CTRL +'{}' Pressed\n\n", c)); + } + KeyEvent::Esc => { + println!("{}", format!("ESC pressed\n\n")); + } + KeyEvent::F(number) => { + println!("{}", format!("F{} key pressed\n\n", number)); + } + KeyEvent::PageUp => { + println!("{}", format!("Page Up\n\n")); + } + KeyEvent::PageDown => { + println!("{}", format!("Page Down\n\n")); + } + KeyEvent::Delete => { + println!("{}", format!("Delete\n\n")); } _ => { - screen - .stdout - .write_string(format!("'{}' pressed\n\n", c)) - .unwrap(); + println!("{}", format!("OTHER: {:?}\n\n", k)); + () } - }, - KeyEvent::Alt(c) => { - screen - .stdout - .write_string(format!("ALT +'{}' pressed\n\n", c)) - .unwrap(); } - KeyEvent::Ctrl(c) => { - screen - .stdout - .write_string(format!("CTRL +'{}' Pressed\n\n", c)) - .unwrap(); - } - KeyEvent::Esc => { - screen - .stdout - .write_string(format!("ESC pressed\n\n")) - .unwrap(); - } - KeyEvent::F(number) => { - screen - .stdout - .write_string(format!("F{} key pressed\n\n", number)) - .unwrap(); - } - KeyEvent::PageUp => { - screen.stdout.write_string(format!("Page Up\n\n")).unwrap(); - } - KeyEvent::PageDown => { - screen - .stdout - .write_string(format!("Page Down\n\n")) - .unwrap(); - } - KeyEvent::Delete => { - screen.stdout.write_string(format!("Delete\n\n")).unwrap(); - } - _ => { - screen - .stdout - .write_string(format!("OTHER: {:?}\n\n", k)) - .unwrap(); - () - } - }, + } InputEvent::Mouse(m) => match m { MouseEvent::Press(b, x, y) => match b { MouseButton::Left => { - screen - .stdout - .write_string(format!("left mouse press @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("left mouse press @ {}, {}\n\n", x, y)); } MouseButton::Right => { - screen - .stdout - .write_string(format!("right mouse press @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("right mouse press @ {}, {}\n\n", x, y)); } MouseButton::Middle => { - screen - .stdout - .write_string(format!("mid mouse press @ {}, {}\n\n", x, y)) - .unwrap(); - } - MouseButton::WheelUp => { - screen - .stdout - .write_string(format!("wheel up @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("mid mouse press @ {}, {}\n\n", x, y)); } + MouseButton::WheelUp => println!("{}", format!("wheel up @ {}, {}\n\n", x, y)), MouseButton::WheelDown => { - screen - .stdout - .write_string(format!("wheel down @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("wheel down @ {}, {}\n\n", x, y)); } }, MouseEvent::Release(x, y) => { - screen - .stdout - .write_string(format!("mouse released @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("mouse released @ {}, {}\n\n", x, y)); } MouseEvent::Hold(x, y) => { - screen - .stdout - .write_string(format!("dragging @ {}, {}\n\n", x, y)) - .unwrap(); + println!("{}", format!("dragging @ {}, {}\n\n", x, y)); } _ => { - screen.stdout.write_str("Unknown mouse event").unwrap(); + println!("{}", "Unknown mouse event"); } }, _ => println!("Unknown!"), @@ -122,56 +77,56 @@ fn process_input_event(key_event: InputEvent, screen: &Screen) -> bool { pub fn read_asynchronously() { // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. - let screen = Screen::new(true); + if let Ok(_raw) = RawScreen::into_raw_mode() { + let input = input(); - let input = TerminalInput::from_output(&screen.stdout); + // enable mouse events to be captured. + input.enable_mouse_mode().unwrap(); - // enable mouse events to be captured. - input.enable_mouse_mode().unwrap(); + let mut stdin = input.read_async(); - let stdin = input.read_async(); - - loop { - if let Some(key_event) = stdin.next() { - if process_input_event(key_event, &screen) { - break; + loop { + if let Some(key_event) = stdin.next() { + if process_input_event(key_event) { + break; + } } + thread::sleep(Duration::from_millis(50)); } - thread::sleep(Duration::from_millis(50)); - } - // disable mouse events to be captured. - input.disable_mouse_mode().unwrap(); + // disable mouse events to be captured. + input.disable_mouse_mode().unwrap(); + } // <=== raw modes will be disabled here } // <=== background reader will be disposed when dropped. pub fn read_synchronously() { // make sure to enable raw mode, this will make sure key events won't be handled by the terminal it's self and allows crossterm to read the input and pass it back to you. - let screen = Screen::new(true); + if let Ok(_raw) = RawScreen::into_raw_mode() { + let input = input(); - let input = TerminalInput::from_output(&screen.stdout); + // enable mouse events to be captured. + input.enable_mouse_mode().unwrap(); - // enable mouse events to be captured. - input.enable_mouse_mode().unwrap(); + let mut sync_stdin = input.read_sync(); - let mut sync_stdin = input.read_sync(); + loop { + let event = sync_stdin.next(); - loop { - let event = sync_stdin.next(); - - if let Some(key_event) = event { - if process_input_event(key_event, &screen) { - break; + if let Some(key_event) = event { + if process_input_event(key_event) { + break; + } } } - } - // disable mouse events to be captured. - input.disable_mouse_mode().unwrap(); + // disable mouse events to be captured. + input.disable_mouse_mode().unwrap(); + } // <=== raw modes will be disabled here } fn main() { // un-comment below and run with // `cargo run --example key_events`: - // read_synchronously(); - read_asynchronously(); -} \ No newline at end of file + read_synchronously(); + // read_asynchronously(); +} diff --git a/examples/program_examples/command_bar.rs b/examples/program_examples/command_bar.rs index f57f8a5..e3b9bb7 100644 --- a/examples/program_examples/command_bar.rs +++ b/examples/program_examples/command_bar.rs @@ -1,24 +1,19 @@ extern crate crossterm; use crossterm::{ - input, ClearType, Crossterm, InputEvent, KeyEvent, Screen, Terminal, TerminalCursor, + input, cursor, terminal, ClearType, Crossterm, InputEvent, KeyEvent, AlternateScreen, RawScreen, Terminal, TerminalCursor }; -use std::io::Read; use std::sync::{Arc, Mutex}; use std::{thread, time}; fn main() { - use crossterm::color; - - let screen = Screen::new(true); - let crossterm = Crossterm::from_screen(&screen); - let cursor = crossterm.cursor(); - cursor.hide(); + let screen = RawScreen::into_raw_mode(); + cursor().hide(); let input_buf = Arc::new(Mutex::new(String::new())); - let threads = log(input_buf.clone(), &screen); + let threads = log(input_buf.clone()); let mut count = 0; @@ -47,19 +42,18 @@ fn main() { thread.join(); } - cursor.show(); + cursor().show(); } -fn log(input_buf: Arc>, screen: &Screen) -> Vec> { +fn log(input_buf: Arc>) -> Vec> { let mut threads = Vec::with_capacity(10); - let (_, term_height) = Terminal::from_output(&screen.stdout).terminal_size(); + let (_, term_height) = terminal().terminal_size(); for i in 0..1 { let input_buffer = input_buf.clone(); - let _clone_stdout = screen.stdout.clone(); - let crossterm = Crossterm::from(screen.stdout.clone()); + let crossterm = Crossterm::new(); let join = thread::spawn(move || { let cursor = crossterm.cursor(); @@ -93,5 +87,5 @@ pub fn swap_write( cursor.goto(0, term_height); terminal.clear(ClearType::CurrentLine); terminal.write(format!("{}\r\n", msg)); - terminal.write(format!(">{}", input_buf)); + terminal.write(format!("> {}", input_buf)); } diff --git a/examples/program_examples/first_depth_search/src/algorithm.rs b/examples/program_examples/first_depth_search/src/algorithm.rs index b11ad97..758a716 100644 --- a/examples/program_examples/first_depth_search/src/algorithm.rs +++ b/examples/program_examples/first_depth_search/src/algorithm.rs @@ -2,37 +2,29 @@ use super::map::Map; use super::variables::{Direction, Position}; - -use crossterm::{Color, Crossterm, Screen}; +use crossterm::{Color, Colored, Colorize, Crossterm}; +use std::io::{stdout, Write}; use super::rand; use super::rand::distributions::{IndependentSample, Range}; - -use std::io::Write; use std::{thread, time}; -pub struct FirstDepthSearch<'screen> { +pub struct FirstDepthSearch { direction: Direction, map: Map, stack: Vec, root_pos: Position, is_terminated: bool, - screen: &'screen Screen, } -impl<'screen> FirstDepthSearch<'screen> { - pub fn new( - map: Map, - start_pos: Position, - crossterm: &'screen Screen, - ) -> FirstDepthSearch<'screen> { +impl FirstDepthSearch { + pub fn new(map: Map, start_pos: Position) -> FirstDepthSearch { FirstDepthSearch { direction: Direction::Up, map, stack: Vec::new(), root_pos: start_pos, is_terminated: false, - screen: crossterm, } } @@ -42,10 +34,18 @@ impl<'screen> FirstDepthSearch<'screen> { // push first position on the stack self.stack.push(self.root_pos); - let crossterm = Crossterm::from_screen(&self.screen); - let mut cursor = crossterm.cursor(); + let crossterm = Crossterm::new(); + let cursor = crossterm.cursor(); cursor.hide(); + write!( + ::std::io::stdout(), + "{}{}", + Colored::Fg(Color::Green), + Colored::Bg(Color::Black) + ); + ::std::io::stdout().flush(); + // loop until there are now items left in the stack. loop { if self.stack.len() == 0 { @@ -60,16 +60,14 @@ impl<'screen> FirstDepthSearch<'screen> { self.update_position(); - let cell = crossterm.style(" ").on(Color::Blue); - let pos = self.root_pos.clone(); let x = pos.x as u16; let y = pos.y as u16; cursor.goto(x, y); - cell.paint(&self.screen.stdout); - self.screen.stdout.flush(); + write!(stdout(), "{}", " ".on_yellow()); + stdout().flush(); thread::sleep(time::Duration::from_millis(1)); } diff --git a/examples/program_examples/first_depth_search/src/main.rs b/examples/program_examples/first_depth_search/src/main.rs index e3f91e7..ece7b94 100644 --- a/examples/program_examples/first_depth_search/src/main.rs +++ b/examples/program_examples/first_depth_search/src/main.rs @@ -6,12 +6,12 @@ mod map; mod messages; mod variables; -use self::crossterm::{ClearType, Color, Crossterm, InputEvent, KeyEvent, Screen}; - -use self::messages::WELCOME_MESSAGE; +use self::crossterm::{ + color, cursor, input, terminal, AlternateScreen, ClearType, Color, Colored, Crossterm, + InputEvent, KeyEvent, RawScreen, +}; use self::variables::{Position, Size}; -use std::io::Read; use std::iter::Iterator; use std::{thread, time}; @@ -21,40 +21,38 @@ fn main() { /// run the program pub fn run() { + // let screen = RawScreen::into_raw_mode().expect("failed to enable raw modes"); print_welcome_screen(); - - // This is represents the current screen. - let mut screen = Screen::new(true); - start_algorithm(&screen); + start_algorithm(); + exit(); } -fn start_algorithm(screen: &Screen) { +fn start_algorithm() { // we first want to switch to alternate screen. On the alternate screen we are going to run or firstdepthsearch algorithm - if let Ok(ref alternate_screen) = screen.enable_alternate_modes(true) { + if let Ok(ref alternate_screen) = AlternateScreen::to_alternate(true) { // setup the map size and the position to start searching for a path. let map_size = Size::new(50, 40); let start_pos = Position::new(10, 10); // create and render the map. Or map border is going to have an █ look and inside the map is just a space. let mut map = map::Map::new(map_size, '█', ' '); - map.render_map(&alternate_screen.screen); + map.render_map(); // create the algorithm and start it on the alternate screen. Make sure to pass the refrence to the AlternateScreen screen. - let mut algorithm = - algorithm::FirstDepthSearch::new(map, start_pos, &alternate_screen.screen); + let mut algorithm = algorithm::FirstDepthSearch::new(map, start_pos); algorithm.start(); } } fn print_welcome_screen() { - let mut screen = Screen::new(true); - - let crossterm = Crossterm::from_screen(&screen); + // we have to keep this screen arround to prevent te + let _screen = RawScreen::into_raw_mode(); + let crossterm = Crossterm::new(); // create the handle for the cursor and terminal. - let terminal = crossterm.terminal(); - let cursor = crossterm.cursor(); - let input = crossterm.input(); + let terminal = terminal(); + let cursor = cursor(); + let input = input(); // set size of terminal so the map we are going to draw is fitting the screen. terminal.set_size(110, 60); @@ -63,10 +61,12 @@ fn print_welcome_screen() { terminal.clear(ClearType::All); cursor.goto(0, 0); - crossterm - .style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r"))) - .with(Color::Cyan) - .paint(&screen.stdout); + print!( + "{}", + crossterm + .style(format!("{}", messages::WELCOME_MESSAGE.join("\n\r"))) + .with(Color::Cyan) + ); cursor.hide(); cursor.goto(0, 10); @@ -80,20 +80,29 @@ fn print_welcome_screen() { // print some progress example. for i in (1..5).rev() { if let Some(InputEvent::Keyboard(KeyEvent::Char('q'))) = stdin.next() { - drop(screen); + exit(); terminal.exit(); break; } else { // print the current counter at the line of `Seconds to Go: {counter}` cursor.goto(48, 10); - crossterm - .style(format!("{}", i)) - .with(Color::Red) - .on(Color::Blue) - .paint(&screen.stdout); + print!( + "{}{}{}", + Colored::Fg(Color::Red), + Colored::Bg(Color::Blue), + i + ); } + color().reset(); + // 1 second delay thread::sleep(time::Duration::from_secs(1)); } } + +fn exit() { + RawScreen::disable_raw_mode().expect("failed to disable raw modes."); + cursor().show(); + color().reset(); +} diff --git a/examples/program_examples/first_depth_search/src/map.rs b/examples/program_examples/first_depth_search/src/map.rs index 37a02c4..36ed410 100644 --- a/examples/program_examples/first_depth_search/src/map.rs +++ b/examples/program_examples/first_depth_search/src/map.rs @@ -1,5 +1,6 @@ use super::variables::{Cell, Position, Size}; -use crossterm::{cursor, Color, Crossterm, Screen}; +use crossterm::{cursor, Color, Crossterm}; +use std::io::{stdout, Write}; pub struct Map { pub map: Vec>, @@ -42,8 +43,8 @@ impl Map { } // render the map on the screen. - pub fn render_map(&mut self, screen: &Screen) { - let crossterm = Crossterm::from_screen(screen); + pub fn render_map(&mut self) { + let crossterm = Crossterm::new(); for row in self.map.iter_mut() { for column in row.iter_mut() { @@ -51,9 +52,13 @@ impl Map { if (column.position.y == 0 || column.position.y == self.size.height - 1) || (column.position.x == 0 || column.position.x == self.size.width - 1) { - let cell_style = crossterm.style(column.look).on(column.color); cursor().goto(column.position.x as u16, column.position.y as u16); - cell_style.paint(&screen.stdout); + write!( + stdout(), + "{}", + crossterm.style(column.look).on(column.color) + ); + stdout().flush(); } } } diff --git a/examples/program_examples/first_depth_search/src/messages.rs b/examples/program_examples/first_depth_search/src/messages.rs index 3cd0b56..1ce2c85 100644 --- a/examples/program_examples/first_depth_search/src/messages.rs +++ b/examples/program_examples/first_depth_search/src/messages.rs @@ -8,13 +8,3 @@ pub const WELCOME_MESSAGE: [&str; 6] = [ " \\__/\\ / \\___ >____/__|_ \\____/|__|_| /\\___ > ", " \\/ \\/ \\/ \\/ \\/ ", ]; - -pub const END_MESSAGE: [&str; 5] = [ - "-----------------------", - " ", - " No routes (DONE) ", - " ", - "-----------------------", -]; - -pub fn print_stack_count(position: Position) {} diff --git a/examples/program_examples/first_depth_search/src/variables.rs b/examples/program_examples/first_depth_search/src/variables.rs index a4a979d..0a172c9 100644 --- a/examples/program_examples/first_depth_search/src/variables.rs +++ b/examples/program_examples/first_depth_search/src/variables.rs @@ -1,6 +1,6 @@ extern crate crossterm; -use self::crossterm::{Color, ObjectStyle, StyledObject}; +use self::crossterm::Color; #[derive(Copy, Clone, Debug)] pub enum Direction { diff --git a/examples/program_examples/logging.rs b/examples/program_examples/logging.rs deleted file mode 100644 index a0ea0b2..0000000 --- a/examples/program_examples/logging.rs +++ /dev/null @@ -1,152 +0,0 @@ -extern crate crossterm; - -use crossterm::Screen; -use std::collections::VecDeque; -use std::sync::{ - mpsc::{self, Receiver, Sender}, - Arc, Mutex, -}; -use std::thread::{self, JoinHandle}; - -/// This is an que that could be shared between threads safely. -#[derive(Clone)] -struct WorkQueue { - inner: Arc>>, -} - -impl WorkQueue { - fn new() -> Self { - Self { - inner: Arc::new(Mutex::new(VecDeque::new())), - } - } - - // get an item from the que if exists - fn get_work(&self) -> Option { - let maybe_queue = self.inner.lock(); - - if let Ok(mut queue) = maybe_queue { - queue.pop_front() - } else { - panic!("WorkQueue::get_work() tried to lock a poisoned mutex"); - } - } - - // add an item to the que - fn add_work(&self, work: T) -> usize { - if let Ok(mut queue) = self.inner.lock() { - queue.push_back(work); - queue.len() - } else { - panic!("WorkQueue::add_work() tried to lock a poisoned mutex"); - } - } -} - -#[derive(Clone)] -struct SyncFlagTx { - inner: Arc>, -} - -impl SyncFlagTx { - pub fn set(&mut self, state: bool) -> Result<(), ()> { - if let Ok(mut v) = self.inner.lock() { - *v = state; - Ok(()) - } else { - Err(()) - } - } -} - -#[derive(Clone)] -struct SyncFlagRx { - inner: Arc>, -} - -impl SyncFlagRx { - pub fn get(&self) -> Result { - if let Ok(v) = self.inner.lock() { - Ok(*v) - } else { - Err(()) - } - } -} - -fn new_sync_flag(initial_state: bool) -> (SyncFlagTx, SyncFlagRx) { - let state = Arc::new(Mutex::new(initial_state)); - let tx = SyncFlagTx { - inner: state.clone(), - }; - let rx = SyncFlagRx { - inner: state.clone(), - }; - - return (tx, rx); -} - -fn main() { - let (_results_tx, _results_rx): (Sender, Receiver) = mpsc::channel(); - let (more_jobs_tx, more_jobs_rx) = new_sync_flag(true); - - // queue with all log entry's. - let queue = WorkQueue::new(); - - // queue x logs with different threads. - let _thread_handles = log_with_different_threads(more_jobs_tx.clone(), queue.clone()); - - // a thread that will log all logs in the queue. - handle_incoming_logs(more_jobs_rx.clone(), queue.clone()); -} - -fn handle_incoming_logs(more_jobs_rx: SyncFlagRx, queue: WorkQueue) { - thread::spawn(move || { - let screen: Screen = Screen::default(); - - // Loop while there's expected to be work, looking for work. - while more_jobs_rx.get().unwrap() { - // If work is available, do that work. - if let Some(work) = queue.get_work() { - let mut log = work; - log.push('\n'); - - // write the log - screen.stdout.write_string(log); - } - std::thread::yield_now(); - } - }) - .join(); -} - -// start different threads that log contiguously. -fn log_with_different_threads( - more_jobs_tx: SyncFlagTx, - queue: WorkQueue, -) -> Vec> { - // one vector that will have the thread handles in it. - let mut threads = Vec::new(); - - for thread_num in 1..5 { - let mut more_jobs = more_jobs_tx.clone(); - let thread_queue = queue.clone(); - - // create new thread - let thread = thread::spawn(move || { - // log 400 messages - for log_entry_count in 1..400 { - thread_queue.add_work(format!( - "Log {} from thread {} ", - log_entry_count, thread_num - )); - more_jobs.set(true); - } - }); - - threads.push(thread); - } - - println!("All logging threads started"); - return threads; -} diff --git a/examples/program_examples/snake/src/main.rs b/examples/program_examples/snake/src/main.rs index 52e8730..2837767 100644 --- a/examples/program_examples/snake/src/main.rs +++ b/examples/program_examples/snake/src/main.rs @@ -7,7 +7,7 @@ mod snake; mod variables; use self::crossterm::{ - AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, Screen, + AsyncReader, ClearType, Color, Colorize, Crossterm, InputEvent, KeyEvent, RawScreen, }; use map::Map; @@ -15,7 +15,6 @@ use snake::Snake; use variables::{Direction, Position, Size}; use std::collections::HashMap; -use std::io::Write; use std::iter::Iterator; use std::{thread, time}; @@ -23,8 +22,8 @@ fn main() { let map_size = ask_size(); // screen has to be in raw mode in order for the key presses not to be printed to the screen. - let screen = Screen::new(true); - let crossterm = Crossterm::from_screen(&screen); + let raw = RawScreen::into_raw_mode(); + let crossterm = Crossterm::new(); crossterm.cursor().hide(); @@ -34,7 +33,7 @@ fn main() { // render the map let mut map = Map::new(map_size); - map.render_map(&screen, &mut free_positions); + map.render_map(&mut free_positions); let mut snake = Snake::new(); @@ -43,7 +42,7 @@ fn main() { free_positions.remove_entry(format!("{},{}", part.position.x, part.position.y).as_str()); } - map.spawn_food(&free_positions, &screen); + map.spawn_food(&free_positions); let mut stdin = crossterm.input().read_async(); let mut snake_direction = Direction::Right; @@ -54,16 +53,16 @@ fn main() { snake_direction = new_direction; } - snake.move_snake(&snake_direction, &screen, &mut free_positions); + snake.move_snake(&snake_direction, &mut free_positions); if map.is_out_of_bounds(snake.snake_parts[0].position) { break; } - snake.draw_snake(&screen); + snake.draw_snake(); if snake.has_eaten_food(map.foot_pos) { - map.spawn_food(&free_positions, &screen); + map.spawn_food(&free_positions); } thread::sleep(time::Duration::from_millis(400)); diff --git a/examples/program_examples/snake/src/map.rs b/examples/program_examples/snake/src/map.rs index 67ad7a4..ee7e550 100644 --- a/examples/program_examples/snake/src/map.rs +++ b/examples/program_examples/snake/src/map.rs @@ -1,6 +1,6 @@ use super::variables::{Position, Size}; -use crossterm::{style, Color, Colorize, Crossterm, Screen, TerminalCursor}; +use crossterm::{cursor, Colorize, TerminalCursor}; use rand::distributions::{IndependentSample, Range}; @@ -22,16 +22,12 @@ impl Map { } // render the map on the screen. - pub fn render_map(&mut self, screen: &Screen, free_positions: &mut HashMap) { - let crossterm = Crossterm::from_screen(screen); - let cursor = crossterm.cursor(); - let terminal = crossterm.terminal(); - + pub fn render_map(&mut self, free_positions: &mut HashMap) { for y in 0..self.size.height { for x in 0..self.size.height { if (y == 0 || y == self.size.height - 1) || (x == 0 || x == self.size.width - 1) { - cursor.goto(x as u16, y as u16); - "█".magenta().paint(&screen.stdout); + cursor().goto(x as u16, y as u16); + print!("{}", "█".magenta()); } else { free_positions.insert(format!("{},{}", x, y), Position::new(x, y)); } @@ -53,15 +49,15 @@ impl Map { snake_head.x == self.foot_pos.x && snake_head.y == self.foot_pos.y } - pub fn spawn_food(&mut self, free_positions: &HashMap, screen: &Screen) { + pub fn spawn_food(&mut self, free_positions: &HashMap) { let index = Range::new(0, free_positions.len()).ind_sample(&mut rand::thread_rng()); self.foot_pos = free_positions.values().skip(index).next().unwrap().clone(); - self.draw_food(screen); + self.draw_food(); } - fn draw_food(&self, screen: &Screen) { - let cursor = TerminalCursor::from_output(&screen.stdout); + fn draw_food(&self) { + let cursor = TerminalCursor::new(); cursor.goto(self.foot_pos.x as u16, self.foot_pos.y as u16); - "$".green().paint(&screen.stdout); + print!("{}", "$".green()); } } diff --git a/examples/program_examples/snake/src/snake.rs b/examples/program_examples/snake/src/snake.rs index 3854c7f..cbaca67 100644 --- a/examples/program_examples/snake/src/snake.rs +++ b/examples/program_examples/snake/src/snake.rs @@ -1,5 +1,5 @@ -use super::variables::{Direction, Position, Size}; -use crossterm::{Crossterm, Screen}; +use super::variables::{Direction, Position}; +use crossterm::Crossterm; use std::collections::HashMap; @@ -31,18 +31,13 @@ impl Snake { pub fn move_snake( &mut self, direction: &Direction, - screen: &Screen, free_positions: &mut HashMap, ) { - let crossterm = Crossterm::from_screen(screen); - let cursor = crossterm.cursor(); - let terminal = crossterm.terminal(); - let count = self.snake_parts.len(); for (index, ref mut snake_part) in self.snake_parts.iter_mut().enumerate() { if index == count - 1 { - snake_part.position.remove(screen); + snake_part.position.remove(); free_positions.insert( format!("{},{}", snake_part.position.x, snake_part.position.y), snake_part.position, @@ -70,9 +65,9 @@ impl Snake { } } - pub fn draw_snake(&mut self, screen: &Screen) { + pub fn draw_snake(&mut self) { for snake_part in self.snake_parts.iter_mut() { - snake_part.position.draw("■", screen); + snake_part.position.draw("■"); } } diff --git a/examples/program_examples/snake/src/variables.rs b/examples/program_examples/snake/src/variables.rs index c219136..8534ec4 100644 --- a/examples/program_examples/snake/src/variables.rs +++ b/examples/program_examples/snake/src/variables.rs @@ -1,6 +1,8 @@ extern crate crossterm; -use self::crossterm::{style, Color, Crossterm, Screen, TerminalCursor}; +use self::crossterm::{style, Color, Crossterm, TerminalCursor}; +use std::io::stdout; +use std::io::Write; #[derive(Copy, Clone, Debug)] pub enum Direction { @@ -21,16 +23,16 @@ impl Position { Position { x, y } } - pub fn draw(&self, val: &str, screen: &Screen) { - let cursor = TerminalCursor::from_output(&screen.stdout); + pub fn draw(&self, val: &str) { + let cursor = TerminalCursor::new(); cursor.goto(self.x as u16, self.y as u16); - style(val).with(Color::Red).paint(&screen.stdout); - screen.stdout.flush(); + print!("{}", style(val).with(Color::Red)); + stdout().flush(); } - pub fn remove(&self, screen: &Screen) { - let crossterm = Crossterm::from_screen(screen); + pub fn remove(&self) { + let crossterm = Crossterm::new(); crossterm.cursor().goto(self.x as u16, self.y as u16); crossterm.terminal().write(" "); diff --git a/examples/raw_mode.rs b/examples/raw_mode.rs index c51a512..8045d26 100644 --- a/examples/raw_mode.rs +++ b/examples/raw_mode.rs @@ -1,12 +1,12 @@ extern crate crossterm; -use crossterm::{Crossterm, Screen, terminal, ClearType, Color, style}; +use crossterm::{style, terminal, AlternateScreen, ClearType, Color, Crossterm, RawScreen}; use std::io::{stdout, Write}; use std::{thread, time}; -fn print_wait_screen(screen: &mut Screen) { - let crossterm = Crossterm::from_screen(screen); +fn print_wait_screen() { + let crossterm = Crossterm::new(); let terminal = crossterm.terminal(); let cursor = crossterm.cursor(); @@ -14,22 +14,25 @@ fn print_wait_screen(screen: &mut Screen) { cursor.hide(); cursor.goto(0, 0); - screen.write(b"Welcome to the wait screen."); + println!("Welcome to the wait screen."); cursor.goto(0, 1); - screen.write(b"Please wait a few seconds until we arrive back at the main screen."); + println!("Please wait a few seconds until we arrive back at the main screen."); cursor.goto(0, 2); - screen.write(b"Progress:"); + println!("Progress:"); cursor.goto(0, 3); // print some progress example. for i in 1..5 { // print the current counter at the line of `Seconds to Go: {counter}` cursor.goto(10, 2); - style(format!("{} of the 5 items processed", i)) - .with(Color::Red) - .on(Color::Blue) - .paint(&screen.stdout); - screen.stdout.flush(); + print!( + "{}", + style(format!("{} of the 5 items processed", i)) + .with(Color::Red) + .on(Color::Blue) + ); + + stdout().flush(); // 1 second delay thread::sleep(time::Duration::from_secs(1)); @@ -37,18 +40,12 @@ fn print_wait_screen(screen: &mut Screen) { } pub fn print_wait_screen_on_alternate_window() { - let screen = Screen::default(); - // by passing in 'true' the alternate screen will be in raw modes. - if let Ok(ref mut alternate) = screen.enable_alternate_modes(true) { - print_wait_screen(&mut alternate.screen); + if let Ok(alternate) = AlternateScreen::to_alternate(true) { + print_wait_screen(); } // <- drop alternate screen; this will cause the alternate screen to drop. - - drop(screen); // <- drop screen; this will cause raw mode to be turned off. - - println!("Whe are back at the main screen"); } fn main() { print_wait_screen_on_alternate_window(); -} \ No newline at end of file +} diff --git a/examples/style.rs b/examples/style.rs index 21958b8..56fd186 100644 --- a/examples/style.rs +++ b/examples/style.rs @@ -1,12 +1,9 @@ //! //! Examples of coloring the terminal. //! -#[macro_use] -extern crate crosstterm; +extern crate crossterm; -use self::crossterm_style::{ - color, style, Attribute, Color, Colored, Colorize, Styler, TerminalColor, -}; +use self::crossterm::{color, style, Attribute, Color, Colored, Colorize, Styler}; /// print some red font | demonstration. pub fn paint_foreground() { diff --git a/examples/terminal.rs b/examples/terminal.rs index 18d276f..57eb596 100644 --- a/examples/terminal.rs +++ b/examples/terminal.rs @@ -127,5 +127,5 @@ pub fn exit() { } fn main() { - clear_all_lines(); + print_terminal_size(); } diff --git a/src/crossterm.rs b/src/crossterm.rs index 694c755..4d6e3ff 100644 --- a/src/crossterm.rs +++ b/src/crossterm.rs @@ -1,8 +1,4 @@ -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`. /// @@ -11,40 +7,24 @@ use std::sync::Arc; /// ```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(); -/// } +// let color = crossterm.color(); +// let terminal = crossterm.terminal(); +// let terminal = crossterm.input(); +// let style = crossterm +// .style(format!("{} {}", 0, "Black font on green background")) +// .with(Color::Black) +// .on(Color::Green); /// ``` /// /// # 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>, -} +pub struct Crossterm; 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()), - } + Crossterm } /// Get a `TerminalCursor` implementation whereon cursor related actions can be performed. @@ -55,10 +35,7 @@ impl<'crossterm> Crossterm { /// ``` #[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), - } + crossterm_cursor::TerminalCursor::new() } /// Get a `TerminalInput` implementation whereon terminal related actions can be performed. @@ -69,10 +46,7 @@ impl<'crossterm> Crossterm { /// ``` #[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), - } + crossterm_input::TerminalInput::new() } /// Get a `Terminal` implementation whereon terminal related actions can be performed. @@ -83,10 +57,7 @@ impl<'crossterm> Crossterm { /// ``` #[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), - } + crossterm_terminal::Terminal::new() } /// Get a `TerminalColor` implementation whereon color related actions can be performed. @@ -97,10 +68,7 @@ impl<'crossterm> Crossterm { /// ``` #[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), - } + crossterm_style::TerminalColor::new() } /// This could be used to style any type implementing `Display` with colors and attributes. @@ -130,78 +98,4 @@ impl<'crossterm> Crossterm { { 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/lib.rs b/src/lib.rs index 591b61c..20d01b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -#[macro_use] extern crate crossterm_utils; #[cfg(feature = "cursor")] @@ -21,14 +20,13 @@ pub use self::crossterm_input::{ input, AsyncReader, InputEvent, KeyEvent, MouseButton, MouseEvent, SyncReader, TerminalInput, }; #[cfg(feature = "screen")] -pub use self::crossterm_screen::{AlternateScreen, Screen}; +pub use self::crossterm_screen::{AlternateScreen, IntoRawMode, RawScreen}; #[cfg(feature = "style")] pub use self::crossterm_style::{ color, style, Attribute, Color, Colored, Colorize, ObjectStyle, StyledObject, Styler, TerminalColor, }; #[cfg(feature = "terminal")] -pub use self::crossterm_terminal::*; +pub use self::crossterm_terminal::{terminal, ClearType, Terminal}; pub use self::crossterm::Crossterm; -pub use self::crossterm_utils::{error, TerminalOutput};