diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc08a72e..ecf434138 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,596 +1,596 @@ -# Version 0.17.6 -- Add functionality to retrieve color based on passed ansi code. -- Switch from 'futures' to 'futures-util' crate to reduce dependency count -- Mio 0.7 update -- signal-hook update -- Make windows raw_mode act on CONIN$ -- Added From<(u8, u8, u8)> Trait to Color::Rgb Enum -- Implement Color::try_from() -- Implement styler traits for `&'a str` - -# Version 0.17.5 -- Improved support of keymodifier for linux, arrow keys, function keys, home keys etc. -- Add `SetTitle` command to change the terminal title. -- Mio 0.7 update - -# Version 0.17.4 -- Add macros for `Colorize` and `Styler` impls, add an impl for `String` -- Add shift modifier to uppercase char events on unix - -# Version 0.17.3 -- Fix get terminal size mac os, this did not report the correct size. - -# Version 0.17.2 -- Windows unicode support - -# Version 0.17.1 -- Reverted bug in 0.17.0: "Make terminal size function fallback to `STDOUT_FILENO` if `/dev/tty` is missing.". -- Support for querying whether the current instance is a TTY. - -# Version 0.17 -- Impl Display for MoveToColumn, MoveToNextLine, MoveToPreviousLine -- Make unix event reader always use `/dev/tty`. -- Direct write command ansi_codes into formatter instead of double allocation. -- Add NONE flag to KeyModifiers -- Add support for converting chars to StylizedContent -- Make terminal size function fallback to `STDOUT_FILENO` if `/dev/tty` is missing. - -# Version 0.16.0 -- Change attribute vector in `ContentStyle` to bitmask. -- Add `SetAttributes` command. -- Add `Attributes` type, which is a bitfield of enabled attributes. -- Remove `exit()`, was useless. - -# Version 0.15.0 -- Fix CTRL + J key combination. This used to return an ENTER event. -- Add a generic implementation `Command` for `&T: Command`. This allows commands to be queued by reference, as well as by value. -- Remove unnecessary `Clone` trait bounds from `StyledContent`. -- Add `StyledContent::style_mut`. -- Handle error correctly for `execute!` and `queue!`. -- Fix minor syntax bug in `execute!` and `queue!`. -- Change `ContentStyle::apply` to take self by value instead of reference, to prevent an unnecessary extra clone. -- Added basic trait implementations (`Debug`, `Clone`, `Copy`, etc) to all of the command structs -- `ResetColor` uses `&'static str` instead of `String` - -# Version 0.14.2 -- Fix TIOCGWINSZ for FreeBSD - -# Version 0.14.1 -- Made windows cursor position relative to the window instead absolute to the screen buffer windows. -- Fix windows bug with `queue` macro were it consumed a type and required an type to be `Copy`. - -# Version 0.14 - -- Replace the `input` module with brand new `event` module - - Terminal Resize Events - - Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and - - futures Stream (feature 'event-stream') - - Poll/read API - - It's **highly recommended** to read the - [Upgrade from 0.13 to 0.14](https://github.com/crossterm-rs/crossterm/wiki/Upgrade-from-0.13-to-0.14) - documentation -- Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths) - documentation -- Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands -- Merge `screen` module into `terminal` - - Remove `screen::AlternateScreen` - - Remove `screen::Rawscreen` - * Move and rename `Rawscreen::into_raw_mode` and `Rawscreen::disable_raw_mode` to `terminal::enable_raw_mode` and `terminal::disable_raw_mode` - - Move `screen::EnterAlternateScreen` and `screen::LeaveAlternateScreen` to `terminal::EnterAlternateScreen` and `terminal::LeaveAlternateScreen` - - Replace `utils::Output` command with `style::Print` command -- Fix enable/disable mouse capture commands on Windows -- Allow trailing comma `queue!` & `execute!` macros - -# Version 0.13.3 - -- Remove thread from AsyncReader on Windows. -- Improve HANDLE management windows. - -# Version 0.13.2 - -- New `input::stop_reading_thread()` function - - Temporary workaround for the UNIX platform to stop the background - reading thread and close the file descriptor - - This function will be removed in the next version - -# Version 0.13.1 - -- Async Reader fix, join background thread and avoid looping forever on windows. - -# Version 0.13.0 - -**Major API-change, removed old-api** - -- Remove `Crossterm` type -- Remove `TerminalCursor`, `TerminalColor`, `Terminal` -- Remove `cursor()`, `color()` , `terminal()` -- Remove re-exports at root, accessible via `module::types` (`cursor::MoveTo`) -- `input` module - - Derive 'Copy' for 'KeyEvent' - - Add the `EnableMouseCapture` and `EnableMouseCapture` commands -- `cursor` module - - Introduce static function `crossterm::cursor::position` in place of `TerminalCursor::pos` - - Rename `Goto` to `MoveTo` - - Rename `Up` to `MoveLeft` - - Rename `Right` to `MoveRight` - - Rename `Down` to `MoveDown` - - Rename `BlinkOn` to `EnableBlinking` - - Rename `BlinkOff` to `DisableBlinking` - - Rename `ResetPos` to `ResetPosition` - - Rename `SavePos` to `SavePosition` -- `terminal` - - Introduce static function `crossterm::terminal::size` in place of `Terminal::size` - - Introduce static function `crossterm::terminal::exit` in place of `Terminal::exit` -- `style module` - - Rename `ObjectStyle` to `ContentStyle`. Now full names are used for methods - - Rename `StyledObject` to `StyledContent` and made members private - - Rename `PrintStyledFont` to `PrintStyledContent` - - Rename `attr` method to `attribute`. - - Rename `Attribute::NoInverse` to `NoReverse` - - Update documentation - - Made `Colored` private, user should use commands instead - - Rename `SetFg` -> `SetForegroundColor` - - Rename `SetBg` -> `SetBackgroundColor` - - Rename `SetAttr` -> `SetAttribute` - - Rename `ContentStyle::fg_color` -> `ContentStyle::foreground_color` - - Rename `ContentStyle::bg_color` -> `ContentStyle::background_color` - - Rename `ContentStyle::attrs` -> `ContentStyle::attributes` -- Improve documentation -- Unix terminal size calculation with TPUT - -# Version 0.12.1 - -- Move all the `crossterm_` crates code was moved to the `crossterm` crate - - `crossterm_cursor` is in the `cursor` module, etc. - - All these modules are public -- No public API breaking changes - -# Version 0.12.0 - -- Following crates are deprecated and no longer maintained - - `crossterm_cursor` - - `crossterm_input` - - `crossterm_screen` - - `crossterm_style` - - `crossterm_terminal` - - `crossterm_utils` - -## `crossterm_cursor` 0.4.0 - -- Fix examples link ([PR #6](https://github.com/crossterm-rs/crossterm-cursor/pull/6)) -- Sync documentation style ([PR #7](https://github.com/crossterm-rs/crossterm-cursor/pull/7)) -- Remove all references to the crossterm book ([PR #8](https://github.com/crossterm-rs/crossterm-cursor/pull/8)) -- Replace `RAW_MODE_ENABLED` with `is_raw_mode_enabled` ([PR #9](https://github.com/crossterm-rs/crossterm-cursor/pull/9)) -- Use `SyncReader` & `InputEvent::CursorPosition` for `pos_raw()` ([PR #10](https://github.com/crossterm-rs/crossterm-cursor/pull/10)) - -## `crossterm_input` 0.5.0 - -- Sync documentation style ([PR #4](https://github.com/crossterm-rs/crossterm-input/pull/4)) -- Sync `SyncReader::next()` Windows and UNIX behavior ([PR #5](https://github.com/crossterm-rs/crossterm-input/pull/5)) -- Remove all references to the crossterm book ([PR #6](https://github.com/crossterm-rs/crossterm-input/pull/6)) -- Mouse coordinates synchronized with the cursor ([PR #7](https://github.com/crossterm-rs/crossterm-input/pull/7)) - - Upper/left reported as `(0, 0)` -- Fix bug that read sync didn't block (Windows) ([PR #8](https://github.com/crossterm-rs/crossterm-input/pull/8)) -- Refactor UNIX readers ([PR #9](https://github.com/crossterm-rs/crossterm-input/pull/9)) - - AsyncReader produces mouse events - - One reading thread per application, not per `AsyncReader` - - Cursor position no longer consumed by another `AsyncReader` - - Implement sync reader for read_char (requires raw mode) - - Fix `SIGTTIN` when executed under the LLDB - - Add mio for reading from FD and more efficient polling (UNIX only) -- Sync UNIX and Windows vertical mouse position ([PR #11](https://github.com/crossterm-rs/crossterm-input/pull/11)) - - Top is always reported as `0` - -## `crossterm_screen` 0.3.2 - -- `to_alternate` switch back to main screen if it fails to switch into raw mode ([PR #4](https://github.com/crossterm-rs/crossterm-screen/pull/4)) -- Improve the documentation ([PR #5](https://github.com/crossterm-rs/crossterm-screen/pull/5)) - - Public API - - Include the book content in the documentation -- Remove all references to the crossterm book ([PR #6](https://github.com/crossterm-rs/crossterm-screen/pull/6)) -- New commands introduced ([PR #7](https://github.com/crossterm-rs/crossterm-screen/pull/7)) - - `EnterAlternateScreen` - - `LeaveAlternateScreen` -- Sync Windows and UNIX raw mode behavior ([PR #8](https://github.com/crossterm-rs/crossterm-screen/pull/8)) - -## `crossterm_style` 0.5.2 - -- Refactor ([PR #2](https://github.com/crossterm-rs/crossterm-style/pull/2)) - - Added unit tests - - Improved documentation and added book page to `lib.rs` - - Fixed bug with `SetBg` command, WinApi logic - - Fixed bug with `StyledObject`, used stdout for resetting terminal color - - Introduced `ResetColor` command -- Sync documentation style ([PR #3](https://github.com/crossterm-rs/crossterm-style/pull/3)) -- Remove all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-style/pull/4)) -- Windows 7 grey/white foreground/intensity swapped ([PR #5](https://github.com/crossterm-rs/crossterm-style/pull/5)) - -## `crossterm_terminal` 0.3.2 - -- Removed `crossterm_cursor::sys` dependency ([PR #2](https://github.com/crossterm-rs/crossterm-terminal/pull/2)) -- Internal refactoring & documentation ([PR #3](https://github.com/crossterm-rs/crossterm-terminal/pull/3)) -- Removed all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-terminal/pull/4)) - -## `crossterm_utils` 0.4.0 - -- Add deprecation note ([PR #3](https://github.com/crossterm-rs/crossterm-utils/pull/3)) -- Remove all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-utils/pull/4)) -- Remove unsafe static mut ([PR #5](https://github.com/crossterm-rs/crossterm-utils/pull/5)) - - `sys::unix::RAW_MODE_ENABLED` replaced with `sys::unix::is_raw_mode_enabled()` (breaking) - - New `lazy_static` dependency - -## `crossterm_winapi` 0.3.0 - -- Make read sync block for windows systems ([PR #2](https://github.com/crossterm-rs/crossterm-winapi/pull/2)) - -# Version 0.11.1 - -- Maintenance release -- All sub-crates were moved to their own repositories in the `crossterm-rs` organization - -# Version 0.11.0 - -As a preparation for crossterm 0.1.0 we have moved crossterm to an organisation called 'crossterm-rs'. - -### Code Quality - -- Code Cleanup: [warning-cleanup], [crossterm_style-cleanup], [crossterm_screen-cleanup], [crossterm_terminal-cleanup], [crossterm_utils-cleanup], [2018-cleanup], [api-cleanup-1], [api-cleanup-2], [api-cleanup-3] -- Examples: [example-cleanup_1], [example-cleanup_2], [example-fix], [commandbar-fix], [snake-game-improved] -- Fixed all broken tests and added tests - -### Important Changes - -- Return written bytes: [return-written-bytes] -- Added derives: `Debug` for `ObjectStyle` [debug-derive], Serialize/Deserialize for key events [serde] -- Improved error handling: - - Return `crossterm::Result` from all api's: [return_crossterm_result] - * `TerminalCursor::pos()` returns `Result<(u16, u16)>` - * `Terminal::size()` returns `Result<(u16, u16)>` - * `TerminalCursor::move_*` returns `crossterm::Result` - * `ExecutableCommand::queue` returns `crossterm::Result` - * `QueueableCommand::queue` returns `crossterm::Result` - * `get_available_color_count` returns no result - * `RawScreen::into_raw_mode` returns `crossterm::Result` instead of `io::Result` - * `RawScreen::disable_raw_mode` returns `crossterm::Result` instead of `io::Result` - * `AlternateScreen::to_alternate` returns `crossterm::Result` instead of `io::Result` - * `TerminalInput::read_line` returns `crossterm::Result` instead of `io::Result` - * `TerminalInput::read_char` returns `crossterm::Result` instead of `io::Result` - * Maybe I forgot something, a lot of functions have changed - - Removed all unwraps/expects from library -- Add KeyEvent::Enter and KeyEvent::Tab: [added-key-event-enter], [added-key-event-tab] -- Sync set/get terminal size behaviour: [fixed-get-set-terminal-size] -- Method renames: - * `AsyncReader::stop_reading()` to `stop()` - * `RawScreen::disable_raw_mode_on_drop` to `keep_raw_mode_on_drop` - * `TerminalCursor::reset_position()` to `restore_position()` - * `Command::get_anis_code()` to `ansi_code()` - * `available_color_count` to `available_color_count()` - * `Terminal::terminal_size` to `Terminal::size` - * `Console::get_handle` to `Console::handle` -- All `i16` values for indexing: set size, set cursor pos, scrolling synced to `u16` values -- Command API takes mutable self instead of self - -[serde]: https://github.com/crossterm-rs/crossterm/pull/190 - -[debug-derive]: https://github.com/crossterm-rs/crossterm/pull/192 -[example-fix]: https://github.com/crossterm-rs/crossterm/pull/193 -[commandbar-fix]: https://github.com/crossterm-rs/crossterm/pull/204 - -[warning-cleanup]: https://github.com/crossterm-rs/crossterm/pull/198 -[example-cleanup_1]: https://github.com/crossterm-rs/crossterm/pull/196 -[example-cleanup_2]: https://github.com/crossterm-rs/crossterm/pull/225 -[snake-game-improved]: https://github.com/crossterm-rs/crossterm/pull/231 -[crossterm_style-cleanup]: https://github.com/crossterm-rs/crossterm/pull/208 -[crossterm_screen-cleanup]: https://github.com/crossterm-rs/crossterm/pull/209 -[crossterm_terminal-cleanup]: https://github.com/crossterm-rs/crossterm/pull/210 -[crossterm_utils-cleanup]: https://github.com/crossterm-rs/crossterm/pull/211 -[2018-cleanup]: https://github.com/crossterm-rs/crossterm/pull/222 -[wild-card-cleanup]: https://github.com/crossterm-rs/crossterm/pull/224 - -[api-cleanup-1]: https://github.com/crossterm-rs/crossterm/pull/235 -[api-cleanup-2]: https://github.com/crossterm-rs/crossterm/pull/238 -[api-cleanup-3]: https://github.com/crossterm-rs/crossterm/pull/240 - -[return-written-bytes]: https://github.com/crossterm-rs/crossterm/pull/212 - -[return_crossterm_result]: https://github.com/crossterm-rs/crossterm/pull/232 -[added-key-event-tab]: https://github.com/crossterm-rs/crossterm/pull/239 -[added-key-event-enter]: https://github.com/crossterm-rs/crossterm/pull/236 -[fixed-get-set-terminal-size]: https://github.com/crossterm-rs/crossterm/pull/242 - -# Version 0.10.1 - -# Version 0.10.0 ~ yanked -- Implement command API, to have better performance and more control over how and when commands are executed. [PR](https://github.com/crossterm-rs/crossterm/commit/1a60924abd462ab169b6706aab68f4cca31d7bc2), [issue](https://github.com/crossterm-rs/crossterm/issues/171) -- Fix showing, hiding cursor windows implementation -- Remove some of the parsing logic from windows keys to ansi codes to key events [PR](https://github.com/crossterm-rs/crossterm/commit/762c3a9b8e3d1fba87acde237f8ed09e74cd9ecd) -- Made terminal size 1-based [PR](https://github.com/crossterm-rs/crossterm/commit/d689d7e8ed46a335474b8262bd76f21feaaf0c50) -- Add some derives - -# Version 0.9.6 - -- Copy for KeyEvent -- CTRL + Left, Down, Up, Right key support -- SHIFT + Left, Down, Up, Right key support -- Fixed UNIX cursor position bug [issue](https://github.com/crossterm-rs/crossterm/issues/140), [PR](https://github.com/crossterm-rs/crossterm/pull/152) - -# Version 0.9.5 - -- Prefetch buffer size for more efficient windows input reads. [PR](https://github.com/crossterm-rs/crossterm/pull/144) - -# Version 0.9.4 - -- Reset foreground and background color individually. [PR](https://github.com/crossterm-rs/crossterm/pull/138) -- Backtap input support. [PR](https://github.com/crossterm-rs/crossterm/pull/129) -- Corrected white/grey and added dark grey. -- Fixed getting cursor position with raw screen enabled. [PR](https://github.com/crossterm-rs/crossterm/pull/134) -- Removed one redundant stdout lock - -# Version 0.9.3 - -- Removed println from `SyncReader` - -## Version 0.9.2 - -- Terminal size linux was not 0-based -- Windows mouse input event position was 0-based ans should be 1-based -- Result, ErrorKind are made re-exported -- Fixed some special key combination detections for UNIX systems -- Made FreeBSD compile - -## Version 0.9.1 - -- Fixed libc compile error - -## Version 0.9.0 (yanked) - -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 - -## Version 0.8.2 - -- Bug fix for sync reader UNIX. - -## Version 0.8.1 - -- Added public re-exports for input. - -# Version 0.8.0 - -- Introduced KeyEvents -- Introduced MouseEvents -- Upgraded crossterm_winapi 0.2 - -# Version 0.7.0 - -- Introduced more `Attributes` -- Introduced easier ways to style text [issue 87](https://github.com/crossterm-rs/crossterm/issues/87). -- Removed `ColorType` since it was unnecessary. - -# Version 0.6.0 - -- Introduced feature flags; input, cursor, style, terminal, screen. -- All modules are moved to their own crate. -- Introduced crossterm workspace -- Less dependencies. -- Improved namespaces. - -[PR 84](https://github.com/crossterm-rs/crossterm/pull/84) - -# Version 0.5.5 - -- Error module is made public [PR 78](https://github.com/crossterm-rs/crossterm/pull/78). - -# Version 0.5.4 - -- WinApi rewrite and correctly error handled [PR 67](https://github.com/crossterm-rs/crossterm/pull/67) -- Windows attribute support [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) -- Readline bug fix windows systems [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) -- Error handling improvement. -- General refactoring, all warnings removed. -- Documentation improvement. - -# Version 0.5.1 - -- Documentation refactor. -- Fixed broken API documentation [PR 53](https://github.com/crossterm-rs/crossterm/pull/53). - -# Version 0.5.0 - -- Added ability to pause the terminal [issue](https://github.com/crossterm-rs/crossterm/issues/39) -- RGB support for Windows 10 systems -- ANSI color value (255) color support -- More convenient API, no need to care about `Screen` unless working with when working with alternate or raw screen [PR](https://github.com/crossterm-rs/crossterm/pull/44) -- Implemented Display for styled object - -# Version 0.4.3 - -- Fixed bug [issue 41](https://github.com/crossterm-rs/crossterm/issues/41) - -# Version 0.4.2 - -- Added functionality to make a styled object writable to screen [issue 33](https://github.com/crossterm-rs/crossterm/issues/33) -- Added unit tests. -- Bugfix with getting terminal size unix. -- Bugfix with returning written bytes [pull request 31](https://github.com/crossterm-rs/crossterm/pull/31) -- removed methods calls: `as_any()` and `as_any_mut()` from `TerminalOutput` - -# Version 0.4.1 - -- Fixed resizing of ansi terminal with and height where in the wrong order. - -# Version 0.4.0 - -- Input support (read_line, read_char, read_async, read_until_async) -- Styling module improved -- Everything is multithreaded (`Send`, `Sync`) -- Performance enhancements: removed mutexes, removed state manager, removed context type removed unnecessarily RC types. -- Bug fix resetting console color. -- Bug fix whit undoing raw modes. -- More correct error handling. -- Overall commend improvement. -- Overall refactor of code. - -# Version 0.3.0 - -This version has some braking changes check [upgrade manual](UPGRADE%20Manual.md) for more information about what is changed. -I think you should not switch to version `0.3.0` if you aren't going to use the AlternateScreen feature. -Because you will have some work to get to the new version of crossterm depending on your situation. - -Some Features crossterm 0.3.0 -- Alternate Screen for windows and unix systems. -- Raw screen for unix and windows systems [Issue 5](https://github.com/crossterm-rs/crossterm/issues/5).. -- Hiding an showing the cursor. -- Control over blinking of the terminal cursor (only some terminals are supporting this). -- The terminal state will be set to its original state when process ends [issue7](https://github.com/crossterm-rs/crossterm/issues/7). -- exit the current process. - -## Alternate screen - -This create supports alternate screen for both windows and unix systems. You can use - -*Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them. -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. - -I Highly recommend you to check the `examples/program_examples/first_depth_search` for seeing this in action. - -## Raw screen - -This crate now supports raw screen for both windows and unix systems. -What exactly is raw state: -- No line buffering. - Normally the terminals uses line buffering. This means that the input will be send to the terminal line by line. - With raw mode the input will be send one byte at a time. -- Input - All input has to be written manually by the programmer. -- Characters - The characters are not processed by the terminal driver, but are sent straight through. - Special character have no meaning, like backspace will not be interpret as backspace but instead will be directly send to the terminal. -With these modes you can easier design the terminal screen. - -## Some functionalities added - -- Hiding and showing terminal cursor -- Enable or disabling blinking of the cursor for unix systems (this is not widely supported) -- Restoring the terminal to original modes. -- Added a [wrapper](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) for managing all the functionalities of crossterm `Crossterm`. -- Exit the current running process - -## Examples -Added [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) for each version of the crossterm version. -Also added a folder with some [real life examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/program_examples). - -## Context - -What is the `Context` all about? This `Context` has several reasons why it is introduced into `crossterm version 0.3.0`. -These points are related to the features like `Alternatescreen` and managing the terminal state. - -- At first `Terminal state`: - - Because this is a terminal manipulating library there will be made changes to terminal when running an process. - If you stop the process you want the terminal back in its original state. - Therefore, I need to track the changes made to the terminal. - -- At second `Handle to the console` - - In Rust we can use `stdout()` to get an handle to the current default console handle. - For example when in unix systems you want to print something to the main screen you can use the following code: - - write!(std::io::stdout(), "{}", "some text"). - - But things change when we are in alternate screen modes. - We can not simply use `stdout()` to get a handle to the alternate screen, since this call returns the current default console handle (handle to mainscreen). - - Because of that we need to store an handle to the current screen. - This handle could be used to put into alternate screen modes and back into main screen modes. - Through this stored handle Crossterm can execute its command and write on and to the current screen whether it be alternate screen or main screen. - - For unix systems we store the handle gotten from `stdout()` for windows systems that are not supporting ANSI escape codes we store WinApi `HANDLE` struct witch will provide access to the current screen. - -So to recap this `Context` struct is a wrapper for a type that manges terminal state changes. -When this `Context` goes out of scope all changes made will be undone. -Also is this `Context` is a wrapper for access to the current console screen. - -Because Crossterm needs access to the above to types quite often I have chosen to add those two in one struct called `Context` so that this type could be shared throughout library. -Check this link for more info: [cleanup of rust code](https://stackoverflow.com/questions/48732387/how-can-i-run-clean-up-code-in-a-rust-library). -More info over writing to alternate screen buffer on windows and unix see this [link](https://github.com/crossterm-rs/crossterm/issues/17) - -__Now the user has to pass an context type to the modules of Crossterm like this:__ - - let context = Context::new(); - - let cursor = cursor(&context); - let terminal = terminal(&context); - let color = color(&context); - -Because this looks a little odd I will provide a type withs will manage the `Context` for you. You can call the different modules like the following: - - let crossterm = Crossterm::new(); - let color = crossterm.color(); - let cursor = crossterm.cursor(); - let terminal = crossterm.terminal(); - - -### Alternate screen -When you want to switch to alternate screen there are a couple of things to keep in mind for it to work correctly. -First off some code of how to switch to Alternate screen, for more info check the [alternate screen example](https://github.com/crossterm-rs/crossterm/blob/master/examples/alternate_screen.rs). - -_Create alternate screen from `Context`_ - - // create context. - let context = crossterm::Context::new(); - // create instance of Alternatescreen by the given context, this wil also switch to it. - let mut screen = crossterm::AlternateScreen::from(context.clone()); - // write to the alternate screen. - write!(screen, "test"); - -_Create alternate screen from `Crossterm`:_ - - // create context. - let crossterm = ::crossterm::Crossterm::new(); - // create instance of Alternatescreen by the given refrence to crossterm, this wil also switch to it. - let mut screen = crossterm::AlternateScreen::from(&crossterm); - // write to the alternate screen. - write!(screen, "test"); - -like demonstrated above, to get the functionalities of `cursor(), color(), terminal()` also working on alternate screen. -You need to pass it the same `Context` as you have passed to the previous three called functions, -If you don't use the same `Context` in `cursor(), color(), terminal()` than these modules will be using the main screen and you will not see anything at the alternate screen. If you use the [Crossterm](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) type you can get the `Context` from it by calling the crossterm.get_context() whereafter you can create the AlternateScreen from it. - -# Version 0.2.2 - -- Bug see [issue 15](https://github.com/crossterm-rs/crossterm/issues/15) - -# Version 0.2.1 - -- Default ANSI escape codes for windows machines, if windows does not support ANSI switch back to WinApi. -- method grammar mistake fixed [Issue 3](https://github.com/crossterm-rs/crossterm/issues/3) -- Some Refactorings in method names see [issue 4](https://github.com/crossterm-rs/crossterm/issues/4) -- Removed bin reference from crate [Issue 6](https://github.com/crossterm-rs/crossterm/issues/6) -- Get position unix fixed [issue 8](https://github.com/crossterm-rs/crossterm/issues/8) - -# Version 0.2 - -- 256 color support. -- Text Attributes like: bold, italic, underscore and crossed word ect. -- Custom ANSI color code input to set fore- and background color for unix. -- Storing the current cursor position and resetting to that stored cursor position later. -- Resizing the terminal. +# Version 0.17.6 +- Add functionality to retrieve color based on passed ansi code. +- Switch from 'futures' to 'futures-util' crate to reduce dependency count +- Mio 0.7 update +- signal-hook update +- Make windows raw_mode act on CONIN$ +- Added From<(u8, u8, u8)> Trait to Color::Rgb Enum +- Implement Color::try_from() +- Implement styler traits for `&'a str` + +# Version 0.17.5 +- Improved support of keymodifier for linux, arrow keys, function keys, home keys etc. +- Add `SetTitle` command to change the terminal title. +- Mio 0.7 update + +# Version 0.17.4 +- Add macros for `Colorize` and `Styler` impls, add an impl for `String` +- Add shift modifier to uppercase char events on unix + +# Version 0.17.3 +- Fix get terminal size mac os, this did not report the correct size. + +# Version 0.17.2 +- Windows unicode support + +# Version 0.17.1 +- Reverted bug in 0.17.0: "Make terminal size function fallback to `STDOUT_FILENO` if `/dev/tty` is missing.". +- Support for querying whether the current instance is a TTY. + +# Version 0.17 +- Impl Display for MoveToColumn, MoveToNextLine, MoveToPreviousLine +- Make unix event reader always use `/dev/tty`. +- Direct write command ansi_codes into formatter instead of double allocation. +- Add NONE flag to KeyModifiers +- Add support for converting chars to StylizedContent +- Make terminal size function fallback to `STDOUT_FILENO` if `/dev/tty` is missing. + +# Version 0.16.0 +- Change attribute vector in `ContentStyle` to bitmask. +- Add `SetAttributes` command. +- Add `Attributes` type, which is a bitfield of enabled attributes. +- Remove `exit()`, was useless. + +# Version 0.15.0 +- Fix CTRL + J key combination. This used to return an ENTER event. +- Add a generic implementation `Command` for `&T: Command`. This allows commands to be queued by reference, as well as by value. +- Remove unnecessary `Clone` trait bounds from `StyledContent`. +- Add `StyledContent::style_mut`. +- Handle error correctly for `execute!` and `queue!`. +- Fix minor syntax bug in `execute!` and `queue!`. +- Change `ContentStyle::apply` to take self by value instead of reference, to prevent an unnecessary extra clone. +- Added basic trait implementations (`Debug`, `Clone`, `Copy`, etc) to all of the command structs +- `ResetColor` uses `&'static str` instead of `String` + +# Version 0.14.2 +- Fix TIOCGWINSZ for FreeBSD + +# Version 0.14.1 +- Made windows cursor position relative to the window instead absolute to the screen buffer windows. +- Fix windows bug with `queue` macro were it consumed a type and required an type to be `Copy`. + +# Version 0.14 + +- Replace the `input` module with brand new `event` module + - Terminal Resize Events + - Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and + - futures Stream (feature 'event-stream') + - Poll/read API + - It's **highly recommended** to read the + [Upgrade from 0.13 to 0.14](https://github.com/crossterm-rs/crossterm/wiki/Upgrade-from-0.13-to-0.14) + documentation +- Replace `docs/UPGRADE.md` with the [Upgrade Paths](https://github.com/crossterm-rs/crossterm/wiki#upgrade-paths) + documentation +- Add `MoveToColumn`, `MoveToPreviousLine`, `MoveToNextLine` commands +- Merge `screen` module into `terminal` + - Remove `screen::AlternateScreen` + - Remove `screen::Rawscreen` + * Move and rename `Rawscreen::into_raw_mode` and `Rawscreen::disable_raw_mode` to `terminal::enable_raw_mode` and `terminal::disable_raw_mode` + - Move `screen::EnterAlternateScreen` and `screen::LeaveAlternateScreen` to `terminal::EnterAlternateScreen` and `terminal::LeaveAlternateScreen` + - Replace `utils::Output` command with `style::Print` command +- Fix enable/disable mouse capture commands on Windows +- Allow trailing comma `queue!` & `execute!` macros + +# Version 0.13.3 + +- Remove thread from AsyncReader on Windows. +- Improve HANDLE management windows. + +# Version 0.13.2 + +- New `input::stop_reading_thread()` function + - Temporary workaround for the UNIX platform to stop the background + reading thread and close the file descriptor + - This function will be removed in the next version + +# Version 0.13.1 + +- Async Reader fix, join background thread and avoid looping forever on windows. + +# Version 0.13.0 + +**Major API-change, removed old-api** + +- Remove `Crossterm` type +- Remove `TerminalCursor`, `TerminalColor`, `Terminal` +- Remove `cursor()`, `color()` , `terminal()` +- Remove re-exports at root, accessible via `module::types` (`cursor::MoveTo`) +- `input` module + - Derive 'Copy' for 'KeyEvent' + - Add the `EnableMouseCapture` and `EnableMouseCapture` commands +- `cursor` module + - Introduce static function `crossterm::cursor::position` in place of `TerminalCursor::pos` + - Rename `Goto` to `MoveTo` + - Rename `Up` to `MoveLeft` + - Rename `Right` to `MoveRight` + - Rename `Down` to `MoveDown` + - Rename `BlinkOn` to `EnableBlinking` + - Rename `BlinkOff` to `DisableBlinking` + - Rename `ResetPos` to `ResetPosition` + - Rename `SavePos` to `SavePosition` +- `terminal` + - Introduce static function `crossterm::terminal::size` in place of `Terminal::size` + - Introduce static function `crossterm::terminal::exit` in place of `Terminal::exit` +- `style module` + - Rename `ObjectStyle` to `ContentStyle`. Now full names are used for methods + - Rename `StyledObject` to `StyledContent` and made members private + - Rename `PrintStyledFont` to `PrintStyledContent` + - Rename `attr` method to `attribute`. + - Rename `Attribute::NoInverse` to `NoReverse` + - Update documentation + - Made `Colored` private, user should use commands instead + - Rename `SetFg` -> `SetForegroundColor` + - Rename `SetBg` -> `SetBackgroundColor` + - Rename `SetAttr` -> `SetAttribute` + - Rename `ContentStyle::fg_color` -> `ContentStyle::foreground_color` + - Rename `ContentStyle::bg_color` -> `ContentStyle::background_color` + - Rename `ContentStyle::attrs` -> `ContentStyle::attributes` +- Improve documentation +- Unix terminal size calculation with TPUT + +# Version 0.12.1 + +- Move all the `crossterm_` crates code was moved to the `crossterm` crate + - `crossterm_cursor` is in the `cursor` module, etc. + - All these modules are public +- No public API breaking changes + +# Version 0.12.0 + +- Following crates are deprecated and no longer maintained + - `crossterm_cursor` + - `crossterm_input` + - `crossterm_screen` + - `crossterm_style` + - `crossterm_terminal` + - `crossterm_utils` + +## `crossterm_cursor` 0.4.0 + +- Fix examples link ([PR #6](https://github.com/crossterm-rs/crossterm-cursor/pull/6)) +- Sync documentation style ([PR #7](https://github.com/crossterm-rs/crossterm-cursor/pull/7)) +- Remove all references to the crossterm book ([PR #8](https://github.com/crossterm-rs/crossterm-cursor/pull/8)) +- Replace `RAW_MODE_ENABLED` with `is_raw_mode_enabled` ([PR #9](https://github.com/crossterm-rs/crossterm-cursor/pull/9)) +- Use `SyncReader` & `InputEvent::CursorPosition` for `pos_raw()` ([PR #10](https://github.com/crossterm-rs/crossterm-cursor/pull/10)) + +## `crossterm_input` 0.5.0 + +- Sync documentation style ([PR #4](https://github.com/crossterm-rs/crossterm-input/pull/4)) +- Sync `SyncReader::next()` Windows and UNIX behavior ([PR #5](https://github.com/crossterm-rs/crossterm-input/pull/5)) +- Remove all references to the crossterm book ([PR #6](https://github.com/crossterm-rs/crossterm-input/pull/6)) +- Mouse coordinates synchronized with the cursor ([PR #7](https://github.com/crossterm-rs/crossterm-input/pull/7)) + - Upper/left reported as `(0, 0)` +- Fix bug that read sync didn't block (Windows) ([PR #8](https://github.com/crossterm-rs/crossterm-input/pull/8)) +- Refactor UNIX readers ([PR #9](https://github.com/crossterm-rs/crossterm-input/pull/9)) + - AsyncReader produces mouse events + - One reading thread per application, not per `AsyncReader` + - Cursor position no longer consumed by another `AsyncReader` + - Implement sync reader for read_char (requires raw mode) + - Fix `SIGTTIN` when executed under the LLDB + - Add mio for reading from FD and more efficient polling (UNIX only) +- Sync UNIX and Windows vertical mouse position ([PR #11](https://github.com/crossterm-rs/crossterm-input/pull/11)) + - Top is always reported as `0` + +## `crossterm_screen` 0.3.2 + +- `to_alternate` switch back to main screen if it fails to switch into raw mode ([PR #4](https://github.com/crossterm-rs/crossterm-screen/pull/4)) +- Improve the documentation ([PR #5](https://github.com/crossterm-rs/crossterm-screen/pull/5)) + - Public API + - Include the book content in the documentation +- Remove all references to the crossterm book ([PR #6](https://github.com/crossterm-rs/crossterm-screen/pull/6)) +- New commands introduced ([PR #7](https://github.com/crossterm-rs/crossterm-screen/pull/7)) + - `EnterAlternateScreen` + - `LeaveAlternateScreen` +- Sync Windows and UNIX raw mode behavior ([PR #8](https://github.com/crossterm-rs/crossterm-screen/pull/8)) + +## `crossterm_style` 0.5.2 + +- Refactor ([PR #2](https://github.com/crossterm-rs/crossterm-style/pull/2)) + - Added unit tests + - Improved documentation and added book page to `lib.rs` + - Fixed bug with `SetBg` command, WinApi logic + - Fixed bug with `StyledObject`, used stdout for resetting terminal color + - Introduced `ResetColor` command +- Sync documentation style ([PR #3](https://github.com/crossterm-rs/crossterm-style/pull/3)) +- Remove all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-style/pull/4)) +- Windows 7 grey/white foreground/intensity swapped ([PR #5](https://github.com/crossterm-rs/crossterm-style/pull/5)) + +## `crossterm_terminal` 0.3.2 + +- Removed `crossterm_cursor::sys` dependency ([PR #2](https://github.com/crossterm-rs/crossterm-terminal/pull/2)) +- Internal refactoring & documentation ([PR #3](https://github.com/crossterm-rs/crossterm-terminal/pull/3)) +- Removed all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-terminal/pull/4)) + +## `crossterm_utils` 0.4.0 + +- Add deprecation note ([PR #3](https://github.com/crossterm-rs/crossterm-utils/pull/3)) +- Remove all references to the crossterm book ([PR #4](https://github.com/crossterm-rs/crossterm-utils/pull/4)) +- Remove unsafe static mut ([PR #5](https://github.com/crossterm-rs/crossterm-utils/pull/5)) + - `sys::unix::RAW_MODE_ENABLED` replaced with `sys::unix::is_raw_mode_enabled()` (breaking) + - New `lazy_static` dependency + +## `crossterm_winapi` 0.3.0 + +- Make read sync block for windows systems ([PR #2](https://github.com/crossterm-rs/crossterm-winapi/pull/2)) + +# Version 0.11.1 + +- Maintenance release +- All sub-crates were moved to their own repositories in the `crossterm-rs` organization + +# Version 0.11.0 + +As a preparation for crossterm 0.1.0 we have moved crossterm to an organisation called 'crossterm-rs'. + +### Code Quality + +- Code Cleanup: [warning-cleanup], [crossterm_style-cleanup], [crossterm_screen-cleanup], [crossterm_terminal-cleanup], [crossterm_utils-cleanup], [2018-cleanup], [api-cleanup-1], [api-cleanup-2], [api-cleanup-3] +- Examples: [example-cleanup_1], [example-cleanup_2], [example-fix], [commandbar-fix], [snake-game-improved] +- Fixed all broken tests and added tests + +### Important Changes + +- Return written bytes: [return-written-bytes] +- Added derives: `Debug` for `ObjectStyle` [debug-derive], Serialize/Deserialize for key events [serde] +- Improved error handling: + - Return `crossterm::Result` from all api's: [return_crossterm_result] + * `TerminalCursor::pos()` returns `Result<(u16, u16)>` + * `Terminal::size()` returns `Result<(u16, u16)>` + * `TerminalCursor::move_*` returns `crossterm::Result` + * `ExecutableCommand::queue` returns `crossterm::Result` + * `QueueableCommand::queue` returns `crossterm::Result` + * `get_available_color_count` returns no result + * `RawScreen::into_raw_mode` returns `crossterm::Result` instead of `io::Result` + * `RawScreen::disable_raw_mode` returns `crossterm::Result` instead of `io::Result` + * `AlternateScreen::to_alternate` returns `crossterm::Result` instead of `io::Result` + * `TerminalInput::read_line` returns `crossterm::Result` instead of `io::Result` + * `TerminalInput::read_char` returns `crossterm::Result` instead of `io::Result` + * Maybe I forgot something, a lot of functions have changed + - Removed all unwraps/expects from library +- Add KeyEvent::Enter and KeyEvent::Tab: [added-key-event-enter], [added-key-event-tab] +- Sync set/get terminal size behaviour: [fixed-get-set-terminal-size] +- Method renames: + * `AsyncReader::stop_reading()` to `stop()` + * `RawScreen::disable_raw_mode_on_drop` to `keep_raw_mode_on_drop` + * `TerminalCursor::reset_position()` to `restore_position()` + * `Command::get_anis_code()` to `ansi_code()` + * `available_color_count` to `available_color_count()` + * `Terminal::terminal_size` to `Terminal::size` + * `Console::get_handle` to `Console::handle` +- All `i16` values for indexing: set size, set cursor pos, scrolling synced to `u16` values +- Command API takes mutable self instead of self + +[serde]: https://github.com/crossterm-rs/crossterm/pull/190 + +[debug-derive]: https://github.com/crossterm-rs/crossterm/pull/192 +[example-fix]: https://github.com/crossterm-rs/crossterm/pull/193 +[commandbar-fix]: https://github.com/crossterm-rs/crossterm/pull/204 + +[warning-cleanup]: https://github.com/crossterm-rs/crossterm/pull/198 +[example-cleanup_1]: https://github.com/crossterm-rs/crossterm/pull/196 +[example-cleanup_2]: https://github.com/crossterm-rs/crossterm/pull/225 +[snake-game-improved]: https://github.com/crossterm-rs/crossterm/pull/231 +[crossterm_style-cleanup]: https://github.com/crossterm-rs/crossterm/pull/208 +[crossterm_screen-cleanup]: https://github.com/crossterm-rs/crossterm/pull/209 +[crossterm_terminal-cleanup]: https://github.com/crossterm-rs/crossterm/pull/210 +[crossterm_utils-cleanup]: https://github.com/crossterm-rs/crossterm/pull/211 +[2018-cleanup]: https://github.com/crossterm-rs/crossterm/pull/222 +[wild-card-cleanup]: https://github.com/crossterm-rs/crossterm/pull/224 + +[api-cleanup-1]: https://github.com/crossterm-rs/crossterm/pull/235 +[api-cleanup-2]: https://github.com/crossterm-rs/crossterm/pull/238 +[api-cleanup-3]: https://github.com/crossterm-rs/crossterm/pull/240 + +[return-written-bytes]: https://github.com/crossterm-rs/crossterm/pull/212 + +[return_crossterm_result]: https://github.com/crossterm-rs/crossterm/pull/232 +[added-key-event-tab]: https://github.com/crossterm-rs/crossterm/pull/239 +[added-key-event-enter]: https://github.com/crossterm-rs/crossterm/pull/236 +[fixed-get-set-terminal-size]: https://github.com/crossterm-rs/crossterm/pull/242 + +# Version 0.10.1 + +# Version 0.10.0 ~ yanked +- Implement command API, to have better performance and more control over how and when commands are executed. [PR](https://github.com/crossterm-rs/crossterm/commit/1a60924abd462ab169b6706aab68f4cca31d7bc2), [issue](https://github.com/crossterm-rs/crossterm/issues/171) +- Fix showing, hiding cursor windows implementation +- Remove some of the parsing logic from windows keys to ansi codes to key events [PR](https://github.com/crossterm-rs/crossterm/commit/762c3a9b8e3d1fba87acde237f8ed09e74cd9ecd) +- Made terminal size 1-based [PR](https://github.com/crossterm-rs/crossterm/commit/d689d7e8ed46a335474b8262bd76f21feaaf0c50) +- Add some derives + +# Version 0.9.6 + +- Copy for KeyEvent +- CTRL + Left, Down, Up, Right key support +- SHIFT + Left, Down, Up, Right key support +- Fixed UNIX cursor position bug [issue](https://github.com/crossterm-rs/crossterm/issues/140), [PR](https://github.com/crossterm-rs/crossterm/pull/152) + +# Version 0.9.5 + +- Prefetch buffer size for more efficient windows input reads. [PR](https://github.com/crossterm-rs/crossterm/pull/144) + +# Version 0.9.4 + +- Reset foreground and background color individually. [PR](https://github.com/crossterm-rs/crossterm/pull/138) +- Backtap input support. [PR](https://github.com/crossterm-rs/crossterm/pull/129) +- Corrected white/grey and added dark grey. +- Fixed getting cursor position with raw screen enabled. [PR](https://github.com/crossterm-rs/crossterm/pull/134) +- Removed one redundant stdout lock + +# Version 0.9.3 + +- Removed println from `SyncReader` + +## Version 0.9.2 + +- Terminal size linux was not 0-based +- Windows mouse input event position was 0-based ans should be 1-based +- Result, ErrorKind are made re-exported +- Fixed some special key combination detections for UNIX systems +- Made FreeBSD compile + +## Version 0.9.1 + +- Fixed libc compile error + +## Version 0.9.0 (yanked) + +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 + +## Version 0.8.2 + +- Bug fix for sync reader UNIX. + +## Version 0.8.1 + +- Added public re-exports for input. + +# Version 0.8.0 + +- Introduced KeyEvents +- Introduced MouseEvents +- Upgraded crossterm_winapi 0.2 + +# Version 0.7.0 + +- Introduced more `Attributes` +- Introduced easier ways to style text [issue 87](https://github.com/crossterm-rs/crossterm/issues/87). +- Removed `ColorType` since it was unnecessary. + +# Version 0.6.0 + +- Introduced feature flags; input, cursor, style, terminal, screen. +- All modules are moved to their own crate. +- Introduced crossterm workspace +- Less dependencies. +- Improved namespaces. + +[PR 84](https://github.com/crossterm-rs/crossterm/pull/84) + +# Version 0.5.5 + +- Error module is made public [PR 78](https://github.com/crossterm-rs/crossterm/pull/78). + +# Version 0.5.4 + +- WinApi rewrite and correctly error handled [PR 67](https://github.com/crossterm-rs/crossterm/pull/67) +- Windows attribute support [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) +- Readline bug fix windows systems [PR 62](https://github.com/crossterm-rs/crossterm/pull/62) +- Error handling improvement. +- General refactoring, all warnings removed. +- Documentation improvement. + +# Version 0.5.1 + +- Documentation refactor. +- Fixed broken API documentation [PR 53](https://github.com/crossterm-rs/crossterm/pull/53). + +# Version 0.5.0 + +- Added ability to pause the terminal [issue](https://github.com/crossterm-rs/crossterm/issues/39) +- RGB support for Windows 10 systems +- ANSI color value (255) color support +- More convenient API, no need to care about `Screen` unless working with when working with alternate or raw screen [PR](https://github.com/crossterm-rs/crossterm/pull/44) +- Implemented Display for styled object + +# Version 0.4.3 + +- Fixed bug [issue 41](https://github.com/crossterm-rs/crossterm/issues/41) + +# Version 0.4.2 + +- Added functionality to make a styled object writable to screen [issue 33](https://github.com/crossterm-rs/crossterm/issues/33) +- Added unit tests. +- Bugfix with getting terminal size unix. +- Bugfix with returning written bytes [pull request 31](https://github.com/crossterm-rs/crossterm/pull/31) +- removed methods calls: `as_any()` and `as_any_mut()` from `TerminalOutput` + +# Version 0.4.1 + +- Fixed resizing of ansi terminal with and height where in the wrong order. + +# Version 0.4.0 + +- Input support (read_line, read_char, read_async, read_until_async) +- Styling module improved +- Everything is multithreaded (`Send`, `Sync`) +- Performance enhancements: removed mutexes, removed state manager, removed context type removed unnecessarily RC types. +- Bug fix resetting console color. +- Bug fix whit undoing raw modes. +- More correct error handling. +- Overall commend improvement. +- Overall refactor of code. + +# Version 0.3.0 + +This version has some braking changes check [upgrade manual](UPGRADE%20Manual.md) for more information about what is changed. +I think you should not switch to version `0.3.0` if you aren't going to use the AlternateScreen feature. +Because you will have some work to get to the new version of crossterm depending on your situation. + +Some Features crossterm 0.3.0 +- Alternate Screen for windows and unix systems. +- Raw screen for unix and windows systems [Issue 5](https://github.com/crossterm-rs/crossterm/issues/5).. +- Hiding an showing the cursor. +- Control over blinking of the terminal cursor (only some terminals are supporting this). +- The terminal state will be set to its original state when process ends [issue7](https://github.com/crossterm-rs/crossterm/issues/7). +- exit the current process. + +## Alternate screen + +This create supports alternate screen for both windows and unix systems. You can use + +*Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them. +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. + +I Highly recommend you to check the `examples/program_examples/first_depth_search` for seeing this in action. + +## Raw screen + +This crate now supports raw screen for both windows and unix systems. +What exactly is raw state: +- No line buffering. + Normally the terminals uses line buffering. This means that the input will be send to the terminal line by line. + With raw mode the input will be send one byte at a time. +- Input + All input has to be written manually by the programmer. +- Characters + The characters are not processed by the terminal driver, but are sent straight through. + Special character have no meaning, like backspace will not be interpret as backspace but instead will be directly send to the terminal. +With these modes you can easier design the terminal screen. + +## Some functionalities added + +- Hiding and showing terminal cursor +- Enable or disabling blinking of the cursor for unix systems (this is not widely supported) +- Restoring the terminal to original modes. +- Added a [wrapper](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) for managing all the functionalities of crossterm `Crossterm`. +- Exit the current running process + +## Examples +Added [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) for each version of the crossterm version. +Also added a folder with some [real life examples](https://github.com/crossterm-rs/crossterm/tree/master/examples/program_examples). + +## Context + +What is the `Context` all about? This `Context` has several reasons why it is introduced into `crossterm version 0.3.0`. +These points are related to the features like `Alternatescreen` and managing the terminal state. + +- At first `Terminal state`: + + Because this is a terminal manipulating library there will be made changes to terminal when running an process. + If you stop the process you want the terminal back in its original state. + Therefore, I need to track the changes made to the terminal. + +- At second `Handle to the console` + + In Rust we can use `stdout()` to get an handle to the current default console handle. + For example when in unix systems you want to print something to the main screen you can use the following code: + + write!(std::io::stdout(), "{}", "some text"). + + But things change when we are in alternate screen modes. + We can not simply use `stdout()` to get a handle to the alternate screen, since this call returns the current default console handle (handle to mainscreen). + + Because of that we need to store an handle to the current screen. + This handle could be used to put into alternate screen modes and back into main screen modes. + Through this stored handle Crossterm can execute its command and write on and to the current screen whether it be alternate screen or main screen. + + For unix systems we store the handle gotten from `stdout()` for windows systems that are not supporting ANSI escape codes we store WinApi `HANDLE` struct witch will provide access to the current screen. + +So to recap this `Context` struct is a wrapper for a type that manges terminal state changes. +When this `Context` goes out of scope all changes made will be undone. +Also is this `Context` is a wrapper for access to the current console screen. + +Because Crossterm needs access to the above to types quite often I have chosen to add those two in one struct called `Context` so that this type could be shared throughout library. +Check this link for more info: [cleanup of rust code](https://stackoverflow.com/questions/48732387/how-can-i-run-clean-up-code-in-a-rust-library). +More info over writing to alternate screen buffer on windows and unix see this [link](https://github.com/crossterm-rs/crossterm/issues/17) + +__Now the user has to pass an context type to the modules of Crossterm like this:__ + + let context = Context::new(); + + let cursor = cursor(&context); + let terminal = terminal(&context); + let color = color(&context); + +Because this looks a little odd I will provide a type withs will manage the `Context` for you. You can call the different modules like the following: + + let crossterm = Crossterm::new(); + let color = crossterm.color(); + let cursor = crossterm.cursor(); + let terminal = crossterm.terminal(); + + +### Alternate screen +When you want to switch to alternate screen there are a couple of things to keep in mind for it to work correctly. +First off some code of how to switch to Alternate screen, for more info check the [alternate screen example](https://github.com/crossterm-rs/crossterm/blob/master/examples/alternate_screen.rs). + +_Create alternate screen from `Context`_ + + // create context. + let context = crossterm::Context::new(); + // create instance of Alternatescreen by the given context, this wil also switch to it. + let mut screen = crossterm::AlternateScreen::from(context.clone()); + // write to the alternate screen. + write!(screen, "test"); + +_Create alternate screen from `Crossterm`:_ + + // create context. + let crossterm = ::crossterm::Crossterm::new(); + // create instance of Alternatescreen by the given refrence to crossterm, this wil also switch to it. + let mut screen = crossterm::AlternateScreen::from(&crossterm); + // write to the alternate screen. + write!(screen, "test"); + +like demonstrated above, to get the functionalities of `cursor(), color(), terminal()` also working on alternate screen. +You need to pass it the same `Context` as you have passed to the previous three called functions, +If you don't use the same `Context` in `cursor(), color(), terminal()` than these modules will be using the main screen and you will not see anything at the alternate screen. If you use the [Crossterm](https://github.com/crossterm-rs/crossterm/blob/master/src/shared/crossterm.rs) type you can get the `Context` from it by calling the crossterm.get_context() whereafter you can create the AlternateScreen from it. + +# Version 0.2.2 + +- Bug see [issue 15](https://github.com/crossterm-rs/crossterm/issues/15) + +# Version 0.2.1 + +- Default ANSI escape codes for windows machines, if windows does not support ANSI switch back to WinApi. +- method grammar mistake fixed [Issue 3](https://github.com/crossterm-rs/crossterm/issues/3) +- Some Refactorings in method names see [issue 4](https://github.com/crossterm-rs/crossterm/issues/4) +- Removed bin reference from crate [Issue 6](https://github.com/crossterm-rs/crossterm/issues/6) +- Get position unix fixed [issue 8](https://github.com/crossterm-rs/crossterm/issues/8) + +# Version 0.2 + +- 256 color support. +- Text Attributes like: bold, italic, underscore and crossed word ect. +- Custom ANSI color code input to set fore- and background color for unix. +- Storing the current cursor position and resetting to that stored cursor position later. +- Resizing the terminal. diff --git a/Cargo.toml b/Cargo.toml index f99e3d897..cd70045b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,67 +1,67 @@ -[package] -name = "crossterm" -version = "0.17.6" -authors = ["T. Post"] -description = "An crossplatform terminal library for manipulating terminals." -repository = "https://github.com/crossterm-rs/crossterm" -documentation = "https://docs.rs/crossterm/" -license = "MIT" -keywords = ["event", "color", "cli", "input", "terminal"] -exclude = ["target", "Cargo.lock"] -readme = "README.md" -edition = "2018" -categories = ["command-line-interface", "command-line-utilities"] - -[lib] -name = "crossterm" -path = "src/lib.rs" - -# -# Build documentation with all features -> EventStream is available -# -[package.metadata.docs.rs] -all-features = true - -# -# Features -# -[features] -default = [] -event-stream = ["futures-util"] - -# -# Shared dependencies -# -[dependencies] -bitflags = "1.2" -lazy_static = "1.4" -parking_lot = "0.10" -futures-util = { version = "0.3", optional = true, default-features = false } -serde = { version = "1.0", features = ["derive"], optional = true } - -# -# Windows dependencies -# -[target.'cfg(windows)'.dependencies.winapi] -version = "0.3.8" -features = ["winuser"] - -[target.'cfg(windows)'.dependencies] -crossterm_winapi = "0.6.1" - -# -# UNIX dependencies -# -[target.'cfg(unix)'.dependencies] -libc = "0.2" -mio = {version="0.7", features=["os-poll"]} -signal-hook = { version = "0.1.15", features = ["mio-0_7-support"] } - -# -# Dev dependencies (examples, ...) -# -[dev-dependencies] -tokio = { version = "0.2.11", features = ["full"] } -futures = "0.3" -futures-timer = "3.0" -async-std = "1.4" +[package] +name = "crossterm" +version = "0.17.6" +authors = ["T. Post"] +description = "An crossplatform terminal library for manipulating terminals." +repository = "https://github.com/crossterm-rs/crossterm" +documentation = "https://docs.rs/crossterm/" +license = "MIT" +keywords = ["event", "color", "cli", "input", "terminal"] +exclude = ["target", "Cargo.lock"] +readme = "README.md" +edition = "2018" +categories = ["command-line-interface", "command-line-utilities"] + +[lib] +name = "crossterm" +path = "src/lib.rs" + +# +# Build documentation with all features -> EventStream is available +# +[package.metadata.docs.rs] +all-features = true + +# +# Features +# +[features] +default = [] +event-stream = ["futures-util"] + +# +# Shared dependencies +# +[dependencies] +bitflags = "1.2" +lazy_static = "1.4" +parking_lot = "0.10" +futures-util = { version = "0.3", optional = true, default-features = false } +serde = { version = "1.0", features = ["derive"], optional = true } + +# +# Windows dependencies +# +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3.8" +features = ["winuser"] + +[target.'cfg(windows)'.dependencies] +crossterm_winapi = "0.6.1" + +# +# UNIX dependencies +# +[target.'cfg(unix)'.dependencies] +libc = "0.2" +mio = {version="0.7", features=["os-poll"]} +signal-hook = { version = "0.1.15", features = ["mio-0_7-support"] } + +# +# Dev dependencies (examples, ...) +# +[dev-dependencies] +tokio = { version = "0.2.11", features = ["full"] } +futures = "0.3" +futures-timer = "3.0" +async-std = "1.4" diff --git a/README.md b/README.md index 1b7edf435..c4d194122 100644 --- a/README.md +++ b/README.md @@ -1,198 +1,198 @@ -

