crossterm-0.22.1/.cargo_vcs_info.json0000644000000001120000000000100131310ustar { "git": { "sha1": "db956267f80744b1d8ab08dcd7d92ee21b7a60f2" } } crossterm-0.22.1/.github/CODEOWNERS000064400000000000000000000000160072674642500147070ustar 00000000000000* @TimonPost crossterm-0.22.1/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000010370072674642500201750ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **OS** - e.g. MacOs, Windows, Linux, (version) etc... **Terminal/Console** - e.g. ConHost/xterm/iterm2/Windows Terminal etc. crossterm-0.22.1/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000010700072674642500212250ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered in any** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Anything else? crossterm-0.22.1/.github/workflows/crossterm_test.yml000064400000000000000000000041610072674642500211610ustar 00000000000000name: Crossterm Test on: # Build master branch only push: branches: - master # Build pull requests targeting master branch only pull_request: branches: - master jobs: test: name: ${{matrix.rust}} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-2019, macOS-latest] rust: [stable, nightly] # Allow failures on nightly, it's just informative include: - rust: stable can-fail: false - rust: nightly can-fail: true steps: - name: Checkout Repository uses: actions/checkout@v1 with: fetch-depth: 1 - name: Install Rust uses: hecrj/setup-rust-action@master with: rust-version: ${{ matrix.rust }} components: rustfmt,clippy - name: Toolchain Information run: | rustc --version rustfmt --version rustup --version cargo --version - name: Check Formatting if: matrix.rust == 'stable' run: cargo fmt --all -- --check continue-on-error: ${{ matrix.can-fail }} - name: Clippy run: cargo clippy -- -D clippy::all continue-on-error: ${{ matrix.can-fail }} - name: Test Build run: cargo build continue-on-error: ${{ matrix.can-fail }} - name: Test default features run: cargo test --lib -- --nocapture --test-threads 1 continue-on-error: ${{ matrix.can-fail }} - name: Test serde feature run: cargo test --lib --features serde -- --nocapture --test-threads 1 continue-on-error: ${{ matrix.can-fail }} - name: Test event-stream feature run: cargo test --lib --features event-stream -- --nocapture --test-threads 1 continue-on-error: ${{ matrix.can-fail }} - name: Test all features run: cargo test --all-features -- --nocapture --test-threads 1 continue-on-error: ${{ matrix.can-fail }} - name: Test Packaging if: matrix.rust == 'stable' run: cargo package continue-on-error: ${{ matrix.can-fail }} crossterm-0.22.1/.gitignore000064400000000000000000000000770072674642500137530ustar 00000000000000**/target/ **/.idea/ **/.vscode/ **/*.rs.bk **/Cargo.lock crossterm-0.22.1/.travis.yml000064400000000000000000000017540072674642500140770ustar 00000000000000# Build only pushed (merged) master or any pull request. This avoids the # pull request to be build twice. branches: only: - master language: rust rust: - stable - nightly os: - linux - windows - osx git: depth: 1 quiet: true matrix: allow_failures: - rust: nightly before_script: - export PATH=$PATH:/home/travis/.cargo/bin - rustup component add rustfmt - rustup component add clippy script: - cargo fmt --version - rustup --version - rustc --version - if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi - cargo clippy -- -D clippy::all - cargo build - cargo test --lib -- --nocapture --test-threads 1 - cargo test --lib --features serde -- --nocapture --test-threads 1 - cargo test --lib --features event-stream -- --nocapture --test-threads 1 - cargo test --all-features -- --nocapture --test-threads 1 - if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo package; fi crossterm-0.22.1/CHANGELOG.md000064400000000000000000000747350072674642500136100ustar 00000000000000# Version 0.22.1 - Update yanked version crossterm-winapi and move to crossterm-winapi 0.9.0. - Changed panic to error when calling disable-mouse capture without setting it first. - Update bitflags dependency. # Version 0.22 - Fix serde Color serialisation/deserialization inconsistency. - Update crossterm-winapi 0.8.1 to fix panic for certain mouse events # Version 0.21 - Expose `is_raw` function. - Add 'purge' option on unix system, this clears the entire screen buffer. - Improve serialisation for color enum values. # Version 0.20 - Update from signal-hook with 'mio-feature flag' to signal-hook-mio 0.2.1. - Manually implements Eq, PartialEq and Hash for KeyEvent improving equality checks and hash calculation. - `crossterm::ErrorKind` to `io::Error`. - Added Cursor Shape Support. - Add support for function keys F13...F20. - Support taking any Display in `SetTitle` command. - Remove lazy_static dependency. - Remove extra Clone bounds in the style module. - Add `MoveToRow` command. - Remove writer parameter from execute_winapi # Version 0.19 - Use single thread for async event reader. - Patch timeout handling for event polling this was not working correctly. - Add unix support for more key combinations mainly complex ones with ALT/SHIFT/CTRL. - Derive `PartialEq` and `Eq` for ContentStyle - Fix windows resize event size, this used to be the buffer size but is screen size now. - Change `Command::ansi_code` to `Command::write_ansi`, this way the ansi code will be written to given formatter. # Version 0.18.2 - Fix panic when only setting bold and redirecting stdout. - Use `tty_fd` for set/get terminal attributes # Version 0.18.1 - Fix enabling ANSI support when stdout is redirected - Update crossterm-winapi to 0.6.2 # Version 0.18.0 - Fix get position bug - Fix windows 8 or lower write to user-given stdout instead of stdout. - Make MoveCursor(Left/Right/Up/Dow) command with input 0 not move. - Switch to futures-core to reduce dependencies. - Command API restricts to only accept `std::io::Write` - Make `supports_ansi` public - Implement ALT + numbers windows systems. # Version 0.17.7 - Fix cursor position retrieval bug linux. # 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. crossterm-0.22.1/Cargo.lock0000644000000534720000000000100111250ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] name = "async-channel" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" dependencies = [ "concurrent-queue", "event-listener", "futures-core", ] [[package]] name = "async-executor" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "once_cell", "vec-arena", ] [[package]] name = "async-global-executor" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" dependencies = [ "async-channel", "async-executor", "async-io", "async-mutex", "blocking", "futures-lite", "num_cpus", "once_cell", ] [[package]] name = "async-io" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd" dependencies = [ "concurrent-queue", "fastrand", "futures-lite", "libc", "log", "nb-connect", "once_cell", "parking", "polling", "vec-arena", "waker-fn", "winapi", ] [[package]] name = "async-lock" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" dependencies = [ "event-listener", ] [[package]] name = "async-mutex" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" dependencies = [ "event-listener", ] [[package]] name = "async-std" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "atomic-waker" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blocking" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" dependencies = [ "async-channel", "async-task", "atomic-waker", "fastrand", "futures-lite", "once_cell", ] [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "bytes" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f8e949d755f9d79112b5bb46938e0ef9d3804a0b16dfab13aafcaa5f0fa72" [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] [[package]] name = "crossbeam-utils" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg", "cfg-if 1.0.0", "lazy_static", ] [[package]] name = "crossterm" version = "0.22.1" dependencies = [ "async-std", "bitflags", "crossterm_winapi", "futures", "futures-core", "futures-timer", "libc", "mio", "parking_lot", "serde", "serde_json", "signal-hook", "signal-hook-mio", "tokio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c" dependencies = [ "winapi", ] [[package]] name = "event-listener" version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" dependencies = [ "instant", ] [[package]] name = "futures" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" [[package]] name = "futures-executor" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4caa2b2b68b880003057c1dd49f1ed937e38f22fcf6c212188a121f08cf40a65" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-lite" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "futures-macro" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" dependencies = [ "proc-macro-hack", "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" [[package]] name = "futures-task" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" dependencies = [ "once_cell", ] [[package]] name = "futures-timer" version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project", "pin-utils", "proc-macro-hack", "proc-macro-nested", "slab", ] [[package]] name = "gloo-timers" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "hermit-abi" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ "libc", ] [[package]] name = "instant" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "js-sys" version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" dependencies = [ "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "lock_api" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ "cfg-if 0.1.10", ] [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "mio" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f33bc887064ef1fd66020c9adfc45bb9f33d75a42096c81e7c56c65b75dd1a8b" dependencies = [ "libc", "log", "miow", "ntapi", "winapi", ] [[package]] name = "miow" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2", "winapi", ] [[package]] name = "nb-connect" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" dependencies = [ "libc", "winapi", ] [[package]] name = "ntapi" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ "winapi", ] [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "once_cell" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccb628cad4f84851442432c60ad8e1f607e29752d0bf072cbd0baf28aa34272" dependencies = [ "cfg-if 1.0.0", "instant", "libc", "redox_syscall", "smallvec", "winapi", ] [[package]] name = "pin-project" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccc2237c2c489783abd8c4c80e5450fc0e98644555b1364da68cc29aa151ca7" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8e8d2bf0b23038a4424865103a4df472855692821aab4e4f5c3312d461d9e5f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "polling" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ "cfg-if 0.1.10", "libc", "log", "wepoll-sys", "winapi", ] [[package]] name = "proc-macro-hack" version = "0.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro-nested" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "signal-hook" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "socket2" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", "winapi", ] [[package]] name = "syn" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "tokio" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "once_cell", "parking_lot", "pin-project-lite", "signal-hook-registry", "tokio-macros", "winapi", ] [[package]] name = "tokio-macros" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "vec-arena" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" [[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasm-bindgen" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" dependencies = [ "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" [[package]] name = "web-sys" version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wepoll-sys" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" dependencies = [ "cc", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" crossterm-0.22.1/Cargo.toml0000644000000043060000000000100111400ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "crossterm" version = "0.22.1" authors = ["T. Post"] exclude = ["target", "Cargo.lock"] description = "A crossplatform terminal library for manipulating terminals." documentation = "https://docs.rs/crossterm/" readme = "README.md" keywords = ["event", "color", "cli", "input", "terminal"] categories = ["command-line-interface", "command-line-utilities"] license = "MIT" repository = "https://github.com/crossterm-rs/crossterm" [package.metadata.docs.rs] all-features = true [lib] name = "crossterm" path = "src/lib.rs" [[example]] name = "event-stream-async-std" required-features = ["event-stream"] [[example]] name = "event-stream-tokio" required-features = ["event-stream"] [dependencies.bitflags] version = "1.3" [dependencies.futures-core] version = "0.3" optional = true default-features = false [dependencies.parking_lot] version = "0.11" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dev-dependencies.async-std] version = "1.9" [dev-dependencies.futures] version = "0.3" [dev-dependencies.futures-timer] version = "3.0" [dev-dependencies.serde_json] version = "1.0.45" [dev-dependencies.tokio] version = "1.5" features = ["full"] [features] default = [] event-stream = ["futures-core"] [target."cfg(unix)".dependencies.libc] version = "0.2" [target."cfg(unix)".dependencies.mio] version = "0.7" features = ["os-poll"] [target."cfg(unix)".dependencies.signal-hook] version = "0.3.8" [target."cfg(unix)".dependencies.signal-hook-mio] version = "0.2.1" features = ["support-v0_7"] [target."cfg(windows)".dependencies.crossterm_winapi] version = "0.9" [target."cfg(windows)".dependencies.winapi] version = "0.3.9" features = ["winuser"] crossterm-0.22.1/Cargo.toml.orig000064400000000000000000000034220072674642500146470ustar 00000000000000[package] name = "crossterm" version = "0.22.1" authors = ["T. Post"] description = "A 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-core"] # # Shared dependencies # [dependencies] bitflags = "1.3" parking_lot = "0.11" # optional deps only added when requested futures-core = { 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.9" features = ["winuser"] [target.'cfg(windows)'.dependencies] crossterm_winapi = "0.9" # # UNIX dependencies # [target.'cfg(unix)'.dependencies] libc = "0.2" mio = { version="0.7", features=["os-poll"] } signal-hook = { version = "0.3.8" } signal-hook-mio = { version = "0.2.1", features = ["support-v0_7"] } # # Dev dependencies (examples, ...) # [dev-dependencies] tokio = { version = "1.5", features = ["full"] } futures = "0.3" futures-timer = "3.0" async-std = "1.9" serde_json = "1.0.45" # # Examples # [[example]] name = "event-stream-async-std" required-features = ["event-stream"] [[example]] name = "event-stream-tokio" required-features = ["event-stream"] crossterm-0.22.1/LICENSE000064400000000000000000000020730072674642500127660ustar 00000000000000MIT License Copyright (c) 2019 Timon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. crossterm-0.22.1/README.md000064400000000000000000000150370072674642500132440ustar 00000000000000

