├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── crossterm_test.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
├── docs
├── .gitignore
├── CONTRIBUTING.md
├── crossterm_c.png
├── crossterm_full.png
├── crossterm_full.svg
└── know-problems.md
├── examples
├── README.md
├── copy-to-clipboard.rs
├── event-match-modifiers.rs
├── event-poll-read.rs
├── event-read-char-line.rs
├── event-read.rs
├── event-stream-async-std.rs
├── event-stream-tokio.rs
├── interactive-demo
│ ├── Cargo.toml
│ └── src
│ │ ├── macros.rs
│ │ ├── main.rs
│ │ ├── test.rs
│ │ └── test
│ │ ├── attribute.rs
│ │ ├── color.rs
│ │ ├── cursor.rs
│ │ ├── event.rs
│ │ └── synchronized_output.rs
├── is_tty.rs
├── key-display.rs
└── stderr.rs
└── src
├── ansi_support.rs
├── clipboard.rs
├── command.rs
├── cursor.rs
├── cursor
├── sys.rs
└── sys
│ ├── unix.rs
│ └── windows.rs
├── event.rs
├── event
├── filter.rs
├── read.rs
├── source.rs
├── source
│ ├── unix.rs
│ ├── unix
│ │ ├── mio.rs
│ │ └── tty.rs
│ └── windows.rs
├── stream.rs
├── sys.rs
├── sys
│ ├── unix.rs
│ ├── unix
│ │ ├── parse.rs
│ │ ├── waker.rs
│ │ └── waker
│ │ │ ├── mio.rs
│ │ │ └── tty.rs
│ ├── windows.rs
│ └── windows
│ │ ├── parse.rs
│ │ ├── poll.rs
│ │ └── waker.rs
└── timeout.rs
├── lib.rs
├── macros.rs
├── style.rs
├── style
├── attributes.rs
├── content_style.rs
├── styled_content.rs
├── stylize.rs
├── sys.rs
├── sys
│ └── windows.rs
├── types.rs
└── types
│ ├── attribute.rs
│ ├── color.rs
│ ├── colored.rs
│ └── colors.rs
├── terminal.rs
├── terminal
├── sys.rs
└── sys
│ ├── file_descriptor.rs
│ ├── unix.rs
│ └── windows.rs
└── tty.rs
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @TimonPost
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **OS**
24 | - e.g. MacOs, Windows, Linux, (version) etc...
25 |
26 | **Terminal/Console**
27 | - e.g. ConHost/xterm/iterm2/Windows Terminal etc.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered if any**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Anything else?
21 |
--------------------------------------------------------------------------------
/.github/workflows/crossterm_test.yml:
--------------------------------------------------------------------------------
1 | name: Crossterm Test
2 |
3 | on:
4 | # Build master branch only
5 | push:
6 | branches:
7 | - master
8 | # Build pull requests targeting master branch only
9 | pull_request:
10 | branches:
11 | - master
12 |
13 | jobs:
14 | test:
15 | name: ${{matrix.rust}} on ${{ matrix.os }}
16 | runs-on: ${{ matrix.os }}
17 | strategy:
18 | matrix:
19 | os: [ubuntu-latest, windows-2019, macOS-latest]
20 | rust: [stable, nightly]
21 | # Allow failures on nightly, it's just informative
22 | include:
23 | - rust: stable
24 | can-fail: false
25 | - rust: nightly
26 | can-fail: true
27 | steps:
28 | - name: Checkout Repository
29 | uses: actions/checkout@v1
30 | with:
31 | fetch-depth: 1
32 | - name: Install Rust
33 | uses: hecrj/setup-rust-action@master
34 | with:
35 | rust-version: ${{ matrix.rust }}
36 | components: rustfmt,clippy
37 | - name: Toolchain Information
38 | run: |
39 | rustc --version
40 | rustfmt --version
41 | rustup --version
42 | cargo --version
43 | - name: Check Formatting
44 | if: matrix.rust == 'stable'
45 | run: cargo fmt --all -- --check
46 | continue-on-error: ${{ matrix.can-fail }}
47 | - name: Clippy
48 | run: cargo clippy --all-features -- -D clippy::all
49 | continue-on-error: ${{ matrix.can-fail }}
50 | - name: Test Build
51 | run: cargo build
52 | continue-on-error: ${{ matrix.can-fail }}
53 | - name: Test default features
54 | run: cargo test --lib -- --nocapture --test-threads 1
55 | continue-on-error: ${{ matrix.can-fail }}
56 | - name: Test serde feature
57 | run: cargo test --lib --features serde -- --nocapture --test-threads 1
58 | continue-on-error: ${{ matrix.can-fail }}
59 | - name: Test event-stream feature
60 | run: cargo test --lib --features "event-stream,events" -- --nocapture --test-threads 1
61 | continue-on-error: ${{ matrix.can-fail }}
62 | - name: Test all features
63 | run: cargo test --all-features -- --nocapture --test-threads 1
64 | continue-on-error: ${{ matrix.can-fail }}
65 | - name: Test no default features
66 | if: matrix.os != 'windows-2019'
67 | run: cargo test --no-default-features -- --nocapture --test-threads 1
68 | continue-on-error: ${{ matrix.can-fail }}
69 | - name: Test no default features with use-dev-tty feature enabled
70 | if: matrix.os != 'windows-2019'
71 | run: cargo test --no-default-features --features "use-dev-tty events event-stream bracketed-paste" -- --nocapture --test-threads 1
72 | continue-on-error: ${{ matrix.can-fail }}
73 | - name: Test no default features with windows feature enabled
74 | if: matrix.os == 'windows-2019'
75 | run: cargo test --no-default-features --features "windows" -- --nocapture --test-threads 1
76 | continue-on-error: ${{ matrix.can-fail }}
77 | - name: Test Packaging
78 | if: matrix.rust == 'stable'
79 | run: cargo package
80 | continue-on-error: ${{ matrix.can-fail }}
81 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/target/
2 | **/.idea/
3 | **/.vscode/
4 | **/*.rs.bk
5 | **/Cargo.lock
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # Build only pushed (merged) master or any pull request. This avoids the
2 | # pull request to be build twice.
3 | branches:
4 | only:
5 | - master
6 |
7 | language: rust
8 |
9 | rust:
10 | - stable
11 | - nightly
12 |
13 | os:
14 | - linux
15 | - windows
16 | - osx
17 |
18 | git:
19 | depth: 1
20 | quiet: true
21 |
22 | matrix:
23 | allow_failures:
24 | - rust: nightly
25 |
26 | before_script:
27 | - export PATH=$PATH:/home/travis/.cargo/bin
28 | - rustup component add rustfmt
29 | - rustup component add clippy
30 |
31 | script:
32 | - cargo fmt --version
33 | - rustup --version
34 | - rustc --version
35 | - if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo fmt --all -- --check; fi
36 | - cargo clippy -- -D clippy::all
37 | - cargo build
38 | - cargo test --lib -- --nocapture --test-threads 1
39 | - cargo test --lib --features serde -- --nocapture --test-threads 1
40 | - cargo test --lib --features event-stream -- --nocapture --test-threads 1
41 | - cargo test --all-features -- --nocapture --test-threads 1
42 | - if [ "$TRAVIS_RUST_VERSION" = "stable" ]; then cargo package; fi
43 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "crossterm"
3 | version = "0.29.0"
4 | authors = ["T. Post"]
5 | description = "A crossplatform terminal library for manipulating terminals."
6 | repository = "https://github.com/crossterm-rs/crossterm"
7 | documentation = "https://docs.rs/crossterm/"
8 | license = "MIT"
9 | keywords = ["event", "color", "cli", "input", "terminal"]
10 | exclude = ["target", "Cargo.lock"]
11 | readme = "README.md"
12 | edition = "2021"
13 | rust-version = "1.63.0"
14 | categories = ["command-line-interface", "command-line-utilities"]
15 |
16 | [lib]
17 | name = "crossterm"
18 | path = "src/lib.rs"
19 |
20 | [package.metadata.docs.rs]
21 | all-features = true
22 |
23 | [features]
24 | default = ["bracketed-paste", "derive-more", "events", "windows"]
25 |
26 | #! ### Default features
27 | ## Enables triggering [`Event::Paste`](event::Event::Paste) when pasting text into the terminal.
28 | bracketed-paste = []
29 |
30 | ## Enables reading input/events from the system using the [`event`] module.
31 | events = ["dep:mio", "dep:signal-hook", "dep:signal-hook-mio"]
32 |
33 | ## Enables windows specific crates.
34 | windows = ["dep:winapi", "dep:crossterm_winapi"]
35 |
36 | ## Enables `is_*` helper functions for event enums.
37 | derive-more = ["dep:derive_more"]
38 |
39 | #! ### Optional Features
40 |
41 | ## Enables documentation for crate features.
42 | document-features = ["dep:document-features"]
43 |
44 | ## Enables the [EventStream](event::EventStream) struct for async event reading.
45 | event-stream = ["dep:futures-core", "events"]
46 |
47 | ## Enables [`serde`] for various types.
48 | serde = ["dep:serde", "bitflags/serde"]
49 |
50 | ## Enables raw file descriptor polling / selecting instead of mio.
51 | use-dev-tty = ["filedescriptor", "rustix/process"]
52 |
53 | ## Enables interacting with a host clipboard via [`clipboard`](clipboard/index.html)
54 | osc52 = ["dep:base64"]
55 |
56 | [dependencies]
57 | base64 = { version = "0.22", optional = true }
58 | bitflags = { version = "2.9" }
59 | derive_more = { version = "2.0.0", features = ["is_variant"], optional = true }
60 | document-features = { version = "0.2.11", optional = true }
61 | futures-core = { version = "0.3", optional = true, default-features = false }
62 | parking_lot = "0.12"
63 | serde = { version = "1.0", features = ["derive"], optional = true }
64 |
65 | # Windows dependencies
66 | [target.'cfg(windows)'.dependencies]
67 | crossterm_winapi = { version = "0.9.1", optional = true }
68 | winapi = { version = "0.3.9", optional = true, features = ["winuser", "winerror"] }
69 |
70 | # UNIX dependencies
71 | [target.'cfg(unix)'.dependencies]
72 | filedescriptor = { version = "0.8", optional = true }
73 | # Default to using rustix for UNIX systems, but provide an option to use libc for backwards
74 | # compatibility.
75 | libc = { version = "0.2", default-features = false, optional = true }
76 | mio = { version = "1.0", features = ["os-poll"], optional = true }
77 | rustix = { version = "1", default-features = false, features = ["std", "stdio", "termios"] }
78 | signal-hook = { version = "0.3.17", optional = true }
79 | signal-hook-mio = { version = "0.2.4", features = ["support-v1_0"], optional = true }
80 |
81 | [dev-dependencies]
82 | async-std = "1.13"
83 | futures = "0.3"
84 | futures-timer = "3.0"
85 | serde_json = "1.0"
86 | serial_test = "3.0.0"
87 | temp-env = "0.3.6"
88 | tokio = { version = "1.44", features = ["full"] }
89 |
90 | # Examples
91 | [[example]]
92 | name = "event-read"
93 | required-features = ["bracketed-paste", "events"]
94 |
95 | [[example]]
96 | name = "event-match-modifiers"
97 | required-features = ["bracketed-paste", "events"]
98 |
99 | [[example]]
100 | name = "event-poll-read"
101 | required-features = ["bracketed-paste", "events"]
102 |
103 | [[example]]
104 | name = "event-stream-async-std"
105 | required-features = ["event-stream", "events"]
106 |
107 | [[example]]
108 | name = "event-stream-tokio"
109 | required-features = ["event-stream", "events"]
110 |
111 | [[example]]
112 | name = "event-read-char-line"
113 | required-features = ["events"]
114 |
115 | [[example]]
116 | name = "stderr"
117 | required-features = ["events"]
118 |
119 | [[example]]
120 | name = "key-display"
121 | required-features = ["events"]
122 |
123 | [[example]]
124 | name = "copy-to-clipboard"
125 | required-features = ["osc52"]
126 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Timon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 |
3 | [](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]
4 |
5 | # Cross-platform Terminal Manipulation Library
6 |
7 | 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,
8 | see [Tested Terminals](#tested-terminals) for more info).
9 |
10 | ## Table of Contents
11 |
12 | - [Cross-platform Terminal Manipulation Library](#cross-platform-terminal-manipulation-library)
13 | - [Table of Contents](#table-of-contents)
14 | - [Features](#features)
15 | - [Tested Terminals](#tested-terminals)
16 | - [Getting Started](#getting-started)
17 | - [Feature Flags](#feature-flags)
18 | - [Dependency Justification](#dependency-justification)
19 | - [Other Resources](#other-resources)
20 | - [Used By](#used-by)
21 | - [Contributing](#contributing)
22 | - [Authors](#authors)
23 | - [License](#license)
24 |
25 | ## Features
26 |
27 | - Cross-platform
28 | - Multi-threaded (send, sync)
29 | - Detailed documentation
30 | - Few dependencies
31 | - Full control over writing and flushing output buffer
32 | - Is tty
33 | - Cursor
34 | - Move the cursor N times (up, down, left, right)
35 | - Move to previous / next line
36 | - Move to column
37 | - Set/get the cursor position
38 | - Store the cursor position and restore to it later
39 | - Hide/show the cursor
40 | - Enable/disable cursor blinking (not all terminals do support this feature)
41 | - Styled output
42 | - Foreground color (16 base colors)
43 | - Background color (16 base colors)
44 | - 256 (ANSI) color support (Windows 10 and UNIX only)
45 | - RGB color support (Windows 10 and UNIX only)
46 | - Text attributes like bold, italic, underscore, crossed, etc
47 | - Terminal
48 | - Clear (all lines, current line, from cursor down and up, until new line)
49 | - Scroll up, down
50 | - Set/get the terminal size
51 | - Exit current process
52 | - Alternate screen
53 | - Raw screen
54 | - Set terminal title
55 | - Enable/disable line wrapping
56 | - Event
57 | - Input Events
58 | - Mouse Events (press, release, position, button, drag)
59 | - Terminal Resize Events
60 | - Advanced modifier (SHIFT | ALT | CTRL) support for both mouse and key events and
61 | - futures Stream (feature 'event-stream')
62 | - Poll/read API
63 |
64 |
67 |
68 | ### Tested Terminals
69 |
70 | - Console Host
71 | - Windows 10 (Pro)
72 | - Windows 8.1 (N)
73 | - Windows Terminal
74 | - Windows 10 x86_64 (Enterprise)
75 | - Windows 11 arm64 (Enterprise)
76 | - Ubuntu Desktop Terminal
77 | - Ubuntu 23.04 64-bit
78 | - Ubuntu 17.10
79 | - Pop!_OS ( Ubuntu ) 20.04
80 | - (Arch, Manjaro) KDE Konsole
81 | - (Arch, NixOS) Kitty
82 | - Linux Mint
83 | - (OpenSuse) Alacritty
84 | - (Chrome OS) Crostini
85 | - Apple
86 | - macOS Monterey 12.7.1 (Intel-Chip)
87 | - macOS Sonama 14.4 (M1 Max, Apple Silicon-Chip)
88 |
89 | This crate supports all UNIX terminals and Windows terminals down to Windows 7; however, not all of the
90 | terminals have been tested. If you have used this library for a terminal other than the above list without
91 | issues, then feel free to add it to the above list - I really would appreciate it!
92 |
93 | ## Getting Started
94 | _see the [examples directory](examples/) and [documentation](https://docs.rs/crossterm/) for more advanced examples._
95 |
96 |
97 |
98 | Click to show Cargo.toml.
99 |
100 |
101 | ```toml
102 | [dependencies]
103 | crossterm = "0.27"
104 | ```
105 |
106 |
107 |
108 |
109 | ```rust
110 | use std::io::{stdout, Write};
111 |
112 | use crossterm::{
113 | execute,
114 | style::{Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor},
115 | ExecutableCommand,
116 | event,
117 | };
118 |
119 | fn main() -> std::io::Result<()> {
120 | // using the macro
121 | execute!(
122 | stdout(),
123 | SetForegroundColor(Color::Blue),
124 | SetBackgroundColor(Color::Red),
125 | Print("Styled text here."),
126 | ResetColor
127 | )?;
128 |
129 | // or using functions
130 | stdout()
131 | .execute(SetForegroundColor(Color::Blue))?
132 | .execute(SetBackgroundColor(Color::Red))?
133 | .execute(Print("Styled text here."))?
134 | .execute(ResetColor)?;
135 |
136 | Ok(())
137 | }
138 | ```
139 |
140 | Checkout this [list](https://docs.rs/crossterm/latest/crossterm/index.html#supported-commands) with all possible commands.
141 |
142 | ### Feature Flags
143 |
144 | ```toml
145 | [dependencies.crossterm]
146 | version = "0.27"
147 | features = ["event-stream"]
148 | ```
149 |
150 | | Feature | Description |
151 | |:---------------|:---------------------------------------------|
152 | | `event-stream` | `futures::Stream` producing `Result`. |
153 | | `serde` | (De)serializing of events. |
154 | | `events` | Reading input/system events (enabled by default) |
155 | | `filedescriptor` | Use raw filedescriptor for all events rather then mio dependency |
156 | | `derive-more` | Adds `is_*` helper functions for event types |
157 | | `osc52` | Enables crossterm::clipboard |
158 |
159 |
160 | To use crossterm as a very thin layer you can disable the `events` feature or use `filedescriptor` feature.
161 | This can disable `mio` / `signal-hook` / `signal-hook-mio` dependencies.
162 |
163 | ### Dependency Justification
164 |
165 | | Dependency | Used for | Included |
166 | |:---------------|:---------------------------------------------------------------------------------|:--------------------------------------|
167 | | `bitflags` | `KeyModifiers`, those are differ based on input. | always |
168 | | `parking_lot` | locking `RwLock`s with a timeout, const mutexes. | always |
169 | | `libc` | UNIX terminal_size/raw modes/set_title and several other low level functionality. | optional (`events` feature), UNIX only |
170 | | `Mio` | event readiness polling, waking up poller | optional (`events` feature), UNIX only |
171 | | `signal-hook` | signal-hook is used to handle terminal resize SIGNAL with Mio. | optional (`events` feature),UNIX only |
172 | | `winapi` | Used for low-level windows system calls which ANSI codes can't replace | windows only |
173 | | `futures-core` | For async stream of events | only with `event-stream` feature flag |
174 | | `serde` | ***ser***ializing and ***de***serializing of events | only with `serde` feature flag |
175 | | `derive_more` | Adds `is_*` helper functions for event types | optional (`derive-more` feature), included by default |
176 | | `base64` | Encoding clipboard data for OSC52 sequences in crossterm::clipboard | only with `osc52` feature flag |
177 |
178 | ### Other Resources
179 |
180 | - [API documentation](https://docs.rs/crossterm/)
181 | - [Deprecated examples repository](https://github.com/crossterm-rs/examples)
182 |
183 | ## Used By
184 |
185 | - [Broot](https://dystroy.org/broot/)
186 | - [Cursive](https://github.com/gyscos/Cursive)
187 | - [Ratatui](https://github.com/ratatui/ratatui)
188 | - [Rust-sloth](https://github.com/ecumene/rust-sloth)
189 | - [Rusty-rain](https://github.com/cowboy8625/rusty-rain)
190 |
191 | ## Contributing
192 |
193 | We highly appreciate when anyone contributes to this crate. Before you do, please,
194 | read the [Contributing](docs/CONTRIBUTING.md) guidelines.
195 |
196 | ## Authors
197 |
198 | * **Timon Post** - *Project Owner & creator*
199 |
200 | ## License
201 |
202 | This project, `crossterm` and all its sub-crates: `crossterm_screen`, `crossterm_cursor`, `crossterm_style`,
203 | `crossterm_input`, `crossterm_terminal`, `crossterm_winapi`, `crossterm_utils` are licensed under the MIT
204 | License - see the [LICENSE](https://github.com/crossterm-rs/crossterm/blob/master/LICENSE) file for details.
205 |
206 | [s1]: https://img.shields.io/crates/v/crossterm.svg
207 | [l1]: https://crates.io/crates/crossterm
208 |
209 | [s2]: https://img.shields.io/badge/license-MIT-blue.svg
210 | [l2]: ./LICENSE
211 |
212 | [s3]: https://docs.rs/crossterm/badge.svg
213 | [l3]: https://docs.rs/crossterm/
214 |
215 | [s3]: https://docs.rs/crossterm/badge.svg
216 | [l3]: https://docs.rs/crossterm/
217 |
218 | [s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
219 | [l5]: https://discord.gg/K4nyTDB
220 |
221 | [s6]: https://tokei.rs/b1/github/crossterm-rs/crossterm?category=code
222 | [s7]: https://travis-ci.org/crossterm-rs/crossterm.svg?branch=master
223 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | book
2 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | I would appreciate any contributions to this crate. However, some things are handy to know.
4 |
5 | ## Code Style
6 |
7 | ### Import Order
8 |
9 | All imports are semantically grouped and ordered. The order is:
10 |
11 | - standard library (`use std::...`)
12 | - external crates (`use rand::...`)
13 | - current crate (`use crate::...`)
14 | - parent module (`use super::..`)
15 | - current module (`use self::...`)
16 | - module declaration (`mod ...`)
17 |
18 | There must be an empty line between groups. An example:
19 |
20 | ```rust
21 | use crossterm_utils::{csi, write_cout, Result};
22 |
23 | use crate::sys::{get_cursor_position, show_cursor};
24 |
25 | use super::Cursor;
26 | ```
27 |
28 | #### CLion Tips
29 |
30 | The CLion IDE does this for you (_Menu_ -> _Code_ -> _Optimize Imports_). Be aware that the CLion sorts
31 | imports in a group in a different way when compared to the `rustfmt`. It's effectively two steps operation
32 | to get proper grouping & sorting:
33 |
34 | * _Menu_ -> _Code_ -> _Optimize Imports_ - group & semantically order imports
35 | * `cargo fmt` - fix ordering within the group
36 |
37 | Second step can be automated via _CLion_ -> _Preferences_ ->
38 | _Languages & Frameworks_ -> _Rust_ -> _Rustfmt_ -> _Run rustfmt on save_.
39 |
40 | ### Max Line Length
41 |
42 | | Type | Max line length |
43 | |:---------------------|----------------:|
44 | | Code | 100 |
45 | | Comments in the code | 120 |
46 | | Documentation | 120 |
47 |
48 | 100 is the [`max_width`](https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width)
49 | default value.
50 |
51 | 120 is because of the GitHub. The editor & viewer width there is +- 123 characters.
52 |
53 | ### Warnings
54 |
55 | The code must be warning free. It's quite hard to find an error if the build logs are polluted with warnings.
56 | If you decide to silent a warning with (`#[allow(...)]`), please add a comment why it's required.
57 |
58 | Always consult the [Travis CI](https://travis-ci.org/crossterm-rs/crossterm/pull_requests) build logs.
59 |
60 | ### Forbidden Warnings
61 |
62 | Search for `#![deny(...)]` in the code:
63 |
64 | * `unused_must_use`
65 | * `unused_imports`
66 |
--------------------------------------------------------------------------------
/docs/crossterm_c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossterm-rs/crossterm/17192310472778bf94566b027ac1be0abdd05a7c/docs/crossterm_c.png
--------------------------------------------------------------------------------
/docs/crossterm_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crossterm-rs/crossterm/17192310472778bf94566b027ac1be0abdd05a7c/docs/crossterm_full.png
--------------------------------------------------------------------------------
/docs/crossterm_full.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
104 |
--------------------------------------------------------------------------------
/docs/know-problems.md:
--------------------------------------------------------------------------------
1 | # Known Problems
2 |
3 | There are some problems I discovered during development.
4 | And I don't think it has to do anything with crossterm but it has to do with how terminals handle ANSI or WinApi.
5 |
6 | ## WinAPI
7 |
8 | - Power shell does not interpreter 'DarkYellow' and is instead using gray instead, cmd is working perfectly fine.
9 | - Power shell inserts an '\n' (enter) when the program starts, this enter is the one you pressed when running the command.
10 | - After the program ran, power shell will reset the background and foreground colors.
11 |
12 | ## UNIX-terminals
13 |
14 | The Arc and Manjaro KDE Konsole's are not seeming to resize the terminal instead they are resizing the buffer.
15 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | ![Lines of Code][s7] [![MIT][s2]][l2] [![Join us on Discord][s5]][l5]
2 |
3 | # Crossterm Examples
4 |
5 | The examples are compatible with the latest release.
6 |
7 | ## Structure
8 |
9 | ```
10 | ├── examples
11 | │ └── interactive-test
12 | │ └── event-*
13 | │ └── stderr
14 | ```
15 | | File Name | Description | Topics |
16 | |:----------------------------|:-------------------------------|:------------------------------------------|
17 | | `examples/interactive-test` | interactive, walk through, demo | cursor, style, event |
18 | | `event-*` | event reading demos | (async) event reading |
19 | | `stderr` | crossterm over stderr demo | raw mode, alternate screen, custom output |
20 | | `is_tty` | Is this instance a tty ? | tty |
21 |
22 | ## Run examples
23 |
24 | ```bash
25 | $ cargo run --example [file name]
26 | ```
27 |
28 | To run the interactive-demo go into the folder `examples/interactive-demo` and run `cargo run`.
29 |
30 | ## License
31 |
32 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE) file for details.
33 |
34 | [s2]: https://img.shields.io/badge/license-MIT-blue.svg
35 | [l2]: LICENSE
36 |
37 | [s5]: https://img.shields.io/discord/560857607196377088.svg?logo=discord
38 | [l5]: https://discord.gg/K4nyTDB
39 |
40 | [s7]: https://travis-ci.org/crossterm-rs/examples.svg?branch=master
41 |
--------------------------------------------------------------------------------
/examples/copy-to-clipboard.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates copying a string to clipboard
2 | //!
3 | //! This example uses OSC control sequence `Pr = 5 2` (See
4 | //!
5 | //! to copy data to the terminal host clipboard.
6 | //!
7 | //! This only works if it is enabled on the respective terminal emulator. If a terminal multiplexer
8 | //! is used, the multiplexer will likely need to support it, too.
9 | //!
10 | //! ```no_run
11 | //! cargo run --example copy-to-clipboard -- --clipboard "Some String"
12 | //! cargo run --example copy-to-clipboard -- --primary "Some String"
13 | //! cargo run --example copy-to-clipboard -- "Some String"
14 | //! ```
15 |
16 | use std::io;
17 |
18 | use crossterm::clipboard;
19 | use crossterm::execute;
20 |
21 | fn main() -> io::Result<()> {
22 | let mut stdout = io::stdout();
23 | let mut args = std::env::args();
24 | args.next(); // Skip to first argument
25 |
26 | let default_text = String::from("Example text");
27 | let (text, dest) = match args.next().as_deref() {
28 | Some("--clipboard") => (
29 | args.next().unwrap_or(default_text),
30 | clipboard::ClipboardType::Clipboard,
31 | ),
32 | Some("--primary") => (
33 | args.next().unwrap_or(default_text),
34 | clipboard::ClipboardType::Primary,
35 | ),
36 | Some(text) => (text.to_owned(), clipboard::ClipboardType::Clipboard),
37 | None => (default_text, clipboard::ClipboardType::Clipboard),
38 | };
39 | execute!(
40 | stdout,
41 | clipboard::CopyToClipboard {
42 | content: text,
43 | destination: clipboard::ClipboardSelection(vec![dest])
44 | }
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/examples/event-match-modifiers.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to match on modifiers like: Control, alt, shift.
2 | //!
3 | //! cargo run --example event-match-modifiers
4 |
5 | use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
6 |
7 | fn match_event(event: Event) {
8 | if let Some(key) = event.as_key_press_event() {
9 | match key {
10 | KeyEvent {
11 | modifiers: KeyModifiers::CONTROL,
12 | code,
13 | ..
14 | } => {
15 | println!("Control + {:?}", code);
16 | }
17 | KeyEvent {
18 | modifiers: KeyModifiers::SHIFT,
19 | code,
20 | ..
21 | } => {
22 | println!("Shift + {:?}", code);
23 | }
24 | KeyEvent {
25 | modifiers: KeyModifiers::ALT,
26 | code,
27 | ..
28 | } => {
29 | println!("Alt + {:?}", code);
30 | }
31 |
32 | // Match on multiple modifiers:
33 | KeyEvent {
34 | code, modifiers, ..
35 | } => {
36 | if modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) {
37 | println!("Alt + Shift {:?}", code);
38 | } else {
39 | println!("({:?}) with key: {:?}", modifiers, code)
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | fn main() {
47 | match_event(Event::Key(KeyEvent::new(
48 | KeyCode::Char('z'),
49 | KeyModifiers::CONTROL,
50 | )));
51 | match_event(Event::Key(KeyEvent::new(
52 | KeyCode::Left,
53 | KeyModifiers::SHIFT,
54 | )));
55 | match_event(Event::Key(KeyEvent::new(
56 | KeyCode::Delete,
57 | KeyModifiers::ALT,
58 | )));
59 | match_event(Event::Key(KeyEvent::new(
60 | KeyCode::Right,
61 | KeyModifiers::ALT | KeyModifiers::SHIFT,
62 | )));
63 | match_event(Event::Key(KeyEvent::new(
64 | KeyCode::Home,
65 | KeyModifiers::ALT | KeyModifiers::CONTROL,
66 | )));
67 | }
68 |
--------------------------------------------------------------------------------
/examples/event-poll-read.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to match on modifiers like: Control, alt, shift.
2 | //!
3 | //! cargo run --example event-poll-read
4 |
5 | use std::{io, time::Duration};
6 |
7 | use crossterm::{
8 | cursor::position,
9 | event::{poll, read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
10 | execute,
11 | terminal::{disable_raw_mode, enable_raw_mode},
12 | };
13 |
14 | const HELP: &str = r#"Blocking poll() & non-blocking read()
15 | - Keyboard, mouse and terminal resize events enabled
16 | - Prints "." every second if there's no event
17 | - Hit "c" to print current cursor position
18 | - Use Esc to quit
19 | "#;
20 |
21 | fn print_events() -> io::Result<()> {
22 | loop {
23 | // Wait up to 1s for another event
24 | if poll(Duration::from_millis(1_000))? {
25 | // It's guaranteed that read() won't block if `poll` returns `Ok(true)`
26 | let event = read()?;
27 |
28 | println!("Event::{:?}\r", event);
29 |
30 | if event == Event::Key(KeyCode::Char('c').into()) {
31 | println!("Cursor position: {:?}\r", position());
32 | }
33 |
34 | if event == Event::Key(KeyCode::Esc.into()) {
35 | break;
36 | }
37 | } else {
38 | // Timeout expired, no event for 1s
39 | println!(".\r");
40 | }
41 | }
42 |
43 | Ok(())
44 | }
45 |
46 | fn main() -> io::Result<()> {
47 | println!("{}", HELP);
48 |
49 | enable_raw_mode()?;
50 |
51 | let mut stdout = io::stdout();
52 | execute!(stdout, EnableMouseCapture)?;
53 |
54 | if let Err(e) = print_events() {
55 | println!("Error: {:?}\r", e);
56 | }
57 |
58 | execute!(stdout, DisableMouseCapture)?;
59 |
60 | disable_raw_mode()
61 | }
62 |
--------------------------------------------------------------------------------
/examples/event-read-char-line.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to block read characters or a full line.
2 | //! Just note that crossterm is not required to do this and can be done with `io::stdin()`.
3 | //!
4 | //! cargo run --example event-read-char-line
5 |
6 | use std::io;
7 |
8 | use crossterm::{
9 | event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
10 | terminal,
11 | };
12 |
13 | pub fn read_char() -> io::Result {
14 | loop {
15 | if let Event::Key(KeyEvent {
16 | code: KeyCode::Char(c),
17 | kind: KeyEventKind::Press,
18 | ..
19 | }) = event::read()?
20 | {
21 | return Ok(c);
22 | }
23 | }
24 | }
25 |
26 | pub fn read_line() -> io::Result {
27 | let mut line = String::new();
28 | loop {
29 | if let Event::Key(KeyEvent {
30 | code,
31 | kind: KeyEventKind::Press,
32 | ..
33 | }) = event::read()?
34 | {
35 | match code {
36 | KeyCode::Enter => {
37 | break;
38 | }
39 | KeyCode::Char(c) => {
40 | line.push(c);
41 | }
42 | _ => {}
43 | }
44 | }
45 | }
46 |
47 | Ok(line)
48 | }
49 |
50 | fn main() -> io::Result<()> {
51 | terminal::enable_raw_mode()?;
52 |
53 | println!("read line:\r");
54 | println!("{:?}\r", read_line());
55 | println!("read char:\r");
56 | println!("{:?}\r", read_char());
57 | println!("read char again:\r");
58 | println!("{:?}\r", read_char());
59 |
60 | terminal::disable_raw_mode()
61 | }
62 |
--------------------------------------------------------------------------------
/examples/event-read.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to block read events.
2 | //!
3 | //! cargo run --example event-read
4 |
5 | use std::io;
6 |
7 | use crossterm::event::{
8 | poll, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
9 | };
10 | use crossterm::{
11 | cursor::position,
12 | event::{
13 | read, DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
14 | EnableFocusChange, EnableMouseCapture, Event, KeyCode,
15 | },
16 | execute, queue,
17 | terminal::{disable_raw_mode, enable_raw_mode},
18 | };
19 | use std::time::Duration;
20 |
21 | const HELP: &str = r#"Blocking read()
22 | - Keyboard, mouse, focus and terminal resize events enabled
23 | - Hit "c" to print current cursor position
24 | - Use Esc to quit
25 | "#;
26 |
27 | fn print_events() -> io::Result<()> {
28 | loop {
29 | // Blocking read
30 | let event = read()?;
31 |
32 | println!("Event: {:?}\r", event);
33 |
34 | if event == Event::Key(KeyCode::Char('c').into()) {
35 | println!("Cursor position: {:?}\r", position());
36 | }
37 |
38 | if let Event::Resize(x, y) = event {
39 | let (original_size, new_size) = flush_resize_events((x, y));
40 | println!("Resize from: {:?}, to: {:?}\r", original_size, new_size);
41 | }
42 |
43 | if event == Event::Key(KeyCode::Esc.into()) {
44 | break;
45 | }
46 | }
47 |
48 | Ok(())
49 | }
50 |
51 | // Resize events can occur in batches.
52 | // With a simple loop they can be flushed.
53 | // This function will keep the first and last resize event.
54 | fn flush_resize_events(first_resize: (u16, u16)) -> ((u16, u16), (u16, u16)) {
55 | let mut last_resize = first_resize;
56 | while let Ok(true) = poll(Duration::from_millis(50)) {
57 | if let Ok(Event::Resize(x, y)) = read() {
58 | last_resize = (x, y);
59 | }
60 | }
61 |
62 | (first_resize, last_resize)
63 | }
64 |
65 | fn main() -> io::Result<()> {
66 | println!("{}", HELP);
67 |
68 | enable_raw_mode()?;
69 |
70 | let mut stdout = io::stdout();
71 |
72 | let supports_keyboard_enhancement = matches!(
73 | crossterm::terminal::supports_keyboard_enhancement(),
74 | Ok(true)
75 | );
76 |
77 | if supports_keyboard_enhancement {
78 | queue!(
79 | stdout,
80 | PushKeyboardEnhancementFlags(
81 | KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
82 | | KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
83 | | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
84 | | KeyboardEnhancementFlags::REPORT_EVENT_TYPES
85 | )
86 | )?;
87 | }
88 |
89 | execute!(
90 | stdout,
91 | EnableBracketedPaste,
92 | EnableFocusChange,
93 | EnableMouseCapture,
94 | )?;
95 |
96 | if let Err(e) = print_events() {
97 | println!("Error: {:?}\r", e);
98 | }
99 |
100 | if supports_keyboard_enhancement {
101 | queue!(stdout, PopKeyboardEnhancementFlags)?;
102 | }
103 |
104 | execute!(
105 | stdout,
106 | DisableBracketedPaste,
107 | DisableFocusChange,
108 | DisableMouseCapture
109 | )?;
110 |
111 | disable_raw_mode()
112 | }
113 |
--------------------------------------------------------------------------------
/examples/event-stream-async-std.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to read events asynchronously with async-std.
2 | //!
3 | //! cargo run --features="event-stream" --example event-stream-async-std
4 |
5 | use std::{io::stdout, time::Duration};
6 |
7 | use futures::{future::FutureExt, select, StreamExt};
8 | use futures_timer::Delay;
9 |
10 | use crossterm::{
11 | cursor::position,
12 | event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode},
13 | execute,
14 | terminal::{disable_raw_mode, enable_raw_mode},
15 | };
16 |
17 | const HELP: &str = r#"EventStream based on futures_util::stream::Stream with async-std
18 | - Keyboard, mouse and terminal resize events enabled
19 | - Prints "." every second if there's no event
20 | - Hit "c" to print current cursor position
21 | - Use Esc to quit
22 | "#;
23 |
24 | async fn print_events() {
25 | let mut reader = EventStream::new();
26 |
27 | loop {
28 | let mut delay = Delay::new(Duration::from_millis(1_000)).fuse();
29 | let mut event = reader.next().fuse();
30 |
31 | select! {
32 | _ = delay => { println!(".\r"); },
33 | maybe_event = event => {
34 | match maybe_event {
35 | Some(Ok(event)) => {
36 | println!("Event::{:?}\r", event);
37 |
38 | if event == Event::Key(KeyCode::Char('c').into()) {
39 | println!("Cursor position: {:?}\r", position());
40 | }
41 |
42 | if event == Event::Key(KeyCode::Esc.into()) {
43 | break;
44 | }
45 | }
46 | Some(Err(e)) => println!("Error: {:?}\r", e),
47 | None => break,
48 | }
49 | }
50 | };
51 | }
52 | }
53 |
54 | fn main() -> std::io::Result<()> {
55 | println!("{}", HELP);
56 |
57 | enable_raw_mode()?;
58 |
59 | let mut stdout = stdout();
60 | execute!(stdout, EnableMouseCapture)?;
61 |
62 | async_std::task::block_on(print_events());
63 |
64 | execute!(stdout, DisableMouseCapture)?;
65 |
66 | disable_raw_mode()
67 | }
68 |
--------------------------------------------------------------------------------
/examples/event-stream-tokio.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates how to read events asynchronously with tokio.
2 | //!
3 | //! cargo run --features="event-stream" --example event-stream-tokio
4 |
5 | use std::{io::stdout, time::Duration};
6 |
7 | use futures::{future::FutureExt, select, StreamExt};
8 | use futures_timer::Delay;
9 |
10 | use crossterm::{
11 | cursor::position,
12 | event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream, KeyCode},
13 | execute,
14 | terminal::{disable_raw_mode, enable_raw_mode},
15 | };
16 |
17 | const HELP: &str = r#"EventStream based on futures_util::Stream with tokio
18 | - Keyboard, mouse and terminal resize events enabled
19 | - Prints "." every second if there's no event
20 | - Hit "c" to print current cursor position
21 | - Use Esc to quit
22 | "#;
23 |
24 | async fn print_events() {
25 | let mut reader = EventStream::new();
26 |
27 | loop {
28 | let mut delay = Delay::new(Duration::from_millis(1_000)).fuse();
29 | let mut event = reader.next().fuse();
30 |
31 | select! {
32 | _ = delay => { println!(".\r"); },
33 | maybe_event = event => {
34 | match maybe_event {
35 | Some(Ok(event)) => {
36 | println!("Event::{:?}\r", event);
37 |
38 | if event == Event::Key(KeyCode::Char('c').into()) {
39 | println!("Cursor position: {:?}\r", position());
40 | }
41 |
42 | if event == Event::Key(KeyCode::Esc.into()) {
43 | break;
44 | }
45 | }
46 | Some(Err(e)) => println!("Error: {:?}\r", e),
47 | None => break,
48 | }
49 | }
50 | };
51 | }
52 | }
53 |
54 | #[tokio::main]
55 | async fn main() -> std::io::Result<()> {
56 | println!("{}", HELP);
57 |
58 | enable_raw_mode()?;
59 |
60 | let mut stdout = stdout();
61 | execute!(stdout, EnableMouseCapture)?;
62 |
63 | print_events().await;
64 |
65 | execute!(stdout, DisableMouseCapture)?;
66 |
67 | disable_raw_mode()
68 | }
69 |
--------------------------------------------------------------------------------
/examples/interactive-demo/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "interactive-demo"
3 | version = "0.0.1"
4 | authors = ["T. Post", "Robert Vojta "]
5 | edition = "2018"
6 | description = "Interactive demo for crossterm."
7 | license = "MIT"
8 | exclude = ["target", "Cargo.lock"]
9 | readme = "README.md"
10 | publish = false
11 |
12 | [dependencies]
13 | crossterm = { path = "../../" }
--------------------------------------------------------------------------------
/examples/interactive-demo/src/macros.rs:
--------------------------------------------------------------------------------
1 | macro_rules! run_tests {
2 | (
3 | $dst:expr,
4 | $(
5 | $testfn:ident
6 | ),*
7 | $(,)?
8 | ) => {
9 | use crossterm::{queue, style, terminal, cursor};
10 | $(
11 | queue!(
12 | $dst,
13 | style::ResetColor,
14 | terminal::Clear(terminal::ClearType::All),
15 | cursor::MoveTo(1, 1),
16 | cursor::Show,
17 | cursor::EnableBlinking
18 | )?;
19 |
20 | $testfn($dst)?;
21 |
22 | match $crate::read_char() {
23 | Ok('q') => return Ok(()),
24 | Err(e) => return Err(e),
25 | _ => { },
26 | };
27 | )*
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/main.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::cognitive_complexity)]
2 |
3 | use std::io;
4 |
5 | use crossterm::event::KeyEventKind;
6 | pub use crossterm::{
7 | cursor,
8 | event::{self, Event, KeyCode, KeyEvent},
9 | execute, queue, style,
10 | terminal::{self, ClearType},
11 | Command,
12 | };
13 |
14 | #[macro_use]
15 | mod macros;
16 | mod test;
17 |
18 | const MENU: &str = r#"Crossterm interactive test
19 |
20 | Controls:
21 |
22 | - 'q' - quit interactive test (or return to this menu)
23 | - any other key - continue with next step
24 |
25 | Available tests:
26 |
27 | 1. cursor
28 | 2. color (foreground, background)
29 | 3. attributes (bold, italic, ...)
30 | 4. input
31 | 5. synchronized output
32 |
33 | Select test to run ('1', '2', ...) or hit 'q' to quit.
34 | "#;
35 |
36 | fn run(w: &mut W) -> io::Result<()>
37 | where
38 | W: io::Write,
39 | {
40 | execute!(w, terminal::EnterAlternateScreen)?;
41 |
42 | terminal::enable_raw_mode()?;
43 |
44 | loop {
45 | queue!(
46 | w,
47 | style::ResetColor,
48 | terminal::Clear(ClearType::All),
49 | cursor::Hide,
50 | cursor::MoveTo(1, 1)
51 | )?;
52 |
53 | for line in MENU.split('\n') {
54 | queue!(w, style::Print(line), cursor::MoveToNextLine(1))?;
55 | }
56 |
57 | w.flush()?;
58 |
59 | match read_char()? {
60 | '1' => test::cursor::run(w)?,
61 | '2' => test::color::run(w)?,
62 | '3' => test::attribute::run(w)?,
63 | '4' => test::event::run(w)?,
64 | '5' => test::synchronized_output::run(w)?,
65 | 'q' => {
66 | execute!(w, cursor::SetCursorStyle::DefaultUserShape).unwrap();
67 | break;
68 | }
69 | _ => {}
70 | };
71 | }
72 |
73 | execute!(
74 | w,
75 | style::ResetColor,
76 | cursor::Show,
77 | terminal::LeaveAlternateScreen
78 | )?;
79 |
80 | terminal::disable_raw_mode()
81 | }
82 |
83 | pub fn read_char() -> std::io::Result {
84 | loop {
85 | if let Ok(Event::Key(KeyEvent {
86 | code: KeyCode::Char(c),
87 | kind: KeyEventKind::Press,
88 | modifiers: _,
89 | state: _,
90 | })) = event::read()
91 | {
92 | return Ok(c);
93 | }
94 | }
95 | }
96 |
97 | pub fn buffer_size() -> io::Result<(u16, u16)> {
98 | terminal::size()
99 | }
100 |
101 | fn main() -> std::io::Result<()> {
102 | let mut stdout = io::stdout();
103 | run(&mut stdout)
104 | }
105 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test.rs:
--------------------------------------------------------------------------------
1 | pub mod attribute;
2 | pub mod color;
3 | pub mod cursor;
4 | pub mod event;
5 | pub mod synchronized_output;
6 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test/attribute.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::cognitive_complexity)]
2 |
3 | use crossterm::{cursor, queue, style};
4 | use std::io::Write;
5 |
6 | const ATTRIBUTES: [(style::Attribute, style::Attribute); 10] = [
7 | (style::Attribute::Bold, style::Attribute::NormalIntensity),
8 | (style::Attribute::Italic, style::Attribute::NoItalic),
9 | (style::Attribute::Underlined, style::Attribute::NoUnderline),
10 | (
11 | style::Attribute::DoubleUnderlined,
12 | style::Attribute::NoUnderline,
13 | ),
14 | (style::Attribute::Undercurled, style::Attribute::NoUnderline),
15 | (style::Attribute::Underdotted, style::Attribute::NoUnderline),
16 | (style::Attribute::Underdashed, style::Attribute::NoUnderline),
17 | (style::Attribute::Reverse, style::Attribute::NoReverse),
18 | (
19 | style::Attribute::CrossedOut,
20 | style::Attribute::NotCrossedOut,
21 | ),
22 | (style::Attribute::SlowBlink, style::Attribute::NoBlink),
23 | ];
24 |
25 | fn test_set_display_attributes(w: &mut W) -> std::io::Result<()>
26 | where
27 | W: Write,
28 | {
29 | queue!(
30 | w,
31 | style::Print("Display attributes"),
32 | cursor::MoveToNextLine(2)
33 | )?;
34 |
35 | for (on, off) in &ATTRIBUTES {
36 | queue!(
37 | w,
38 | style::SetAttribute(*on),
39 | style::Print(format!("{:>width$} ", format!("{:?}", on), width = 35)),
40 | style::SetAttribute(*off),
41 | style::Print(format!("{:>width$}", format!("{:?}", off), width = 35)),
42 | style::ResetColor,
43 | cursor::MoveToNextLine(1)
44 | )?;
45 | }
46 |
47 | w.flush()?;
48 |
49 | Ok(())
50 | }
51 |
52 | pub fn run(w: &mut W) -> std::io::Result<()>
53 | where
54 | W: Write,
55 | {
56 | run_tests!(w, test_set_display_attributes,);
57 | Ok(())
58 | }
59 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test/color.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::cognitive_complexity)]
2 |
3 | use crossterm::{cursor, queue, style, style::Color};
4 | use std::io::Write;
5 |
6 | const COLORS: [Color; 21] = [
7 | Color::Black,
8 | Color::DarkGrey,
9 | Color::Grey,
10 | Color::White,
11 | Color::DarkRed,
12 | Color::Red,
13 | Color::DarkGreen,
14 | Color::Green,
15 | Color::DarkYellow,
16 | Color::Yellow,
17 | Color::DarkBlue,
18 | Color::Blue,
19 | Color::DarkMagenta,
20 | Color::Magenta,
21 | Color::DarkCyan,
22 | Color::Cyan,
23 | Color::AnsiValue(0),
24 | Color::AnsiValue(15),
25 | Color::Rgb { r: 255, g: 0, b: 0 },
26 | Color::Rgb { r: 0, g: 255, b: 0 },
27 | Color::Rgb { r: 0, g: 0, b: 255 },
28 | ];
29 |
30 | fn test_set_foreground_color(w: &mut W) -> std::io::Result<()>
31 | where
32 | W: Write,
33 | {
34 | queue!(
35 | w,
36 | style::Print("Foreground colors on the black & white background"),
37 | cursor::MoveToNextLine(2)
38 | )?;
39 |
40 | for color in &COLORS {
41 | queue!(
42 | w,
43 | style::SetForegroundColor(*color),
44 | style::SetBackgroundColor(Color::Black),
45 | style::Print(format!(
46 | "{:>width$} ",
47 | format!("{:?} ████████████", color),
48 | width = 40
49 | )),
50 | style::SetBackgroundColor(Color::White),
51 | style::Print(format!(
52 | "{:>width$}",
53 | format!("{:?} ████████████", color),
54 | width = 40
55 | )),
56 | cursor::MoveToNextLine(1)
57 | )?;
58 | }
59 |
60 | w.flush()?;
61 |
62 | Ok(())
63 | }
64 |
65 | fn test_set_background_color(w: &mut W) -> std::io::Result<()>
66 | where
67 | W: Write,
68 | {
69 | queue!(
70 | w,
71 | style::Print("Background colors with black & white foreground"),
72 | cursor::MoveToNextLine(2)
73 | )?;
74 |
75 | for color in &COLORS {
76 | queue!(
77 | w,
78 | style::SetBackgroundColor(*color),
79 | style::SetForegroundColor(Color::Black),
80 | style::Print(format!(
81 | "{:>width$} ",
82 | format!("{:?} ▒▒▒▒▒▒▒▒▒▒▒▒", color),
83 | width = 40
84 | )),
85 | style::SetForegroundColor(Color::White),
86 | style::Print(format!(
87 | "{:>width$}",
88 | format!("{:?} ▒▒▒▒▒▒▒▒▒▒▒▒", color),
89 | width = 40
90 | )),
91 | cursor::MoveToNextLine(1)
92 | )?;
93 | }
94 |
95 | w.flush()?;
96 |
97 | Ok(())
98 | }
99 |
100 | fn test_color_values_matrix_16x16(w: &mut W, title: &str, color: F) -> std::io::Result<()>
101 | where
102 | W: Write,
103 | F: Fn(u16, u16) -> Color,
104 | {
105 | queue!(w, style::Print(title))?;
106 |
107 | for idx in 0..=15 {
108 | queue!(
109 | w,
110 | cursor::MoveTo(1, idx + 4),
111 | style::Print(format!("{:>width$}", idx, width = 2))
112 | )?;
113 | queue!(
114 | w,
115 | cursor::MoveTo(idx * 3 + 3, 3),
116 | style::Print(format!("{:>width$}", idx, width = 3))
117 | )?;
118 | }
119 |
120 | for row in 0..=15u16 {
121 | queue!(w, cursor::MoveTo(4, row + 4))?;
122 | for col in 0..=15u16 {
123 | queue!(
124 | w,
125 | style::SetForegroundColor(color(col, row)),
126 | style::Print("███")
127 | )?;
128 | }
129 | queue!(
130 | w,
131 | style::SetForegroundColor(Color::White),
132 | style::Print(format!("{:>width$} ..= ", row * 16, width = 3)),
133 | style::Print(format!("{:>width$}", row * 16 + 15, width = 3))
134 | )?;
135 | }
136 |
137 | w.flush()?;
138 |
139 | Ok(())
140 | }
141 |
142 | fn test_color_ansi_values(w: &mut W) -> std::io::Result<()>
143 | where
144 | W: Write,
145 | {
146 | test_color_values_matrix_16x16(w, "Color::Ansi values", |col, row| {
147 | Color::AnsiValue((row * 16 + col) as u8)
148 | })
149 | }
150 |
151 | fn test_rgb_red_values(w: &mut W) -> std::io::Result<()>
152 | where
153 | W: Write,
154 | {
155 | test_color_values_matrix_16x16(w, "Color::Rgb red values", |col, row| Color::Rgb {
156 | r: (row * 16 + col) as u8,
157 | g: 0_u8,
158 | b: 0,
159 | })
160 | }
161 |
162 | fn test_rgb_green_values(w: &mut W) -> std::io::Result<()>
163 | where
164 | W: Write,
165 | {
166 | test_color_values_matrix_16x16(w, "Color::Rgb green values", |col, row| Color::Rgb {
167 | r: 0,
168 | g: (row * 16 + col) as u8,
169 | b: 0,
170 | })
171 | }
172 |
173 | fn test_rgb_blue_values(w: &mut W) -> std::io::Result<()>
174 | where
175 | W: Write,
176 | {
177 | test_color_values_matrix_16x16(w, "Color::Rgb blue values", |col, row| Color::Rgb {
178 | r: 0,
179 | g: 0,
180 | b: (row * 16 + col) as u8,
181 | })
182 | }
183 |
184 | pub fn run(w: &mut W) -> std::io::Result<()>
185 | where
186 | W: Write,
187 | {
188 | run_tests!(
189 | w,
190 | test_set_foreground_color,
191 | test_set_background_color,
192 | test_color_ansi_values,
193 | test_rgb_red_values,
194 | test_rgb_green_values,
195 | test_rgb_blue_values,
196 | );
197 | Ok(())
198 | }
199 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test/cursor.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::cognitive_complexity)]
2 |
3 | use std::io::Write;
4 |
5 | use crossterm::{cursor, execute, queue, style, style::Stylize, Command};
6 | use std::thread;
7 | use std::time::Duration;
8 |
9 | fn test_move_cursor_up(w: &mut W) -> std::io::Result<()>
10 | where
11 | W: Write,
12 | {
13 | draw_cursor_box(w, "Move Up (2)", |_, _| cursor::MoveUp(2))
14 | }
15 |
16 | fn test_move_cursor_down(w: &mut W) -> std::io::Result<()>
17 | where
18 | W: Write,
19 | {
20 | draw_cursor_box(w, "Move Down (2)", |_, _| cursor::MoveDown(2))
21 | }
22 |
23 | fn test_move_cursor_left(w: &mut W) -> std::io::Result<()>
24 | where
25 | W: Write,
26 | {
27 | draw_cursor_box(w, "Move Left (2)", |_, _| cursor::MoveLeft(2))
28 | }
29 |
30 | fn test_move_cursor_right(w: &mut W) -> std::io::Result<()>
31 | where
32 | W: Write,
33 | {
34 | draw_cursor_box(w, "Move Right (2)", |_, _| cursor::MoveRight(2))
35 | }
36 |
37 | fn test_move_cursor_to_previous_line(w: &mut W) -> std::io::Result<()>
38 | where
39 | W: Write,
40 | {
41 | draw_cursor_box(w, "MoveToPreviousLine (1)", |_, _| {
42 | cursor::MoveToPreviousLine(1)
43 | })
44 | }
45 |
46 | fn test_move_cursor_to_next_line(w: &mut W) -> std::io::Result<()>
47 | where
48 | W: Write,
49 | {
50 | draw_cursor_box(w, "MoveToNextLine (1)", |_, _| cursor::MoveToNextLine(1))
51 | }
52 |
53 | fn test_move_cursor_to_column(w: &mut W) -> std::io::Result<()>
54 | where
55 | W: Write,
56 | {
57 | draw_cursor_box(w, "MoveToColumn (1)", |center_x, _| {
58 | cursor::MoveToColumn(center_x + 1)
59 | })
60 | }
61 |
62 | fn test_hide_cursor(w: &mut W) -> std::io::Result<()>
63 | where
64 | W: Write,
65 | {
66 | execute!(w, style::Print("HideCursor"), cursor::Hide)
67 | }
68 |
69 | fn test_show_cursor(w: &mut W) -> std::io::Result<()>
70 | where
71 | W: Write,
72 | {
73 | execute!(w, style::Print("ShowCursor"), cursor::Show)
74 | }
75 |
76 | fn test_cursor_blinking_block(w: &mut W) -> std::io::Result<()>
77 | where
78 | W: Write,
79 | {
80 | execute!(
81 | w,
82 | style::Print("Blinking Block:"),
83 | cursor::MoveLeft(2),
84 | cursor::SetCursorStyle::BlinkingBlock,
85 | )
86 | }
87 |
88 | fn test_cursor_blinking_underscore(w: &mut W) -> std::io::Result<()>
89 | where
90 | W: Write,
91 | {
92 | execute!(
93 | w,
94 | style::Print("Blinking Underscore:"),
95 | cursor::MoveLeft(2),
96 | cursor::SetCursorStyle::BlinkingUnderScore,
97 | )
98 | }
99 |
100 | fn test_cursor_blinking_bar(w: &mut W) -> std::io::Result<()>
101 | where
102 | W: Write,
103 | {
104 | execute!(
105 | w,
106 | style::Print("Blinking bar:"),
107 | cursor::MoveLeft(2),
108 | cursor::SetCursorStyle::BlinkingBar,
109 | )
110 | }
111 |
112 | fn test_move_cursor_to(w: &mut W) -> std::io::Result<()>
113 | where
114 | W: Write,
115 | {
116 | draw_cursor_box(
117 | w,
118 | "MoveTo (x: 1, y: 1) removed from center",
119 | |center_x, center_y| cursor::MoveTo(center_x + 1, center_y + 1),
120 | )
121 | }
122 |
123 | fn test_save_restore_cursor_position(w: &mut W) -> std::io::Result<()>
124 | where
125 | W: Write,
126 | {
127 | execute!(w,
128 | cursor::MoveTo(0, 0),
129 | style::Print("Save position, print character elsewhere, after three seconds restore to old position."),
130 | cursor::MoveToNextLine(2),
131 | style::Print("Save ->[ ]<- Position"),
132 | cursor::MoveTo(8, 2),
133 | cursor::SavePosition,
134 | cursor::MoveTo(10,10),
135 | style::Print("Move To ->[√]<- Position")
136 | )?;
137 |
138 | thread::sleep(Duration::from_secs(3));
139 |
140 | execute!(w, cursor::RestorePosition, style::Print("√"))
141 | }
142 |
143 | /// Draws a box with an colored center, this center can be taken as a reference point after running the given cursor command.
144 | fn draw_cursor_box(w: &mut W, description: &str, cursor_command: F) -> std::io::Result<()>
145 | where
146 | W: Write,
147 | F: Fn(u16, u16) -> T,
148 | T: Command,
149 | {
150 | execute!(
151 | w,
152 | cursor::Hide,
153 | cursor::MoveTo(0, 0),
154 | style::SetForegroundColor(style::Color::Red),
155 | style::Print(format!(
156 | "Red box is the center. After the action: '{}' '√' is drawn to reflect the action from the center.",
157 | description
158 | ))
159 | )?;
160 |
161 | let start_y = 2;
162 | let width = 21;
163 | let height = 11 + start_y;
164 | let center_x = width / 2;
165 | let center_y = (height + start_y) / 2;
166 |
167 | for row in start_y..=10 + start_y {
168 | for column in 0..=width {
169 | if (row == start_y || row == height - 1) || (column == 0 || column == width) {
170 | queue!(
171 | w,
172 | cursor::MoveTo(column, row),
173 | style::PrintStyledContent("▓".red()),
174 | )?;
175 | } else {
176 | queue!(
177 | w,
178 | cursor::MoveTo(column, row),
179 | style::PrintStyledContent("_".red().on_white())
180 | )?;
181 | }
182 | }
183 | }
184 |
185 | queue!(
186 | w,
187 | cursor::MoveTo(center_x, center_y),
188 | style::PrintStyledContent("▀".red().on_white()),
189 | cursor::MoveTo(center_x, center_y),
190 | )?;
191 | queue!(
192 | w,
193 | cursor_command(center_x, center_y),
194 | style::PrintStyledContent("√".magenta().on_white())
195 | )?;
196 | w.flush()?;
197 | Ok(())
198 | }
199 |
200 | pub fn run(w: &mut W) -> std::io::Result<()>
201 | where
202 | W: Write,
203 | {
204 | run_tests!(
205 | w,
206 | test_hide_cursor,
207 | test_show_cursor,
208 | test_cursor_blinking_bar,
209 | test_cursor_blinking_block,
210 | test_cursor_blinking_underscore,
211 | test_move_cursor_left,
212 | test_move_cursor_right,
213 | test_move_cursor_up,
214 | test_move_cursor_down,
215 | test_move_cursor_to,
216 | test_move_cursor_to_next_line,
217 | test_move_cursor_to_previous_line,
218 | test_move_cursor_to_column,
219 | test_save_restore_cursor_position
220 | );
221 | Ok(())
222 | }
223 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test/event.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::cognitive_complexity)]
2 |
3 | use crossterm::{
4 | cursor::position,
5 | event::{read, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
6 | execute,
7 | };
8 | use std::io::{self, Write};
9 |
10 | fn test_event(w: &mut W) -> io::Result<()>
11 | where
12 | W: io::Write,
13 | {
14 | execute!(w, EnableMouseCapture)?;
15 |
16 | loop {
17 | // Blocking read
18 | let event = read()?;
19 |
20 | println!("Event::{:?}\r", event);
21 |
22 | if event == Event::Key(KeyCode::Char('c').into()) {
23 | println!("Cursor position: {:?}\r", position());
24 | }
25 |
26 | if event == Event::Key(KeyCode::Char('q').into()) {
27 | break;
28 | }
29 | }
30 |
31 | execute!(w, DisableMouseCapture)?;
32 |
33 | Ok(())
34 | }
35 |
36 | pub fn run(w: &mut W) -> std::io::Result<()>
37 | where
38 | W: Write,
39 | {
40 | run_tests!(w, test_event);
41 | Ok(())
42 | }
43 |
--------------------------------------------------------------------------------
/examples/interactive-demo/src/test/synchronized_output.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 |
3 | use crossterm::{cursor, execute, style::Print, SynchronizedUpdate};
4 |
5 | fn render_slowly(w: &mut W) -> std::io::Result<()>
6 | where
7 | W: Write,
8 | {
9 | for i in 1..10 {
10 | execute!(w, Print(format!("{}", i)))?;
11 | std::thread::sleep(std::time::Duration::from_millis(50));
12 | }
13 | Ok(())
14 | }
15 |
16 | fn test_slow_rendering(w: &mut W) -> std::io::Result<()>
17 | where
18 | W: Write,
19 | {
20 | execute!(w, Print("Rendering without synchronized update:"))?;
21 | execute!(w, cursor::MoveToNextLine(1))?;
22 | std::thread::sleep(std::time::Duration::from_millis(50));
23 | render_slowly(w)?;
24 |
25 | execute!(w, cursor::MoveToNextLine(1))?;
26 | execute!(w, Print("Rendering with synchronized update:"))?;
27 | execute!(w, cursor::MoveToNextLine(1))?;
28 | std::thread::sleep(std::time::Duration::from_millis(50));
29 | w.sync_update(render_slowly)??;
30 |
31 | execute!(w, cursor::MoveToNextLine(1))?;
32 | Ok(())
33 | }
34 |
35 | pub fn run(w: &mut W) -> std::io::Result<()>
36 | where
37 | W: Write,
38 | {
39 | run_tests!(w, test_slow_rendering,);
40 | Ok(())
41 | }
42 |
--------------------------------------------------------------------------------
/examples/is_tty.rs:
--------------------------------------------------------------------------------
1 | use crossterm::{
2 | execute,
3 | terminal::{size, SetSize},
4 | tty::IsTty,
5 | };
6 | use std::io::{stdin, stdout};
7 |
8 | pub fn main() {
9 | println!("size: {:?}", size().unwrap());
10 | execute!(stdout(), SetSize(10, 10)).unwrap();
11 | println!("resized: {:?}", size().unwrap());
12 |
13 | if stdin().is_tty() {
14 | println!("Is TTY");
15 | } else {
16 | println!("Is not TTY");
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/key-display.rs:
--------------------------------------------------------------------------------
1 | //! Demonstrates the display format of key events.
2 | //!
3 | //! This example demonstrates the display format of key events, which is useful for displaying in
4 | //! the help section of a terminal application.
5 | //!
6 | //! cargo run --example key-display
7 |
8 | use std::io;
9 |
10 | use crossterm::event::KeyModifiers;
11 | use crossterm::{
12 | event::{read, KeyCode},
13 | terminal::{disable_raw_mode, enable_raw_mode},
14 | };
15 |
16 | const HELP: &str = r#"Key display
17 | - Press any key to see its display format
18 | - Use Esc to quit
19 | "#;
20 |
21 | fn main() -> io::Result<()> {
22 | println!("{}", HELP);
23 | enable_raw_mode()?;
24 | if let Err(e) = print_events() {
25 | println!("Error: {:?}\r", e);
26 | }
27 | disable_raw_mode()?;
28 | Ok(())
29 | }
30 |
31 | fn print_events() -> io::Result<()> {
32 | while let Ok(event) = read() {
33 | let Some(event) = event.as_key_press_event() else {
34 | continue;
35 | };
36 | let modifier = match event.modifiers {
37 | KeyModifiers::NONE => "".to_string(),
38 | _ => format!("{:}+", event.modifiers),
39 | };
40 | println!("Key pressed: {modifier}{code}\r", code = event.code);
41 | if event.code == KeyCode::Esc {
42 | break;
43 | }
44 | }
45 | Ok(())
46 | }
47 |
--------------------------------------------------------------------------------
/examples/stderr.rs:
--------------------------------------------------------------------------------
1 | //! This shows how an application can write on stderr
2 | //! instead of stdout, thus making it possible to
3 | //! the command API instead of the "old style" direct
4 | //! unbuffered API.
5 | //!
6 | //! This particular example is only suited to Unix
7 | //! for now.
8 | //!
9 | //! cargo run --example stderr
10 |
11 | use std::io;
12 |
13 | use crossterm::{
14 | cursor::{Hide, MoveTo, Show},
15 | event,
16 | event::{Event, KeyCode, KeyEvent},
17 | execute, queue,
18 | style::Print,
19 | terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
20 | };
21 |
22 | const TEXT: &str = r#"
23 | This screen is ran on stderr.
24 | And when you hit enter, it prints on stdout.
25 | This makes it possible to run an application and choose what will
26 | be sent to any application calling yours.
27 |
28 | For example, assuming you build this example with
29 |
30 | cargo build --bin stderr
31 |
32 | and then you run it with
33 |
34 | cd "$(target/debug/stderr)"
35 |
36 | what the application prints on stdout is used as argument to cd.
37 |
38 | Try it out.
39 |
40 | Hit any key to quit this screen:
41 |
42 | 1 will print `..`
43 | 2 will print `/`
44 | 3 will print `~`
45 | Any other key will print this text (so that you may copy-paste)
46 | "#;
47 |
48 | fn run_app(write: &mut W) -> io::Result
49 | where
50 | W: io::Write,
51 | {
52 | queue!(
53 | write,
54 | EnterAlternateScreen, // enter alternate screen
55 | Hide // hide the cursor
56 | )?;
57 |
58 | let mut y = 1;
59 | for line in TEXT.split('\n') {
60 | queue!(write, MoveTo(1, y), Print(line.to_string()))?;
61 | y += 1;
62 | }
63 |
64 | write.flush()?;
65 |
66 | terminal::enable_raw_mode()?;
67 | let user_char = read_char()?; // we wait for the user to hit a key
68 | execute!(write, Show, LeaveAlternateScreen)?; // restore the cursor and leave the alternate screen
69 |
70 | terminal::disable_raw_mode()?;
71 |
72 | Ok(user_char)
73 | }
74 |
75 | pub fn read_char() -> io::Result {
76 | loop {
77 | if let Event::Key(KeyEvent {
78 | code: KeyCode::Char(c),
79 | ..
80 | }) = event::read()?
81 | {
82 | return Ok(c);
83 | }
84 | }
85 | }
86 |
87 | // cargo run --example stderr
88 | fn main() {
89 | match run_app(&mut io::stderr()).unwrap() {
90 | '1' => print!(".."),
91 | '2' => print!("/"),
92 | '3' => print!("~"),
93 | _ => println!("{}", TEXT),
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/ansi_support.rs:
--------------------------------------------------------------------------------
1 | use std::sync::atomic::{AtomicBool, Ordering};
2 |
3 | use crossterm_winapi::{ConsoleMode, Handle};
4 | use parking_lot::Once;
5 | use winapi::um::wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
6 |
7 | /// Enable virtual terminal processing.
8 | ///
9 | /// This method attempts to enable virtual terminal processing for this
10 | /// console. If there was a problem enabling it, then an error returned.
11 | /// On success, the caller may assume that enabling it was successful.
12 | ///
13 | /// When virtual terminal processing is enabled, characters emitted to the
14 | /// console are parsed for VT100 and similar control character sequences
15 | /// that control color and other similar operations.
16 | fn enable_vt_processing() -> std::io::Result<()> {
17 | let mask = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
18 |
19 | let console_mode = ConsoleMode::from(Handle::current_out_handle()?);
20 | let old_mode = console_mode.mode()?;
21 |
22 | if old_mode & mask == 0 {
23 | console_mode.set_mode(old_mode | mask)?;
24 | }
25 |
26 | Ok(())
27 | }
28 |
29 | static SUPPORTS_ANSI_ESCAPE_CODES: AtomicBool = AtomicBool::new(false);
30 | static INITIALIZER: Once = Once::new();
31 |
32 | /// Checks if the current terminal supports ANSI escape sequences
33 | pub fn supports_ansi() -> bool {
34 | INITIALIZER.call_once(|| {
35 | // Some terminals on Windows like GitBash can't use WinAPI calls directly
36 | // so when we try to enable the ANSI-flag for Windows this won't work.
37 | // Because of that we should check first if the TERM-variable is set
38 | // and see if the current terminal is a terminal who does support ANSI.
39 | let supported = enable_vt_processing().is_ok()
40 | || std::env::var("TERM").map_or(false, |term| term != "dumb");
41 |
42 | SUPPORTS_ANSI_ESCAPE_CODES.store(supported, Ordering::SeqCst);
43 | });
44 |
45 | SUPPORTS_ANSI_ESCAPE_CODES.load(Ordering::SeqCst)
46 | }
47 |
--------------------------------------------------------------------------------
/src/clipboard.rs:
--------------------------------------------------------------------------------
1 | //! # Clipboard
2 | //!
3 | //! The `clipboard` module provides functionality to work with a host clipboard.
4 | //!
5 | //! ## Implemented operations:
6 | //!
7 | //! - Copy: [`CopyToClipboard`](struct.CopyToClipboard.html)
8 | use base64::prelude::{Engine, BASE64_STANDARD};
9 |
10 | use std::fmt;
11 | use std::str::FromStr;
12 |
13 | use crate::{osc, Command};
14 |
15 | /// Different clipboard types
16 | ///
17 | /// Some operating systems and desktop environments support multiple buffers
18 | /// for copy/cut/paste. Their details differ between operating systems.
19 | /// See
20 | /// for a detailed survey of supported types based on the X window system.
21 | #[derive(Debug, Clone, PartialEq, Eq)]
22 | pub enum ClipboardType {
23 | /// Default clipboard when using Ctrl+C or Ctrl+V
24 | Clipboard,
25 |
26 | /// Clipboard on Linux/X/Wayland when using selection and middle mouse button
27 | Primary,
28 |
29 | /// Other clipboard type not explicitly supported by crossterm
30 | ///
31 | /// See
32 | /// [XTerm Control Sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands)
33 | /// for potential values.
34 | ///
35 | /// Note that support for these in terminal emulators is very limited.
36 | Other(char),
37 | }
38 |
39 | impl From<&ClipboardType> for char {
40 | fn from(val: &ClipboardType) -> Self {
41 | match val {
42 | ClipboardType::Clipboard => 'c',
43 | ClipboardType::Primary => 'p',
44 | ClipboardType::Other(other) => *other,
45 | }
46 | }
47 | }
48 |
49 | impl From for ClipboardType {
50 | fn from(value: char) -> Self {
51 | match value {
52 | 'c' => ClipboardType::Clipboard,
53 | 'p' => ClipboardType::Primary,
54 | other => ClipboardType::Other(other),
55 | }
56 | }
57 | }
58 |
59 | /// A sequence of clipboard types
60 | #[derive(Debug, Clone, PartialEq, Eq)]
61 | pub struct ClipboardSelection(
62 | /// An ordered list of clipboards which will be the destination for the copied selection.
63 | ///
64 | /// Order matters due to implementations deviating from the
65 | /// [XTerm Control Sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands)
66 | /// reference. Some terminal emulators may only interpret the first character of this
67 | /// parameter. For differences, see
68 | /// [`CopyToClipboard` (Terminal Support)](struct.CopyToClipboard.html#terminal-support).
69 | pub Vec,
70 | );
71 |
72 | impl ClipboardSelection {
73 | /// Returns a String corresponsing to the "Pc" parameter of the OSC52
74 | /// sequence.
75 | fn to_osc52_pc(&self) -> String {
76 | self.0.iter().map(Into::::into).collect()
77 | }
78 | }
79 |
80 | impl fmt::Display for ClipboardSelection {
81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 | f.write_str(&self.to_osc52_pc())
83 | }
84 | }
85 |
86 | impl FromStr for ClipboardSelection {
87 | type Err = ();
88 | fn from_str(s: &str) -> Result {
89 | Ok(ClipboardSelection(
90 | s.chars().map(From::::from).collect(),
91 | ))
92 | }
93 | }
94 |
95 | /// A command that copies to clipboard
96 | ///
97 | /// This command uses OSC control sequence `Pr = 5 2` (See
98 | /// [XTerm Control Sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands) )
99 | /// to copy data to the terminal host clipboard.
100 | ///
101 | /// This only works if it is enabled on the user's terminal emulator. If a terminal multiplexer
102 | /// is used, the multiplexer must support it, too.
103 | ///
104 | /// Commands must be executed/queued for execution otherwise they do nothing.
105 | ///
106 | /// # Examples
107 | ///
108 | /// ```no_run
109 | /// use crossterm::execute;
110 | /// use crossterm::clipboard::CopyToClipboard;
111 | /// // Copy foo to clipboard
112 | /// execute!(std::io::stdout(), CopyToClipboard::to_clipboard_from("foo"));
113 | /// // Copy bar to primary
114 | /// execute!(std::io::stdout(), CopyToClipboard::to_primary_from("bar"));
115 | /// ```
116 | ///
117 | /// See also examples/copy-to-clipboard.rs.
118 | ///
119 | /// # Terminal Support
120 | ///
121 | /// The following table shows what destinations are filled by different terminal emulators when
122 | /// asked to copy to different destination sequences.
123 | ///
124 | /// | Terminal (Version) | dest '' | dest 'c' | dest 'p' | dest 'cp' | dest'pc' |
125 | /// | --------------------- | --------- | --------- | -------- | ------------- | ------------- |
126 | /// | xterm (397) *3 | primary | clipboard | primary | clipb., prim. | clipb., prim. |
127 | /// | Alacritty (0.15.1) *3 | clipboard | clipboard | primary | clipb. | prim. |
128 | /// | Wezterm (*1) *3 | clipboard | clipboard | primary | clipb. | clipb. |
129 | /// | Konsole (24.12.3) *3 | clipboard | clipboard | primary | clipb., prim. | clipb., prim. |
130 | /// | Kitty (0.40.0) *3 | clipboard | clipboard | primary | clipb. | clipb. |
131 | /// | foot (1.20.2) *3 | clipboard | clipboard | primary | clipb., prim. | clipb., prim. |
132 | /// | tmux (3.5a) *2 *3 | primary | clipboard | primary | clipb., prim. | clipb., prim. |
133 | ///
134 | /// Asterisks:
135 | /// 1. 20240203-110809-5046fc22
136 | /// 2. set-clipboard set to [external](https://github.com/tmux/tmux/wiki/Clipboard#how-it-works),
137 | /// i.e. this is OSC52 pass-through.
138 | /// 3. This was tested on wayland with the
139 | /// [primary selection protocol](https://wayland.app/protocols/primary-selection-unstable-v1)
140 | /// enabled.
141 | #[derive(Debug, Clone, PartialEq, Eq)]
142 | pub struct CopyToClipboard {
143 | /// Content to be copied
144 | pub content: T,
145 | /// Sequence of copy destinations
146 | ///
147 | /// Not all sequences are equally supported by terminal emulators. See
148 | /// [`CopyToClipboard` (Terminal Support)](struct.CopyToClipboard.html#terminal-support).
149 | pub destination: ClipboardSelection,
150 | }
151 |
152 | impl> Command for CopyToClipboard {
153 | fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
154 | write!(
155 | f,
156 | osc!("52;{destination};{encoded_text}"),
157 | destination = self.destination.to_osc52_pc(),
158 | encoded_text = BASE64_STANDARD.encode(&self.content)
159 | )
160 | }
161 |
162 | #[cfg(windows)]
163 | fn execute_winapi(&self) -> std::io::Result<()> {
164 | use std::io;
165 |
166 | Err(io::Error::new(
167 | io::ErrorKind::Unsupported,
168 | "Copying is not implemented for the Windows API.",
169 | ))
170 | }
171 | }
172 |
173 | impl> CopyToClipboard {
174 | /// Construct a [`CopyToClipboard`] that writes content into the
175 | /// "clipboard" (or 'c') clipboard selection.
176 | ///
177 | /// # Example
178 | ///
179 | /// ```no_run
180 | /// use crossterm::{execute, Command};
181 | /// use crossterm::clipboard::CopyToClipboard;
182 | /// execute!(std::io::stdout(), CopyToClipboard::to_clipboard_from("foo"));
183 | /// ```
184 | pub fn to_clipboard_from(content: T) -> CopyToClipboard {
185 | CopyToClipboard {
186 | content,
187 | destination: ClipboardSelection(vec![ClipboardType::Clipboard]),
188 | }
189 | }
190 |
191 | /// Construct a [`CopyToClipboard`] that writes content into the "primary"
192 | /// (or 'p') clipboard selection.
193 | ///
194 | /// # Example
195 | ///
196 | /// ```no_run
197 | /// use crossterm::execute;
198 | /// use crossterm::clipboard::CopyToClipboard;
199 | /// execute!(std::io::stdout(), CopyToClipboard::to_primary_from("foo"));
200 | /// ```
201 | pub fn to_primary_from(content: T) -> CopyToClipboard {
202 | CopyToClipboard {
203 | content,
204 | destination: ClipboardSelection(vec![ClipboardType::Primary]),
205 | }
206 | }
207 | }
208 |
209 | #[cfg(test)]
210 | mod tests {
211 | use super::*;
212 |
213 | #[test]
214 | fn test_clipboard_string_to_selection() {
215 | assert_eq!(
216 | ClipboardSelection::from_str("p").unwrap(),
217 | ClipboardSelection(vec![ClipboardType::Primary])
218 | );
219 | assert_eq!(
220 | ClipboardSelection::from_str("").unwrap(),
221 | ClipboardSelection(vec![])
222 | );
223 | assert_eq!(
224 | ClipboardSelection::from_str("cp").unwrap(),
225 | ClipboardSelection(vec![ClipboardType::Clipboard, ClipboardType::Primary])
226 | );
227 | }
228 | #[test]
229 | fn test_clipboard_selection_to_osc52_pc() {
230 | assert_eq!(ClipboardSelection(vec![]).to_osc52_pc(), "");
231 | assert_eq!(
232 | ClipboardSelection(vec![ClipboardType::Clipboard]).to_osc52_pc(),
233 | "c"
234 | );
235 | assert_eq!(
236 | ClipboardSelection(vec![ClipboardType::Primary]).to_osc52_pc(),
237 | "p"
238 | );
239 | assert_eq!(
240 | ClipboardSelection(vec![ClipboardType::Primary, ClipboardType::Clipboard])
241 | .to_osc52_pc(),
242 | "pc"
243 | );
244 | assert_eq!(
245 | ClipboardSelection(vec![ClipboardType::Clipboard, ClipboardType::Primary])
246 | .to_osc52_pc(),
247 | "cp"
248 | );
249 | assert_eq!(
250 | ClipboardSelection(vec![ClipboardType::Other('s')]).to_osc52_pc(),
251 | "s"
252 | );
253 | }
254 |
255 | #[test]
256 | fn test_clipboard_copy_string_osc52() {
257 | let mut buffer = String::new();
258 | super::CopyToClipboard {
259 | content: "foo",
260 | destination: ClipboardSelection(vec![ClipboardType::Clipboard]),
261 | }
262 | .write_ansi(&mut buffer)
263 | .unwrap();
264 | assert_eq!(buffer, "\x1b]52;c;Zm9v\x1b\\");
265 |
266 | buffer.clear();
267 | super::CopyToClipboard {
268 | content: "foo",
269 | destination: ClipboardSelection(vec![ClipboardType::Primary]),
270 | }
271 | .write_ansi(&mut buffer)
272 | .unwrap();
273 | assert_eq!(buffer, "\x1b]52;p;Zm9v\x1b\\");
274 |
275 | buffer.clear();
276 | super::CopyToClipboard {
277 | content: "foo",
278 | destination: ClipboardSelection(vec![ClipboardType::Primary, ClipboardType::Clipboard]),
279 | }
280 | .write_ansi(&mut buffer)
281 | .unwrap();
282 | assert_eq!(buffer, "\x1b]52;pc;Zm9v\x1b\\");
283 |
284 | buffer.clear();
285 | super::CopyToClipboard {
286 | content: "foo",
287 | destination: ClipboardSelection(vec![]),
288 | }
289 | .write_ansi(&mut buffer)
290 | .unwrap();
291 | assert_eq!(buffer, "\x1b]52;;Zm9v\x1b\\");
292 | }
293 |
294 | #[test]
295 | fn test_clipboard_copy_string_osc52_constructor() {
296 | let mut buffer = String::new();
297 | super::CopyToClipboard::to_clipboard_from("foo")
298 | .write_ansi(&mut buffer)
299 | .unwrap();
300 | assert_eq!(buffer, "\x1b]52;c;Zm9v\x1b\\");
301 |
302 | let mut buffer = String::new();
303 | super::CopyToClipboard::to_primary_from("foo")
304 | .write_ansi(&mut buffer)
305 | .unwrap();
306 | assert_eq!(buffer, "\x1b]52;p;Zm9v\x1b\\");
307 | }
308 | }
309 |
--------------------------------------------------------------------------------
/src/cursor/sys.rs:
--------------------------------------------------------------------------------
1 | //! This module provides platform related functions.
2 |
3 | #[cfg(unix)]
4 | #[cfg(feature = "events")]
5 | pub use self::unix::position;
6 | #[cfg(windows)]
7 | #[cfg(feature = "events")]
8 | pub use self::windows::position;
9 | #[cfg(windows)]
10 | pub(crate) use self::windows::{
11 | move_down, move_left, move_right, move_to, move_to_column, move_to_next_line,
12 | move_to_previous_line, move_to_row, move_up, restore_position, save_position, show_cursor,
13 | };
14 |
15 | #[cfg(windows)]
16 | pub(crate) mod windows;
17 |
18 | #[cfg(unix)]
19 | #[cfg(feature = "events")]
20 | pub(crate) mod unix;
21 |
--------------------------------------------------------------------------------
/src/cursor/sys/unix.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | io::{self, Error, ErrorKind, Write},
3 | time::Duration,
4 | };
5 |
6 | use crate::{
7 | event::{filter::CursorPositionFilter, poll_internal, read_internal, InternalEvent},
8 | terminal::{disable_raw_mode, enable_raw_mode, sys::is_raw_mode_enabled},
9 | };
10 |
11 | /// Returns the cursor position (column, row).
12 | ///
13 | /// The top left cell is represented as `(0, 0)`.
14 | ///
15 | /// On unix systems, this function will block and possibly time out while
16 | /// [`crossterm::event::read`](crate::event::read) or [`crossterm::event::poll`](crate::event::poll) are being called.
17 | pub fn position() -> io::Result<(u16, u16)> {
18 | if is_raw_mode_enabled() {
19 | read_position_raw()
20 | } else {
21 | read_position()
22 | }
23 | }
24 |
25 | fn read_position() -> io::Result<(u16, u16)> {
26 | enable_raw_mode()?;
27 | let pos = read_position_raw();
28 | disable_raw_mode()?;
29 | pos
30 | }
31 |
32 | fn read_position_raw() -> io::Result<(u16, u16)> {
33 | // Use `ESC [ 6 n` to and retrieve the cursor position.
34 | let mut stdout = io::stdout();
35 | stdout.write_all(b"\x1B[6n")?;
36 | stdout.flush()?;
37 |
38 | loop {
39 | match poll_internal(Some(Duration::from_millis(2000)), &CursorPositionFilter) {
40 | Ok(true) => {
41 | if let Ok(InternalEvent::CursorPosition(x, y)) =
42 | read_internal(&CursorPositionFilter)
43 | {
44 | return Ok((x, y));
45 | }
46 | }
47 | Ok(false) => {
48 | return Err(Error::new(
49 | ErrorKind::Other,
50 | "The cursor position could not be read within a normal duration",
51 | ));
52 | }
53 | Err(_) => {}
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/cursor/sys/windows.rs:
--------------------------------------------------------------------------------
1 | //! WinAPI related logic to cursor manipulation.
2 |
3 | use std::convert::TryFrom;
4 | use std::io;
5 | use std::sync::atomic::{AtomicU64, Ordering};
6 |
7 | use crossterm_winapi::{result, Coord, Handle, HandleType, ScreenBuffer};
8 | use winapi::{
9 | shared::minwindef::{FALSE, TRUE},
10 | um::wincon::{SetConsoleCursorInfo, SetConsoleCursorPosition, CONSOLE_CURSOR_INFO, COORD},
11 | };
12 |
13 | /// The position of the cursor, written when you save the cursor's position.
14 | ///
15 | /// This is `u64::MAX` initially. Otherwise, it stores the cursor's x position bit-shifted left 16
16 | /// times or-ed with the cursor's y position, where both are `i16`s.
17 | static SAVED_CURSOR_POS: AtomicU64 = AtomicU64::new(u64::MAX);
18 |
19 | // The 'y' position of the cursor is not relative to the window but absolute to screen buffer.
20 | // We can calculate the relative cursor position by subtracting the top position of the terminal window from the y position.
21 | // This results in an 1-based coord zo subtract 1 to make cursor position 0-based.
22 | pub fn parse_relative_y(y: i16) -> std::io::Result {
23 | let window = ScreenBuffer::current()?.info()?;
24 |
25 | let window_size = window.terminal_window();
26 | let screen_size = window.terminal_size();
27 |
28 | if y <= screen_size.height {
29 | Ok(y)
30 | } else {
31 | Ok(y - window_size.top)
32 | }
33 | }
34 |
35 | /// Returns the cursor position (column, row).
36 | ///
37 | /// The top left cell is represented `0,0`.
38 | pub fn position() -> io::Result<(u16, u16)> {
39 | let cursor = ScreenBufferCursor::output()?;
40 | let mut position = cursor.position()?;
41 | // if position.y != 0 {
42 | position.y = parse_relative_y(position.y)?;
43 | // }
44 | Ok(position.into())
45 | }
46 |
47 | pub(crate) fn show_cursor(show_cursor: bool) -> std::io::Result<()> {
48 | ScreenBufferCursor::from(Handle::current_out_handle()?).set_visibility(show_cursor)
49 | }
50 |
51 | pub(crate) fn move_to(column: u16, row: u16) -> std::io::Result<()> {
52 | let cursor = ScreenBufferCursor::output()?;
53 | cursor.move_to(column as i16, row as i16)?;
54 | Ok(())
55 | }
56 |
57 | pub(crate) fn move_up(count: u16) -> std::io::Result<()> {
58 | let (column, row) = position()?;
59 | move_to(column, row - count)?;
60 | Ok(())
61 | }
62 |
63 | pub(crate) fn move_right(count: u16) -> std::io::Result<()> {
64 | let (column, row) = position()?;
65 | move_to(column + count, row)?;
66 | Ok(())
67 | }
68 |
69 | pub(crate) fn move_down(count: u16) -> std::io::Result<()> {
70 | let (column, row) = position()?;
71 | move_to(column, row + count)?;
72 | Ok(())
73 | }
74 |
75 | pub(crate) fn move_left(count: u16) -> std::io::Result<()> {
76 | let (column, row) = position()?;
77 | move_to(column - count, row)?;
78 | Ok(())
79 | }
80 |
81 | pub(crate) fn move_to_column(new_column: u16) -> std::io::Result<()> {
82 | let (_, row) = position()?;
83 | move_to(new_column, row)?;
84 | Ok(())
85 | }
86 |
87 | pub(crate) fn move_to_row(new_row: u16) -> std::io::Result<()> {
88 | let (col, _) = position()?;
89 | move_to(col, new_row)?;
90 | Ok(())
91 | }
92 |
93 | pub(crate) fn move_to_next_line(count: u16) -> std::io::Result<()> {
94 | let (_, row) = position()?;
95 | move_to(0, row + count)?;
96 | Ok(())
97 | }
98 |
99 | pub(crate) fn move_to_previous_line(count: u16) -> std::io::Result<()> {
100 | let (_, row) = position()?;
101 | move_to(0, row - count)?;
102 | Ok(())
103 | }
104 |
105 | pub(crate) fn save_position() -> std::io::Result<()> {
106 | ScreenBufferCursor::output()?.save_position()?;
107 | Ok(())
108 | }
109 |
110 | pub(crate) fn restore_position() -> std::io::Result<()> {
111 | ScreenBufferCursor::output()?.restore_position()?;
112 | Ok(())
113 | }
114 |
115 | /// WinAPI wrapper over terminal cursor behaviour.
116 | struct ScreenBufferCursor {
117 | screen_buffer: ScreenBuffer,
118 | }
119 |
120 | impl ScreenBufferCursor {
121 | fn output() -> std::io::Result {
122 | Ok(ScreenBufferCursor {
123 | screen_buffer: ScreenBuffer::from(Handle::new(HandleType::CurrentOutputHandle)?),
124 | })
125 | }
126 |
127 | fn position(&self) -> std::io::Result {
128 | Ok(self.screen_buffer.info()?.cursor_pos())
129 | }
130 |
131 | fn move_to(&self, x: i16, y: i16) -> std::io::Result<()> {
132 | if x < 0 {
133 | return Err(io::Error::new(
134 | io::ErrorKind::Other,
135 | format!("Argument Out of Range Exception when setting cursor position to X: {x}"),
136 | ));
137 | }
138 |
139 | if y < 0 {
140 | return Err(io::Error::new(
141 | io::ErrorKind::Other,
142 | format!("Argument Out of Range Exception when setting cursor position to Y: {y}"),
143 | ));
144 | }
145 |
146 | let position = COORD { X: x, Y: y };
147 |
148 | unsafe {
149 | if result(SetConsoleCursorPosition(
150 | **self.screen_buffer.handle(),
151 | position,
152 | ))
153 | .is_err()
154 | {
155 | return Err(io::Error::last_os_error());
156 | }
157 | }
158 | Ok(())
159 | }
160 |
161 | fn set_visibility(&self, visible: bool) -> std::io::Result<()> {
162 | let cursor_info = CONSOLE_CURSOR_INFO {
163 | dwSize: 100,
164 | bVisible: if visible { TRUE } else { FALSE },
165 | };
166 |
167 | unsafe {
168 | if result(SetConsoleCursorInfo(
169 | **self.screen_buffer.handle(),
170 | &cursor_info,
171 | ))
172 | .is_err()
173 | {
174 | return Err(io::Error::last_os_error());
175 | }
176 | }
177 | Ok(())
178 | }
179 |
180 | fn restore_position(&self) -> std::io::Result<()> {
181 | if let Ok(val) = u32::try_from(SAVED_CURSOR_POS.load(Ordering::Relaxed)) {
182 | let x = (val >> 16) as i16;
183 | let y = val as i16;
184 | self.move_to(x, y)?;
185 | }
186 |
187 | Ok(())
188 | }
189 |
190 | fn save_position(&self) -> std::io::Result<()> {
191 | let position = self.position()?;
192 |
193 | let upper = u32::from(position.x as u16) << 16;
194 | let lower = u32::from(position.y as u16);
195 | let bits = u64::from(upper | lower);
196 | SAVED_CURSOR_POS.store(bits, Ordering::Relaxed);
197 |
198 | Ok(())
199 | }
200 | }
201 |
202 | impl From for ScreenBufferCursor {
203 | fn from(handle: Handle) -> Self {
204 | ScreenBufferCursor {
205 | screen_buffer: ScreenBuffer::from(handle),
206 | }
207 | }
208 | }
209 |
210 | #[cfg(test)]
211 | mod tests {
212 | use super::{
213 | move_down, move_left, move_right, move_to, move_to_column, move_to_next_line,
214 | move_to_previous_line, move_to_row, move_up, position, restore_position, save_position,
215 | };
216 | use crate::terminal::sys::temp_screen_buffer;
217 | use serial_test::serial;
218 |
219 | #[test]
220 | #[serial]
221 | fn test_move_to_winapi() {
222 | let _test_screen = temp_screen_buffer().unwrap();
223 |
224 | let (saved_x, saved_y) = position().unwrap();
225 |
226 | move_to(saved_x + 1, saved_y + 1).unwrap();
227 | assert_eq!(position().unwrap(), (saved_x + 1, saved_y + 1));
228 |
229 | move_to(saved_x, saved_y).unwrap();
230 | assert_eq!(position().unwrap(), (saved_x, saved_y));
231 | }
232 |
233 | #[test]
234 | #[serial]
235 | fn test_move_right_winapi() {
236 | let _test_screen = temp_screen_buffer().unwrap();
237 |
238 | let (saved_x, saved_y) = position().unwrap();
239 | move_right(1).unwrap();
240 | assert_eq!(position().unwrap(), (saved_x + 1, saved_y));
241 | }
242 |
243 | #[test]
244 | #[serial]
245 | fn test_move_left_winapi() {
246 | let _test_screen = temp_screen_buffer().unwrap();
247 |
248 | move_to(2, 0).unwrap();
249 |
250 | move_left(2).unwrap();
251 |
252 | assert_eq!(position().unwrap(), (0, 0));
253 | }
254 |
255 | #[test]
256 | #[serial]
257 | fn test_move_up_winapi() {
258 | let _test_screen = temp_screen_buffer().unwrap();
259 |
260 | move_to(0, 2).unwrap();
261 |
262 | move_up(2).unwrap();
263 |
264 | assert_eq!(position().unwrap(), (0, 0));
265 | }
266 |
267 | #[test]
268 | #[serial]
269 | fn test_move_to_next_line_winapi() {
270 | let _test_screen = temp_screen_buffer().unwrap();
271 |
272 | move_to(0, 2).unwrap();
273 |
274 | move_to_next_line(2).unwrap();
275 |
276 | assert_eq!(position().unwrap(), (0, 4));
277 | }
278 |
279 | #[test]
280 | #[serial]
281 | fn test_move_to_previous_line_winapi() {
282 | let _test_screen = temp_screen_buffer().unwrap();
283 |
284 | move_to(0, 2).unwrap();
285 |
286 | move_to_previous_line(2).unwrap();
287 |
288 | assert_eq!(position().unwrap(), (0, 0));
289 | }
290 |
291 | #[test]
292 | #[serial]
293 | fn test_move_to_column_winapi() {
294 | let _test_screen = temp_screen_buffer().unwrap();
295 |
296 | move_to(0, 2).unwrap();
297 |
298 | move_to_column(12).unwrap();
299 |
300 | assert_eq!(position().unwrap(), (12, 2));
301 | }
302 |
303 | #[test]
304 | #[serial]
305 | fn test_move_to_row_winapi() {
306 | let _test_screen = temp_screen_buffer().unwrap();
307 |
308 | move_to(0, 2).unwrap();
309 |
310 | move_to_row(5).unwrap();
311 |
312 | assert_eq!(position().unwrap(), (0, 5));
313 | }
314 |
315 | #[test]
316 | #[serial]
317 | fn test_move_down_winapi() {
318 | let _test_screen = temp_screen_buffer().unwrap();
319 |
320 | move_to(0, 0).unwrap();
321 |
322 | move_down(2).unwrap();
323 |
324 | assert_eq!(position().unwrap(), (0, 2));
325 | }
326 |
327 | #[test]
328 | #[serial]
329 | fn test_save_restore_position_winapi() {
330 | let _test_screen = temp_screen_buffer().unwrap();
331 |
332 | let (saved_x, saved_y) = position().unwrap();
333 |
334 | save_position().unwrap();
335 | move_to(saved_x + 1, saved_y + 1).unwrap();
336 | restore_position().unwrap();
337 |
338 | let (x, y) = position().unwrap();
339 |
340 | assert_eq!(x, saved_x);
341 | assert_eq!(y, saved_y);
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/src/event/filter.rs:
--------------------------------------------------------------------------------
1 | use crate::event::InternalEvent;
2 |
3 | /// Interface for filtering an `InternalEvent`.
4 | pub(crate) trait Filter: Send + Sync + 'static {
5 | /// Returns whether the given event fulfills the filter.
6 | fn eval(&self, event: &InternalEvent) -> bool;
7 | }
8 |
9 | #[cfg(unix)]
10 | #[derive(Debug, Clone)]
11 | pub(crate) struct CursorPositionFilter;
12 |
13 | #[cfg(unix)]
14 | impl Filter for CursorPositionFilter {
15 | fn eval(&self, event: &InternalEvent) -> bool {
16 | matches!(*event, InternalEvent::CursorPosition(_, _))
17 | }
18 | }
19 |
20 | #[cfg(unix)]
21 | #[derive(Debug, Clone)]
22 | pub(crate) struct KeyboardEnhancementFlagsFilter;
23 |
24 | #[cfg(unix)]
25 | impl Filter for KeyboardEnhancementFlagsFilter {
26 | fn eval(&self, event: &InternalEvent) -> bool {
27 | // This filter checks for either a KeyboardEnhancementFlags response or
28 | // a PrimaryDeviceAttributes response. If we receive the PrimaryDeviceAttributes
29 | // response but not KeyboardEnhancementFlags, the terminal does not support
30 | // progressive keyboard enhancement.
31 | matches!(
32 | *event,
33 | InternalEvent::KeyboardEnhancementFlags(_) | InternalEvent::PrimaryDeviceAttributes
34 | )
35 | }
36 | }
37 |
38 | #[cfg(unix)]
39 | #[derive(Debug, Clone)]
40 | pub(crate) struct PrimaryDeviceAttributesFilter;
41 |
42 | #[cfg(unix)]
43 | impl Filter for PrimaryDeviceAttributesFilter {
44 | fn eval(&self, event: &InternalEvent) -> bool {
45 | matches!(*event, InternalEvent::PrimaryDeviceAttributes)
46 | }
47 | }
48 |
49 | #[derive(Debug, Clone)]
50 | pub(crate) struct EventFilter;
51 |
52 | impl Filter for EventFilter {
53 | #[cfg(unix)]
54 | fn eval(&self, event: &InternalEvent) -> bool {
55 | matches!(*event, InternalEvent::Event(_))
56 | }
57 |
58 | #[cfg(windows)]
59 | fn eval(&self, _: &InternalEvent) -> bool {
60 | true
61 | }
62 | }
63 |
64 | #[cfg(test)]
65 | #[cfg(unix)]
66 | mod tests {
67 | use super::{
68 | super::Event, CursorPositionFilter, EventFilter, Filter, InternalEvent,
69 | KeyboardEnhancementFlagsFilter, PrimaryDeviceAttributesFilter,
70 | };
71 |
72 | #[derive(Debug, Clone)]
73 | pub(crate) struct InternalEventFilter;
74 |
75 | impl Filter for InternalEventFilter {
76 | fn eval(&self, _: &InternalEvent) -> bool {
77 | true
78 | }
79 | }
80 |
81 | #[test]
82 | fn test_cursor_position_filter_filters_cursor_position() {
83 | assert!(!CursorPositionFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
84 | assert!(CursorPositionFilter.eval(&InternalEvent::CursorPosition(0, 0)));
85 | }
86 |
87 | #[test]
88 | fn test_keyboard_enhancement_status_filter_filters_keyboard_enhancement_status() {
89 | assert!(!KeyboardEnhancementFlagsFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
90 | assert!(
91 | KeyboardEnhancementFlagsFilter.eval(&InternalEvent::KeyboardEnhancementFlags(
92 | crate::event::KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
93 | ))
94 | );
95 | assert!(KeyboardEnhancementFlagsFilter.eval(&InternalEvent::PrimaryDeviceAttributes));
96 | }
97 |
98 | #[test]
99 | fn test_primary_device_attributes_filter_filters_primary_device_attributes() {
100 | assert!(!PrimaryDeviceAttributesFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
101 | assert!(PrimaryDeviceAttributesFilter.eval(&InternalEvent::PrimaryDeviceAttributes));
102 | }
103 |
104 | #[test]
105 | fn test_event_filter_filters_events() {
106 | assert!(EventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
107 | assert!(!EventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
108 | }
109 |
110 | #[test]
111 | fn test_event_filter_filters_internal_events() {
112 | assert!(InternalEventFilter.eval(&InternalEvent::Event(Event::Resize(10, 10))));
113 | assert!(InternalEventFilter.eval(&InternalEvent::CursorPosition(0, 0)));
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/event/source.rs:
--------------------------------------------------------------------------------
1 | use std::{io, time::Duration};
2 |
3 | #[cfg(feature = "event-stream")]
4 | use super::sys::Waker;
5 | use super::InternalEvent;
6 |
7 | #[cfg(unix)]
8 | pub(crate) mod unix;
9 | #[cfg(windows)]
10 | pub(crate) mod windows;
11 |
12 | /// An interface for trying to read an `InternalEvent` within an optional `Duration`.
13 | pub(crate) trait EventSource: Sync + Send {
14 | /// Tries to read an `InternalEvent` within the given duration.
15 | ///
16 | /// # Arguments
17 | ///
18 | /// * `timeout` - `None` block indefinitely until an event is available, `Some(duration)` blocks
19 | /// for the given timeout
20 | ///
21 | /// Returns `Ok(None)` if there's no event available and timeout expires.
22 | fn try_read(&mut self, timeout: Option) -> io::Result