- -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z8QK6XU749JB2) ![Travis][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] [![Join us on Discord][s5]][l5] - -# Cross-platform Terminal Manipulation Library - -Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform text-based interfaces (see [features](#features)). It supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested, -see [Tested Terminals](#tested-terminals) for more info). - -## Table of Contents - -* [Features](#features) - * [Tested Terminals](#tested-terminals) -* [Getting Started](#getting-started) - * [Feature Flags](#feature-flags) -* [Other Resources](#other-resources) -* [Used By](#used-by) -* [Contributing](#contributing) - -## Features - -- Cross-platform -- Multi-threaded (send, sync) -- Detailed documentation -- Few dependencies -- Full control over writing and flushing output buffer -- Is tty -- Cursor - - Move the cursor N times (up, down, left, right) - - Move to previous / next line - - Move to column - - Set/get the cursor position - - Store the cursor position and restore to it later - - Hide/show the cursor - - Enable/disable cursor blinking (not all terminals do support this feature) -- Styled output - - Foreground color (16 base colors) - - Background color (16 base colors) - - 256 (ANSI) color support (Windows 10 and UNIX only) - - RGB color support (Windows 10 and UNIX only) - - Text attributes like bold, italic, underscore, crossed, etc -- Terminal - - Clear (all lines, current line, from cursor down and up, until new line) - - Scroll up, down - - Set/get the terminal size - - Exit current process - - Alternate screen - - Raw screen - - Set terminal title -- Event - - Input Events - - Mouse Events (press, release, position, button, drag) - - Terminal Resize Events - - Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and - - futures Stream (feature 'event-stream') - - Poll/read API - - - -### Tested Terminals - -- Console Host - - Windows 10 (Pro) - - Windows 8.1 (N) -- Ubuntu Desktop Terminal - - Ubuntu 17.10 -- (Arch, Manjaro) KDE Konsole -- (Arch) Kitty -- Linux Mint - -This crate supports all UNIX terminals and Windows terminals down to Windows 7; however, not all of the -terminals have been tested. If you have used this library for a terminal other than the above list without -issues, then feel free to add it to the above list - I really would appreciate it! - -## Getting Started -_see the [examples directory](examples/) and [documentation](https://docs.rs/crossterm/) for more advanced examples._ - -
- -Click to show Cargo.toml. - - -```toml -[dependencies] -crossterm = "0.17" -``` - -
-