[![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 - Enable/disable line wrapping - 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 - Pop!_OS ( Ubuntu ) 20.04 - (Arch, Manjaro) KDE Konsole - (Arch) Kitty - Linux Mint - OpenSuse/Linux Alacritty 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.22" ```

```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 | `parking_lot` | locking `RwLock`s with a timeout, const mutexes. | 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-core`| 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) - [Rusty-rain](https://github.com/cowboy8625/rusty-rain) ## 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 crossterm-0.22.1/docs/.gitignore000064400000000000000000000000060072674642500146730ustar 00000000000000book crossterm-0.22.1/docs/CONTRIBUTING.md000064400000000000000000000036620072674642500151470ustar 00000000000000# Contributing I would appreciate any contributions to this crate. However, some things are handy to know. ## Code Style ### Import Order All imports are semantically grouped and ordered. The order is: - standard library (`use std::...`) - external crates (`use rand::...`) - current crate (`use crate::...`) - parent module (`use super::..`) - current module (`use self::...`) - module declaration (`mod ...`) There must be an empty line between groups. An example: ```rust use crossterm_utils::{csi, write_cout, Result}; use crate::sys::{get_cursor_position, show_cursor}; use super::Cursor; ``` #### CLion Tips The CLion IDE does this for you (_Menu_ -> _Code_ -> _Optimize Imports_). Be aware that the CLion sorts imports in a group in a different way when compared to the `rustfmt`. It's effectively two steps operation to get proper grouping & sorting: * _Menu_ -> _Code_ -> _Optimize Imports_ - group & semantically order imports * `cargo fmt` - fix ordering within the group Second step can be automated via _CLion_ -> _Preferences_ -> _Languages & Frameworks_ -> _Rust_ -> _Rustfmt_ -> _Run rustfmt on save_. ### Max Line Length | Type | Max line length | | :--- | ---: | | Code | 100 | | Comments in the code | 120 | | Documentation | 120 | 100 is the [`max_width`](https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width) default value. 120 is because of the GitHub. The editor & viewer width there is +- 123 characters. ### Warnings The code must be warning free. It's quite hard to find an error if the build logs are polluted with warnings. If you decide to silent a warning with (`#[allow(...)]`), please add a comment why it's required. Always consult the [Travis CI](https://travis-ci.org/crossterm-rs/crossterm/pull_requests) build logs. ### Forbidden Warnings Search for `#![deny(...)]` in the code: * `unused_must_use` * `unused_imports` crossterm-0.22.1/docs/crossterm_c.png000064400000000000000000000224000072674642500157360ustar 00000000000000‰PNG  IHDR,,N£~GsRGBÙÉ, pHYs  šœPLTE³0ôˆtRNSVÀÞÁX…ýÿˆ/D+ .ùú10¾øõ´ËϳÈÎosÌÉ KØÚ²M¨·ª®µ«äþLOå2|™ Öʊƒ†‡H›¸AïñŒ5?•¶šIeìéw÷t¹½q“vèífâQNã¦ûcæ^)Jhîði[PE¢¤‰žÍ-êÐò€ ~(ü¥&%*FöG—7TÓÔ@Äp3çÙn}Wmóy×{g©x§ '‚Õ˜ÛÒu_`Y"ÝRB8:b Ö6 ”dzC]à9¡¼œ4#£Ÿß|® IDATxœíy|L×ûø§–È$"‘ µEB"‚AB,±G)Š Aí‚P!$ª¤©*>Ô–Rû–ÅÒ¢ZÑ–¢¨µýP{UQU{ÛO—o2“¹sï¹g½÷Ì\~¿y¿^ùçÎ=ç>çÉ̹ç<çY .\¸páÂ… .\¸páÂÅ‹ÍKEŠ+®·/n%ÜAp/馷 Ï¥J—ñ„\÷ò 0–…|æY¦t9G öâSÞWð«à£¸î X©¨ÔJ¥Ê~‚o‘@gˆ÷\áù²E#U#¯*ب~äSÝr½FsD|~¨Y¨‘ Áòë!µDeÕ•V§ðƒpç‰ù|Pצ’zõ¥—M ; #¤…5²]oì\Qu§\ÛÈͤÚjê-Q–w3É1¸²Ùv½y¤ÓåÕ•(wQ%î-ZŠ—£‹RZµ?iÙFÒ¢­"ëFL;‰JÜÛ‹3vANGÛžÜ%—«ûë#¶ÃéÜ%¶MWðb¤ŸT%ÆW /—é(«»Wá'¯Jž‚_°Ãž½^ëÝÇ‘Ãp }ãòׯ„üê¹Nâ,WÝ ƒ[>y=^~yˆ¼»ˆ¡Ãò/áŒ9P£utuGJ¯†Œ•2ºàr¸BW…$‚WÇŒ•=dœõj’ì!/ãmÛÐYr5Ñ Ž¾Û“'M†(+eJòÄ©ÊR%½¥5·]íëìáñ¥‹8<ïöÓl=ß„h…é⮲õ[öél†>ƒäÅLÉË—+¼˜>L³²Þ.UØ—× ÉÕYz “ žFég¿3Çru®f] Â;–ž‚Úv—^Lš£ç`µ2O>ÂŒwç$v-Ì,°I¼7=C~uÞÖBSpŒýß_è^TÅ¢‹›*~ΉzX ¯sÑ = «Î‹D/'+ë?zX ¯9YY±z˜–à%K½KpØNVÖ²år<Ò—.¬‹Ï+*…ø•«dÒfNp²²>”í°ëWm/g)¶ñº3mµEZcõ5’ÿäÒEjFüAï¶õzY±#¢!~­ýáaë¬kûÚë¯<}E7$T²]ܨb¼›J[Ûz©Zm¶=ºRµÙâÅ-ÎWž’‰go}ßbüŒ¨« c›mb—Û}Ù•Õ « åœ´º³%Ûë¤$%d2ûf7Ë1äîHa«¹‚dÃâÑÆÜ eçð„ÖEPMš”VꪀàÅ3@ß7¾v¤‚¤ÌP+aó(´Ã}„ùûÿ¢©´Yõ‰ÈwÐ ”)êä;tÁ„ë5ºp+ŠésuË á®ÁOÄo`")èæÄ sù ’0ž©WTœ!ùæqÒ‘Ö³Xes¿Ú—">éâ÷ ]å’[Vee(éG(ýA­ŸÙºžXÔÖòÇ–ä»m°*«I(¹O^˜ÊS 5¹}¹79ë|/˜‡ßÞ‚]g°*ëKçZÙL%’9à‚šÎ¿x釶‘ï“Àª¬(5r©¥;Ù÷|}rO|`T–щS,žDI“¥Î ýfTÖ¨§I–éQ *ÎŒ dTÖ5gNYÒé§9Ûy«>³²93^1è Á~5¡¸ÒdåH•e>ãÀþÖÇ:ÝÚ³nÄkS;¯ø6Òk a~ÿå}'§û`}«zEæ­èœºöëöDÝ}ë¿­b×Õòÿ‹‘°oýz·GS¼Ýq7Ê«yyé3°ìoøÀ¬,³8y€/a;媫ƒã¹ve9¸.0=õ Ÿ‡²UVØE$KùHâÅ`5Õ%‹TYþGŽã½ÈO¡Á£ý#ïÅûsPeb~¥ÿ7·âµÝXG}Bw”ÙÈÀ¸² -‡Ð —(¨S*3ôù^!•eð§·øíù×Ñ=ñŒS·\RPÊ2xRypûb ahø §9RHeÁ²@ØÆQ˜š'Þãø@FÐÊ2„ÒXu÷ð¦…ß]]'ÚµA0Ê2ÜXF}v%lï¬úËÖë˜+§,ÃÏD÷·º|¥I#…³-ãû¼²"ç+uݨÇŸ«tšï¼² 9Up²gpO‘ƒ&3ŽsxÄȼµCKÜ?zæ|ã:¿•o¸ð~Ôƒ©ÉßΣzm”e˜6#ü.þÛ\>‹î»5u¼ýÌ•ÚÈÞ‡kñ°æ9l$ea~m4 #—«:aÎüë%c߯ô,a×ãŽ{aù– €*«©äNh DoÃnÖNÌ0RšÂ½> l[{ž92øíìñ°Ù‘¨,CkŒ‡àNާ+ËÏ.ÚŠ‹ ¿¦âÌòF­¿„‚Z÷o‚½A=,eÊ2ô@ÿcR¶F-í±ì“ÏÜfÕvÇ÷CÝÔɬŸ«ÒÔFó_WÈ:l»I®¬ÁpïóEvÅî®Ö,–s‚‘_¶ 2¾g4`3º{NeðšG3j£=H"z‡\Y†`ò)º ZŒg?oéKø>üu–~óZ0'{@áûf¡‡ žP–¡'e¿FÖvgú©÷ú vŠœ-èõ!üCPY†_){묔G z«ÓöévÙË™Lƒù†PÄ”ªPV(mÀO 6e1d û²Ë©›èûd¡KeÄ e¾¡ìò56e1äŸBeš9Èì}­¥²^¥lÙ‹MY=éó®Ò„kb‚¾†BY”‘>ÃzÂÆ€¡µLä ZÓªîänø£PÖuʆÌ9)MÓiešHêJ‡_ PYA”Y"¦³R¥õuÇ„¸õ %¨@eÁ2¨Ch¢Æ è8eb´}Ø]ô9,(”ñ„ª•oiº“­!ÁÆ„î¥:qt €²:Ó­²J¨Ò•!šê|RHA÷Äžô#re¹Ñ%¡­NY†2tÛ8dû`>IŠÔ"WÖzª|)OUê*ÅKµ›F¥I>Ý„0<`k‰cÇ—¤ïÝf˜=oÉëÇ–¼­¦'¹²¨&•¤Íˆ±PàyÚ¥»üý†Hz‘" Àû·uÍç$þʼ‘[J¡&/S*íYÎ)Ö¼æ[Ó¾œòêË’‰û2Ô©^9ðêIˆË5!zD »ß;YW!þ+Ú/î…4ÌeÌJäÛ…FÐèwR&J•åc7bg4Ê ËŸï}Â+*€m÷v2U\T…Ùß.”ÿ±·Øtõ*u…Ç tïh@YÅÅÙ·ßZ›´õw€^”ﻃ :.«0uE|¾â,i[nó"ï±ÈTŽ2׫TY¢†S¤†½€Ý\[î}ùšR^u*RŒgrLY o±zU›¶S9ÖJ”UJœ &I JYÀ:pƒzWkO¯¹r+p† I<õ!ðô_€[ì%¼ä&þ?YtµCpùû(ò Ä®¬Pñ›8NÞËàÕ`£O%²ž‚úg¥4Ôîz*ÊPKº™Îe 2xƒQ2‘D™]Yö ð$ b;5Éðs,ÕZ'¸o¾ý¿+±-F0äÚÎÐPˆ¼ic,*+PLrãý? “ -ýÚE•2Ðà™œÝ"W¸5ÝnÄ‘¸Ñ”„7†êj´JEY(ó¾wQYÇįzøE®Šhë>&;™<{ùÜF?½(p¯$ëí(qšÇ`ëÓ˜N;P`SV„$œÌQpÓþ6)/ >øY¾žÊ’.š^ÜÆ°#¿©ÌôÀF‰Ú”•+YÿÄÊ¢b°¹ÝN¦®RØÉ¹,Jü¸ôI60ÚÓ¦|~ÑîÊy׿MYÅ%×&Ë=Éþ‡0ž0qÇo¼äÓRZ±×fo/C½éÈîebS–ì j†ô?t‰`ó>€x|‰¸ŸV‰'¡wdŸì´^ݺ.Y÷Ja,n6e^4¾b_åÂϯ~eð÷¬€áì^Ömœçç3å×›Y.ß Ï•Á§tXhD‚MYeeS‹ooÛ¼]n!0¾é}X‰T !£^ÕOÿº¾‡Ê[çB†ƒg^n¯h¿›²‚.˯oˆ:;Ç-ìæHÞgÕÉA*>u}@±æÒçÞóæéSùqµEEµ#Aø›& '¬2ó©h÷5Ö¦ L_Á ÁÀLÔ#Deea\•Q˜+ÓEÕ…ÕaKΘQ¥ÐTºþ?Ï/+õ ûÞ0háïL#Ê_Áס@ îÅ -ßvl ”¯ð¬H…újIL4n73öº÷¢÷“Ó‰6„|vïŸí —é¥áê3ñ ÙEVzGzñ¼;±ØØ<ÿ¡Ê*ÛüÞaIù©…>æ¤' nˆóIàøÞtqÂzgÑ?Œ@Ôv\˜ò!†uÃQú‘ƒÈõ¬ôü3L  ™ê«2KŽ7u{ä%01L¢á*Õ§>|) Q–ÁíàQÒN²áa:ìw>YáIzs?@ç²ëõ¨••ÏÓ_¿Ç¾¿©‘`²»[.AœnߢWÖ5ò`xƒEYù{¿a¶e„ !œšˆÎ¾? õOfðZÛ®F 9Ð RYù‹£ÄläûÓ Ü<ÌùH¯+{šÐ™GÝò@;X›|°?Þ­ʈˆðÊÿàpOG¶¸„oñmP)©¬9žžFŽô¼Å»ôºêÎ(Œc8”ït:C7n­ïb|þIªëUá2X­ ÍBÊÝÖ´ß.Sx1ÜPáqÊþôº~ä7b ”ʼnX,‘Î>¤1~cx Úê)ƒ²þá9fÕ¬ÇÊèݘâߣ:g€øì¼”AYN+ã‚%Œ eíÂÑ­\EB‚ÿ~Òeˆ·Âûè LD9+–ÃÎ\wɦ™$xK–°/šú0N€ìŠí}Óü% £Ÿ/¼)K<¯Órêã¡°ëº_G7§I$oÊRK\¯4Š4&;Ì.VQQ ‚7¼)CL½»cÆÎ è #æ›…*ƒÕþùXÀ 4Y+Š ›¯¥24b΃^WÂGž 7 £©ûK˜JPT@ w> Ê]Su"ˆ,©7>ž.e¸°=˜ùŠAY—2vfB‰‚¶#m¨ý«Ö"tMÝʲ۹Êàj¸D³VU 7Ü­øßâ X£\eµâ=lu´Æ 齕ÎÛ`u˜Êþd9ÏÉÏÐ 'c±pÚ|·è˜eÓPXÒ®TÊ÷G¬t´„)˜‚Ÿ^Aö4vÿ`eÅñ¬VÎ"¼ÂêÀé–Š:dm ½ï^)ã9YÁ§!Ä›ÌlƒÏ碳Ûлésׂ.e. NXTYÛPç†'¡wÿ ,ê@L‡‚*,[SMg¨ioè~‡¥Äû~m£äƒ'ʧ*ç*ò j„g)rSNÛ09q!š8µTd¤Æ¿°Û©jÂׇF-(éº5cî ‘t°h fÊB¾ÑÆ26Öb>ƒ²8'ö·MaTÓå\™Oü}Ï?¼OéjXCî]lz bìAW0 Æadñ)%x+›¡nÙ$c…wþFxøÿê1¦Å¬„ÔÞÊÛH~ðP»Ã)ze9àu·z`b©BðFe÷¿é Y“#, å¦2( ሣfÐç @7ØGÑ\‡F[•ÈÙÞNÁÚ±–ÿP©4C¡ÏÙ‰¼9õÄrÒóåd»²P:ý‘þEøÏðßAŸ]äX˜G±=kGÒ)ÞÐB´Ú+CÔ=ÂG·¡A&ºŒh½ 2aAˆdµbîkÊàÚ-ôÕ¦sàÓ5rà…ÚëÈð#l 1Ò…T†5`ˆê£M^MK_èS†!ïÇÄ¿J9€*>úÞ†´žƒ#YmÎFðC¤£B]áTRVº2çam—PëJuºÑðÅ2ùö$:ÏŒò$+eàmšnAmZ¤ðj ͹–¬ÛÒ£ýü~€«¤Œ!CPÓì1d—èŽg/½²„O5èFÁ&ø3çä$âîcÆ5…æ}TPjs R„´Ô¤?CÍ¡Ï8ÖoEøßBÝf}Tð{ÍtYŽ,Ä”ýãvÓsÚ¬#½²„IÌ:A‚¨Ë€òêŒ38$ý£,ëN?0ü;\b~Ø¡ÃØ­ ÊŠã“ò)â ¨¬þÛp‡ ß5Uužâ‘…ÞûLUÎÐýÙR“Bmˆ*X>Þ?òW¸mp¨±9]}¥òäø«ƒ^´Iþó  –vP?*0U–:ËÇlm†6ôêgÙ}áÁ¤X>Ó’ÈXu¼¹å=½£Ó˜ò½á“&ü¼ôu±,)TUú“Sµ @®H1G1´É‚~Ë~-~/ü?R•ëE`®œ™SÒý¢E féÜÑ†Ž‹èbŠÍÙ«4Cî ¨û‘y@æ÷˜´Šš²v{â ëÖDZ¥¨óBCÁÅ{éŠÞöŸEµÁ´äƒßŒ›Š 6Ó!,§öPêªUš¢u…ä6öc º* <ÅnÎF0×TUR‹ac!#³ï†\ñ>ÀT×°ˆþ ?ÌW_V¡%IïDeQ`ª†[ÿ…j6‡dpøM¥¥e§§‰Yìy'þÅV´¸Šlדhpè¨JWijì$=YÌbßÊ™„ÿN§ ÏC(ª:ù¦©ÐU(U9nœì@;wõÉ&õ…^.£0‘6gÏF·c8‚M;"‰bå¾Eþ¢Vg±ã˜çÐ=šÇ®†ÉÏV¡â’Çv}p´<˜ÎF1ô/zÅ ²€°lbXÁâ} EÕnÑBó*õŽ”HØ»øð–†K™iVuhÑð*uðçILPq]/¬•3j²B1Ÿ_OuèËa˜hõ šãöÇ1Tû…Q=/¿ÚúPEœ'ôÚj§Ø”ÅPGZI¿Âú«´t¢Š“_`ÆTŸfƳЂMY‹5ldî‰yÿR߉*~iS#Únâ³)ËpLí+=Ú/Ãà<©¸SQ·í´Ý$±»­Œì»0€6µ«òyò^ôç!ŸÀé«¿„&U,ˆÙ[þ"ÚÄyÿeg0:‰Æ%|¤ f¨¨c%éÐîuÿßþkÉïw8Ê{X3õ¥Õ3|k zrçcÛõY¼^‡]åÕÛÌFÂê¥ÉkN„˜p{åb³Ã2æ/°/lͱrò·Va7’~ îFùŽq54F^Ó¶´ïÔ¶ÿªIûŠîÓõÛH¯ªXYÙçÊh{Àÿ\…Ø‹Ê/úÒælì¿oÂ2®ñŠü¶kŸÑÅ÷MZÕ¿m§ö[xÖ[ZˆÙÉÿ.=“+žÖÝæ}*`#Êö„Œ)ö­iÎÖ`^¨ùÈ‹\(u‚ìÎH›ÐÃôÏkùÌöà{²ë¸ã(bð3WLׂ4±¹`…²W/zÇ¢Ø ïÅ–*eePcóžT¨¹ëÀZûâð™#Di/vï-ÿæ–Åž€¡ÓñÆ„uY$Ý¿GŸ´àª(’ ŸÅ¤Ëò9ø¶6N›´ZãOIã.ˆ‹„ÌC’ëîù/K“W¶Ï•Ž`Efa]uy²„`:íwßêø° ¨•W7ï>#&MæKd®3±ðƒyuf‡xí'”ã2b7îû³.$•uí½tù?iضo5Øý¢S?‡xoC>a™ÂM  ‰¢`˜Ý˜ûECÙ´ñPx%w!Â’(Ƭ’?/×2}¹Õ?X·0 ¡7mˆ•?×ûÖÊÓŒ^}Þå¿ÉóQ§/7Ÿ%ß\aô…k¶]°¤ v6:„SϽ5‚c²]>¬Ñ[#Hñ}NGŸÜ5tà#ÍuÌ;˜qù&m£ùéÞep[ÔVo倔‰çûnÍ–†÷æjܧ¨ ÛÜ÷ -“§ËcØÀs_Ý‘’éžf9:ˆ Ç’ñf«Å lNéqR3ê[úªF‰ý(qÃC1–Q.ÁˆN Ë_·Çóð.\£™iÖtÞë$'K¹®Rÿ’ýá9fð´FY·S$«**ë!øÑrëká*—"’/•NÇ ~••IýmÑ–•%×ê×ù]ˆ¤¯?¨>ø§÷\ûޤ[çxoXÑ"Ï=ÓÿÿZ6ØQ°HÈèà˜øóÿç¸~§h10<Ü… .\¸páÂ… .\¸xø?ÆxŽ’ßDNÔIEND®B`‚crossterm-0.22.1/docs/crossterm_full.png000064400000000000000000000300560072674642500164640ustar 00000000000000‰PNG  IHDR¼±·•2sRGBÙÉ, pHYs  šœPLTE³0ôˆtRNSZa)_e/ 5Þö÷êc!Šl& ²üÿþÑFW‰u3”áëä±=ióù–*$Ýæ¶@kØò2Íãh Žåñ­-%‘øý¹DÏ+GȦ'bàïŒp…";Ð s·1€ ˜#ƒ~í©.ÛûOI¡:SÖx[‚ÚmÀ94ú°VçyT´ÒtU\ÕÌ’Ü(B`Ä?f•éª0Ÿ×õƬ¾Jq}£„oE8¢âô眵ºX,¼‡šÎ™É ¿ÙPÓÊ]†ÇRË7 d»6ˆ½ð^ÔwM|¯Ár¨g—LC{®‹³«ÅNzjÂxZœ+»IDATxœíy@TUßÇg|RÔÀD±™1³\÷eSL3—À]'H&MPà !3³T R3­Ô’ Ͳȅ2³4I[Ô2MŸJ³”,i±Íž‡z_fîÜ™{Ïrï†æûùG¹sîÙî÷ž{–ßù ¢¯õ¯kj×ñw.ð€ ºõê_ìï\à>! ®kÚ¨q˜¿ó€ M®¹þ†¦²+†fáF£©ùzéÅ7µ¼ù–*Í*4mÕºMÛ[ÛI®D´ï`¬ c§Î’‹]ºvkÓ½‡¹ªs€={U5²Yoç… v·íôé弨ïöþ&£qÀzVø…w6´ uÐà!¢.‡Þ-ˆ7fØp1Ôˆ–­c*®„å§l@3zŒ ÔcÇÙ/XîŽ5:ˆ»§Ÿ(b|lŒýJ÷x?f8Öˆ!M¥s`Ö{» B55ì4Á¦^ëÄ[N&ÕK°Jü÷uáÂ…†“§Hn2Ü75¡J³˜„û’’§Ý=Ý¥¸“;ŠJíp¿!¥¢ƒ0s–K¼Ñ³çTÈ9øš¹©Ž ¦>÷9{½úyiéwÞóÀ|¿ÖTÈ0öú‡ŠW^ä’ê€GúÕ±<:Æ(¡õâZu¢^’ê¼0(Iþ”qKm»ŒeA~* ,Ë´ü³¯›b¿4MÒΗ¯XYÿ‰L©xM«ž\½æ©̧ׅçYm7ZÃÌÏ °‡hµ P,\ãÐàÚû×ÙMúŒ1ºGèz[¯7áÙì &áÂíÓý\( œ)j°Ã­9‰íçsmÝÔ®1õù]†Q·çŠ^Èów©@@P7ß)˜×™byb“»â5µ2¼‡s˜gÜÜN=]¼fz†K…1³z¼”›Ê§šzm,ΜÙhý²¿K‚QÒ^‚)´¡É}íVÜÝP¢]cæ¿Rü],Mô ¥Uå‹¿Ëj" s¶¾úZ;ç’DÔã>Ю±¥AŒß:ªàõ{¶%ú§¬ †±}ÇÎÂþo´J›',‹=ø¤69v\ò滆åjœEÛá˜nH˜˜ôÖÚA¹»ß†åðžàlë¦A›;=ÚÄÖü6i¥EŒ1}:­¼eÔžœö“ã4‰÷›‘¯~Þïënë•„îáïrƒ@Äb‡¾B÷½×xNâÀ5ûµˆ1öÞuö»ƒ· ¤)üÖéEnY<È1üËâçbƒš€S¼FcêÁä'gf„jÐâ â’YJÉZÄ›úþ]ñ·rE ñ‚J`þL‰ÆL¡Ú$FvÞ¿ñƒBM·Èçίóc‘AM!q…&íÉÉÚ㊠{‰Ìžà¿ƒÃ³ 2vÔrEö¡Œê¿ƒCP¼Ú»S2YðÑÇDðj ¿Ô zºkðXA#ÎÛ­}ûx Þüy~,1¨1ôí¨®5’µ¯8oñ¼'Æ»Žú±Ä Æp¬ƒfÉeÎ 4Ó©Ÿ|ê¸;héZǃÖFj—ñ4xèqtÔñé.+¯khQ[jŸaŸ5žXûÄIaÂ+ò‰Ïí;},§& [‚L§¯™óØÊ­/­ÕfÖó¼k‹f‹# "ª¾@õ¤éâý“6g]Íö0›‚,ÿQoy ÷?Sïøž~aAº¨ÃKìê5E~üÅ—ë¿Z:»›à|$¼Ïá½>jƺ‡nº÷ÌiÕMgí³ ECVo=›±ï¶¯a«41åß×™Œ¦Žçžz#뙂oÚýgd¸²Î½sýáo»,´ w‡õ}Qè9„Ÿ^õ᡹…BO!³×a‹ÀÜïÓf6ËUŽÔØú™áæcçg_˜´³°"ºY»6ú¯>@5¢V¾8½`*»á¹Š2ëøâëÏ™ ÒêÀøET×6z_½(I”˾뺹Tñ•8—±åb#ç<Ç~ìqZˆÈ×:®Ši³åžGG‘înF|ÿ"AÃë‘ö¹ú&?\*‹ÕlÖ~èÚª*=¨Ö$´Ô6½ÓýÌø¯j1"èwi‰L½›~<Ìr~tûOci›† ¶ÑÇ…5„ösµHwçîûÛqö;4™¹V2ò/§°Ã%Œëûó¢Ln.J÷Z}W\P“x X]N§?ß›?Û$©3d‡3'ø~ôôu9ÓV}¯ÛO¾('¨t¾SMLÑÝ./S<*eèù6Ž …Ã~UôCd˜9Ru³Å¡o*·„ Ær´¥ò³ W¯Œè 7ú* ZòöÓjšÉlöòBõx<Äñû¿æ³|€êÈw*ËÆè'òá®2·Ä›û§ï2ª!oU´á5»Å—§Tº%Þ÷«Gˆ¨­JûLÎìñ©¿\R¼JoRôlx?2FoQÐKhÆßÚÓ’âݤ Þ¹>Í ¨~<¸˜¿%Ç´è»0ßN®’âÝÇ÷EiúÎOÁÿøC¶FwqvBT¤x/å¨çþŽM€ ÉŸ<¹tûÝsCNõÊËË lÿQ9ë‡ïâœ[y¹y.I‰X¶Z /?ëŽv-9åÅÅŒ½qÅ] x"&Å›T”Æñ§YŽõ5@±n/S-თižhÈ)/;Ék1]N£LŠ7Yvl'óþâÐk‰«g±´;émvd†‚2ÝJX`ßL6úÉ:ëñ wv¼¿Eå—T{¬ÇY­æÈ¥u5ÜkèªÖâ’œìš&¹œéHÖé‚òÎ2n‹ìâ³ Õ—y¿0¼A†ŽWw¯o©7ÒMå Ä•‹ýj«}|ÅÅÄ'Ñ÷Dß5Ô§•ªA§7}ê†À& Wëðò=R® ß|{÷¡„úÁv¢ nãR†?ß‹÷þúPíZ¾žºÕ”°¦ýöQãwÍþÑá‹TÎ=*‡ò˜µwtÙTÈ×BOMØÅ«öÚ®8µû˜¬iåí½¬s-ô š:Cz~1x[¾Ã°ÕЦdf ®uÔåËп]¼º#Ý8$bN7ï±÷µÙcP“±®ûZÅÕÒ So[]>‚xu ã(™èlAKÑj5'Ûø7—k;Û#âµFŒQÊ é–J ŒWSW¼fÏf4b“)è®ìëQUSOà*¤ÎJO[ÞòÊ* §xŸW:Ì-o cz‡ŠŠØâµTÆ@M ±ÛðìZ¥>yñ6ŸZÈWÊEÌêak Æ(«ˆ)^³{»â€m…ÒþŽÖ‡ÞFÇ⬌$gÅD•(Žç™âÍ®,‰òÄk÷¾Â¡·N x³³6Q¼Ö(ºÕgˆ·@³3’“JÌfû"šÙl.IJg¬÷²Ä[tS¡B˜Á·`¢,°Å« jz†µ²&BÏój\ ž”Äì…™“4mî­5YÁÉõÅ—¾Ó ý¤xu)£/*´½/“®x5i·4ÙÀLËŽ%žcõNŠ·hÛ n—ƹt£Ïª¤úàâÕ­è.ÓHtÿ¹}œŸë›ŠäÓµh7ImÙ¶$K‹xG´rJ5œ|¿RwÔ ø™† \“í*^]Ý$~ÊŽÙõ÷Òï;mpL(<ÒBTËXm’–Ã*ó”»¿6ñ&<ÔMø£tß_ï]þy³ÔÄ,uU_ú@©.¸&V¼º®si7«$¬âÊüѯ ‡[gÉÜ:Ó¶‹4éMRo­ÿÿï?öíºú”ˆo^ïïêî‚E™NV‡+ÞÅÎ5Øð-ë„!¼¾öû,UîjI —§>¿«}²\©ñ­ˆEÿéíÿ-M¯mn]x¾³í }«H%ö@@jKâµ&Ì3\O’2ŵ[´ O…=NpÍ7¨›3LrÇHÑÂWo…xçÚ²ÞbøÄ'œ®HL§ï«UðÞxI xSŠæÝ²¸¹s®,ú‚Ë|wFWû¥§œG´*~èíĺg`ËWo…x{¶ÿ/õK×þOÉæ|Sÿ÷x61( W)ò¤5pâµ&Lˆß 5ˆ|×ÕIXxX¸ætål6ªQªe¨&…«^³.èKa¹v™+xÄ*Y E-°z-²=¯&^ëkÊ–o’­¿–þîoâ áZèb¡éµ¨ïvW»|õšuµ/ Üý’£õcÙœ^xè¬7V\¨Ç æÉŸG`‰÷Ú{šç’Î;´r â-í…k¦9ö¿Õ; Ùä"•Y·z‰ð¿§Ž¸%¥nŠžµdöh/+¢zB.Ò”xgLkD½dNrË:Ut›ÐíÛŸÕ™†dòÁ~'Ì.vXþàzŸÚÍ¥CÆD^¸V-¼ׂ_^½òò²âòò4…5@;9åååÅ6*þ­§ý£’—VnK¡¸«ÍK›$‘<{\ż™D‹Í¯[×âⲊ»Ór$?$ ÷É2`)§>ƒZÅk+–cq¾[åräÒµoÐ’cËp~E)݋Ö…|GHç3âïeBÅó¢Ͳ…4E~'=ç¸XºØöw2#¸55p`vÌCÄehSz1¤¾Ó2½»Ë_o^ŽÃºU(¼ËcšãѦåKúÈÞð2嵪¸k=Õáhí{¥¸ÜñS5ŒÛ edÓp2¿@Е8Ò§ -9i]Y³>ù9.8Š4äÓwÆ93(£"¡&» 5éºÑ,fYG¾s‚Ôž¾hcœïxKlÏÁÂPN˜‚ì*Ó¡€ÈâH–¾ÞǨcÌm¿ æ.Á¿M»q¶'o`…–áYÃËé8˜{ÿ{Ý ŽíAÎeHænuÆÅ5ú±‹74¡+aäGÉùO~ãgA¸fe²¿0´xëñÒ;Ù5Ï5Ò°?|õ13' [2üí²'˩גŸ’=#äËVæˆAù6¶£„®ÎÔÙ>@ò)ùiÆÜd*.¸ecQØ_žç~½žÓù°áeGmžð…3GO?ª¢çdøåc¦QÌ‹_9£R¯¥+ãz>õÐTæ²ÏÙq£âæ’8çS#„eQÞÆzÒ«wâUI†ˆ¢xY•'4÷J·Q‡ÈIG³L½"]‰âÔ9¼!”pî ×oyçEçRlæìÇ|Ùð²ã6'þ#öZŒ¦¹ ž¼ûî‚Ë™;‚Â×n sF¥ ^ [•ò®¢AÃö¦“Ì5DÖ›ÁD.¬<ÍÛX½o‰úÒhœü°%ñ² \âìoµÛ· Ø”oQð¤0’h2®)æûÎw‘¹M°–©^fÓkÖõNîàü˹s'˃¥-{‹~ŸáŠIA¼¼š‘ô™m3ƒ|ªñ5hßJ-V¶öýT^ˆWË+it}ùuÊ)™yFŶûø·)Vn…ò9­‹R½Á+?é ÞFêÎ3ßÙŽ½Tãä‰jÅÊeU7åÛÙ­LÑtlþ¤ôTN¾x¹ýQ×C¹ö¤5©ýN¹°ÜÙOå¹x5X¤œ”¼Èü”â¹ÑÙÚ/îm*UT¬V‡e„h¦<0YY½ ¯Ûriˆm¦JÝšL«-Úº·âQ ý;ãd¨â;cô¾žó¥qÅ«ðüTÈ4œ'ÔëŽ ‰°8sÜl</S»¥¬rtKcJ2Ò•Ä«†êF®"µ»É ^Úmò±zŽºH¼ÚyM·@öèRZü²»[©Bóúqv¢,"Õ-FÙÔ`Ñs†vK“íM‘!‰ÞÂ,S/e Ÿœm¿Ñb6g'“÷º„EùŒMöûå™ÍñÔ¢§âetw3²í¹7ÓÝ5W±TS*M6“Í–¬¶ zÂHR5ÖÎw²GmÆðÝ:=æ4ÛR}-J=¬ Ÿø.追<·'ßÌ ?0-ÒͼmFyŒ§âHŠîº,ä,ôË+©J2÷YòfÙœ.Ó¯KXÄ8¢4^^'Ùò7‘h¨ q)­»+¤™z+‰Ù oãLi£Ê\EËãoô*UÞ…›‘œžÁBµVÃXV8Sf¯ñM%õ¯˜ž,vÕi…‡$£CÞ]ÎîarÁèxI[|ÏÄO…“gz²'‰˜1áÛB=4IÔ¬'+ »E—¼¿ôkÅZz©KÙâ|35Jfè­> ñÖÙPl3åh“’;Se'ܯ£ê¨*µ_¥(B ñTçð—¸^¬œ#ç# uÃZí“´ø‰—ñNÊû4ôKI4C,ñ:úS”ð%Úc<ykÀEɾ=ô-Þ:ŸSSÿ?>@ZÉ2"ð±—g}b“ÿ}B%úž›=–xÅf|Zö©2Æd1oMwËÅTbe¬5dgSã|äp)®œ1Uî—Gâ¥8ò»@‰ˆhzâUÏ©f¥Ý"ÆüÑߢYOºu?æÒ®C×Us•Ë ú€Î%޳ý¶32ìÜãAhFh‰â©ðÔkN?Åxnb6#¢-b7/ƒ¡ñdY9éìÝùmöH¼ôô<ù‰¤{„ò¦—‘K1%ò—怮BšŒÆ€hóéQ›ü÷ˆc[Ó=Þ;jTŸKgZ‚T.ÐɶÖ“yd†Ò{O|(…5Ý/¢z®t#æh£¸sàr›“œˆ7­G(8ÞqÑ,S·±ÄËÈšzUÉ×ùé\º&–䟤-9¤§³F¼¶tÊÒ_ë¿™µ@|ût"õñZˆ7ìFºuúf>–!^WÍH§qÓSŒ^Õe¼Áy¼Ä$Ðm°ˆâœ4£ –CÝÁ/=‘KMiÒ¹—÷è\JÄ-}¡3 ÊÑ’ù£k´JâÕ×~î/¶×ñ{Ѩ/QTÿBvû_š±b5ÙKWôí²8Òúˆ§ã¥F  ¥Ö°NÆT°ºž’‚9™‘COÏÇ0Æâ2Ò’ÖŒYü9–xÙéÚ"+” @6Í âZÿÅZÎä9yä¥údCˆ7j5;iS›¿VGLeñê„ox‰«:ŸjðÄШ£k¡äBXBY=y„ lF½ ¨p¬G -ÙÈJY¼­ì¤¤x3U[ˆ—,€‚xKvs†ÿžAD£^Þ*oð˼Ä3-M”*¨þђØu¤Ñao„zê@²á@Dzp`Ùk)¡º+Yw_M¼|+Þ þaß·PK‡‚9GY¹ÝÈMÝÔæÚq²°îŠ—Ñý¤ÑaâuG#å’Õ‡NNR­¯–zb\c$¾RW¥x—ð3ïx«à@~Ë[1nËþR¶ŠÄçøE“çL[`‡AtR䦖ÖXñ¾Ï?¨ÄƒnCˆ·è+~ò…'^–…uW¼Œ¥v:#]ñ'̱ͣÊ'[%7ŒºÑ´K¹úÅûú,®¥ì¥òX4 §«@¼!˸©G?½1X¶ŠZ^×¼S¶2”dÅâ†êã ʹ©¼–WVö«R¼W¬å©÷µ~n×AˆW?”—xÜëôò醪íóÚàÚ2ªk½IS¦œ¡„âæw »ŒIP®š«@¼ÖùG./g×¹HquˆW×t‡3/Ï#=>Uílƒ@^’Ö.¬Ür§„2WçaVÌM’ì²=,ã“éöjÒU)ÞŠïp¿ì ̽äï­SKdzy S¼1Oý^;˜ ê®xCz%†\B6dgijHɸóÒ52#[·å”PüËÌTcÄ«³†=tÿAF%£–Žgò–x—çÿ:‚᥷Ä«}…MŽÅœ¤ª`Vmåe'e¨4Á²}0œÌÈ“ ã ÍuU¹ZÅ«Ó%ô»éÎAT˜Ÿª¥Cá¥-º}ÞÐwnØÃtî®xßWê1Œ\“¯fúUs|²B'±„%:ErÝf(QT°4;œʳÏX×w×CÁÕ+^.øºÕó­Z:ª û€ û¨TÏ~Áë®xæwÔ t=Å¿­š9›Óp|ÿÅ?‰7ŸßvKW*8ÿ¤ÂXæ63®n‘LfW³xuú¦Ô›¹¤ÏPõŽ\ˆwÊ·Tªk9aÝod§xãØßdj®£Ûàü›r˜&@MKä”Êè7ðš^§ÝºÜjòj¯~ÞáRR¼¦úDGòª°ç¥ÅsÎîa·Å˨)â!Ó_`Æf´‘lõÆw:ž£ëB9ó6ò‹'3xå”Pü‹›kÑ!?ògy‹W_+›aä°µ¯`<ÜÄR¤“†¦d€¢mTª¹‹Ç±Ot_¼ôí¤¬é¥]’¸öJÞí8æ“%ÇzŽ;%Wʨ™9Äã“M•Q]¹biÁK9QØÉR¯¤ür#+ÅKo+]¼)3ú¶¦Kg¼L쪊=l”8ȉitªîéÌö:B…Tõ+A]dsz#ž}k£ñ%|ü‰"”^#|4Ú!_(•¾h~ZNW±oãl=YoŒ´$gQÅKW'¡Mº;N>"zÿ¬ô#^9G…¨à­ãj yV"%¨ÉâáD2 ßÅê9Ð!9ÝQ ô„ƒÄŒvÓ'yÆÄƒ*#¨“ž;›Ï‰’FçfâéÉ÷–)[Ep=¢8Ò’9CÏ+¦$’ GgœºÚ•€üKÆßU%¢²(d1“íÆé"iž«¾ðéíD/U2ÄÆxVºÑo‘.RdÛ{L—–® Û' ÃÄë¤àQÞR@G(=,‘ÖÇÈ®å999ö (Ý;…EÝWV^/ÇvŒE^NZ9ù)’»=Pq}(Ño×HûYÒ£ì8+‡8AÂIWUÒ=tîˆëÞ‚•%1@ºrÊöDû}ǶÍéPŸÌ§eöúCO†04c&úÖc’@šŒ9£K¶bóMôéžå¡«uë6êˆ;¥å8Ù6swì5ÝÍýÁU™™RõÅ—¡‡-Ñqïñ¬"MIóÔ¤Eâ툩0ëŽѦ f…hXð¦F¼ÃÈ©uK¼’¿;·iÚÍìDîßà õº›;‰·Öóm¸½¯’¯¹rl6Ü??P¹‚Éî{·<_‘›>éì å•xuMö1ÔØÃñJ¿PnÜÆ:Žœ¿§›, ÃE+™ŸJMwøK¼_}È÷Û¼ùš+JÄË~ÕÃ!'NÄó8ô?åzLªù4*NJsÝJo'Ç~ÎDUo–kP¼Zªª­,ÖLÃïª#>ª!1hj|³dÓr‡ßÄ»’y”•@£Ÿˆ(õmÚÞõÔ§ü‰›tîLg(oÅ+ñéÀaý¼ÄDK-fÅj/M’kP¼ž¤’ho*‡=a­œ3Yjþ¤XøK¼s&35³~W "Oê¯ú tË@6oãÞä%lšû3”×âµ9uæfbÓY3nb¢¶…™{ó$jBÆñƒmêÌ·dOV˜…dµ¨YœIAíl]R0Ò›ÔU.Þà»Ïär7a^™Èy@|¼é7Ðɤ‡æÑœo²)òP’˶̬¾ÇEâ-Œ›l†AclVçýtÔ8[’Ng5+›!)ÇoŽ’!;‹j!bÓÕ| ¯Z©’Ô+ô›ÌÐEl2+s:-5IK^—­^÷ªTâ°%6|ëÙ1Ø’8ø Ynv8)^œeE÷JÈSÝ ç™/Ú UœüYÏVÀ\Q…Év_ÁÉfvχk^IRR–ài8)‰ö'ÃÃbK4Ñh¶Ö…ƒý•MOJŠ×òõ3”ØBÛJf»Ãìå@Û$øè|‹¬6}6…«>dc:UÖ„…žK%Zœ„[>®-îtÍ(2£ `™PoÇÇmé­lt&jØÿî±ÛzàA:3lB9ç5µyð+õ4IP3H8ré:jõà)bGîTÕcÉÕkÍ0Þ ¢×ô]R» ŸÙbeÇŠ]Hõ.h! °ðµ†ªÚõxÌÆpBIt÷F\OH}Ÿ¹y Ï­¥ä³è˜ä÷¥¹¤[îOÆÁªÄd–uuE‡V¼] À¸Ô–ÒOá›Î bÛXûãÙh2—“Ã:˜hxþÞ† ZÆ>Ÿ¿OÛ ˜–8šÞg5K×õ²´K®"¬ÜLÍ“™õåí¾„uÆV‡¶ÃKv3ïzT£W™êµ0c'Þ®ŒÙ¼ƒá¶÷>]Pƒ°<×\PNxaܹ6œÕ­ÿÞpdÝÐÎÃÏ;n[éu²Qí[ÔúO[¹ìÓœ¾“9;ÖRã–ÏŠÌrØçzß8òÕ–ë³Û?wøÑJ~øæ«ÉÌŶ3%¶µŠ }OÛe”»ÔëTœ¢~ªçº`.eãñ{˜ö3ýò¯%'¾\s¬oãºÀÈI):5Eðz›ð-kµ­ÏÊ…BH}­ëíË|×k·úͶG=hi-aæ"aÔK¬¶w_=a³ZPHâÑ`Ø6%F´d(èk§5zPç¶v“¯d¯7ï> Lr4*6¦SÖ³,Ðe§c Dí;â]³Ðùû”Ýö‘ÃÖ{™Žµžàs½¯ké"ñ£ãðãh/sîÁì²Ä Ùkß’¾Jõ<>¢¾: fךYÈŽMtÒ½zz™²—0Ä;\¢°¯í k³ÕK¯é­„¨'JÞ‹] W nÇ h#á£õ3þ&þn=ºÁn[~ÙK_aÇo¢¾É5ò+ÆðÕóR˜B,¸xö2C»Æ[›ŠÍ_‘HaT5¸¶w ­v=ß©®Ø„§LoÃZ©øñˆw €aâ–xSGÓOmæ˜ÏÚð«Wýë)ÇrǬm3 ëÀϘ½½í]ƒ@á_ûX2æ6ÞhûÕz`—x¥Ïý^}ÎÏ‹­ì¡v‚ŸhK}ö&Ðåç½/þfo ‹)ÍjÜ»é·[G:Wc:ŒÜêñ\ïÀ5Íœž­S{]Z— ;º~g]Ç?pz ÐBÔežOÜMçNvk}ZÚ8fž~qÁãóÜŸ 1¿;¦û&×îöÔÓKnÛß|'o¨i¶]-ÔuëÜÀ˜Ò¹ÿײdª‹¶zKÎ¥És‰‚)/6¨ èG_Ùd*\ôV«ŸÞ^?¤…åÈë I)1q‹.ÜùAãm-ü.DQ“ãnÝõÖmç”<õ;0ÿ±qÜñÿ\ÿ×m§c2÷ý‚ƒ²&®ÿ~ñ+7å Ü(=§qS{ø€‹f??~é£_m¯Ýož¸—’1uÜcÃW{äö=ö-ï¨-.ã{‚Ù”~GJþ)Ÿ¹º…ŸªT;¬EG%}ØÆJçBPlêÓ|Ëàü/ZμùÉS§NÅ?¹"©å3»^ؽÉ·N¸ì:cMŸ…ÕaàϱÖÖ@̦M›¢==£p/N©•À?ú¨•Î×O0@ý)?h׸àA—ÔB~ò‡xÏb—%ðžàïý!ÞŸ•Þ@ ÷‡xÿÂá–À{6&I4ÕaÑS¾’kÛæ¹’¿ÊFù»Ü ð ëˆ«s;J&Ü·•ïøÙ "¿^àø»7c;æÀæouØ!ôúì«&QÖ„Þ/øB¼O¯.ÒM±ìæórè6€J è„Í6=uÌÖmû’oÝ×|!Þ²íö´jmoÅæ¹¡Ïß8T-{aðÍ¿p¬‡Õ÷…x;­b·ZŽÔÿzwÖÍëüW^P“°Ü2|»Åiï8åF·ýPkàR-grÁC>h¬€°ÞÇ:WÕKÂÁW?¨ÆMr‰ÎTøñþåžØ> Xõá9‰Ñά8  T—ÉÌgWŸxí çÜW>¦¸iõþ÷Ùû.sµU¿ù»T hñ§(¹Â}å#*.|І©PÂÏ Ôéæg2K¼ðV— aÿ8šÏA“?²›ŒOì%Â¥fRF¼¦LùŠèú6s]ý‘]¹Ž.Ç^Ì.€ª è+»8ÃOïrœÀž ó/–ùÔmÝ"å¡mG’n„3µ™*Ì&Lý>ÖÞå0m…¨ ¬†Ü mšæöœ'^ùä¸UÓ ôu‰“j7söƒºyù’ŽqßE·EÃ7ÛÞ„†a²T E?œ /Ë)rNÌ‘œâ6r{ˆÎš';^mÌGV~Þ`×ðìôJÑŽ5¡ËgÑÆÒïgø©( аMÝ®ŸË§®.Äé¦)æà·6Ÿy!o/ri·Í½6¦ú©ÍDÏ»á]Kº” ~ØoN ª°…È–ÀîvJ¦ü-Ø®Ë3Ý`Óìûì—¬³Ÿ•.›ÔM†tÿèò–}„–Ú+ÍÑ—šø–(ÞUÙÿ¥ú#W:د¬5û1«È iis,¾¤±ÓÌâ8+ÅØðO熞 wÛæB‡-dÇ€?>¬£1üà¿7:/X»LÄ{a›ËIÑÃ[¢1½~ñCà1õÞ+g{¶\)Zoï/¿!Brñè‰iWÆžQÅ™@‘&ËFÉíÇçý»‘ɹwì¡=03W;ú-'²·Áõ\u¤¬›“KGp•óÿG“ñPކ©fIEND®B`‚crossterm-0.22.1/docs/crossterm_full.svg000064400000000000000000000155650072674642500165070ustar 00000000000000 Created by potrace 1.15, written by Peter Selinger 2001-2017 crossterm-0.22.1/docs/know-problems.md000064400000000000000000000012400072674642500160250ustar 00000000000000# Known Problems There are some problems I discovered during development. And I don't think it has to do anything with crossterm but it has to do whit how terminals handle ANSI or WinApi. ## WinAPI - Power shell does not interpreter 'DarkYellow' and is instead using gray instead, cmd is working perfectly fine. - Power shell inserts an '\n' (enter) when the program starts, this enter is the one you pressed when running the command. - After the program ran, power shell will reset the background and foreground colors. ## UNIX-terminals The Arc and Manjaro KDE Konsole's are not seeming to resize the terminal instead they are resizing the buffer. crossterm-0.22.1/examples/README.md000064400000000000000000000022310072674642500150520ustar 00000000000000![Lines of Code][s7] [![MIT][s2]][l2] [![Join us on Discord][s5]][l5] # Crossterm Examples The examples are compatible with the latest release. ## Structure ``` ├── examples │   └── interactive-test │   └── event-* │   └── stderr ``` | File Name | Description | Topics | :------- | :------- | :------- | | `examples/interactive-test` | interactive, walk trough, demo | cursor, style, event | `event-*`| event reading demos | (async) event reading | `stderr` | crossterm over stderr demo | raw mode, alternate screen, custom output | `is_tty` | Is this instance a tty ? | tty | ## Run examples ```bash $ cargo run --example [file name] ``` To run the interactive-demo go into the folder `examples/interactive-demo` and run `cargo run`. ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details. [s2]: https://img.shields.io/badge/license-MIT-blue.svg [l2]: LICENSE [s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord [l5]: https://discord.gg/K4nyTDB [s7]: https://travis-ci.org/crossterm-rs/examples.svg?branch=master crossterm-0.22.1/examples/event-match-modifiers.rs000064400000000000000000000034460072674642500203440ustar 00000000000000//! Demonstrates how to match on modifiers like: Control, alt, shift. //! //! cargo run --example event-match-modifiers use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; fn match_event(read_event: Event) { match read_event { // Match one one modifier: Event::Key(KeyEvent { modifiers: KeyModifiers::CONTROL, code, }) => { println!("Control + {:?}", code); } Event::Key(KeyEvent { modifiers: KeyModifiers::SHIFT, code, }) => { println!("Shift + {:?}", code); } Event::Key(KeyEvent { modifiers: KeyModifiers::ALT, code, }) => { println!("Alt + {:?}", code); } // Match on multiple modifiers: Event::Key(KeyEvent { code, modifiers }) => { if modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) { println!("Alt + Shift {:?}", code); } else { println!("({:?}) with key: {:?}", modifiers, code) } } _ => {} } } fn main() { match_event(Event::Key(KeyEvent { modifiers: KeyModifiers::CONTROL, code: KeyCode::Char('z'), })); match_event(Event::Key(KeyEvent { modifiers: KeyModifiers::SHIFT, code: KeyCode::Left, })); match_event(Event::Key(KeyEvent { modifiers: KeyModifiers::ALT, code: KeyCode::Delete, })); match_event(Event::Key(KeyEvent { modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT, code: KeyCode::Right, })); match_event(Event::Key(KeyEvent { modifiers: KeyModifiers::ALT | KeyModifiers::CONTROL, code: KeyCode::Home, })); } crossterm-0.22.1/examples/event-poll-read.rs000064400000000000000000000031210072674642500171360ustar 00000000000000//! Demonstrates how to match on modifiers like: Control, alt, shift. //! //! cargo run --example event-poll-read use std::{io::stdout, time::Duration}; use crossterm::{ cursor::position, event::{poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode}, Result, }; const HELP: &str = r#"Blocking poll() & non-blocking read() - 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 "#; fn print_events() -> Result<()> { loop { // Wait up to 1s for another event if poll(Duration::from_millis(1_000))? { // It's guaranteed that read() wont block if `poll` returns `Ok(true)` let event = read()?; 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; } } else { // Timeout expired, no event for 1s println!(".\r"); } } Ok(()) } fn main() -> Result<()> { println!("{}", HELP); enable_raw_mode()?; let mut stdout = stdout(); execute!(stdout, EnableMouseCapture)?; if let Err(e) = print_events() { println!("Error: {:?}\r", e); } execute!(stdout, DisableMouseCapture)?; disable_raw_mode() } crossterm-0.22.1/examples/event-read-char-line.rs000064400000000000000000000020270072674642500200360ustar 00000000000000//! Demonstrates how to block read characters or a full line. //! Just note that crossterm is not required to do this and can be done with `io::stdin()`. //! //! cargo run --example event-read-char-line use crossterm::{ event::{self, Event, KeyCode, KeyEvent}, Result, }; pub fn read_char() -> Result { loop { if let Event::Key(KeyEvent { code: KeyCode::Char(c), .. }) = event::read()? { return Ok(c); } } } pub fn read_line() -> Result { let mut line = String::new(); while let Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Enter => { break; } KeyCode::Char(c) => { line.push(c); } _ => {} } } Ok(line) } fn main() { println!("read line:"); println!("{:?}", read_line()); println!("read char:"); println!("{:?}", read_char()); } crossterm-0.22.1/examples/event-read.rs000064400000000000000000000037420072674642500162030ustar 00000000000000//! Demonstrates how to block read events. //! //! cargo run --example event-read use std::io::stdout; use crossterm::event::poll; use crossterm::{ cursor::position, event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, execute, terminal::{disable_raw_mode, enable_raw_mode}, Result, }; use std::time::Duration; const HELP: &str = r#"Blocking read() - Keyboard, mouse and terminal resize events enabled - Hit "c" to print current cursor position - Use Esc to quit "#; fn print_events() -> Result<()> { loop { // Blocking read let event = read()?; println!("Event: {:?}\r", event); if event == Event::Key(KeyCode::Char('c').into()) { println!("Cursor position: {:?}\r", position()); } if let Event::Resize(_, _) = event { let (original_size, new_size) = flush_resize_events(event); println!("Resize from: {:?}, to: {:?}", original_size, new_size); } if event == Event::Key(KeyCode::Esc.into()) { break; } } Ok(()) } // Resize events can occur in batches. // With a simple loop they can be flushed. // This function will keep the first and last resize event. fn flush_resize_events(event: Event) -> ((u16, u16), (u16, u16)) { if let Event::Resize(x, y) = event { let mut last_resize = (x, y); while let Ok(true) = poll(Duration::from_millis(50)) { if let Ok(Event::Resize(x, y)) = read() { last_resize = (x, y); } } return ((x, y), last_resize); } ((0, 0), (0, 0)) } fn main() -> Result<()> { println!("{}", HELP); enable_raw_mode()?; let mut stdout = stdout(); execute!(stdout, EnableMouseCapture)?; if let Err(e) = print_events() { println!("Error: {:?}\r", e); } execute!(stdout, DisableMouseCapture)?; disable_raw_mode() } crossterm-0.22.1/examples/event-stream-async-std.rs000064400000000000000000000037010072674642500204610ustar 00000000000000//! Demonstrates how to read events asynchronously with async-std. //! //! cargo run --features="event-stream" --example event-stream-async-std use std::{io::stdout, time::Duration}; use futures::{future::FutureExt, select, StreamExt}; use futures_timer::Delay; 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() } crossterm-0.22.1/examples/event-stream-tokio.rs000064400000000000000000000036560072674642500177120ustar 00000000000000//! Demonstrates how to read events asynchronously with tokio. //! //! cargo run --features="event-stream" --example event-stream-tokio use std::{io::stdout, time::Duration}; use futures::{future::FutureExt, select, StreamExt}; use futures_timer::Delay; 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() } crossterm-0.22.1/examples/is_tty.rs000064400000000000000000000002650072674642500154610ustar 00000000000000use crossterm::tty::IsTty; use std::io::stdin; pub fn main() { if stdin().is_tty() { println!("Is TTY"); } else { println!("Is not TTY"); } } crossterm-0.22.1/examples/stderr.rs000064400000000000000000000043470072674642500154560ustar 00000000000000//! This shows how an application can write on stderr //! instead of stdout, thus making it possible to //! the command API instead of the "old style" direct //! unbuffered API. //! //! This particular example is only suited to Unix //! for now. //! //! cargo run --example stderr use std::io::{stderr, Write}; use crossterm::{ cursor::{Hide, MoveTo, Show}, event, event::{Event, KeyCode, KeyEvent}, execute, queue, style::Print, terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}, Result, }; const TEXT: &str = r#" This screen is ran on stderr. And when you hit enter, it prints on stdout. This makes it possible to run an application and choose what will be sent to any application calling yours. For example, assuming you build this example with cargo build --bin stderr and then you run it with cd "$(target/debug/stderr)" what the application prints on stdout is used as argument to cd. Try it out. Hit any key to quit this screen: 1 will print `..` 2 will print `/` 3 will print `~` Any other key will print this text (so that you may copy-paste) "#; fn run_app(write: &mut W) -> Result where W: Write, { queue!( write, EnterAlternateScreen, // enter alternate screen Hide // hide the cursor )?; let mut y = 1; for line in TEXT.split('\n') { queue!(write, MoveTo(1, y), Print(line.to_string()))?; y += 1; } write.flush()?; terminal::enable_raw_mode()?; let user_char = read_char()?; // we wait for the user to hit a key execute!(write, Show, LeaveAlternateScreen)?; // restore the cursor and leave the alternate screen terminal::disable_raw_mode()?; Ok(user_char) } pub fn read_char() -> Result { loop { if let Event::Key(KeyEvent { code: KeyCode::Char(c), .. }) = event::read()? { return Ok(c); } } } // cargo run --example stderr fn main() { match run_app(&mut stderr()).unwrap() { '1' => print!(".."), '2' => print!("/"), '3' => print!("~"), _ => println!("{}", TEXT), } } crossterm-0.22.1/src/ansi_support.rs000064400000000000000000000034670072674642500156540ustar 00000000000000use std::sync::atomic::{AtomicBool, Ordering}; use crossterm_winapi::{ConsoleMode, Handle}; use parking_lot::Once; use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING; use crate::Result; /// Enable virtual terminal processing. /// /// This method attempts to enable virtual terminal processing for this /// console. If there was a problem enabling it, then an error returned. /// On success, the caller may assume that enabling it was successful. /// /// When virtual terminal processing is enabled, characters emitted to the /// console are parsed for VT100 and similar control character sequences /// that control color and other similar operations. fn enable_vt_processing() -> Result<()> { let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING; let console_mode = ConsoleMode::from(Handle::current_out_handle()?); let old_mode = console_mode.mode()?; if old_mode & mask == 0 { console_mode.set_mode(old_mode | mask)?; } Ok(()) } static SUPPORTS_ANSI_ESCAPE_CODES: AtomicBool = AtomicBool::new(false); static INITIALIZER: Once = Once::new(); /// Checks if the current terminal supports ansi escape sequences pub fn supports_ansi() -> bool { INITIALIZER.call_once(|| { // Some terminals on Windows like GitBash can't use WinAPI calls directly // so when we try to enable the ANSI-flag for Windows this won't work. // Because of that we should check first if the TERM-variable is set // and see if the current terminal is a terminal who does support ANSI. let supported = std::env::var("TERM").map_or(false, |term| term != "dumb") || enable_vt_processing().is_ok(); SUPPORTS_ANSI_ESCAPE_CODES.store(supported, Ordering::SeqCst); }); SUPPORTS_ANSI_ESCAPE_CODES.load(Ordering::SeqCst) } crossterm-0.22.1/src/command.rs000064400000000000000000000176620072674642500145460ustar 00000000000000use std::fmt; use std::io::{self, Write}; use super::error::Result; /// An interface for a command that performs an action on the terminal. /// /// Crossterm provides a set of commands, /// and there is no immediate reason to implement a command yourself. /// In order to understand how to use and execute commands, /// it is recommended that you take a look at [Command Api](../#command-api) chapter. pub trait Command { /// Write an ANSI representation of this command to the given writer. /// An ANSI code can manipulate the terminal by writing it to the terminal buffer. /// However, only Windows 10 and UNIX systems support this. /// /// This method does not need to be accessed manually, as it is used by the crossterm's [Command Api](../#command-api) fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result; /// Execute this command. /// /// Windows versions lower than windows 10 do not support ANSI escape codes, /// therefore a direct WinAPI call is made. /// /// This method does not need to be accessed manually, as it is used by the crossterm's [Command Api](../#command-api) #[cfg(windows)] fn execute_winapi(&self) -> Result<()>; /// Returns whether the ansi code representation of this command is supported by windows. /// /// A list of supported ANSI escape codes /// can be found [here](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences). #[cfg(windows)] fn is_ansi_code_supported(&self) -> bool { super::ansi_support::supports_ansi() } } impl Command for &T { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { (**self).write_ansi(f) } #[inline] #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { T::execute_winapi(self) } #[cfg(windows)] #[inline] fn is_ansi_code_supported(&self) -> bool { T::is_ansi_code_supported(self) } } /// An interface for types that can queue commands for further execution. pub trait QueueableCommand { /// Queues the given command for further execution. fn queue(&mut self, command: impl Command) -> Result<&mut Self>; } /// An interface for types that can directly execute commands. pub trait ExecutableCommand { /// Executes the given command directly. fn execute(&mut self, command: impl Command) -> Result<&mut Self>; } impl QueueableCommand for T { /// Queues the given command for further execution. /// /// Queued commands will be executed in the following cases: /// /// * When `flush` is called manually on the given type implementing `io::Write`. /// * The terminal will `flush` automatically if the buffer is full. /// * Each line is flushed in case of `stdout`, because it is line buffered. /// /// # Arguments /// /// - [Command](./trait.Command.html) /// /// The command that you want to queue for later execution. /// /// # Examples /// /// ```rust /// use std::io::{Write, stdout}; /// /// use crossterm::{Result, QueueableCommand, style::Print}; /// /// fn main() -> Result<()> { /// let mut stdout = stdout(); /// /// // `Print` will executed executed when `flush` is called. /// stdout /// .queue(Print("foo 1\n".to_string()))? /// .queue(Print("foo 2".to_string()))?; /// /// // some other code (no execution happening here) ... /// /// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed. /// stdout.flush()?; /// /// Ok(()) /// /// // ==== Output ==== /// // foo 1 /// // foo 2 /// } /// ``` /// /// Have a look over at the [Command API](./#command-api) for more details. /// /// # Notes /// /// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'. /// * In case of Windows versions lower than 10, a direct WinAPI call will be made. /// The reason for this is that Windows versions lower than 10 do not support ANSI codes, /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html) /// and [queue](./trait.QueueableCommand.html) for those old Windows versions. fn queue(&mut self, command: impl Command) -> Result<&mut Self> { #[cfg(windows)] if !command.is_ansi_code_supported() { // There may be queued commands in this writer, but `execute_winapi` will execute the // command immediately. To prevent commands being executed out of order we flush the // writer now. self.flush()?; command.execute_winapi()?; return Ok(self); } write_command_ansi(self, command)?; Ok(self) } } impl ExecutableCommand for T { /// Executes the given command directly. /// /// The given command its ANSI escape code will be written and flushed onto `Self`. /// /// # Arguments /// /// - [Command](./trait.Command.html) /// /// The command that you want to execute directly. /// /// # Example /// /// ```rust /// use std::io::{Write, stdout}; /// /// use crossterm::{Result, ExecutableCommand, style::Print}; /// /// fn main() -> Result<()> { /// // will be executed directly /// stdout() /// .execute(Print("sum:\n".to_string()))? /// .execute(Print(format!("1 + 1= {} ", 1 + 1)))?; /// /// Ok(()) /// /// // ==== Output ==== /// // sum: /// // 1 + 1 = 2 /// } /// ``` /// /// Have a look over at the [Command API](./#command-api) for more details. /// /// # Notes /// /// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'. /// * In case of Windows versions lower than 10, a direct WinAPI call will be made. /// The reason for this is that Windows versions lower than 10 do not support ANSI codes, /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](./trait.ExecutableCommand.html) /// and [queue](./trait.QueueableCommand.html) for those old Windows versions. fn execute(&mut self, command: impl Command) -> Result<&mut Self> { self.queue(command)?; self.flush()?; Ok(self) } } /// Writes the ANSI representation of a command to the given writer. fn write_command_ansi( io: &mut (impl io::Write + ?Sized), command: C, ) -> io::Result<()> { struct Adapter { inner: T, res: io::Result<()>, } impl fmt::Write for Adapter { fn write_str(&mut self, s: &str) -> fmt::Result { self.inner.write_all(s.as_bytes()).map_err(|e| { self.res = Err(e); fmt::Error }) } } let mut adapter = Adapter { inner: io, res: Ok(()), }; command .write_ansi(&mut adapter) .map_err(|fmt::Error| match adapter.res { Ok(()) => panic!( "<{}>::write_ansi incorrectly errored", std::any::type_name::() ), Err(e) => e, }) } /// Executes the ANSI representation of a command, using the given `fmt::Write`. pub(crate) fn execute_fmt(f: &mut impl fmt::Write, command: impl Command) -> fmt::Result { #[cfg(windows)] if !command.is_ansi_code_supported() { return command.execute_winapi().map_err(|_| fmt::Error); } command.write_ansi(f) } crossterm-0.22.1/src/cursor/sys/unix.rs000064400000000000000000000027420072674642500162370ustar 00000000000000use std::{ io::{self, Error, ErrorKind, Write}, time::Duration, }; use crate::{ event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent}, terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled}, Result, }; /// Returns the cursor position (column, row). /// /// The top left cell is represented `0,0`. pub fn position() -> Result<(u16, u16)> { if is_raw_mode_enabled() { read_position_raw() } else { read_position() } } fn read_position() -> Result<(u16, u16)> { enable_raw_mode()?; let pos = read_position_raw(); disable_raw_mode()?; pos } fn read_position_raw() -> Result<(u16, u16)> { // Use `ESC [ 6 n` to and retrieve the cursor position. let mut stdout = io::stdout(); stdout.write_all(b"\x1B[6n")?; stdout.flush()?; loop { match poll_internal(Some(Duration::from_millis(2000)), &CursorPositionFilter) { Ok(true) => { if let Ok(InternalEvent::CursorPosition(x, y)) = read_internal(&CursorPositionFilter) { return Ok((x, y)); } } Ok(false) => { return Err(Error::new( ErrorKind::Other, "The cursor position could not be read within a normal duration", )); } Err(_) => {} } } } crossterm-0.22.1/src/cursor/sys/windows.rs000064400000000000000000000205320072674642500167430ustar 00000000000000//! WinAPI related logic to cursor manipulation. use std::convert::TryFrom; use std::io; use std::sync::atomic::{AtomicU64, Ordering}; use crossterm_winapi::{result, Coord, Handle, HandleType, ScreenBuffer}; use winapi::{ shared::minwindef::{FALSE, TRUE}, um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD}, }; use crate::Result; /// The position of the cursor, written when you save the cursor's position. /// /// This is `u64::MAX` initially. Otherwise, it stores the cursor's x position bit-shifted left 16 /// times or-ed with the cursor's y position, where both are `i16`s. static SAVED_CURSOR_POS: AtomicU64 = AtomicU64::new(u64::MAX); // The 'y' position of the cursor is not relative to the window but absolute to screen buffer. // We can calculate the relative cursor position by subtracting the top position of the terminal window from the y position. // This results in an 1-based coord zo subtract 1 to make cursor position 0-based. pub fn parse_relative_y(y: i16) -> Result { let window = ScreenBuffer::current()?.info()?; let window_size = window.terminal_window(); let screen_size = window.terminal_size(); if y <= screen_size.height { Ok(y) } else { Ok(y - window_size.top) } } /// Returns the cursor position (column, row). /// /// The top left cell is represented `0,0`. pub fn position() -> Result<(u16, u16)> { let cursor = ScreenBufferCursor::output()?; let mut position = cursor.position()?; // if position.y != 0 { position.y = parse_relative_y(position.y)?; // } Ok(position.into()) } pub(crate) fn show_cursor(show_cursor: bool) -> Result<()> { ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor) } pub(crate) fn move_to(column: u16, row: u16) -> Result<()> { let cursor = ScreenBufferCursor::output()?; cursor.move_to(column as i16, row as i16)?; Ok(()) } pub(crate) fn move_up(count: u16) -> Result<()> { let (column, row) = position()?; move_to(column, row - count)?; Ok(()) } pub(crate) fn move_right(count: u16) -> Result<()> { let (column, row) = position()?; move_to(column + count, row)?; Ok(()) } pub(crate) fn move_down(count: u16) -> Result<()> { let (column, row) = position()?; move_to(column, row + count)?; Ok(()) } pub(crate) fn move_left(count: u16) -> Result<()> { let (column, row) = position()?; move_to(column - count, row)?; Ok(()) } pub(crate) fn move_to_column(new_column: u16) -> Result<()> { let (_, row) = position()?; move_to(new_column, row)?; Ok(()) } pub(crate) fn move_to_row(new_row: u16) -> Result<()> { let (col, _) = position()?; move_to(col, new_row)?; Ok(()) } pub(crate) fn move_to_next_line(count: u16) -> Result<()> { let (_, row) = position()?; move_to(0, row + count)?; Ok(()) } pub(crate) fn move_to_previous_line(count: u16) -> Result<()> { let (_, row) = position()?; move_to(0, row - count)?; Ok(()) } pub(crate) fn save_position() -> Result<()> { ScreenBufferCursor::output()?.save_position()?; Ok(()) } pub(crate) fn restore_position() -> Result<()> { ScreenBufferCursor::output()?.restore_position()?; Ok(()) } /// WinAPI wrapper over terminal cursor behaviour. struct ScreenBufferCursor { screen_buffer: ScreenBuffer, } impl ScreenBufferCursor { fn output() -> Result { Ok(ScreenBufferCursor { screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?), }) } fn position(&self) -> Result { Ok(self.screen_buffer.info()?.cursor_pos()) } fn move_to(&self, x: i16, y: i16) -> Result<()> { if x < 0 { return Err(io::Error::new( io::ErrorKind::Other, format!( "Argument Out of Range Exception when setting cursor position to X: {}", x ), )); } if y < 0 { return Err(io::Error::new( io::ErrorKind::Other, format!( "Argument Out of Range Exception when setting cursor position to Y: {}", y ), )); } let position = COORD { X: x, Y: y }; unsafe { if result(SetConsoleCursorPosition( **self.screen_buffer.handle(), position, )) .is_err() { return Err(io::Error::last_os_error()); } } Ok(()) } fn set_visibility(&self, visible: bool) -> Result<()> { let cursor_info = CONSOLE_CURSOR_INFO { dwSize: 100, bVisible: if visible { TRUE } else { FALSE }, }; unsafe { if result(SetConsoleCursorInfo( **self.screen_buffer.handle(), &cursor_info, )) .is_err() { return Err(io::Error::last_os_error()); } } Ok(()) } fn restore_position(&self) -> Result<()> { if let Ok(val) = u32::try_from(SAVED_CURSOR_POS.load(Ordering::Relaxed)) { let x = (val >> 16) as i16; let y = val as i16; self.move_to(x, y)?; } Ok(()) } fn save_position(&self) -> Result<()> { let position = self.position()?; let bits = u64::from(u32::from(position.x as u16) << 16 | u32::from(position.y as u16)); SAVED_CURSOR_POS.store(bits, Ordering::Relaxed); Ok(()) } } impl From for ScreenBufferCursor { fn from(handle: Handle) -> Self { ScreenBufferCursor { screen_buffer: ScreenBuffer::from(handle), } } } #[cfg(test)] mod tests { use super::{ move_down, move_left, move_right, move_to, move_to_column, move_to_next_line, move_to_previous_line, move_to_row, move_up, position, restore_position, save_position, }; #[test] fn test_move_to_winapi() { let (saved_x, saved_y) = position().unwrap(); move_to(saved_x + 1, saved_y + 1).unwrap(); assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1)); move_to(saved_x, saved_y).unwrap(); assert_eq!(position().unwrap(), (saved_x, saved_y)); } #[test] fn test_move_right_winapi() { let (saved_x, saved_y) = position().unwrap(); move_right(1).unwrap(); assert_eq!(position().unwrap(), (saved_x + 1, saved_y)); } #[test] fn test_move_left_winapi() { move_to(2, 0).unwrap(); move_left(2).unwrap(); assert_eq!(position().unwrap(), (0, 0)); } #[test] fn test_move_up_winapi() { move_to(0, 2).unwrap(); move_up(2).unwrap(); assert_eq!(position().unwrap(), (0, 0)); } #[test] fn test_move_to_next_line_winapi() { move_to(0, 2).unwrap(); move_to_next_line(2).unwrap(); assert_eq!(position().unwrap(), (0, 4)); } #[test] fn test_move_to_previous_line_winapi() { move_to(0, 2).unwrap(); move_to_previous_line(2).unwrap(); assert_eq!(position().unwrap(), (0, 0)); } #[test] fn test_move_to_column_winapi() { move_to(0, 2).unwrap(); move_to_column(12).unwrap(); assert_eq!(position().unwrap(), (12, 2)); } #[test] fn test_move_to_row_winapi() { move_to(0, 2).unwrap(); move_to_row(5).unwrap(); assert_eq!(position().unwrap(), (0, 5)); } #[test] fn test_move_down_winapi() { move_to(0, 0).unwrap(); move_down(2).unwrap(); assert_eq!(position().unwrap(), (0, 2)); } #[test] fn test_save_restore_position_winapi() { let (saved_x, saved_y) = position().unwrap(); save_position().unwrap(); move_to(saved_x + 1, saved_y + 1).unwrap(); restore_position().unwrap(); let (x, y) = position().unwrap(); assert_eq!(x, saved_x); assert_eq!(y, saved_y); } } crossterm-0.22.1/src/cursor/sys.rs000064400000000000000000000007230072674642500152510ustar 00000000000000//! This module provides platform related functions. #[cfg(unix)] pub use self::unix::position; #[cfg(windows)] pub use self::windows::position; #[cfg(windows)] pub(crate) use self::windows::{ move_down, move_left, move_right, move_to, move_to_column, move_to_next_line, move_to_previous_line, move_to_row, move_up, restore_position, save_position, show_cursor, }; #[cfg(windows)] pub(crate) mod windows; #[cfg(unix)] pub(crate) mod unix; crossterm-0.22.1/src/cursor.rs000064400000000000000000000321310072674642500144310ustar 00000000000000//! # Cursor //! //! The `cursor` module provides functionality to work with the terminal cursor. //! //! 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. //! //! ## Examples //! //! Cursor actions can be performed with commands. //! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation. //! //! ```no_run //! use std::io::{stdout, Write}; //! //! use crossterm::{ //! ExecutableCommand, execute, Result, //! cursor::{DisableBlinking, EnableBlinking, MoveTo, RestorePosition, SavePosition} //! }; //! //! fn main() -> Result<()> { //! // with macro //! execute!( //! stdout(), //! SavePosition, //! MoveTo(10, 10), //! EnableBlinking, //! DisableBlinking, //! RestorePosition //! ); //! //! // with function //! stdout() //! .execute(MoveTo(11,11))? //! .execute(RestorePosition); //! //! Ok(()) //! } //! ``` //! //! For manual execution control check out [crossterm::queue](../macro.queue.html). use std::fmt; #[cfg(windows)] use crate::Result; use crate::{csi, impl_display, Command}; pub use sys::position; pub(crate) mod sys; /// A command that moves the terminal cursor to the given position (column, row). /// /// # Notes /// /// * Top left cell is represented as `0,0`. /// * Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveTo(pub u16, pub u16); impl Command for MoveTo { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{};{}H"), self.1 + 1, self.0 + 1) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_to(self.0, self.1) } } /// A command that moves the terminal cursor down the given number of lines, /// and moves it to the first column. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToNextLine(pub u16); impl Command for MoveToNextLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}E"), self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_to_next_line(self.0) } } /// A command that moves the terminal cursor up the given number of lines, /// and moves it to the first column. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToPreviousLine(pub u16); impl Command for MoveToPreviousLine { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}F"), self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_to_previous_line(self.0) } } /// A command that moves the terminal cursor to the given column on the current row. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToColumn(pub u16); impl Command for MoveToColumn { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}G"), self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_to_column(self.0) } } /// A command that moves the terminal cursor to the given row on the current column. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveToRow(pub u16); impl Command for MoveToRow { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}d"), self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_to_row(self.0) } } /// A command that moves the terminal cursor a given number of rows up. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveUp(pub u16); impl Command for MoveUp { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}A"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_up(self.0) } } /// A command that moves the terminal cursor a given number of columns to the right. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveRight(pub u16); impl Command for MoveRight { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}C"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_right(self.0) } } /// A command that moves the terminal cursor a given number of rows down. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveDown(pub u16); impl Command for MoveDown { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}B"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_down(self.0) } } /// A command that moves the terminal cursor a given number of columns to the left. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MoveLeft(pub u16); impl Command for MoveLeft { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}D"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::move_left(self.0) } } /// A command that saves the current terminal cursor position. /// /// See the [RestorePosition](./struct.RestorePosition.html) command. /// /// # Notes /// /// - The cursor position is stored globally. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SavePosition; impl Command for SavePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str("\x1B7") } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::save_position() } } /// A command that restores the saved terminal cursor position. /// /// See the [SavePosition](./struct.SavePosition.html) command. /// /// # Notes /// /// - The cursor position is stored globally. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct RestorePosition; impl Command for RestorePosition { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str("\x1B8") } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::restore_position() } } /// A command that hides the terminal cursor. /// /// # Notes /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Hide; impl Command for Hide { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?25l")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::show_cursor(false) } } /// A command that shows the terminal cursor. /// /// # Notes /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Show; impl Command for Show { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?25h")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::show_cursor(true) } } /// A command that enables blinking of the terminal cursor. /// /// # Notes /// /// - Windows versions lower than Windows 10 do not support this functionality. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnableBlinking; impl Command for EnableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?12h")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { Ok(()) } } /// A command that disables blinking of the terminal cursor. /// /// # Notes /// /// - Windows versions lower than Windows 10 do not support this functionality. /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DisableBlinking; impl Command for DisableBlinking { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?12l")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { Ok(()) } } /// All supported cursor shapes /// /// # Note /// /// - Used with SetCursorShape #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CursorShape { UnderScore, Line, Block, } /// A command that sets the shape of the cursor /// /// # Note /// /// - Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetCursorShape(pub CursorShape); impl Command for SetCursorShape { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { use CursorShape::*; match self.0 { UnderScore => f.write_str("\x1b[3 q"), Line => f.write_str("\x1b[5 q"), Block => f.write_str("\x1b[2 q"), } } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { Ok(()) } } impl_display!(for MoveTo); impl_display!(for MoveToColumn); impl_display!(for MoveToRow); impl_display!(for MoveToNextLine); impl_display!(for MoveToPreviousLine); impl_display!(for MoveUp); impl_display!(for MoveDown); impl_display!(for MoveLeft); impl_display!(for MoveRight); impl_display!(for SavePosition); impl_display!(for RestorePosition); impl_display!(for Hide); impl_display!(for Show); impl_display!(for EnableBlinking); impl_display!(for DisableBlinking); impl_display!(for SetCursorShape); #[cfg(test)] mod tests { use std::io::{self, stdout}; use crate::execute; use super::{ position, MoveDown, MoveLeft, MoveRight, MoveTo, MoveUp, RestorePosition, SavePosition, }; // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_move_to() { let (saved_x, saved_y) = position().unwrap(); execute!(stdout(), MoveTo(saved_x + 1, saved_y + 1)).unwrap(); assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1)); execute!(stdout(), MoveTo(saved_x, saved_y)).unwrap(); assert_eq!(position().unwrap(), (saved_x, saved_y)); } // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_move_right() { let (saved_x, saved_y) = position().unwrap(); execute!(io::stdout(), MoveRight(1)).unwrap(); assert_eq!(position().unwrap(), (saved_x + 1, saved_y)); } // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_move_left() { execute!(stdout(), MoveTo(2, 0), MoveLeft(2)).unwrap(); assert_eq!(position().unwrap(), (0, 0)); } // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_move_up() { execute!(stdout(), MoveTo(0, 2), MoveUp(2)).unwrap(); assert_eq!(position().unwrap(), (0, 0)); } // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_move_down() { execute!(stdout(), MoveTo(0, 0), MoveDown(2)).unwrap(); assert_eq!(position().unwrap(), (0, 2)); } // Test is disabled, because it's failing on Travis #[test] #[ignore] fn test_save_restore_position() { let (saved_x, saved_y) = position().unwrap(); execute!( stdout(), SavePosition, MoveTo(saved_x + 1, saved_y + 1), RestorePosition ) .unwrap(); let (x, y) = position().unwrap(); assert_eq!(x, saved_x); assert_eq!(y, saved_y); } } crossterm-0.22.1/src/error.rs000064400000000000000000000002750072674642500142510ustar 00000000000000//! Module containing error handling logic. use std::io; /// The `crossterm` result type. pub type Result = std::result::Result; pub type ErrorKind = io::Error; crossterm-0.22.1/src/event/filter.rs000064400000000000000000000036010072674642500155220ustar 00000000000000use crate::event::InternalEvent; /// Interface for filtering an `InternalEvent`. pub(crate) trait Filter: Send + Sync + 'static { /// Returns whether the given event fulfills the filter. fn eval(&self, event: &InternalEvent) -> bool; } #[cfg(unix)] #[derive(Debug, Clone)] pub(crate) struct CursorPositionFilter; #[cfg(unix)] impl Filter for CursorPositionFilter { fn eval(&self, event: &InternalEvent) -> bool { matches!(*event, InternalEvent::CursorPosition(_, _)) } } #[derive(Debug, Clone)] pub(crate) struct EventFilter; impl Filter for EventFilter { #[cfg(unix)] fn eval(&self, event: &InternalEvent) -> bool { matches!(*event, InternalEvent::Event(_)) } #[cfg(windows)] fn eval(&self, _: &InternalEvent) -> bool { true } } #[derive(Debug, Clone)] pub(crate) struct InternalEventFilter; impl Filter for InternalEventFilter { fn eval(&self, _: &InternalEvent) -> bool { true } } #[cfg(test)] #[cfg(unix)] mod tests { use super::{ super::Event, CursorPositionFilter, EventFilter, Filter, InternalEvent, InternalEventFilter, }; #[test] fn test_cursor_position_filter_filters_cursor_position() { assert!(!CursorPositionFilter.eval(&InternalEvent::Event(Event::Resize(10, 10)))); assert!(CursorPositionFilter.eval(&InternalEvent::CursorPosition(0, 0))); } #[test] fn test_event_filter_filters_events() { assert!(EventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10)))); assert!(!EventFilter.eval(&InternalEvent::CursorPosition(0, 0))); } #[test] fn test_event_filter_filters_internal_events() { assert!(InternalEventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10)))); assert!(InternalEventFilter.eval(&InternalEvent::CursorPosition(0, 0))); } } crossterm-0.22.1/src/event/read.rs000064400000000000000000000350730072674642500151600ustar 00000000000000use std::{collections::vec_deque::VecDeque, io, time::Duration}; #[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", )) } }; let poll_timeout = PollTimeout::new(timeout); loop { let maybe_event = match event_source.try_read(poll_timeout.leftover()) { Ok(None) => None, Ok(Some(event)) => { if filter.eval(&event) { Some(event) } else { self.skipped_events.push(event); None } } Err(e) => { if e.kind() == io::ErrorKind::Interrupted { return Ok(false); } 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::io; 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::from(io::ErrorKind::Other)); 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::from(io::ErrorKind::Other))) ); } #[test] fn test_read_propagates_error() { let source = FakeSource::with_error(ErrorKind::from(io::ErrorKind::Other)); 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::from(io::ErrorKind::Other))) ); } #[test] fn test_poll_continues_after_error() { const EVENT: InternalEvent = InternalEvent::Event(Event::Resize(10, 10)); let source = FakeSource::new(&[EVENT, EVENT], ErrorKind::from(io::ErrorKind::Other)); 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::from(io::ErrorKind::Other)); 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!(); } } } crossterm-0.22.1/src/event/source/unix.rs000064400000000000000000000222470072674642500165270ustar 00000000000000use std::{collections::VecDeque, io, time::Duration}; use mio::{unix::SourceFd, Events, Interest, Poll, Token}; use signal_hook_mio::v0_7::Signals; use crate::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 { 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::consts::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 { if let Err(e) = self.poll.poll(&mut self.events, timeout.leftover()) { // Mio will throw an interrupted error in case of cursor position retrieval. We need to retry until it succeeds. // Previous versions of Mio (< 0.7) would automatically retry the poll call if it was interrupted (if EINTR was returned). // https://docs.rs/mio/0.7.0/mio/struct.Poll.html#notes if e.kind() == io::ErrorKind::Interrupted { continue; } else { return Err(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(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; } } }; if let Some(event) = self.parser.next() { return Ok(Some(event)); } } } SIGNAL_TOKEN => { for signal in self.signals.pending() { match signal { signal_hook::consts::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`", )); } _ => 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() } } crossterm-0.22.1/src/event/source/windows.rs000064400000000000000000000042350072674642500172330ustar 00000000000000use std::time::Duration; use crossterm_winapi::{Console, Handle, InputRecord}; use crate::event::{sys::windows::poll::WinApiPoll, Event}; #[cfg(feature = "event-stream")] use super::super::sys::Waker; use super::super::{ source::EventSource, sys::windows::parse::{handle_key_event, handle_mouse_event}, timeout::PollTimeout, InternalEvent, Result, }; pub(crate) struct WindowsEventSource { console: Console, poll: WinApiPoll, } impl WindowsEventSource { pub(crate) fn new() -> Result { let console = Console::from(Handle::current_in_handle()?); Ok(WindowsEventSource { console, #[cfg(not(feature = "event-stream"))] poll: WinApiPoll::new(), #[cfg(feature = "event-stream")] poll: WinApiPoll::new()?, }) } } impl EventSource for WindowsEventSource { fn try_read(&mut self, timeout: Option) -> Result> { let poll_timeout = PollTimeout::new(timeout); loop { if let Some(event_ready) = self.poll.poll(poll_timeout.leftover())? { let number = self.console.number_of_console_input_events()?; if event_ready && number != 0 { let event = match self.console.read_single_input_event()? { InputRecord::KeyEvent(record) => handle_key_event(record), InputRecord::MouseEvent(record) => handle_mouse_event(record), InputRecord::WindowBufferSizeEvent(record) => { Some(Event::Resize(record.size.x as u16, record.size.y as u16)) } _ => None, }; if let Some(event) = event { return Ok(Some(InternalEvent::Event(event))); } } } if poll_timeout.elapsed() { return Ok(None); } } } #[cfg(feature = "event-stream")] fn waker(&self) -> Waker { self.poll.waker() } } crossterm-0.22.1/src/event/source.rs000064400000000000000000000016460072674642500155440ustar 00000000000000use std::time::Duration; #[cfg(feature = "event-stream")] use super::sys::Waker; use super::InternalEvent; #[cfg(unix)] pub(crate) mod unix; #[cfg(windows)] pub(crate) mod windows; /// An interface for trying to read an `InternalEvent` within an optional `Duration`. pub(crate) trait EventSource: Sync + Send { /// Tries to read an `InternalEvent` within the given duration. /// /// # Arguments /// /// * `timeout` - `None` block indefinitely until an event is available, `Some(duration)` blocks /// for the given timeout /// /// Returns `Ok(None)` if there's no event available and timeout expires. fn try_read(&mut self, timeout: Option) -> crate::Result>; /// Returns a `Waker` allowing to wake/force the `try_read` method to return `Ok(None)`. #[cfg(feature = "event-stream")] fn waker(&self) -> Waker; } crossterm-0.22.1/src/event/stream.rs000064400000000000000000000120310072674642500155250ustar 00000000000000use std::{ pin::Pin, sync::{ atomic::{AtomicBool, Ordering}, mpsc::{self, SyncSender}, Arc, }, task::{Context, Poll}, thread, time::Duration, }; use futures_core::stream::Stream; use crate::Result; use super::{ filter::EventFilter, lock_internal_event_reader, poll_internal, read_internal, sys::Waker, Event, InternalEvent, }; /// 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_task_executed: Arc, stream_wake_task_should_shutdown: Arc, task_sender: SyncSender, } impl Default for EventStream { fn default() -> Self { let (task_sender, receiver) = mpsc::sync_channel::(1); thread::spawn(move || { while let Ok(task) = receiver.recv() { loop { if let Ok(true) = poll_internal(None, &EventFilter) { break; } if task.stream_wake_task_should_shutdown.load(Ordering::SeqCst) { break; } } task.stream_wake_task_executed .store(false, Ordering::SeqCst); task.stream_waker.wake(); } }); EventStream { poll_internal_waker: lock_internal_event_reader().waker(), stream_wake_task_executed: Arc::new(AtomicBool::new(false)), stream_wake_task_should_shutdown: Arc::new(AtomicBool::new(false)), task_sender, } } } impl EventStream { /// Constructs a new instance of `EventStream`. pub fn new() -> EventStream { EventStream::default() } } struct Task { stream_waker: std::task::Waker, stream_wake_task_executed: Arc, stream_wake_task_should_shutdown: Arc, } // 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_task_executed .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) // https://github.com/rust-lang/rust/issues/80486#issuecomment-752244166 .unwrap_or_else(|x| x) { let stream_waker = cx.waker().clone(); let stream_wake_task_executed = self.stream_wake_task_executed.clone(); let stream_wake_task_should_shutdown = self.stream_wake_task_should_shutdown.clone(); stream_wake_task_should_shutdown.store(false, Ordering::SeqCst); let _ = self.task_sender.send(Task { stream_waker, stream_wake_task_executed, stream_wake_task_should_shutdown, }); } Poll::Pending } Err(e) => Poll::Ready(Some(Err(e))), }; result } } impl Drop for EventStream { fn drop(&mut self) { self.stream_wake_task_should_shutdown .store(true, Ordering::SeqCst); let _ = self.poll_internal_waker.wake(); } } crossterm-0.22.1/src/event/sys/unix/file_descriptor.rs000064400000000000000000000044470072674642500212240ustar 00000000000000use std::{ fs, io, os::unix::io::{IntoRawFd, RawFd}, }; use libc::size_t; use crate::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(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)) } crossterm-0.22.1/src/event/sys/unix/parse.rs000064400000000000000000000644340072674642500171630ustar 00000000000000use std::io; use crate::{ event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, ErrorKind, Result, }; use super::super::super::InternalEvent; // Event parsing // // This code (& previous one) are kind of ugly. We have to think about this, // because it's really not maintainable, no tests, etc. // // Every fn returns Result> // // Ok(None) -> wait for more bytes // Err(_) -> failed to parse event, clear the buffer // Ok(Some(event)) -> we have event, clear the buffer // fn could_not_parse_event_error() -> ErrorKind { io::Error::new(io::ErrorKind::Other, "Could not parse an event.") } pub(crate) fn parse_event(buffer: &[u8], input_available: bool) -> Result> { if buffer.is_empty() { return Ok(None); } match buffer[0] { b'\x1B' => { if buffer.len() == 1 { if input_available { // Possible Esc sequence Ok(None) } else { Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))) } } else { match buffer[1] { b'O' => { if buffer.len() == 2 { Ok(None) } else { match buffer[2] { // F1-F4 val @ b'P'..=b'S' => Ok(Some(InternalEvent::Event(Event::Key( KeyCode::F(1 + val - b'P').into(), )))), _ => Err(could_not_parse_event_error()), } } } b'[' => parse_csi(buffer), b'\x1B' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))), _ => parse_event(&buffer[1..], input_available).map(|event_option| { event_option.map(|event| { if let InternalEvent::Event(Event::Key(key_event)) = event { let mut alt_key_event = key_event; alt_key_event.modifiers |= KeyModifiers::ALT; InternalEvent::Event(Event::Key(alt_key_event)) } else { event } }) }), } } } b'\r' => Ok(Some(InternalEvent::Event(Event::Key( KeyCode::Enter.into(), )))), // Issue #371: \n = 0xA, which is also the keycode for Ctrl+J. The only reason we get // newlines as input is because the terminal converts \r into \n for us. When we // enter raw mode, we disable that, so \n no longer has any meaning - it's better to // use Ctrl+J. Waiting to handle it here means it gets picked up later b'\n' if !crate::terminal::sys::is_raw_mode_enabled() => Ok(Some(InternalEvent::Event( Event::Key(KeyCode::Enter.into()), ))), b'\t' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into())))), b'\x7F' => Ok(Some(InternalEvent::Event(Event::Key( KeyCode::Backspace.into(), )))), c @ b'\x01'..=b'\x1A' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char((c as u8 - 0x1 + b'a') as char), KeyModifiers::CONTROL, ))))), c @ b'\x1C'..=b'\x1F' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char((c as u8 - 0x1C + b'4') as char), KeyModifiers::CONTROL, ))))), b'\0' => Ok(Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char(' '), KeyModifiers::CONTROL, ))))), _ => parse_utf8_char(buffer).map(|maybe_char| { maybe_char .map(KeyCode::Char) .map(char_code_to_event) .map(Event::Key) .map(InternalEvent::Event) }), } } // converts KeyCode to KeyEvent (adds shift modifier in case of uppercase characters) fn char_code_to_event(code: KeyCode) -> KeyEvent { let modifiers = match code { KeyCode::Char(c) if c.is_uppercase() => KeyModifiers::SHIFT, _ => KeyModifiers::empty(), }; KeyEvent::new(code, modifiers) } pub(crate) fn parse_csi(buffer: &[u8]) -> Result> { assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ if buffer.len() == 2 { return Ok(None); } let input_event = match buffer[2] { b'[' => { if buffer.len() == 3 { None } else { match buffer[3] { // NOTE (@imdaveho): cannot find when this occurs; // having another '[' after ESC[ not a likely scenario val @ b'A'..=b'E' => Some(Event::Key(KeyCode::F(1 + val - b'A').into())), _ => return Err(could_not_parse_event_error()), } } } b'D' => Some(Event::Key(KeyCode::Left.into())), b'C' => Some(Event::Key(KeyCode::Right.into())), b'A' => Some(Event::Key(KeyCode::Up.into())), b'B' => Some(Event::Key(KeyCode::Down.into())), b'H' => Some(Event::Key(KeyCode::Home.into())), b'F' => Some(Event::Key(KeyCode::End.into())), b'Z' => Some(Event::Key(KeyEvent { code: KeyCode::BackTab, modifiers: KeyModifiers::SHIFT, })), b'M' => return parse_csi_normal_mouse(buffer), b'<' => return parse_csi_sgr_mouse(buffer), b'0'..=b'9' => { // Numbered escape code. if buffer.len() == 3 { None } else { // The final byte of a CSI sequence can be in the range 64-126, so // let's keep reading anything else. let last_byte = *buffer.last().unwrap(); if !(64..=126).contains(&last_byte) { None } else { match buffer[buffer.len() - 1] { b'M' => return parse_csi_rxvt_mouse(buffer), b'~' => return parse_csi_special_key_code(buffer), b'R' => return parse_csi_cursor_position(buffer), _ => return parse_csi_modifier_key_code(buffer), } } } } _ => return Err(could_not_parse_event_error()), }; Ok(input_event.map(InternalEvent::Event)) } pub(crate) fn next_parsed(iter: &mut dyn Iterator) -> Result where T: std::str::FromStr, { iter.next() .ok_or_else(could_not_parse_event_error)? .parse::() .map_err(|_| could_not_parse_event_error()) } pub(crate) fn parse_csi_cursor_position(buffer: &[u8]) -> Result> { // ESC [ Cy ; Cx R // Cy - cursor row number (starting from 1) // Cx - cursor column number (starting from 1) assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ assert!(buffer.ends_with(&[b'R'])); let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) .map_err(|_| could_not_parse_event_error())?; let mut split = s.split(';'); let y = next_parsed::(&mut split)? - 1; let x = next_parsed::(&mut split)? - 1; Ok(Some(InternalEvent::CursorPosition(x, y))) } fn parse_modifiers(mask: u8) -> KeyModifiers { let modifier_mask = mask.saturating_sub(1); let mut modifiers = KeyModifiers::empty(); if modifier_mask & 1 != 0 { modifiers |= KeyModifiers::SHIFT; } if modifier_mask & 2 != 0 { modifiers |= KeyModifiers::ALT; } if modifier_mask & 4 != 0 { modifiers |= KeyModifiers::CONTROL; } modifiers } pub(crate) fn parse_csi_modifier_key_code(buffer: &[u8]) -> Result> { assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ let modifier_mask = buffer[buffer.len() - 2]; let key = buffer[buffer.len() - 1]; let modifiers = parse_modifiers(modifier_mask); let keycode = match key { b'A' => KeyCode::Up, b'B' => KeyCode::Down, b'C' => KeyCode::Right, b'D' => KeyCode::Left, b'F' => KeyCode::End, b'H' => KeyCode::Home, b'P' => KeyCode::F(1), b'Q' => KeyCode::F(2), b'R' => KeyCode::F(3), b'S' => KeyCode::F(4), _ => return Err(could_not_parse_event_error()), }; let input_event = Event::Key(KeyEvent::new(keycode, modifiers)); Ok(Some(InternalEvent::Event(input_event))) } pub(crate) fn parse_csi_special_key_code(buffer: &[u8]) -> Result> { assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ assert!(buffer.ends_with(&[b'~'])); let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) .map_err(|_| could_not_parse_event_error())?; let mut split = s.split(';'); // This CSI sequence can be a list of semicolon-separated numbers. let first = next_parsed::(&mut split)?; let modifiers = if let Ok(modifier_mask) = next_parsed::(&mut split) { parse_modifiers(modifier_mask) } else { KeyModifiers::NONE }; let keycode = match first { 1 | 7 => KeyCode::Home, 2 => KeyCode::Insert, 3 => KeyCode::Delete, 4 | 8 => KeyCode::End, 5 => KeyCode::PageUp, 6 => KeyCode::PageDown, v @ 11..=15 => KeyCode::F(v - 10), v @ 17..=21 => KeyCode::F(v - 11), v @ 23..=26 => KeyCode::F(v - 12), v @ 28..=29 => KeyCode::F(v - 15), v @ 31..=34 => KeyCode::F(v - 17), _ => return Err(could_not_parse_event_error()), }; let input_event = Event::Key(KeyEvent::new(keycode, modifiers)); Ok(Some(InternalEvent::Event(input_event))) } pub(crate) fn parse_csi_rxvt_mouse(buffer: &[u8]) -> Result> { // rxvt mouse encoding: // ESC [ Cb ; Cx ; Cy ; M assert!(buffer.starts_with(&[b'\x1B', b'['])); // ESC [ assert!(buffer.ends_with(&[b'M'])); let s = std::str::from_utf8(&buffer[2..buffer.len() - 1]) .map_err(|_| could_not_parse_event_error())?; let mut split = s.split(';'); let cb = next_parsed::(&mut split)? .checked_sub(32) .ok_or_else(could_not_parse_event_error)?; let (kind, modifiers) = parse_cb(cb)?; let cx = next_parsed::(&mut split)? - 1; let cy = next_parsed::(&mut split)? - 1; Ok(Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind, column: cx, row: cy, modifiers, })))) } pub(crate) fn parse_csi_normal_mouse(buffer: &[u8]) -> Result> { // Normal mouse encoding: ESC [ M CB Cx Cy (6 characters only). assert!(buffer.starts_with(&[b'\x1B', b'[', b'M'])); // ESC [ M if buffer.len() < 6 { return Ok(None); } let cb = buffer[3] .checked_sub(32) .ok_or_else(could_not_parse_event_error)?; let (kind, modifiers) = parse_cb(cb)?; // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking // The upper left character position on the terminal is denoted as 1,1. // Subtract 1 to keep it synced with cursor let cx = u16::from(buffer[4].saturating_sub(32)) - 1; let cy = u16::from(buffer[5].saturating_sub(32)) - 1; Ok(Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind, column: cx, row: cy, modifiers, })))) } pub(crate) fn parse_csi_sgr_mouse(buffer: &[u8]) -> Result> { // ESC [ < Cb ; Cx ; Cy (;) (M or m) assert!(buffer.starts_with(&[b'\x1B', b'[', b'<'])); // ESC [ < if !buffer.ends_with(&[b'm']) && !buffer.ends_with(&[b'M']) { return Ok(None); } let s = std::str::from_utf8(&buffer[3..buffer.len() - 1]) .map_err(|_| could_not_parse_event_error())?; let mut split = s.split(';'); let cb = next_parsed::(&mut split)?; let (kind, modifiers) = parse_cb(cb)?; // See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking // The upper left character position on the terminal is denoted as 1,1. // Subtract 1 to keep it synced with cursor let cx = next_parsed::(&mut split)? - 1; let cy = next_parsed::(&mut split)? - 1; // When button 3 in Cb is used to represent mouse release, you can't tell which button was // released. SGR mode solves this by having the sequence end with a lowercase m if it's a // button release and an uppercase M if it's a buton press. // // We've already checked that the last character is a lowercase or uppercase M at the start of // this function, so we just need one if. let kind = if buffer.last() == Some(&b'm') { match kind { MouseEventKind::Down(button) => MouseEventKind::Up(button), other => other, } } else { kind }; Ok(Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind, column: cx, row: cy, modifiers, })))) } /// Cb is the byte of a mouse input that contains the button being used, the key modifiers being /// held and whether the mouse is dragging or not. /// /// Bit layout of cb, from low to high: /// /// - button number /// - button number /// - shift /// - meta (alt) /// - control /// - mouse is dragging /// - button number /// - button number fn parse_cb(cb: u8) -> Result<(MouseEventKind, KeyModifiers)> { let button_number = (cb & 0b0000_0011) | ((cb & 0b1100_0000) >> 4); let dragging = cb & 0b0010_0000 == 0b0010_0000; let kind = match (button_number, dragging) { (0, false) => MouseEventKind::Down(MouseButton::Left), (1, false) => MouseEventKind::Down(MouseButton::Middle), (2, false) => MouseEventKind::Down(MouseButton::Right), (0, true) => MouseEventKind::Drag(MouseButton::Left), (1, true) => MouseEventKind::Drag(MouseButton::Middle), (2, true) => MouseEventKind::Drag(MouseButton::Right), (3, false) => MouseEventKind::Up(MouseButton::Left), (3, true) | (4, true) | (5, true) => MouseEventKind::Moved, (4, false) => MouseEventKind::ScrollUp, (5, false) => MouseEventKind::ScrollDown, // We do not support other buttons. _ => return Err(could_not_parse_event_error()), }; let mut modifiers = KeyModifiers::empty(); if cb & 0b0000_0100 == 0b0000_0100 { modifiers |= KeyModifiers::SHIFT; } if cb & 0b0000_1000 == 0b0000_1000 { modifiers |= KeyModifiers::ALT; } if cb & 0b0001_0000 == 0b0001_0000 { modifiers |= KeyModifiers::CONTROL; } Ok((kind, modifiers)) } pub(crate) fn parse_utf8_char(buffer: &[u8]) -> Result> { match std::str::from_utf8(buffer) { Ok(s) => { let ch = s.chars().next().ok_or_else(could_not_parse_event_error)?; Ok(Some(ch)) } Err(_) => { // from_utf8 failed, but we have to check if we need more bytes for code point // and if all the bytes we have no are valid let required_bytes = match buffer[0] { // https://en.wikipedia.org/wiki/UTF-8#Description (0x00..=0x7F) => 1, // 0xxxxxxx (0xC0..=0xDF) => 2, // 110xxxxx 10xxxxxx (0xE0..=0xEF) => 3, // 1110xxxx 10xxxxxx 10xxxxxx (0xF0..=0xF7) => 4, // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (0x80..=0xBF) | (0xF8..=0xFF) => return Err(could_not_parse_event_error()), }; // More than 1 byte, check them for 10xxxxxx pattern if required_bytes > 1 && buffer.len() > 1 { for byte in &buffer[1..] { if byte & !0b0011_1111 != 0b1000_0000 { return Err(could_not_parse_event_error()); } } } if buffer.len() < required_bytes { // All bytes looks good so far, but we need more of them Ok(None) } else { Err(could_not_parse_event_error()) } } } } #[cfg(test)] mod tests { use crate::event::{KeyModifiers, MouseButton, MouseEvent}; use super::*; #[test] fn test_esc_key() { assert_eq!( parse_event(b"\x1B", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into()))), ); } #[test] fn test_possible_esc_sequence() { assert_eq!(parse_event(b"\x1B", true).unwrap(), None,); } #[test] fn test_alt_key() { assert_eq!( parse_event(b"\x1Bc", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('c'), KeyModifiers::ALT )))), ); } #[test] fn test_alt_shift() { assert_eq!( parse_event(b"\x1BH", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('H'), KeyModifiers::ALT | KeyModifiers::SHIFT )))), ); } #[test] fn test_alt_ctrl() { assert_eq!( parse_event(b"\x1B\x14", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('t'), KeyModifiers::ALT | KeyModifiers::CONTROL )))), ); } #[test] fn test_parse_event_subsequent_calls() { // The main purpose of this test is to check if we're passing // correct slice to other parse_ functions. // parse_csi_cursor_position assert_eq!( parse_event(b"\x1B[20;10R", false).unwrap(), Some(InternalEvent::CursorPosition(9, 19)) ); // parse_csi assert_eq!( parse_event(b"\x1B[D", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), ); // parse_csi_modifier_key_code assert_eq!( parse_event(b"\x1B[2D", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Left, KeyModifiers::SHIFT )))) ); // parse_csi_special_key_code assert_eq!( parse_event(b"\x1B[3~", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), ); // parse_csi_rxvt_mouse assert_eq!( parse_event(b"\x1B[32;30;40;M", false).unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 29, row: 39, modifiers: KeyModifiers::empty(), }))) ); // parse_csi_normal_mouse assert_eq!( parse_event(b"\x1B[M0\x60\x70", false).unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 63, row: 79, modifiers: KeyModifiers::CONTROL, }))) ); // parse_csi_sgr_mouse assert_eq!( parse_event(b"\x1B[<0;20;10;M", false).unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 19, row: 9, modifiers: KeyModifiers::empty(), }))) ); // parse_utf8_char assert_eq!( parse_event("Ž".as_bytes(), false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('Ž'), KeyModifiers::SHIFT )))), ); } #[test] fn test_parse_event() { assert_eq!( parse_event(b"\t", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Tab.into()))), ); } #[test] fn test_parse_csi_cursor_position() { assert_eq!( parse_csi_cursor_position(b"\x1B[20;10R").unwrap(), Some(InternalEvent::CursorPosition(9, 19)) ); } #[test] fn test_parse_csi() { assert_eq!( parse_csi(b"\x1B[D").unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Left.into()))), ); } #[test] fn test_parse_csi_modifier_key_code() { assert_eq!( parse_csi_modifier_key_code(b"\x1B[2D").unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Left, KeyModifiers::SHIFT )))), ); } #[test] fn test_parse_csi_special_key_code() { assert_eq!( parse_csi_special_key_code(b"\x1B[3~").unwrap(), Some(InternalEvent::Event(Event::Key(KeyCode::Delete.into()))), ); } #[test] fn test_parse_csi_special_key_code_multiple_values_not_supported() { assert_eq!( parse_csi_special_key_code(b"\x1B[3;2~").unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Delete, KeyModifiers::SHIFT )))), ); } #[test] fn test_parse_csi_rxvt_mouse() { assert_eq!( parse_csi_rxvt_mouse(b"\x1B[32;30;40;M").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 29, row: 39, modifiers: KeyModifiers::empty(), }))) ); } #[test] fn test_parse_csi_normal_mouse() { assert_eq!( parse_csi_normal_mouse(b"\x1B[M0\x60\x70").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 63, row: 79, modifiers: KeyModifiers::CONTROL, }))) ); } #[test] fn test_parse_csi_sgr_mouse() { assert_eq!( parse_csi_sgr_mouse(b"\x1B[<0;20;10;M").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 19, row: 9, modifiers: KeyModifiers::empty(), }))) ); assert_eq!( parse_csi_sgr_mouse(b"\x1B[<0;20;10M").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Down(MouseButton::Left), column: 19, row: 9, modifiers: KeyModifiers::empty(), }))) ); assert_eq!( parse_csi_sgr_mouse(b"\x1B[<0;20;10;m").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Up(MouseButton::Left), column: 19, row: 9, modifiers: KeyModifiers::empty(), }))) ); assert_eq!( parse_csi_sgr_mouse(b"\x1B[<0;20;10m").unwrap(), Some(InternalEvent::Event(Event::Mouse(MouseEvent { kind: MouseEventKind::Up(MouseButton::Left), column: 19, row: 9, modifiers: KeyModifiers::empty(), }))) ); } #[test] fn test_utf8() { // https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805 // 'Valid ASCII' => "a", assert_eq!(parse_utf8_char(b"a").unwrap(), Some('a'),); // 'Valid 2 Octet Sequence' => "\xc3\xb1", assert_eq!(parse_utf8_char(&[0xC3, 0xB1]).unwrap(), Some('ñ'),); // 'Invalid 2 Octet Sequence' => "\xc3\x28", assert!(parse_utf8_char(&[0xC3, 0x28]).is_err()); // 'Invalid Sequence Identifier' => "\xa0\xa1", assert!(parse_utf8_char(&[0xA0, 0xA1]).is_err()); // 'Valid 3 Octet Sequence' => "\xe2\x82\xa1", assert_eq!( parse_utf8_char(&[0xE2, 0x81, 0xA1]).unwrap(), Some('\u{2061}'), ); // 'Invalid 3 Octet Sequence (in 2nd Octet)' => "\xe2\x28\xa1", assert!(parse_utf8_char(&[0xE2, 0x28, 0xA1]).is_err()); // 'Invalid 3 Octet Sequence (in 3rd Octet)' => "\xe2\x82\x28", assert!(parse_utf8_char(&[0xE2, 0x82, 0x28]).is_err()); // 'Valid 4 Octet Sequence' => "\xf0\x90\x8c\xbc", assert_eq!( parse_utf8_char(&[0xF0, 0x90, 0x8C, 0xBC]).unwrap(), Some('ðŒ¼'), ); // 'Invalid 4 Octet Sequence (in 2nd Octet)' => "\xf0\x28\x8c\xbc", assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0xBC]).is_err()); // 'Invalid 4 Octet Sequence (in 3rd Octet)' => "\xf0\x90\x28\xbc", assert!(parse_utf8_char(&[0xF0, 0x90, 0x28, 0xBC]).is_err()); // 'Invalid 4 Octet Sequence (in 4th Octet)' => "\xf0\x28\x8c\x28", assert!(parse_utf8_char(&[0xF0, 0x28, 0x8C, 0x28]).is_err()); } #[test] fn test_parse_char_event_lowercase() { assert_eq!( parse_event(b"c", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('c'), KeyModifiers::empty() )))), ); } #[test] fn test_parse_char_event_uppercase() { assert_eq!( parse_event(b"C", false).unwrap(), Some(InternalEvent::Event(Event::Key(KeyEvent::new( KeyCode::Char('C'), KeyModifiers::SHIFT )))), ); } } crossterm-0.22.1/src/event/sys/unix/waker.rs000064400000000000000000000017710072674642500171550ustar 00000000000000use std::sync::{Arc, Mutex}; use mio::{Registry, Token}; use crate::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() } /// Resets the state so the same waker can be reused. /// /// This function is not impl #[allow(dead_code, clippy::clippy::unnecessary_wraps)] pub(crate) fn reset(&self) -> Result<()> { Ok(()) } } crossterm-0.22.1/src/event/sys/unix.rs000064400000000000000000000001630072674642500160360ustar 00000000000000#[cfg(feature = "event-stream")] pub(crate) mod waker; pub(crate) mod file_descriptor; pub(crate) mod parse; crossterm-0.22.1/src/event/sys/windows/parse.rs000064400000000000000000000146320072674642500176650ustar 00000000000000use crossterm_winapi::{ControlKeyState, EventFlags, KeyEventRecord, MouseEvent, ScreenBuffer}; use winapi::um::{ wincon::{ LEFT_ALT_PRESSED, LEFT_CTRL_PRESSED, RIGHT_ALT_PRESSED, RIGHT_CTRL_PRESSED, SHIFT_PRESSED, }, winuser::{ VK_BACK, VK_CONTROL, VK_DELETE, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_SHIFT, VK_UP, }, }; use crate::{ event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind}, Result, }; pub(crate) fn handle_mouse_event(mouse_event: MouseEvent) -> Option { if let Ok(Some(event)) = parse_mouse_event_record(&mouse_event) { return Some(Event::Mouse(event)); } None } pub(crate) fn handle_key_event(key_event: KeyEventRecord) -> Option { if key_event.key_down { if let Some(event) = parse_key_event_record(&key_event) { return Some(Event::Key(event)); } } None } impl From for KeyModifiers { fn from(state: ControlKeyState) -> Self { let shift = state.has_state(SHIFT_PRESSED); let alt = state.has_state(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED); let control = state.has_state(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED); let mut modifier = KeyModifiers::empty(); if shift { modifier |= KeyModifiers::SHIFT; } if control { modifier |= KeyModifiers::CONTROL; } if alt { modifier |= KeyModifiers::ALT; } modifier } } fn parse_key_event_record(key_event: &KeyEventRecord) -> Option { let modifiers = KeyModifiers::from(key_event.control_key_state); let key_code = key_event.virtual_key_code as i32; let parse_result = match key_code { VK_SHIFT | VK_CONTROL | VK_MENU => None, VK_BACK => Some(KeyCode::Backspace), VK_ESCAPE => Some(KeyCode::Esc), VK_RETURN => Some(KeyCode::Enter), VK_F1..=VK_F24 => Some(KeyCode::F((key_event.virtual_key_code - 111) as u8)), VK_LEFT => Some(KeyCode::Left), VK_UP => Some(KeyCode::Up), VK_RIGHT => Some(KeyCode::Right), VK_DOWN => Some(KeyCode::Down), VK_PRIOR => Some(KeyCode::PageUp), VK_NEXT => Some(KeyCode::PageDown), VK_HOME => Some(KeyCode::Home), VK_END => Some(KeyCode::End), VK_DELETE => Some(KeyCode::Delete), VK_INSERT => Some(KeyCode::Insert), _ => { // Modifier Keys (Ctrl, Alt, Shift) Support let character_raw = key_event.u_char; if character_raw < 255 { // Invalid character if character_raw == 0 { return None; } let mut character = character_raw as u8 as char; if modifiers.contains(KeyModifiers::CONTROL) && !modifiers.contains(KeyModifiers::ALT) { // we need to do some parsing character = match character_raw as u8 { c @ b'\x01'..=b'\x1A' => (c as u8 - 0x1 + b'a') as char, c @ b'\x1C'..=b'\x1F' => (c as u8 - 0x1C + b'4') as char, _ => return None, } } if modifiers.contains(KeyModifiers::SHIFT) && character == '\t' { Some(KeyCode::BackTab) } else if character == '\t' { Some(KeyCode::Tab) } else { Some(KeyCode::Char(character)) } } else { std::char::from_u32(character_raw as u32).map(KeyCode::Char) } } }; if let Some(key_code) = parse_result { return Some(KeyEvent::new(key_code, modifiers)); } None } // The 'y' position of a mouse event or resize event is not relative to the window but absolute to screen buffer. // This means that when the mouse cursor is at the top left it will be x: 0, y: 2295 (e.g. y = number of cells conting from the absolute buffer height) instead of relative x: 0, y: 0 to the window. pub fn parse_relative_y(y: i16) -> Result { let window_size = ScreenBuffer::current()?.info()?.terminal_window(); Ok(y - window_size.top) } fn parse_mouse_event_record(event: &MouseEvent) -> Result> { let modifiers = KeyModifiers::from(event.control_key_state); let xpos = event.mouse_position.x as u16; let ypos = parse_relative_y(event.mouse_position.y)? as u16; let button_state = event.button_state; let button = if button_state.right_button() { MouseButton::Right } else if button_state.middle_button() { MouseButton::Middle } else { MouseButton::Left }; let kind = match event.event_flags { EventFlags::PressOrRelease => { if button_state.release_button() { // in order to read the up button type, we have to check the last down input record. Some(MouseEventKind::Up(MouseButton::Left)) } else { Some(MouseEventKind::Down(button)) } } EventFlags::MouseMoved => { if button_state.release_button() { Some(MouseEventKind::Moved) } else { Some(MouseEventKind::Drag(button)) } } EventFlags::MouseWheeled => { // Vertical scroll // from https://docs.microsoft.com/en-us/windows/console/mouse-event-record-str // if `button_state` is negative then the wheel was rotated backward, toward the user. if button_state.scroll_down() { Some(MouseEventKind::ScrollDown) } else if button_state.scroll_up() { Some(MouseEventKind::ScrollUp) } else { None } } EventFlags::DoubleClick => None, // double click not supported by unix terminals EventFlags::MouseHwheeled => None, // horizontal scroll not supported by unix terminals _ => None, }; Ok(kind.map(|kind| crate::event::MouseEvent { kind, column: xpos, row: ypos, modifiers, })) } crossterm-0.22.1/src/event/sys/windows/poll.rs000064400000000000000000000050160072674642500175150ustar 00000000000000use std::io; use std::time::Duration; use crossterm_winapi::Handle; use winapi::{ shared::winerror::WAIT_TIMEOUT, um::{ synchapi::WaitForMultipleObjects, winbase::{INFINITE, WAIT_ABANDONED_0, WAIT_FAILED, WAIT_OBJECT_0}, }, }; use crate::Result; #[cfg(feature = "event-stream")] pub(crate) use super::waker::Waker; #[derive(Debug)] pub(crate) struct WinApiPoll { #[cfg(feature = "event-stream")] waker: Waker, } impl WinApiPoll { #[cfg(not(feature = "event-stream"))] pub(crate) fn new() -> WinApiPoll { WinApiPoll {} } #[cfg(feature = "event-stream")] pub(crate) fn new() -> Result { Ok(WinApiPoll { waker: Waker::new()?, }) } } impl WinApiPoll { pub fn poll(&mut self, timeout: Option) -> Result> { let dw_millis = if let Some(duration) = timeout { duration.as_millis() as u32 } else { INFINITE }; let console_handle = Handle::current_in_handle()?; #[cfg(feature = "event-stream")] let semaphore = self.waker.semaphore(); #[cfg(feature = "event-stream")] let handles = &[*console_handle, **semaphore.handle()]; #[cfg(not(feature = "event-stream"))] let handles = &[*console_handle]; let output = unsafe { WaitForMultipleObjects(handles.len() as u32, handles.as_ptr(), 0, dw_millis) }; match output { output if output == WAIT_OBJECT_0 => { // input handle triggered Ok(Some(true)) } #[cfg(feature = "event-stream")] output if output == WAIT_OBJECT_0 + 1 => { // semaphore handle triggered let _ = self.waker.reset(); Err(io::Error::new( io::ErrorKind::Interrupted, "Poll operation was woken up by `Waker::wake`", ) .into()) } WAIT_TIMEOUT | WAIT_ABANDONED_0 => { // timeout elapsed Ok(None) } WAIT_FAILED => Err(io::Error::last_os_error()), _ => Err(io::Error::new( io::ErrorKind::Other, "WaitForMultipleObjects returned unexpected result.", )), } } #[cfg(feature = "event-stream")] pub fn waker(&self) -> Waker { self.waker.clone() } } crossterm-0.22.1/src/event/sys/windows/waker.rs000064400000000000000000000021770072674642500176650ustar 00000000000000use std::sync::{Arc, Mutex}; use crossterm_winapi::Semaphore; use crate::Result; /// Allows to wake up the `WinApiPoll::poll()` method. #[derive(Clone, Debug)] pub(crate) struct Waker { inner: Arc>, } impl Waker { /// Creates a new waker. /// /// `Waker` is based on the `Semaphore`. You have to use the semaphore /// handle along with the `WaitForMultipleObjects`. pub(crate) fn new() -> Result { let inner = Semaphore::new()?; Ok(Self { inner: Arc::new(Mutex::new(inner)), }) } /// Wakes the `WaitForMultipleObjects`. pub(crate) fn wake(&self) -> Result<()> { self.inner.lock().unwrap().release()?; Ok(()) } /// Replaces the current semaphore with a new one allowing us to reuse the same `Waker`. pub(crate) fn reset(&self) -> Result<()> { *self.inner.lock().unwrap() = Semaphore::new()?; Ok(()) } /// Returns the semaphore associated with the waker. pub(crate) fn semaphore(&self) -> Semaphore { self.inner.lock().unwrap().clone() } } crossterm-0.22.1/src/event/sys/windows.rs000064400000000000000000000032500072674642500165450ustar 00000000000000//! This is a WINDOWS specific implementation for input related action. use std::convert::TryFrom; use std::io; use std::sync::atomic::{AtomicU64, Ordering}; use crossterm_winapi::{ConsoleMode, Handle}; use crate::Result; #[cfg(feature = "event-stream")] pub(crate) mod waker; pub(crate) mod parse; pub(crate) mod poll; const ENABLE_MOUSE_MODE: u32 = 0x0010 | 0x0080 | 0x0008; /// This is a either `u64::MAX` if it's uninitialized or a valid `u32` that stores the original /// console mode if it's initialized. static ORIGINAL_CONSOLE_MODE: AtomicU64 = AtomicU64::new(u64::MAX); /// Initializes the default console color. It will will be skipped if it has already been initialized. fn init_original_console_mode(original_mode: u32) { let _ = ORIGINAL_CONSOLE_MODE.compare_exchange( u64::MAX, u64::from(original_mode), Ordering::Relaxed, Ordering::Relaxed, ); } /// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic. fn original_console_mode() -> Result { u32::try_from(ORIGINAL_CONSOLE_MODE.load(Ordering::Relaxed)) .map_err(|_| io::Error::new(io::ErrorKind::Other, "Initial console modes not set")) } pub(crate) fn enable_mouse_capture() -> Result<()> { let mode = ConsoleMode::from(Handle::current_in_handle()?); init_original_console_mode(mode.mode()?); mode.set_mode(ENABLE_MOUSE_MODE)?; Ok(()) } pub(crate) fn disable_mouse_capture() -> Result<()> { let mode = ConsoleMode::from(Handle::current_in_handle()?); mode.set_mode(original_console_mode()?)?; Ok(()) } crossterm-0.22.1/src/event/sys.rs000064400000000000000000000003700072674642500150530ustar 00000000000000#[cfg(all(unix, feature = "event-stream"))] pub(crate) use unix::waker::Waker; #[cfg(all(windows, feature = "event-stream"))] pub(crate) use windows::waker::Waker; #[cfg(unix)] pub(crate) mod unix; #[cfg(windows)] pub(crate) mod windows; crossterm-0.22.1/src/event/timeout.rs000064400000000000000000000051460072674642500157310ustar 00000000000000use std::time::{Duration, Instant}; /// Keeps track of the elapsed time since the moment the polling started. #[derive(Debug, Clone)] pub struct PollTimeout { timeout: Option, start: Instant, } impl PollTimeout { /// Constructs a new `PollTimeout` with the given optional `Duration`. pub fn new(timeout: Option) -> PollTimeout { PollTimeout { timeout, start: Instant::now(), } } /// Returns whether the timeout has elapsed. /// /// It always returns `false` if the initial timeout was set to `None`. pub fn elapsed(&self) -> bool { self.timeout .map(|timeout| self.start.elapsed() >= timeout) .unwrap_or(false) } /// Returns the timeout leftover (initial timeout duration - elapsed duration). pub fn leftover(&self) -> Option { self.timeout.map(|timeout| { let elapsed = self.start.elapsed(); if elapsed >= timeout { Duration::from_secs(0) } else { timeout - elapsed } }) } } #[cfg(test)] mod tests { use std::time::{Duration, Instant}; use super::PollTimeout; #[test] pub fn test_timeout_without_duration_does_not_have_leftover() { let timeout = PollTimeout::new(None); assert_eq!(timeout.leftover(), None) } #[test] pub fn test_timeout_without_duration_never_elapses() { let timeout = PollTimeout::new(None); assert!(!timeout.elapsed()); } #[test] pub fn test_timeout_elapses() { const TIMEOUT_MILLIS: u64 = 100; let timeout = PollTimeout { timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)), start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS), }; assert!(timeout.elapsed()); } #[test] pub fn test_elapsed_timeout_has_zero_leftover() { const TIMEOUT_MILLIS: u64 = 100; let timeout = PollTimeout { timeout: Some(Duration::from_millis(TIMEOUT_MILLIS)), start: Instant::now() - Duration::from_millis(2 * TIMEOUT_MILLIS), }; assert!(timeout.elapsed()); assert_eq!(timeout.leftover(), Some(Duration::from_millis(0))); } #[test] pub fn test_not_elapsed_timeout_has_positive_leftover() { let timeout = PollTimeout::new(Some(Duration::from_secs(60))); assert!(!timeout.elapsed()); assert!(timeout.leftover().unwrap() > Duration::from_secs(0)); } } crossterm-0.22.1/src/event.rs000064400000000000000000000412530072674642500142420ustar 00000000000000//! # Event //! //! The `event` module provides the functionality to read keyboard, mouse and terminal resize events. //! //! * The [`read`](fn.read.html) function returns an [`Event`](enum.Event.html) immediately //! (if available) or blocks until an [`Event`](enum.Event.html) is available. //! //! * The [`poll`](fn.poll.html) function allows you to check if there is or isn't an [`Event`](enum.Event.html) available //! within the given period of time. In other words - if subsequent call to the [`read`](fn.read.html) //! function will block or not. //! //! It's **not allowed** to call these functions from different threads or combine them with the //! [`EventStream`](struct.EventStream.html). You're allowed to either: //! //! * use the [`read`](fn.read.html) & [`poll`](fn.poll.html) functions on any, but same, thread //! * or the [`EventStream`](struct.EventStream.html). //! //! **Make sure to enable [raw mode](../terminal/#raw-mode) in order for keyboard events to work properly** //! //! ## Mouse Events //! //! Mouse events are not enabled by default. You have to enable them with the //! [`EnableMouseCapture`](struct.EnableMouseCapture.html) command. See [Command API](../index.html#command-api) //! for more information. //! //! ## Examples //! //! Blocking read: //! //! ```no_run //! use crossterm::event::{read, Event}; //! //! fn print_events() -> crossterm::Result<()> { //! loop { //! // `read()` blocks until an `Event` is available //! match read()? { //! Event::Key(event) => println!("{:?}", event), //! Event::Mouse(event) => println!("{:?}", event), //! Event::Resize(width, height) => println!("New size {}x{}", width, height), //! } //! } //! Ok(()) //! } //! ``` //! //! Non-blocking read: //! //! ```no_run //! use std::time::Duration; //! //! use crossterm::event::{poll, read, Event}; //! //! fn print_events() -> crossterm::Result<()> { //! loop { //! // `poll()` waits for an `Event` for a given time period //! if poll(Duration::from_millis(500))? { //! // It's guaranteed that the `read()` won't block when the `poll()` //! // function returns `true` //! match read()? { //! Event::Key(event) => println!("{:?}", event), //! Event::Mouse(event) => println!("{:?}", event), //! Event::Resize(width, height) => println!("New size {}x{}", width, height), //! } //! } else { //! // Timeout expired and no `Event` is available //! } //! } //! Ok(()) //! } //! ``` //! //! Check the [examples](https://github.com/crossterm-rs/crossterm/tree/master/examples) folder for more of //! them (`event-*`). use std::fmt; use std::hash::{Hash, Hasher}; use std::time::Duration; use bitflags::bitflags; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::{csi, Command, Result}; use filter::{EventFilter, Filter}; use read::InternalEventReader; #[cfg(feature = "event-stream")] pub use stream::EventStream; use timeout::PollTimeout; pub(crate) mod filter; mod read; mod source; #[cfg(feature = "event-stream")] mod stream; pub(crate) mod sys; mod timeout; /// Static instance of `InternalEventReader`. /// This needs to be static because there can be one event reader. static INTERNAL_EVENT_READER: Mutex> = parking_lot::const_mutex(None); fn lock_internal_event_reader() -> MappedMutexGuard<'static, InternalEventReader> { MutexGuard::map(INTERNAL_EVENT_READER.lock(), |reader| { reader.get_or_insert_with(InternalEventReader::default) }) } fn try_lock_internal_event_reader_for( duration: Duration, ) -> Option> { Some(MutexGuard::map( INTERNAL_EVENT_READER.try_lock_for(duration)?, |reader| reader.get_or_insert_with(InternalEventReader::default), )) } /// Checks if there is an [`Event`](enum.Event.html) available. /// /// Returns `Ok(true)` if an [`Event`](enum.Event.html) is available otherwise it returns `Ok(false)`. /// /// `Ok(true)` guarantees that subsequent call to the [`read`](fn.read.html) function /// wont block. /// /// # Arguments /// /// * `timeout` - maximum waiting time for event availability /// /// # Examples /// /// Return immediately: /// /// ```no_run /// use std::time::Duration; /// /// use crossterm::{event::poll, Result}; /// /// fn is_event_available() -> Result { /// // Zero duration says that the `poll` function must return immediately /// // with an `Event` availability information /// poll(Duration::from_secs(0)) /// } /// ``` /// /// Wait up to 100ms: /// /// ```no_run /// use std::time::Duration; /// /// use crossterm::{event::poll, Result}; /// /// fn is_event_available() -> Result { /// // Wait for an `Event` availability for 100ms. It returns immediately /// // if an `Event` is/becomes available. /// poll(Duration::from_millis(100)) /// } /// ``` pub fn poll(timeout: Duration) -> Result { poll_internal(Some(timeout), &EventFilter) } /// Reads a single [`Event`](enum.Event.html). /// /// This function blocks until an [`Event`](enum.Event.html) is available. Combine it with the /// [`poll`](fn.poll.html) function to get non-blocking reads. /// /// # Examples /// /// Blocking read: /// /// ```no_run /// use crossterm::{event::read, Result}; /// /// fn print_events() -> Result { /// loop { /// // Blocks until an `Event` is available /// println!("{:?}", read()?); /// } /// } /// ``` /// /// Non-blocking read: /// /// ```no_run /// use std::time::Duration; /// /// use crossterm::{event::{read, poll}, Result}; /// /// fn print_events() -> Result { /// loop { /// if poll(Duration::from_millis(100))? { /// // It's guaranteed that `read` wont block, because `poll` returned /// // `Ok(true)`. /// println!("{:?}", read()?); /// } else { /// // Timeout expired, no `Event` is available /// } /// } /// } /// ``` pub fn read() -> Result { match read_internal(&EventFilter)? { InternalEvent::Event(event) => Ok(event), #[cfg(unix)] _ => unreachable!(), } } /// Polls to check if there are any `InternalEvent`s that can be read within the given duration. pub(crate) fn poll_internal(timeout: Option, filter: &F) -> Result where F: Filter, { let (mut reader, timeout) = if let Some(timeout) = timeout { let poll_timeout = PollTimeout::new(Some(timeout)); if let Some(reader) = try_lock_internal_event_reader_for(timeout) { (reader, poll_timeout.leftover()) } else { return Ok(false); } } else { (lock_internal_event_reader(), None) }; reader.poll(timeout, filter) } /// Reads a single `InternalEvent`. pub(crate) fn read_internal(filter: &F) -> Result where F: Filter, { let mut reader = lock_internal_event_reader(); reader.read(filter) } /// A command that enables mouse event capturing. /// /// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnableMouseCapture; impl Command for EnableMouseCapture { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(concat!( // Normal tracking: Send mouse X & Y on button press and release csi!("?1000h"), // Button-event tracking: Report button motion events (dragging) csi!("?1002h"), // Any-event tracking: Report all motion events csi!("?1003h"), // RXVT mouse mode: Allows mouse coordinates of >223 csi!("?1015h"), // SGR mouse mode: Allows mouse coordinates of >223, preferred over RXVT mode csi!("?1006h"), )) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::windows::enable_mouse_capture() } #[cfg(windows)] fn is_ansi_code_supported(&self) -> bool { false } } /// A command that disables mouse event capturing. /// /// Mouse events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DisableMouseCapture; impl Command for DisableMouseCapture { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(concat!( // The inverse commands of EnableMouseCapture, in reverse order. csi!("?1006l"), csi!("?1015l"), csi!("?1003l"), csi!("?1002l"), csi!("?1000l"), )) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::windows::disable_mouse_capture() } #[cfg(windows)] fn is_ansi_code_supported(&self) -> bool { false } } /// Represents an event. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub enum Event { /// A single key event with additional pressed modifiers. Key(KeyEvent), /// A single mouse event with additional pressed modifiers. Mouse(MouseEvent), /// An resize event with new dimensions after resize (columns, rows). /// **Note** that resize events can be occur in batches. Resize(u16, u16), } /// Represents a mouse event. /// /// # Platform-specific Notes /// /// ## Mouse Buttons /// /// Some platforms/terminals do not report mouse button for the /// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left` /// is returned if we don't know which button was used. /// /// ## Key Modifiers /// /// Some platforms/terminals does not report all key modifiers /// combinations for all mouse event types. For example - macOS reports /// `Ctrl` + left mouse button click as a right mouse button click. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub struct MouseEvent { /// The kind of mouse event that was caused. pub kind: MouseEventKind, /// The column that the event occurred on. pub column: u16, /// The row that the event occurred on. pub row: u16, /// The key modifiers active when the event occurred. pub modifiers: KeyModifiers, } /// A mouse event kind. /// /// # Platform-specific Notes /// /// ## Mouse Buttons /// /// Some platforms/terminals do not report mouse button for the /// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left` /// is returned if we don't know which button was used. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub enum MouseEventKind { /// Pressed mouse button. Contains the button that was pressed. Down(MouseButton), /// Released mouse button. Contains the button that was released. Up(MouseButton), /// Moved the mouse cursor while pressing the contained mouse button. Drag(MouseButton), /// Moved the mouse cursor while not pressing a mouse button. Moved, /// Scrolled mouse wheel downwards (towards the user). ScrollDown, /// Scrolled mouse wheel upwards (away from the user). ScrollUp, } /// Represents a mouse button. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] pub enum MouseButton { /// Left mouse button. Left, /// Right mouse button. Right, /// Middle mouse button. Middle, } bitflags! { /// Represents key modifiers (shift, control, alt). #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct KeyModifiers: u8 { const SHIFT = 0b0000_0001; const CONTROL = 0b0000_0010; const ALT = 0b0000_0100; const NONE = 0b0000_0000; } } /// Represents a key event. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialOrd, Clone, Copy)] pub struct KeyEvent { /// The key itself. pub code: KeyCode, /// Additional key modifiers. pub modifiers: KeyModifiers, } impl KeyEvent { pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent { KeyEvent { code, modifiers } } // modifies the KeyEvent, // so that KeyModifiers::SHIFT is present iff // an uppercase char is present. fn normalize_case(mut self) -> KeyEvent { let c = match self.code { KeyCode::Char(c) => c, _ => return self, }; if c.is_ascii_uppercase() { self.modifiers.insert(KeyModifiers::SHIFT); } else if self.modifiers.contains(KeyModifiers::SHIFT) { self.code = KeyCode::Char(c.to_ascii_uppercase()) } self } } impl From for KeyEvent { fn from(code: KeyCode) -> Self { KeyEvent { code, modifiers: KeyModifiers::empty(), } } } impl PartialEq for KeyEvent { fn eq(&self, other: &KeyEvent) -> bool { let KeyEvent { code: lhs_code, modifiers: lhs_modifiers, } = self.normalize_case(); let KeyEvent { code: rhs_code, modifiers: rhs_modifiers, } = other.normalize_case(); (lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers) } } impl Eq for KeyEvent {} impl Hash for KeyEvent { fn hash(&self, state: &mut H) { let KeyEvent { code, modifiers } = self.normalize_case(); code.hash(state); modifiers.hash(state); } } /// Represents a key. #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum KeyCode { /// Backspace key. Backspace, /// Enter key. Enter, /// Left arrow key. Left, /// Right arrow key. Right, /// Up arrow key. Up, /// Down arrow key. Down, /// Home key. Home, /// End key. End, /// Page up key. PageUp, /// Page dow key. PageDown, /// Tab key. Tab, /// Shift + Tab key. BackTab, /// Delete key. Delete, /// Insert key. Insert, /// F key. /// /// `KeyCode::F(1)` represents F1 key, etc. F(u8), /// A character. /// /// `KeyCode::Char('c')` represents `c` character, etc. Char(char), /// Null. Null, /// Escape key. Esc, } /// An internal event. /// /// Encapsulates publicly available `Event` with additional internal /// events that shouldn't be publicly available to the crate users. #[derive(Debug, PartialOrd, PartialEq, Hash, Clone, Eq)] pub(crate) enum InternalEvent { /// An event. Event(Event), /// A cursor position (`col`, `row`). #[cfg(unix)] CursorPosition(u16, u16), } #[cfg(test)] mod tests { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use super::{KeyCode, KeyEvent, KeyModifiers}; #[test] fn test_equality() { let lowercase_d_with_shift = KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT); let uppercase_d_with_shift = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT); let uppercase_d = KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE); assert_eq!(lowercase_d_with_shift, uppercase_d_with_shift); assert_eq!(uppercase_d, uppercase_d_with_shift); } #[test] fn test_hash() { let lowercase_d_with_shift_hash = { let mut hasher = DefaultHasher::new(); KeyEvent::new(KeyCode::Char('d'), KeyModifiers::SHIFT).hash(&mut hasher); hasher.finish() }; let uppercase_d_with_shift_hash = { let mut hasher = DefaultHasher::new(); KeyEvent::new(KeyCode::Char('D'), KeyModifiers::SHIFT).hash(&mut hasher); hasher.finish() }; let uppercase_d_hash = { let mut hasher = DefaultHasher::new(); KeyEvent::new(KeyCode::Char('D'), KeyModifiers::NONE).hash(&mut hasher); hasher.finish() }; assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash); assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash); } } crossterm-0.22.1/src/lib.rs000064400000000000000000000240020072674642500136600ustar 00000000000000#![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`](cursor/index.html) //! - 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),[`MoveToRow`](cursor/struct.MoveToRow.html), //! [`MoveToNextLine`](cursor/struct.MoveToNextLine.html), [`MoveToPreviousLine`](cursor/struct.MoveToPreviousLine.html), //! - Shape - //! [`SetCursorShape`](cursor/struct.SetCursorShape.html) //! - Module [`event`](event/index.html) //! - Mouse events - [`EnableMouseCapture`](event/struct.EnableMouseCapture.html), //! [`DisableMouseCapture`](event/struct.DisableMouseCapture.html) //! - Module [`style`](style/index.html) //! - 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`](terminal/index.html) //! - 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) //! [`DisableLineWrap`](terminal/struct.DisableLineWrap.html) //! [`EnableLineWrap`](terminal/struct.EnableLineWrap.html) //! - Alternate screen - [`EnterAlternateScreen`](terminal/struct.EnterAlternateScreen.html), //! [`LeaveAlternateScreen`](terminal/struct.LeaveAlternateScreen.html) //! //! ### Command Execution //! //! There are two different ways 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, Stylize}, 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, Stylize}, 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::{ 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; #[cfg(windows)] /// A module that exposes one function to check if the current terminal supports ansi sequences. pub mod ansi_support; mod command; mod error; pub(crate) mod macros; crossterm-0.22.1/src/macros.rs000064400000000000000000000305050072674642500144030ustar 00000000000000/// Append a the first few characters of an ANSI escape code to the given string. #[macro_export] #[doc(hidden)] macro_rules! csi { ($( $l:expr ),*) => { concat!("\x1B[", $( $l ),*) }; } /// Queues one or more command(s) for further execution. /// /// Queued commands must be flushed to the underlying device to be executed. /// This generally happens in the following cases: /// /// * When `flush` is called manually on the given type implementing `io::Write`. /// * The terminal will `flush` automatically if the buffer is full. /// * Each line is flushed in case of `stdout`, because it is line buffered. /// /// # Arguments /// /// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html) /// /// ANSI escape codes are written on the given 'writer', after which they are flushed. /// /// - [Command](./trait.Command.html) /// /// One or more commands /// /// # Examples /// /// ```rust /// use std::io::{Write, stdout}; /// use crossterm::{queue, style::Print}; /// /// fn main() { /// let mut stdout = stdout(); /// /// // `Print` will executed executed when `flush` is called. /// queue!(stdout, Print("foo".to_string())); /// /// // some other code (no execution happening here) ... /// /// // when calling `flush` on `stdout`, all commands will be written to the stdout and therefore executed. /// stdout.flush(); /// /// // ==== Output ==== /// // foo /// } /// ``` /// /// Have a look over at the [Command API](./#command-api) for more details. /// /// # Notes /// /// In case of Windows versions lower than 10, a direct WinAPI call will be made. /// The reason for this is that Windows versions lower than 10 do not support ANSI codes, /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](macro.execute.html) /// and [queue](macro.queue.html) for those old Windows versions. /// #[macro_export] macro_rules! queue { ($writer:expr $(, $command:expr)* $(,)?) => {{ use ::std::io::Write; // This allows the macro to take both mut impl Write and &mut impl Write. Ok($writer.by_ref()) $(.and_then(|writer| $crate::QueueableCommand::queue(writer, $command)))* .map(|_| ()) }} } /// Executes one or more command(s). /// /// # Arguments /// /// - [std::io::Writer](https://doc.rust-lang.org/std/io/trait.Write.html) /// /// ANSI escape codes are written on the given 'writer', after which they are flushed. /// /// - [Command](./trait.Command.html) /// /// One or more commands /// /// # Examples /// /// ```rust /// use std::io::{Write, stdout}; /// use crossterm::{execute, style::Print}; /// /// fn main() { /// // will be executed directly /// execute!(stdout(), Print("sum:\n".to_string())); /// /// // will be executed directly /// execute!(stdout(), Print("1 + 1= ".to_string()), Print((1+1).to_string())); /// /// // ==== Output ==== /// // sum: /// // 1 + 1 = 2 /// } /// ``` /// /// Have a look over at the [Command API](./#command-api) for more details. /// /// # Notes /// /// * In the case of UNIX and Windows 10, ANSI codes are written to the given 'writer'. /// * In case of Windows versions lower than 10, a direct WinAPI call will be made. /// The reason for this is that Windows versions lower than 10 do not support ANSI codes, /// and can therefore not be written to the given `writer`. /// Therefore, there is no difference between [execute](macro.execute.html) /// and [queue](macro.queue.html) for those old Windows versions. #[macro_export] macro_rules! execute { ($writer:expr $(, $command:expr)* $(,)? ) => {{ use ::std::io::Write; // Queue each command, then flush $crate::queue!($writer $(, $command)*) .and_then(|()| { ::std::io::Write::flush($writer.by_ref()) }) }} } #[doc(hidden)] #[macro_export] macro_rules! impl_display { (for $($t:ty),+) => { $(impl ::std::fmt::Display for $t { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { $crate::command::execute_fmt(f, self) } })* } } #[doc(hidden)] #[macro_export] macro_rules! impl_from { ($from:path, $to:expr) => { impl From<$from> for ErrorKind { fn from(e: $from) -> Self { $to(e) } } }; } #[cfg(test)] mod tests { use std::io; use std::str; // Helper for execute tests to confirm flush #[derive(Default, Debug, Clone)] pub(self) struct FakeWrite { buffer: String, flushed: bool, } impl io::Write for FakeWrite { fn write(&mut self, content: &[u8]) -> io::Result { let content = str::from_utf8(content) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; self.buffer.push_str(content); self.flushed = false; Ok(content.len()) } fn flush(&mut self) -> io::Result<()> { self.flushed = true; Ok(()) } } #[cfg(not(windows))] mod unix { use std::fmt; use super::FakeWrite; use crate::command::Command; pub struct FakeCommand; impl Command for FakeCommand { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str("cmd") } } #[test] fn test_queue_one() { let mut result = FakeWrite::default(); queue!(&mut result, FakeCommand).unwrap(); assert_eq!(&result.buffer, "cmd"); assert!(!result.flushed); } #[test] fn test_queue_many() { let mut result = FakeWrite::default(); queue!(&mut result, FakeCommand, FakeCommand).unwrap(); assert_eq!(&result.buffer, "cmdcmd"); assert!(!result.flushed); } #[test] fn test_queue_trailing_comma() { let mut result = FakeWrite::default(); queue!(&mut result, FakeCommand, FakeCommand,).unwrap(); assert_eq!(&result.buffer, "cmdcmd"); assert!(!result.flushed); } #[test] fn test_execute_one() { let mut result = FakeWrite::default(); execute!(&mut result, FakeCommand).unwrap(); assert_eq!(&result.buffer, "cmd"); assert!(result.flushed); } #[test] fn test_execute_many() { let mut result = FakeWrite::default(); execute!(&mut result, FakeCommand, FakeCommand).unwrap(); assert_eq!(&result.buffer, "cmdcmd"); assert!(result.flushed); } #[test] fn test_execute_trailing_comma() { let mut result = FakeWrite::default(); execute!(&mut result, FakeCommand, FakeCommand,).unwrap(); assert_eq!(&result.buffer, "cmdcmd"); assert!(result.flushed); } } #[cfg(windows)] mod windows { use std::fmt; use std::cell::RefCell; use std::fmt::Debug; use super::FakeWrite; use crate::command::Command; use crate::error::Result as CrosstermResult; // We need to test two different APIs: WinAPI and the write api. We // don't know until runtime which we're supporting (via // Command::is_ansi_code_supported), so we have to test them both. The // CI environment hopefully includes both versions of windows. // WindowsEventStream is a place for execute_winapi to push strings, // when called. type WindowsEventStream = Vec<&'static str>; struct FakeCommand<'a> { // Need to use a refcell because we want execute_winapi to be able // push to the vector, but execute_winapi take &self. stream: RefCell<&'a mut WindowsEventStream>, value: &'static str, } impl<'a> FakeCommand<'a> { fn new(stream: &'a mut WindowsEventStream, value: &'static str) -> Self { Self { value, stream: RefCell::new(stream), } } } impl<'a> Command for FakeCommand<'a> { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(self.value) } fn execute_winapi(&self) -> CrosstermResult<()> { self.stream.borrow_mut().push(self.value); Ok(()) } } // Helper function for running tests against either WinAPI or an // io::Write. // // This function will execute the `test` function, which should // queue some commands against the given FakeWrite and // WindowsEventStream. It will then test that the correct data sink // was populated. It does not currently check is_ansi_code_supported; // for now it simply checks that one of the two streams was correctly // populated. // // If the stream was populated, it tests that the two arrays are equal. // If the writer was populated, it tests that the contents of the // write buffer are equal to the concatenation of `stream_result`. fn test_harness( stream_result: &[&'static str], test: impl FnOnce(&mut FakeWrite, &mut WindowsEventStream) -> Result<(), E>, ) { let mut stream = WindowsEventStream::default(); let mut writer = FakeWrite::default(); if let Err(err) = test(&mut writer, &mut stream) { panic!("Error returned from test function: {:?}", err); } // We need this for type inference, for whatever reason. const EMPTY_RESULT: [&str; 0] = []; // TODO: confirm that the correct sink was used, based on // is_ansi_code_supported match (writer.buffer.is_empty(), stream.is_empty()) { (true, true) if stream_result == EMPTY_RESULT => {} (true, true) => panic!( "Neither the event stream nor the writer were populated. Expected {:?}", stream_result ), // writer is populated (false, true) => { // Concat the stream result to find the string result let result: String = stream_result.iter().copied().collect(); assert_eq!(result, writer.buffer); assert_eq!(&stream, &EMPTY_RESULT); } // stream is populated (true, false) => { assert_eq!(stream, stream_result); assert_eq!(writer.buffer, ""); } // Both are populated (false, false) => panic!( "Both the writer and the event stream were written to.\n\ Only one should be used, based on is_ansi_code_supported.\n\ stream: {stream:?}\n\ writer: {writer:?}", stream = stream, writer = writer, ), } } #[test] fn test_queue_one() { test_harness(&["cmd1"], |writer, stream| { queue!(writer, FakeCommand::new(stream, "cmd1")) }) } #[test] fn test_queue_some() { test_harness(&["cmd1", "cmd2"], |writer, stream| { queue!( writer, FakeCommand::new(stream, "cmd1"), FakeCommand::new(stream, "cmd2"), ) }) } #[test] fn test_many_queues() { test_harness(&["cmd1", "cmd2", "cmd3"], |writer, stream| { queue!(writer, FakeCommand::new(stream, "cmd1"))?; queue!(writer, FakeCommand::new(stream, "cmd2"))?; queue!(writer, FakeCommand::new(stream, "cmd3")) }) } } } crossterm-0.22.1/src/style/attributes.rs000064400000000000000000000060500072674642500164430ustar 00000000000000use std::ops::{BitAnd, BitOr, BitXor}; use crate::style::Attribute; /// a bitset for all possible attributes #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct Attributes(u32); impl From for Attributes { fn from(attribute: Attribute) -> Self { Self(attribute.bytes()) } } impl From<&[Attribute]> for Attributes { fn from(arr: &[Attribute]) -> Self { let mut attributes = Attributes::default(); for &attr in arr { attributes.set(attr); } attributes } } impl BitAnd for Attributes { type Output = Self; fn bitand(self, rhs: Attribute) -> Self { Self(self.0 & rhs.bytes()) } } impl BitAnd for Attributes { type Output = Self; fn bitand(self, rhs: Self) -> Self { Self(self.0 & rhs.0) } } impl BitOr for Attributes { type Output = Self; fn bitor(self, rhs: Attribute) -> Self { Self(self.0 | rhs.bytes()) } } impl BitOr for Attributes { type Output = Self; fn bitor(self, rhs: Self) -> Self { Self(self.0 | rhs.0) } } impl BitXor for Attributes { type Output = Self; fn bitxor(self, rhs: Attribute) -> Self { Self(self.0 ^ rhs.bytes()) } } impl BitXor for Attributes { type Output = Self; fn bitxor(self, rhs: Self) -> Self { Self(self.0 ^ rhs.0) } } impl Attributes { /// Sets the attribute. /// If it's already set, this does nothing. #[inline(always)] pub fn set(&mut self, attribute: Attribute) { self.0 |= attribute.bytes(); } /// Unsets the attribute. /// If it's not set, this changes nothing. #[inline(always)] pub fn unset(&mut self, attribute: Attribute) { self.0 &= !attribute.bytes(); } /// Sets the attribute if it's unset, unset it /// if it is set. #[inline(always)] pub fn toggle(&mut self, attribute: Attribute) { self.0 ^= attribute.bytes(); } /// Returns whether the attribute is set. #[inline(always)] pub const fn has(self, attribute: Attribute) -> bool { self.0 & attribute.bytes() != 0 } /// Sets all the passed attributes. Removes none. #[inline(always)] pub fn extend(&mut self, attributes: Attributes) { self.0 |= attributes.0; } /// Returns whether there is no attribute set. #[inline(always)] pub const fn is_empty(self) -> bool { self.0 == 0 } } #[cfg(test)] mod tests { use super::{Attribute, Attributes}; #[test] fn test_attributes() { let mut attributes: Attributes = Attribute::Bold.into(); assert!(attributes.has(Attribute::Bold)); attributes.set(Attribute::Italic); assert!(attributes.has(Attribute::Italic)); attributes.unset(Attribute::Italic); assert!(!attributes.has(Attribute::Italic)); attributes.toggle(Attribute::Bold); assert!(attributes.is_empty()); } } crossterm-0.22.1/src/style/content_style.rs000064400000000000000000000020770072674642500171540ustar 00000000000000//! This module contains the `content style` that can be applied to an `styled content`. use std::fmt::Display; use crate::style::{Attributes, Color, StyledContent}; /// The style that can be put on content. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub struct ContentStyle { /// The foreground color. pub foreground_color: Option, /// The background color. pub background_color: Option, /// List of attributes. pub attributes: Attributes, } impl ContentStyle { /// Creates a `StyledContent` by applying the style to the given `val`. #[inline] pub fn apply(self, val: D) -> StyledContent { StyledContent::new(self, val) } /// Creates a new `ContentStyle`. #[inline] pub fn new() -> ContentStyle { ContentStyle::default() } } impl AsRef for ContentStyle { fn as_ref(&self) -> &Self { self } } impl AsMut for ContentStyle { fn as_mut(&mut self) -> &mut Self { self } } crossterm-0.22.1/src/style/styled_content.rs000064400000000000000000000036450072674642500173220ustar 00000000000000//! This module contains the logic to style some content. use std::fmt::{self, Display, Formatter}; use super::{ContentStyle, PrintStyledContent}; /// The style with the content to be styled. /// /// # Examples /// /// ```rust /// use crossterm::style::{style, Color, Attribute, Stylize}; /// /// let styled = "Hello there" /// .with(Color::Yellow) /// .on(Color::Blue) /// .attribute(Attribute::Bold); /// /// println!("{}", styled); /// ``` #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct StyledContent { /// The style (colors, content attributes). style: ContentStyle, /// A content to apply the style on. content: D, } impl StyledContent { /// Creates a new `StyledContent`. #[inline] pub fn new(style: ContentStyle, content: D) -> StyledContent { StyledContent { style, content } } /// Returns the content. #[inline] pub fn content(&self) -> &D { &self.content } /// Returns the style. #[inline] pub fn style(&self) -> &ContentStyle { &self.style } /// Returns a mutable reference to the style, so that it can be further /// manipulated #[inline] pub fn style_mut(&mut self) -> &mut ContentStyle { &mut self.style } } impl AsRef for StyledContent { fn as_ref(&self) -> &ContentStyle { &self.style } } impl AsMut for StyledContent { fn as_mut(&mut self) -> &mut ContentStyle { &mut self.style } } impl Display for StyledContent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { crate::command::execute_fmt( f, PrintStyledContent(StyledContent { style: self.style, content: &self.content, }), ) } } crossterm-0.22.1/src/style/stylize.rs000064400000000000000000000136410072674642500157640ustar 00000000000000use std::fmt::Display; use super::{style, Attribute, Color, ContentStyle, StyledContent}; macro_rules! stylize_method { ($method_name:ident Attribute::$attribute:ident) => { calculated_docs! { #[doc = concat!( "Applies the [`", stringify!($attribute), "`](Attribute::", stringify!($attribute), ") attribute to the text.", )] fn $method_name(self) -> Self::Styled { self.attribute(Attribute::$attribute) } } }; ($method_name_fg:ident, $method_name_bg:ident Color::$color:ident) => { calculated_docs! { #[doc = concat!( "Sets the foreground color to [`", stringify!($color), "`](Color::", stringify!($color), ")." )] fn $method_name_fg(self) -> Self::Styled { self.with(Color::$color) } #[doc = concat!( "Sets the background color to [`", stringify!($color), "`](Color::", stringify!($color), ")." )] fn $method_name_bg(self) -> Self::Styled { self.on(Color::$color) } } }; } /// Provides a set of methods to set attributes and colors. /// /// # Examples /// /// ```no_run /// use crossterm::style::Stylize; /// /// println!("{}", "Bold text".bold()); /// println!("{}", "Underlined text".underlined()); /// println!("{}", "Negative text".negative()); /// println!("{}", "Red on blue".red().on_blue()); /// ``` pub trait Stylize: Sized { /// This type with styles applied. type Styled: AsRef + AsMut; /// Styles this type. fn stylize(self) -> Self::Styled; /// Sets the foreground color. fn with(self, color: Color) -> Self::Styled { let mut styled = self.stylize(); styled.as_mut().foreground_color = Some(color); styled } /// Sets the background color. fn on(self, color: Color) -> Self::Styled { let mut styled = self.stylize(); styled.as_mut().background_color = Some(color); styled } /// Styles the content with the attribute. fn attribute(self, attr: Attribute) -> Self::Styled { let mut styled = self.stylize(); styled.as_mut().attributes.set(attr); styled } stylize_method!(reset Attribute::Reset); stylize_method!(bold Attribute::Bold); stylize_method!(underlined Attribute::Underlined); stylize_method!(reverse Attribute::Reverse); stylize_method!(dim Attribute::Dim); stylize_method!(italic Attribute::Italic); stylize_method!(negative Attribute::Reverse); stylize_method!(slow_blink Attribute::SlowBlink); stylize_method!(rapid_blink Attribute::RapidBlink); stylize_method!(hidden Attribute::Hidden); stylize_method!(crossed_out Attribute::CrossedOut); stylize_method!(black, on_black Color::Black); stylize_method!(dark_grey, on_dark_grey Color::DarkGrey); stylize_method!(red, on_red Color::Red); stylize_method!(dark_red, on_dark_red Color::DarkRed); stylize_method!(green, on_green Color::Green); stylize_method!(dark_green, on_dark_green Color::DarkGreen); stylize_method!(yellow, on_yellow Color::Yellow); stylize_method!(dark_yellow, on_dark_yellow Color::DarkYellow); stylize_method!(blue, on_blue Color::Blue); stylize_method!(dark_blue, on_dark_blue Color::DarkBlue); stylize_method!(magenta, on_magenta Color::Magenta); stylize_method!(dark_magenta, on_dark_magenta Color::DarkMagenta); stylize_method!(cyan, on_cyan Color::Cyan); stylize_method!(dark_cyan, on_dark_cyan Color::DarkCyan); stylize_method!(white, on_white Color::White); stylize_method!(grey, on_grey Color::Grey); } macro_rules! impl_stylize_for_display { ($($t:ty),*) => { $( impl Stylize for $t { type Styled = StyledContent; #[inline] fn stylize(self) -> Self::Styled { style(self) } } )* } } impl_stylize_for_display!(String, char, &str); impl Stylize for ContentStyle { type Styled = Self; #[inline] fn stylize(self) -> Self::Styled { self } } impl Stylize for StyledContent { type Styled = StyledContent; fn stylize(self) -> Self::Styled { self } } // Workaround for https://github.com/rust-lang/rust/issues/78835 macro_rules! calculated_docs { ($(#[doc = $doc:expr] $item:item)*) => { $(#[doc = $doc] $item)* }; } // Remove once https://github.com/rust-lang/rust-clippy/issues/7106 stabilizes. #[allow(clippy::single_component_path_imports)] #[allow(clippy::useless_attribute)] use calculated_docs; #[cfg(test)] mod tests { use super::super::{Attribute, Color, ContentStyle, Stylize}; #[test] fn set_fg_bg_add_attr() { let style = ContentStyle::new() .with(Color::Blue) .on(Color::Red) .attribute(Attribute::Bold); assert_eq!(style.foreground_color, Some(Color::Blue)); assert_eq!(style.background_color, Some(Color::Red)); assert!(style.attributes.has(Attribute::Bold)); let mut styled_content = style.apply("test"); styled_content = styled_content .with(Color::Green) .on(Color::Magenta) .attribute(Attribute::NoItalic); let style = styled_content.style(); assert_eq!(style.foreground_color, Some(Color::Green)); assert_eq!(style.background_color, Some(Color::Magenta)); assert!(style.attributes.has(Attribute::Bold)); assert!(style.attributes.has(Attribute::NoItalic)); } } crossterm-0.22.1/src/style/sys/windows.rs000064400000000000000000000206460072674642500165740ustar 00000000000000use std::convert::TryFrom; use std::sync::atomic::{AtomicU32, Ordering}; use crossterm_winapi::{Console, Handle, HandleType, ScreenBuffer}; use winapi::um::wincon; use crate::Result; use super::super::{Color, Colored}; const FG_GREEN: u16 = wincon::FOREGROUND_GREEN; const FG_RED: u16 = wincon::FOREGROUND_RED; const FG_BLUE: u16 = wincon::FOREGROUND_BLUE; const FG_INTENSITY: u16 = wincon::FOREGROUND_INTENSITY; const BG_GREEN: u16 = wincon::BACKGROUND_GREEN; const BG_RED: u16 = wincon::BACKGROUND_RED; const BG_BLUE: u16 = wincon::BACKGROUND_BLUE; const BG_INTENSITY: u16 = wincon::BACKGROUND_INTENSITY; pub(crate) fn set_foreground_color(fg_color: Color) -> Result<()> { init_console_color()?; let color_value: u16 = Colored::ForegroundColor(fg_color).into(); let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; // Notice that the color values are stored in wAttribute. // So we need to use bitwise operators to check if the values exists or to get current console colors. let mut color: u16; let attrs = csbi.attributes(); let bg_color = attrs & 0x0070; color = color_value | bg_color; // background intensity is a separate value in attrs, // wee need to check if this was applied to the current bg color. if (attrs & wincon::BACKGROUND_INTENSITY as u16) != 0 { color |= wincon::BACKGROUND_INTENSITY as u16; } Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?; Ok(()) } pub(crate) fn set_background_color(bg_color: Color) -> Result<()> { init_console_color()?; let color_value: u16 = Colored::BackgroundColor(bg_color).into(); let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; // Notice that the color values are stored in wAttribute. // So wee need to use bitwise operators to check if the values exists or to get current console colors. let mut color: u16; let attrs = csbi.attributes(); let fg_color = attrs & 0x0007; color = fg_color | color_value; // Foreground intensity is a separate value in attrs, // So we need to check if this was applied to the current fg color. if (attrs & wincon::FOREGROUND_INTENSITY as u16) != 0 { color |= wincon::FOREGROUND_INTENSITY as u16; } Console::from(screen_buffer.handle().clone()).set_text_attribute(color)?; Ok(()) } pub(crate) fn reset() -> Result<()> { if let Ok(original_color) = u16::try_from(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed)) { Console::from(Handle::new(HandleType::CurrentOutputHandle)?) .set_text_attribute(original_color)?; } Ok(()) } /// Initializes the default console color. It will will be skipped if it has already been initialized. pub(crate) fn init_console_color() -> Result<()> { if ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed) == u32::MAX { let screen_buffer = ScreenBuffer::current()?; let attr = screen_buffer.info()?.attributes(); ORIGINAL_CONSOLE_COLOR.store(u32::from(attr), Ordering::Relaxed); } Ok(()) } /// Returns the original console color, make sure to call `init_console_color` before calling this function. Otherwise this function will panic. pub(crate) fn original_console_color() -> u16 { u16::try_from(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed)) // safe unwrap, initial console color was set with `init_console_color` in `WinApiColor::new()` .expect("Initial console color not set") } // This is either a valid u16 in which case it stores the original console color or it is u32::MAX // in which case it is uninitialized. static ORIGINAL_CONSOLE_COLOR: AtomicU32 = AtomicU32::new(u32::MAX); impl From for u16 { /// Returns the WinAPI color value (u16) from the `Colored` struct. fn from(colored: Colored) -> Self { match colored { Colored::ForegroundColor(color) => { match color { Color::Black => 0, Color::DarkGrey => FG_INTENSITY, Color::Red => FG_INTENSITY | FG_RED, Color::DarkRed => FG_RED, Color::Green => FG_INTENSITY | FG_GREEN, Color::DarkGreen => FG_GREEN, Color::Yellow => FG_INTENSITY | FG_GREEN | FG_RED, Color::DarkYellow => FG_GREEN | FG_RED, Color::Blue => FG_INTENSITY | FG_BLUE, Color::DarkBlue => FG_BLUE, Color::Magenta => FG_INTENSITY | FG_RED | FG_BLUE, Color::DarkMagenta => FG_RED | FG_BLUE, Color::Cyan => FG_INTENSITY | FG_GREEN | FG_BLUE, Color::DarkCyan => FG_GREEN | FG_BLUE, Color::White => FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE, Color::Grey => FG_RED | FG_GREEN | FG_BLUE, Color::Reset => { // safe unwrap, initial console color was set with `init_console_color`. let original_color = original_console_color(); const REMOVE_BG_MASK: u16 = BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE; // remove all background values from the original color, we don't want to reset those. original_color & !REMOVE_BG_MASK } /* WinAPI will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ Color::Rgb { .. } => 0, Color::AnsiValue(_val) => 0, } } Colored::BackgroundColor(color) => { match color { Color::Black => 0, Color::DarkGrey => BG_INTENSITY, Color::Red => BG_INTENSITY | BG_RED, Color::DarkRed => BG_RED, Color::Green => BG_INTENSITY | BG_GREEN, Color::DarkGreen => BG_GREEN, Color::Yellow => BG_INTENSITY | BG_GREEN | BG_RED, Color::DarkYellow => BG_GREEN | BG_RED, Color::Blue => BG_INTENSITY | BG_BLUE, Color::DarkBlue => BG_BLUE, Color::Magenta => BG_INTENSITY | BG_RED | BG_BLUE, Color::DarkMagenta => BG_RED | BG_BLUE, Color::Cyan => BG_INTENSITY | BG_GREEN | BG_BLUE, Color::DarkCyan => BG_GREEN | BG_BLUE, Color::White => BG_INTENSITY | BG_RED | BG_GREEN | BG_BLUE, Color::Grey => BG_RED | BG_GREEN | BG_BLUE, Color::Reset => { let original_color = original_console_color(); const REMOVE_FG_MASK: u16 = FG_INTENSITY | FG_RED | FG_GREEN | FG_BLUE; // remove all foreground values from the original color, we don't want to reset those. original_color & !REMOVE_FG_MASK } /* WinAPI will be used for systems that do not support ANSI, those are windows version less then 10. RGB and 255 (AnsiBValue) colors are not supported in that case.*/ Color::Rgb { .. } => 0, Color::AnsiValue(_val) => 0, } } } } } #[cfg(test)] mod tests { use std::sync::atomic::Ordering; use crate::style::sys::windows::set_foreground_color; use super::{ Color, Colored, BG_INTENSITY, BG_RED, FG_INTENSITY, FG_RED, ORIGINAL_CONSOLE_COLOR, }; #[test] fn test_parse_fg_color() { let colored = Colored::ForegroundColor(Color::Red); assert_eq!(Into::::into(colored), FG_INTENSITY | FG_RED); } #[test] fn test_parse_bg_color() { let colored = Colored::BackgroundColor(Color::Red); assert_eq!(Into::::into(colored), BG_INTENSITY | BG_RED); } #[test] fn test_original_console_color_is_set() { assert_eq!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX); // will call `init_console_color` set_foreground_color(Color::Blue).unwrap(); assert_ne!(ORIGINAL_CONSOLE_COLOR.load(Ordering::Relaxed), u32::MAX); } } crossterm-0.22.1/src/style/sys.rs000064400000000000000000000000520072674642500150670ustar 00000000000000#[cfg(windows)] pub(crate) mod windows; crossterm-0.22.1/src/style/types/attribute.rs000064400000000000000000000134410072674642500174260ustar 00000000000000use 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::Stylize; /// /// 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)] #[non_exhaustive] 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, } 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 pub fn sgr(self) -> i16 { SGR[self as usize] } } crossterm-0.22.1/src/style/types/color.rs000064400000000000000000000362640072674642500165510ustar 00000000000000use std::{convert::AsRef, convert::TryFrom, result::Result, str::FromStr}; #[cfg(feature = "serde")] use std::fmt; use crate::style::parse_next_u8; /// 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 | /// | :--| :-- | /// | `DarkGrey` | `Black` | /// | `Red` | `DarkRed` | /// | `Green` | `DarkGreen` | /// | `Yellow` | `DarkYellow` | /// | `Blue` | `DarkBlue` | /// | `Magenta` | `DarkMagenta` | /// | `Cyan` | `DarkCyan` | /// | `White` | `Grey` | /// /// Most UNIX terminals and Windows 10 consoles support additional colors. /// See [`Color::Rgb`] or [`Color::AnsiValue`] for more info. #[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 Color { /// Parses an ANSI color sequence. /// /// # Examples /// /// ``` /// use crossterm::style::Color; /// /// assert_eq!(Color::parse_ansi("5;0"), Some(Color::Black)); /// assert_eq!(Color::parse_ansi("5;26"), Some(Color::AnsiValue(26))); /// assert_eq!(Color::parse_ansi("2;50;60;70"), Some(Color::Rgb { r: 50, g: 60, b: 70 })); /// assert_eq!(Color::parse_ansi("invalid color"), None); /// ``` /// /// Currently, 3/4 bit color values aren't supported so return `None`. /// /// See also: [`Colored::parse_ansi`](crate::style::Colored::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. pub(crate) fn parse_ansi_iter<'a>(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) } } 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(feature = "serde")] impl serde::ser::Serialize for Color { fn serialize(&self, serializer: S) -> Result where S: serde::ser::Serializer, { let str = match *self { Color::Black => "black", Color::DarkGrey => "dark_grey", Color::Red => "red", Color::DarkRed => "dark_red", Color::Green => "green", Color::DarkGreen => "dark_green", Color::Yellow => "yellow", Color::DarkYellow => "dark_yellow", Color::Blue => "blue", Color::DarkBlue => "dark_blue", Color::Magenta => "magenta", Color::DarkMagenta => "dark_magenta", Color::Cyan => "cyan", Color::DarkCyan => "dark_cyan", Color::White => "white", Color::Grey => "grey", _ => "", }; if str == "" { println!("color: {:?}", self); match *self { Color::AnsiValue(value) => { return serializer.serialize_str(&format!("ansi_({})", value)); } Color::Rgb { r, g, b } => { return serializer.serialize_str(&format!("rgb_({},{},{})", r, g, b)); } _ => { return Err(serde::ser::Error::custom("Could not serialize enum type")); } } } else { return serializer.serialize_str(str); } } } #[cfg(feature = "serde")] impl<'de> serde::de::Deserialize<'de> for Color { fn deserialize(deserializer: D) -> Result where D: serde::de::Deserializer<'de>, { struct ColorVisitor; impl<'de> serde::de::Visitor<'de> for ColorVisitor { type Value = Color; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str( "`black`, `blue`, `dark_blue`, `cyan`, `dark_cyan`, `green`, `dark_green`, `grey`, `dark_grey`, `magenta`, `dark_magenta`, `red`, `dark_red`, `white`, `yellow`, `dark_yellow`, `ansi_(value)`, or `rgb_(r,g,b)`", ) } fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { if let Ok(c) = Color::try_from(value) { Ok(c) } else { if value.contains("ansi") { // strip away `ansi_(..)' and get the inner value between parenthesis. let results = value.replace("ansi_(", "").replace(")", ""); let ansi_val = results.parse::(); if let Ok(ansi) = ansi_val { return Ok(Color::AnsiValue(ansi)); } } else if value.contains("rgb") { // strip away `rgb_(..)' and get the inner values between parenthesis. let results = value .replace("rgb_(", "") .replace(")", "") .split(',') .map(|x| x.to_string()) .collect::>(); if results.len() == 3 { let r = results[0].parse::(); let g = results[1].parse::(); let b = results[2].parse::(); if r.is_ok() && g.is_ok() && b.is_ok() { return Ok(Color::Rgb { r: r.unwrap(), g: g.unwrap(), b: b.unwrap(), }); } } } Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) } } } deserializer.deserialize_str(ColorVisitor) } } #[cfg(test)] mod tests { use super::Color; #[test] fn test_known_color_conversion() { assert_eq!("grey".parse(), Ok(Color::Grey)); 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!("black".parse(), Ok(Color::Black)); } #[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 } ); } } #[cfg(test)] #[cfg(feature = "serde")] mod serde_tests { use super::Color; use serde_json; #[test] fn test_deserial_known_color_conversion() { assert_eq!( serde_json::from_str::("\"Red\"").unwrap(), Color::Red ); assert_eq!( serde_json::from_str::("\"red\"").unwrap(), Color::Red ); assert_eq!( serde_json::from_str::("\"dark_red\"").unwrap(), Color::DarkRed ); assert_eq!( serde_json::from_str::("\"grey\"").unwrap(), Color::Grey ); assert_eq!( serde_json::from_str::("\"dark_grey\"").unwrap(), Color::DarkGrey ); assert_eq!( serde_json::from_str::("\"green\"").unwrap(), Color::Green ); assert_eq!( serde_json::from_str::("\"dark_green\"").unwrap(), Color::DarkGreen ); assert_eq!( serde_json::from_str::("\"yellow\"").unwrap(), Color::Yellow ); assert_eq!( serde_json::from_str::("\"dark_yellow\"").unwrap(), Color::DarkYellow ); assert_eq!( serde_json::from_str::("\"blue\"").unwrap(), Color::Blue ); assert_eq!( serde_json::from_str::("\"dark_blue\"").unwrap(), Color::DarkBlue ); assert_eq!( serde_json::from_str::("\"magenta\"").unwrap(), Color::Magenta ); assert_eq!( serde_json::from_str::("\"dark_magenta\"").unwrap(), Color::DarkMagenta ); assert_eq!( serde_json::from_str::("\"cyan\"").unwrap(), Color::Cyan ); assert_eq!( serde_json::from_str::("\"dark_cyan\"").unwrap(), Color::DarkCyan ); assert_eq!( serde_json::from_str::("\"white\"").unwrap(), Color::White ); assert_eq!( serde_json::from_str::("\"black\"").unwrap(), Color::Black ); } #[test] fn test_deserial_unknown_color_conversion() { assert!(serde_json::from_str::("\"unknown\"").is_err()); } #[test] fn test_deserial_ansi_value() { assert_eq!( serde_json::from_str::("\"ansi_(255)\"").unwrap(), Color::AnsiValue(255) ); } #[test] fn test_deserial_unvalid_ansi_value() { assert!(serde_json::from_str::("\"ansi_(256)\"").is_err()); assert!(serde_json::from_str::("\"ansi_(-1)\"").is_err()); } #[test] fn test_deserial_rgb() { assert_eq!( serde_json::from_str::("\"rgb_(255,255,255)\"").unwrap(), Color::from((255, 255, 255)) ); } #[test] fn test_deserial_unvalid_rgb() { assert!(serde_json::from_str::("\"rgb_(255,255,255,255)\"").is_err()); assert!(serde_json::from_str::("\"rgb_(256,255,255)\"").is_err()); } } crossterm-0.22.1/src/style/types/colored.rs000064400000000000000000000174070072674642500170600ustar 00000000000000use std::fmt::{self, Formatter}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::style::{parse_next_u8, Color}; /// 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), } 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. /// /// # Examples /// /// ``` /// use crossterm::style::{Colored::{self, ForegroundColor, BackgroundColor}, Color}; /// /// assert_eq!(Colored::parse_ansi("38;5;0"), Some(ForegroundColor(Color::Black))); /// assert_eq!(Colored::parse_ansi("38;5;26"), Some(ForegroundColor(Color::AnsiValue(26)))); /// assert_eq!(Colored::parse_ansi("48;2;50;60;70"), Some(BackgroundColor(Color::Rgb { r: 50, g: 60, b: 70 }))); /// assert_eq!(Colored::parse_ansi("49"), Some(BackgroundColor(Color::Reset))); /// assert_eq!(Colored::parse_ansi("invalid color"), None); /// ``` /// /// Currently, 3/4 bit color values aren't supported so return `None`. /// /// See also: [`Color::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 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(()), } } } #[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"); } } crossterm-0.22.1/src/style/types/colors.rs000064400000000000000000000140710072674642500167240ustar 00000000000000use 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, } ); } } crossterm-0.22.1/src/style/types.rs000064400000000000000000000002200072674642500154120ustar 00000000000000pub use self::{attribute::Attribute, color::Color, colored::Colored, colors::Colors}; mod attribute; mod color; mod colored; mod colors; crossterm-0.22.1/src/style.rs000064400000000000000000000302220072674642500142530ustar 00000000000000//! # 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 [`Stylize`](crate::style::Stylize) on a `String` or `&'static str` to color //! it. //! //! ```no_run //! use crossterm::style::Stylize; //! //! 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 [`Stylize`](crate::style::Stylize) functions on a `String` or `&'static str` to set //! attributes to it. //! //! ```no_run //! use crossterm::style::Stylize; //! //! 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::{self, Display}, }; use crate::command::execute_fmt; #[cfg(windows)] use crate::Result; use crate::{csi, impl_display, Command}; pub use self::{ attributes::Attributes, content_style::ContentStyle, styled_content::StyledContent, stylize::Stylize, types::{Attribute, Color, Colored, Colors}, }; mod attributes; mod content_style; mod styled_content; mod stylize; mod sys; 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, Stylize, Color}; /// /// let styled_content = style("Blue colored text on yellow background") /// .with(Color::Blue) /// .on(Color::Yellow); /// /// println!("{}", styled_content); /// ``` pub fn style(val: D) -> StyledContent { ContentStyle::new().apply(val) } /// 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 Command for SetForegroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}m"), Colored::ForegroundColor(self.0)) } #[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 Command for SetBackgroundColor { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}m"), Colored::BackgroundColor(self.0)) } #[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 Command for SetColors { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if let Some(color) = self.0.foreground { SetForegroundColor(color).write_ansi(f)?; } if let Some(color) = self.0.background { SetBackgroundColor(color).write_ansi(f)?; } Ok(()) } #[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 Command for SetAttribute { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("{}m"), self.0.sgr()) } #[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 Command for SetAttributes { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { for attr in Attribute::iterator() { if self.0.has(attr) { SetAttribute(attr).write_ansi(f)?; } } Ok(()) } #[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, Copy, Clone)] pub struct PrintStyledContent(pub StyledContent); impl Command for PrintStyledContent { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { let style = self.0.style(); let mut reset_background = false; let mut reset_foreground = false; let mut reset = false; if let Some(bg) = style.background_color { execute_fmt(f, SetBackgroundColor(bg)).map_err(|_| fmt::Error)?; reset_background = true; } if let Some(fg) = style.foreground_color { execute_fmt(f, SetForegroundColor(fg)).map_err(|_| fmt::Error)?; reset_foreground = true; } if !style.attributes.is_empty() { execute_fmt(f, SetAttributes(style.attributes)).map_err(|_| fmt::Error)?; reset = true; } write!(f, "{}", self.0.content())?; if reset { // NOTE: This will reset colors even though self has no colors, hence produce unexpected // resets. // TODO: reset the set attributes only. execute_fmt(f, ResetColor).map_err(|_| fmt::Error)?; } else { // NOTE: Since the above bug, we do not need to reset colors when we reset attributes. if reset_background { execute_fmt(f, SetBackgroundColor(Color::Reset)).map_err(|_| fmt::Error)?; } if reset_foreground { execute_fmt(f, SetForegroundColor(Color::Reset)).map_err(|_| fmt::Error)?; } } Ok(()) } #[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 { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("0m")) } #[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 { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, "{}", self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { panic!("tried to execute Print command using WinAPI, use ANSI instead"); } #[cfg(windows)] fn is_ansi_code_supported(&self) -> bool { true } } impl Display for Print { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } 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); /// 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| s.parse().ok()) } crossterm-0.22.1/src/terminal/sys/unix.rs000064400000000000000000000101320072674642500165250ustar 00000000000000//! UNIX related logic for terminal manipulation. use std::fs::File; use std::os::unix::io::{IntoRawFd, RawFd}; use std::{io, mem, process}; use libc::{ cfmakeraw, ioctl, tcgetattr, tcsetattr, termios as Termios, winsize, STDOUT_FILENO, TCSANOW, TIOCGWINSZ, }; use parking_lot::Mutex; use crate::error::Result; use crate::event::sys::unix::file_descriptor::{tty_fd, FileDesc}; // Some(Termios) -> we're in the raw mode and this is the previous mode // None -> we're not in the raw mode static TERMINAL_MODE_PRIOR_RAW_MODE: Mutex> = parking_lot::const_mutex(None); pub(crate) fn is_raw_mode_enabled() -> bool { TERMINAL_MODE_PRIOR_RAW_MODE.lock().is_some() } #[allow(clippy::useless_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 wrap_with_result(unsafe { ioctl(fd, TIOCGWINSZ.into(), &mut size) }).is_ok() { 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(); if original_mode.is_some() { return Ok(()); } let tty = tty_fd()?; let fd = tty.raw_fd(); let mut ios = get_terminal_attr(fd)?; let original_mode_ios = ios; raw_terminal_attr(&mut ios); set_terminal_attr(fd, &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(()) } /// Reset the raw mode. /// /// More precisely, reset the whole termios mode to what it was before the first call /// to [enable_raw_mode]. If you don't mess with termios outside of crossterm, it's /// effectively disabling the raw mode and doing nothing else. pub(crate) fn disable_raw_mode() -> Result<()> { let mut original_mode = TERMINAL_MODE_PRIOR_RAW_MODE.lock(); if let Some(original_mode_ios) = original_mode.as_ref() { let tty = tty_fd()?; set_terminal_attr(tty.raw_fd(), 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 { let output = process::Command::new("tput").arg(arg).output().ok()?; let value = output .stdout .into_iter() .filter_map(|b| char::from(b).to_digit(10)) .fold(0, |v, n| v * 10 + n as u16); if value > 0 { Some(value) } else { 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(fd: RawFd) -> Result { unsafe { let mut termios = mem::zeroed(); wrap_with_result(tcgetattr(fd, &mut termios))?; Ok(termios) } } fn set_terminal_attr(fd: RawFd, termios: &Termios) -> Result<()> { wrap_with_result(unsafe { tcsetattr(fd, TCSANOW, termios) }) } fn wrap_with_result(result: i32) -> Result<()> { if result == -1 { Err(io::Error::last_os_error()) } else { Ok(()) } } crossterm-0.22.1/src/terminal/sys/windows.rs000064400000000000000000000303470072674642500172460ustar 00000000000000//! WinAPI related logic for terminal manipulation. use std::fmt::{self, Write}; use std::io; use crossterm_winapi::{Console, ConsoleMode, Coord, Handle, ScreenBuffer, Size}; use winapi::{ shared::minwindef::DWORD, um::wincon::{SetConsoleTitleW, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT}, }; use crate::{cursor, terminal::ClearType, ErrorKind, Result}; /// bits which can't be set in raw mode const NOT_RAW_MODE_MASK: DWORD = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT; pub(crate) fn is_raw_mode_enabled() -> Result { let console_mode = ConsoleMode::from(Handle::current_in_handle()?); let dw_mode = console_mode.mode()?; Ok( // check none of the "not raw" bits is set dw_mode & NOT_RAW_MODE_MASK == 0, ) } pub(crate) fn enable_raw_mode() -> Result<()> { let console_mode = ConsoleMode::from(Handle::current_in_handle()?); let dw_mode = console_mode.mode()?; let new_mode = dw_mode & !NOT_RAW_MODE_MASK; console_mode.set_mode(new_mode)?; Ok(()) } pub(crate) fn disable_raw_mode() -> Result<()> { let console_mode = ConsoleMode::from(Handle::current_in_handle()?); let dw_mode = console_mode.mode()?; let new_mode = dw_mode | NOT_RAW_MODE_MASK; console_mode.set_mode(new_mode)?; Ok(()) } pub(crate) fn size() -> Result<(u16, u16)> { let terminal_size = ScreenBuffer::current()?.info()?.terminal_size(); // windows starts counting at 0, unix at 1, add one to replicated unix behaviour. Ok(( (terminal_size.width + 1) as u16, (terminal_size.height + 1) as u16, )) } pub(crate) fn clear(clear_type: ClearType) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; let pos = csbi.cursor_pos(); let buffer_size = csbi.buffer_size(); let current_attribute = csbi.attributes(); match clear_type { ClearType::All => { clear_entire_screen(buffer_size, current_attribute)?; } ClearType::FromCursorDown => clear_after_cursor(pos, buffer_size, current_attribute)?, ClearType::FromCursorUp => clear_before_cursor(pos, buffer_size, current_attribute)?, ClearType::CurrentLine => clear_current_line(pos, buffer_size, current_attribute)?, ClearType::UntilNewLine => clear_until_line(pos, buffer_size, current_attribute)?, _ => { clear_entire_screen(buffer_size, current_attribute)?; } //TODO: make purge flush the entire screen buffer not just the visible window. }; Ok(()) } pub(crate) fn scroll_up(row_count: u16) -> Result<()> { let csbi = ScreenBuffer::current()?; let mut window = csbi.info()?.terminal_window(); // check whether the window is too close to the screen buffer top let count = row_count as i16; if window.top >= count { window.top -= count; // move top down window.bottom -= count; // move bottom down Console::output()?.set_console_info(true, window)?; } Ok(()) } pub(crate) fn scroll_down(row_count: u16) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let csbi = screen_buffer.info()?; let mut window = csbi.terminal_window(); let buffer_size = csbi.buffer_size(); // check whether the window is too close to the screen buffer top let count = row_count as i16; if window.bottom < buffer_size.height - count { window.top += count; // move top down window.bottom += count; // move bottom down Console::output()?.set_console_info(true, window)?; } Ok(()) } pub(crate) fn set_size(width: u16, height: u16) -> Result<()> { if width <= 1 { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, "terminal width must be at least 1", )); } if height <= 1 { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, "terminal height must be at least 1", )); } // get the position of the current console window let screen_buffer = ScreenBuffer::current()?; let console = Console::from(screen_buffer.handle().clone()); let csbi = screen_buffer.info()?; let current_size = csbi.buffer_size(); let window = csbi.terminal_window(); let mut new_size = Size::new(current_size.width, current_size.height); // If the buffer is smaller than this new window size, resize the // buffer to be large enough. Include window position. let mut resize_buffer = false; let width = width as i16; if current_size.width < window.left + width { if window.left >= i16::max_value() - width { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, "terminal width too large", )); } new_size.width = window.left + width; resize_buffer = true; } let height = height as i16; if current_size.height < window.top + height { if window.top >= i16::max_value() - height { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, "terminal height too large", )); } new_size.height = window.top + height; resize_buffer = true; } if resize_buffer { screen_buffer.set_size(new_size.width - 1, new_size.height - 1)?; } let mut window = window; // preserve the position, but change the size. window.bottom = window.top + height - 1; window.right = window.left + width - 1; console.set_console_info(true, window)?; // if we resized the buffer, un-resize it. if resize_buffer { screen_buffer.set_size(current_size.width - 1, current_size.height - 1)?; } let bounds = console.largest_window_size()?; if width > bounds.x { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, format!("terminal width {} too large", width), )); } if height > bounds.y { return Err(ErrorKind::new( io::ErrorKind::InvalidInput, format!("terminal height {} too large", height), )); } Ok(()) } pub(crate) fn set_window_title(title: impl fmt::Display) -> Result<()> { struct Utf16Encoder(Vec); impl Write for Utf16Encoder { fn write_str(&mut self, s: &str) -> fmt::Result { self.0.extend(s.encode_utf16()); Ok(()) } } let mut title_utf16 = Utf16Encoder(Vec::new()); write!(title_utf16, "{}", title).expect("formatting failed"); title_utf16.0.push(0); let title = title_utf16.0; let result = unsafe { SetConsoleTitleW(title.as_ptr()) }; if result != 0 { Ok(()) } else { Err(ErrorKind::last_os_error()) } } fn clear_after_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { let (mut x, mut y) = (location.x, location.y); // if cursor position is at the outer right position if x as i16 > buffer_size.width { y += 1; x = 0; } // location where to start clearing let start_location = Coord::new(x, y); // get sum cells before cursor let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; clear_winapi(start_location, cells_to_write, current_attribute) } fn clear_before_cursor(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { let (xpos, ypos) = (location.x, location.y); // one cell after cursor position let x = 0; // one at row of cursor position let y = 0; // location where to start clearing let start_location = Coord::new(x, y); // get sum cells before cursor let cells_to_write = (buffer_size.width as u32 * ypos as u32) + (xpos as u32 + 1); // clear everything before cursor position clear_winapi(start_location, cells_to_write, current_attribute) } fn clear_entire_screen(buffer_size: Size, current_attribute: u16) -> Result<()> { // get sum cells before cursor let cells_to_write = buffer_size.width as u32 * buffer_size.height as u32; // location where to start clearing let start_location = Coord::new(0, 0); // clear the entire screen clear_winapi(start_location, cells_to_write, current_attribute)?; // put the cursor back at cell 0,0 cursor::sys::move_to(0, 0)?; Ok(()) } fn clear_current_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { // location where to start clearing let start_location = Coord::new(0, location.y); // get sum cells before cursor let cells_to_write = buffer_size.width as u32; // clear the whole current line clear_winapi(start_location, cells_to_write, current_attribute)?; // put the cursor back at cell 1 on current row cursor::sys::move_to(0, location.y as u16)?; Ok(()) } fn clear_until_line(location: Coord, buffer_size: Size, current_attribute: u16) -> Result<()> { let (x, y) = (location.x, location.y); // location where to start clearing let start_location = Coord::new(x, y); // get sum cells before cursor let cells_to_write = (buffer_size.width - x as i16) as u32; // clear until the current line clear_winapi(start_location, cells_to_write, current_attribute)?; // put the cursor back at original cursor position before we did the clearing cursor::sys::move_to(x as u16, y as u16)?; Ok(()) } fn clear_winapi(start_location: Coord, cells_to_write: u32, current_attribute: u16) -> Result<()> { let console = Console::from(Handle::current_out_handle()?); console.fill_whit_character(start_location, cells_to_write, ' ')?; console.fill_whit_attribute(start_location, cells_to_write, current_attribute)?; Ok(()) } #[cfg(test)] mod tests { use std::{ffi::OsString, os::windows::ffi::OsStringExt}; use crossterm_winapi::ScreenBuffer; use winapi::um::wincon::GetConsoleTitleW; use super::{scroll_down, scroll_up, set_size, set_window_title, size}; #[test] fn test_resize_winapi() { let (width, height) = size().unwrap(); set_size(30, 30).unwrap(); assert_eq!((30, 30), size().unwrap()); // reset to previous size set_size(width, height).unwrap(); assert_eq!((width, height), size().unwrap()); } // Test is disabled, because it's failing on Travis CI #[test] #[ignore] fn test_scroll_down_winapi() { let current_window = ScreenBuffer::current() .unwrap() .info() .unwrap() .terminal_window(); scroll_down(2).unwrap(); let new_window = ScreenBuffer::current() .unwrap() .info() .unwrap() .terminal_window(); assert_eq!(new_window.top, current_window.top + 2); assert_eq!(new_window.bottom, current_window.bottom + 2); } // Test is disabled, because it's failing on Travis CI #[test] #[ignore] fn test_scroll_up_winapi() { // move the terminal buffer down before moving it up test_scroll_down_winapi(); let current_window = ScreenBuffer::current() .unwrap() .info() .unwrap() .terminal_window(); scroll_up(2).unwrap(); let new_window = ScreenBuffer::current() .unwrap() .info() .unwrap() .terminal_window(); assert_eq!(new_window.top, current_window.top - 2); assert_eq!(new_window.bottom, current_window.bottom - 2); } #[test] fn test_set_title_winapi() { let test_title = "this is a crossterm test title"; set_window_title(test_title).unwrap(); let mut raw = [0_u16; 128]; let length = unsafe { GetConsoleTitleW(raw.as_mut_ptr(), raw.len() as u32) } as usize; assert_ne!(0, length); let console_title = OsString::from_wide(&raw[..length]).into_string().unwrap(); assert_eq!(test_title, &console_title[..]); } } crossterm-0.22.1/src/terminal/sys.rs000064400000000000000000000006270072674642500155520ustar 00000000000000//! This module provides platform related functions. #[cfg(unix)] pub(crate) use self::unix::{disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, size}; #[cfg(windows)] pub(crate) use self::windows::{ clear, disable_raw_mode, enable_raw_mode, is_raw_mode_enabled, scroll_down, scroll_up, set_size, set_window_title, size, }; #[cfg(windows)] mod windows; #[cfg(unix)] mod unix; crossterm-0.22.1/src/terminal.rs000064400000000000000000000325100072674642500147300ustar 00000000000000//! # Terminal //! //! The `terminal` module provides functionality to work with the terminal. //! //! 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. //! //! Most terminal actions can be performed with commands. //! Please have a look at [command documentation](../index.html#command-api) for a more detailed documentation. //! //! ## Screen Buffer //! //! A screen buffer is a two-dimensional array of character //! and color data which is displayed in a terminal screen. //! //! The terminal has several of those buffers and is able to switch between them. //! The default screen in which you work is called the 'main screen'. //! The other screens are called the 'alternative screen'. //! //! It is important to understand that crossterm does not yet support creating screens, //! or switch between more than two buffers, and only offers the ability to change //! between the 'alternate' and 'main screen'. //! //! ### Alternate Screen //! //! By default, you will be working on the main screen. //! There is also another screen called the 'alternative' screen. //! This screen is slightly different from the main screen. //! For example, it has the exact dimensions of the terminal window, //! without any scroll-back area. //! //! Crossterm offers the possibility to switch to the 'alternative' screen, //! make some modifications, and move back to the 'main' screen again. //! The main screen will stay intact and will have the original data as we performed all //! operations on the alternative screen. //! //! An good example of this is Vim. //! When it is launched from bash, a whole new buffer is used to modify a file. //! Then, when the modification is finished, it closes again and continues on the main screen. //! //! ### Raw Mode //! //! By default, the terminal functions in a certain way. //! For example, it will move the cursor to the beginning of the next line when the input hits the end of a line. //! Or that the backspace is interpreted for character removal. //! //! Sometimes these default modes are irrelevant, //! and in this case, we can turn them off. //! This is what happens when you enable raw modes. //! //! Those modes will be set when enabling raw modes: //! //! - Input will not be forwarded to screen //! - Input will not be processed on enter press //! - Input will not be line buffered (input sent byte-by-byte to input buffer) //! - Special keys like backspace and CTL+C will not be processed by terminal driver //! - New line character will not be processed therefore `println!` can't be used, use `write!` instead //! //! Raw mode can be enabled/disabled with the [enable_raw_mode](terminal::enable_raw_mode) and [disable_raw_mode](terminal::disable_raw_mode) functions. //! //! ## Examples //! //! ```no_run //! use std::io::{stdout, Write}; //! use crossterm::{execute, Result, terminal::{ScrollUp, SetSize, size}}; //! //! fn main() -> Result<()> { //! let (cols, rows) = size()?; //! // Resize terminal and scroll up. //! execute!( //! stdout(), //! SetSize(10, 10), //! ScrollUp(5) //! )?; //! //! // Be a good citizen, cleanup //! execute!(stdout(), SetSize(cols, rows))?; //! Ok(()) //! } //! ``` //! //! For manual execution control check out [crossterm::queue](../macro.queue.html). use std::fmt; #[cfg(windows)] use crossterm_winapi::{ConsoleMode, Handle, ScreenBuffer}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(windows)] use winapi::um::wincon::ENABLE_WRAP_AT_EOL_OUTPUT; #[doc(no_inline)] use crate::Command; use crate::{csi, impl_display, Result}; pub(crate) mod sys; /// Tells whether the raw mode is enabled. /// /// Please have a look at the [raw mode](./#raw-mode) section. pub fn is_raw_mode_enabled() -> Result { #[cfg(unix)] { Ok(sys::is_raw_mode_enabled()) } #[cfg(windows)] { sys::is_raw_mode_enabled() } } /// Enables raw mode. /// /// Please have a look at the [raw mode](./#raw-mode) section. pub fn enable_raw_mode() -> Result<()> { sys::enable_raw_mode() } /// Disables raw mode. /// /// Please have a look at the [raw mode](./#raw-mode) section. pub fn disable_raw_mode() -> Result<()> { sys::disable_raw_mode() } /// Returns the terminal size `(columns, rows)`. /// /// The top left cell is represented `(1, 1)`. pub fn size() -> Result<(u16, u16)> { sys::size() } /// Disables line wrapping. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct DisableLineWrap; impl Command for DisableLineWrap { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?7l")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let console_mode = ConsoleMode::from(screen_buffer.handle().clone()); let new_mode = console_mode.mode()? & !ENABLE_WRAP_AT_EOL_OUTPUT; console_mode.set_mode(new_mode)?; Ok(()) } } /// Enable line wrapping. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnableLineWrap; impl Command for EnableLineWrap { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?7h")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { let screen_buffer = ScreenBuffer::current()?; let console_mode = ConsoleMode::from(screen_buffer.handle().clone()); let new_mode = console_mode.mode()? | ENABLE_WRAP_AT_EOL_OUTPUT; console_mode.set_mode(new_mode)?; Ok(()) } } /// A command that switches to alternate screen. /// /// # Notes /// /// * Commands must be executed/queued for execution otherwise they do nothing. /// * Use [LeaveAlternateScreen](./struct.LeaveAlternateScreen.html) command to leave the entered alternate screen. /// /// # Examples /// /// ```no_run /// use std::io::{stdout, Write}; /// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}}; /// /// fn main() -> Result<()> { /// execute!(stdout(), EnterAlternateScreen)?; /// /// // Do anything on the alternate screen /// /// execute!(stdout(), LeaveAlternateScreen) /// } /// ``` /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EnterAlternateScreen; impl Command for EnterAlternateScreen { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?1049h")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { let alternate_screen = ScreenBuffer::create()?; alternate_screen.show()?; Ok(()) } } /// A command that switches back to the main screen. /// /// # Notes /// /// * Commands must be executed/queued for execution otherwise they do nothing. /// * Use [EnterAlternateScreen](./struct.EnterAlternateScreen.html) to enter the alternate screen. /// /// # Examples /// /// ```no_run /// use std::io::{stdout, Write}; /// use crossterm::{execute, Result, terminal::{EnterAlternateScreen, LeaveAlternateScreen}}; /// /// fn main() -> Result<()> { /// execute!(stdout(), EnterAlternateScreen)?; /// /// // Do anything on the alternate screen /// /// execute!(stdout(), LeaveAlternateScreen) /// } /// ``` /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LeaveAlternateScreen; impl Command for LeaveAlternateScreen { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(csi!("?1049l")) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { let screen_buffer = ScreenBuffer::from(Handle::current_out_handle()?); screen_buffer.show()?; Ok(()) } } /// Different ways to clear the terminal buffer. #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] pub enum ClearType { /// All cells. All, /// All plus history Purge, /// All cells from the cursor position downwards. FromCursorDown, /// All cells from the cursor position upwards. FromCursorUp, /// All cells at the cursor row. CurrentLine, /// All cells from the cursor position until the new line. UntilNewLine, } /// A command that scrolls the terminal screen a given number of rows up. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ScrollUp(pub u16); impl Command for ScrollUp { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}S"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::scroll_up(self.0) } } /// A command that scrolls the terminal screen a given number of rows down. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ScrollDown(pub u16); impl Command for ScrollDown { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { if self.0 != 0 { write!(f, csi!("{}T"), self.0)?; } Ok(()) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::scroll_down(self.0) } } /// A command that clears the terminal screen buffer. /// /// See the [`ClearType`](enum.ClearType.html) enum. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Clear(pub ClearType); impl Command for Clear { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { f.write_str(match self.0 { ClearType::All => csi!("2J"), ClearType::Purge => csi!("3J"), ClearType::FromCursorDown => csi!("J"), ClearType::FromCursorUp => csi!("1J"), ClearType::CurrentLine => csi!("2K"), ClearType::UntilNewLine => csi!("K"), }) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::clear(self.0) } } /// A command that sets the terminal size `(columns, rows)`. /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetSize(pub u16, pub u16); impl Command for SetSize { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, csi!("8;{};{}t"), self.1, self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::set_size(self.0, self.1) } } /// A command that sets the terminal title /// /// # Notes /// /// Commands must be executed/queued for execution otherwise they do nothing. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SetTitle(pub T); impl Command for SetTitle { fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result { write!(f, "\x1B]0;{}\x07", &self.0) } #[cfg(windows)] fn execute_winapi(&self) -> Result<()> { sys::set_window_title(&self.0) } } impl_display!(for ScrollUp); impl_display!(for ScrollDown); impl_display!(for SetSize); impl_display!(for Clear); #[cfg(test)] mod tests { use std::{io::stdout, thread, time}; use crate::execute; use super::*; // Test is disabled, because it's failing on Travis CI #[test] #[ignore] fn test_resize_ansi() { let (width, height) = size().unwrap(); execute!(stdout(), SetSize(35, 35)).unwrap(); // see issue: https://github.com/eminence/terminal-size/issues/11 thread::sleep(time::Duration::from_millis(30)); assert_eq!((35, 35), size().unwrap()); // reset to previous size execute!(stdout(), SetSize(width, height)).unwrap(); // see issue: https://github.com/eminence/terminal-size/issues/11 thread::sleep(time::Duration::from_millis(30)); assert_eq!((width, height), size().unwrap()); } #[test] fn test_raw_mode() { // check we start from normal mode (may fail on some test harnesses) assert_eq!(is_raw_mode_enabled().unwrap(), false); // enable the raw mode if enable_raw_mode().is_err() { // Enabling raw mode doesn't work on the ci // So we just ignore it return; } // check it worked (on unix it doesn't really check the underlying // tty but rather check that the code is consistent) assert_eq!(is_raw_mode_enabled().unwrap(), true); // enable it again, this should not change anything enable_raw_mode().unwrap(); // check we're still in raw mode assert_eq!(is_raw_mode_enabled().unwrap(), true); // now let's disable it disable_raw_mode().unwrap(); // check we're back to normal mode assert_eq!(is_raw_mode_enabled().unwrap(), false); } } crossterm-0.22.1/src/tty.rs000064400000000000000000000024560072674642500137430ustar 00000000000000//! Making it a little more convenient and safe to query whether //! something is a terminal teletype or not. //! This module defines the IsTty trait and the is_tty method to //! return true if the item represents a terminal. #[cfg(unix)] use std::os::unix::io::AsRawFd; #[cfg(windows)] use std::os::windows::io::AsRawHandle; #[cfg(windows)] use winapi::um::consoleapi::GetConsoleMode; /// Adds the `is_tty` method to types that might represent a terminal /// /// ```rust /// use std::io::stdout; /// use crossterm::tty::IsTty; /// /// let is_tty: bool = stdout().is_tty(); /// ``` pub trait IsTty { /// Returns true when an instance is a terminal teletype, otherwise false. fn is_tty(&self) -> bool; } /// On unix, the `isatty()` function returns true if a file /// descriptor is a terminal. #[cfg(unix)] impl IsTty for S { fn is_tty(&self) -> bool { let fd = self.as_raw_fd(); unsafe { libc::isatty(fd) == 1 } } } /// On windows, `GetConsoleMode` will return true if we are in a terminal. /// Otherwise false. #[cfg(windows)] impl IsTty for S { fn is_tty(&self) -> bool { let mut mode = 0; let ok = unsafe { GetConsoleMode(self.as_raw_handle() as *mut _, &mut mode) }; ok == 1 } }