- -```rust -use std::io::{stdout, Write}; - -use crossterm::{ - execute, - style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor}, - ExecutableCommand, Result, - event, -}; - -fn main() -> Result<()> { - // using the macro - execute!( - stdout(), - SetForegroundColor(Color::Blue), - SetBackgroundColor(Color::Red), - Print("Styled text here."), - ResetColor - )?; - - // or using functions - stdout() - .execute(SetForegroundColor(Color::Blue))? - .execute(SetBackgroundColor(Color::Red))? - .execute(Print("Styled text here."))? - .execute(ResetColor)?; - - Ok(()) -} -``` - -Checkout this [list](https://docs.rs/crossterm/0.14.0/crossterm/index.html#supported-commands) with all possible commands. - -### Feature Flags - -To optional feature flags. - -```toml -[dependencies.crossterm] -version = "0.17" -features = ["event-stream"] -``` - -| Feature | Description | -| :----- | :----- | -| `event-stream` | `futures::Stream` producing `Result`.| - -### Dependency Justification - -| Dependency | Used for | Included | -| :----- | :----- | :----- -| `bitflags` | `KeyModifiers`, those are differ based on input.| always -| `lazy_static` | original console color, original terminal mode, saved cursor position, supports ANSI on windows, single event reader per application.| always -| `parking_lot` | used for an RW LOCK. | always -| `libc` | UNIX terminal_size/raw modes/set_title and several other lowlevel functionality. | UNIX only -| `Mio` | event readiness polling, waking up poller | UNIX only -| `signal-hook`| signalhook is used to handle terminal resize SIGNAL with Mio. | UNIX only -| `winapi`| Used for low-level windows system calls which ANSI codes can't replace| windows only -| `futures`| Can be used to for async stream of events | only with a feature flag -| `serde`| Se/dese/realizing of events | only with a feature flag - - -### Other Resources - -- [API documentation](https://docs.rs/crossterm/) -- [Deprecated examples repository](https://github.com/crossterm-rs/examples) - -## Used By - -- [Broot](https://dystroy.org/broot/) -- [Cursive](https://github.com/gyscos/Cursive) -- [TUI](https://github.com/fdehau/tui-rs) -- [Rust-sloth](https://github.com/ecumene/rust-sloth) - -## Contributing - -We highly appreciate when anyone contributes to this crate. Before you do, please, -read the [Contributing](docs/CONTRIBUTING.md) guidelines. - -## Authors - -* **Timon Post** - *Project Owner & creator* - -## License - -This project, `crossterm` and all its sub-crates: `crossterm_screen`, `crossterm_cursor`, `crossterm_style`, -`crossterm_input`, `crossterm_terminal`, `crossterm_winapi`, `crossterm_utils` are licensed under the MIT -License - see the [LICENSE](https://github.com/crossterm-rs/crossterm/blob/master/LICENSE) file for details. - -[s1]: https://img.shields.io/crates/v/crossterm.svg -[l1]: https://crates.io/crates/crossterm - -[s2]: https://img.shields.io/badge/license-MIT-blue.svg -[l2]: ./LICENSE - -[s3]: https://docs.rs/crossterm/badge.svg -[l3]: https://docs.rs/crossterm/ - -[s3]: https://docs.rs/crossterm/badge.svg -[l3]: https://docs.rs/crossterm/ - -[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord -[l5]: https://discord.gg/K4nyTDB - -[s6]: https://tokei.rs/b1/github/crossterm-rs/crossterm?category=code -[s7]: https://travis-ci.org/crossterm-rs/crossterm.svg?branch=master +

+ +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Z8QK6XU749JB2) ![Travis][s7] [![Latest Version][s1]][l1] [![MIT][s2]][l2] [![docs][s3]][l3] ![Lines of Code][s6] [![Join us on Discord][s5]][l5] + +# Cross-platform Terminal Manipulation Library + +Crossterm is a pure-rust, terminal manipulation library that makes it possible to write cross-platform text-based interfaces (see [features](#features)). It supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested, +see [Tested Terminals](#tested-terminals) for more info). + +## Table of Contents + +* [Features](#features) + * [Tested Terminals](#tested-terminals) +* [Getting Started](#getting-started) + * [Feature Flags](#feature-flags) +* [Other Resources](#other-resources) +* [Used By](#used-by) +* [Contributing](#contributing) + +## Features + +- Cross-platform +- Multi-threaded (send, sync) +- Detailed documentation +- Few dependencies +- Full control over writing and flushing output buffer +- Is tty +- Cursor + - Move the cursor N times (up, down, left, right) + - Move to previous / next line + - Move to column + - Set/get the cursor position + - Store the cursor position and restore to it later + - Hide/show the cursor + - Enable/disable cursor blinking (not all terminals do support this feature) +- Styled output + - Foreground color (16 base colors) + - Background color (16 base colors) + - 256 (ANSI) color support (Windows 10 and UNIX only) + - RGB color support (Windows 10 and UNIX only) + - Text attributes like bold, italic, underscore, crossed, etc +- Terminal + - Clear (all lines, current line, from cursor down and up, until new line) + - Scroll up, down + - Set/get the terminal size + - Exit current process + - Alternate screen + - Raw screen + - Set terminal title +- Event + - Input Events + - Mouse Events (press, release, position, button, drag) + - Terminal Resize Events + - Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and + - futures Stream (feature 'event-stream') + - Poll/read API + + + +### Tested Terminals + +- Console Host + - Windows 10 (Pro) + - Windows 8.1 (N) +- Ubuntu Desktop Terminal + - Ubuntu 17.10 +- (Arch, Manjaro) KDE Konsole +- (Arch) Kitty +- Linux Mint + +This crate supports all UNIX terminals and Windows terminals down to Windows 7; however, not all of the +terminals have been tested. If you have used this library for a terminal other than the above list without +issues, then feel free to add it to the above list - I really would appreciate it! + +## Getting Started +_see the [examples directory](examples/) and [documentation](https://docs.rs/crossterm/) for more advanced examples._ + +
+ +Click to show Cargo.toml. + + +```toml +[dependencies] +crossterm = "0.17" +``` + +
+

+ +```rust +use std::io::{stdout, Write}; + +use crossterm::{ + execute, + style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor}, + ExecutableCommand, Result, + event, +}; + +fn main() -> Result<()> { + // using the macro + execute!( + stdout(), + SetForegroundColor(Color::Blue), + SetBackgroundColor(Color::Red), + Print("Styled text here."), + ResetColor + )?; + + // or using functions + stdout() + .execute(SetForegroundColor(Color::Blue))? + .execute(SetBackgroundColor(Color::Red))? + .execute(Print("Styled text here."))? + .execute(ResetColor)?; + + Ok(()) +} +``` + +Checkout this [list](https://docs.rs/crossterm/0.14.0/crossterm/index.html#supported-commands) with all possible commands. + +### Feature Flags + +To optional feature flags. + +```toml +[dependencies.crossterm] +version = "0.17" +features = ["event-stream"] +``` + +| Feature | Description | +| :----- | :----- | +| `event-stream` | `futures::Stream` producing `Result`.| + +### Dependency Justification + +| Dependency | Used for | Included | +| :----- | :----- | :----- +| `bitflags` | `KeyModifiers`, those are differ based on input.| always +| `lazy_static` | original console color, original terminal mode, saved cursor position, supports ANSI on windows, single event reader per application.| always +| `parking_lot` | used for an RW LOCK. | always +| `libc` | UNIX terminal_size/raw modes/set_title and several other lowlevel functionality. | UNIX only +| `Mio` | event readiness polling, waking up poller | UNIX only +| `signal-hook`| signalhook is used to handle terminal resize SIGNAL with Mio. | UNIX only +| `winapi`| Used for low-level windows system calls which ANSI codes can't replace| windows only +| `futures`| Can be used to for async stream of events | only with a feature flag +| `serde`| Se/dese/realizing of events | only with a feature flag + + +### Other Resources + +- [API documentation](https://docs.rs/crossterm/) +- [Deprecated examples repository](https://github.com/crossterm-rs/examples) + +## Used By + +- [Broot](https://dystroy.org/broot/) +- [Cursive](https://github.com/gyscos/Cursive) +- [TUI](https://github.com/fdehau/tui-rs) +- [Rust-sloth](https://github.com/ecumene/rust-sloth) + +## Contributing + +We highly appreciate when anyone contributes to this crate. Before you do, please, +read the [Contributing](docs/CONTRIBUTING.md) guidelines. + +## Authors + +* **Timon Post** - *Project Owner & creator* + +## License + +This project, `crossterm` and all its sub-crates: `crossterm_screen`, `crossterm_cursor`, `crossterm_style`, +`crossterm_input`, `crossterm_terminal`, `crossterm_winapi`, `crossterm_utils` are licensed under the MIT +License - see the [LICENSE](https://github.com/crossterm-rs/crossterm/blob/master/LICENSE) file for details. + +[s1]: https://img.shields.io/crates/v/crossterm.svg +[l1]: https://crates.io/crates/crossterm + +[s2]: https://img.shields.io/badge/license-MIT-blue.svg +[l2]: ./LICENSE + +[s3]: https://docs.rs/crossterm/badge.svg +[l3]: https://docs.rs/crossterm/ + +[s3]: https://docs.rs/crossterm/badge.svg +[l3]: https://docs.rs/crossterm/ + +[s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord +[l5]: https://discord.gg/K4nyTDB + +[s6]: https://tokei.rs/b1/github/crossterm-rs/crossterm?category=code +[s7]: https://travis-ci.org/crossterm-rs/crossterm.svg?branch=master diff --git a/examples/event-stream-async-std.rs b/examples/event-stream-async-std.rs index f06f5dcf4..327a2175b 100644 --- a/examples/event-stream-async-std.rs +++ b/examples/event-stream-async-std.rs @@ -1,71 +1,71 @@ -//! Demonstrates how to read events asynchronously with async-std. -//! -//! cargo run --features="event-stream" --example event-stream-async-std - -use std::{ - io::{stdout, Write}, - time::Duration, -}; - -use futures_timer::Delay; -use futures_util::{future::FutureExt, select, StreamExt}; - -use crossterm::{ - cursor::position, - event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode}, - Result, -}; - -const HELP: &str = r#"EventStream based on futures_util::stream::Stream with async-std - - Keyboard, mouse and terminal resize events enabled - - Prints "." every second if there's no event - - Hit "c" to print current cursor position - - Use Esc to quit -"#; - -async fn print_events() { - let mut reader = EventStream::new(); - - loop { - let mut delay = Delay::new(Duration::from_millis(1_000)).fuse(); - let mut event = reader.next().fuse(); - - select! { - _ = delay => { println!(".\r"); }, - maybe_event = event => { - match maybe_event { - Some(Ok(event)) => { - println!("Event::{:?}\r", event); - - if event == Event::Key(KeyCode::Char('c').into()) { - println!("Cursor position: {:?}\r", position()); - } - - if event == Event::Key(KeyCode::Esc.into()) { - break; - } - } - Some(Err(e)) => println!("Error: {:?}\r", e), - None => break, - } - } - }; - } -} - -fn main() -> Result<()> { - println!("{}", HELP); - - enable_raw_mode()?; - - let mut stdout = stdout(); - execute!(stdout, EnableMouseCapture)?; - - async_std::task::block_on(print_events()); - - execute!(stdout, DisableMouseCapture)?; - - disable_raw_mode() -} +//! Demonstrates how to read events asynchronously with async-std. +//! +//! cargo run --features="event-stream" --example event-stream-async-std + +use std::{ + io::{stdout, Write}, + time::Duration, +}; + +use futures_timer::Delay; +use futures_util::{future::FutureExt, select, StreamExt}; + +use crossterm::{ + cursor::position, + event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode}, + execute, + terminal::{disable_raw_mode, enable_raw_mode}, + Result, +}; + +const HELP: &str = r#"EventStream based on futures_util::stream::Stream with async-std + - Keyboard, mouse and terminal resize events enabled + - Prints "." every second if there's no event + - Hit "c" to print current cursor position + - Use Esc to quit +"#; + +async fn print_events() { + let mut reader = EventStream::new(); + + loop { + let mut delay = Delay::new(Duration::from_millis(1_000)).fuse(); + let mut event = reader.next().fuse(); + + select! { + _ = delay => { println!(".\r"); }, + maybe_event = event => { + match maybe_event { + Some(Ok(event)) => { + println!("Event::{:?}\r", event); + + if event == Event::Key(KeyCode::Char('c').into()) { + println!("Cursor position: {:?}\r", position()); + } + + if event == Event::Key(KeyCode::Esc.into()) { + break; + } + } + Some(Err(e)) => println!("Error: {:?}\r", e), + None => break, + } + } + }; + } +} + +fn main() -> Result<()> { + println!("{}", HELP); + + enable_raw_mode()?; + + let mut stdout = stdout(); + execute!(stdout, EnableMouseCapture)?; + + async_std::task::block_on(print_events()); + + execute!(stdout, DisableMouseCapture)?; + + disable_raw_mode() +} diff --git a/examples/event-stream-tokio.rs b/examples/event-stream-tokio.rs index 3f3525d48..91cab9a5d 100644 --- a/examples/event-stream-tokio.rs +++ b/examples/event-stream-tokio.rs @@ -1,72 +1,72 @@ -//! Demonstrates how to read events asynchronously with tokio. -//! -//! cargo run --features="event-stream" --example event-stream-tokio - -use std::{ - io::{stdout, Write}, - time::Duration, -}; - -use futures_timer::Delay; -use futures_util::{future::FutureExt, select, StreamExt}; - -use crossterm::{ - cursor::position, - event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode}, - execute, - terminal::{disable_raw_mode, enable_raw_mode}, - Result, -}; - -const HELP: &str = r#"EventStream based on futures_util::Stream with tokio - - Keyboard, mouse and terminal resize events enabled - - Prints "." every second if there's no event - - Hit "c" to print current cursor position - - Use Esc to quit -"#; - -async fn print_events() { - let mut reader = EventStream::new(); - - loop { - let mut delay = Delay::new(Duration::from_millis(1_000)).fuse(); - let mut event = reader.next().fuse(); - - select! { - _ = delay => { println!(".\r"); }, - maybe_event = event => { - match maybe_event { - Some(Ok(event)) => { - println!("Event::{:?}\r", event); - - if event == Event::Key(KeyCode::Char('c').into()) { - println!("Cursor position: {:?}\r", position()); - } - - if event == Event::Key(KeyCode::Esc.into()) { - break; - } - } - Some(Err(e)) => println!("Error: {:?}\r", e), - None => break, - } - } - }; - } -} - -#[tokio::main] -async fn main() -> Result<()> { - println!("{}", HELP); - - enable_raw_mode()?; - - let mut stdout = stdout(); - execute!(stdout, EnableMouseCapture)?; - - print_events().await; - - execute!(stdout, DisableMouseCapture)?; - - disable_raw_mode() -} +//! Demonstrates how to read events asynchronously with tokio. +//! +//! cargo run --features="event-stream" --example event-stream-tokio + +use std::{ + io::{stdout, Write}, + time::Duration, +}; + +use futures_timer::Delay; +use futures_util::{future::FutureExt, select, StreamExt}; + +use crossterm::{ + cursor::position, + event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode}, + execute, + terminal::{disable_raw_mode, enable_raw_mode}, + Result, +}; + +const HELP: &str = r#"EventStream based on futures_util::Stream with tokio + - Keyboard, mouse and terminal resize events enabled + - Prints "." every second if there's no event + - Hit "c" to print current cursor position + - Use Esc to quit +"#; + +async fn print_events() { + let mut reader = EventStream::new(); + + loop { + let mut delay = Delay::new(Duration::from_millis(1_000)).fuse(); + let mut event = reader.next().fuse(); + + select! { + _ = delay => { println!(".\r"); }, + maybe_event = event => { + match maybe_event { + Some(Ok(event)) => { + println!("Event::{:?}\r", event); + + if event == Event::Key(KeyCode::Char('c').into()) { + println!("Cursor position: {:?}\r", position()); + } + + if event == Event::Key(KeyCode::Esc.into()) { + break; + } + } + Some(Err(e)) => println!("Error: {:?}\r", e), + None => break, + } + } + }; + } +} + +#[tokio::main] +async fn main() -> Result<()> { + println!("{}", HELP); + + enable_raw_mode()?; + + let mut stdout = stdout(); + execute!(stdout, EnableMouseCapture)?; + + print_events().await; + + execute!(stdout, DisableMouseCapture)?; + + disable_raw_mode() +} diff --git a/src/event/read.rs b/src/event/read.rs index ec2d895df..4540a8713 100644 --- a/src/event/read.rs +++ b/src/event/read.rs @@ -1,461 +1,461 @@ -use std::{collections::vec_deque::VecDeque, io, time::Duration}; - -use crate::ErrorKind; - -#[cfg(unix)] -use super::source::unix::UnixInternalEventSource; -#[cfg(windows)] -use super::source::windows::WindowsEventSource; -#[cfg(feature = "event-stream")] -use super::sys::Waker; -use super::{filter::Filter, source::EventSource, timeout::PollTimeout, InternalEvent, Result}; - -/// Can be used to read `InternalEvent`s. -pub(crate) struct InternalEventReader { - events: VecDeque, - source: Option>, - skipped_events: Vec, -} - -impl Default for InternalEventReader { - fn default() -> Self { - #[cfg(windows)] - let source = WindowsEventSource::new(); - #[cfg(unix)] - let source = UnixInternalEventSource::new(); - - let source = source.ok().map(|x| Box::new(x) as Box); - - InternalEventReader { - source, - events: VecDeque::with_capacity(32), - skipped_events: Vec::with_capacity(32), - } - } -} - -impl InternalEventReader { - /// Returns a `Waker` allowing to wake/force the `poll` method to return `Ok(false)`. - #[cfg(feature = "event-stream")] - pub(crate) fn waker(&self) -> Waker { - self.source.as_ref().expect("reader source not set").waker() - } - - pub(crate) fn poll(&mut self, timeout: Option, filter: &F) -> Result - where - F: Filter, - { - for event in &self.events { - if filter.eval(&event) { - return Ok(true); - } - } - - let event_source = match self.source.as_mut() { - Some(source) => source, - None => { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Failed to initialize input reader", - ) - .into()) - } - }; - - let poll_timeout = PollTimeout::new(timeout); - - loop { - let maybe_event = match event_source.try_read(timeout) { - Ok(None) => None, - Ok(Some(event)) => { - if filter.eval(&event) { - Some(event) - } else { - self.skipped_events.push(event); - None - } - } - Err(ErrorKind::IoError(e)) => { - if e.kind() == io::ErrorKind::Interrupted { - return Ok(false); - } - - return Err(ErrorKind::IoError(e)); - } - Err(e) => return Err(e), - }; - - if poll_timeout.elapsed() || maybe_event.is_some() { - self.events.extend(self.skipped_events.drain(..)); - - if let Some(event) = maybe_event { - self.events.push_front(event); - return Ok(true); - } - - return Ok(false); - } - } - } - - pub(crate) fn read(&mut self, filter: &F) -> Result - where - F: Filter, - { - let mut skipped_events = VecDeque::new(); - - loop { - while let Some(event) = self.events.pop_front() { - if filter.eval(&event) { - while let Some(event) = skipped_events.pop_front() { - self.events.push_back(event); - } - - return Ok(event); - } else { - // We can not directly write events back to `self.events`. - // If we did, we would put our self's into an endless loop - // that would enqueue -> dequeue -> enqueue etc. - // This happens because `poll` in this function will always return true if there are events in it's. - // And because we just put the non-fulfilling event there this is going to be the case. - // Instead we can store them into the temporary buffer, - // and then when the filter is fulfilled write all events back in order. - skipped_events.push_back(event); - } - } - - let _ = self.poll(None, filter)?; - } - } -} - -#[cfg(test)] -mod tests { - use std::{collections::VecDeque, time::Duration}; - - use crate::ErrorKind; - - #[cfg(unix)] - use super::super::filter::CursorPositionFilter; - use super::{ - super::{filter::InternalEventFilter, Event}, - EventSource, InternalEvent, InternalEventReader, - }; - - #[test] - fn test_poll_fails_without_event_source() { - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert!(reader.poll(None, &InternalEventFilter).is_err()); - assert!(reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .is_err()); - assert!(reader - .poll(Some(Duration::from_secs(10)), &InternalEventFilter) - .is_err()); - } - - #[test] - fn test_poll_returns_true_for_matching_event_in_queue_at_front() { - let mut reader = InternalEventReader { - events: vec![InternalEvent::Event(Event::Resize(10, 10))].into(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert!(reader.poll(None, &InternalEventFilter).unwrap()); - } - - #[test] - #[cfg(unix)] - fn test_poll_returns_true_for_matching_event_in_queue_at_back() { - let mut reader = InternalEventReader { - events: vec![ - InternalEvent::Event(Event::Resize(10, 10)), - InternalEvent::CursorPosition(10, 20), - ] - .into(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert!(reader.poll(None, &CursorPositionFilter).unwrap()); - } - - #[test] - fn test_read_returns_matching_event_in_queue_at_front() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let mut reader = InternalEventReader { - events: vec![EVENT].into(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - } - - #[test] - #[cfg(unix)] - fn test_read_returns_matching_event_in_queue_at_back() { - const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20); - - let mut reader = InternalEventReader { - events: vec![InternalEvent::Event(Event::Resize(10, 10)), CURSOR_EVENT].into(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT); - } - - #[test] - #[cfg(unix)] - fn test_read_does_not_consume_skipped_event() { - const SKIPPED_EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20); - - let mut reader = InternalEventReader { - events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(), - source: None, - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT); - } - - #[test] - fn test_poll_timeouts_if_source_has_no_events() { - let source = FakeSource::default(); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert!(!reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .unwrap()); - } - - #[test] - fn test_poll_returns_true_if_source_has_at_least_one_event() { - let source = FakeSource::with_events(&[InternalEvent::Event(Event::Resize(10, 10))]); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert!(reader.poll(None, &InternalEventFilter).unwrap()); - assert!(reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .unwrap()); - } - - #[test] - fn test_reads_returns_event_if_source_has_at_least_one_event() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let source = FakeSource::with_events(&[EVENT]); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - } - - #[test] - fn test_read_returns_events_if_source_has_events() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - } - - #[test] - fn test_poll_returns_false_after_all_source_events_are_consumed() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert!(!reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .unwrap()); - } - - #[test] - fn test_poll_propagates_error() { - let source = FakeSource::with_error(ErrorKind::ResizingTerminalFailure("Foo".to_string())); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!( - reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .err() - .map(|e| format!("{:?}", &e)), - Some(format!( - "{:?}", - ErrorKind::ResizingTerminalFailure("Foo".to_string()) - )) - ); - } - - #[test] - fn test_read_propagates_error() { - let source = FakeSource::with_error(ErrorKind::ResizingTerminalFailure("Foo".to_string())); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!( - reader - .read(&InternalEventFilter) - .err() - .map(|e| format!("{:?}", &e)), - Some(format!( - "{:?}", - ErrorKind::ResizingTerminalFailure("Foo".to_string()) - )) - ); - } - - #[test] - fn test_poll_continues_after_error() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let source = FakeSource::new( - &[EVENT, EVENT], - ErrorKind::ResizingTerminalFailure("Foo".to_string()), - ); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert!(reader.read(&InternalEventFilter).is_err()); - assert!(reader - .poll(Some(Duration::from_secs(0)), &InternalEventFilter) - .unwrap()); - } - - #[test] - fn test_read_continues_after_error() { - const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); - - let source = FakeSource::new( - &[EVENT, EVENT], - ErrorKind::ResizingTerminalFailure("Foo".to_string()), - ); - - let mut reader = InternalEventReader { - events: VecDeque::new(), - source: Some(Box::new(source)), - skipped_events: Vec::with_capacity(32), - }; - - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - assert!(reader.read(&InternalEventFilter).is_err()); - assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); - } - - #[derive(Default)] - struct FakeSource { - events: VecDeque, - error: Option, - } - - impl FakeSource { - fn new(events: &[InternalEvent], error: ErrorKind) -> FakeSource { - FakeSource { - events: events.to_vec().into(), - error: Some(error), - } - } - - fn with_events(events: &[InternalEvent]) -> FakeSource { - FakeSource { - events: events.to_vec().into(), - error: None, - } - } - - fn with_error(error: ErrorKind) -> FakeSource { - FakeSource { - events: VecDeque::new(), - error: Some(error), - } - } - } - - impl EventSource for FakeSource { - fn try_read( - &mut self, - _timeout: Option, - ) -> Result, ErrorKind> { - // Return error if set in case there's just one remaining event - if self.events.len() == 1 { - if let Some(error) = self.error.take() { - return Err(error); - } - } - - // Return all events from the queue - if let Some(event) = self.events.pop_front() { - return Ok(Some(event)); - } - - // Return error if there're no more events - if let Some(error) = self.error.take() { - return Err(error); - } - - // Timeout - Ok(None) - } - - #[cfg(feature = "event-stream")] - fn waker(&self) -> super::super::sys::Waker { - unimplemented!(); - } - } -} +use std::{collections::vec_deque::VecDeque, io, time::Duration}; + +use crate::ErrorKind; + +#[cfg(unix)] +use super::source::unix::UnixInternalEventSource; +#[cfg(windows)] +use super::source::windows::WindowsEventSource; +#[cfg(feature = "event-stream")] +use super::sys::Waker; +use super::{filter::Filter, source::EventSource, timeout::PollTimeout, InternalEvent, Result}; + +/// Can be used to read `InternalEvent`s. +pub(crate) struct InternalEventReader { + events: VecDeque, + source: Option>, + skipped_events: Vec, +} + +impl Default for InternalEventReader { + fn default() -> Self { + #[cfg(windows)] + let source = WindowsEventSource::new(); + #[cfg(unix)] + let source = UnixInternalEventSource::new(); + + let source = source.ok().map(|x| Box::new(x) as Box); + + InternalEventReader { + source, + events: VecDeque::with_capacity(32), + skipped_events: Vec::with_capacity(32), + } + } +} + +impl InternalEventReader { + /// Returns a `Waker` allowing to wake/force the `poll` method to return `Ok(false)`. + #[cfg(feature = "event-stream")] + pub(crate) fn waker(&self) -> Waker { + self.source.as_ref().expect("reader source not set").waker() + } + + pub(crate) fn poll(&mut self, timeout: Option, filter: &F) -> Result + where + F: Filter, + { + for event in &self.events { + if filter.eval(&event) { + return Ok(true); + } + } + + let event_source = match self.source.as_mut() { + Some(source) => source, + None => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to initialize input reader", + ) + .into()) + } + }; + + let poll_timeout = PollTimeout::new(timeout); + + loop { + let maybe_event = match event_source.try_read(timeout) { + Ok(None) => None, + Ok(Some(event)) => { + if filter.eval(&event) { + Some(event) + } else { + self.skipped_events.push(event); + None + } + } + Err(ErrorKind::IoError(e)) => { + if e.kind() == io::ErrorKind::Interrupted { + return Ok(false); + } + + return Err(ErrorKind::IoError(e)); + } + Err(e) => return Err(e), + }; + + if poll_timeout.elapsed() || maybe_event.is_some() { + self.events.extend(self.skipped_events.drain(..)); + + if let Some(event) = maybe_event { + self.events.push_front(event); + return Ok(true); + } + + return Ok(false); + } + } + } + + pub(crate) fn read(&mut self, filter: &F) -> Result + where + F: Filter, + { + let mut skipped_events = VecDeque::new(); + + loop { + while let Some(event) = self.events.pop_front() { + if filter.eval(&event) { + while let Some(event) = skipped_events.pop_front() { + self.events.push_back(event); + } + + return Ok(event); + } else { + // We can not directly write events back to `self.events`. + // If we did, we would put our self's into an endless loop + // that would enqueue -> dequeue -> enqueue etc. + // This happens because `poll` in this function will always return true if there are events in it's. + // And because we just put the non-fulfilling event there this is going to be the case. + // Instead we can store them into the temporary buffer, + // and then when the filter is fulfilled write all events back in order. + skipped_events.push_back(event); + } + } + + let _ = self.poll(None, filter)?; + } + } +} + +#[cfg(test)] +mod tests { + use std::{collections::VecDeque, time::Duration}; + + use crate::ErrorKind; + + #[cfg(unix)] + use super::super::filter::CursorPositionFilter; + use super::{ + super::{filter::InternalEventFilter, Event}, + EventSource, InternalEvent, InternalEventReader, + }; + + #[test] + fn test_poll_fails_without_event_source() { + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert!(reader.poll(None, &InternalEventFilter).is_err()); + assert!(reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .is_err()); + assert!(reader + .poll(Some(Duration::from_secs(10)), &InternalEventFilter) + .is_err()); + } + + #[test] + fn test_poll_returns_true_for_matching_event_in_queue_at_front() { + let mut reader = InternalEventReader { + events: vec![InternalEvent::Event(Event::Resize(10, 10))].into(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert!(reader.poll(None, &InternalEventFilter).unwrap()); + } + + #[test] + #[cfg(unix)] + fn test_poll_returns_true_for_matching_event_in_queue_at_back() { + let mut reader = InternalEventReader { + events: vec![ + InternalEvent::Event(Event::Resize(10, 10)), + InternalEvent::CursorPosition(10, 20), + ] + .into(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert!(reader.poll(None, &CursorPositionFilter).unwrap()); + } + + #[test] + fn test_read_returns_matching_event_in_queue_at_front() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let mut reader = InternalEventReader { + events: vec![EVENT].into(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + } + + #[test] + #[cfg(unix)] + fn test_read_returns_matching_event_in_queue_at_back() { + const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20); + + let mut reader = InternalEventReader { + events: vec![InternalEvent::Event(Event::Resize(10, 10)), CURSOR_EVENT].into(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT); + } + + #[test] + #[cfg(unix)] + fn test_read_does_not_consume_skipped_event() { + const SKIPPED_EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + const CURSOR_EVENT: InternalEvent = InternalEvent::CursorPosition(10, 20); + + let mut reader = InternalEventReader { + events: vec![SKIPPED_EVENT, CURSOR_EVENT].into(), + source: None, + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&CursorPositionFilter).unwrap(), CURSOR_EVENT); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), SKIPPED_EVENT); + } + + #[test] + fn test_poll_timeouts_if_source_has_no_events() { + let source = FakeSource::default(); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert!(!reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .unwrap()); + } + + #[test] + fn test_poll_returns_true_if_source_has_at_least_one_event() { + let source = FakeSource::with_events(&[InternalEvent::Event(Event::Resize(10, 10))]); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert!(reader.poll(None, &InternalEventFilter).unwrap()); + assert!(reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .unwrap()); + } + + #[test] + fn test_reads_returns_event_if_source_has_at_least_one_event() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let source = FakeSource::with_events(&[EVENT]); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + } + + #[test] + fn test_read_returns_events_if_source_has_events() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + } + + #[test] + fn test_poll_returns_false_after_all_source_events_are_consumed() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let source = FakeSource::with_events(&[EVENT, EVENT, EVENT]); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert!(!reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .unwrap()); + } + + #[test] + fn test_poll_propagates_error() { + let source = FakeSource::with_error(ErrorKind::ResizingTerminalFailure("Foo".to_string())); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!( + reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .err() + .map(|e| format!("{:?}", &e)), + Some(format!( + "{:?}", + ErrorKind::ResizingTerminalFailure("Foo".to_string()) + )) + ); + } + + #[test] + fn test_read_propagates_error() { + let source = FakeSource::with_error(ErrorKind::ResizingTerminalFailure("Foo".to_string())); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!( + reader + .read(&InternalEventFilter) + .err() + .map(|e| format!("{:?}", &e)), + Some(format!( + "{:?}", + ErrorKind::ResizingTerminalFailure("Foo".to_string()) + )) + ); + } + + #[test] + fn test_poll_continues_after_error() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let source = FakeSource::new( + &[EVENT, EVENT], + ErrorKind::ResizingTerminalFailure("Foo".to_string()), + ); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert!(reader.read(&InternalEventFilter).is_err()); + assert!(reader + .poll(Some(Duration::from_secs(0)), &InternalEventFilter) + .unwrap()); + } + + #[test] + fn test_read_continues_after_error() { + const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); + + let source = FakeSource::new( + &[EVENT, EVENT], + ErrorKind::ResizingTerminalFailure("Foo".to_string()), + ); + + let mut reader = InternalEventReader { + events: VecDeque::new(), + source: Some(Box::new(source)), + skipped_events: Vec::with_capacity(32), + }; + + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + assert!(reader.read(&InternalEventFilter).is_err()); + assert_eq!(reader.read(&InternalEventFilter).unwrap(), EVENT); + } + + #[derive(Default)] + struct FakeSource { + events: VecDeque, + error: Option, + } + + impl FakeSource { + fn new(events: &[InternalEvent], error: ErrorKind) -> FakeSource { + FakeSource { + events: events.to_vec().into(), + error: Some(error), + } + } + + fn with_events(events: &[InternalEvent]) -> FakeSource { + FakeSource { + events: events.to_vec().into(), + error: None, + } + } + + fn with_error(error: ErrorKind) -> FakeSource { + FakeSource { + events: VecDeque::new(), + error: Some(error), + } + } + } + + impl EventSource for FakeSource { + fn try_read( + &mut self, + _timeout: Option, + ) -> Result, ErrorKind> { + // Return error if set in case there's just one remaining event + if self.events.len() == 1 { + if let Some(error) = self.error.take() { + return Err(error); + } + } + + // Return all events from the queue + if let Some(event) = self.events.pop_front() { + return Ok(Some(event)); + } + + // Return error if there're no more events + if let Some(error) = self.error.take() { + return Err(error); + } + + // Timeout + Ok(None) + } + + #[cfg(feature = "event-stream")] + fn waker(&self) -> super::super::sys::Waker { + unimplemented!(); + } + } +} diff --git a/src/event/source/unix.rs b/src/event/source/unix.rs index 9b3c5cea7..e89e56ebe 100644 --- a/src/event/source/unix.rs +++ b/src/event/source/unix.rs @@ -1,233 +1,236 @@ -use mio::{unix::SourceFd, Events, Interest, Poll, Token}; -use signal_hook::iterator::Signals; -use std::{collections::VecDeque, io, time::Duration}; - -use crate::{ErrorKind, Result}; - -#[cfg(feature = "event-stream")] -use super::super::sys::Waker; -use super::super::{ - source::EventSource, - sys::unix::{ - file_descriptor::{tty_fd, FileDesc}, - parse::parse_event, - }, - timeout::PollTimeout, - Event, InternalEvent, -}; - -// Tokens to identify file descriptor -const TTY_TOKEN: Token = Token(0); -const SIGNAL_TOKEN: Token = Token(1); -#[cfg(feature = "event-stream")] -const WAKE_TOKEN: Token = Token(2); - -// I (@zrzka) wasn't able to read more than 1_022 bytes when testing -// reading on macOS/Linux -> we don't need bigger buffer and 1k of bytes -// is enough. -const TTY_BUFFER_SIZE: usize = 1_204; - -pub(crate) struct UnixInternalEventSource { - poll: Poll, - events: Events, - parser: Parser, - tty_buffer: [u8; TTY_BUFFER_SIZE], - tty_fd: FileDesc, - signals: Signals, - #[cfg(feature = "event-stream")] - waker: Waker, -} - -impl UnixInternalEventSource { - pub fn new() -> Result { - Ok(UnixInternalEventSource::from_file_descriptor(tty_fd()?)?) - } - - pub(crate) fn from_file_descriptor(input_fd: FileDesc) -> Result { - let poll = Poll::new()?; - let registry = poll.registry(); - - let tty_raw_fd = input_fd.raw_fd(); - let mut tty_ev = SourceFd(&tty_raw_fd); - registry.register(&mut tty_ev, TTY_TOKEN, Interest::READABLE)?; - - let mut signals = Signals::new(&[signal_hook::SIGWINCH])?; - registry.register(&mut signals, SIGNAL_TOKEN, Interest::READABLE)?; - - #[cfg(feature = "event-stream")] - let waker = Waker::new(registry, WAKE_TOKEN)?; - - Ok(UnixInternalEventSource { - poll, - events: Events::with_capacity(3), - parser: Parser::default(), - tty_buffer: [0u8; TTY_BUFFER_SIZE], - tty_fd: input_fd, - signals, - #[cfg(feature = "event-stream")] - waker, - }) - } -} - -impl EventSource for UnixInternalEventSource { - fn try_read(&mut self, timeout: Option) -> Result> { - if let Some(event) = self.parser.next() { - return Ok(Some(event)); - } - - let timeout = PollTimeout::new(timeout); - - loop { - self.poll.poll(&mut self.events, timeout.leftover())?; - - if self.events.is_empty() { - // No readiness events = timeout - return Ok(None); - } - - for token in self.events.iter().map(|x| x.token()) { - match token { - TTY_TOKEN => { - loop { - match self.tty_fd.read(&mut self.tty_buffer, TTY_BUFFER_SIZE) { - Ok(read_count) => { - if read_count > 0 { - self.parser.advance( - &self.tty_buffer[..read_count], - read_count == TTY_BUFFER_SIZE, - ); - } - } - Err(ErrorKind::IoError(e)) => { - // No more data to read at the moment. We will receive another event - if e.kind() == io::ErrorKind::WouldBlock { - break; - } - // once more data is available to read. - else if e.kind() == io::ErrorKind::Interrupted { - continue; - } - } - Err(e) => return Err(e), - }; - - if let Some(event) = self.parser.next() { - return Ok(Some(event)); - } - } - } - SIGNAL_TOKEN => { - for signal in &self.signals { - match signal as libc::c_int { - signal_hook::SIGWINCH => { - // TODO Should we remove tput? - // - // This can take a really long time, because terminal::size can - // launch new process (tput) and then it parses its output. It's - // not a really long time from the absolute time point of view, but - // it's a really long time from the mio, async-std/tokio executor, ... - // point of view. - let new_size = crate::terminal::size()?; - return Ok(Some(InternalEvent::Event(Event::Resize( - new_size.0, new_size.1, - )))); - } - _ => unreachable!("Synchronize signal registration & handling"), - }; - } - } - #[cfg(feature = "event-stream")] - WAKE_TOKEN => { - return Err(std::io::Error::new( - std::io::ErrorKind::Interrupted, - "Poll operation was woken up by `Waker::wake`", - ) - .into()); - } - _ => unreachable!("Synchronize Evented handle registration & token handling"), - } - } - - // Processing above can take some time, check if timeout expired - if timeout.elapsed() { - return Ok(None); - } - } - } - - #[cfg(feature = "event-stream")] - fn waker(&self) -> Waker { - self.waker.clone() - } -} - -// -// Following `Parser` structure exists for two reasons: -// -// * mimick anes Parser interface -// * move the advancing, parsing, ... stuff out of the `try_read` method -// -#[derive(Debug)] -struct Parser { - buffer: Vec, - internal_events: VecDeque, -} - -impl Default for Parser { - fn default() -> Self { - Parser { - // This buffer is used for -> 1 <- ANSI escape sequence. Are we - // aware of any ANSI escape sequence that is bigger? Can we make - // it smaller? - // - // Probably not worth spending more time on this as "there's a plan" - // to use the anes crate parser. - buffer: Vec::with_capacity(256), - // TTY_BUFFER_SIZE is 1_024 bytes. How many ANSI escape sequences can - // fit? What is an average sequence length? Let's guess here - // and say that the average ANSI escape sequence length is 8 bytes. Thus - // the buffer size should be 1024/8=128 to avoid additional allocations - // when processing large amounts of data. - // - // There's no need to make it bigger, because when you look at the `try_read` - // method implementation, all events are consumed before the next TTY_BUFFER - // is processed -> events pushed. - internal_events: VecDeque::with_capacity(128), - } - } -} - -impl Parser { - fn advance(&mut self, buffer: &[u8], more: bool) { - for (idx, byte) in buffer.iter().enumerate() { - let more = idx + 1 < buffer.len() || more; - - self.buffer.push(*byte); - - match parse_event(&self.buffer, more) { - Ok(Some(ie)) => { - self.internal_events.push_back(ie); - self.buffer.clear(); - } - Ok(None) => { - // Event can't be parsed, because we don't have enough bytes for - // the current sequence. Keep the buffer and process next bytes. - } - Err(_) => { - // Event can't be parsed (not enough parameters, parameter is not a number, ...). - // Clear the buffer and continue with another sequence. - self.buffer.clear(); - } - } - } - } -} - -impl Iterator for Parser { - type Item = InternalEvent; - - fn next(&mut self) -> Option { - self.internal_events.pop_front() - } -} +use mio::{unix::SourceFd, Events, Interest, Poll, Token}; +use signal_hook::iterator::Signals; +use std::{collections::VecDeque, io, time::Duration}; + +use crate::{ErrorKind, Result}; + +#[cfg(feature = "event-stream")] +use super::super::sys::Waker; +use super::super::{ + source::EventSource, + sys::unix::{ + file_descriptor::{tty_fd, FileDesc}, + parse::parse_event, + }, + timeout::PollTimeout, + Event, InternalEvent, +}; + +// Tokens to identify file descriptor +const TTY_TOKEN: Token = Token(0); +const SIGNAL_TOKEN: Token = Token(1); +#[cfg(feature = "event-stream")] +const WAKE_TOKEN: Token = Token(2); + +// I (@zrzka) wasn't able to read more than 1_022 bytes when testing +// reading on macOS/Linux -> we don't need bigger buffer and 1k of bytes +// is enough. +const TTY_BUFFER_SIZE: usize = 1_204; + +pub(crate) struct UnixInternalEventSource { + poll: Poll, + events: Events, + parser: Parser, + tty_buffer: [u8; TTY_BUFFER_SIZE], + tty_fd: FileDesc, + signals: Signals, + #[cfg(feature = "event-stream")] + waker: Waker, +} + +impl UnixInternalEventSource { + pub fn new() -> Result { + Ok(UnixInternalEventSource::from_file_descriptor(tty_fd()?)?) + } + + pub(crate) fn from_file_descriptor(input_fd: FileDesc) -> Result { + let poll = Poll::new()?; + let registry = poll.registry(); + + let tty_raw_fd = input_fd.raw_fd(); + let mut tty_ev = SourceFd(&tty_raw_fd); + registry.register(&mut tty_ev, TTY_TOKEN, Interest::READABLE)?; + + let mut signals = Signals::new(&[signal_hook::SIGWINCH])?; + registry.register(&mut signals, SIGNAL_TOKEN, Interest::READABLE)?; + + #[cfg(feature = "event-stream")] + let waker = Waker::new(registry, WAKE_TOKEN)?; + + Ok(UnixInternalEventSource { + poll, + events: Events::with_capacity(3), + parser: Parser::default(), + tty_buffer: [0u8; TTY_BUFFER_SIZE], + tty_fd: input_fd, + signals, + #[cfg(feature = "event-stream")] + waker, + }) + } +} + +impl EventSource for UnixInternalEventSource { + fn try_read(&mut self, timeout: Option) -> Result> { + if let Some(event) = self.parser.next() { + return Ok(Some(event)); + } + + let timeout = PollTimeout::new(timeout); + + loop { + match self.poll.poll(&mut self.events, timeout.leftover()) { + Ok(_) => {} + Err(e) => if e.kind() == io::ErrorKind::Interrupted {continue} else { return Err(ErrorKind::IoError(e)) } + }; + + if self.events.is_empty() { + // No readiness events = timeout + return Ok(None); + } + + for token in self.events.iter().map(|x| x.token()) { + match token { + TTY_TOKEN => { + loop { + match self.tty_fd.read(&mut self.tty_buffer, TTY_BUFFER_SIZE) { + Ok(read_count) => { + if read_count > 0 { + self.parser.advance( + &self.tty_buffer[..read_count], + read_count == TTY_BUFFER_SIZE, + ); + } + } + Err(ErrorKind::IoError(e)) => { + // No more data to read at the moment. We will receive another event + if e.kind() == io::ErrorKind::WouldBlock { + break; + } + // once more data is available to read. + else if e.kind() == io::ErrorKind::Interrupted { + continue; + } + } + Err(e) => return Err(e), + }; + + if let Some(event) = self.parser.next() { + return Ok(Some(event)); + } + } + } + SIGNAL_TOKEN => { + for signal in &self.signals { + match signal as libc::c_int { + signal_hook::SIGWINCH => { + // TODO Should we remove tput? + // + // This can take a really long time, because terminal::size can + // launch new process (tput) and then it parses its output. It's + // not a really long time from the absolute time point of view, but + // it's a really long time from the mio, async-std/tokio executor, ... + // point of view. + let new_size = crate::terminal::size()?; + return Ok(Some(InternalEvent::Event(Event::Resize( + new_size.0, new_size.1, + )))); + } + _ => unreachable!("Synchronize signal registration & handling"), + }; + } + } + #[cfg(feature = "event-stream")] + WAKE_TOKEN => { + return Err(std::io::Error::new( + std::io::ErrorKind::Interrupted, + "Poll operation was woken up by `Waker::wake`", + ) + .into()); + } + _ => unreachable!("Synchronize Evented handle registration & token handling"), + } + } + + // Processing above can take some time, check if timeout expired + if timeout.elapsed() { + return Ok(None); + } + } + } + + #[cfg(feature = "event-stream")] + fn waker(&self) -> Waker { + self.waker.clone() + } +} + +// +// Following `Parser` structure exists for two reasons: +// +// * mimick anes Parser interface +// * move the advancing, parsing, ... stuff out of the `try_read` method +// +#[derive(Debug)] +struct Parser { + buffer: Vec, + internal_events: VecDeque, +} + +impl Default for Parser { + fn default() -> Self { + Parser { + // This buffer is used for -> 1 <- ANSI escape sequence. Are we + // aware of any ANSI escape sequence that is bigger? Can we make + // it smaller? + // + // Probably not worth spending more time on this as "there's a plan" + // to use the anes crate parser. + buffer: Vec::with_capacity(256), + // TTY_BUFFER_SIZE is 1_024 bytes. How many ANSI escape sequences can + // fit? What is an average sequence length? Let's guess here + // and say that the average ANSI escape sequence length is 8 bytes. Thus + // the buffer size should be 1024/8=128 to avoid additional allocations + // when processing large amounts of data. + // + // There's no need to make it bigger, because when you look at the `try_read` + // method implementation, all events are consumed before the next TTY_BUFFER + // is processed -> events pushed. + internal_events: VecDeque::with_capacity(128), + } + } +} + +impl Parser { + fn advance(&mut self, buffer: &[u8], more: bool) { + for (idx, byte) in buffer.iter().enumerate() { + let more = idx + 1 < buffer.len() || more; + + self.buffer.push(*byte); + + match parse_event(&self.buffer, more) { + Ok(Some(ie)) => { + self.internal_events.push_back(ie); + self.buffer.clear(); + } + Ok(None) => { + // Event can't be parsed, because we don't have enough bytes for + // the current sequence. Keep the buffer and process next bytes. + } + Err(_) => { + // Event can't be parsed (not enough parameters, parameter is not a number, ...). + // Clear the buffer and continue with another sequence. + self.buffer.clear(); + } + } + } + } +} + +impl Iterator for Parser { + type Item = InternalEvent; + + fn next(&mut self) -> Option { + self.internal_events.pop_front() + } +} diff --git a/src/event/stream.rs b/src/event/stream.rs index 0d1123ca9..292177e09 100644 --- a/src/event/stream.rs +++ b/src/event/stream.rs @@ -1,127 +1,127 @@ -use std::{ - pin::Pin, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - thread, - time::Duration, -}; - -use futures_util::{ - stream::Stream, - task::{Context, Poll}, -}; - -use crate::Result; - -use super::{ - filter::EventFilter, poll_internal, read_internal, sys::Waker, Event, InternalEvent, - INTERNAL_EVENT_READER, -}; - -/// A stream of `Result`. -/// -/// **This type is not available by default. You have to use the `event-stream` feature flag -/// to make it available.** -/// -/// It implements the [`futures::stream::Stream`](https://docs.rs/futures/0.3.1/futures/stream/trait.Stream.html) -/// trait and allows you to receive `Event`s with [`async-std`](https://crates.io/crates/async-std) -/// or [`tokio`](https://crates.io/crates/tokio) crates. -/// -/// Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder to see how to use -/// it (`event-stream-*`). -#[derive(Debug)] -pub struct EventStream { - poll_internal_waker: Waker, - stream_wake_thread_spawned: Arc, - stream_wake_thread_should_shutdown: Arc, -} - -impl Default for EventStream { - fn default() -> Self { - EventStream { - poll_internal_waker: INTERNAL_EVENT_READER.write().waker(), - stream_wake_thread_spawned: Arc::new(AtomicBool::new(false)), - stream_wake_thread_should_shutdown: Arc::new(AtomicBool::new(false)), - } - } -} - -impl EventStream { - /// Constructs a new instance of `EventStream`. - pub fn new() -> EventStream { - EventStream::default() - } -} - -// Note to future me -// -// We need two wakers in order to implement EventStream correctly. -// -// 1. futures::Stream waker -// -// Stream::poll_next can return Poll::Pending which means that there's no -// event available. We are going to spawn a thread with the -// poll_internal(None, &EventFilter) call. This call blocks until an -// event is available and then we have to wake up the executor with notification -// that the task can be resumed. -// -// 2. poll_internal waker -// -// There's no event available, Poll::Pending was returned, stream waker thread -// is up and sitting in the poll_internal. User wants to drop the EventStream. -// We have to wake up the poll_internal (force it to return Ok(false)) and quit -// the thread before we drop. -impl Stream for EventStream { - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let result = match poll_internal(Some(Duration::from_secs(0)), &EventFilter) { - Ok(true) => match read_internal(&EventFilter) { - Ok(InternalEvent::Event(event)) => Poll::Ready(Some(Ok(event))), - Err(e) => Poll::Ready(Some(Err(e))), - #[cfg(unix)] - _ => unreachable!(), - }, - Ok(false) => { - if !self - .stream_wake_thread_spawned - .compare_and_swap(false, true, Ordering::SeqCst) - { - let stream_waker = cx.waker().clone(); - let stream_wake_thread_spawned = self.stream_wake_thread_spawned.clone(); - let stream_wake_thread_should_shutdown = - self.stream_wake_thread_should_shutdown.clone(); - - stream_wake_thread_should_shutdown.store(false, Ordering::SeqCst); - - thread::spawn(move || { - loop { - if let Ok(true) = poll_internal(None, &EventFilter) { - break; - } - - if stream_wake_thread_should_shutdown.load(Ordering::SeqCst) { - break; - } - } - stream_wake_thread_spawned.store(false, Ordering::SeqCst); - stream_waker.wake(); - }); - } - Poll::Pending - } - Err(e) => Poll::Ready(Some(Err(e))), - }; - result - } -} - -impl Drop for EventStream { - fn drop(&mut self) { - self.stream_wake_thread_should_shutdown - .store(true, Ordering::SeqCst); - let _ = self.poll_internal_waker.wake(); - } -} +use std::{ + pin::Pin, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, +}; + +use futures_util::{ + stream::Stream, + task::{Context, Poll}, +}; + +use crate::Result; + +use super::{ + filter::EventFilter, poll_internal, read_internal, sys::Waker, Event, InternalEvent, + INTERNAL_EVENT_READER, +}; + +/// A stream of `Result`. +/// +/// **This type is not available by default. You have to use the `event-stream` feature flag +/// to make it available.** +/// +/// It implements the [`futures::stream::Stream`](https://docs.rs/futures/0.3.1/futures/stream/trait.Stream.html) +/// trait and allows you to receive `Event`s with [`async-std`](https://crates.io/crates/async-std) +/// or [`tokio`](https://crates.io/crates/tokio) crates. +/// +/// Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder to see how to use +/// it (`event-stream-*`). +#[derive(Debug)] +pub struct EventStream { + poll_internal_waker: Waker, + stream_wake_thread_spawned: Arc, + stream_wake_thread_should_shutdown: Arc, +} + +impl Default for EventStream { + fn default() -> Self { + EventStream { + poll_internal_waker: INTERNAL_EVENT_READER.write().waker(), + stream_wake_thread_spawned: Arc::new(AtomicBool::new(false)), + stream_wake_thread_should_shutdown: Arc::new(AtomicBool::new(false)), + } + } +} + +impl EventStream { + /// Constructs a new instance of `EventStream`. + pub fn new() -> EventStream { + EventStream::default() + } +} + +// Note to future me +// +// We need two wakers in order to implement EventStream correctly. +// +// 1. futures::Stream waker +// +// Stream::poll_next can return Poll::Pending which means that there's no +// event available. We are going to spawn a thread with the +// poll_internal(None, &EventFilter) call. This call blocks until an +// event is available and then we have to wake up the executor with notification +// that the task can be resumed. +// +// 2. poll_internal waker +// +// There's no event available, Poll::Pending was returned, stream waker thread +// is up and sitting in the poll_internal. User wants to drop the EventStream. +// We have to wake up the poll_internal (force it to return Ok(false)) and quit +// the thread before we drop. +impl Stream for EventStream { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let result = match poll_internal(Some(Duration::from_secs(0)), &EventFilter) { + Ok(true) => match read_internal(&EventFilter) { + Ok(InternalEvent::Event(event)) => Poll::Ready(Some(Ok(event))), + Err(e) => Poll::Ready(Some(Err(e))), + #[cfg(unix)] + _ => unreachable!(), + }, + Ok(false) => { + if !self + .stream_wake_thread_spawned + .compare_and_swap(false, true, Ordering::SeqCst) + { + let stream_waker = cx.waker().clone(); + let stream_wake_thread_spawned = self.stream_wake_thread_spawned.clone(); + let stream_wake_thread_should_shutdown = + self.stream_wake_thread_should_shutdown.clone(); + + stream_wake_thread_should_shutdown.store(false, Ordering::SeqCst); + + thread::spawn(move || { + loop { + if let Ok(true) = poll_internal(None, &EventFilter) { + break; + } + + if stream_wake_thread_should_shutdown.load(Ordering::SeqCst) { + break; + } + } + stream_wake_thread_spawned.store(false, Ordering::SeqCst); + stream_waker.wake(); + }); + } + Poll::Pending + } + Err(e) => Poll::Ready(Some(Err(e))), + }; + result + } +} + +impl Drop for EventStream { + fn drop(&mut self) { + self.stream_wake_thread_should_shutdown + .store(true, Ordering::SeqCst); + let _ = self.poll_internal_waker.wake(); + } +} diff --git a/src/event/sys/unix/file_descriptor.rs b/src/event/sys/unix/file_descriptor.rs index c80557123..8415be06c 100644 --- a/src/event/sys/unix/file_descriptor.rs +++ b/src/event/sys/unix/file_descriptor.rs @@ -1,82 +1,82 @@ -use std::{ - fs, io, - os::unix::io::{IntoRawFd, RawFd}, -}; - -use libc::size_t; - -use crate::{ErrorKind, Result}; - -/// A file descriptor wrapper. -/// -/// It allows to retrieve raw file descriptor, write to the file descriptor and -/// mainly it closes the file descriptor once dropped. -#[derive(Debug)] -pub struct FileDesc { - fd: RawFd, - close_on_drop: bool, -} - -impl FileDesc { - /// Constructs a new `FileDesc` with the given `RawFd`. - /// - /// # Arguments - /// - /// * `fd` - raw file descriptor - /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped - pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc { - FileDesc { fd, close_on_drop } - } - - pub fn read(&self, buffer: &mut [u8], size: usize) -> Result { - let result = unsafe { - libc::read( - self.fd, - buffer.as_mut_ptr() as *mut libc::c_void, - size as size_t, - ) as isize - }; - - if result < 0 { - Err(ErrorKind::IoError(io::Error::last_os_error())) - } else { - Ok(result as usize) - } - } - - /// Returns the underlying file descriptor. - pub fn raw_fd(&self) -> RawFd { - self.fd - } -} - -impl Drop for FileDesc { - fn drop(&mut self) { - if self.close_on_drop { - // Note that errors are ignored when closing a file descriptor. The - // reason for this is that if an error occurs we don't actually know if - // the file descriptor was closed or not, and if we retried (for - // something like EINTR), we might close another valid file descriptor - // opened after we closed ours. - let _ = unsafe { libc::close(self.fd) }; - } - } -} - -/// Creates a file descriptor pointing to the standard input or `/dev/tty`. -pub fn tty_fd() -> Result { - let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { - (libc::STDIN_FILENO, false) - } else { - ( - fs::OpenOptions::new() - .read(true) - .write(true) - .open("/dev/tty")? - .into_raw_fd(), - true, - ) - }; - - Ok(FileDesc::new(fd, close_on_drop)) -} +use std::{ + fs, io, + os::unix::io::{IntoRawFd, RawFd}, +}; + +use libc::size_t; + +use crate::{ErrorKind, Result}; + +/// A file descriptor wrapper. +/// +/// It allows to retrieve raw file descriptor, write to the file descriptor and +/// mainly it closes the file descriptor once dropped. +#[derive(Debug)] +pub struct FileDesc { + fd: RawFd, + close_on_drop: bool, +} + +impl FileDesc { + /// Constructs a new `FileDesc` with the given `RawFd`. + /// + /// # Arguments + /// + /// * `fd` - raw file descriptor + /// * `close_on_drop` - specify if the raw file descriptor should be closed once the `FileDesc` is dropped + pub fn new(fd: RawFd, close_on_drop: bool) -> FileDesc { + FileDesc { fd, close_on_drop } + } + + pub fn read(&self, buffer: &mut [u8], size: usize) -> Result { + let result = unsafe { + libc::read( + self.fd, + buffer.as_mut_ptr() as *mut libc::c_void, + size as size_t, + ) as isize + }; + + if result < 0 { + Err(ErrorKind::IoError(io::Error::last_os_error())) + } else { + Ok(result as usize) + } + } + + /// Returns the underlying file descriptor. + pub fn raw_fd(&self) -> RawFd { + self.fd + } +} + +impl Drop for FileDesc { + fn drop(&mut self) { + if self.close_on_drop { + // Note that errors are ignored when closing a file descriptor. The + // reason for this is that if an error occurs we don't actually know if + // the file descriptor was closed or not, and if we retried (for + // something like EINTR), we might close another valid file descriptor + // opened after we closed ours. + let _ = unsafe { libc::close(self.fd) }; + } + } +} + +/// Creates a file descriptor pointing to the standard input or `/dev/tty`. +pub fn tty_fd() -> Result { + let (fd, close_on_drop) = if unsafe { libc::isatty(libc::STDIN_FILENO) == 1 } { + (libc::STDIN_FILENO, false) + } else { + ( + fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/tty")? + .into_raw_fd(), + true, + ) + }; + + Ok(FileDesc::new(fd, close_on_drop)) +} diff --git a/src/event/sys/unix/waker.rs b/src/event/sys/unix/waker.rs index f040827c7..1274faa56 100644 --- a/src/event/sys/unix/waker.rs +++ b/src/event/sys/unix/waker.rs @@ -1,39 +1,39 @@ -use std::sync::{Arc, Mutex}; - -use mio::{Registry, Token}; - -use crate::{ErrorKind, Result}; - -/// Allows to wake up the `mio::Poll::poll()` method. -/// This type wraps `mio::Waker`, for more information see its documentation. -#[derive(Clone, Debug)] -pub(crate) struct Waker { - inner: Arc>, -} - -impl Waker { - /// Create a new `Waker`. - pub(crate) fn new(registry: &Registry, waker_token: Token) -> Result { - Ok(Self { - inner: Arc::new(Mutex::new(mio::Waker::new(registry, waker_token)?)), - }) - } - - /// Wake up the [`Poll`] associated with this `Waker`. - /// - /// Readiness is set to `Ready::readable()`. - pub(crate) fn wake(&self) -> Result<()> { - self.inner - .lock() - .unwrap() - .wake() - .map_err(|e| ErrorKind::IoError(e)) - } - - /// Resets the state so the same waker can be reused. - /// - /// This function is not impl - pub(crate) fn reset(&self) -> Result<()> { - Ok(()) - } -} +use std::sync::{Arc, Mutex}; + +use mio::{Registry, Token}; + +use crate::{ErrorKind, Result}; + +/// Allows to wake up the `mio::Poll::poll()` method. +/// This type wraps `mio::Waker`, for more information see its documentation. +#[derive(Clone, Debug)] +pub(crate) struct Waker { + inner: Arc>, +} + +impl Waker { + /// Create a new `Waker`. + pub(crate) fn new(registry: &Registry, waker_token: Token) -> Result { + Ok(Self { + inner: Arc::new(Mutex::new(mio::Waker::new(registry, waker_token)?)), + }) + } + + /// Wake up the [`Poll`] associated with this `Waker`. + /// + /// Readiness is set to `Ready::readable()`. + pub(crate) fn wake(&self) -> Result<()> { + self.inner + .lock() + .unwrap() + .wake() + .map_err(|e| ErrorKind::IoError(e)) + } + + /// Resets the state so the same waker can be reused. + /// + /// This function is not impl + pub(crate) fn reset(&self) -> Result<()> { + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index f589b32e6..fe60b3969 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,254 +1,254 @@ -#![deny(unused_imports, unused_must_use)] - -//! # Crossterm -//! -//! Have you ever been disappointed when a terminal library for rust was only written for UNIX systems? -//! Crossterm provides clearing, event (input) handling, styling, cursor movement, and terminal actions for both -//! Windows and UNIX systems. -//! -//! Crossterm aims to be simple and easy to call in code. Through the simplicity of Crossterm, you do not -//! have to worry about the platform you are working with. -//! -//! This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested -//! see [Tested Terminals](https://github.com/crossterm-rs/crossterm#tested-terminals) -//! for more info). -//! -//! ## Command API -//! -//! The command API makes the use of `crossterm` much easier and offers more control over when and how a -//! command is executed. A command is just an action you can perform on the terminal e.g. cursor movement. -//! -//! The command API offers: -//! -//! * Better Performance. -//! * Complete control over when to flush. -//! * Complete control over where the ANSI escape commands are executed to. -//! * Way easier and nicer API. -//! -//! There are two ways to use the API command: -//! -//! * Functions can execute commands on types that implement Write. Functions are easier to use and debug. -//! There is a disadvantage, and that is that there is a boilerplate code involved. -//! * Macros are generally seen as more difficult and aren't always well supported by editors but offer an API with less boilerplate code. If you are -//! not afraid of macros, this is a recommendation. -//! -//! Linux and Windows 10 systems support ANSI escape codes. Those ANSI escape codes are strings or rather a -//! byte sequence. When we `write` and `flush` those to the terminal we can perform some action. -//! For older windows systems a WinApi call is made. -//! -//! ### Supported Commands -//! -//! - Module `cursor` -//! - Visibility - [`Show`](cursor/struct.Show.html), [`Hide`](cursor/struct.Hide.html) -//! - Appearance - [`EnableBlinking`](cursor/struct.EnableBlinking.html), -//! [`DisableBlinking`](cursor/struct.DisableBlinking.html) -//! - Position - -//! [`SavePosition`](cursor/struct.SavePosition.html), [`RestorePosition`](cursor/struct.RestorePosition.html), -//! [`MoveUp`](cursor/struct.MoveUp.html), [`MoveDown`](cursor/struct.MoveDown.html), -//! [`MoveLeft`](cursor/struct.MoveLeft.html), [`MoveRight`](cursor/struct.MoveRight.html), -//! [`MoveTo`](cursor/struct.MoveTo.html), [`MoveToColumn`](cursor/struct.MoveToColumn.html), -//! [`MoveToNextLine`](cursor/struct.MoveToNextLine.html), [`MoveToPreviousLine`](cursor/struct.MoveToPreviousLine.html), -//! - Module `event` -//! - Mouse events - [`EnableMouseCapture`](event/struct.EnableMouseCapture.html), -//! [`DisableMouseCapture`](event/struct.DisableMouseCapture.html) -//! - Module `style` -//! - Colors - [`SetForegroundColor`](style/struct.SetForegroundColor.html), -//! [`SetBackgroundColor`](style/struct.SetBackgroundColor.html), -//! [`ResetColor`](style/struct.ResetColor.html), [`SetColors`](style/struct.SetColors.html) -//! - Attributes - [`SetAttribute`](style/struct.SetAttribute.html), [`SetAttributes`](style/struct.SetAttributes.html), -//! [`PrintStyledContent`](style/struct.PrintStyledContent.html) -//! - Module `terminal` -//! - Scrolling - [`ScrollUp`](terminal/struct.ScrollUp.html), -//! [`ScrollDown`](terminal/struct.ScrollDown.html) -//! - Miscellaneous - [`Clear`](terminal/struct.Clear.html), -//! [`SetSize`](terminal/struct.SetSize.html) -//! [`SetTitle`](terminal/struct.SetTitle.html) -//! - Alternate screen - [`EnterAlternateScreen`](terminal/struct.EnterAlternateScreen.html), -//! [`LeaveAlternateScreen`](terminal/struct.LeaveAlternateScreen.html) -//! -//! ### Command Execution -//! -//! There are two different way's to execute commands: -//! -//! * [Lazy Execution](#lazy-execution) -//! * [Direct Execution](#direct-execution) -//! -//! #### Lazy Execution -//! -//! Flushing bytes to the terminal buffer is a heavy system call. If we perform a lot of actions with the terminal, -//! we want to do this periodically - like with a TUI editor - so that we can flush more data to the terminal buffer -//! at the same time. -//! -//! Crossterm offers the possibility to do this with `queue`. -//! With `queue` you can queue commands, and when you call [Write::flush][flush] these commands will be executed. -//! -//! You can pass a custom buffer implementing [std::io::Write][write] to this `queue` operation. -//! The commands will be executed on that buffer. -//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well. -//! -//! ##### Examples -//! -//! A simple demonstration that shows the command API in action with cursor commands. -//! -//! Functions: -//! -//! ```no_run -//! use std::io::{Write, stdout}; -//! use crossterm::{QueueableCommand, cursor}; -//! -//! let mut stdout = stdout(); -//! stdout.queue(cursor::MoveTo(5,5)); -//! -//! // some other code ... -//! -//! stdout.flush(); -//! ``` -//! -//! The [queue](./trait.QueueableCommand.html) function returns itself, therefore you can use this to queue another -//! command. Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))`. -//! -//! Macros: -//! -//! ```no_run -//! use std::io::{Write, stdout}; -//! use crossterm::{queue, QueueableCommand, cursor}; -//! -//! let mut stdout = stdout(); -//! queue!(stdout, cursor::MoveTo(5, 5)); -//! -//! // some other code ... -//! -//! // move operation is performed only if we flush the buffer. -//! stdout.flush(); -//! ``` -//! -//! You can pass more than one command into the [queue](./macro.queue.html) macro like -//! `queue!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and -//! they will be executed in the given order from left to right. -//! -//! #### Direct Execution -//! -//! For many applications it is not at all important to be efficient with 'flush' operations. -//! For this use case there is the `execute` operation. -//! This operation executes the command immediately, and calls the `flush` under water. -//! -//! You can pass a custom buffer implementing [std::io::Write][write] to this `execute` operation. -//! The commands will be executed on that buffer. -//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well. -//! -//! ##### Examples -//! -//! Functions: -//! -//! ```no_run -//! use std::io::{Write, stdout}; -//! use crossterm::{ExecutableCommand, cursor}; -//! -//! let mut stdout = stdout(); -//! stdout.execute(cursor::MoveTo(5,5)); -//! ``` -//! The [execute](./trait.ExecutableCommand.html) function returns itself, therefore you can use this to queue -//! another command. Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))`. -//! -//! Macros: -//! -//! ```no_run -//! use std::io::{Write, stdout}; -//! use crossterm::{execute, ExecutableCommand, cursor}; -//! -//! let mut stdout = stdout(); -//! execute!(stdout, cursor::MoveTo(5, 5)); -//! ``` -//! You can pass more than one command into the [execute](./macro.execute.html) macro like -//! `execute!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and they will be executed in the given order from -//! left to right. -//! -//! ## Examples -//! -//! Print a rectangle colored with magenta and use both direct execution and lazy execution. -//! -//! Functions: -//! -//! ```no_run -//! use std::io::{stdout, Write}; -//! use crossterm::{ -//! ExecutableCommand, QueueableCommand, -//! terminal, cursor, style::{self, Colorize}, Result -//! }; -//! -//! fn main() -> Result<()> { -//! let mut stdout = stdout(); -//! -//! stdout.execute(terminal::Clear(terminal::ClearType::All))?; -//! -//! for y in 0..40 { -//! for x in 0..150 { -//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { -//! // in this loop we are more efficient by not flushing the buffer. -//! stdout -//! .queue(cursor::MoveTo(x,y))? -//! .queue(style::PrintStyledContent( "█".magenta()))?; -//! } -//! } -//! } -//! stdout.flush()?; -//! Ok(()) -//! } -//! ``` -//! -//! Macros: -//! -//! ```no_run -//! use std::io::{stdout, Write}; -//! use crossterm::{ -//! execute, queue, -//! style::{self, Colorize}, cursor, terminal, Result -//! }; -//! -//! fn main() -> Result<()> { -//! let mut stdout = stdout(); -//! -//! execute!(stdout, terminal::Clear(terminal::ClearType::All))?; -//! -//! for y in 0..40 { -//! for x in 0..150 { -//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { -//! // in this loop we are more efficient by not flushing the buffer. -//! queue!(stdout, cursor::MoveTo(x,y), style::PrintStyledContent( "█".magenta()))?; -//! } -//! } -//! } -//! stdout.flush()?; -//! Ok(()) -//! } -//!``` -//! -//! [write]: https://doc.rust-lang.org/std/io/trait.Write.html -//! [stdout]: https://doc.rust-lang.org/std/io/fn.stdout.html -//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html -//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush - -pub use crate::{ - ansi::Ansi, - command::{Command, ExecutableCommand, QueueableCommand}, - error::{ErrorKind, Result}, -}; - -/// A module to work with the terminal cursor -pub mod cursor; -/// A module to read events. -pub mod event; -/// A module to apply attributes and colors on your text. -pub mod style; -/// A module to work with the terminal. -pub mod terminal; - -/// A module to query if the current instance is a tty. -pub mod tty; - -mod ansi; -#[cfg(windows)] -pub(crate) mod ansi_support; -mod command; -mod error; -pub(crate) mod macros; +#![deny(unused_imports, unused_must_use)] + +//! # Crossterm +//! +//! Have you ever been disappointed when a terminal library for rust was only written for UNIX systems? +//! Crossterm provides clearing, event (input) handling, styling, cursor movement, and terminal actions for both +//! Windows and UNIX systems. +//! +//! Crossterm aims to be simple and easy to call in code. Through the simplicity of Crossterm, you do not +//! have to worry about the platform you are working with. +//! +//! This crate supports all UNIX and Windows terminals down to Windows 7 (not all terminals are tested +//! see [Tested Terminals](https://github.com/crossterm-rs/crossterm#tested-terminals) +//! for more info). +//! +//! ## Command API +//! +//! The command API makes the use of `crossterm` much easier and offers more control over when and how a +//! command is executed. A command is just an action you can perform on the terminal e.g. cursor movement. +//! +//! The command API offers: +//! +//! * Better Performance. +//! * Complete control over when to flush. +//! * Complete control over where the ANSI escape commands are executed to. +//! * Way easier and nicer API. +//! +//! There are two ways to use the API command: +//! +//! * Functions can execute commands on types that implement Write. Functions are easier to use and debug. +//! There is a disadvantage, and that is that there is a boilerplate code involved. +//! * Macros are generally seen as more difficult and aren't always well supported by editors but offer an API with less boilerplate code. If you are +//! not afraid of macros, this is a recommendation. +//! +//! Linux and Windows 10 systems support ANSI escape codes. Those ANSI escape codes are strings or rather a +//! byte sequence. When we `write` and `flush` those to the terminal we can perform some action. +//! For older windows systems a WinApi call is made. +//! +//! ### Supported Commands +//! +//! - Module `cursor` +//! - Visibility - [`Show`](cursor/struct.Show.html), [`Hide`](cursor/struct.Hide.html) +//! - Appearance - [`EnableBlinking`](cursor/struct.EnableBlinking.html), +//! [`DisableBlinking`](cursor/struct.DisableBlinking.html) +//! - Position - +//! [`SavePosition`](cursor/struct.SavePosition.html), [`RestorePosition`](cursor/struct.RestorePosition.html), +//! [`MoveUp`](cursor/struct.MoveUp.html), [`MoveDown`](cursor/struct.MoveDown.html), +//! [`MoveLeft`](cursor/struct.MoveLeft.html), [`MoveRight`](cursor/struct.MoveRight.html), +//! [`MoveTo`](cursor/struct.MoveTo.html), [`MoveToColumn`](cursor/struct.MoveToColumn.html), +//! [`MoveToNextLine`](cursor/struct.MoveToNextLine.html), [`MoveToPreviousLine`](cursor/struct.MoveToPreviousLine.html), +//! - Module `event` +//! - Mouse events - [`EnableMouseCapture`](event/struct.EnableMouseCapture.html), +//! [`DisableMouseCapture`](event/struct.DisableMouseCapture.html) +//! - Module `style` +//! - Colors - [`SetForegroundColor`](style/struct.SetForegroundColor.html), +//! [`SetBackgroundColor`](style/struct.SetBackgroundColor.html), +//! [`ResetColor`](style/struct.ResetColor.html), [`SetColors`](style/struct.SetColors.html) +//! - Attributes - [`SetAttribute`](style/struct.SetAttribute.html), [`SetAttributes`](style/struct.SetAttributes.html), +//! [`PrintStyledContent`](style/struct.PrintStyledContent.html) +//! - Module `terminal` +//! - Scrolling - [`ScrollUp`](terminal/struct.ScrollUp.html), +//! [`ScrollDown`](terminal/struct.ScrollDown.html) +//! - Miscellaneous - [`Clear`](terminal/struct.Clear.html), +//! [`SetSize`](terminal/struct.SetSize.html) +//! [`SetTitle`](terminal/struct.SetTitle.html) +//! - Alternate screen - [`EnterAlternateScreen`](terminal/struct.EnterAlternateScreen.html), +//! [`LeaveAlternateScreen`](terminal/struct.LeaveAlternateScreen.html) +//! +//! ### Command Execution +//! +//! There are two different way's to execute commands: +//! +//! * [Lazy Execution](#lazy-execution) +//! * [Direct Execution](#direct-execution) +//! +//! #### Lazy Execution +//! +//! Flushing bytes to the terminal buffer is a heavy system call. If we perform a lot of actions with the terminal, +//! we want to do this periodically - like with a TUI editor - so that we can flush more data to the terminal buffer +//! at the same time. +//! +//! Crossterm offers the possibility to do this with `queue`. +//! With `queue` you can queue commands, and when you call [Write::flush][flush] these commands will be executed. +//! +//! You can pass a custom buffer implementing [std::io::Write][write] to this `queue` operation. +//! The commands will be executed on that buffer. +//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well. +//! +//! ##### Examples +//! +//! A simple demonstration that shows the command API in action with cursor commands. +//! +//! Functions: +//! +//! ```no_run +//! use std::io::{Write, stdout}; +//! use crossterm::{QueueableCommand, cursor}; +//! +//! let mut stdout = stdout(); +//! stdout.queue(cursor::MoveTo(5,5)); +//! +//! // some other code ... +//! +//! stdout.flush(); +//! ``` +//! +//! The [queue](./trait.QueueableCommand.html) function returns itself, therefore you can use this to queue another +//! command. Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))`. +//! +//! Macros: +//! +//! ```no_run +//! use std::io::{Write, stdout}; +//! use crossterm::{queue, QueueableCommand, cursor}; +//! +//! let mut stdout = stdout(); +//! queue!(stdout, cursor::MoveTo(5, 5)); +//! +//! // some other code ... +//! +//! // move operation is performed only if we flush the buffer. +//! stdout.flush(); +//! ``` +//! +//! You can pass more than one command into the [queue](./macro.queue.html) macro like +//! `queue!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and +//! they will be executed in the given order from left to right. +//! +//! #### Direct Execution +//! +//! For many applications it is not at all important to be efficient with 'flush' operations. +//! For this use case there is the `execute` operation. +//! This operation executes the command immediately, and calls the `flush` under water. +//! +//! You can pass a custom buffer implementing [std::io::Write][write] to this `execute` operation. +//! The commands will be executed on that buffer. +//! The most common buffer is [std::io::stdout][stdout] however, [std::io::stderr][stderr] is used sometimes as well. +//! +//! ##### Examples +//! +//! Functions: +//! +//! ```no_run +//! use std::io::{Write, stdout}; +//! use crossterm::{ExecutableCommand, cursor}; +//! +//! let mut stdout = stdout(); +//! stdout.execute(cursor::MoveTo(5,5)); +//! ``` +//! The [execute](./trait.ExecutableCommand.html) function returns itself, therefore you can use this to queue +//! another command. Like `stdout.queue(Goto(5,5)).queue(Clear(ClearType::All))`. +//! +//! Macros: +//! +//! ```no_run +//! use std::io::{Write, stdout}; +//! use crossterm::{execute, ExecutableCommand, cursor}; +//! +//! let mut stdout = stdout(); +//! execute!(stdout, cursor::MoveTo(5, 5)); +//! ``` +//! You can pass more than one command into the [execute](./macro.execute.html) macro like +//! `execute!(stdout, MoveTo(5, 5), Clear(ClearType::All))` and they will be executed in the given order from +//! left to right. +//! +//! ## Examples +//! +//! Print a rectangle colored with magenta and use both direct execution and lazy execution. +//! +//! Functions: +//! +//! ```no_run +//! use std::io::{stdout, Write}; +//! use crossterm::{ +//! ExecutableCommand, QueueableCommand, +//! terminal, cursor, style::{self, Colorize}, Result +//! }; +//! +//! fn main() -> Result<()> { +//! let mut stdout = stdout(); +//! +//! stdout.execute(terminal::Clear(terminal::ClearType::All))?; +//! +//! for y in 0..40 { +//! for x in 0..150 { +//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { +//! // in this loop we are more efficient by not flushing the buffer. +//! stdout +//! .queue(cursor::MoveTo(x,y))? +//! .queue(style::PrintStyledContent( "█".magenta()))?; +//! } +//! } +//! } +//! stdout.flush()?; +//! Ok(()) +//! } +//! ``` +//! +//! Macros: +//! +//! ```no_run +//! use std::io::{stdout, Write}; +//! use crossterm::{ +//! execute, queue, +//! style::{self, Colorize}, cursor, terminal, Result +//! }; +//! +//! fn main() -> Result<()> { +//! let mut stdout = stdout(); +//! +//! execute!(stdout, terminal::Clear(terminal::ClearType::All))?; +//! +//! for y in 0..40 { +//! for x in 0..150 { +//! if (y == 0 || y == 40 - 1) || (x == 0 || x == 150 - 1) { +//! // in this loop we are more efficient by not flushing the buffer. +//! queue!(stdout, cursor::MoveTo(x,y), style::PrintStyledContent( "█".magenta()))?; +//! } +//! } +//! } +//! stdout.flush()?; +//! Ok(()) +//! } +//!``` +//! +//! [write]: https://doc.rust-lang.org/std/io/trait.Write.html +//! [stdout]: https://doc.rust-lang.org/std/io/fn.stdout.html +//! [stderr]: https://doc.rust-lang.org/std/io/fn.stderr.html +//! [flush]: https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush + +pub use crate::{ + ansi::Ansi, + command::{Command, ExecutableCommand, QueueableCommand}, + error::{ErrorKind, Result}, +}; + +/// A module to work with the terminal cursor +pub mod cursor; +/// A module to read events. +pub mod event; +/// A module to apply attributes and colors on your text. +pub mod style; +/// A module to work with the terminal. +pub mod terminal; + +/// A module to query if the current instance is a tty. +pub mod tty; + +mod ansi; +#[cfg(windows)] +pub(crate) mod ansi_support; +mod command; +mod error; +pub(crate) mod macros; diff --git a/src/style.rs b/src/style.rs index 677372547..3fb502f56 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,455 +1,455 @@ -//! # Style -//! -//! The `style` module provides a functionality to apply attributes and colors on your text. -//! -//! This documentation does not contain a lot of examples. The reason is that it's fairly -//! obvious how to use this crate. Although, we do provide -//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository -//! to demonstrate the capabilities. -//! -//! ## Platform-specific Notes -//! -//! Not all features are supported on all terminals/platforms. You should always consult -//! platform-specific notes of the following types: -//! -//! * [Color](enum.Color.html#platform-specific-notes) -//! * [Attribute](enum.Attribute.html#platform-specific-notes) -//! -//! ## Examples -//! -//! A few examples of how to use the style module. -//! -//! ### Colors -//! -//! How to change the terminal text color. -//! -//! Command API: -//! -//! Using the Command API to color text. -//! -//! ```no_run -//! use std::io::{stdout, Write}; -//! -//! use crossterm::{execute, Result}; -//! use crossterm::style::{Print, SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute}; -//! -//! fn main() -> Result<()> { -//! execute!( -//! stdout(), -//! // Blue foreground -//! SetForegroundColor(Color::Blue), -//! // Red background -//! SetBackgroundColor(Color::Red), -//! // Print text -//! Print("Blue text on Red.".to_string()), -//! // Reset to default colors -//! ResetColor -//! ) -//! } -//! ``` -//! -//! Functions: -//! -//! Using functions from [`Colorize`](trait.Colorize.html) on a `String` or `&'static str` to color it. -//! -//! ```no_run -//! use crossterm::style::Colorize; -//! -//! println!("{}", "Red foreground color & blue background.".red().on_blue()); -//! ``` -//! -//! ### Attributes -//! -//! How to appy terminal attributes to text. -//! -//! Command API: -//! -//! Using the Command API to set attributes. -//! -//! ```no_run -//! use std::io::{stdout, Write}; -//! -//! use crossterm::{execute, Result, style::Print}; -//! use crossterm::style::{SetAttribute, Attribute}; -//! -//! fn main() -> Result<()> { -//! execute!( -//! stdout(), -//! // Set to bold -//! SetAttribute(Attribute::Bold), -//! Print("Bold text here.".to_string()), -//! // Reset all attributes -//! SetAttribute(Attribute::Reset) -//! ) -//! } -//! ``` -//! -//! Functions: -//! -//! Using [`Styler`](trait.Styler.html) functions on a `String` or `&'static str` to set attributes to it. -//! -//! ```no_run -//! use crossterm::style::Styler; -//! -//! println!("{}", "Bold".bold()); -//! println!("{}", "Underlined".underlined()); -//! println!("{}", "Negative".negative()); -//! ``` -//! -//! Displayable: -//! -//! [`Attribute`](enum.Attribute.html) implements [Display](https://doc.rust-lang.org/beta/std/fmt/trait.Display.html) and therefore it can be formatted like: -//! -//! ```no_run -//! use crossterm::style::Attribute; -//! -//! println!( -//! "{} Underlined {} No Underline", -//! Attribute::Underlined, -//! Attribute::NoUnderline -//! ); -//! ``` - -use std::{env, fmt::Display}; - -#[cfg(windows)] -use crate::Result; -use crate::{impl_display, Ansi, Command}; -use std::fmt; - -pub use self::{ - attributes::Attributes, - content_style::ContentStyle, - styled_content::StyledContent, - traits::{Colorize, Styler}, - types::{Attribute, Color, Colored, Colors}, -}; - -#[macro_use] -mod macros; -mod ansi; -mod attributes; -mod content_style; -mod styled_content; -mod sys; -mod traits; -mod types; - -/// Creates a `StyledContent`. -/// -/// This could be used to style any type that implements `Display` with colors and text attributes. -/// -/// See [`StyledContent`](struct.StyledContent.html) for more info. -/// -/// # Examples -/// -/// ```no_run -/// use crossterm::style::{style, Color}; -/// -/// let styled_content = style("Blue colored text on yellow background") -/// .with(Color::Blue) -/// .on(Color::Yellow); -/// -/// println!("{}", styled_content); -/// ``` -pub fn style<'a, D: 'a>(val: D) -> StyledContent -where - D: Display + Clone, -{ - ContentStyle::new().apply(val) -} - -impl_colorize!(String); -impl_colorize!(char); - -// We do actually need the parentheses here because the macro doesn't work without them otherwise -// This is probably a bug somewhere in the compiler, but it isn't that big a deal. -#[allow(unused_parens)] -impl<'a> Colorize<&'a str> for &'a str { - impl_colorize_callback!(def_color_base!((&'a str))); -} - -impl_styler!(String); -impl_styler!(char); - -#[allow(unused_parens)] -impl<'a> Styler<&'a str> for &'a str { - impl_styler_callback!(def_attr_base!((&'a str))); -} - -/// Returns available color count. -/// -/// # Notes -/// -/// This does not always provide a good result. -pub fn available_color_count() -> u16 { - env::var("TERM") - .map(|x| if x.contains("256color") { 256 } else { 8 }) - .unwrap_or(8) -} - -/// A command that sets the the foreground color. -/// -/// See [`Color`](enum.Color.html) for more info. -/// -/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background -/// color in one command. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetForegroundColor(pub Color); - -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_fg_csi_sequence(f, (self.0).0) - } -} - -impl Command for SetForegroundColor { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - sys::windows::set_foreground_color(self.0) - } -} - -/// A command that sets the the background color. -/// -/// See [`Color`](enum.Color.html) for more info. -/// -/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background -/// color with one command. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetBackgroundColor(pub Color); - -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_bg_csi_sequence(f, (self.0).0) - } -} - -impl Command for SetBackgroundColor { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - sys::windows::set_background_color(self.0) - } -} - -/// A command that optionally sets the foreground and/or background color. -/// -/// For example: -/// ```no_run -/// use std::io::{stdout, Write}; -/// use crossterm::execute; -/// use crossterm::style::{Color::{Green, Black}, Colors, Print, SetColors}; -/// -/// execute!( -/// stdout(), -/// SetColors(Colors::new(Green, Black)), -/// Print("Hello, world!".to_string()), -/// ).unwrap(); -/// ``` -/// -/// See [`Colors`](struct.Colors.html) for more info. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetColors(pub Colors); - -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(color) = (self.0).0.foreground { - ansi::set_fg_csi_sequence(f, color)?; - } - if let Some(color) = (self.0).0.background { - ansi::set_bg_csi_sequence(f, color)?; - } - Ok(()) - } -} - -impl Command for SetColors { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - if let Some(color) = self.0.foreground { - sys::windows::set_foreground_color(color)?; - } - if let Some(color) = self.0.background { - sys::windows::set_background_color(color)?; - } - Ok(()) - } -} - -/// A command that sets an attribute. -/// -/// See [`Attribute`](enum.Attribute.html) for more info. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetAttribute(pub Attribute); - -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_attr_csi_sequence(f, (self.0).0) - } -} - -impl Command for SetAttribute { - type AnsiType = Ansi; - - #[inline] - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - // attributes are not supported by WinAPI. - Ok(()) - } -} - -/// A command that sets several attributes. -/// -/// See [`Attributes`](struct.Attributes.html) for more info. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct SetAttributes(pub Attributes); - -impl fmt::Display for Ansi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ansi::set_attrs_csi_sequence(f, (self.0).0) - } -} - -impl Command for SetAttributes { - type AnsiType = Ansi; - - fn ansi_code(&self) -> Self::AnsiType { - Ansi(*self) - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - // attributes are not supported by WinAPI. - Ok(()) - } -} - -/// A command that prints styled content. -/// -/// See [`StyledContent`](struct.StyledContent.html) for more info. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone)] -pub struct PrintStyledContent(pub StyledContent); - -impl Command for PrintStyledContent -where - D: Display + Clone, -{ - type AnsiType = StyledContent; - - fn ansi_code(&self) -> Self::AnsiType { - self.0.clone() - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - Ok(()) - } -} - -/// A command that resets the colors back to default. -/// -/// # Notes -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ResetColor; - -impl Command for ResetColor { - type AnsiType = &'static str; - - fn ansi_code(&self) -> Self::AnsiType { - ansi::RESET_CSI_SEQUENCE - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - sys::windows::reset() - } -} - -/// A command that prints the given displayable type. -/// -/// Commands must be executed/queued for execution otherwise they do nothing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Print(pub T); - -impl Command for Print { - type AnsiType = T; - - fn ansi_code(&self) -> Self::AnsiType { - self.0.clone() - } - - #[cfg(windows)] - fn execute_winapi(&self) -> Result<()> { - print!("{}", self.0); - Ok(()) - } -} - -impl Display for Print { - fn fmt( - &self, - f: &mut ::std::fmt::Formatter<'_>, - ) -> ::std::result::Result<(), ::std::fmt::Error> { - write!(f, "{}", self.ansi_code()) - } -} - -impl_display!(for SetForegroundColor); -impl_display!(for SetBackgroundColor); -impl_display!(for SetColors); -impl_display!(for SetAttribute); -impl_display!(for PrintStyledContent); -impl_display!(for PrintStyledContent<&'static str>); -impl_display!(for ResetColor); +//! # Style +//! +//! The `style` module provides a functionality to apply attributes and colors on your text. +//! +//! This documentation does not contain a lot of examples. The reason is that it's fairly +//! obvious how to use this crate. Although, we do provide +//! [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) repository +//! to demonstrate the capabilities. +//! +//! ## Platform-specific Notes +//! +//! Not all features are supported on all terminals/platforms. You should always consult +//! platform-specific notes of the following types: +//! +//! * [Color](enum.Color.html#platform-specific-notes) +//! * [Attribute](enum.Attribute.html#platform-specific-notes) +//! +//! ## Examples +//! +//! A few examples of how to use the style module. +//! +//! ### Colors +//! +//! How to change the terminal text color. +//! +//! Command API: +//! +//! Using the Command API to color text. +//! +//! ```no_run +//! use std::io::{stdout, Write}; +//! +//! use crossterm::{execute, Result}; +//! use crossterm::style::{Print, SetForegroundColor, SetBackgroundColor, ResetColor, Color, Attribute}; +//! +//! fn main() -> Result<()> { +//! execute!( +//! stdout(), +//! // Blue foreground +//! SetForegroundColor(Color::Blue), +//! // Red background +//! SetBackgroundColor(Color::Red), +//! // Print text +//! Print("Blue text on Red.".to_string()), +//! // Reset to default colors +//! ResetColor +//! ) +//! } +//! ``` +//! +//! Functions: +//! +//! Using functions from [`Colorize`](trait.Colorize.html) on a `String` or `&'static str` to color it. +//! +//! ```no_run +//! use crossterm::style::Colorize; +//! +//! println!("{}", "Red foreground color & blue background.".red().on_blue()); +//! ``` +//! +//! ### Attributes +//! +//! How to appy terminal attributes to text. +//! +//! Command API: +//! +//! Using the Command API to set attributes. +//! +//! ```no_run +//! use std::io::{stdout, Write}; +//! +//! use crossterm::{execute, Result, style::Print}; +//! use crossterm::style::{SetAttribute, Attribute}; +//! +//! fn main() -> Result<()> { +//! execute!( +//! stdout(), +//! // Set to bold +//! SetAttribute(Attribute::Bold), +//! Print("Bold text here.".to_string()), +//! // Reset all attributes +//! SetAttribute(Attribute::Reset) +//! ) +//! } +//! ``` +//! +//! Functions: +//! +//! Using [`Styler`](trait.Styler.html) functions on a `String` or `&'static str` to set attributes to it. +//! +//! ```no_run +//! use crossterm::style::Styler; +//! +//! println!("{}", "Bold".bold()); +//! println!("{}", "Underlined".underlined()); +//! println!("{}", "Negative".negative()); +//! ``` +//! +//! Displayable: +//! +//! [`Attribute`](enum.Attribute.html) implements [Display](https://doc.rust-lang.org/beta/std/fmt/trait.Display.html) and therefore it can be formatted like: +//! +//! ```no_run +//! use crossterm::style::Attribute; +//! +//! println!( +//! "{} Underlined {} No Underline", +//! Attribute::Underlined, +//! Attribute::NoUnderline +//! ); +//! ``` + +use std::{env, fmt::Display}; + +#[cfg(windows)] +use crate::Result; +use crate::{impl_display, Ansi, Command}; +use std::fmt; + +pub use self::{ + attributes::Attributes, + content_style::ContentStyle, + styled_content::StyledContent, + traits::{Colorize, Styler}, + types::{Attribute, Color, Colored, Colors}, +}; + +#[macro_use] +mod macros; +mod ansi; +mod attributes; +mod content_style; +mod styled_content; +mod sys; +mod traits; +mod types; + +/// Creates a `StyledContent`. +/// +/// This could be used to style any type that implements `Display` with colors and text attributes. +/// +/// See [`StyledContent`](struct.StyledContent.html) for more info. +/// +/// # Examples +/// +/// ```no_run +/// use crossterm::style::{style, Color}; +/// +/// let styled_content = style("Blue colored text on yellow background") +/// .with(Color::Blue) +/// .on(Color::Yellow); +/// +/// println!("{}", styled_content); +/// ``` +pub fn style<'a, D: 'a>(val: D) -> StyledContent +where + D: Display + Clone, +{ + ContentStyle::new().apply(val) +} + +impl_colorize!(String); +impl_colorize!(char); + +// We do actually need the parentheses here because the macro doesn't work without them otherwise +// This is probably a bug somewhere in the compiler, but it isn't that big a deal. +#[allow(unused_parens)] +impl<'a> Colorize<&'a str> for &'a str { + impl_colorize_callback!(def_color_base!((&'a str))); +} + +impl_styler!(String); +impl_styler!(char); + +#[allow(unused_parens)] +impl<'a> Styler<&'a str> for &'a str { + impl_styler_callback!(def_attr_base!((&'a str))); +} + +/// Returns available color count. +/// +/// # Notes +/// +/// This does not always provide a good result. +pub fn available_color_count() -> u16 { + env::var("TERM") + .map(|x| if x.contains("256color") { 256 } else { 8 }) + .unwrap_or(8) +} + +/// A command that sets the the foreground color. +/// +/// See [`Color`](enum.Color.html) for more info. +/// +/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background +/// color in one command. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetForegroundColor(pub Color); + +impl fmt::Display for Ansi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ansi::set_fg_csi_sequence(f, (self.0).0) + } +} + +impl Command for SetForegroundColor { + type AnsiType = Ansi; + + #[inline] + fn ansi_code(&self) -> Self::AnsiType { + Ansi(*self) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + sys::windows::set_foreground_color(self.0) + } +} + +/// A command that sets the the background color. +/// +/// See [`Color`](enum.Color.html) for more info. +/// +/// [`SetColors`](struct.SetColors.html) can also be used to set both the foreground and background +/// color with one command. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetBackgroundColor(pub Color); + +impl fmt::Display for Ansi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ansi::set_bg_csi_sequence(f, (self.0).0) + } +} + +impl Command for SetBackgroundColor { + type AnsiType = Ansi; + + #[inline] + fn ansi_code(&self) -> Self::AnsiType { + Ansi(*self) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + sys::windows::set_background_color(self.0) + } +} + +/// A command that optionally sets the foreground and/or background color. +/// +/// For example: +/// ```no_run +/// use std::io::{stdout, Write}; +/// use crossterm::execute; +/// use crossterm::style::{Color::{Green, Black}, Colors, Print, SetColors}; +/// +/// execute!( +/// stdout(), +/// SetColors(Colors::new(Green, Black)), +/// Print("Hello, world!".to_string()), +/// ).unwrap(); +/// ``` +/// +/// See [`Colors`](struct.Colors.html) for more info. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetColors(pub Colors); + +impl fmt::Display for Ansi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(color) = (self.0).0.foreground { + ansi::set_fg_csi_sequence(f, color)?; + } + if let Some(color) = (self.0).0.background { + ansi::set_bg_csi_sequence(f, color)?; + } + Ok(()) + } +} + +impl Command for SetColors { + type AnsiType = Ansi; + + #[inline] + fn ansi_code(&self) -> Self::AnsiType { + Ansi(*self) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + if let Some(color) = self.0.foreground { + sys::windows::set_foreground_color(color)?; + } + if let Some(color) = self.0.background { + sys::windows::set_background_color(color)?; + } + Ok(()) + } +} + +/// A command that sets an attribute. +/// +/// See [`Attribute`](enum.Attribute.html) for more info. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetAttribute(pub Attribute); + +impl fmt::Display for Ansi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ansi::set_attr_csi_sequence(f, (self.0).0) + } +} + +impl Command for SetAttribute { + type AnsiType = Ansi; + + #[inline] + fn ansi_code(&self) -> Self::AnsiType { + Ansi(*self) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // attributes are not supported by WinAPI. + Ok(()) + } +} + +/// A command that sets several attributes. +/// +/// See [`Attributes`](struct.Attributes.html) for more info. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SetAttributes(pub Attributes); + +impl fmt::Display for Ansi { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ansi::set_attrs_csi_sequence(f, (self.0).0) + } +} + +impl Command for SetAttributes { + type AnsiType = Ansi; + + fn ansi_code(&self) -> Self::AnsiType { + Ansi(*self) + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + // attributes are not supported by WinAPI. + Ok(()) + } +} + +/// A command that prints styled content. +/// +/// See [`StyledContent`](struct.StyledContent.html) for more info. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone)] +pub struct PrintStyledContent(pub StyledContent); + +impl Command for PrintStyledContent +where + D: Display + Clone, +{ + type AnsiType = StyledContent; + + fn ansi_code(&self) -> Self::AnsiType { + self.0.clone() + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + Ok(()) + } +} + +/// A command that resets the colors back to default. +/// +/// # Notes +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ResetColor; + +impl Command for ResetColor { + type AnsiType = &'static str; + + fn ansi_code(&self) -> Self::AnsiType { + ansi::RESET_CSI_SEQUENCE + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + sys::windows::reset() + } +} + +/// A command that prints the given displayable type. +/// +/// Commands must be executed/queued for execution otherwise they do nothing. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Print(pub T); + +impl Command for Print { + type AnsiType = T; + + fn ansi_code(&self) -> Self::AnsiType { + self.0.clone() + } + + #[cfg(windows)] + fn execute_winapi(&self) -> Result<()> { + print!("{}", self.0); + Ok(()) + } +} + +impl Display for Print { + fn fmt( + &self, + f: &mut ::std::fmt::Formatter<'_>, + ) -> ::std::result::Result<(), ::std::fmt::Error> { + write!(f, "{}", self.ansi_code()) + } +} + +impl_display!(for SetForegroundColor); +impl_display!(for SetBackgroundColor); +impl_display!(for SetColors); +impl_display!(for SetAttribute); +impl_display!(for PrintStyledContent); +impl_display!(for PrintStyledContent<&'static str>); +impl_display!(for ResetColor); diff --git a/src/style/ansi.rs b/src/style/ansi.rs index 7b789fb7a..78a95450b 100644 --- a/src/style/ansi.rs +++ b/src/style/ansi.rs @@ -1,334 +1,334 @@ -//! This is a ANSI specific implementation for styling related action. -//! This module is used for Windows 10 terminals and Unix terminals by default. - -use std::fmt::{self, Formatter}; - -use crate::{ - csi, - style::{Attribute, Attributes, Color, Colored}, -}; - -pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result { - write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color)) -} - -pub(crate) fn set_bg_csi_sequence(f: &mut Formatter, bg_color: Color) -> fmt::Result { - write!(f, csi!("{}m"), Colored::BackgroundColor(bg_color)) -} - -pub(crate) fn set_attr_csi_sequence(f: &mut Formatter, attribute: Attribute) -> fmt::Result { - write!(f, csi!("{}m"), attribute.sgr()) -} - -pub(crate) fn set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) -> fmt::Result { - for attr in Attribute::iterator() { - if attributes.has(attr) { - write!(f, csi!("{}m"), attr.sgr())?; - } - } - Ok(()) -} - -pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m"); - -impl fmt::Display for Colored { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let color; - - match *self { - Colored::ForegroundColor(new_color) => { - if new_color == Color::Reset { - return f.write_str("39"); - } else { - f.write_str("38;")?; - color = new_color; - } - } - Colored::BackgroundColor(new_color) => { - if new_color == Color::Reset { - return f.write_str("49"); - } else { - f.write_str("48;")?; - color = new_color; - } - } - } - - match color { - Color::Black => f.write_str("5;0"), - Color::DarkGrey => f.write_str("5;8"), - Color::Red => f.write_str("5;9"), - Color::DarkRed => f.write_str("5;1"), - Color::Green => f.write_str("5;10"), - Color::DarkGreen => f.write_str("5;2"), - Color::Yellow => f.write_str("5;11"), - Color::DarkYellow => f.write_str("5;3"), - Color::Blue => f.write_str("5;12"), - Color::DarkBlue => f.write_str("5;4"), - Color::Magenta => f.write_str("5;13"), - Color::DarkMagenta => f.write_str("5;5"), - Color::Cyan => f.write_str("5;14"), - Color::DarkCyan => f.write_str("5;6"), - Color::White => f.write_str("5;15"), - Color::Grey => f.write_str("5;7"), - Color::Rgb { r, g, b } => write!(f, "2;{};{};{}", r, g, b), - Color::AnsiValue(val) => write!(f, "5;{}", val), - _ => Ok(()), - } - } -} - -/// Utility function for ANSI parsing in Color and Colored. -/// Gets the next element of `iter` and tries to parse it as a u8. -fn parse_next_u8<'a>(iter: &mut impl Iterator) -> Option { - iter.next() - .and_then(|s| u8::from_str_radix(s, 10).map(Some).unwrap_or(None)) -} - -impl Colored { - /// Parse an ANSI foreground or background color. - /// This is the string that would appear within an `ESC [ m` escape sequence, as found in - /// various configuration files. - /// - /// For example: `38;5;0 -> ForegroundColor(Black)`, - /// `38;5;26 -> ForegroundColor(AnsiValue(26))` - /// `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))` - /// `49 -> BackgroundColor(Reset)` - /// Invalid sequences map to None. - /// - /// Currently, 3/4 bit color values aren't supported so return None. - /// - /// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi) - pub fn parse_ansi(ansi: &str) -> Option { - use Colored::{BackgroundColor, ForegroundColor}; - - let values = &mut ansi.split(';'); - - let output = match parse_next_u8(values)? { - 38 => return Color::parse_ansi_iter(values).map(ForegroundColor), - 48 => return Color::parse_ansi_iter(values).map(BackgroundColor), - - 39 => ForegroundColor(Color::Reset), - 49 => BackgroundColor(Color::Reset), - - _ => return None, - }; - - if values.next().is_some() { - return None; - } - - Some(output) - } -} - -impl<'a> Color { - /// Parses an ANSI color sequence. - /// For example: `5;0 -> Black`, `5;26 -> AnsiValue(26)`, `2;50;60;70 -> Rgb(50, 60, 70)`. - /// Invalid sequences map to None. - /// - /// Currently, 3/4 bit color values aren't supported so return None. - /// - /// See also: [Colored::parse_ansi](enum.Colored.html#method.parse_ansi) - pub fn parse_ansi(ansi: &str) -> Option { - Self::parse_ansi_iter(&mut ansi.split(';')) - } - - /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the - /// ';'). It's a separate function so it can be used by both Color::parse_ansi and - /// colored::parse_ansi. - /// Tested in Colored tests. - fn parse_ansi_iter(values: &mut impl Iterator) -> Option { - let color = match parse_next_u8(values)? { - // 8 bit colors: `5;` - 5 => { - let n = parse_next_u8(values)?; - - use Color::*; - [ - Black, // 0 - DarkRed, // 1 - DarkGreen, // 2 - DarkYellow, // 3 - DarkBlue, // 4 - DarkMagenta, // 5 - DarkCyan, // 6 - Grey, // 7 - DarkGrey, // 8 - Red, // 9 - Green, // 10 - Yellow, // 11 - Blue, // 12 - Magenta, // 13 - Cyan, // 14 - White, // 15 - ] - .get(n as usize) - .copied() - .unwrap_or(Color::AnsiValue(n)) - } - - // 24 bit colors: `2;;;` - 2 => Color::Rgb { - r: parse_next_u8(values)?, - g: parse_next_u8(values)?, - b: parse_next_u8(values)?, - }, - - _ => return None, - }; - // If there's another value, it's unexpected so return None. - if values.next().is_some() { - return None; - } - Some(color) - } -} - -#[cfg(test)] -mod tests { - use crate::style::{Color, Colored}; - - #[test] - fn test_format_fg_color() { - let colored = Colored::ForegroundColor(Color::Red); - assert_eq!(colored.to_string(), "38;5;9"); - } - - #[test] - fn test_format_bg_color() { - let colored = Colored::BackgroundColor(Color::Red); - assert_eq!(colored.to_string(), "48;5;9"); - } - - #[test] - fn test_format_reset_fg_color() { - let colored = Colored::ForegroundColor(Color::Reset); - assert_eq!(colored.to_string(), "39"); - } - - #[test] - fn test_format_reset_bg_color() { - let colored = Colored::BackgroundColor(Color::Reset); - assert_eq!(colored.to_string(), "49"); - } - - #[test] - fn test_format_fg_rgb_color() { - let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 }); - assert_eq!(colored.to_string(), "48;2;1;2;3"); - } - - #[test] - fn test_format_fg_ansi_color() { - let colored = Colored::ForegroundColor(Color::AnsiValue(255)); - assert_eq!(colored.to_string(), "38;5;255"); - } - - #[test] - fn test_parse_ansi_fg() { - test_parse_ansi(Colored::ForegroundColor) - } - - #[test] - fn test_parse_ansi_bg() { - test_parse_ansi(Colored::ForegroundColor) - } - - /// Used for test_parse_ansi_fg and test_parse_ansi_bg - fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) { - /// Formats a re-parses `color` to check the result. - macro_rules! test { - ($color:expr) => { - let colored = bg_or_fg($color); - assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored)); - }; - } - - use Color::*; - - test!(Reset); - test!(Black); - test!(DarkGrey); - test!(Red); - test!(DarkRed); - test!(Green); - test!(DarkGreen); - test!(Yellow); - test!(DarkYellow); - test!(Blue); - test!(DarkBlue); - test!(Magenta); - test!(DarkMagenta); - test!(Cyan); - test!(DarkCyan); - test!(White); - test!(Grey); - - // n in 0..=15 will give us the color values above back. - for n in 16..=255 { - test!(AnsiValue(n)); - } - - for r in 0..=255 { - for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() { - for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() { - test!(Rgb { r, g, b }); - } - } - } - } - - #[test] - fn test_parse_invalid_ansi_color() { - /// Checks that trying to parse `s` yields None. - fn test(s: &str) { - assert_eq!(Colored::parse_ansi(s), None); - } - test(""); - test(";"); - test(";;"); - test(";;"); - test("0"); - test("1"); - test("12"); - test("100"); - test("100048949345"); - test("39;"); - test("49;"); - test("39;2"); - test("49;2"); - test("38"); - test("38;"); - test("38;0"); - test("38;5"); - test("38;5;0;"); - test("38;5;0;2"); - test("38;5;80;"); - test("38;5;80;2"); - test("38;5;257"); - test("38;2"); - test("38;2;"); - test("38;2;0"); - test("38;2;0;2"); - test("38;2;0;2;257"); - test("38;2;0;2;25;"); - test("38;2;0;2;25;3"); - test("48"); - test("48;"); - test("48;0"); - test("48;5"); - test("48;5;0;"); - test("48;5;0;2"); - test("48;5;80;"); - test("48;5;80;2"); - test("48;5;257"); - test("48;2"); - test("48;2;"); - test("48;2;0"); - test("48;2;0;2"); - test("48;2;0;2;257"); - test("48;2;0;2;25;"); - test("48;2;0;2;25;3"); - } -} +//! This is a ANSI specific implementation for styling related action. +//! This module is used for Windows 10 terminals and Unix terminals by default. + +use std::fmt::{self, Formatter}; + +use crate::{ + csi, + style::{Attribute, Attributes, Color, Colored}, +}; + +pub(crate) fn set_fg_csi_sequence(f: &mut Formatter, fg_color: Color) -> fmt::Result { + write!(f, csi!("{}m"), Colored::ForegroundColor(fg_color)) +} + +pub(crate) fn set_bg_csi_sequence(f: &mut Formatter, bg_color: Color) -> fmt::Result { + write!(f, csi!("{}m"), Colored::BackgroundColor(bg_color)) +} + +pub(crate) fn set_attr_csi_sequence(f: &mut Formatter, attribute: Attribute) -> fmt::Result { + write!(f, csi!("{}m"), attribute.sgr()) +} + +pub(crate) fn set_attrs_csi_sequence(f: &mut Formatter, attributes: Attributes) -> fmt::Result { + for attr in Attribute::iterator() { + if attributes.has(attr) { + write!(f, csi!("{}m"), attr.sgr())?; + } + } + Ok(()) +} + +pub(crate) const RESET_CSI_SEQUENCE: &str = csi!("0m"); + +impl fmt::Display for Colored { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let color; + + match *self { + Colored::ForegroundColor(new_color) => { + if new_color == Color::Reset { + return f.write_str("39"); + } else { + f.write_str("38;")?; + color = new_color; + } + } + Colored::BackgroundColor(new_color) => { + if new_color == Color::Reset { + return f.write_str("49"); + } else { + f.write_str("48;")?; + color = new_color; + } + } + } + + match color { + Color::Black => f.write_str("5;0"), + Color::DarkGrey => f.write_str("5;8"), + Color::Red => f.write_str("5;9"), + Color::DarkRed => f.write_str("5;1"), + Color::Green => f.write_str("5;10"), + Color::DarkGreen => f.write_str("5;2"), + Color::Yellow => f.write_str("5;11"), + Color::DarkYellow => f.write_str("5;3"), + Color::Blue => f.write_str("5;12"), + Color::DarkBlue => f.write_str("5;4"), + Color::Magenta => f.write_str("5;13"), + Color::DarkMagenta => f.write_str("5;5"), + Color::Cyan => f.write_str("5;14"), + Color::DarkCyan => f.write_str("5;6"), + Color::White => f.write_str("5;15"), + Color::Grey => f.write_str("5;7"), + Color::Rgb { r, g, b } => write!(f, "2;{};{};{}", r, g, b), + Color::AnsiValue(val) => write!(f, "5;{}", val), + _ => Ok(()), + } + } +} + +/// Utility function for ANSI parsing in Color and Colored. +/// Gets the next element of `iter` and tries to parse it as a u8. +fn parse_next_u8<'a>(iter: &mut impl Iterator) -> Option { + iter.next() + .and_then(|s| u8::from_str_radix(s, 10).map(Some).unwrap_or(None)) +} + +impl Colored { + /// Parse an ANSI foreground or background color. + /// This is the string that would appear within an `ESC [ m` escape sequence, as found in + /// various configuration files. + /// + /// For example: `38;5;0 -> ForegroundColor(Black)`, + /// `38;5;26 -> ForegroundColor(AnsiValue(26))` + /// `48;2;50;60;70 -> BackgroundColor(Rgb(50, 60, 70))` + /// `49 -> BackgroundColor(Reset)` + /// Invalid sequences map to None. + /// + /// Currently, 3/4 bit color values aren't supported so return None. + /// + /// See also: [Color::parse_ansi](enum.Color.html#method.parse_ansi) + pub fn parse_ansi(ansi: &str) -> Option { + use Colored::{BackgroundColor, ForegroundColor}; + + let values = &mut ansi.split(';'); + + let output = match parse_next_u8(values)? { + 38 => return Color::parse_ansi_iter(values).map(ForegroundColor), + 48 => return Color::parse_ansi_iter(values).map(BackgroundColor), + + 39 => ForegroundColor(Color::Reset), + 49 => BackgroundColor(Color::Reset), + + _ => return None, + }; + + if values.next().is_some() { + return None; + } + + Some(output) + } +} + +impl<'a> Color { + /// Parses an ANSI color sequence. + /// For example: `5;0 -> Black`, `5;26 -> AnsiValue(26)`, `2;50;60;70 -> Rgb(50, 60, 70)`. + /// Invalid sequences map to None. + /// + /// Currently, 3/4 bit color values aren't supported so return None. + /// + /// See also: [Colored::parse_ansi](enum.Colored.html#method.parse_ansi) + pub fn parse_ansi(ansi: &str) -> Option { + Self::parse_ansi_iter(&mut ansi.split(';')) + } + + /// The logic for parse_ansi, takes an iterator of the sequences terms (the numbers between the + /// ';'). It's a separate function so it can be used by both Color::parse_ansi and + /// colored::parse_ansi. + /// Tested in Colored tests. + fn parse_ansi_iter(values: &mut impl Iterator) -> Option { + let color = match parse_next_u8(values)? { + // 8 bit colors: `5;` + 5 => { + let n = parse_next_u8(values)?; + + use Color::*; + [ + Black, // 0 + DarkRed, // 1 + DarkGreen, // 2 + DarkYellow, // 3 + DarkBlue, // 4 + DarkMagenta, // 5 + DarkCyan, // 6 + Grey, // 7 + DarkGrey, // 8 + Red, // 9 + Green, // 10 + Yellow, // 11 + Blue, // 12 + Magenta, // 13 + Cyan, // 14 + White, // 15 + ] + .get(n as usize) + .copied() + .unwrap_or(Color::AnsiValue(n)) + } + + // 24 bit colors: `2;;;` + 2 => Color::Rgb { + r: parse_next_u8(values)?, + g: parse_next_u8(values)?, + b: parse_next_u8(values)?, + }, + + _ => return None, + }; + // If there's another value, it's unexpected so return None. + if values.next().is_some() { + return None; + } + Some(color) + } +} + +#[cfg(test)] +mod tests { + use crate::style::{Color, Colored}; + + #[test] + fn test_format_fg_color() { + let colored = Colored::ForegroundColor(Color::Red); + assert_eq!(colored.to_string(), "38;5;9"); + } + + #[test] + fn test_format_bg_color() { + let colored = Colored::BackgroundColor(Color::Red); + assert_eq!(colored.to_string(), "48;5;9"); + } + + #[test] + fn test_format_reset_fg_color() { + let colored = Colored::ForegroundColor(Color::Reset); + assert_eq!(colored.to_string(), "39"); + } + + #[test] + fn test_format_reset_bg_color() { + let colored = Colored::BackgroundColor(Color::Reset); + assert_eq!(colored.to_string(), "49"); + } + + #[test] + fn test_format_fg_rgb_color() { + let colored = Colored::BackgroundColor(Color::Rgb { r: 1, g: 2, b: 3 }); + assert_eq!(colored.to_string(), "48;2;1;2;3"); + } + + #[test] + fn test_format_fg_ansi_color() { + let colored = Colored::ForegroundColor(Color::AnsiValue(255)); + assert_eq!(colored.to_string(), "38;5;255"); + } + + #[test] + fn test_parse_ansi_fg() { + test_parse_ansi(Colored::ForegroundColor) + } + + #[test] + fn test_parse_ansi_bg() { + test_parse_ansi(Colored::ForegroundColor) + } + + /// Used for test_parse_ansi_fg and test_parse_ansi_bg + fn test_parse_ansi(bg_or_fg: impl Fn(Color) -> Colored) { + /// Formats a re-parses `color` to check the result. + macro_rules! test { + ($color:expr) => { + let colored = bg_or_fg($color); + assert_eq!(Colored::parse_ansi(&format!("{}", colored)), Some(colored)); + }; + } + + use Color::*; + + test!(Reset); + test!(Black); + test!(DarkGrey); + test!(Red); + test!(DarkRed); + test!(Green); + test!(DarkGreen); + test!(Yellow); + test!(DarkYellow); + test!(Blue); + test!(DarkBlue); + test!(Magenta); + test!(DarkMagenta); + test!(Cyan); + test!(DarkCyan); + test!(White); + test!(Grey); + + // n in 0..=15 will give us the color values above back. + for n in 16..=255 { + test!(AnsiValue(n)); + } + + for r in 0..=255 { + for g in [0, 2, 18, 19, 60, 100, 200, 250, 254, 255].iter().copied() { + for b in [0, 12, 16, 99, 100, 161, 200, 255].iter().copied() { + test!(Rgb { r, g, b }); + } + } + } + } + + #[test] + fn test_parse_invalid_ansi_color() { + /// Checks that trying to parse `s` yields None. + fn test(s: &str) { + assert_eq!(Colored::parse_ansi(s), None); + } + test(""); + test(";"); + test(";;"); + test(";;"); + test("0"); + test("1"); + test("12"); + test("100"); + test("100048949345"); + test("39;"); + test("49;"); + test("39;2"); + test("49;2"); + test("38"); + test("38;"); + test("38;0"); + test("38;5"); + test("38;5;0;"); + test("38;5;0;2"); + test("38;5;80;"); + test("38;5;80;2"); + test("38;5;257"); + test("38;2"); + test("38;2;"); + test("38;2;0"); + test("38;2;0;2"); + test("38;2;0;2;257"); + test("38;2;0;2;25;"); + test("38;2;0;2;25;3"); + test("48"); + test("48;"); + test("48;0"); + test("48;5"); + test("48;5;0;"); + test("48;5;0;2"); + test("48;5;80;"); + test("48;5;80;2"); + test("48;5;257"); + test("48;2"); + test("48;2;"); + test("48;2;0"); + test("48;2;0;2"); + test("48;2;0;2;257"); + test("48;2;0;2;25;"); + test("48;2;0;2;25;3"); + } +} diff --git a/src/style/types.rs b/src/style/types.rs index 7cd7d6eec..74ed652cd 100644 --- a/src/style/types.rs +++ b/src/style/types.rs @@ -1,6 +1,6 @@ -pub use self::{attribute::Attribute, color::Color, colored::Colored, colors::Colors}; - -mod attribute; -mod color; -mod colored; -mod colors; +pub use self::{attribute::Attribute, color::Color, colored::Colored, colors::Colors}; + +mod attribute; +mod color; +mod colored; +mod colors; diff --git a/src/style/types/attribute.rs b/src/style/types/attribute.rs index 8334116dd..3da08fd99 100644 --- a/src/style/types/attribute.rs +++ b/src/style/types/attribute.rs @@ -1,170 +1,170 @@ -use std::fmt::Display; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use super::super::SetAttribute; - -// This macro generates the Attribute enum, its iterator -// function, and the static array containing the sgr code -// of each attribute -macro_rules! Attribute { - ( - $( - $(#[$inner:ident $($args:tt)*])* - $name:ident = $sgr:expr, - )* - ) => { - /// Represents an attribute. - /// - /// # Platform-specific Notes - /// - /// * Only UNIX and Windows 10 terminals do support text attributes. - /// * Keep in mind that not all terminals support all attributes. - /// * Crossterm implements almost all attributes listed in the - /// [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters). - /// - /// | Attribute | Windows | UNIX | Notes | - /// | :-- | :--: | :--: | :-- | - /// | `Reset` | ✓ | ✓ | | - /// | `Bold` | ✓ | ✓ | | - /// | `Dim` | ✓ | ✓ | | - /// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. | - /// | `Underlined` | ✓ | ✓ | | - /// | `SlowBlink` | ? | ? | Not widely supported, sometimes treated as inverse. | - /// | `RapidBlink` | ? | ? | Not widely supported. MS-DOS ANSI.SYS; 150+ per minute. | - /// | `Reverse` | ✓ | ✓ | | - /// | `Hidden` | ✓ | ✓ | Also known as Conceal. | - /// | `Fraktur` | ✗ | ✓ | Legible characters, but marked for deletion. | - /// | `DefaultForegroundColor` | ? | ? | Implementation specific (according to standard). | - /// | `DefaultBackgroundColor` | ? | ? | Implementation specific (according to standard). | - /// | `Framed` | ? | ? | Not widely supported. | - /// | `Encircled` | ? | ? | This should turn on the encircled attribute. | - /// | `OverLined` | ? | ? | This should draw a line at the top of the text. | - /// - /// # Examples - /// - /// Basic usage: - /// - /// ```no_run - /// use crossterm::style::Attribute; - /// - /// println!( - /// "{} Underlined {} No Underline", - /// Attribute::Underlined, - /// Attribute::NoUnderline - /// ); - /// ``` - /// - /// Style existing text: - /// - /// ```no_run - /// use crossterm::style::Styler; - /// - /// println!("{}", "Bold text".bold()); - /// println!("{}", "Underlined text".underlined()); - /// println!("{}", "Negative text".negative()); - /// ``` - #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] - #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] - pub enum Attribute { - $( - $(#[$inner $($args)*])* - $name, - )* - } - - pub static SGR: &'static[i16] = &[ - $($sgr,)* - ]; - - impl Attribute { - /// Iterates over all the variants of the Attribute enum. - pub fn iterator() -> impl Iterator { - use self::Attribute::*; - [ $($name,)* ].iter().copied() - } - } - } -} - -Attribute! { - /// Resets all the attributes. - Reset = 0, - /// Increases the text intensity. - Bold = 1, - /// Decreases the text intensity. - Dim = 2, - /// Emphasises the text. - Italic = 3, - /// Underlines the text. - Underlined = 4, - /// Makes the text blinking (< 150 per minute). - SlowBlink = 5, - /// Makes the text blinking (>= 150 per minute). - RapidBlink = 6, - /// Swaps foreground and background colors. - Reverse = 7, - /// Hides the text (also known as Conceal). - Hidden = 8, - /// Crosses the text. - CrossedOut = 9, - /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface. - /// - /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols). - Fraktur = 20, - /// Turns off the `Bold` attribute. - Inconsistent - Prefer to use NormalIntensity - NoBold = 21, - /// Switches the text back to normal intensity (no bold, italic). - NormalIntensity = 22, - /// Turns off the `Italic` attribute. - NoItalic = 23, - /// Turns off the `Underlined` attribute. - NoUnderline = 24, - /// Turns off the text blinking (`SlowBlink` or `RapidBlink`). - NoBlink = 25, - /// Turns off the `Reverse` attribute. - NoReverse = 27, - /// Turns off the `Hidden` attribute. - NoHidden = 28, - /// Turns off the `CrossedOut` attribute. - NotCrossedOut = 29, - /// Makes the text framed. - Framed = 51, - /// Makes the text encircled. - Encircled = 52, - /// Draws a line at the top of the text. - OverLined = 53, - /// Turns off the `Frame` and `Encircled` attributes. - NotFramedOrEncircled = 54, - /// Turns off the `OverLined` attribute. - NotOverLined = 55, - #[doc(hidden)] - __Nonexhaustive = 255, -} - -impl Display for Attribute { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "{}", SetAttribute(*self))?; - Ok(()) - } -} - -impl Attribute { - /// Returns a u32 with one bit set, which is the - /// signature of this attribute in the Attributes - /// bitset. - /// - /// The +1 enables storing Reset (whose index is 0) - /// in the bitset Attributes. - #[inline(always)] - pub const fn bytes(self) -> u32 { - 1 << ((self as u32) + 1) - } - /// Returns the SGR attribute value. - /// - /// See https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters - pub fn sgr(self) -> i16 { - SGR[self as usize] - } -} +use std::fmt::Display; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +use super::super::SetAttribute; + +// This macro generates the Attribute enum, its iterator +// function, and the static array containing the sgr code +// of each attribute +macro_rules! Attribute { + ( + $( + $(#[$inner:ident $($args:tt)*])* + $name:ident = $sgr:expr, + )* + ) => { + /// Represents an attribute. + /// + /// # Platform-specific Notes + /// + /// * Only UNIX and Windows 10 terminals do support text attributes. + /// * Keep in mind that not all terminals support all attributes. + /// * Crossterm implements almost all attributes listed in the + /// [SGR parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters). + /// + /// | Attribute | Windows | UNIX | Notes | + /// | :-- | :--: | :--: | :-- | + /// | `Reset` | ✓ | ✓ | | + /// | `Bold` | ✓ | ✓ | | + /// | `Dim` | ✓ | ✓ | | + /// | `Italic` | ? | ? | Not widely supported, sometimes treated as inverse. | + /// | `Underlined` | ✓ | ✓ | | + /// | `SlowBlink` | ? | ? | Not widely supported, sometimes treated as inverse. | + /// | `RapidBlink` | ? | ? | Not widely supported. MS-DOS ANSI.SYS; 150+ per minute. | + /// | `Reverse` | ✓ | ✓ | | + /// | `Hidden` | ✓ | ✓ | Also known as Conceal. | + /// | `Fraktur` | ✗ | ✓ | Legible characters, but marked for deletion. | + /// | `DefaultForegroundColor` | ? | ? | Implementation specific (according to standard). | + /// | `DefaultBackgroundColor` | ? | ? | Implementation specific (according to standard). | + /// | `Framed` | ? | ? | Not widely supported. | + /// | `Encircled` | ? | ? | This should turn on the encircled attribute. | + /// | `OverLined` | ? | ? | This should draw a line at the top of the text. | + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```no_run + /// use crossterm::style::Attribute; + /// + /// println!( + /// "{} Underlined {} No Underline", + /// Attribute::Underlined, + /// Attribute::NoUnderline + /// ); + /// ``` + /// + /// Style existing text: + /// + /// ```no_run + /// use crossterm::style::Styler; + /// + /// println!("{}", "Bold text".bold()); + /// println!("{}", "Underlined text".underlined()); + /// println!("{}", "Negative text".negative()); + /// ``` + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] + #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] + pub enum Attribute { + $( + $(#[$inner $($args)*])* + $name, + )* + } + + pub static SGR: &'static[i16] = &[ + $($sgr,)* + ]; + + impl Attribute { + /// Iterates over all the variants of the Attribute enum. + pub fn iterator() -> impl Iterator { + use self::Attribute::*; + [ $($name,)* ].iter().copied() + } + } + } +} + +Attribute! { + /// Resets all the attributes. + Reset = 0, + /// Increases the text intensity. + Bold = 1, + /// Decreases the text intensity. + Dim = 2, + /// Emphasises the text. + Italic = 3, + /// Underlines the text. + Underlined = 4, + /// Makes the text blinking (< 150 per minute). + SlowBlink = 5, + /// Makes the text blinking (>= 150 per minute). + RapidBlink = 6, + /// Swaps foreground and background colors. + Reverse = 7, + /// Hides the text (also known as Conceal). + Hidden = 8, + /// Crosses the text. + CrossedOut = 9, + /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface. + /// + /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols). + Fraktur = 20, + /// Turns off the `Bold` attribute. - Inconsistent - Prefer to use NormalIntensity + NoBold = 21, + /// Switches the text back to normal intensity (no bold, italic). + NormalIntensity = 22, + /// Turns off the `Italic` attribute. + NoItalic = 23, + /// Turns off the `Underlined` attribute. + NoUnderline = 24, + /// Turns off the text blinking (`SlowBlink` or `RapidBlink`). + NoBlink = 25, + /// Turns off the `Reverse` attribute. + NoReverse = 27, + /// Turns off the `Hidden` attribute. + NoHidden = 28, + /// Turns off the `CrossedOut` attribute. + NotCrossedOut = 29, + /// Makes the text framed. + Framed = 51, + /// Makes the text encircled. + Encircled = 52, + /// Draws a line at the top of the text. + OverLined = 53, + /// Turns off the `Frame` and `Encircled` attributes. + NotFramedOrEncircled = 54, + /// Turns off the `OverLined` attribute. + NotOverLined = 55, + #[doc(hidden)] + __Nonexhaustive = 255, +} + +impl Display for Attribute { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", SetAttribute(*self))?; + Ok(()) + } +} + +impl Attribute { + /// Returns a u32 with one bit set, which is the + /// signature of this attribute in the Attributes + /// bitset. + /// + /// The +1 enables storing Reset (whose index is 0) + /// in the bitset Attributes. + #[inline(always)] + pub const fn bytes(self) -> u32 { + 1 << ((self as u32) + 1) + } + /// Returns the SGR attribute value. + /// + /// See https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters + pub fn sgr(self) -> i16 { + SGR[self as usize] + } +} diff --git a/src/style/types/color.rs b/src/style/types/color.rs index 9507289af..41aea37fe 100644 --- a/src/style/types/color.rs +++ b/src/style/types/color.rs @@ -1,185 +1,185 @@ -use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr}; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// Represents a color. -/// -/// # Platform-specific Notes -/// -/// The following list of 16 base colors are available for almost all terminals (Windows 7 and 8 included). -/// -/// | Light | Dark | -/// | :--| :-- | -/// | `Grey` | `Black` | -/// | `Red` | `DarkRed` | -/// | `Green` | `DarkGreen` | -/// | `Yellow` | `DarkYellow` | -/// | `Blue` | `DarkBlue` | -/// | `Magenta` | `DarkMagenta` | -/// | `Cyan` | `DarkCyan` | -/// | `White` | `DarkWhite` | -/// -/// Most UNIX terminals and Windows 10 consoles support additional colors. -/// See [`Color::Rgb`](enum.Color.html#variant.Rgb) or [`Color::AnsiValue`](enum.Color.html#variant.AnsiValue) for -/// more info. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub enum Color { - /// Resets the terminal color. - Reset, - - /// Black color. - Black, - - /// Dark grey color. - DarkGrey, - - /// Light red color. - Red, - - /// Dark red color. - DarkRed, - - /// Light green color. - Green, - - /// Dark green color. - DarkGreen, - - /// Light yellow color. - Yellow, - - /// Dark yellow color. - DarkYellow, - - /// Light blue color. - Blue, - - /// Dark blue color. - DarkBlue, - - /// Light magenta color. - Magenta, - - /// Dark magenta color. - DarkMagenta, - - /// Light cyan color. - Cyan, - - /// Dark cyan color. - DarkCyan, - - /// White color. - White, - - /// Grey color. - Grey, - - /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info. - /// - /// Most UNIX terminals and Windows 10 supported only. - /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. - Rgb { r: u8, g: u8, b: u8 }, - - /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info. - /// - /// Most UNIX terminals and Windows 10 supported only. - /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. - AnsiValue(u8), -} - -impl TryFrom<&str> for Color { - type Error = (); - - /// Try to create a `Color` from the string representation. This returns an error if the string does not match. - fn try_from(src: &str) -> Result { - let src = src.to_lowercase(); - - match src.as_ref() { - "black" => Ok(Color::Black), - "dark_grey" => Ok(Color::DarkGrey), - "red" => Ok(Color::Red), - "dark_red" => Ok(Color::DarkRed), - "green" => Ok(Color::Green), - "dark_green" => Ok(Color::DarkGreen), - "yellow" => Ok(Color::Yellow), - "dark_yellow" => Ok(Color::DarkYellow), - "blue" => Ok(Color::Blue), - "dark_blue" => Ok(Color::DarkBlue), - "magenta" => Ok(Color::Magenta), - "dark_magenta" => Ok(Color::DarkMagenta), - "cyan" => Ok(Color::Cyan), - "dark_cyan" => Ok(Color::DarkCyan), - "white" => Ok(Color::White), - "grey" => Ok(Color::Grey), - _ => Err(()), - } - } -} - -impl FromStr for Color { - type Err = (); - - /// Creates a `Color` from the string representation. - /// - /// # Notes - /// - /// * Returns `Color::White` in case of an unknown color. - /// * Does not return `Err` and you can safely unwrap. - fn from_str(src: &str) -> Result { - Ok(Color::try_from(src).unwrap_or(Color::White)) - } -} - -impl From<(u8, u8, u8)> for Color { - /// Creates a 'Color' from the tuple representation. - fn from(val: (u8, u8, u8)) -> Self { - let (r, g, b) = val; - Self::Rgb { r, g, b } - } -} - -#[cfg(test)] -mod tests { - use super::Color; - - #[test] - fn test_known_color_conversion() { - assert_eq!("black".parse(), Ok(Color::Black)); - assert_eq!("dark_grey".parse(), Ok(Color::DarkGrey)); - assert_eq!("red".parse(), Ok(Color::Red)); - assert_eq!("dark_red".parse(), Ok(Color::DarkRed)); - assert_eq!("green".parse(), Ok(Color::Green)); - assert_eq!("dark_green".parse(), Ok(Color::DarkGreen)); - assert_eq!("yellow".parse(), Ok(Color::Yellow)); - assert_eq!("dark_yellow".parse(), Ok(Color::DarkYellow)); - assert_eq!("blue".parse(), Ok(Color::Blue)); - assert_eq!("dark_blue".parse(), Ok(Color::DarkBlue)); - assert_eq!("magenta".parse(), Ok(Color::Magenta)); - assert_eq!("dark_magenta".parse(), Ok(Color::DarkMagenta)); - assert_eq!("cyan".parse(), Ok(Color::Cyan)); - assert_eq!("dark_cyan".parse(), Ok(Color::DarkCyan)); - assert_eq!("white".parse(), Ok(Color::White)); - assert_eq!("grey".parse(), Ok(Color::Grey)); - } - - #[test] - fn test_unknown_color_conversion_yields_white() { - assert_eq!("foo".parse(), Ok(Color::White)); - } - - #[test] - fn test_know_rgb_color_conversion() { - assert_eq!(Color::from((0, 0, 0)), Color::Rgb { r: 0, g: 0, b: 0 }); - assert_eq!( - Color::from((255, 255, 255)), - Color::Rgb { - r: 255, - g: 255, - b: 255 - } - ); - } -} +use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Represents a color. +/// +/// # Platform-specific Notes +/// +/// The following list of 16 base colors are available for almost all terminals (Windows 7 and 8 included). +/// +/// | Light | Dark | +/// | :--| :-- | +/// | `Grey` | `Black` | +/// | `Red` | `DarkRed` | +/// | `Green` | `DarkGreen` | +/// | `Yellow` | `DarkYellow` | +/// | `Blue` | `DarkBlue` | +/// | `Magenta` | `DarkMagenta` | +/// | `Cyan` | `DarkCyan` | +/// | `White` | `DarkWhite` | +/// +/// Most UNIX terminals and Windows 10 consoles support additional colors. +/// See [`Color::Rgb`](enum.Color.html#variant.Rgb) or [`Color::AnsiValue`](enum.Color.html#variant.AnsiValue) for +/// more info. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum Color { + /// Resets the terminal color. + Reset, + + /// Black color. + Black, + + /// Dark grey color. + DarkGrey, + + /// Light red color. + Red, + + /// Dark red color. + DarkRed, + + /// Light green color. + Green, + + /// Dark green color. + DarkGreen, + + /// Light yellow color. + Yellow, + + /// Dark yellow color. + DarkYellow, + + /// Light blue color. + Blue, + + /// Dark blue color. + DarkBlue, + + /// Light magenta color. + Magenta, + + /// Dark magenta color. + DarkMagenta, + + /// Light cyan color. + Cyan, + + /// Dark cyan color. + DarkCyan, + + /// White color. + White, + + /// Grey color. + Grey, + + /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info. + /// + /// Most UNIX terminals and Windows 10 supported only. + /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. + Rgb { r: u8, g: u8, b: u8 }, + + /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info. + /// + /// Most UNIX terminals and Windows 10 supported only. + /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info. + AnsiValue(u8), +} + +impl TryFrom<&str> for Color { + type Error = (); + + /// Try to create a `Color` from the string representation. This returns an error if the string does not match. + fn try_from(src: &str) -> Result { + let src = src.to_lowercase(); + + match src.as_ref() { + "black" => Ok(Color::Black), + "dark_grey" => Ok(Color::DarkGrey), + "red" => Ok(Color::Red), + "dark_red" => Ok(Color::DarkRed), + "green" => Ok(Color::Green), + "dark_green" => Ok(Color::DarkGreen), + "yellow" => Ok(Color::Yellow), + "dark_yellow" => Ok(Color::DarkYellow), + "blue" => Ok(Color::Blue), + "dark_blue" => Ok(Color::DarkBlue), + "magenta" => Ok(Color::Magenta), + "dark_magenta" => Ok(Color::DarkMagenta), + "cyan" => Ok(Color::Cyan), + "dark_cyan" => Ok(Color::DarkCyan), + "white" => Ok(Color::White), + "grey" => Ok(Color::Grey), + _ => Err(()), + } + } +} + +impl FromStr for Color { + type Err = (); + + /// Creates a `Color` from the string representation. + /// + /// # Notes + /// + /// * Returns `Color::White` in case of an unknown color. + /// * Does not return `Err` and you can safely unwrap. + fn from_str(src: &str) -> Result { + Ok(Color::try_from(src).unwrap_or(Color::White)) + } +} + +impl From<(u8, u8, u8)> for Color { + /// Creates a 'Color' from the tuple representation. + fn from(val: (u8, u8, u8)) -> Self { + let (r, g, b) = val; + Self::Rgb { r, g, b } + } +} + +#[cfg(test)] +mod tests { + use super::Color; + + #[test] + fn test_known_color_conversion() { + assert_eq!("black".parse(), Ok(Color::Black)); + assert_eq!("dark_grey".parse(), Ok(Color::DarkGrey)); + assert_eq!("red".parse(), Ok(Color::Red)); + assert_eq!("dark_red".parse(), Ok(Color::DarkRed)); + assert_eq!("green".parse(), Ok(Color::Green)); + assert_eq!("dark_green".parse(), Ok(Color::DarkGreen)); + assert_eq!("yellow".parse(), Ok(Color::Yellow)); + assert_eq!("dark_yellow".parse(), Ok(Color::DarkYellow)); + assert_eq!("blue".parse(), Ok(Color::Blue)); + assert_eq!("dark_blue".parse(), Ok(Color::DarkBlue)); + assert_eq!("magenta".parse(), Ok(Color::Magenta)); + assert_eq!("dark_magenta".parse(), Ok(Color::DarkMagenta)); + assert_eq!("cyan".parse(), Ok(Color::Cyan)); + assert_eq!("dark_cyan".parse(), Ok(Color::DarkCyan)); + assert_eq!("white".parse(), Ok(Color::White)); + assert_eq!("grey".parse(), Ok(Color::Grey)); + } + + #[test] + fn test_unknown_color_conversion_yields_white() { + assert_eq!("foo".parse(), Ok(Color::White)); + } + + #[test] + fn test_know_rgb_color_conversion() { + assert_eq!(Color::from((0, 0, 0)), Color::Rgb { r: 0, g: 0, b: 0 }); + assert_eq!( + Color::from((255, 255, 255)), + Color::Rgb { + r: 255, + g: 255, + b: 255 + } + ); + } +} diff --git a/src/style/types/colored.rs b/src/style/types/colored.rs index 1ee8a2de9..adfb0bd60 100644 --- a/src/style/types/colored.rs +++ b/src/style/types/colored.rs @@ -1,17 +1,17 @@ -use crate::style::Color; - -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -/// Represents a foreground or background color. -/// -/// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied -/// using the [SetColors](struct.SetColors.html) command. -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub enum Colored { - /// A foreground color. - ForegroundColor(Color), - /// A background color. - BackgroundColor(Color), -} +use crate::style::Color; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Represents a foreground or background color. +/// +/// This can be converted to a [Colors](struct.Colors.html) by calling `into()` and applied +/// using the [SetColors](struct.SetColors.html) command. +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum Colored { + /// A foreground color. + ForegroundColor(Color), + /// A background color. + BackgroundColor(Color), +} diff --git a/src/style/types/colors.rs b/src/style/types/colors.rs index 44e4233aa..a5ce6327b 100644 --- a/src/style/types/colors.rs +++ b/src/style/types/colors.rs @@ -1,230 +1,230 @@ -use crate::style::{Color, Colored}; - -/// Represents, optionally, a foreground and/or a background color. -/// -/// It can be applied using the `SetColors` command. -/// -/// It can also be created from a [Colored](enum.Colored.html) value or a tuple of -/// `(Color, Color)` in the order `(foreground, background)`. -/// -/// The [then](#method.then) method can be used to combine `Colors` values. -/// -/// For example: -/// ```no_run -/// use crossterm::style::{Color, Colors, Colored}; -/// -/// // An example color, loaded from a config, file in ANSI format. -/// let config_color = "38;2;23;147;209"; -/// -/// // Default to green text on a black background. -/// let default_colors = Colors::new(Color::Green, Color::Black); -/// // Load a colored value from a config and override the default colors -/// let colors = match Colored::parse_ansi(config_color) { -/// Some(colored) => default_colors.then(&colored.into()), -/// None => default_colors, -/// }; -/// ``` -/// -/// See [Color](enum.Color.html). -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Colors { - pub foreground: Option, - pub background: Option, -} - -impl Colors { - /// Returns a new `Color` which, when applied, has the same effect as applying `self` and *then* - /// `other`. - pub fn then(&self, other: &Colors) -> Colors { - Colors { - foreground: other.foreground.or(self.foreground), - background: other.background.or(self.background), - } - } -} - -impl Colors { - pub fn new(foreground: Color, background: Color) -> Colors { - Colors { - foreground: Some(foreground), - background: Some(background), - } - } -} - -impl From for Colors { - fn from(colored: Colored) -> Colors { - match colored { - Colored::ForegroundColor(color) => Colors { - foreground: Some(color), - background: None, - }, - Colored::BackgroundColor(color) => Colors { - foreground: None, - background: Some(color), - }, - } - } -} - -#[cfg(test)] -mod tests { - use crate::style::{Color, Colors}; - - #[test] - fn test_colors_then() { - use Color::*; - - assert_eq!( - Colors { - foreground: None, - background: None, - } - .then(&Colors { - foreground: None, - background: None, - }), - Colors { - foreground: None, - background: None, - } - ); - - assert_eq!( - Colors { - foreground: None, - background: None, - } - .then(&Colors { - foreground: Some(Black), - background: None, - }), - Colors { - foreground: Some(Black), - background: None, - } - ); - - assert_eq!( - Colors { - foreground: None, - background: None, - } - .then(&Colors { - foreground: None, - background: Some(Grey), - }), - Colors { - foreground: None, - background: Some(Grey), - } - ); - - assert_eq!( - Colors { - foreground: None, - background: None, - } - .then(&Colors::new(White, Grey)), - Colors::new(White, Grey), - ); - - assert_eq!( - Colors { - foreground: None, - background: Some(Blue), - } - .then(&Colors::new(White, Grey)), - Colors::new(White, Grey), - ); - - assert_eq!( - Colors { - foreground: Some(Blue), - background: None, - } - .then(&Colors::new(White, Grey)), - Colors::new(White, Grey), - ); - - assert_eq!( - Colors::new(Blue, Green).then(&Colors::new(White, Grey)), - Colors::new(White, Grey), - ); - - assert_eq!( - Colors { - foreground: Some(Blue), - background: Some(Green), - } - .then(&Colors { - foreground: None, - background: Some(Grey), - }), - Colors { - foreground: Some(Blue), - background: Some(Grey), - } - ); - - assert_eq!( - Colors { - foreground: Some(Blue), - background: Some(Green), - } - .then(&Colors { - foreground: Some(White), - background: None, - }), - Colors { - foreground: Some(White), - background: Some(Green), - } - ); - - assert_eq!( - Colors { - foreground: Some(Blue), - background: Some(Green), - } - .then(&Colors { - foreground: None, - background: None, - }), - Colors { - foreground: Some(Blue), - background: Some(Green), - } - ); - - assert_eq!( - Colors { - foreground: None, - background: Some(Green), - } - .then(&Colors { - foreground: None, - background: None, - }), - Colors { - foreground: None, - background: Some(Green), - } - ); - - assert_eq!( - Colors { - foreground: Some(Blue), - background: None, - } - .then(&Colors { - foreground: None, - background: None, - }), - Colors { - foreground: Some(Blue), - background: None, - } - ); - } -} +use crate::style::{Color, Colored}; + +/// Represents, optionally, a foreground and/or a background color. +/// +/// It can be applied using the `SetColors` command. +/// +/// It can also be created from a [Colored](enum.Colored.html) value or a tuple of +/// `(Color, Color)` in the order `(foreground, background)`. +/// +/// The [then](#method.then) method can be used to combine `Colors` values. +/// +/// For example: +/// ```no_run +/// use crossterm::style::{Color, Colors, Colored}; +/// +/// // An example color, loaded from a config, file in ANSI format. +/// let config_color = "38;2;23;147;209"; +/// +/// // Default to green text on a black background. +/// let default_colors = Colors::new(Color::Green, Color::Black); +/// // Load a colored value from a config and override the default colors +/// let colors = match Colored::parse_ansi(config_color) { +/// Some(colored) => default_colors.then(&colored.into()), +/// None => default_colors, +/// }; +/// ``` +/// +/// See [Color](enum.Color.html). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Colors { + pub foreground: Option, + pub background: Option, +} + +impl Colors { + /// Returns a new `Color` which, when applied, has the same effect as applying `self` and *then* + /// `other`. + pub fn then(&self, other: &Colors) -> Colors { + Colors { + foreground: other.foreground.or(self.foreground), + background: other.background.or(self.background), + } + } +} + +impl Colors { + pub fn new(foreground: Color, background: Color) -> Colors { + Colors { + foreground: Some(foreground), + background: Some(background), + } + } +} + +impl From for Colors { + fn from(colored: Colored) -> Colors { + match colored { + Colored::ForegroundColor(color) => Colors { + foreground: Some(color), + background: None, + }, + Colored::BackgroundColor(color) => Colors { + foreground: None, + background: Some(color), + }, + } + } +} + +#[cfg(test)] +mod tests { + use crate::style::{Color, Colors}; + + #[test] + fn test_colors_then() { + use Color::*; + + assert_eq!( + Colors { + foreground: None, + background: None, + } + .then(&Colors { + foreground: None, + background: None, + }), + Colors { + foreground: None, + background: None, + } + ); + + assert_eq!( + Colors { + foreground: None, + background: None, + } + .then(&Colors { + foreground: Some(Black), + background: None, + }), + Colors { + foreground: Some(Black), + background: None, + } + ); + + assert_eq!( + Colors { + foreground: None, + background: None, + } + .then(&Colors { + foreground: None, + background: Some(Grey), + }), + Colors { + foreground: None, + background: Some(Grey), + } + ); + + assert_eq!( + Colors { + foreground: None, + background: None, + } + .then(&Colors::new(White, Grey)), + Colors::new(White, Grey), + ); + + assert_eq!( + Colors { + foreground: None, + background: Some(Blue), + } + .then(&Colors::new(White, Grey)), + Colors::new(White, Grey), + ); + + assert_eq!( + Colors { + foreground: Some(Blue), + background: None, + } + .then(&Colors::new(White, Grey)), + Colors::new(White, Grey), + ); + + assert_eq!( + Colors::new(Blue, Green).then(&Colors::new(White, Grey)), + Colors::new(White, Grey), + ); + + assert_eq!( + Colors { + foreground: Some(Blue), + background: Some(Green), + } + .then(&Colors { + foreground: None, + background: Some(Grey), + }), + Colors { + foreground: Some(Blue), + background: Some(Grey), + } + ); + + assert_eq!( + Colors { + foreground: Some(Blue), + background: Some(Green), + } + .then(&Colors { + foreground: Some(White), + background: None, + }), + Colors { + foreground: Some(White), + background: Some(Green), + } + ); + + assert_eq!( + Colors { + foreground: Some(Blue), + background: Some(Green), + } + .then(&Colors { + foreground: None, + background: None, + }), + Colors { + foreground: Some(Blue), + background: Some(Green), + } + ); + + assert_eq!( + Colors { + foreground: None, + background: Some(Green), + } + .then(&Colors { + foreground: None, + background: None, + }), + Colors { + foreground: None, + background: Some(Green), + } + ); + + assert_eq!( + Colors { + foreground: Some(Blue), + background: None, + } + .then(&Colors { + foreground: None, + background: None, + }), + Colors { + foreground: Some(Blue), + background: None, + } + ); + } +} diff --git a/src/terminal/sys/unix.rs b/src/terminal/sys/unix.rs index 13b56bd03..04409b7a1 100644 --- a/src/terminal/sys/unix.rs +++ b/src/terminal/sys/unix.rs @@ -1,137 +1,137 @@ -//! UNIX related logic for terminal manipulation. -use std::{io, mem, process, sync::Mutex}; - -use crate::event::sys::unix::file_descriptor::FileDesc; -use lazy_static::lazy_static; -use libc::{ - cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO, - STDOUT_FILENO, TCSANOW, TIOCGWINSZ, -}; - -use crate::error::{ErrorKind, Result}; -use std::fs::File; -use std::os::unix::io::IntoRawFd; - -lazy_static! { - // Some(Termios) -> we're in the raw mode and this is the previous mode - // None -> we're not in the raw mode - static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = Mutex::new(None); -} - -pub(crate) fn is_raw_mode_enabled() -> bool { - TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() -} - -#[allow(clippy::identity_conversion)] -pub(crate) fn size() -> Result<(u16, u16)> { - // http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc - let mut size = winsize { - ws_row: 0, - ws_col: 0, - ws_xpixel: 0, - ws_ypixel: 0, - }; - - let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true))); - let fd = if let Ok(file) = &file { - file.raw_fd() - } else { - // Fallback to libc::STDOUT_FILENO if /dev/tty is missing - STDOUT_FILENO - }; - - if let Ok(true) = wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }) { - Ok((size.ws_col, size.ws_row)) - } else { - tput_size().ok_or_else(|| std::io::Error::last_os_error().into()) - } -} - -pub(crate) fn enable_raw_mode() -> Result<()> { - let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); - - if original_mode.is_some() { - return Ok(()); - } - - let mut ios = get_terminal_attr()?; - let original_mode_ios = ios; - - raw_terminal_attr(&mut ios); - set_terminal_attr(&ios)?; - - // Keep it last - set the original mode only if we were able to switch to the raw mode - *original_mode = Some(original_mode_ios); - - Ok(()) -} - -pub(crate) fn disable_raw_mode() -> Result<()> { - let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); - - if let Some(original_mode_ios) = original_mode.as_ref() { - set_terminal_attr(original_mode_ios)?; - // Keep it last - remove the original mode only if we were able to switch back - *original_mode = None; - } - - Ok(()) -} - -/// execute tput with the given argument and parse -/// the output as a u16. -/// -/// The arg should be "cols" or "lines" -fn tput_value(arg: &str) -> Option { - match process::Command::new("tput").arg(arg).output() { - Ok(process::Output { stdout, .. }) => { - let value = stdout - .iter() - .map(|&b| b as u16) - .take_while(|&b| b >= 48 && b <= 58) - .fold(0, |v, b| v * 10 + (b - 48)); - if value > 0 { - Some(value) - } else { - None - } - } - _ => None, - } -} - -/// Returns the size of the screen as determined by tput. -/// -/// This alternate way of computing the size is useful -/// when in a subshell. -fn tput_size() -> Option<(u16, u16)> { - match (tput_value("cols"), tput_value("lines")) { - (Some(w), Some(h)) => Some((w, h)), - _ => None, - } -} - -// Transform the given mode into an raw mode (non-canonical) mode. -fn raw_terminal_attr(termios: &mut Termios) { - unsafe { cfmakeraw(termios) } -} - -fn get_terminal_attr() -> Result { - unsafe { - let mut termios = mem::zeroed(); - wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?; - Ok(termios) - } -} - -fn set_terminal_attr(termios: &Termios) -> Result { - wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) -} - -pub fn wrap_with_result(result: i32) -> Result { - if result == -1 { - Err(ErrorKind::IoError(io::Error::last_os_error())) - } else { - Ok(true) - } -} +//! UNIX related logic for terminal manipulation. +use std::{io, mem, process, sync::Mutex}; + +use crate::event::sys::unix::file_descriptor::FileDesc; +use lazy_static::lazy_static; +use libc::{ + cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDIN_FILENO, + STDOUT_FILENO, TCSANOW, TIOCGWINSZ, +}; + +use crate::error::{ErrorKind, Result}; +use std::fs::File; +use std::os::unix::io::IntoRawFd; + +lazy_static! { + // Some(Termios) -> we're in the raw mode and this is the previous mode + // None -> we're not in the raw mode + static ref TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = Mutex::new(None); +} + +pub(crate) fn is_raw_mode_enabled() -> bool { + TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap().is_some() +} + +#[allow(clippy::identity_conversion)] +pub(crate) fn size() -> Result<(u16, u16)> { + // http://rosettacode.org/wiki/Terminal_control/Dimensions#Library:_BSD_libc + let mut size = winsize { + ws_row: 0, + ws_col: 0, + ws_xpixel: 0, + ws_ypixel: 0, + }; + + let file = File::open("/dev/tty").map(|file| (FileDesc::new(file.into_raw_fd(), true))); + let fd = if let Ok(file) = &file { + file.raw_fd() + } else { + // Fallback to libc::STDOUT_FILENO if /dev/tty is missing + STDOUT_FILENO + }; + + if let Ok(true) = wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }) { + Ok((size.ws_col, size.ws_row)) + } else { + tput_size().ok_or_else(|| std::io::Error::last_os_error().into()) + } +} + +pub(crate) fn enable_raw_mode() -> Result<()> { + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + + if original_mode.is_some() { + return Ok(()); + } + + let mut ios = get_terminal_attr()?; + let original_mode_ios = ios; + + raw_terminal_attr(&mut ios); + set_terminal_attr(&ios)?; + + // Keep it last - set the original mode only if we were able to switch to the raw mode + *original_mode = Some(original_mode_ios); + + Ok(()) +} + +pub(crate) fn disable_raw_mode() -> Result<()> { + let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock().unwrap(); + + if let Some(original_mode_ios) = original_mode.as_ref() { + set_terminal_attr(original_mode_ios)?; + // Keep it last - remove the original mode only if we were able to switch back + *original_mode = None; + } + + Ok(()) +} + +/// execute tput with the given argument and parse +/// the output as a u16. +/// +/// The arg should be "cols" or "lines" +fn tput_value(arg: &str) -> Option { + match process::Command::new("tput").arg(arg).output() { + Ok(process::Output { stdout, .. }) => { + let value = stdout + .iter() + .map(|&b| b as u16) + .take_while(|&b| b >= 48 && b <= 58) + .fold(0, |v, b| v * 10 + (b - 48)); + if value > 0 { + Some(value) + } else { + None + } + } + _ => None, + } +} + +/// Returns the size of the screen as determined by tput. +/// +/// This alternate way of computing the size is useful +/// when in a subshell. +fn tput_size() -> Option<(u16, u16)> { + match (tput_value("cols"), tput_value("lines")) { + (Some(w), Some(h)) => Some((w, h)), + _ => None, + } +} + +// Transform the given mode into an raw mode (non-canonical) mode. +fn raw_terminal_attr(termios: &mut Termios) { + unsafe { cfmakeraw(termios) } +} + +fn get_terminal_attr() -> Result { + unsafe { + let mut termios = mem::zeroed(); + wrap_with_result(tcgetattr(STDIN_FILENO, &mut termios))?; + Ok(termios) + } +} + +fn set_terminal_attr(termios: &Termios) -> Result { + wrap_with_result(unsafe { tcsetattr(STDIN_FILENO, TCSANOW, termios) }) +} + +pub fn wrap_with_result(result: i32) -> Result { + if result == -1 { + Err(ErrorKind::IoError(io::Error::last_os_error())) + } else { + Ok(true) + } +}