├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── attribute_test.rs ├── color.rs ├── input.rs ├── macros_screen.rs ├── macros_term.rs ├── raw_input.rs ├── screen.rs ├── screen_test.rs ├── screen_threads.rs ├── signals.rs ├── style.rs ├── terminal.rs ├── threads.rs └── unix_open.rs └── src ├── buffer.rs ├── lib.rs ├── macros.rs ├── priv_util.rs ├── screen.rs ├── sequence.rs ├── signal.rs ├── terminal.rs ├── unix ├── ext.rs ├── mod.rs ├── screen.rs └── terminal.rs ├── util.rs └── windows ├── ext.rs ├── mod.rs ├── screen.rs └── terminal.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.rs' 7 | - '**.toml' 8 | - '.github/workflows/ci.yml' 9 | push: 10 | branches: [master] 11 | paths: 12 | - '**.rs' 13 | - '**.toml' 14 | - '.github/workflows/ci.yml' 15 | 16 | jobs: 17 | Test: 18 | strategy: 19 | fail-fast: false 20 | 21 | matrix: 22 | platform: 23 | - { target: x86_64-pc-windows-gnu, os: windows-latest } 24 | - { target: x86_64-pc-windows-msvc, os: windows-latest } 25 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest } 26 | - { target: x86_64-apple-darwin, os: macos-latest } 27 | 28 | runs-on: ${{ matrix.platform.os }} 29 | 30 | steps: 31 | - uses: actions/checkout@v1 32 | - uses: hecrj/setup-rust-action@v1 33 | with: 34 | rust-version: stable-${{ matrix.platform.target }} 35 | 36 | - name: Build 37 | run: cargo build --verbose 38 | 39 | - name: Test 40 | run: cargo test --verbose 41 | 42 | env: 43 | RUST_BACKTRACE: 1 44 | RUST_INCREMENTAL: 0 45 | RUSTFLAGS: "-C debuginfo=0" 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mortal" 3 | version = "0.2.4" 4 | authors = ["Murarth "] 5 | edition = "2018" 6 | 7 | description = "Cross-platform terminal interface" 8 | 9 | documentation = "https://docs.rs/mortal/" 10 | homepage = "https://github.com/murarth/mortal" 11 | repository = "https://github.com/murarth/mortal" 12 | 13 | categories = ["command-line-interface"] 14 | keywords = ["cli", "console", "screen", "term", "terminal"] 15 | license = "MIT/Apache-2.0" 16 | readme = "README.md" 17 | 18 | [dependencies] 19 | bitflags = "2.0" 20 | smallstr = "0.2" 21 | unicode-normalization = "0.1" 22 | unicode-width = "0.1" 23 | 24 | [target.'cfg(unix)'.dependencies] 25 | libc = "0.2" 26 | nix = { version = "0.26", default-features = false, features = ["poll", "signal", "term"] } 27 | terminfo = "0.8" 28 | 29 | [target.'cfg(windows)'.dependencies] 30 | winapi = { version = "0.3", features = [ 31 | "consoleapi", "handleapi", "minwindef", "ntdef", "processenv", "synchapi", 32 | "winbase", "wincon", "winerror", "winnt", "winuser" ] } 33 | 34 | [dev-dependencies] 35 | rand = "0.8" 36 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Murarth 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mortal 2 | 3 | Concurrent cross-platform terminal interface, 4 | for Unix terminals and Windows console. 5 | 6 | Provides a line-by-line terminal interface and a screen buffer interface. 7 | 8 | [Documentation](https://docs.rs/mortal/) 9 | 10 | ## Building 11 | 12 | To include `mortal` in your project, add the following to your `Cargo.toml`: 13 | 14 | ```toml 15 | [dependencies] 16 | mortal = "0.2" 17 | ``` 18 | 19 | And the following to your crate root: 20 | 21 | ```rust 22 | extern crate mortal; 23 | ``` 24 | 25 | ## License 26 | 27 | mortal is distributed under the terms of both the MIT license and the 28 | Apache License (Version 2.0). 29 | 30 | See LICENSE-APACHE and LICENSE-MIT for details. 31 | -------------------------------------------------------------------------------- /examples/attribute_test.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | 3 | use std::io; 4 | 5 | use mortal::{Color, Style, Terminal}; 6 | 7 | fn main() -> io::Result<()> { 8 | let term = Terminal::new()?; 9 | 10 | write!(term, "plain")?; 11 | 12 | term.set_fg(Color::Red)?; 13 | write!(term, " add red fg")?; 14 | 15 | term.set_bg(Color::Blue)?; 16 | write!(term, " add blue bg")?; 17 | 18 | term.add_style(Style::BOLD)?; 19 | write!(term, " add bold")?; 20 | 21 | term.add_style(Style::REVERSE)?; 22 | write!(term, " add reverse")?; 23 | 24 | term.add_style(Style::UNDERLINE)?; 25 | write!(term, " add underline")?; 26 | 27 | term.add_style(Style::ITALIC)?; 28 | write!(term, " add italic")?; 29 | 30 | term.remove_style(Style::REVERSE)?; 31 | write!(term, " remove reverse")?; 32 | 33 | term.set_fg(Color::Magenta)?; 34 | write!(term, " add magenta fg")?; 35 | 36 | term.set_bg(Color::Green)?; 37 | write!(term, " add green bg")?; 38 | 39 | term.set_fg(None)?; 40 | write!(term, " reset fg")?; 41 | 42 | term.remove_style(Style::UNDERLINE)?; 43 | write!(term, " remove underline")?; 44 | 45 | term.remove_style(Style::BOLD)?; 46 | write!(term, " remove bold")?; 47 | 48 | term.set_bg(None)?; 49 | write!(term, " reset bg")?; 50 | 51 | term.remove_style(Style::ITALIC)?; 52 | write!(term, " remove italic")?; 53 | 54 | write!(term, "\n") 55 | } 56 | -------------------------------------------------------------------------------- /examples/color.rs: -------------------------------------------------------------------------------- 1 | //! Example usage of color/styled terminal output 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Color, Style, Terminal}; 8 | 9 | fn main() -> io::Result<()> { 10 | let term = Terminal::new()?; 11 | 12 | // There are two ways to write color/styled text to the terminal. 13 | 14 | // 1. Set style/color methods and write text. 15 | term.bold()?; 16 | term.set_fg(Color::Red)?; 17 | write!(term, "error")?; 18 | 19 | // Remember to clear attributes when you want to write plain text again. 20 | term.clear_attributes()?; 21 | writeln!(term, ": error message")?; 22 | 23 | // 2. Use the `write_styled` method to write color/styled text. 24 | term.write_styled(Color::Green, None, Style::BOLD, "help")?; 25 | // After `write`, all attributes are cleared and text is plain. 26 | writeln!(term, ": help message")?; 27 | 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /examples/input.rs: -------------------------------------------------------------------------------- 1 | //! Example of reading input events from the terminal 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Event, Key, PrepareConfig, Terminal}; 8 | 9 | fn main() -> io::Result<()> { 10 | let term = Terminal::new()?; 11 | 12 | // Prepare to read from the terminal. 13 | let state = term.prepare(PrepareConfig{ 14 | enable_mouse: true, 15 | .. Default::default() 16 | })?; 17 | 18 | println!("Reading input. Press 'q' to stop."); 19 | 20 | // Read input from the terminal, one key at a time. 21 | loop { 22 | if let Some(ev) = term.read_event(None)? { 23 | if let Event::NoEvent = ev { 24 | continue; 25 | } 26 | 27 | println!("read event: {:?}", ev); 28 | 29 | if let Event::Key(Key::Char('q')) = ev { 30 | break; 31 | } 32 | } 33 | } 34 | 35 | // Restore terminal settings. 36 | term.restore(state)?; 37 | 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /examples/macros_screen.rs: -------------------------------------------------------------------------------- 1 | //! Example of printing to the screen via macros 2 | 3 | #[macro_use] extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Color, Style, Theme, Screen, Event, Key}; 8 | 9 | pub fn main() -> io::Result<()> { 10 | let screen = Screen::new(Default::default())?; 11 | 12 | let razzle_dazzle = Color::Red; 13 | let flooby_doo = Color::Blue; 14 | let zamabamafoo = Style::BOLD; 15 | let glitter_exploding = Theme::new(razzle_dazzle, flooby_doo, zamabamafoo); 16 | 17 | term_writeln!(screen, "Press 'q' to exit."); 18 | 19 | term_writeln!(screen); 20 | 21 | term_writeln!(screen, 22 | [black] "black " 23 | [blue] "blue " 24 | [cyan] "cyan " 25 | [green] "green " 26 | [magenta] "magenta " 27 | [red] "red " 28 | [white] "white " 29 | [yellow] "yellow" 30 | [reset]); 31 | 32 | term_writeln!(screen, 33 | [#black] "black " 34 | [#blue] "blue " 35 | [#cyan] "cyan " 36 | [#green] "green " 37 | [#magenta] "magenta " 38 | [#red] "red " 39 | [#white] "white " 40 | [#yellow] "yellow" 41 | [reset]); 42 | 43 | term_writeln!(screen, 44 | [bold] "bold " [!bold] 45 | [italic] "italic " [!italic] 46 | [reverse] "reverse " [!reverse] 47 | [underline] "underline" [!underline]); 48 | 49 | term_writeln!(screen, 50 | [fg=razzle_dazzle] "razzle dazzle " [!fg] 51 | [bg=flooby_doo] "flooby doo " [!bg] 52 | [style=zamabamafoo] "zamabamafoo!\n" [!style] 53 | [theme=glitter_exploding] "Like glitter is exploding inside me!" [reset]); 54 | 55 | term_writeln!(screen, 56 | ("foo {}", 42) " " 57 | (: "bar") " " 58 | (? "baz") " " 59 | "quux"); 60 | 61 | screen.refresh()?; 62 | 63 | loop { 64 | if let Some(Event::Key(Key::Char('q'))) = screen.read_event(None)? { 65 | break; 66 | } 67 | } 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /examples/macros_term.rs: -------------------------------------------------------------------------------- 1 | //! Example of printing to the terminal via macros 2 | 3 | #[macro_use] extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Color, Style, Theme, Terminal}; 8 | 9 | pub fn main() -> io::Result<()> { 10 | let term = Terminal::new()?; 11 | 12 | let razzle_dazzle = Color::Red; 13 | let flooby_doo = Color::Blue; 14 | let zamabamafoo = Style::BOLD; 15 | let glitter_exploding = Theme::new(razzle_dazzle, flooby_doo, zamabamafoo); 16 | 17 | term_writeln!(term)?; 18 | 19 | term_writeln!(term, 20 | [black] "black " 21 | [blue] "blue " 22 | [cyan] "cyan " 23 | [green] "green " 24 | [magenta] "magenta " 25 | [red] "red " 26 | [white] "white " 27 | [yellow] "yellow" 28 | [reset])?; 29 | 30 | term_writeln!(term, 31 | [#black] "black " 32 | [#blue] "blue " 33 | [#cyan] "cyan " 34 | [#green] "green " 35 | [#magenta] "magenta " 36 | [#red] "red " 37 | [#white] "white " 38 | [#yellow] "yellow" 39 | [reset])?; 40 | 41 | term_writeln!(term, 42 | [bold] "bold " [!bold] 43 | [italic] "italic " [!italic] 44 | [reverse] "reverse " [!reverse] 45 | [underline] "underline" [!underline])?; 46 | 47 | term_writeln!(term, 48 | [fg=razzle_dazzle] "razzle dazzle " [!fg] 49 | [bg=flooby_doo] "flooby doo " [!bg] 50 | [style=zamabamafoo] "zamabamafoo!\n" [!style] 51 | [theme=glitter_exploding] "Like glitter is exploding inside me!" [reset])?; 52 | 53 | term_writeln!(term, 54 | ("foo {}", 42) " " 55 | (: "bar") " " 56 | (? "baz") " " 57 | "quux")?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /examples/raw_input.rs: -------------------------------------------------------------------------------- 1 | //! Example of reading raw input from the terminal 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Event, Terminal}; 8 | 9 | #[cfg(unix)] 10 | use mortal::unix::TerminalExt; 11 | 12 | #[cfg(windows)] 13 | use mortal::windows::TerminalExt; 14 | 15 | #[cfg(unix)] 16 | type RawUnit = u8; 17 | 18 | #[cfg(windows)] 19 | type RawUnit = u16; 20 | 21 | fn main() -> io::Result<()> { 22 | let mut term = Terminal::new()?; 23 | 24 | // Prepare to read from the terminal. 25 | let state = term.prepare(Default::default())?; 26 | 27 | println!("Reading input. Press 'q' to stop."); 28 | 29 | let mut buf = [0; 32]; 30 | 31 | // Read raw input data from the terminal in native encoding. 32 | loop { 33 | if let Some(ev) = term.read_raw(&mut buf, None)? { 34 | 35 | match ev { 36 | Event::Raw(n) => { 37 | println!("read {}: {:?}", n, &buf[..n]); 38 | 39 | if n == 1 && buf[0] == b'q' as RawUnit { 40 | break; 41 | } 42 | } 43 | ev => { 44 | println!("read event: {:?}", ev); 45 | } 46 | } 47 | } 48 | } 49 | 50 | // Restore terminal settings. 51 | term.restore(state)?; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /examples/screen.rs: -------------------------------------------------------------------------------- 1 | //! Example showing Screen usage 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Color, Event, Key, PrepareConfig, Screen, Style}; 8 | 9 | fn main() -> io::Result<()> { 10 | let screen = Screen::new(PrepareConfig{ 11 | enable_keypad: true, 12 | enable_mouse: true, 13 | .. PrepareConfig::default() 14 | })?; 15 | 16 | draw_screen(&screen); 17 | screen.refresh()?; 18 | 19 | loop { 20 | if let Some(ev) = screen.read_event(None)? { 21 | if let Event::NoEvent = ev { 22 | continue; 23 | } 24 | 25 | if let Event::Key(Key::Char('q')) = ev { 26 | break; 27 | } 28 | 29 | draw_screen(&screen); 30 | screen.set_cursor((4, 0)); 31 | write!(screen, "{:#?}", ev); 32 | 33 | screen.refresh()?; 34 | } 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | fn draw_screen(screen: &Screen) { 41 | screen.clear_screen(); 42 | 43 | screen.write_at((0, 0), "Reading input. Press 'q' to stop."); 44 | 45 | screen.write_styled_at((2, 5), 46 | Color::Red, None, Style::BOLD, "Hello, world!"); 47 | } 48 | -------------------------------------------------------------------------------- /examples/screen_test.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | 3 | use std::io; 4 | 5 | use mortal::{Event, Key, Screen}; 6 | 7 | fn main() -> io::Result<()> { 8 | let screen = Screen::new(Default::default())?; 9 | 10 | let size = screen.size(); 11 | 12 | if size.lines < 10 || size.columns < 20 { 13 | drop(screen); 14 | eprintln!("screen is too small"); 15 | return Ok(()); 16 | } 17 | 18 | writeln!(screen, "Testing Screen drawing"); 19 | writeln!(screen, "Press 'q' to quit"); 20 | 21 | screen.set_cursor((3, 0)); 22 | writeln!(screen, "Full Width"); 23 | 24 | screen.set_cursor((5, size.columns - 4)); 25 | writeln!(screen, "wrapping text"); 26 | 27 | screen.set_cursor((7, size.columns - 15)); 28 | writeln!(screen, "Wrapping full width"); 29 | 30 | screen.set_cursor((9, 0)); 31 | writeln!(screen, "Interrupted full width"); 32 | 33 | screen.set_cursor((9, 15)); 34 | write!(screen, "xxxx"); 35 | 36 | screen.set_cursor((0, 0)); 37 | screen.refresh()?; 38 | 39 | loop { 40 | let ev = screen.read_event(None)?; 41 | 42 | if let Some(Event::Key(Key::Char('q'))) = ev { 43 | break; 44 | } 45 | } 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /examples/screen_threads.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | extern crate rand; 3 | 4 | use std::io; 5 | use std::sync::Arc; 6 | use std::sync::mpsc::{sync_channel, SyncSender, TrySendError}; 7 | use std::thread::{spawn, sleep}; 8 | use std::time::Duration; 9 | 10 | use mortal::{Color, Cursor, Event, Key, PrepareConfig, Screen}; 11 | 12 | use rand::{Rng, seq::SliceRandom, thread_rng}; 13 | 14 | // A unique color for each thread 15 | const COLORS: &[Color] = &[ 16 | Color::Blue, 17 | Color::Red, 18 | Color::Green, 19 | Color::Cyan, 20 | Color::Magenta, 21 | ]; 22 | 23 | fn main() -> io::Result<()> { 24 | // Wrapping the Screen in an Arc allows us 25 | // to share ownership with multiple threads. 26 | let screen = Arc::new(Screen::new(PrepareConfig{ 27 | block_signals: true, 28 | .. PrepareConfig::default() 29 | })?); 30 | 31 | // Join handles for spawned threads 32 | let mut handles = Vec::new(); 33 | // We use a "rendezvous" SyncChannel to signal termination to threads. 34 | // The main thread holds the reader and, when it is dropped, all threads 35 | // will exit. 36 | let (shutdown_tx, shutdown_rx) = sync_channel(0); 37 | 38 | // Give a random color to each thread 39 | let mut colors = COLORS.to_vec(); 40 | colors.shuffle(&mut thread_rng()); 41 | 42 | screen.write_at((0, 0), "Running threads. Press 'q' to stop."); 43 | screen.set_cursor((0, 0)); 44 | screen.refresh()?; 45 | 46 | for i in 0..5 { 47 | let name = format!("child{}", i); 48 | let screen = screen.clone(); 49 | let color = colors[i]; 50 | let line = 2 + i * 2; 51 | let chan = shutdown_tx.clone(); 52 | 53 | let handle = spawn(move || { 54 | run_task(&name, line, color, &screen, &chan).unwrap() 55 | }); 56 | 57 | handles.push(handle); 58 | } 59 | 60 | // Only one thread will be reading events, 61 | // so we can hold the read lock for the duration of the program. 62 | let mut read = screen.lock_read().unwrap(); 63 | 64 | loop { 65 | let ev = read.read_event(None)?; 66 | 67 | if let Some(Event::Key(Key::Char('q'))) = ev { 68 | break; 69 | } 70 | } 71 | 72 | // Signal threads to shutdown 73 | drop(shutdown_rx); 74 | 75 | for handle in handles { 76 | handle.join().unwrap(); 77 | } 78 | 79 | Ok(()) 80 | } 81 | 82 | fn run_task(name: &str, line: usize, color: Color, screen: &Screen, 83 | sender: &SyncSender<()>) -> io::Result<()> { 84 | let mut rng = thread_rng(); 85 | 86 | loop { 87 | // Check whether the main thread is signalling an exit 88 | match sender.try_send(()) { 89 | Err(TrySendError::Disconnected(_)) => break, 90 | _ => () 91 | } 92 | 93 | sleep(Duration::from_millis(rng.gen_range(300..500))); 94 | 95 | // Hold the lock while we perform a few different write operations. 96 | // This ensures that no other thread's output will interrupt ours. 97 | let mut lock = screen.lock_write().unwrap(); 98 | 99 | lock.set_cursor(Cursor{line, column: 0}); 100 | 101 | lock.write_str("["); 102 | lock.bold(); 103 | lock.set_fg(color); 104 | lock.write_str(name); 105 | lock.clear_attributes(); 106 | writeln!(lock, "]: random output: {:>3}", rng.gen::()); 107 | 108 | // Set the cursor back to the starting point. 109 | // Without this, the cursor would jump around the screen after each write. 110 | lock.set_cursor((0, 0)); 111 | lock.refresh()?; 112 | 113 | // The lock is dropped at the end of the loop, 114 | // giving other threads a chance to grab it. 115 | } 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /examples/signals.rs: -------------------------------------------------------------------------------- 1 | //! Example of reading input from the terminal while allowing signals 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Event, Key, PrepareConfig, SignalSet, Terminal}; 8 | 9 | fn main() -> io::Result<()> { 10 | let term = Terminal::new()?; 11 | 12 | // Prepare to read from the terminal. 13 | let state = term.prepare(PrepareConfig{ 14 | block_signals: false, 15 | report_signals: SignalSet::all(), 16 | .. PrepareConfig::default() 17 | })?; 18 | 19 | println!("Reading input. Press 'q' to stop."); 20 | 21 | // Read input from the terminal, one key at a time. 22 | loop { 23 | if let Some(ev) = term.read_event(None)? { 24 | if let Event::NoEvent = ev { 25 | continue; 26 | } 27 | 28 | println!("read event: {:?}", ev); 29 | 30 | if let Event::Key(Key::Char('q')) = ev { 31 | break; 32 | } 33 | } 34 | } 35 | 36 | // Restore terminal settings. 37 | term.restore(state)?; 38 | 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /examples/style.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | 3 | use std::io; 4 | 5 | use mortal::{Style, Terminal}; 6 | 7 | fn main() -> io::Result<()> { 8 | let term = Terminal::new()?; 9 | 10 | term.write_styled(None, None, None, 11 | "normal text\n")?; 12 | 13 | term.write_styled(None, None, Style::BOLD, 14 | "bold text\n")?; 15 | 16 | term.write_styled(None, None, Style::ITALIC, 17 | "italic text\n")?; 18 | 19 | term.write_styled(None, None, Style::REVERSE, 20 | "reverse text\n")?; 21 | 22 | term.write_styled(None, None, Style::UNDERLINE, 23 | "underlined text\n")?; 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /examples/terminal.rs: -------------------------------------------------------------------------------- 1 | //! Example using cursor movement in Terminal mode 2 | 3 | extern crate mortal; 4 | 5 | use std::io; 6 | 7 | use mortal::{Event, Key, Terminal}; 8 | 9 | fn main() -> io::Result<()> { 10 | let mut term = Terminal::new()?; 11 | 12 | let mut n_events = 0; 13 | let mut last_event = None; 14 | 15 | // Prepare to read from the terminal. 16 | let state = term.prepare(Default::default())?; 17 | 18 | println!("Collecting input. Press 'q' to stop."); 19 | println!(); 20 | 21 | // Write a few empty lines for where our text will be. 22 | println!(); 23 | println!(); 24 | println!(); 25 | 26 | write_data(&mut term, n_events, last_event)?; 27 | 28 | loop { 29 | if let Some(ev) = term.read_event(None)? { 30 | if let Event::NoEvent = ev { 31 | continue; 32 | } 33 | 34 | n_events += 1; 35 | last_event = Some(ev); 36 | 37 | write_data(&mut term, n_events, last_event)?; 38 | 39 | if let Event::Key(Key::Char('q')) = ev { 40 | break; 41 | } 42 | } 43 | } 44 | 45 | // Restore terminal state 46 | term.restore(state)?; 47 | 48 | Ok(()) 49 | } 50 | 51 | fn write_data(term: &mut Terminal, n_events: usize, last_event: Option) 52 | -> io::Result<()> { 53 | // Move the cursor up 2 lines. 54 | term.move_up(2)?; 55 | // Clear all text from the previous write. 56 | term.clear_to_screen_end()?; 57 | 58 | writeln!(term, "Number of events: {}", n_events)?; 59 | 60 | if let Some(ev) = last_event { 61 | writeln!(term, "Last event: {:?}", ev)?; 62 | } else { 63 | writeln!(term, "Last event: None")?; 64 | } 65 | 66 | Ok(()) 67 | } 68 | -------------------------------------------------------------------------------- /examples/threads.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | extern crate rand; 3 | 4 | use std::io; 5 | use std::sync::Arc; 6 | use std::thread::{spawn, sleep}; 7 | use std::time::Duration; 8 | 9 | use mortal::{Color, Terminal}; 10 | 11 | use rand::{Rng, seq::SliceRandom, thread_rng}; 12 | 13 | // A unique color for each thread 14 | const COLORS: &[Color] = &[ 15 | Color::Blue, 16 | Color::Red, 17 | Color::Green, 18 | Color::Cyan, 19 | Color::Magenta, 20 | ]; 21 | 22 | fn main() -> io::Result<()> { 23 | // Wrapping the Terminal in an Arc allows us 24 | // to share ownership with multiple threads. 25 | let term = Arc::new(Terminal::new()?); 26 | 27 | // Join handles for spawned threads 28 | let mut handles = Vec::new(); 29 | 30 | // Give a random color to each thread 31 | let mut colors = COLORS.to_vec(); 32 | colors.shuffle(&mut thread_rng()); 33 | 34 | for i in 0..5 { 35 | let name = format!("child{}", i); 36 | let term = term.clone(); 37 | let color = colors[i]; 38 | 39 | let handle = spawn(move || { 40 | run_task(&name, color, &term).unwrap() 41 | }); 42 | 43 | handles.push(handle); 44 | } 45 | 46 | for handle in handles { 47 | handle.join().unwrap(); 48 | } 49 | 50 | Ok(()) 51 | } 52 | 53 | fn run_task(name: &str, color: Color, term: &Terminal) 54 | -> io::Result<()> { 55 | let mut rng = thread_rng(); 56 | 57 | for _ in 0..5 { 58 | sleep(Duration::from_millis(rng.gen_range(100..300))); 59 | 60 | // Hold the lock while we perform a few different write operations. 61 | // This ensures that no other thread's output will interrupt ours. 62 | let mut lock = term.lock_write().unwrap(); 63 | 64 | lock.write_str("[")?; 65 | lock.bold()?; 66 | lock.set_fg(color)?; 67 | lock.write_str(name)?; 68 | lock.clear_attributes()?; 69 | writeln!(lock, "]: random output: {}", rng.gen::())?; 70 | 71 | // The lock is dropped at the end of the loop, 72 | // giving other threads a chance to grab it. 73 | } 74 | 75 | Ok(()) 76 | } 77 | -------------------------------------------------------------------------------- /examples/unix_open.rs: -------------------------------------------------------------------------------- 1 | extern crate mortal; 2 | 3 | use std::io; 4 | 5 | fn main() -> io::Result<()> { 6 | run_example() 7 | } 8 | 9 | #[cfg(not(unix))] 10 | fn run_example() -> io::Result<()> { 11 | eprintln!("This example demonstrates functionality specific to Unix platforms."); 12 | Ok(()) 13 | } 14 | 15 | #[cfg(unix)] 16 | fn run_example() -> io::Result<()> { 17 | use std::env::args; 18 | 19 | use mortal::Terminal; 20 | use mortal::unix::OpenTerminalExt; 21 | 22 | let path = match args().nth(1) { 23 | Some(path) => path, 24 | None => { 25 | eprintln!("No path specified. Try using /dev/tty"); 26 | return Ok(()); 27 | } 28 | }; 29 | 30 | let term = Terminal::from_path(&path)?; 31 | 32 | writeln!(term, "Hello, terminal!")?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::mem::swap; 2 | use std::ops::Range; 3 | 4 | use smallstr::SmallString; 5 | 6 | use crate::priv_util::is_visible; 7 | use crate::terminal::{Color, Cursor, Size, Style, Theme}; 8 | use crate::util::{char_width, is_combining_mark}; 9 | 10 | const TAB_STOP: usize = 8; 11 | 12 | pub struct ScreenBuffer { 13 | buffer: Vec, 14 | back_buffer: Vec, 15 | size: Size, 16 | cursor: Cursor, 17 | 18 | fg: Option, 19 | bg: Option, 20 | style: Style, 21 | } 22 | 23 | impl ScreenBuffer { 24 | pub fn new(size: Size) -> ScreenBuffer { 25 | let area = size.area(); 26 | 27 | ScreenBuffer{ 28 | buffer: vec![Cell::default(); area], 29 | back_buffer: vec![Cell::default(); area], 30 | size: size, 31 | cursor: Cursor::default(), 32 | 33 | fg: None, 34 | bg: None, 35 | style: Style::empty(), 36 | } 37 | } 38 | 39 | pub fn cursor(&self) -> Cursor { 40 | self.cursor 41 | } 42 | 43 | pub fn size(&self) -> Size { 44 | self.size 45 | } 46 | 47 | pub fn resize(&mut self, new_size: Size) { 48 | // Try our best to maintain the contents of the buffer; 49 | // though it's really best if users redraw when Resize event is read. 50 | resize_buffer(&mut self.buffer, self.size, new_size); 51 | // Totally invalidate the back buffer. 52 | // Screen implementations will clear the screen and redraw. 53 | new_buffer(&mut self.back_buffer, new_size); 54 | self.size = new_size; 55 | } 56 | 57 | pub fn set_cursor(&mut self, pos: Cursor) { 58 | self.cursor = pos; 59 | } 60 | 61 | pub fn next_line(&mut self, column: usize) { 62 | self.cursor.line += 1; 63 | self.cursor.column = column; 64 | } 65 | 66 | pub fn clear_attributes(&mut self) { 67 | self.fg = None; 68 | self.bg = None; 69 | self.style = Style::empty(); 70 | } 71 | 72 | pub fn add_style(&mut self, style: Style) { 73 | self.style |= style; 74 | } 75 | 76 | pub fn remove_style(&mut self, style: Style) { 77 | self.style -= style; 78 | } 79 | 80 | pub fn set_style(&mut self, style: Style) { 81 | self.style = style; 82 | } 83 | 84 | pub fn set_fg(&mut self, fg: Option) { 85 | self.fg = fg; 86 | } 87 | 88 | pub fn set_bg(&mut self, bg: Option) { 89 | self.bg = bg; 90 | } 91 | 92 | pub fn set_theme(&mut self, theme: Theme) { 93 | self.set_fg(theme.fg); 94 | self.set_bg(theme.bg); 95 | self.set_style(theme.style); 96 | } 97 | 98 | pub fn clear_screen(&mut self) { 99 | for cell in &mut self.buffer { 100 | *cell = Cell::default(); 101 | } 102 | } 103 | 104 | pub fn indices(&self) -> Range { 105 | 0..self.size.area() 106 | } 107 | 108 | // A wrapper type implementing Iterator would be ideal, but that would 109 | // interefere with Screen implementations calling `&mut self` methods. 110 | pub fn next_cell(&mut self, indices: &mut Range) -> Option<(Cursor, Cell)> { 111 | while let Some(idx) = indices.next() { 112 | let first = self.buffer[idx].first_char(); 113 | let width = char_width(first).unwrap_or(0); 114 | 115 | // Skip cells overlapped by wide characters 116 | if width == 2 { 117 | let _ = indices.next(); 118 | } 119 | 120 | if self.buffer[idx] != self.back_buffer[idx] { 121 | let cell = self.buffer[idx].clone(); 122 | 123 | let line = idx / self.size.columns; 124 | let column = idx % self.size.columns; 125 | 126 | self.back_buffer[idx] = cell.clone(); 127 | 128 | return Some((Cursor{line, column}, cell)); 129 | } 130 | } 131 | 132 | None 133 | } 134 | 135 | #[cfg(test)] 136 | fn cell(&self, pos: Cursor) -> &Cell { 137 | &self.buffer[pos.as_index(self.size)] 138 | } 139 | 140 | fn cell_mut(&mut self, pos: Cursor) -> &mut Cell { 141 | let size = self.size; 142 | &mut self.buffer[pos.as_index(size)] 143 | } 144 | 145 | fn set_cell(&mut self, pos: Cursor, ch: char) { 146 | let fg = self.fg; 147 | let bg = self.bg; 148 | let style = self.style; 149 | 150 | let cell = self.cell_mut(pos); 151 | 152 | cell.fg = fg; 153 | cell.bg = bg; 154 | cell.style = style; 155 | cell.text = ch.into(); 156 | } 157 | 158 | pub fn write_char(&mut self, ch: char) -> Result<(), OutOfBounds> { 159 | if ch == '\t' { 160 | self.try_cursor()?; 161 | let rem = self.size.columns - self.cursor.column; 162 | let n = rem.min(TAB_STOP - (self.cursor.column % TAB_STOP)); 163 | 164 | for _ in 0..n { 165 | self.write_char(' ')?; 166 | } 167 | } else if ch == '\r' { 168 | self.cursor.column = 0; 169 | } else if ch == '\n' { 170 | self.cursor.line += 1; 171 | self.cursor.column = 0; 172 | } else if is_combining_mark(ch) { 173 | if let Some(prev) = self.cursor.previous(self.size) { 174 | self.try_cursor_at(prev)?; 175 | self.cell_mut(prev).text.push(ch); 176 | } 177 | } else if is_visible(ch) { 178 | self.try_cursor()?; 179 | 180 | if let Some(prev) = self.cursor.previous(self.size) { 181 | let cell = self.cell_mut(prev); 182 | 183 | if cell.is_wide() { 184 | *cell = Cell::default(); 185 | } 186 | } 187 | 188 | let rem = self.size.columns - self.cursor.column; 189 | let width = char_width(ch).unwrap_or(0); 190 | 191 | // If insufficient space exists on the current line, 192 | // fill it with spaces and write the char on the next line. 193 | if rem < width { 194 | self.try_cursor()?; 195 | let mut pos = self.cursor; 196 | 197 | for _ in 0..rem { 198 | self.set_cell(pos, ch); 199 | pos.column += 1; 200 | } 201 | 202 | self.cursor.column = 0; 203 | self.cursor.line += 1; 204 | } 205 | 206 | self.try_cursor()?; 207 | 208 | let mut pos = self.cursor; 209 | self.set_cell(pos, ch); 210 | 211 | for _ in 1..width { 212 | pos.column += 1; 213 | self.set_cell(pos, ' '); 214 | } 215 | 216 | self.cursor.column += width; 217 | 218 | if self.cursor.column >= self.size.columns { 219 | self.cursor.line += 1; 220 | self.cursor.column = 0; 221 | } 222 | } 223 | 224 | Ok(()) 225 | } 226 | 227 | pub fn write_str(&mut self, s: &str) -> Result<(), OutOfBounds> { 228 | for ch in s.chars() { 229 | self.write_char(ch)?; 230 | } 231 | 232 | Ok(()) 233 | } 234 | 235 | pub fn write_at(&mut self, pos: Cursor, text: &str) -> Result<(), OutOfBounds> { 236 | self.try_cursor_at(pos)?; 237 | self.cursor = pos; 238 | 239 | self.write_str(text) 240 | } 241 | 242 | pub fn write_styled(&mut self, 243 | fg: Option, bg: Option, style: Style, text: &str) 244 | -> Result<(), OutOfBounds> { 245 | self.fg = fg; 246 | self.bg = bg; 247 | self.style = style; 248 | 249 | self.write_str(text)?; 250 | self.clear_attributes(); 251 | 252 | Ok(()) 253 | } 254 | 255 | pub fn write_styled_at(&mut self, pos: Cursor, 256 | fg: Option, bg: Option, style: Style, text: &str) 257 | -> Result<(), OutOfBounds> { 258 | self.try_cursor_at(pos)?; 259 | self.cursor = pos; 260 | 261 | self.write_styled(fg, bg, style, text) 262 | } 263 | 264 | fn try_cursor(&self) -> Result<(), OutOfBounds> { 265 | self.try_cursor_at(self.cursor) 266 | } 267 | 268 | fn try_cursor_at(&self, pos: Cursor) -> Result<(), OutOfBounds> { 269 | if pos.line >= self.size.lines || pos.column >= self.size.columns { 270 | Err(OutOfBounds(())) 271 | } else { 272 | Ok(()) 273 | } 274 | } 275 | } 276 | 277 | // Generates buffer methods (to be invoked from within an impl block) 278 | // forwarded to a buffer contained in self. 279 | // 280 | // All methods accept `&self`. Interior mutability is required. 281 | macro_rules! forward_screen_buffer_methods { 282 | ( |$slf:ident| $field:expr ) => { 283 | pub fn size(&self) -> crate::terminal::Size { 284 | let $slf = self; 285 | $field.size() 286 | } 287 | 288 | pub fn cursor(&self) -> crate::terminal::Cursor { 289 | let $slf = self; 290 | $field.cursor() 291 | } 292 | 293 | pub fn set_cursor(&self, pos: crate::terminal::Cursor) { 294 | let $slf = self; 295 | $field.set_cursor(pos); 296 | } 297 | 298 | pub fn next_line(&self, column: usize) { 299 | let $slf = self; 300 | $field.next_line(column); 301 | } 302 | 303 | pub fn clear_screen(&self) { 304 | let $slf = self; 305 | $field.clear_screen(); 306 | } 307 | 308 | pub fn clear_attributes(&self) { 309 | let $slf = self; 310 | $field.clear_attributes(); 311 | } 312 | 313 | pub fn add_style(&self, style: crate::terminal::Style) { 314 | let $slf = self; 315 | $field.add_style(style); 316 | } 317 | 318 | pub fn remove_style(&self, style: crate::terminal::Style) { 319 | let $slf = self; 320 | $field.remove_style(style); 321 | } 322 | 323 | pub fn set_style(&self, style: crate::terminal::Style) { 324 | let $slf = self; 325 | $field.set_style(style); 326 | } 327 | 328 | pub fn set_fg(&self, fg: Option) { 329 | let $slf = self; 330 | $field.set_fg(fg); 331 | } 332 | 333 | pub fn set_bg(&self, bg: Option) { 334 | let $slf = self; 335 | $field.set_bg(bg); 336 | } 337 | 338 | pub fn set_theme(&self, theme: crate::terminal::Theme) { 339 | let $slf = self; 340 | $field.set_theme(theme) 341 | } 342 | 343 | pub fn write_char(&self, ch: char) { 344 | let $slf = self; 345 | let _ = $field.write_char(ch); 346 | } 347 | 348 | pub fn write_str(&self, s: &str) { 349 | let $slf = self; 350 | let _ = $field.write_str(s); 351 | } 352 | 353 | pub fn write_at(&self, pos: crate::terminal::Cursor, text: &str) { 354 | let $slf = self; 355 | let _ = $field.write_at(pos, text); 356 | } 357 | 358 | pub fn write_styled(&self, 359 | fg: Option, bg: Option, 360 | style: crate::terminal::Style, text: &str) { 361 | let $slf = self; 362 | let _ = $field.write_styled(fg, bg, style, text); 363 | } 364 | 365 | pub fn write_styled_at(&self, pos: crate::terminal::Cursor, 366 | fg: Option, bg: Option, 367 | style: crate::terminal::Style, text: &str) { 368 | let $slf = self; 369 | let _ = $field.write_styled_at(pos, fg, bg, style, text); 370 | } 371 | } 372 | } 373 | 374 | // Same as above, but methods take `&mut self` where appropriate. 375 | macro_rules! forward_screen_buffer_mut_methods { 376 | ( |$slf:ident| $field:expr ) => { 377 | pub fn size(&self) -> crate::terminal::Size { 378 | let $slf = self; 379 | $field.size() 380 | } 381 | 382 | pub fn cursor(&self) -> crate::terminal::Cursor { 383 | let $slf = self; 384 | $field.cursor() 385 | } 386 | 387 | pub fn set_cursor(&mut self, pos: crate::terminal::Cursor) { 388 | let $slf = self; 389 | $field.set_cursor(pos); 390 | } 391 | 392 | pub fn next_line(&mut self, column: usize) { 393 | let $slf = self; 394 | $field.next_line(column); 395 | } 396 | 397 | pub fn clear_screen(&mut self) { 398 | let $slf = self; 399 | $field.clear_screen(); 400 | } 401 | 402 | pub fn clear_attributes(&mut self) { 403 | let $slf = self; 404 | $field.clear_attributes(); 405 | } 406 | 407 | pub fn add_style(&mut self, style: crate::terminal::Style) { 408 | let $slf = self; 409 | $field.add_style(style); 410 | } 411 | 412 | pub fn remove_style(&mut self, style: crate::terminal::Style) { 413 | let $slf = self; 414 | $field.remove_style(style); 415 | } 416 | 417 | pub fn set_style(&mut self, style: crate::terminal::Style) { 418 | let $slf = self; 419 | $field.set_style(style); 420 | } 421 | 422 | pub fn set_fg(&mut self, fg: Option) { 423 | let $slf = self; 424 | $field.set_fg(fg); 425 | } 426 | 427 | pub fn set_bg(&mut self, bg: Option) { 428 | let $slf = self; 429 | $field.set_bg(bg); 430 | } 431 | 432 | pub fn set_theme(&mut self, theme: crate::terminal::Theme) { 433 | let $slf = self; 434 | $field.set_theme(theme); 435 | } 436 | 437 | pub fn write_char(&mut self, ch: char) { 438 | let $slf = self; 439 | let _ = $field.write_char(ch); 440 | } 441 | 442 | pub fn write_str(&mut self, s: &str) { 443 | let $slf = self; 444 | let _ = $field.write_str(s); 445 | } 446 | 447 | pub fn write_at(&mut self, pos: crate::terminal::Cursor, text: &str) { 448 | let $slf = self; 449 | let _ = $field.write_at(pos, text); 450 | } 451 | 452 | pub fn write_styled(&mut self, 453 | fg: Option, bg: Option, 454 | style: crate::terminal::Style, text: &str) { 455 | let $slf = self; 456 | let _ = $field.write_styled(fg, bg, style, text); 457 | } 458 | 459 | pub fn write_styled_at(&mut self, pos: crate::terminal::Cursor, 460 | fg: Option, bg: Option, 461 | style: crate::terminal::Style, text: &str) { 462 | let $slf = self; 463 | let _ = $field.write_styled_at(pos, fg, bg, style, text); 464 | } 465 | } 466 | } 467 | 468 | #[derive(Debug)] 469 | pub struct OutOfBounds(()); 470 | 471 | #[derive(Clone, Debug, Eq, PartialEq)] 472 | pub struct Cell { 473 | fg: Option, 474 | bg: Option, 475 | style: Style, 476 | text: SmallString<[u8; 8]>, 477 | } 478 | 479 | impl Cell { 480 | fn new(fg: Option, bg: Option, style: Style, chr: char) -> Cell { 481 | Cell{ 482 | fg, 483 | bg, 484 | style, 485 | text: chr.into(), 486 | } 487 | } 488 | 489 | fn invalid() -> Cell { 490 | Cell{ 491 | fg: None, 492 | bg: None, 493 | style: Style::empty(), 494 | text: SmallString::new(), 495 | } 496 | } 497 | 498 | pub fn attrs(&self) -> (Option, Option, Style) { 499 | (self.fg, self.bg, self.style) 500 | } 501 | 502 | pub fn text(&self) -> &str { 503 | &self.text 504 | } 505 | 506 | fn first_char(&self) -> char { 507 | self.text.chars().next().expect("empty cell text") 508 | } 509 | 510 | fn is_wide(&self) -> bool { 511 | self.text.chars().next() 512 | .and_then(char_width).unwrap_or(0) == 2 513 | } 514 | } 515 | 516 | impl Default for Cell { 517 | fn default() -> Cell { 518 | Cell::new(None, None, Style::empty(), ' ') 519 | } 520 | } 521 | 522 | fn resize_buffer(buf: &mut Vec, old: Size, new: Size) { 523 | if old != new { 524 | let mut new_buf = vec![Cell::default(); new.area()]; 525 | 526 | if !buf.is_empty() { 527 | let n_cols = old.columns.min(new.columns); 528 | 529 | for (old, new) in buf.chunks_mut(old.columns) 530 | .zip(new_buf.chunks_mut(new.columns)) { 531 | for i in 0..n_cols { 532 | swap(&mut new[i], &mut old[i]); 533 | } 534 | } 535 | } 536 | 537 | *buf = new_buf; 538 | } 539 | } 540 | 541 | fn new_buffer(buf: &mut Vec, new_size: Size) { 542 | // Invalidate the buffer; all cells will be redrawn 543 | *buf = vec![Cell::invalid(); new_size.area()]; 544 | } 545 | 546 | #[cfg(test)] 547 | mod test { 548 | use crate::terminal::{Cursor, Size}; 549 | use crate::util::char_width; 550 | use super::ScreenBuffer; 551 | 552 | macro_rules! assert_lines { 553 | ( $buf:expr , $lines:expr ) => { 554 | assert_lines(&$buf, &$lines[..], line!()) 555 | } 556 | } 557 | 558 | fn assert_lines(buf: &ScreenBuffer, lines: &[&str], line_num: u32) { 559 | let size = buf.size(); 560 | let mut text = String::with_capacity(size.columns); 561 | 562 | assert_eq!(size.lines, lines.len(), 563 | "line count does not match at line {}", line_num); 564 | 565 | for line in 0..size.lines { 566 | let mut column = 0; 567 | 568 | while column < size.columns { 569 | let cell = buf.cell(Cursor{line, column}); 570 | text.push_str(&cell.text); 571 | 572 | column += cell.text.chars().next() 573 | .and_then(char_width).unwrap_or(1); 574 | } 575 | 576 | let next_line = lines[line]; 577 | 578 | assert_eq!(text.trim_end(), next_line, 579 | "buffer line {} does not match at line {}", line, line_num); 580 | 581 | text.clear(); 582 | } 583 | } 584 | 585 | #[test] 586 | fn test_buffer_bounds() { 587 | let mut buf = ScreenBuffer::new(Size{lines: 1, columns: 1}); 588 | 589 | buf.write_char('a').unwrap(); 590 | assert!(buf.write_char('b').is_err()); 591 | } 592 | 593 | #[test] 594 | fn test_buffer_combining() { 595 | let mut buf = ScreenBuffer::new(Size{lines: 1, columns: 1}); 596 | 597 | buf.write_str("a\u{301}\u{302}\u{303}\u{304}").unwrap(); 598 | assert_lines!(buf, ["a\u{301}\u{302}\u{303}\u{304}"]); 599 | 600 | assert!(buf.write_str("x").is_err()) 601 | } 602 | 603 | #[test] 604 | fn test_buffer_tab() { 605 | let mut buf = ScreenBuffer::new(Size{lines: 2, columns: 10}); 606 | 607 | buf.write_str("xxxxxxxxxx").unwrap(); 608 | assert_lines!(buf, ["xxxxxxxxxx", ""]); 609 | 610 | buf.set_cursor((0, 0).into()); 611 | buf.write_str("\tyyz").unwrap(); 612 | assert_lines!(buf, [" yy", "z"]); 613 | 614 | buf.set_cursor((0, 0).into()); 615 | buf.write_str("\tx\tx").unwrap(); 616 | assert_lines!(buf, [" x", "x"]); 617 | } 618 | 619 | #[test] 620 | fn test_buffer_wide() { 621 | let mut buf = ScreenBuffer::new(Size{lines: 1, columns: 10}); 622 | 623 | buf.write_str("Foo").unwrap(); 624 | assert_lines!(buf, ["Foo"]); 625 | 626 | buf.write_str("\rx").unwrap(); 627 | assert_lines!(buf, ["x oo"]); 628 | 629 | buf.write_str("xx").unwrap(); 630 | assert_lines!(buf, ["xxx o"]); 631 | } 632 | } 633 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Platform-independent terminal interface 2 | //! 3 | //! Two distinct interfaces to operating system terminal devices are provided, 4 | //! each abstracting over the differences between Unix terminals and Windows console. 5 | //! 6 | //! The [`Terminal`] interface treats the terminal as a line-by-line 7 | //! output device. Methods exist to add color and style attributes to text, 8 | //! and to make relative movements of the cursor. 9 | //! 10 | //! The [`Screen`] interface treats the entire terminal window as a drawable 11 | //! buffer. Methods exist to set the cursor position and to write text with 12 | //! color and style attributes. 13 | //! 14 | //! The [`term_write!`] and [`term_writeln!`] macros provide a convenient interface 15 | //! to output attributes and formatted text to either a `Terminal` or `Screen` 16 | //! instance. 17 | //! 18 | //! ## Concurrency 19 | //! 20 | //! Each interface uses internal locking mechanisms to allow sharing of the 21 | //! terminal interface between threads while maintaining coherence of read/write 22 | //! operations. 23 | //! 24 | //! See the documentation for [`Terminal`] and [`Screen`] for further details. 25 | //! 26 | //! [`Screen`]: screen/struct.Screen.html 27 | //! [`Terminal`]: terminal/struct.Terminal.html 28 | //! [`term_write!`]: macro.term_write.html 29 | //! [`term_writeln!`]: macro.term_writeln.html 30 | 31 | #![deny(missing_docs)] 32 | 33 | #[macro_use] extern crate bitflags; 34 | extern crate smallstr; 35 | extern crate unicode_normalization; 36 | extern crate unicode_width; 37 | 38 | #[cfg(unix)] extern crate libc; 39 | #[cfg(unix)] extern crate nix; 40 | #[cfg(unix)] extern crate terminfo; 41 | 42 | #[cfg(windows)] extern crate winapi; 43 | 44 | pub use crate::screen::{Screen, ScreenReadGuard, ScreenWriteGuard}; 45 | pub use crate::sequence::{FindResult, SequenceMap}; 46 | pub use crate::signal::{Signal, SignalSet}; 47 | pub use crate::terminal::{ 48 | Color, Cursor, CursorMode, Size, Style, Theme, 49 | Event, Key, MouseEvent, MouseInput, MouseButton, ModifierState, 50 | PrepareConfig, PrepareState, 51 | Terminal, TerminalReadGuard, TerminalWriteGuard, 52 | }; 53 | 54 | #[macro_use] mod buffer; 55 | #[doc(hidden)] 56 | #[macro_use] pub mod macros; 57 | mod priv_util; 58 | pub mod screen; 59 | pub mod sequence; 60 | pub mod signal; 61 | pub mod terminal; 62 | pub mod util; 63 | 64 | #[cfg(unix)] 65 | #[path = "unix/mod.rs"] 66 | mod sys; 67 | 68 | #[cfg(windows)] 69 | #[path = "windows/mod.rs"] 70 | mod sys; 71 | 72 | #[cfg(unix)] 73 | pub use crate::sys::ext as unix; 74 | 75 | #[cfg(windows)] 76 | pub use sys::ext as windows; 77 | 78 | #[cfg(test)] 79 | mod test { 80 | use crate::screen::Screen; 81 | use crate::terminal::Terminal; 82 | 83 | fn assert_has_traits() {} 84 | 85 | #[test] 86 | fn test_traits() { 87 | assert_has_traits::(); 88 | assert_has_traits::(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | //! Provides macros easier printing with colors and styles. 2 | 3 | use std::io; 4 | 5 | /// Writes attributes and formatted text to a `Terminal` or `Screen`. 6 | /// 7 | /// # Usage 8 | /// 9 | /// `term_write!` accepts a series of attribute elements and formatted text elements. 10 | /// 11 | /// [`term_writeln!`] is equivalent, but writes a newline character 12 | /// to the end of the formatted text. 13 | /// 14 | /// Attribute elements are enclosed in square brackets 15 | /// and take one of the following forms: 16 | /// 17 | /// | Element | Equivalent | 18 | /// | ----------------- | --------------------------------- | 19 | /// | `[red]` | `term.set_fg(Color::Red)` | 20 | /// | `[#blue]` | `term.set_bg(Color::Blue)` | 21 | /// | `[bold]` | `term.add_style(Style::BOLD)` | 22 | /// | `[!bold]` | `term.remove_style(Style::BOLD)` | 23 | /// | `[reset]` | `term.clear_attributes()` | 24 | /// | `[!fg]` | `term.set_fg(None)` | 25 | /// | `[!bg]` | `term.set_bg(None)` | 26 | /// | `[!style]` | `term.set_style(None)` | 27 | /// | `[fg=expr]` | `term.set_fg(expr)` | 28 | /// | `[bg=expr]` | `term.set_bg(expr)` | 29 | /// | `[style=expr]` | `term.set_style(expr)` | 30 | /// | `[style+=expr]` | `term.add_style(expr)` | 31 | /// | `[style-=expr]` | `term.remove_style(expr)` | 32 | /// | `[theme=expr]` | `term.set_theme(expr)` | 33 | /// 34 | /// Formatted text elements are enclosed in parentheses 35 | /// and use Rust [`std::fmt`] functions to write formatted text to the terminal. 36 | /// Additionally, a bare string literal may be given and will be written 37 | /// directly to the terminal. 38 | /// 39 | /// | Element | Equivalent | 40 | /// | ----------------- | --------------------------------- | 41 | /// | `(: expr)` | `write!(term, "{}", expr)` | 42 | /// | `(? expr)` | `write!(term, "{:?}", expr)` | 43 | /// | `("format", ...)` | `write!(term, "format", ...)` | 44 | /// | `"literal str"` | `term.write_str("literal str")` | 45 | /// 46 | /// # Examples 47 | /// 48 | /// ```no_run 49 | /// #[macro_use] extern crate mortal; 50 | /// # use std::io; 51 | /// use mortal::{Color, Style, Theme, Terminal}; 52 | /// 53 | /// # fn main() -> io::Result<()> { 54 | /// let term = Terminal::new()?; 55 | /// 56 | /// term_writeln!(term, [red] "red text" [reset])?; 57 | /// 58 | /// let color = Color::Green; 59 | /// term_writeln!(term, [fg=color] "green text" [reset])?; 60 | /// 61 | /// let style = Style::BOLD; 62 | /// term_writeln!(term, [style=style] "bold text" [reset])?; 63 | /// 64 | /// let value = 42; 65 | /// term_writeln!(term, "The answer is: " [bold] (: value) [reset])?; 66 | /// 67 | /// let theme = Theme::new(color, None, style); 68 | /// term_writeln!(term, [theme=theme] "Green, bold text" [reset])?; 69 | /// # Ok(()) 70 | /// # } 71 | /// ``` 72 | /// 73 | /// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/ 74 | /// [`term_writeln!`]: macro.term_writeln.html 75 | #[macro_export] 76 | macro_rules! term_write { 77 | // Entry rule 78 | ( $term:expr , $first:tt $($rest:tt)* ) => { 79 | match $term.borrow_term_write_guard() { 80 | mut term => { 81 | let init = $crate::macros::Chain::init(); 82 | term_write!(@_INTERNAL main: term ; init ; $first $($rest)*) 83 | } 84 | } 85 | }; 86 | 87 | // Final rule 88 | ( @_INTERNAL main: $term:expr ; $result:expr ; ) => { 89 | $result 90 | }; 91 | 92 | // Color/style rules 93 | ( @_INTERNAL main: $term:expr ; $result:expr ; [ $($tt:tt)* ] $($rest:tt)* ) => { 94 | term_write!( 95 | @_INTERNAL main: $term; 96 | term_write!(@_INTERNAL style: $term; $result; $($tt)*); 97 | $($rest)* 98 | ) 99 | }; 100 | 101 | // Formatting rules 102 | ( @_INTERNAL main: $term:expr ; $result:expr ; ( $($tt:tt)* ) $($rest:tt)* ) => { 103 | term_write!( 104 | @_INTERNAL main: $term; 105 | term_write!(@_INTERNAL format: $term; $result; $($tt)*); 106 | $($rest)* 107 | ) 108 | }; 109 | ( @_INTERNAL main: $term:expr ; $result:expr ; $tt:tt $($rest:tt)* ) => { 110 | term_write!( 111 | @_INTERNAL main: $term; 112 | term_write!(@_INTERNAL literal: $term; $result; $tt); 113 | $($rest)* 114 | ) 115 | }; 116 | 117 | // Set foreground color 118 | ( @_INTERNAL style: $term:expr ; $result:expr ; black ) => { 119 | $crate::macros::Chain::chain( 120 | $result, || $term.set_fg($crate::Color::Black)) 121 | }; 122 | ( @_INTERNAL style: $term:expr ; $result:expr ; blue ) => { 123 | $crate::macros::Chain::chain( 124 | $result, || $term.set_fg($crate::Color::Blue)) 125 | }; 126 | ( @_INTERNAL style: $term:expr ; $result:expr ; cyan ) => { 127 | $crate::macros::Chain::chain( 128 | $result, || $term.set_fg($crate::Color::Cyan)) 129 | }; 130 | ( @_INTERNAL style: $term:expr ; $result:expr ; green ) => { 131 | $crate::macros::Chain::chain( 132 | $result, || $term.set_fg($crate::Color::Green)) 133 | }; 134 | ( @_INTERNAL style: $term:expr ; $result:expr ; magenta ) => { 135 | $crate::macros::Chain::chain( 136 | $result, || $term.set_fg($crate::Color::Magenta)) 137 | }; 138 | ( @_INTERNAL style: $term:expr ; $result:expr ; red ) => { 139 | $crate::macros::Chain::chain( 140 | $result, || $term.set_fg($crate::Color::Red)) 141 | }; 142 | ( @_INTERNAL style: $term:expr ; $result:expr ; white ) => { 143 | $crate::macros::Chain::chain( 144 | $result, || $term.set_fg($crate::Color::White)) 145 | }; 146 | ( @_INTERNAL style: $term:expr ; $result:expr ; yellow ) => { 147 | $crate::macros::Chain::chain( 148 | $result, || $term.set_fg($crate::Color::Yellow)) 149 | }; 150 | 151 | // Set background color 152 | ( @_INTERNAL style: $term:expr ; $result:expr ; # black ) => { 153 | $crate::macros::Chain::chain( 154 | $result, || $term.set_bg($crate::Color::Black)) 155 | }; 156 | ( @_INTERNAL style: $term:expr ; $result:expr ; # blue ) => { 157 | $crate::macros::Chain::chain( 158 | $result, || $term.set_bg($crate::Color::Blue)) 159 | }; 160 | ( @_INTERNAL style: $term:expr ; $result:expr ; # cyan ) => { 161 | $crate::macros::Chain::chain( 162 | $result, || $term.set_bg($crate::Color::Cyan)) 163 | }; 164 | ( @_INTERNAL style: $term:expr ; $result:expr ; # green ) => { 165 | $crate::macros::Chain::chain( 166 | $result, || $term.set_bg($crate::Color::Green)) 167 | }; 168 | ( @_INTERNAL style: $term:expr ; $result:expr ; # magenta ) => { 169 | $crate::macros::Chain::chain( 170 | $result, || $term.set_bg($crate::Color::Magenta)) 171 | }; 172 | ( @_INTERNAL style: $term:expr ; $result:expr ; # red ) => { 173 | $crate::macros::Chain::chain( 174 | $result, || $term.set_bg($crate::Color::Red)) 175 | }; 176 | ( @_INTERNAL style: $term:expr ; $result:expr ; # white ) => { 177 | $crate::macros::Chain::chain( 178 | $result, || $term.set_bg($crate::Color::White)) 179 | }; 180 | ( @_INTERNAL style: $term:expr ; $result:expr ; # yellow ) => { 181 | $crate::macros::Chain::chain( 182 | $result, || $term.set_bg($crate::Color::Yellow)) 183 | }; 184 | 185 | // Add style 186 | ( @_INTERNAL style: $term:expr ; $result:expr ; bold ) => { 187 | $crate::macros::Chain::chain( 188 | $result, || $term.add_style($crate::Style::BOLD)) 189 | }; 190 | ( @_INTERNAL style: $term:expr ; $result:expr ; italic ) => { 191 | $crate::macros::Chain::chain( 192 | $result, || $term.add_style($crate::Style::ITALIC)) 193 | }; 194 | ( @_INTERNAL style: $term:expr ; $result:expr ; reverse ) => { 195 | $crate::macros::Chain::chain( 196 | $result, || $term.add_style($crate::Style::REVERSE)) 197 | }; 198 | ( @_INTERNAL style: $term:expr ; $result:expr ; underline ) => { 199 | $crate::macros::Chain::chain( 200 | $result, || $term.add_style($crate::Style::UNDERLINE)) 201 | }; 202 | 203 | // Remove style 204 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! bold ) => { 205 | $crate::macros::Chain::chain( 206 | $result, || $term.remove_style($crate::Style::BOLD)) 207 | }; 208 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! italic ) => { 209 | $crate::macros::Chain::chain( 210 | $result, || $term.remove_style($crate::Style::ITALIC)) 211 | }; 212 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! reverse ) => { 213 | $crate::macros::Chain::chain( 214 | $result, || $term.remove_style($crate::Style::REVERSE)) 215 | }; 216 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! underline ) => { 217 | $crate::macros::Chain::chain( 218 | $result, || $term.remove_style($crate::Style::UNDERLINE)) 219 | }; 220 | 221 | // Clear attributes 222 | ( @_INTERNAL style: $term:expr ; $result:expr ; reset ) => { 223 | $crate::macros::Chain::chain( 224 | $result, || $term.clear_attributes()) 225 | }; 226 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! fg ) => { 227 | $crate::macros::Chain::chain( 228 | $result, || $term.set_fg(None)) 229 | }; 230 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! bg ) => { 231 | $crate::macros::Chain::chain( 232 | $result, || $term.set_bg(None)) 233 | }; 234 | ( @_INTERNAL style: $term:expr ; $result:expr ; ! style ) => { 235 | $crate::macros::Chain::chain( 236 | $result, || $term.set_style(None)) 237 | }; 238 | 239 | // Color/style expressions 240 | ( @_INTERNAL style: $term:expr ; $result:expr ; fg = $e:expr ) => { 241 | $crate::macros::Chain::chain( 242 | $result, || $term.set_fg($e)) 243 | }; 244 | ( @_INTERNAL style: $term:expr ; $result:expr ; bg = $e:expr ) => { 245 | $crate::macros::Chain::chain( 246 | $result, || $term.set_bg($e)) 247 | }; 248 | ( @_INTERNAL style: $term:expr ; $result:expr ; style = $e:expr ) => { 249 | $crate::macros::Chain::chain( 250 | $result, || $term.set_style($e)) 251 | }; 252 | ( @_INTERNAL style: $term:expr ; $result:expr ; style += $e:expr ) => { 253 | $crate::macros::Chain::chain( 254 | $result, || $term.add_style($e)) 255 | }; 256 | ( @_INTERNAL style: $term:expr ; $result:expr ; style -= $e:expr ) => { 257 | $crate::macros::Chain::chain( 258 | $result, || $term.remove_style($e)) 259 | }; 260 | ( @_INTERNAL style: $term:expr ; $result:expr ; theme = $e:expr ) => { 261 | $crate::macros::Chain::chain( 262 | $result, || $term.set_theme($e)) 263 | }; 264 | 265 | // std::fmt formatting 266 | ( @_INTERNAL format: $term:expr ; $result:expr ; : $e:expr ) => { 267 | $crate::macros::Chain::chain( 268 | $result, || write!($term, "{}", $e)) 269 | }; 270 | ( @_INTERNAL format: $term:expr ; $result:expr ; ? $e:expr ) => { 271 | $crate::macros::Chain::chain( 272 | $result, || write!($term, "{:?}", $e)) 273 | }; 274 | ( @_INTERNAL format: $term:expr ; $result:expr ; $($tt:tt)* ) => { 275 | $crate::macros::Chain::chain( 276 | $result, || write!($term, $($tt)*)) 277 | }; 278 | 279 | // Literal formatting 280 | ( @_INTERNAL literal: $term:expr ; $result:expr ; $lit:tt ) => { 281 | $crate::macros::Chain::chain( 282 | $result, || $term.write_str(concat!($lit))) 283 | }; 284 | } 285 | 286 | /// Writes attributes and formatted text to a `Terminal` or `Screen`. 287 | /// 288 | /// Formatted output is followed by a newline. 289 | /// 290 | /// See [`term_write`] for a description of macro syntax and example usage. 291 | /// 292 | /// [`term_write`]: macro.term_write.html 293 | #[macro_export] 294 | macro_rules! term_writeln { 295 | ( $term:expr ) => { 296 | term_write!($term, "\n") 297 | }; 298 | ( $term:expr , $($tt:tt)* ) => { 299 | term_write!($term, $($tt)* "\n") 300 | }; 301 | } 302 | 303 | // Facilitates chaining calls from either a `Terminal` or `Screen` lock. 304 | // 305 | // Terminal methods return `io::Result<()>` and are chained with 306 | // `Result::and_then`; Screen methods return `()`, so the next function is 307 | // always called. 308 | #[doc(hidden)] 309 | pub trait Chain: Sized { 310 | fn chain Self>(self, f: F) -> Self; 311 | 312 | fn init() -> Self; 313 | } 314 | 315 | impl Chain for () { 316 | fn chain Self>(self, f: F) -> Self { 317 | f() 318 | } 319 | 320 | fn init() -> Self { } 321 | } 322 | 323 | impl Chain for io::Result<()> { 324 | fn chain Self>(self, f: F) -> Self { 325 | self.and_then(|_| f()) 326 | } 327 | 328 | fn init() -> Self { Ok(()) } 329 | } 330 | -------------------------------------------------------------------------------- /src/priv_util.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{LockResult, PoisonError, TryLockError, TryLockResult}; 2 | 3 | use crate::screen::{Screen, ScreenReadGuard}; 4 | use crate::terminal::{Terminal, TerminalReadGuard}; 5 | use crate::util::char_width; 6 | 7 | // Private trait used to prevent external crates from implementing extension traits 8 | pub trait Private {} 9 | 10 | impl Private for Screen {} 11 | impl<'a> Private for ScreenReadGuard<'a> {} 12 | impl Private for Terminal {} 13 | impl<'a> Private for TerminalReadGuard<'a> {} 14 | 15 | pub fn is_visible(ch: char) -> bool { 16 | match ch { 17 | '\t' | '\r' | '\n' => true, 18 | _ => char_width(ch).unwrap_or(0) != 0 19 | } 20 | } 21 | 22 | pub fn map_lock_result(res: LockResult, f: F) -> LockResult 23 | where F: FnOnce(T) -> U { 24 | match res { 25 | Ok(t) => Ok(f(t)), 26 | Err(e) => Err(PoisonError::new(f(e.into_inner()))), 27 | } 28 | } 29 | 30 | pub fn map_try_lock_result(res: TryLockResult, f: F) -> TryLockResult 31 | where F: FnOnce(T) -> U { 32 | match res { 33 | Ok(t) => Ok(f(t)), 34 | Err(TryLockError::Poisoned(p)) => Err(TryLockError::Poisoned( 35 | PoisonError::new(f(p.into_inner())))), 36 | Err(TryLockError::WouldBlock) => Err(TryLockError::WouldBlock), 37 | } 38 | } 39 | 40 | pub fn map2_lock_result(res: LockResult, res2: LockResult, f: F) 41 | -> LockResult where F: FnOnce(T, U) -> R { 42 | match (res, res2) { 43 | (Ok(a), Ok(b)) => Ok(f(a, b)), 44 | (Ok(a), Err(b)) => Err(PoisonError::new(f(a, b.into_inner()))), 45 | (Err(a), Ok(b)) => Err(PoisonError::new(f(a.into_inner(), b))), 46 | (Err(a), Err(b)) => Err(PoisonError::new(f(a.into_inner(), b.into_inner()))), 47 | } 48 | } 49 | 50 | pub fn map2_try_lock_result( 51 | res: TryLockResult, res2: TryLockResult, f: F) 52 | -> TryLockResult where F: FnOnce(T, U) -> R { 53 | match (res, res2) { 54 | (Ok(a), Ok(b)) => Ok(f(a, b)), 55 | (Err(TryLockError::WouldBlock), _) => Err(TryLockError::WouldBlock), 56 | (_, Err(TryLockError::WouldBlock)) => Err(TryLockError::WouldBlock), 57 | (Ok(a), Err(TryLockError::Poisoned(b))) => 58 | Err(TryLockError::Poisoned(PoisonError::new(f(a, b.into_inner())))), 59 | (Err(TryLockError::Poisoned(a)), Ok(b)) => 60 | Err(TryLockError::Poisoned(PoisonError::new(f(a.into_inner(), b)))), 61 | (Err(TryLockError::Poisoned(a)), Err(TryLockError::Poisoned(b))) => 62 | Err(TryLockError::Poisoned(PoisonError::new( 63 | f(a.into_inner(), b.into_inner())))), 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/screen.rs: -------------------------------------------------------------------------------- 1 | //! Provides a drawable buffer on terminal devices 2 | 3 | use std::fmt; 4 | use std::io; 5 | use std::sync::{LockResult, TryLockResult}; 6 | use std::time::Duration; 7 | 8 | use crate::priv_util::{map_lock_result, map_try_lock_result}; 9 | use crate::sys; 10 | use crate::terminal::{ 11 | Color, Cursor, CursorMode, Event, PrepareConfig, Size, Style, Theme, 12 | Terminal, 13 | }; 14 | 15 | /// Provides operations on an underlying terminal device in screen mode. 16 | /// 17 | /// `Screen` uses an internal buffer to store rendered text, colors, and style. 18 | /// 19 | /// Each set of changes must be followed by a call to [`refresh`] to flush these 20 | /// changes to the terminal device. 21 | /// 22 | /// # Concurrency 23 | /// 24 | /// Access to read and write operations is controlled by two internal locks: 25 | /// One for [reading] and one for [writing]. Each lock may be held independently 26 | /// of the other. 27 | /// 28 | /// If any one thread wishes to hold both locks, the read lock 29 | /// must be acquired first, in order to prevent deadlocks. 30 | /// 31 | /// [`refresh`]: #method.refresh 32 | /// [reading]: struct.ScreenReadGuard.html 33 | /// [writing]: struct.ScreenWriteGuard.html 34 | pub struct Screen(sys::Screen); 35 | 36 | /// Holds an exclusive lock for read operations on a `Screen` 37 | /// 38 | /// See [`Screen`] documentation for details on locking. 39 | /// 40 | /// [`Screen`]: struct.Screen.html 41 | pub struct ScreenReadGuard<'a>(sys::ScreenReadGuard<'a>); 42 | 43 | /// Holds an exclusive lock for write operations on a `Screen` 44 | /// 45 | /// See [`Screen`] documentation for details on locking. 46 | /// 47 | /// [`Screen`]: struct.Screen.html 48 | pub struct ScreenWriteGuard<'a>(sys::ScreenWriteGuard<'a>); 49 | 50 | impl Screen { 51 | /// Opens a new screen interface on `stdout`. 52 | pub fn new(config: PrepareConfig) -> io::Result { 53 | sys::Screen::stdout(config).map(Screen) 54 | } 55 | 56 | /// Opens a new screen interface on `stderr`. 57 | pub fn stderr(config: PrepareConfig) -> io::Result { 58 | sys::Screen::stderr(config).map(Screen) 59 | } 60 | 61 | /// Begins a new screen session using the given `Terminal` instance. 62 | pub fn with_terminal(term: Terminal, config: PrepareConfig) -> io::Result { 63 | sys::Screen::new(term.0, config).map(Screen) 64 | } 65 | 66 | /// Returns the name of the terminal. 67 | /// 68 | /// # Notes 69 | /// 70 | /// On Unix, this method returns the contents of the `TERM` environment variable. 71 | /// 72 | /// On Windows, this method always returns the string `"windows-console"`. 73 | #[inline] 74 | pub fn name(&self) -> &str { 75 | self.0.name() 76 | } 77 | 78 | /// Attempts to acquire an exclusive lock on terminal read operations. 79 | /// 80 | /// The current thread will block until the lock can be acquired. 81 | #[inline] 82 | pub fn lock_read(&self) -> LockResult { 83 | map_lock_result(self.0.lock_read(), ScreenReadGuard) 84 | } 85 | 86 | /// Attempts to acquire an exclusive lock on terminal write operations. 87 | /// 88 | /// The current thread will block until the lock can be acquired. 89 | #[inline] 90 | pub fn lock_write(&self) -> LockResult { 91 | map_lock_result(self.0.lock_write(), ScreenWriteGuard) 92 | } 93 | 94 | /// Attempts to acquire an exclusive lock on terminal read operations. 95 | /// 96 | /// If the lock cannot be acquired immediately, `Err(_)` is returned. 97 | #[inline] 98 | pub fn try_lock_read(&self) -> TryLockResult { 99 | map_try_lock_result(self.0.try_lock_read(), ScreenReadGuard) 100 | } 101 | 102 | /// Attempts to acquire an exclusive lock on terminal write operations. 103 | /// 104 | /// If the lock cannot be acquired immediately, `Err(_)` is returned. 105 | #[inline] 106 | pub fn try_lock_write(&self) -> TryLockResult { 107 | map_try_lock_result(self.0.try_lock_write(), ScreenWriteGuard) 108 | } 109 | } 110 | 111 | /// # Locking 112 | /// 113 | /// The following methods internally acquire the read lock. 114 | /// 115 | /// The lock is released before the method returns. 116 | /// 117 | /// These methods are also implemented on [`ScreenReadGuard`], 118 | /// which holds the `Screen` read lock until the value is dropped. 119 | /// 120 | /// [`ScreenReadGuard`]: struct.ScreenReadGuard.html 121 | impl Screen { 122 | /// Waits for an event from the terminal. 123 | /// 124 | /// Returns `Ok(false)` if `timeout` elapses without an event occurring. 125 | /// 126 | /// If `timeout` is `None`, this method will wait indefinitely. 127 | /// 128 | /// # Notes 129 | /// 130 | /// Some low-level terminal events may not generate an `Event` value. 131 | /// Therefore, this method may return `Ok(true)`, while a follow-up call 132 | /// to `read_event` may not immediately return an event. 133 | pub fn wait_event(&self, timeout: Option) -> io::Result { 134 | self.0.wait_event(timeout) 135 | } 136 | 137 | /// Reads an event from the terminal. 138 | /// 139 | /// If `timeout` elapses without an event occurring, this method will return 140 | /// `Ok(None)`. 141 | /// 142 | /// If `timeout` is `None`, this method will wait indefinitely. 143 | pub fn read_event(&self, timeout: Option) -> io::Result> { 144 | self.0.read_event(timeout) 145 | } 146 | } 147 | 148 | /// # Locking 149 | /// 150 | /// The following methods internally acquire the write lock. 151 | /// 152 | /// The lock is released before the method returns. 153 | /// 154 | /// These methods are also implemented on [`ScreenWriteGuard`], 155 | /// which holds the `Screen` write lock until the value is dropped. 156 | /// 157 | /// [`ScreenWriteGuard`]: struct.ScreenWriteGuard.html 158 | impl Screen { 159 | /// Returns the current size of the terminal screen. 160 | #[inline] 161 | pub fn size(&self) -> Size { 162 | self.0.size() 163 | } 164 | 165 | /// Returns the current cursor position. 166 | #[inline] 167 | pub fn cursor(&self) -> Cursor { 168 | self.0.cursor() 169 | } 170 | 171 | /// Sets the cursor position. 172 | #[inline] 173 | pub fn set_cursor>(&self, pos: C) { 174 | self.0.set_cursor(pos.into()); 175 | } 176 | 177 | /// Moves the cursor to the given column on the next line. 178 | #[inline] 179 | pub fn next_line(&self, column: usize) { 180 | self.0.next_line(column); 181 | } 182 | 183 | /// Set the current cursor mode. 184 | /// 185 | /// This setting is a visible hint to the user. 186 | /// It produces no change in behavior. 187 | /// 188 | /// # Notes 189 | /// 190 | /// On Unix systems, this setting may have no effect. 191 | pub fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()> { 192 | self.0.set_cursor_mode(mode) 193 | } 194 | 195 | /// Clears the internal screen buffer. 196 | pub fn clear_screen(&self) { 197 | self.0.clear_screen(); 198 | } 199 | 200 | /// Adds a set of `Style` flags to the current style setting. 201 | #[inline] 202 | pub fn add_style(&self, style: Style) { 203 | self.0.add_style(style); 204 | } 205 | 206 | /// Removes a set of `Style` flags to the current style setting. 207 | #[inline] 208 | pub fn remove_style(&self, style: Style) { 209 | self.0.remove_style(style); 210 | } 211 | 212 | /// Sets the current style setting to the given set of flags. 213 | #[inline] 214 | pub fn set_style>>(&self, style: S) { 215 | self.0.set_style(style.into().unwrap_or_default()); 216 | } 217 | 218 | /// Sets or removes foreground text color. 219 | #[inline] 220 | pub fn set_fg>>(&self, fg: C) { 221 | self.0.set_fg(fg.into()); 222 | } 223 | 224 | /// Sets or removes background text color. 225 | #[inline] 226 | pub fn set_bg>>(&self, bg: C) { 227 | self.0.set_bg(bg.into()); 228 | } 229 | 230 | /// Sets all attributes for the screen. 231 | #[inline] 232 | pub fn set_theme(&self, theme: Theme) { 233 | self.0.set_theme(theme) 234 | } 235 | 236 | /// Removes color and style attributes. 237 | #[inline] 238 | pub fn clear_attributes(&self) { 239 | self.0.clear_attributes(); 240 | } 241 | 242 | /// Adds bold to the current style setting. 243 | /// 244 | /// This is equivalent to `self.add_style(Style::BOLD)`. 245 | #[inline] 246 | pub fn bold(&self) { 247 | self.add_style(Style::BOLD); 248 | } 249 | 250 | /// Adds italic to the current style setting. 251 | /// 252 | /// This is equivalent to `self.add_style(Style::ITALIC)`. 253 | #[inline] 254 | pub fn italic(&self) { 255 | self.add_style(Style::ITALIC); 256 | } 257 | 258 | /// Adds underline to the current style setting. 259 | /// 260 | /// This is equivalent to `self.add_style(Style::UNDERLINE)`. 261 | #[inline] 262 | pub fn underline(&self) { 263 | self.add_style(Style::UNDERLINE); 264 | } 265 | 266 | /// Adds reverse to the current style setting. 267 | /// 268 | /// This is equivalent to `self.add_style(Style::REVERSE)`. 269 | #[inline] 270 | pub fn reverse(&self) { 271 | self.add_style(Style::REVERSE); 272 | } 273 | 274 | /// Renders the internal buffer to the terminal screen. 275 | pub fn refresh(&self) -> io::Result<()> { 276 | self.0.refresh() 277 | } 278 | 279 | /// Writes text at the given position within the screen buffer. 280 | /// 281 | /// Any non-printable characters, such as escape sequences, will be ignored. 282 | pub fn write_at(&self, position: C, text: &str) 283 | where C: Into { 284 | self.0.write_at(position.into(), text); 285 | } 286 | 287 | /// Writes text with the given attributes at the current cursor position. 288 | /// 289 | /// Any non-printable characters, such as escape sequences, will be ignored. 290 | pub fn write_styled(&self, fg: F, bg: B, style: S, text: &str) where 291 | F: Into>, 292 | B: Into>, 293 | S: Into>, 294 | { 295 | self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), text); 296 | } 297 | 298 | /// Writes text with the given attributes at the given position within 299 | /// the screen buffer. 300 | /// 301 | /// Any non-printable characters, such as escape sequences, will be ignored. 302 | pub fn write_styled_at(&self, position: C, 303 | fg: F, bg: B, style: S, text: &str) where 304 | C: Into, 305 | F: Into>, 306 | B: Into>, 307 | S: Into>, 308 | { 309 | self.0.write_styled_at(position.into(), 310 | fg.into(), bg.into(), style.into().unwrap_or_default(), text); 311 | } 312 | 313 | /// Writes a single character at the cursor position 314 | /// using the current style and color settings. 315 | /// 316 | /// If the character is a non-printable character, it will be ignored. 317 | pub fn write_char(&self, ch: char) { 318 | self.0.write_char(ch); 319 | } 320 | 321 | /// Writes a string at the cursor position 322 | /// using the current style and color settings. 323 | /// 324 | /// Any non-printable characters, such as escape sequences, will be ignored. 325 | pub fn write_str(&self, s: &str) { 326 | self.0.write_str(s); 327 | } 328 | 329 | /// Writes formatted text at the cursor position 330 | /// using the current style and color settings. 331 | /// 332 | /// This method enables `Screen` to be used as the receiver to 333 | /// the [`write!`] and [`writeln!`] macros. 334 | /// 335 | /// Any non-printable characters, such as escape sequences, will be ignored. 336 | /// 337 | /// # Examples 338 | /// 339 | /// ```no_run 340 | /// # use std::io; 341 | /// # use mortal::Screen; 342 | /// # fn example() -> io::Result<()> { 343 | /// let screen = Screen::new(Default::default())?; 344 | /// 345 | /// writeln!(screen, "Hello, world!"); 346 | /// # Ok(()) 347 | /// # } 348 | /// ``` 349 | /// 350 | /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html 351 | /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html 352 | pub fn write_fmt(&self, args: fmt::Arguments) { 353 | let s = args.to_string(); 354 | self.write_str(&s) 355 | } 356 | 357 | #[doc(hidden)] 358 | pub fn borrow_term_write_guard(&self) -> ScreenWriteGuard { 359 | self.lock_write().unwrap() 360 | } 361 | } 362 | 363 | impl<'a> ScreenReadGuard<'a> { 364 | /// Waits for an event from the terminal. 365 | /// 366 | /// Returns `Ok(false)` if `timeout` elapses without an event occurring. 367 | /// 368 | /// If `timeout` is `None`, this method will wait indefinitely. 369 | /// 370 | /// # Notes 371 | /// 372 | /// Some low-level terminal events may not generate an `Event` value. 373 | /// Therefore, this method may return `Ok(true)`, while a follow-up call 374 | /// to `read_event` may not immediately return an event. 375 | pub fn wait_event(&mut self, timeout: Option) -> io::Result { 376 | self.0.wait_event(timeout) 377 | } 378 | 379 | /// Reads an event from the terminal. 380 | /// 381 | /// If `timeout` elapses without an event occurring, this method will return 382 | /// `Ok(None)`. 383 | /// 384 | /// If `timeout` is `None`, this method will wait indefinitely. 385 | pub fn read_event(&mut self, timeout: Option) -> io::Result> { 386 | self.0.read_event(timeout) 387 | } 388 | } 389 | 390 | impl<'a> ScreenWriteGuard<'a> { 391 | /// Returns the current size of the terminal screen. 392 | #[inline] 393 | pub fn size(&self) -> Size { 394 | self.0.size() 395 | } 396 | 397 | /// Sets the cursor position. 398 | #[inline] 399 | pub fn cursor(&self) -> Cursor { 400 | self.0.cursor() 401 | } 402 | 403 | /// Moves the cursor to the given column on the next line. 404 | #[inline] 405 | pub fn set_cursor>(&mut self, pos: C) { 406 | self.0.set_cursor(pos.into()); 407 | } 408 | 409 | /// Set the current cursor mode. 410 | #[inline] 411 | pub fn next_line(&mut self, column: usize) { 412 | self.0.next_line(column); 413 | } 414 | 415 | /// Set the current cursor mode. 416 | /// 417 | /// This setting is a visible hint to the user. 418 | /// It produces no change in behavior. 419 | /// 420 | /// # Notes 421 | /// 422 | /// On Unix systems, this setting may have no effect. 423 | pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> { 424 | self.0.set_cursor_mode(mode) 425 | } 426 | 427 | /// Adds a set of `Style` flags to the current style setting. 428 | pub fn clear_screen(&mut self) { 429 | self.0.clear_screen(); 430 | } 431 | 432 | /// Removes a set of `Style` flags to the current style setting. 433 | /// Adds a set of `Style` flags to the current style setting. 434 | #[inline] 435 | pub fn add_style(&mut self, style: Style) { 436 | self.0.add_style(style) 437 | } 438 | 439 | /// Sets the current style setting to the given set of flags. 440 | #[inline] 441 | pub fn remove_style(&mut self, style: Style) { 442 | self.0.remove_style(style) 443 | } 444 | 445 | /// Sets or removes foreground text color. 446 | #[inline] 447 | pub fn set_style>>(&mut self, style: S) { 448 | self.0.set_style(style.into().unwrap_or_default()) 449 | } 450 | 451 | /// Sets or removes background text color. 452 | #[inline] 453 | pub fn set_fg>>(&mut self, fg: C) { 454 | self.0.set_fg(fg.into()) 455 | } 456 | 457 | /// Removes color and style attributes. 458 | #[inline] 459 | pub fn set_bg>>(&mut self, bg: C) { 460 | self.0.set_bg(bg.into()) 461 | } 462 | 463 | /// Sets all attributes for the screen. 464 | #[inline] 465 | pub fn set_theme(&mut self, theme: Theme) { 466 | self.0.set_theme(theme) 467 | } 468 | 469 | /// Adds bold to the current style setting. 470 | #[inline] 471 | pub fn clear_attributes(&mut self) { 472 | self.0.clear_attributes() 473 | } 474 | 475 | /// Adds bold to the current style setting. 476 | /// 477 | /// This is equivalent to `self.add_style(Style::BOLD)`. 478 | #[inline] 479 | pub fn bold(&mut self) { 480 | self.add_style(Style::BOLD) 481 | } 482 | 483 | /// Adds italic to the current style setting. 484 | /// 485 | /// This is equivalent to `self.add_style(Style::ITALIC)`. 486 | #[inline] 487 | pub fn italic(&mut self) { 488 | self.add_style(Style::ITALIC); 489 | } 490 | 491 | /// Adds underline to the current style setting. 492 | /// 493 | /// This is equivalent to `self.add_style(Style::UNDERLINE)`. 494 | #[inline] 495 | pub fn underline(&mut self) { 496 | self.add_style(Style::UNDERLINE) 497 | } 498 | 499 | /// Adds reverse to the current style setting. 500 | /// 501 | /// This is equivalent to `self.add_style(Style::REVERSE)`. 502 | #[inline] 503 | pub fn reverse(&mut self) { 504 | self.add_style(Style::REVERSE) 505 | } 506 | 507 | /// Renders the internal buffer to the terminal screen. 508 | /// 509 | /// This is called automatically when the `ScreenWriteGuard` is dropped. 510 | pub fn refresh(&mut self) -> io::Result<()> { 511 | self.0.refresh() 512 | } 513 | 514 | /// Writes text at the given position within the screen buffer. 515 | /// 516 | /// Any non-printable characters, such as escape sequences, will be ignored. 517 | pub fn write_at(&mut self, position: C, text: &str) 518 | where C: Into { 519 | self.0.write_at(position.into(), text) 520 | } 521 | 522 | /// Writes text with the given attributes at the current cursor position. 523 | /// 524 | /// Any non-printable characters, such as escape sequences, will be ignored. 525 | pub fn write_styled(&mut self, fg: F, bg: B, style: S, text: &str) where 526 | F: Into>, 527 | B: Into>, 528 | S: Into>, 529 | { 530 | self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), text) 531 | } 532 | 533 | /// Writes text with the given attributes at the given position within 534 | /// the screen buffer. 535 | /// 536 | /// Any non-printable characters, such as escape sequences, will be ignored. 537 | pub fn write_styled_at(&mut self, position: C, 538 | fg: F, bg: B, style: S, text: &str) where 539 | C: Into, 540 | F: Into>, 541 | B: Into>, 542 | S: Into>, 543 | { 544 | self.0.write_styled_at(position.into(), 545 | fg.into(), bg.into(), style.into().unwrap_or_default(), text) 546 | } 547 | 548 | /// Writes a single character at the cursor position 549 | /// using the current style and color settings. 550 | /// 551 | /// If the character is a non-printable character, it will be ignored. 552 | pub fn write_char(&mut self, ch: char) { 553 | self.0.write_char(ch) 554 | } 555 | 556 | /// Writes a string at the cursor position 557 | /// using the current style and color settings. 558 | /// 559 | /// Any non-printable characters, such as escape sequences, will be ignored. 560 | pub fn write_str(&mut self, s: &str) { 561 | self.0.write_str(s) 562 | } 563 | 564 | /// Writes formatted text at the cursor position 565 | /// using the current style and color settings. 566 | /// 567 | /// This method enables `ScreenWriteGuard` to be used as the receiver to 568 | /// the [`write!`] and [`writeln!`] macros. 569 | /// 570 | /// Any non-printable characters, such as escape sequences, will be ignored. 571 | /// 572 | /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html 573 | /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html 574 | pub fn write_fmt(&mut self, args: fmt::Arguments) { 575 | let s = args.to_string(); 576 | self.write_str(&s) 577 | } 578 | 579 | #[doc(hidden)] 580 | pub fn borrow_term_write_guard(&mut self) -> &mut Self { 581 | self 582 | } 583 | } 584 | 585 | #[cfg(unix)] 586 | impl crate::unix::TerminalExt for Screen { 587 | fn read_raw(&mut self, buf: &mut [u8], timeout: Option) -> io::Result> { 588 | self.0.read_raw(buf, timeout) 589 | } 590 | } 591 | 592 | #[cfg(unix)] 593 | impl<'a> crate::unix::TerminalExt for ScreenReadGuard<'a> { 594 | fn read_raw(&mut self, buf: &mut [u8], timeout: Option) -> io::Result> { 595 | self.0.read_raw(buf, timeout) 596 | } 597 | } 598 | 599 | #[cfg(windows)] 600 | impl crate::windows::TerminalExt for Screen { 601 | fn read_raw(&mut self, buf: &mut [u16], timeout: Option) -> io::Result> { 602 | self.0.read_raw(buf, timeout) 603 | } 604 | 605 | fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD], 606 | timeout: Option) -> io::Result> { 607 | self.0.read_raw_event(events, timeout) 608 | } 609 | } 610 | 611 | #[cfg(windows)] 612 | impl<'a> crate::windows::TerminalExt for ScreenReadGuard<'a> { 613 | fn read_raw(&mut self, buf: &mut [u16], timeout: Option) -> io::Result> { 614 | self.0.read_raw(buf, timeout) 615 | } 616 | 617 | fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD], 618 | timeout: Option) -> io::Result> { 619 | self.0.read_raw_event(events, timeout) 620 | } 621 | } 622 | -------------------------------------------------------------------------------- /src/sequence.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for manipulating raw input sequences 2 | 3 | use std::fmt; 4 | use std::iter::FromIterator; 5 | use std::mem::replace; 6 | 7 | /// Contains a set of string sequences, mapped to a value. 8 | #[derive(Clone, Debug, Default)] 9 | pub struct SequenceMap { 10 | sequences: Vec<(K, V)>, 11 | } 12 | 13 | /// Represents the result of a `SequenceMap::find` operation. 14 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 15 | pub enum FindResult { 16 | /// No contained sequences begin with the provided input sequence. 17 | NotFound, 18 | /// One or more sequences begin with the provided input sequence, 19 | /// but the sequence does not represent a complete sequence. 20 | Incomplete, 21 | /// A sequence was found exactly matching the input sequence; 22 | /// additionally, one or more sequences begin with the input sequence. 23 | Undecided(V), 24 | /// A sequence was found exactly matching the input sequence; 25 | /// no additional partially-matching sequences exist. 26 | Found(V), 27 | } 28 | 29 | impl<'a, V: Clone> FindResult<&'a V> { 30 | /// Maps `FindResult<&V>` to `FindResult` by cloning the contents 31 | /// of the result value. 32 | pub fn cloned(self) -> FindResult { 33 | match self { 34 | FindResult::NotFound => FindResult::NotFound, 35 | FindResult::Incomplete => FindResult::Incomplete, 36 | FindResult::Undecided(v) => FindResult::Undecided(v.clone()), 37 | FindResult::Found(v) => FindResult::Found(v.clone()), 38 | } 39 | } 40 | } 41 | 42 | impl, V> SequenceMap { 43 | /// Creates an empty `SequenceMap`. 44 | pub fn new() -> SequenceMap { 45 | SequenceMap::with_capacity(0) 46 | } 47 | 48 | /// Creates an empty `SequenceMap` with allocated capacity for `n` elements. 49 | pub fn with_capacity(n: usize) -> SequenceMap { 50 | SequenceMap{ 51 | sequences: Vec::with_capacity(n), 52 | } 53 | } 54 | 55 | /// Returns a slice of all contained sequences, sorted by key. 56 | pub fn sequences(&self) -> &[(K, V)] { 57 | &self.sequences 58 | } 59 | 60 | /// Returns a mutable slice of all contained sequences, sorted by key. 61 | /// 62 | /// # Note 63 | /// 64 | /// Elements must remain sorted by key for the proper functioning of 65 | /// `SequenceMap` operations. If keys are modified, the caller must ensure 66 | /// that the slice is sorted. 67 | pub fn sequences_mut(&mut self) -> &mut [(K, V)] { 68 | &mut self.sequences 69 | } 70 | 71 | /// Returns an `Entry` for the given key. 72 | /// 73 | /// This API matches the entry API for the standard `HashMap` collection. 74 | pub fn entry(&mut self, key: K) -> Entry { 75 | match self.search(key.as_ref()) { 76 | Ok(n) => Entry::Occupied(OccupiedEntry{ 77 | map: self, 78 | index: n, 79 | }), 80 | Err(n) => Entry::Vacant(VacantEntry{ 81 | map: self, 82 | key, 83 | index: n, 84 | }) 85 | } 86 | } 87 | 88 | /// Performs a search for a partial or complete sequence match. 89 | pub fn find(&self, key: &str) -> FindResult<&V> { 90 | let (n, found) = match self.search(key) { 91 | Ok(n) => (n, true), 92 | Err(n) => (n, false) 93 | }; 94 | 95 | let incomplete = self.sequences.get(n + (found as usize)) 96 | .map_or(false, |&(ref next, _)| next.as_ref().starts_with(key)); 97 | 98 | match (found, incomplete) { 99 | (false, false) => FindResult::NotFound, 100 | (false, true) => FindResult::Incomplete, 101 | (true, false) => FindResult::Found(&self.sequences[n].1), 102 | (true, true) => FindResult::Undecided(&self.sequences[n].1), 103 | } 104 | } 105 | 106 | /// Returns the corresponding value for the given sequence. 107 | pub fn get(&self, key: &str) -> Option<&V> { 108 | match self.search(key) { 109 | Ok(n) => Some(&self.sequences[n].1), 110 | Err(_) => None 111 | } 112 | } 113 | 114 | /// Returns a mutable reference to the corresponding value for the given sequence. 115 | pub fn get_mut(&mut self, key: &str) -> Option<&mut V> { 116 | match self.search(key) { 117 | Ok(n) => Some(&mut self.sequences[n].1), 118 | Err(_) => None 119 | } 120 | } 121 | 122 | /// Inserts a key-value pair into the map. 123 | /// 124 | /// If the key already exists in the map, the new value will replace the old 125 | /// value and the old value will be returned. 126 | pub fn insert(&mut self, key: K, value: V) -> Option { 127 | match self.search(key.as_ref()) { 128 | Ok(n) => Some(replace(&mut self.sequences[n], (key, value)).1), 129 | Err(n) => { 130 | self.sequences.insert(n, (key, value)); 131 | None 132 | } 133 | } 134 | } 135 | 136 | /// Removes a key-value pair from the map. 137 | pub fn remove(&mut self, key: &str) -> Option<(K, V)> { 138 | match self.search(key) { 139 | Ok(n) => Some(self.sequences.remove(n)), 140 | Err(_) => None 141 | } 142 | } 143 | 144 | fn search(&self, key: &str) -> Result { 145 | self.sequences.binary_search_by_key(&key, |&(ref k, _)| &k.as_ref()) 146 | } 147 | } 148 | 149 | impl, V> From> for SequenceMap { 150 | /// Creates a `SequenceMap` from a `Vec` of key-value pairs. 151 | /// 152 | /// The input `Vec` will be sorted and deduplicated. 153 | /// 154 | /// If two elements exist with the same key, the first element is used. 155 | fn from(mut sequences: Vec<(K, V)>) -> SequenceMap { 156 | sequences.sort_by(|a, b| a.0.as_ref().cmp(b.0.as_ref())); 157 | sequences.dedup_by(|a, b| a.0.as_ref() == b.0.as_ref()); 158 | 159 | SequenceMap{sequences} 160 | } 161 | } 162 | 163 | impl, V> FromIterator<(K, V)> for SequenceMap { 164 | /// Creates a `SequenceMap` from an iterator of key-value pairs. 165 | /// 166 | /// If two elements exist with the same key, the last element is used. 167 | fn from_iter>(iter: I) -> Self { 168 | let iter = iter.into_iter(); 169 | let mut map = SequenceMap::with_capacity(iter.size_hint().0); 170 | 171 | for (k, v) in iter { 172 | map.insert(k, v); 173 | } 174 | 175 | map 176 | } 177 | } 178 | 179 | /// A view into a single entry of a `SequenceMap`, which may be either occupied 180 | /// or vacant. 181 | /// 182 | /// This value is returned from the [`SequenceMap::entry`] method. 183 | /// 184 | /// [`SequenceMap::entry`]: struct.SequenceMap.html#method.entry 185 | pub enum Entry<'a, K: 'a, V: 'a> { 186 | /// An occupied entry 187 | Occupied(OccupiedEntry<'a, K, V>), 188 | /// A vacant entry 189 | Vacant(VacantEntry<'a, K, V>), 190 | } 191 | 192 | /// A view into an occupied entry in a `SequenceMap`. 193 | pub struct OccupiedEntry<'a, K: 'a, V: 'a> { 194 | map: &'a mut SequenceMap, 195 | index: usize, 196 | } 197 | 198 | /// A view into a vacant entry in a `SequenceMap`. 199 | pub struct VacantEntry<'a, K: 'a, V: 'a> { 200 | map: &'a mut SequenceMap, 201 | key: K, 202 | index: usize, 203 | } 204 | 205 | impl<'a, K, V> Entry<'a, K, V> { 206 | /// Provides in-place mutable access to an occupied entry before any 207 | /// potential inserts into the map. 208 | pub fn and_modify(self, f: F) -> Self { 209 | match self { 210 | Entry::Occupied(mut ent) => { 211 | f(ent.get_mut()); 212 | Entry::Occupied(ent) 213 | } 214 | Entry::Vacant(ent) => Entry::Vacant(ent) 215 | } 216 | } 217 | 218 | /// Returns a mutable reference to the entry value, 219 | /// inserting the provided default if the entry is vacant. 220 | pub fn or_insert(self, default: V) -> &'a mut V { 221 | match self { 222 | Entry::Occupied(ent) => ent.into_mut(), 223 | Entry::Vacant(ent) => ent.insert(default) 224 | } 225 | } 226 | 227 | /// Returns a mutable reference to the entry value, 228 | /// inserting a value using the provided closure if the entry is vacant. 229 | pub fn or_insert_with V>(self, default: F) -> &'a mut V { 230 | match self { 231 | Entry::Occupied(ent) => ent.into_mut(), 232 | Entry::Vacant(ent) => ent.insert(default()) 233 | } 234 | } 235 | 236 | /// Returns a borrowed reference to the entry key. 237 | pub fn key(&self) -> &K { 238 | match *self { 239 | Entry::Occupied(ref ent) => ent.key(), 240 | Entry::Vacant(ref ent) => ent.key(), 241 | } 242 | } 243 | } 244 | 245 | impl<'a, K, V> OccupiedEntry<'a, K, V> { 246 | /// Returns a borrowed reference to the entry key. 247 | pub fn key(&self) -> &K { 248 | &self.map.sequences[self.index].0 249 | } 250 | 251 | /// Returns a borrowed reference to the entry value. 252 | pub fn get(&self) -> &V { 253 | &self.map.sequences[self.index].1 254 | } 255 | 256 | /// Returns a mutable reference to the entry value. 257 | pub fn get_mut(&mut self) -> &mut V { 258 | &mut self.map.sequences[self.index].1 259 | } 260 | 261 | /// Converts the `OccupiedEntry` into a mutable reference whose lifetime 262 | /// is bound to the `SequenceMap`. 263 | pub fn into_mut(self) -> &'a mut V { 264 | &mut self.map.sequences[self.index].1 265 | } 266 | 267 | /// Replaces the entry value with the given value, returning the previous value. 268 | pub fn insert(&mut self, value: V) -> V { 269 | replace(self.get_mut(), value) 270 | } 271 | 272 | /// Removes the entry and returns the value. 273 | pub fn remove(self) -> V { 274 | self.map.sequences.remove(self.index).1 275 | } 276 | 277 | /// Removes the entry and returns the key-value pair. 278 | pub fn remove_entry(self) -> (K, V) { 279 | self.map.sequences.remove(self.index) 280 | } 281 | } 282 | 283 | impl<'a, K, V> VacantEntry<'a, K, V> { 284 | /// Returns a borrowed reference to the entry key. 285 | pub fn key(&self) -> &K { 286 | &self.key 287 | } 288 | 289 | /// Consumes the `VacantEntry` and returns ownership of the key. 290 | pub fn into_key(self) -> K { 291 | self.key 292 | } 293 | 294 | /// Consumes the `VacantEntry` and inserts a value, returning a mutable 295 | /// reference to its place in the `SequenceMap`. 296 | pub fn insert(self, value: V) -> &'a mut V { 297 | self.map.sequences.insert(self.index, (self.key, value)); 298 | &mut self.map.sequences[self.index].1 299 | } 300 | } 301 | 302 | impl<'a, K: fmt::Debug, V: fmt::Debug> fmt::Debug for Entry<'a, K, V> { 303 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 304 | match *self { 305 | Entry::Occupied(ref ent) => 306 | f.debug_tuple("Entry") 307 | .field(ent) 308 | .finish(), 309 | Entry::Vacant(ref ent) => 310 | f.debug_tuple("Entry") 311 | .field(ent) 312 | .finish() 313 | } 314 | } 315 | } 316 | 317 | impl<'a, K: fmt::Debug, V: fmt::Debug> fmt::Debug for OccupiedEntry<'a, K, V> { 318 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 319 | f.debug_struct("OccupiedEntry") 320 | .field("key", self.key()) 321 | .field("value", self.get()) 322 | .finish() 323 | } 324 | } 325 | 326 | impl<'a, K: fmt::Debug, V> fmt::Debug for VacantEntry<'a, K, V> { 327 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 328 | f.debug_tuple("VacantEntry") 329 | .field(self.key()) 330 | .finish() 331 | } 332 | } 333 | 334 | #[cfg(test)] 335 | mod test { 336 | use super::{FindResult, SequenceMap}; 337 | 338 | #[test] 339 | fn test_seq_map_get() { 340 | let mut m = SequenceMap::new(); 341 | 342 | m.insert("ab", 0); 343 | m.insert("ac", 1); 344 | 345 | assert_eq!(m.get("a").cloned(), None); 346 | assert_eq!(m.get("aa").cloned(), None); 347 | assert_eq!(m.get("ab").cloned(), Some(0)); 348 | assert_eq!(m.get("ac").cloned(), Some(1)); 349 | } 350 | 351 | #[test] 352 | fn test_seq_map_find() { 353 | let mut m = SequenceMap::new(); 354 | 355 | m.insert("a", 0); 356 | m.insert("abcd", 1); 357 | 358 | m.insert("bcd", 2); 359 | m.insert("bce", 3); 360 | 361 | m.insert("cd", 4); 362 | m.insert("cde", 5); 363 | m.insert("cdf", 6); 364 | 365 | assert_eq!(m.find("a").cloned(), FindResult::Undecided(0)); 366 | assert_eq!(m.find("ab").cloned(), FindResult::Incomplete); 367 | assert_eq!(m.find("abc").cloned(), FindResult::Incomplete); 368 | assert_eq!(m.find("abcd").cloned(), FindResult::Found(1)); 369 | 370 | assert_eq!(m.find("b").cloned(), FindResult::Incomplete); 371 | assert_eq!(m.find("bc").cloned(), FindResult::Incomplete); 372 | assert_eq!(m.find("bcd").cloned(), FindResult::Found(2)); 373 | assert_eq!(m.find("bce").cloned(), FindResult::Found(3)); 374 | 375 | assert_eq!(m.find("c").cloned(), FindResult::Incomplete); 376 | assert_eq!(m.find("cd").cloned(), FindResult::Undecided(4)); 377 | assert_eq!(m.find("cde").cloned(), FindResult::Found(5)); 378 | assert_eq!(m.find("cdf").cloned(), FindResult::Found(6)); 379 | 380 | assert_eq!(m.find("d").cloned(), FindResult::NotFound); 381 | } 382 | 383 | #[test] 384 | fn test_seq_map_insert() { 385 | let mut m = SequenceMap::new(); 386 | 387 | assert_eq!(m.insert("a", 0), None); 388 | assert_eq!(m.insert("b", 1), None); 389 | assert_eq!(m.insert("a", 2), Some(0)); 390 | } 391 | 392 | #[test] 393 | fn test_seq_map_entry() { 394 | let mut m = SequenceMap::new(); 395 | 396 | assert_eq!(*m.entry("a").or_insert(0), 0); 397 | assert_eq!(m.get("a").cloned(), Some(0)); 398 | assert_eq!(*m.entry("a").or_insert(1), 0); 399 | } 400 | 401 | #[test] 402 | fn test_seq_map_from() { 403 | let m: SequenceMap<&'static str, u32> = [ 404 | ("foo", 0), 405 | ("bar", 1), 406 | ("baz", 2), 407 | ("foo", 3), 408 | ].iter().cloned().collect(); 409 | 410 | assert_eq!(m.sequences(), [ 411 | ("bar", 1), 412 | ("baz", 2), 413 | ("foo", 3), 414 | ]); 415 | 416 | let m = SequenceMap::from(vec![ 417 | ("foo", 0), 418 | ("bar", 1), 419 | ("baz", 2), 420 | ("foo", 3), 421 | ]); 422 | 423 | assert_eq!(m.sequences(), [ 424 | ("bar", 1), 425 | ("baz", 2), 426 | ("foo", 0), 427 | ]); 428 | } 429 | 430 | #[test] 431 | fn test_seq_map_types() { 432 | // Ensure that many str-like types can serve as SequenceMap key type 433 | use std::borrow::Cow; 434 | use std::rc::Rc; 435 | use std::sync::Arc; 436 | 437 | struct Foo<'a> { 438 | _a: SequenceMap<&'a str, ()>, 439 | _b: SequenceMap, ()>, 440 | } 441 | 442 | let _ = Foo{ 443 | _a: SequenceMap::new(), 444 | _b: SequenceMap::new(), 445 | }; 446 | 447 | SequenceMap::<&'static str, ()>::new(); 448 | SequenceMap::::new(); 449 | SequenceMap::, ()>::new(); 450 | SequenceMap::, ()>::new(); 451 | SequenceMap::, ()>::new(); 452 | SequenceMap::, ()>::new(); 453 | } 454 | } 455 | -------------------------------------------------------------------------------- /src/signal.rs: -------------------------------------------------------------------------------- 1 | //! Contains types relating to operating system signals 2 | 3 | use std::fmt; 4 | use std::iter::FromIterator; 5 | use std::ops; 6 | 7 | /// Signal received through a terminal device 8 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 9 | pub enum Signal { 10 | /// Break signal (`CTRL_BREAK_EVENT`); Windows only 11 | Break, 12 | /// Continue signal (`SIGCONT`); Unix only 13 | Continue, 14 | /// Interrupt signal (`SIGINT` on Unix, `CTRL_C_EVENT` on Windows) 15 | Interrupt, 16 | /// Terminal window resize (`SIGWINCH` on Unix, 17 | /// `WINDOW_BUFFER_SIZE_EVENT` on Windows) 18 | /// 19 | /// When this signal is received, it will be translated into an 20 | /// `Event::Resize(_)` value containing the new size of the terminal. 21 | Resize, 22 | /// Suspend signal (`SIGTSTP`); Unix only 23 | Suspend, 24 | /// Quit signal (`SIGQUIT`); Unix only 25 | Quit, 26 | } 27 | 28 | const NUM_SIGNALS: u8 = 6; 29 | 30 | impl Signal { 31 | fn as_bit(&self) -> u8 { 32 | 1 << (*self as u8) 33 | } 34 | 35 | fn all_bits() -> u8 { 36 | (1 << NUM_SIGNALS) - 1 37 | } 38 | } 39 | 40 | impl ops::BitOr for Signal { 41 | type Output = SignalSet; 42 | 43 | fn bitor(self, rhs: Signal) -> SignalSet { 44 | let mut set = SignalSet::new(); 45 | 46 | set.insert(self); 47 | set.insert(rhs); 48 | set 49 | } 50 | } 51 | 52 | impl ops::Not for Signal { 53 | type Output = SignalSet; 54 | 55 | fn not(self) -> SignalSet { 56 | !SignalSet::from(self) 57 | } 58 | } 59 | 60 | /// Represents a set of `Signal` values 61 | #[derive(Copy, Clone, Default, Eq, PartialEq)] 62 | pub struct SignalSet(u8); 63 | 64 | impl SignalSet { 65 | /// Returns an empty `SignalSet`. 66 | pub fn new() -> SignalSet { 67 | SignalSet(0) 68 | } 69 | 70 | /// Returns a `SignalSet` containing all available signals. 71 | pub fn all() -> SignalSet { 72 | SignalSet(Signal::all_bits()) 73 | } 74 | 75 | /// Returns whether this set contains the given `Signal`. 76 | pub fn contains(&self, sig: Signal) -> bool { 77 | self.0 & sig.as_bit() != 0 78 | } 79 | 80 | /// Returns whether this set contains all signals present in another set. 81 | pub fn contains_all(&self, other: SignalSet) -> bool { 82 | self.0 & other.0 == other.0 83 | } 84 | 85 | /// Returns whether this set contains any signals present in another set. 86 | pub fn intersects(&self, other: SignalSet) -> bool { 87 | self.0 & other.0 != 0 88 | } 89 | 90 | /// Returns whether this set contains any signals. 91 | pub fn is_empty(&self) -> bool { 92 | self.0 == 0 93 | } 94 | 95 | /// Inserts a `Signal` into this set. 96 | pub fn insert(&mut self, sig: Signal) { 97 | self.0 |= sig.as_bit(); 98 | } 99 | 100 | /// Removes a `Signal` from this set. 101 | pub fn remove(&mut self, sig: Signal) { 102 | self.0 &= !sig.as_bit(); 103 | } 104 | 105 | /// Sets whether this set contains the given `Signal`. 106 | pub fn set(&mut self, sig: Signal, set: bool) { 107 | if set { 108 | self.insert(sig); 109 | } else { 110 | self.remove(sig); 111 | } 112 | } 113 | 114 | /// Returns the difference of two sets. 115 | /// 116 | /// The result is all signals contained in `self`, except for those 117 | /// also contained in `other`. 118 | /// 119 | /// This is equivalent to `self - other` or `self & !other`. 120 | pub fn difference(&self, other: SignalSet) -> SignalSet { 121 | SignalSet(self.0 & !other.0) 122 | } 123 | 124 | /// Returns the symmetric difference of two sets. 125 | /// 126 | /// The result is all signals contained in either set, but not those contained 127 | /// in both. 128 | /// 129 | /// This is equivalent to `self ^ other`. 130 | pub fn symmetric_difference(&self, other: SignalSet) -> SignalSet { 131 | SignalSet(self.0 ^ other.0) 132 | } 133 | 134 | /// Returns the intersection of two sets. 135 | /// 136 | /// The result is all signals contained in both sets, but not those contained 137 | /// in either one or the other. 138 | /// 139 | /// This is equivalent to `self & other`. 140 | pub fn intersection(&self, other: SignalSet) -> SignalSet { 141 | SignalSet(self.0 & other.0) 142 | } 143 | 144 | /// Returns the union of two sets. 145 | /// 146 | /// The result is all signals contained in either or both sets. 147 | /// 148 | /// This is equivalent to `self | other`. 149 | pub fn union(&self, other: SignalSet) -> SignalSet { 150 | SignalSet(self.0 | other.0) 151 | } 152 | 153 | /// Returns the inverse of the set. 154 | /// 155 | /// The result is all valid signals not contained in this set. 156 | /// 157 | /// This is equivalent to `!self`. 158 | pub fn inverse(&self) -> SignalSet { 159 | SignalSet(!self.0 & Signal::all_bits()) 160 | } 161 | } 162 | 163 | impl fmt::Debug for SignalSet { 164 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 165 | const SIGNALS: &[Signal] = &[ 166 | Signal::Break, 167 | Signal::Continue, 168 | Signal::Interrupt, 169 | Signal::Resize, 170 | Signal::Suspend, 171 | Signal::Quit, 172 | ]; 173 | 174 | let mut first = true; 175 | 176 | f.write_str("SignalSet(")?; 177 | 178 | for &sig in SIGNALS { 179 | if self.contains(sig) { 180 | if !first { 181 | f.write_str(" | ")?; 182 | } 183 | 184 | write!(f, "{:?}", sig)?; 185 | first = false; 186 | } 187 | } 188 | 189 | f.write_str(")") 190 | } 191 | } 192 | 193 | impl From for SignalSet { 194 | fn from(sig: Signal) -> SignalSet { 195 | let mut set = SignalSet::new(); 196 | set.insert(sig); 197 | set 198 | } 199 | } 200 | 201 | impl Extend for SignalSet { 202 | fn extend>(&mut self, iter: I) { 203 | for sig in iter { 204 | self.insert(sig); 205 | } 206 | } 207 | } 208 | 209 | impl FromIterator for SignalSet { 210 | fn from_iter>(iter: I) -> SignalSet { 211 | let mut set = SignalSet::new(); 212 | 213 | set.extend(iter); 214 | set 215 | } 216 | } 217 | 218 | macro_rules! impl_op { 219 | ( $tr:ident , $tr_meth:ident , $method:ident ) => { 220 | impl ops::$tr for SignalSet { 221 | type Output = SignalSet; 222 | 223 | fn $tr_meth(self, rhs: SignalSet) -> SignalSet { 224 | self.$method(rhs) 225 | } 226 | } 227 | } 228 | } 229 | 230 | macro_rules! impl_mut_op { 231 | ( $tr:ident , $tr_meth:ident , $method:ident ) => { 232 | impl ops::$tr for SignalSet { 233 | fn $tr_meth(&mut self, rhs: SignalSet) { 234 | *self = self.$method(rhs); 235 | } 236 | } 237 | } 238 | } 239 | 240 | macro_rules! impl_unary_op { 241 | ( $tr:ident , $tr_meth:ident , $method:ident ) => { 242 | impl ops::$tr for SignalSet { 243 | type Output = SignalSet; 244 | 245 | fn $tr_meth(self) -> SignalSet { 246 | self.$method() 247 | } 248 | } 249 | } 250 | } 251 | 252 | impl_op!{ BitAnd, bitand, intersection } 253 | impl_op!{ BitOr, bitor, union } 254 | impl_op!{ BitXor, bitxor, symmetric_difference } 255 | impl_op!{ Sub, sub, difference } 256 | 257 | impl_unary_op!{ Not, not, inverse } 258 | 259 | impl_mut_op!{ BitAndAssign, bitand_assign, intersection } 260 | impl_mut_op!{ BitOrAssign, bitor_assign, union } 261 | impl_mut_op!{ BitXorAssign, bitxor_assign, symmetric_difference } 262 | impl_mut_op!{ SubAssign, sub_assign, difference } 263 | 264 | #[cfg(test)] 265 | mod test { 266 | use super::{Signal, SignalSet}; 267 | 268 | #[test] 269 | fn test_signal_set() { 270 | let mut set = SignalSet::new(); 271 | 272 | assert!(set.is_empty()); 273 | 274 | set.insert(Signal::Break); 275 | 276 | assert!(set.contains(Signal::Break)); 277 | 278 | set |= Signal::Continue | Signal::Interrupt; 279 | 280 | assert!(set.contains(Signal::Break)); 281 | assert!(set.contains(Signal::Continue)); 282 | assert!(set.contains(Signal::Interrupt)); 283 | 284 | set ^= Signal::Interrupt | Signal::Quit; 285 | 286 | assert!(set.contains(Signal::Break)); 287 | assert!(!set.contains(Signal::Interrupt)); 288 | assert!(set.contains(Signal::Quit)); 289 | 290 | set &= Signal::Break | Signal::Suspend; 291 | 292 | assert!(set.contains(Signal::Break)); 293 | assert!(!set.contains(Signal::Continue)); 294 | assert!(!set.contains(Signal::Interrupt)); 295 | assert!(!set.contains(Signal::Suspend)); 296 | assert!(!set.contains(Signal::Quit)); 297 | 298 | set -= SignalSet::from(Signal::Break); 299 | 300 | assert!(!set.contains(Signal::Break)); 301 | } 302 | 303 | #[test] 304 | fn test_signal_set_all() { 305 | let mut all = SignalSet::all(); 306 | 307 | assert!(all.contains(Signal::Break)); 308 | assert!(all.contains(Signal::Continue)); 309 | assert!(all.contains(Signal::Interrupt)); 310 | assert!(all.contains(Signal::Resize)); 311 | assert!(all.contains(Signal::Suspend)); 312 | assert!(all.contains(Signal::Quit)); 313 | 314 | assert_eq!(all, !SignalSet::new()); 315 | assert_eq!(!all, SignalSet::new()); 316 | 317 | all.remove(Signal::Break); 318 | all.remove(Signal::Continue); 319 | all.remove(Signal::Interrupt); 320 | all.remove(Signal::Resize); 321 | all.remove(Signal::Suspend); 322 | all.remove(Signal::Quit); 323 | 324 | assert_eq!(all.0, 0); 325 | } 326 | 327 | #[test] 328 | fn test_signal_set_debug() { 329 | let mut set = SignalSet::new(); 330 | 331 | assert_eq!(format!("{:?}", set), "SignalSet()"); 332 | 333 | set.insert(Signal::Break); 334 | assert_eq!(format!("{:?}", set), "SignalSet(Break)"); 335 | 336 | set.insert(Signal::Continue); 337 | assert_eq!(format!("{:?}", set), "SignalSet(Break | Continue)"); 338 | 339 | set.insert(Signal::Interrupt); 340 | assert_eq!(format!("{:?}", set), "SignalSet(Break | Continue | Interrupt)"); 341 | 342 | set = SignalSet::all(); 343 | assert_eq!(format!("{:?}", set), 344 | "SignalSet(Break | Continue | Interrupt | Resize | Suspend | Quit)"); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | //! Provides an interface to terminal devices 2 | 3 | use std::fmt; 4 | use std::io; 5 | use std::sync::{LockResult, TryLockResult}; 6 | use std::time::Duration; 7 | 8 | use crate::priv_util::{map_lock_result, map_try_lock_result}; 9 | use crate::signal::{Signal, SignalSet}; 10 | use crate::sys; 11 | 12 | /// Represents a color attribute applied to text foreground or background. 13 | /// 14 | /// # Notes 15 | /// 16 | /// Names here correspond to possible default values for some systems. 17 | /// Because users may reconfigure the set of colors available in their terminal, 18 | /// these color values may correspond to different user-configured display colors. 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 20 | pub enum Color { 21 | /// Black 22 | Black, 23 | /// Blue 24 | Blue, 25 | /// Cyan 26 | Cyan, 27 | /// Green 28 | Green, 29 | /// Magenta 30 | Magenta, 31 | /// Red 32 | Red, 33 | /// White 34 | White, 35 | /// Yellow 36 | Yellow, 37 | } 38 | 39 | bitflags!{ 40 | /// Represents a set of style attributes applied to text. 41 | /// 42 | /// Some styles may not be supported on all systems. 43 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)] 44 | pub struct Style: u8 { 45 | /// Bold 46 | const BOLD = 1 << 0; 47 | /// Italic 48 | const ITALIC = 1 << 1; 49 | /// Reverse; foreground and background color swapped 50 | const REVERSE = 1 << 2; 51 | /// Underline 52 | const UNDERLINE = 1 << 3; 53 | } 54 | } 55 | 56 | /// Represents a terminal output theme. 57 | /// 58 | /// A theme consists of a foreground and background color as well as a style. 59 | #[derive(Copy, Clone, Debug, Default)] 60 | pub struct Theme { 61 | /// Foreground color 62 | pub fg: Option, 63 | /// Background color 64 | pub bg: Option, 65 | /// Style 66 | pub style: Style, 67 | } 68 | 69 | impl Theme { 70 | /// Creates a new theme with given values. 71 | /// 72 | /// # Note 73 | /// 74 | /// In order to create a Theme using default values you might want to use 75 | /// `Theme::default()` instead. 76 | pub fn new(fg: F, bg: B, style: S) -> Theme 77 | where 78 | F: Into>, 79 | B: Into>, 80 | S: Into> { 81 | Theme { 82 | fg: fg.into(), 83 | bg: bg.into(), 84 | style: style.into().unwrap_or_default(), 85 | } 86 | } 87 | 88 | /// Sets the foreground color on the given Theme and returns the new. 89 | pub fn fg(mut self, fg: F) -> Theme 90 | where F: Into> { 91 | self.fg = fg.into(); 92 | self 93 | } 94 | 95 | /// Sets the background color on the given Theme and returns the new. 96 | pub fn bg(mut self, bg: B) -> Theme 97 | where B: Into> { 98 | self.bg = bg.into(); 99 | self 100 | } 101 | 102 | /// Sets the style on the given Theme and returns the new. 103 | pub fn style(mut self, style: S) -> Theme 104 | where S: Into> { 105 | self.style = style.into().unwrap_or_default(); 106 | self 107 | } 108 | } 109 | 110 | /// Represents the cursor position in a terminal device 111 | #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] 112 | pub struct Cursor { 113 | /// Index of line in terminal, beginning at `0`. 114 | pub line: usize, 115 | /// Index of column in terminal, beginning at `0`. 116 | pub column: usize, 117 | } 118 | 119 | impl Cursor { 120 | /// Returns the position of the next cell within a terminal of the given size. 121 | /// 122 | /// Returns `None` if this cursor position represents the last cell. 123 | #[inline] 124 | pub fn next(&self, size: Size) -> Option { 125 | let mut line = self.line; 126 | let mut column = self.column + 1; 127 | 128 | if column >= size.columns { 129 | column = 0; 130 | line += 1; 131 | } 132 | 133 | if line >= size.lines { 134 | None 135 | } else { 136 | Some(Cursor{line, column}) 137 | } 138 | } 139 | 140 | /// Returns the position of the previous cell within a terminal of the given size. 141 | /// 142 | /// Returns `None` if this cursor position represents the first cell. 143 | #[inline] 144 | pub fn previous(&self, size: Size) -> Option { 145 | if self.column == 0 { 146 | if self.line == 0 { 147 | None 148 | } else { 149 | Some(Cursor{line: self.line - 1, column: size.columns - 1}) 150 | } 151 | } else { 152 | Some(Cursor{line: self.line, column: self.column - 1}) 153 | } 154 | } 155 | 156 | /// Returns a `Cursor` pointing to the first cell, i.e. `(0, 0)`. 157 | #[inline] 158 | pub fn first() -> Cursor { 159 | Cursor{ 160 | line: 0, 161 | column: 0, 162 | } 163 | } 164 | 165 | /// Returns a `Cursor` pointing to the last cell of a screen of the given size. 166 | #[inline] 167 | pub fn last(size: Size) -> Cursor { 168 | Cursor{ 169 | line: size.lines - 1, 170 | column: size.columns - 1, 171 | } 172 | } 173 | 174 | /// Returns whether the cursor is out of bounds of the given size. 175 | #[inline] 176 | pub fn is_out_of_bounds(&self, size: Size) -> bool { 177 | self.line >= size.lines || self.column >= size.columns 178 | } 179 | 180 | /// Returns the index of the cursor position within a one-dimensional array 181 | /// of the given size. 182 | pub(crate) fn as_index(&self, size: Size) -> usize { 183 | self.line * size.columns + self.column 184 | } 185 | } 186 | 187 | impl From<(usize, usize)> for Cursor { 188 | /// Returns a `Cursor` value from a `(line, column)` or `(y, x)` tuple. 189 | fn from((line, column): (usize, usize)) -> Cursor { 190 | Cursor{line, column} 191 | } 192 | } 193 | 194 | /// Represents the visual appearance of the cursor in the terminal 195 | /// 196 | /// Some cursor states may not be available on all systems. 197 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 198 | pub enum CursorMode { 199 | /// Normal mode 200 | Normal, 201 | /// Invisible mode 202 | Invisible, 203 | /// Overwrite mode 204 | Overwrite, 205 | } 206 | 207 | /// Represents an event generated from a terminal interface 208 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 209 | pub enum Event { 210 | /// Keyboard event 211 | Key(Key), 212 | /// Mouse event 213 | Mouse(MouseEvent), 214 | /// Raw data read 215 | /// 216 | /// A value of this variant can only be returned when using the 217 | /// platform-dependent extension trait, `TerminalExt`. 218 | /// 219 | /// On Unix, this trait is [`mortal::unix::TerminalExt`]. 220 | /// 221 | /// On Windows, this trait is [`mortal::windows::TerminalExt`]. 222 | /// 223 | /// [`mortal::unix::TerminalExt`]: ../unix/trait.TerminalExt.html 224 | /// [`mortal::windows::TerminalExt`]: ../windows/trait.TerminalExt.html 225 | Raw(usize), 226 | /// Terminal window size changed; contained value is the new size. 227 | Resize(Size), 228 | /// Terminal signal received 229 | Signal(Signal), 230 | /// No event 231 | /// 232 | /// Returned when a low-level terminal event does not correspond 233 | /// to a reported event type. 234 | NoEvent, 235 | } 236 | 237 | /// Represents a keyboard key press event 238 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 239 | pub enum Key { 240 | /// Backspace 241 | Backspace, 242 | /// Enter 243 | Enter, 244 | /// Escape 245 | Escape, 246 | /// Tab 247 | Tab, 248 | /// Up arrow 249 | Up, 250 | /// Down arrow 251 | Down, 252 | /// Left arrow 253 | Left, 254 | /// Right arrow 255 | Right, 256 | /// Delete 257 | Delete, 258 | /// Insert 259 | Insert, 260 | /// Home 261 | Home, 262 | /// End 263 | End, 264 | /// PageUp 265 | PageUp, 266 | /// PageDown 267 | PageDown, 268 | /// Character key 269 | Char(char), 270 | /// Control character 271 | /// 272 | /// # Notes 273 | /// 274 | /// The contained `char` value must always be lowercase; 275 | /// e.g. `Ctrl('a')` and **not** `Ctrl('A')`. 276 | /// 277 | /// On Unix, certain special `Key` values are represented as control 278 | /// characters; therefore, the following combinations will not generate a 279 | /// `Ctrl(_)` value: 280 | /// 281 | /// * Ctrl-I instead generates `Tab` 282 | /// * Ctrl-J and Ctrl-M instead generate `Enter` 283 | Ctrl(char), 284 | /// Function `n` key; e.g. F1, F2, ... 285 | F(u32), 286 | } 287 | 288 | impl From for Key { 289 | fn from(ch: char) -> Key { 290 | use crate::util::{is_ctrl, unctrl_lower}; 291 | 292 | match ch { 293 | '\x1b' => Key::Escape, 294 | '\x7f' => Key::Backspace, 295 | '\r' | '\n' => Key::Enter, 296 | '\t' => Key::Tab, 297 | _ if is_ctrl(ch) => Key::Ctrl(unctrl_lower(ch)), 298 | _ => Key::Char(ch), 299 | } 300 | } 301 | } 302 | 303 | /// Represents a mouse event 304 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 305 | pub struct MouseEvent { 306 | /// The position of the mouse within the terminal when the event occurred 307 | pub position: Cursor, 308 | /// The input event that occurred 309 | pub input: MouseInput, 310 | /// Modifier keys held when the input event occurred 311 | /// 312 | /// # Notes 313 | /// 314 | /// On some systems, certain combinations of mouse button and modifier may 315 | /// be interpreted by the system and not reported as terminal events. 316 | pub modifiers: ModifierState, 317 | } 318 | 319 | /// Represents the type of mouse input event 320 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 321 | pub enum MouseInput { 322 | /// The mouse cursor was moved 323 | Motion, 324 | /// A mouse button was pressed 325 | ButtonPressed(MouseButton), 326 | /// A mouse button was released 327 | ButtonReleased(MouseButton), 328 | /// The mouse wheel was scrolled up 329 | WheelUp, 330 | /// The mouse wheel was scrolled down 331 | WheelDown, 332 | } 333 | 334 | /// Represents a button on a mouse device 335 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 336 | pub enum MouseButton { 337 | /// Left mouse button 338 | Left, 339 | /// Right mouse button 340 | Right, 341 | /// Middle mouse button 342 | Middle, 343 | /// Other mouse button 344 | Other(u32), 345 | } 346 | 347 | bitflags!{ 348 | /// Represents a set of modifier keys 349 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 350 | pub struct ModifierState: u8 { 351 | /// Alt key 352 | const ALT = 1 << 0; 353 | /// Ctrl key 354 | const CTRL = 1 << 1; 355 | /// Shift key 356 | const SHIFT = 1 << 2; 357 | } 358 | } 359 | 360 | /// Configures a [`Terminal`] or [`Screen`] instance to read special input. 361 | /// 362 | /// This struct implements the [`Default`] trait, providing default 363 | /// values for all options. 364 | /// 365 | /// To override only some options while using the remaining default values, 366 | /// one may use the following construct: 367 | /// 368 | /// ```no_run 369 | /// # use std::io; 370 | /// # fn example() -> io::Result<()> { 371 | /// use mortal::{Terminal, PrepareConfig}; 372 | /// 373 | /// let term = Terminal::new()?; 374 | /// 375 | /// let state = term.prepare(PrepareConfig{ 376 | /// enable_keypad: false, 377 | /// enable_mouse: true, 378 | /// .. PrepareConfig::default() 379 | /// })?; 380 | /// 381 | /// // ... 382 | /// 383 | /// term.restore(state)?; 384 | /// # Ok(()) 385 | /// # } 386 | /// ``` 387 | /// 388 | /// [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html 389 | /// [`Terminal`]: struct.Terminal.html 390 | /// [`Screen`]: ../screen/struct.Screen.html 391 | #[derive(Copy, Clone, Debug)] 392 | pub struct PrepareConfig { 393 | /// Whether to block signals that result from user input. 394 | /// 395 | /// If `true`, e.g. when the user presses Ctrl-C, 396 | /// `Key(Ctrl('c'))` will be read instead of `Signal(Interrupt)`. 397 | /// 398 | /// The default is `true`. 399 | pub block_signals: bool, 400 | /// Whether to enable control flow characters. 401 | /// 402 | /// The default is `false`. 403 | /// 404 | /// # Notes 405 | /// 406 | /// On Unix, when this setting is enabled, Ctrl-S and Ctrl-Q 407 | /// will stop and start, respectively, terminal input from being processed. 408 | /// 409 | /// On Windows, this setting has no effect. 410 | pub enable_control_flow: bool, 411 | /// If `true`, the terminal will be configured to generate events from 412 | /// function keys. 413 | /// 414 | /// The default is `true`. 415 | /// 416 | /// # Notes 417 | /// 418 | /// On Unix, this may be required to receive events for arrow keys. 419 | /// 420 | /// On Windows, this setting has no effect. 421 | pub enable_keypad: bool, 422 | /// If `true`, the terminal will be configured to generate events for 423 | /// mouse input, if supported, and `read_event` may return `Event::Mouse(_)`. 424 | /// 425 | /// The default is `false`. 426 | /// 427 | /// # Notes 428 | /// 429 | /// This setting may not be supported on all systems. 430 | pub enable_mouse: bool, 431 | /// If `true`, mouse motion events will always be reported. 432 | /// If `false`, such events will only be reported while at least one mouse 433 | /// button is pressed. 434 | /// 435 | /// Mouse events are only reported if `enable_mouse` is `true`. 436 | /// 437 | /// The default is `false`. 438 | pub always_track_motion: bool, 439 | /// For each signal in the set, a signal handler will intercept the signal 440 | /// and report it by returning an `Event::Signal(_)` value. 441 | /// 442 | /// `block_signals` must be `false` for any of these signals to be received. 443 | /// 444 | /// By default, no signals are reported. 445 | pub report_signals: SignalSet, 446 | } 447 | 448 | impl Default for PrepareConfig { 449 | fn default() -> PrepareConfig { 450 | PrepareConfig{ 451 | block_signals: true, 452 | enable_control_flow: false, 453 | enable_keypad: true, 454 | enable_mouse: false, 455 | always_track_motion: false, 456 | report_signals: SignalSet::new(), 457 | } 458 | } 459 | } 460 | 461 | /// Represents a previous device state of a [`Terminal`]. 462 | /// 463 | /// A value of this type is returned by [`Terminal::prepare`]. 464 | /// 465 | /// Required to revert terminal state using [`Terminal::restore`]. 466 | /// 467 | /// [`Terminal`]: struct.Terminal.html 468 | /// [`Terminal::prepare`]: struct.Terminal.html#method.prepare 469 | /// [`Terminal::restore`]: struct.Terminal.html#method.restore 470 | #[must_use = "the result of `terminal.prepare()` should be passed to \ 471 | `terminal.restore()` to restore terminal to its original state"] 472 | pub struct PrepareState(sys::PrepareState); 473 | 474 | /// Represents the size of a terminal window 475 | /// 476 | /// A valid size must not have zero lines or zero columns. 477 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 478 | pub struct Size { 479 | /// Number of lines in the terminal 480 | pub lines: usize, 481 | /// Number of columns in the terminal 482 | pub columns: usize, 483 | } 484 | 485 | impl Size { 486 | /// Returns the total number of cells in a terminal of the given size. 487 | /// 488 | /// # Panics 489 | /// 490 | /// If `lines * columns` would overflow. 491 | #[inline] 492 | pub fn area(&self) -> usize { 493 | self.checked_area().unwrap_or_else( 494 | || panic!("overflow in Size::area {:?}", self)) 495 | } 496 | 497 | /// Returns the total number of cells in a terminal of the given size. 498 | /// 499 | /// Returns `None` in case of overflow. 500 | #[inline] 501 | pub fn checked_area(&self) -> Option { 502 | self.lines.checked_mul(self.columns) 503 | } 504 | } 505 | 506 | /// Provides concurrent read and write access to a terminal device 507 | /// 508 | /// # Concurrency 509 | /// 510 | /// Access to read and write operations is controlled by two internal locks: 511 | /// One for [reading] and one for [writing]. Each lock may be held independently 512 | /// of the other. 513 | /// 514 | /// If any one thread wishes to hold both locks, the read lock 515 | /// must be acquired first, in order to prevent deadlocks. 516 | /// 517 | /// [reading]: struct.TerminalReadGuard.html 518 | /// [writing]: struct.TerminalWriteGuard.html 519 | pub struct Terminal(pub(crate) sys::Terminal); 520 | 521 | /// Holds an exclusive lock for read operations on a `Terminal` 522 | /// 523 | /// See [`Terminal`] documentation for details on locking. 524 | /// 525 | /// [`Terminal`]: struct.Terminal.html 526 | pub struct TerminalReadGuard<'a>(sys::TerminalReadGuard<'a>); 527 | 528 | /// Holds an exclusive lock for write operations on a `Terminal` 529 | /// 530 | /// See [`Terminal`] documentation for details on locking. 531 | /// 532 | /// [`Terminal`]: struct.Terminal.html 533 | pub struct TerminalWriteGuard<'a>(sys::TerminalWriteGuard<'a>); 534 | 535 | impl Terminal { 536 | /// Opens a new interface to the terminal on `stdout`. 537 | pub fn new() -> io::Result { 538 | Ok(Terminal(sys::Terminal::stdout()?)) 539 | } 540 | 541 | /// Opens a new interface to the terminal on `stderr`. 542 | pub fn stderr() -> io::Result { 543 | Ok(Terminal(sys::Terminal::stderr()?)) 544 | } 545 | 546 | /// Returns the name of the terminal. 547 | /// 548 | /// # Notes 549 | /// 550 | /// On Unix, this method returns the contents of the `TERM` environment variable. 551 | /// 552 | /// On Windows, this method always returns the string `"windows-console"`. 553 | #[inline] 554 | pub fn name(&self) -> &str { 555 | self.0.name() 556 | } 557 | 558 | /// Attempts to acquire an exclusive lock on terminal read operations. 559 | /// 560 | /// The current thread will block until the lock can be acquired. 561 | #[inline] 562 | pub fn lock_read(&self) -> LockResult { 563 | map_lock_result(self.0.lock_read(), TerminalReadGuard) 564 | } 565 | 566 | /// Attempts to acquire an exclusive lock on terminal write operations. 567 | /// 568 | /// The current thread will block until the lock can be acquired. 569 | #[inline] 570 | pub fn lock_write(&self) -> LockResult { 571 | map_lock_result(self.0.lock_write(), TerminalWriteGuard) 572 | } 573 | 574 | /// Attempts to acquire an exclusive lock on terminal read operations. 575 | /// 576 | /// If the lock cannot be acquired immediately, `Err(_)` is returned. 577 | #[inline] 578 | pub fn try_lock_read(&self) -> TryLockResult { 579 | map_try_lock_result(self.0.try_lock_read(), TerminalReadGuard) 580 | } 581 | 582 | /// Attempts to acquire an exclusive lock on terminal write operations. 583 | /// 584 | /// If the lock cannot be acquired immediately, `Err(_)` is returned. 585 | #[inline] 586 | pub fn try_lock_write(&self) -> TryLockResult { 587 | map_try_lock_result(self.0.try_lock_write(), TerminalWriteGuard) 588 | } 589 | } 590 | 591 | /// # Locking 592 | /// 593 | /// The following methods internally acquire both the read and write locks. 594 | /// 595 | /// The locks are released before the method returns. 596 | /// 597 | /// These methods are also implemented on [`TerminalReadGuard`], 598 | /// which holds the `Terminal` read lock until the value is dropped. 599 | /// 600 | /// [`TerminalReadGuard`]: struct.TerminalReadGuard.html 601 | impl Terminal { 602 | /// Prepares the terminal to read input. 603 | /// 604 | /// When reading operations have concluded, [`restore`] should be called 605 | /// with the resulting `PrepareState` value to restore the terminal to 606 | /// its previous state. 607 | /// 608 | /// This method may be called more than once before a corresponding 609 | /// `restore` call. However, each `restore` call must receive the most 610 | /// recently created `PrepareState` value. 611 | /// 612 | /// See [`PrepareConfig`] for details. 613 | /// 614 | /// [`PrepareConfig`]: struct.PrepareConfig.html 615 | /// [`restore`]: #method.restore 616 | pub fn prepare(&self, config: PrepareConfig) -> io::Result { 617 | self.0.prepare(config).map(PrepareState) 618 | } 619 | 620 | /// Restores the terminal to its previous state. 621 | pub fn restore(&self, state: PrepareState) -> io::Result<()> { 622 | self.0.restore(state.0) 623 | } 624 | } 625 | 626 | /// # Locking 627 | /// 628 | /// The following methods internally acquire the read lock. 629 | /// 630 | /// The lock is released before the method returns. 631 | /// 632 | /// These methods are also implemented on [`TerminalReadGuard`], 633 | /// which holds the `Terminal` read lock until the value is dropped. 634 | /// 635 | /// [`TerminalReadGuard`]: struct.TerminalReadGuard.html 636 | impl Terminal { 637 | /// Waits for an event from the terminal. 638 | /// 639 | /// Returns `Ok(false)` if `timeout` elapses without an event occurring. 640 | /// 641 | /// If `timeout` is `None`, this method will wait indefinitely. 642 | pub fn wait_event(&self, timeout: Option) -> io::Result { 643 | self.0.wait_event(timeout) 644 | } 645 | 646 | /// Waits for input and reads an event from the terminal. 647 | /// 648 | /// Returns `Ok(None)` if `timeout` elapses without an event occurring. 649 | /// 650 | /// If `timeout` is `None`, this method will wait indefinitely. 651 | pub fn read_event(&self, timeout: Option) -> io::Result> { 652 | self.0.read_event(timeout) 653 | } 654 | } 655 | 656 | /// # Locking 657 | /// 658 | /// The following methods internally acquire the write lock. 659 | /// 660 | /// The lock is released before the method returns. 661 | /// 662 | /// These methods are also implemented on [`TerminalWriteGuard`], 663 | /// which holds the `Terminal` write lock until the value is dropped. 664 | /// 665 | /// [`TerminalWriteGuard`]: struct.TerminalWriteGuard.html 666 | impl Terminal { 667 | /// Returns the size of the terminal. 668 | #[inline] 669 | pub fn size(&self) -> io::Result { 670 | self.0.size() 671 | } 672 | 673 | /// Clears the terminal screen, placing the cursor at the first line and column. 674 | /// 675 | /// If the terminal contains a scrolling window over a buffer, the window 676 | /// will be scrolled downward, preserving as much of the existing buffer 677 | /// as possible. 678 | pub fn clear_screen(&self) -> io::Result<()> { 679 | self.0.clear_screen() 680 | } 681 | 682 | /// Clears the current line, starting at cursor position. 683 | pub fn clear_to_line_end(&self) -> io::Result<()> { 684 | self.0.clear_to_line_end() 685 | } 686 | 687 | /// Clears the screen, starting at cursor position. 688 | pub fn clear_to_screen_end(&self) -> io::Result<()> { 689 | self.0.clear_to_screen_end() 690 | } 691 | 692 | /// Moves the cursor up `n` lines. 693 | pub fn move_up(&self, n: usize) -> io::Result<()> { 694 | self.0.move_up(n) 695 | } 696 | 697 | /// Moves the cursor down `n` lines. 698 | pub fn move_down(&self, n: usize) -> io::Result<()> { 699 | self.0.move_down(n) 700 | } 701 | 702 | /// Moves the cursor left `n` columns. 703 | pub fn move_left(&self, n: usize) -> io::Result<()> { 704 | self.0.move_left(n) 705 | } 706 | 707 | /// Moves the cursor right `n` columns. 708 | pub fn move_right(&self, n: usize) -> io::Result<()> { 709 | self.0.move_right(n) 710 | } 711 | 712 | /// Moves the cursor to the first column of the current line 713 | pub fn move_to_first_column(&self) -> io::Result<()> { 714 | self.0.move_to_first_column() 715 | } 716 | 717 | /// Set the current cursor mode. 718 | /// 719 | /// This setting is a visible hint to the user. 720 | /// It produces no change in behavior. 721 | /// 722 | /// # Notes 723 | /// 724 | /// On some systems, this setting may have no effect. 725 | pub fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()> { 726 | self.0.set_cursor_mode(mode) 727 | } 728 | 729 | /// Adds a set of `Style` flags to the current style setting. 730 | pub fn add_style(&self, style: Style) -> io::Result<()> { 731 | self.0.add_style(style) 732 | } 733 | 734 | /// Removes a set of `Style` flags from the current style setting. 735 | pub fn remove_style(&self, style: Style) -> io::Result<()> { 736 | self.0.remove_style(style) 737 | } 738 | 739 | /// Sets the current style to the given set of flags. 740 | pub fn set_style(&self, style: S) -> io::Result<()> 741 | where S: Into> { 742 | self.0.set_style(style.into().unwrap_or_default()) 743 | } 744 | 745 | /// Sets all attributes for the terminal. 746 | pub fn set_theme(&self, theme: Theme) -> io::Result<()> { 747 | self.0.set_theme(theme) 748 | } 749 | 750 | /// Sets the foreground text color. 751 | pub fn set_fg>>(&self, fg: C) -> io::Result<()> { 752 | self.0.set_fg(fg.into()) 753 | } 754 | 755 | /// Sets the background text color. 756 | pub fn set_bg>>(&self, bg: C) -> io::Result<()> { 757 | self.0.set_bg(bg.into()) 758 | } 759 | 760 | /// Removes color and style attributes. 761 | pub fn clear_attributes(&self) -> io::Result<()> { 762 | self.0.clear_attributes() 763 | } 764 | 765 | /// Adds bold to the current style setting. 766 | /// 767 | /// This is equivalent to `self.add_style(Style::BOLD)`. 768 | pub fn bold(&self) -> io::Result<()> { 769 | self.add_style(Style::BOLD) 770 | } 771 | 772 | /// Adds italic to the current style setting. 773 | /// 774 | /// This is equivalent to `self.add_style(Style::ITALIC)`. 775 | pub fn italic(&self) -> io::Result<()> { 776 | self.add_style(Style::ITALIC) 777 | } 778 | 779 | /// Adds underline to the current style setting. 780 | /// 781 | /// This is equivalent to `self.add_style(Style::UNDERLINE)`. 782 | pub fn underline(&self) -> io::Result<()> { 783 | self.add_style(Style::UNDERLINE) 784 | } 785 | 786 | /// Adds reverse to the current style setting. 787 | /// 788 | /// This is equivalent to `self.add_style(Style::REVERSE)`. 789 | pub fn reverse(&self) -> io::Result<()> { 790 | self.add_style(Style::REVERSE) 791 | } 792 | 793 | /// Writes output to the terminal with the given color and style. 794 | /// 795 | /// All attributes are removed after the given text is written. 796 | pub fn write_styled(&self, fg: F, bg: B, style: S, s: &str) 797 | -> io::Result<()> where 798 | F: Into>, 799 | B: Into>, 800 | S: Into>, 801 | { 802 | self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), s) 803 | } 804 | 805 | /// Writes a single character to the terminal 806 | /// using the current style and color settings. 807 | pub fn write_char(&self, ch: char) -> io::Result<()> { 808 | self.0.write_char(ch) 809 | } 810 | 811 | /// Writes a string to the terminal 812 | /// using the current style and color settings. 813 | pub fn write_str(&self, s: &str) -> io::Result<()> { 814 | self.0.write_str(s) 815 | } 816 | 817 | /// Writes formatted text to the terminal 818 | /// using the current style and color settings. 819 | /// 820 | /// This method enables `Terminal` to be used as the receiver to 821 | /// the [`write!`] and [`writeln!`] macros. 822 | /// 823 | /// # Examples 824 | /// 825 | /// ```no_run 826 | /// # use std::io; 827 | /// # use mortal::Terminal; 828 | /// # fn example() -> io::Result<()> { 829 | /// let term = Terminal::new()?; 830 | /// 831 | /// writeln!(term, "Hello, world!")?; 832 | /// # Ok(()) 833 | /// # } 834 | /// ``` 835 | /// 836 | /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html 837 | /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html 838 | pub fn write_fmt(&self, args: fmt::Arguments) -> io::Result<()> { 839 | let s = args.to_string(); 840 | self.write_str(&s) 841 | } 842 | 843 | #[doc(hidden)] 844 | pub fn borrow_term_write_guard(&self) -> TerminalWriteGuard { 845 | self.lock_write().unwrap() 846 | } 847 | } 848 | 849 | impl<'a> TerminalReadGuard<'a> { 850 | /// Prepares the terminal to read input. 851 | /// 852 | /// When reading operations have concluded, [`restore`] 853 | /// should be called with the resulting `PrepareState` value to restore 854 | /// the terminal to its previous state. 855 | /// 856 | /// This method may be called more than once before a corresponding 857 | /// `restore` call. However, each `restore` call must receive the most recently 858 | /// created `PrepareState` value. 859 | /// 860 | /// See [`PrepareConfig`] for details. 861 | /// 862 | /// [`PrepareConfig`]: struct.PrepareConfig.html 863 | /// [`restore`]: #method.restore 864 | /// 865 | /// ## Locking 866 | /// 867 | /// This method internally acquires the [`Terminal`] write lock. 868 | /// 869 | /// If the write lock is already held by the current thread, 870 | /// call [`prepare_with_lock`], in order to prevent deadlocks. 871 | /// 872 | /// [`Terminal`]: struct.Terminal.html 873 | /// [`prepare_with_lock`]: #method.prepare_with_lock 874 | pub fn prepare(&mut self, config: PrepareConfig) -> io::Result { 875 | self.0.prepare(config).map(PrepareState) 876 | } 877 | 878 | /// Performs terminal preparation using both [`Terminal`] locks. 879 | /// 880 | /// [`Terminal`]: struct.Terminal.html 881 | pub fn prepare_with_lock(&mut self, writer: &mut TerminalWriteGuard, 882 | config: PrepareConfig) -> io::Result { 883 | self.0.prepare_with_lock(&mut writer.0, config).map(PrepareState) 884 | } 885 | 886 | /// Restores the terminal to its previous state. 887 | /// 888 | /// ## Locking 889 | /// 890 | /// This method internally acquires the [`Terminal`] write lock. 891 | /// 892 | /// If the write lock is already held by the current thread, 893 | /// call [`restore_with_lock`], in order to prevent deadlocks. 894 | /// 895 | /// [`Terminal`]: struct.Terminal.html 896 | /// [`restore_with_lock`]: #method.restore_with_lock 897 | pub fn restore(&mut self, state: PrepareState) -> io::Result<()> { 898 | self.0.restore(state.0) 899 | } 900 | 901 | /// Performs terminal state restoration using both [`Terminal`] locks. 902 | /// 903 | /// [`Terminal`]: struct.Terminal.html 904 | pub fn restore_with_lock(&mut self, writer: &mut TerminalWriteGuard, 905 | state: PrepareState) -> io::Result<()> { 906 | self.0.restore_with_lock(&mut writer.0, state.0) 907 | } 908 | 909 | /// Waits for an event from the terminal. 910 | /// 911 | /// Returns `Ok(false)` if `timeout` elapses without an event occurring. 912 | /// 913 | /// If `timeout` is `None`, this method will wait indefinitely. 914 | pub fn wait_event(&mut self, timeout: Option) -> io::Result { 915 | self.0.wait_event(timeout) 916 | } 917 | 918 | /// Waits for input and reads an event from the terminal. 919 | /// 920 | /// Returns `Ok(None)` if `timeout` elapses without an event occurring. 921 | /// 922 | /// If `timeout` is `None`, this method will wait indefinitely. 923 | pub fn read_event(&mut self, timeout: Option) -> io::Result> { 924 | self.0.read_event(timeout) 925 | } 926 | } 927 | 928 | impl<'a> TerminalWriteGuard<'a> { 929 | /// Flush all output to the terminal device. 930 | /// 931 | /// This is called automatically when the `TerminalWriteGuard` is dropped. 932 | pub fn flush(&mut self) -> io::Result<()> { 933 | self.0.flush() 934 | } 935 | 936 | /// Returns the size of the terminal. 937 | #[inline] 938 | pub fn size(&self) -> io::Result { 939 | self.0.size() 940 | } 941 | 942 | /// Clears the terminal screen, placing the cursor at the first line and column. 943 | /// 944 | /// If the terminal contains a scrolling window over a buffer, the window 945 | /// will be scrolled downward, preserving as much of the existing buffer 946 | /// as possible. 947 | pub fn clear_screen(&mut self) -> io::Result<()> { 948 | self.0.clear_screen() 949 | } 950 | 951 | /// Clears the current line, starting at cursor position. 952 | pub fn clear_to_line_end(&mut self) -> io::Result<()> { 953 | self.0.clear_to_line_end() 954 | } 955 | 956 | /// Clears the screen, starting at cursor position. 957 | pub fn clear_to_screen_end(&mut self) -> io::Result<()> { 958 | self.0.clear_to_screen_end() 959 | } 960 | 961 | /// Moves the cursor up `n` lines. 962 | pub fn move_up(&mut self, n: usize) -> io::Result<()> { 963 | self.0.move_up(n) 964 | } 965 | 966 | /// Moves the cursor down `n` lines. 967 | pub fn move_down(&mut self, n: usize) -> io::Result<()> { 968 | self.0.move_down(n) 969 | } 970 | 971 | /// Moves the cursor left `n` columns. 972 | pub fn move_left(&mut self, n: usize) -> io::Result<()> { 973 | self.0.move_left(n) 974 | } 975 | 976 | /// Moves the cursor right `n` columns. 977 | pub fn move_right(&mut self, n: usize) -> io::Result<()> { 978 | self.0.move_right(n) 979 | } 980 | 981 | /// Moves the cursor to the first column of the current line 982 | pub fn move_to_first_column(&mut self) -> io::Result<()> { 983 | self.0.move_to_first_column() 984 | } 985 | 986 | /// Set the current cursor mode. 987 | /// 988 | /// This setting is a visible hint to the user. 989 | /// It produces no change in behavior. 990 | /// 991 | /// # Notes 992 | /// 993 | /// On some systems, this setting may have no effect. 994 | pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> { 995 | self.0.set_cursor_mode(mode) 996 | } 997 | 998 | /// Adds a set of `Style` flags to the current style setting. 999 | pub fn add_style(&mut self, style: Style) -> io::Result<()> { 1000 | self.0.add_style(style) 1001 | } 1002 | 1003 | /// Removes a set of `Style` flags from the current style setting. 1004 | pub fn remove_style(&mut self, style: Style) -> io::Result<()> { 1005 | self.0.remove_style(style) 1006 | } 1007 | 1008 | /// Sets the current style to the given set of flags. 1009 | pub fn set_style(&mut self, style: S) -> io::Result<()> 1010 | where S: Into> { 1011 | self.0.set_style(style.into().unwrap_or_default()) 1012 | } 1013 | 1014 | /// Sets all attributes for the terminal. 1015 | pub fn set_theme(&mut self, theme: Theme) -> io::Result<()> { 1016 | self.0.set_theme(theme) 1017 | } 1018 | 1019 | /// Sets the background text color. 1020 | pub fn set_fg>>(&mut self, fg: C) -> io::Result<()> { 1021 | self.0.set_fg(fg.into()) 1022 | } 1023 | 1024 | /// Removes color and style attributes. 1025 | pub fn set_bg>>(&mut self, bg: C) -> io::Result<()> { 1026 | self.0.set_bg(bg.into()) 1027 | } 1028 | 1029 | /// Adds bold to the current style setting. 1030 | pub fn clear_attributes(&mut self) -> io::Result<()> { 1031 | self.0.clear_attributes() 1032 | } 1033 | 1034 | /// Adds bold to the current style setting. 1035 | /// 1036 | /// This is equivalent to `self.add_style(Style::BOLD)`. 1037 | pub fn bold(&mut self) -> io::Result<()> { 1038 | self.add_style(Style::BOLD) 1039 | } 1040 | 1041 | /// Adds italic to the current style setting. 1042 | /// 1043 | /// This is equivalent to `self.add_style(Style::ITALIC)`. 1044 | pub fn italic(&mut self) -> io::Result<()> { 1045 | self.add_style(Style::ITALIC) 1046 | } 1047 | 1048 | /// Adds underline to the current style setting. 1049 | /// 1050 | /// This is equivalent to `self.add_style(Style::UNDERLINE)`. 1051 | pub fn underline(&mut self) -> io::Result<()> { 1052 | self.add_style(Style::UNDERLINE) 1053 | } 1054 | 1055 | /// Adds reverse to the current style setting. 1056 | /// 1057 | /// This is equivalent to `self.add_style(Style::REVERSE)`. 1058 | pub fn reverse(&mut self) -> io::Result<()> { 1059 | self.add_style(Style::REVERSE) 1060 | } 1061 | 1062 | /// Writes output to the terminal with the given color and style added. 1063 | /// 1064 | /// All attributes are removed after the given text is written. 1065 | pub fn write_styled(&mut self, fg: F, bg: B, style: S, s: &str) 1066 | -> io::Result<()> where 1067 | F: Into>, 1068 | B: Into>, 1069 | S: Into>, 1070 | { 1071 | self.0.write_styled(fg.into(), bg.into(), style.into().unwrap_or_default(), s) 1072 | } 1073 | 1074 | /// Writes a single character to the terminal 1075 | /// using the current style and color settings. 1076 | pub fn write_char(&mut self, ch: char) -> io::Result<()> { 1077 | self.0.write_char(ch) 1078 | } 1079 | 1080 | /// Writes a string to the terminal 1081 | /// using the current style and color settings. 1082 | pub fn write_str(&mut self, s: &str) -> io::Result<()> { 1083 | self.0.write_str(s) 1084 | } 1085 | 1086 | /// Writes formatted text to the terminal 1087 | /// using the current style and color settings. 1088 | /// 1089 | /// This method enables `TerminalWriteGuard` to be used as the receiver to 1090 | /// the [`write!`] and [`writeln!`] macros. 1091 | /// 1092 | /// [`write!`]: https://doc.rust-lang.org/std/macro.write.html 1093 | /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html 1094 | pub fn write_fmt(&mut self, args: fmt::Arguments) -> io::Result<()> { 1095 | let s = args.to_string(); 1096 | self.write_str(&s) 1097 | } 1098 | 1099 | #[doc(hidden)] 1100 | pub fn borrow_term_write_guard(&mut self) -> &mut Self { 1101 | self 1102 | } 1103 | } 1104 | 1105 | #[cfg(unix)] 1106 | use std::path::Path; 1107 | 1108 | #[cfg(unix)] 1109 | impl crate::unix::OpenTerminalExt for Terminal { 1110 | fn from_path>(path: P) -> io::Result { 1111 | sys::Terminal::open(path).map(Terminal) 1112 | } 1113 | } 1114 | 1115 | #[cfg(unix)] 1116 | impl crate::unix::TerminalExt for Terminal { 1117 | fn read_raw(&mut self, buf: &mut [u8], timeout: Option) -> io::Result> { 1118 | self.0.read_raw(buf, timeout) 1119 | } 1120 | } 1121 | 1122 | #[cfg(unix)] 1123 | impl<'a> crate::unix::TerminalExt for TerminalReadGuard<'a> { 1124 | fn read_raw(&mut self, buf: &mut [u8], timeout: Option) -> io::Result> { 1125 | self.0.read_raw(buf, timeout) 1126 | } 1127 | } 1128 | 1129 | #[cfg(windows)] 1130 | impl crate::windows::TerminalExt for Terminal { 1131 | fn read_raw(&mut self, buf: &mut [u16], timeout: Option) -> io::Result> { 1132 | self.0.read_raw(buf, timeout) 1133 | } 1134 | 1135 | fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD], 1136 | timeout: Option) -> io::Result> { 1137 | self.0.read_raw_event(events, timeout) 1138 | } 1139 | } 1140 | 1141 | #[cfg(windows)] 1142 | impl<'a> crate::windows::TerminalExt for TerminalReadGuard<'a> { 1143 | fn read_raw(&mut self, buf: &mut [u16], timeout: Option) -> io::Result> { 1144 | self.0.read_raw(buf, timeout) 1145 | } 1146 | 1147 | fn read_raw_event(&mut self, events: &mut [::winapi::um::wincon::INPUT_RECORD], 1148 | timeout: Option) -> io::Result> { 1149 | self.0.read_raw_event(events, timeout) 1150 | } 1151 | } 1152 | -------------------------------------------------------------------------------- /src/unix/ext.rs: -------------------------------------------------------------------------------- 1 | //! Unix extension trait 2 | 3 | use std::io; 4 | use std::path::Path; 5 | use std::time::Duration; 6 | 7 | use crate::priv_util::Private; 8 | use crate::terminal::Event; 9 | 10 | /// Implements Unix-only extensions for terminal interfaces. 11 | pub trait OpenTerminalExt: Sized + Private { 12 | /// Opens a terminal interface on the device at the given path. 13 | /// 14 | /// If the path cannot be opened for read/write operations, 15 | /// an error is returned. 16 | fn from_path>(path: P) -> io::Result; 17 | } 18 | 19 | /// Implements Unix-only extensions for terminal interfaces. 20 | pub trait TerminalExt: Private { 21 | /// Reads raw data from the terminal. 22 | /// 23 | /// Data read will be UTF-8 encoded, but may be incomplete. The caller may 24 | /// consume any valid UTF-8 data before performing another `read_raw` call 25 | /// to complete previously read data. 26 | /// 27 | /// If `timeout` elapses without an event occurring, this method will return 28 | /// `Ok(None)`. If `timeout` is `None`, this method will wait indefinitely. 29 | fn read_raw(&mut self, buf: &mut [u8], timeout: Option) 30 | -> io::Result>; 31 | } 32 | -------------------------------------------------------------------------------- /src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::screen::{ 2 | Screen, ScreenReadGuard, ScreenWriteGuard, 3 | }; 4 | pub use self::terminal::{ 5 | PrepareState, 6 | Terminal, TerminalReadGuard, TerminalWriteGuard, 7 | }; 8 | 9 | pub mod ext; 10 | mod screen; 11 | mod terminal; 12 | -------------------------------------------------------------------------------- /src/unix/screen.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::sync::{LockResult, Mutex, MutexGuard, TryLockResult}; 3 | use std::time::Duration; 4 | 5 | use crate::buffer::ScreenBuffer; 6 | use crate::priv_util::{ 7 | map_lock_result, map_try_lock_result, 8 | map2_lock_result, map2_try_lock_result, 9 | }; 10 | use crate::sys::{Terminal, TerminalReadGuard, TerminalWriteGuard, PrepareState}; 11 | use crate::terminal::{Color, Cursor, CursorMode, Event, Size, Style, PrepareConfig}; 12 | 13 | pub struct Screen { 14 | term: Terminal, 15 | 16 | state: Option, 17 | writer: Mutex, 18 | } 19 | 20 | pub struct ScreenReadGuard<'a> { 21 | screen: &'a Screen, 22 | reader: TerminalReadGuard<'a>, 23 | } 24 | 25 | pub struct ScreenWriteGuard<'a> { 26 | writer: TerminalWriteGuard<'a>, 27 | data: MutexGuard<'a, Writer>, 28 | } 29 | 30 | struct Writer { 31 | buffer: ScreenBuffer, 32 | clear_screen: bool, 33 | real_cursor: Cursor, 34 | } 35 | 36 | impl Screen { 37 | pub fn new(term: Terminal, config: PrepareConfig) -> io::Result { 38 | let size = term.size()?; 39 | let state = term.prepare(config)?; 40 | 41 | let screen = Screen{ 42 | term: term, 43 | state: Some(state), 44 | 45 | writer: Mutex::new(Writer{ 46 | buffer: ScreenBuffer::new(size), 47 | clear_screen: false, 48 | real_cursor: Cursor::default(), 49 | }), 50 | }; 51 | 52 | screen.term.enter_screen()?; 53 | 54 | Ok(screen) 55 | } 56 | 57 | pub fn stdout(config: PrepareConfig) -> io::Result { 58 | Screen::new(Terminal::stdout()?, config) 59 | } 60 | 61 | pub fn stderr(config: PrepareConfig) -> io::Result { 62 | Screen::new(Terminal::stderr()?, config) 63 | } 64 | 65 | forward_screen_buffer_methods!{ |slf| slf.lock_write_data().buffer } 66 | 67 | pub fn lock_read(&self) -> LockResult { 68 | map_lock_result(self.term.lock_read(), 69 | |r| ScreenReadGuard::new(self, r)) 70 | } 71 | 72 | pub fn try_lock_read(&self) -> TryLockResult { 73 | map_try_lock_result(self.term.try_lock_read(), 74 | |r| ScreenReadGuard::new(self, r)) 75 | } 76 | 77 | pub fn lock_write(&self) -> LockResult { 78 | map2_lock_result(self.term.lock_write(), self.writer.lock(), 79 | |a, b| ScreenWriteGuard::new(a, b)) 80 | } 81 | 82 | pub fn try_lock_write(&self) -> TryLockResult { 83 | map2_try_lock_result(self.term.try_lock_write(), self.writer.try_lock(), 84 | |a, b| ScreenWriteGuard::new(a, b)) 85 | } 86 | 87 | fn lock_reader(&self) -> ScreenReadGuard { 88 | self.lock_read().expect("Screen::lock_reader") 89 | } 90 | 91 | fn lock_writer(&self) -> ScreenWriteGuard { 92 | self.lock_write().expect("Screen::lock_writer") 93 | } 94 | 95 | fn lock_write_data(&self) -> MutexGuard { 96 | self.writer.lock().expect("Screen::lock_write_data") 97 | } 98 | 99 | pub fn name(&self) -> &str { 100 | self.term.name() 101 | } 102 | 103 | pub fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()> { 104 | self.term.set_cursor_mode(mode) 105 | } 106 | 107 | pub fn wait_event(&self, timeout: Option) -> io::Result { 108 | self.lock_reader().wait_event(timeout) 109 | } 110 | 111 | pub fn read_event(&self, timeout: Option) -> io::Result> { 112 | self.lock_reader().read_event(timeout) 113 | } 114 | 115 | pub fn read_raw(&self, buf: &mut [u8], timeout: Option) -> io::Result> { 116 | self.lock_reader().read_raw(buf, timeout) 117 | } 118 | 119 | pub fn refresh(&self) -> io::Result<()> { 120 | self.lock_writer().refresh() 121 | } 122 | } 123 | 124 | impl Drop for Screen { 125 | fn drop(&mut self) { 126 | let res = if let Some(state) = self.state.take() { 127 | self.term.restore(state) 128 | } else { 129 | Ok(()) 130 | }; 131 | 132 | if let Err(e) = res.and_then(|_| self.term.exit_screen()) { 133 | eprintln!("failed to restore terminal: {}", e); 134 | } 135 | } 136 | } 137 | 138 | impl<'a> ScreenReadGuard<'a> { 139 | fn new(screen: &'a Screen, reader: TerminalReadGuard<'a>) -> ScreenReadGuard<'a> { 140 | ScreenReadGuard{screen, reader} 141 | } 142 | 143 | pub fn wait_event(&mut self, timeout: Option) -> io::Result { 144 | self.reader.wait_event(timeout) 145 | } 146 | 147 | pub fn read_event(&mut self, timeout: Option) -> io::Result> { 148 | let r = self.reader.read_event(timeout)?; 149 | 150 | if let Some(Event::Resize(size)) = r { 151 | self.screen.lock_write_data().update_size(size); 152 | } 153 | 154 | Ok(r) 155 | } 156 | 157 | pub fn read_raw(&mut self, buf: &mut [u8], timeout: Option) -> io::Result> { 158 | let r = self.reader.read_raw(buf, timeout)?; 159 | 160 | if let Some(Event::Resize(size)) = r { 161 | self.screen.lock_write_data().update_size(size); 162 | } 163 | 164 | Ok(r) 165 | } 166 | } 167 | 168 | impl<'a> ScreenWriteGuard<'a> { 169 | fn new(writer: TerminalWriteGuard<'a>, data: MutexGuard<'a, Writer>) 170 | -> ScreenWriteGuard<'a> { 171 | ScreenWriteGuard{writer, data} 172 | } 173 | 174 | forward_screen_buffer_mut_methods!{ |slf| slf.data.buffer } 175 | 176 | pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> { 177 | self.writer.set_cursor_mode(mode) 178 | } 179 | 180 | pub fn refresh(&mut self) -> io::Result<()> { 181 | if self.data.clear_screen { 182 | self.writer.clear_screen()?; 183 | self.data.clear_screen = false; 184 | } 185 | 186 | self.writer.clear_attributes()?; 187 | 188 | let mut indices = self.data.buffer.indices(); 189 | 190 | while let Some((pos, cell)) = self.data.buffer.next_cell(&mut indices) { 191 | self.move_cursor(pos)?; 192 | 193 | self.apply_attrs(cell.attrs())?; 194 | self.writer.write_str(cell.text())?; 195 | self.data.real_cursor.column += 1; 196 | } 197 | 198 | self.writer.clear_attributes()?; 199 | 200 | let size = self.data.buffer.size(); 201 | let pos = self.data.buffer.cursor(); 202 | 203 | if pos.is_out_of_bounds(size) { 204 | self.move_cursor(Cursor::last(size))?; 205 | } else { 206 | self.move_cursor(pos)?; 207 | } 208 | 209 | self.writer.flush() 210 | } 211 | 212 | fn move_cursor(&mut self, pos: Cursor) -> io::Result<()> { 213 | if self.data.real_cursor != pos { 214 | self.writer.move_cursor(pos)?; 215 | self.data.real_cursor = pos; 216 | } 217 | 218 | Ok(()) 219 | } 220 | 221 | fn apply_attrs(&mut self, 222 | (fg, bg, style): (Option, Option, Style)) 223 | -> io::Result<()> { 224 | self.writer.set_attrs(fg, bg, style) 225 | } 226 | } 227 | 228 | impl<'a> Drop for ScreenWriteGuard<'a> { 229 | fn drop(&mut self) { 230 | if let Err(e) = self.refresh() { 231 | eprintln!("failed to refresh screen: {}", e); 232 | } 233 | } 234 | } 235 | 236 | impl Writer { 237 | fn update_size(&mut self, new_size: Size) { 238 | if self.real_cursor.is_out_of_bounds(new_size) { 239 | // Force cursor move on next refresh 240 | self.real_cursor = (!0, !0).into(); 241 | } 242 | self.buffer.resize(new_size); 243 | self.clear_screen = true; 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous utility functions 2 | 3 | use std::str::CharIndices; 4 | 5 | /// Returns the width of a character in the terminal. 6 | /// 7 | /// Returns `None` or `Some(0)` for control characters. 8 | #[inline] 9 | pub fn char_width(ch: char) -> Option { 10 | use unicode_width::UnicodeWidthChar; 11 | 12 | ch.width() 13 | } 14 | 15 | /// Returns whether the given character is a combining mark. 16 | #[inline] 17 | pub fn is_combining_mark(ch: char) -> bool { 18 | use unicode_normalization::char::is_combining_mark; 19 | 20 | is_combining_mark(ch) 21 | } 22 | 23 | const CTRL_MASK: u8 = 0x1f; 24 | const UNCTRL_BIT: u8 = 0x40; 25 | 26 | /// Returns the control character corresponding to the given character. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// # use mortal::util::ctrl; 32 | /// // Ctrl-C 33 | /// assert_eq!(ctrl('c'), '\x03'); 34 | /// ``` 35 | #[inline] 36 | pub fn ctrl(ch: char) -> char { 37 | ((ch as u8) & CTRL_MASK) as char 38 | } 39 | 40 | /// Returns whether the given character is a control character. 41 | /// 42 | /// Control characters are in the range `'\0'` ... `'\x1f'`, inclusive. 43 | #[inline] 44 | pub fn is_ctrl(ch: char) -> bool { 45 | let ch = ch as u32; 46 | ch & (CTRL_MASK as u32) == ch 47 | } 48 | 49 | /// Returns the ASCII character corresponding to the given control character. 50 | /// 51 | /// If `ch` is not a control character, the result is unspecified. 52 | /// 53 | /// # Examples 54 | /// 55 | /// ``` 56 | /// # use mortal::util::unctrl_upper; 57 | /// // Ctrl-C 58 | /// assert_eq!(unctrl_upper('\x03'), 'C'); 59 | /// ``` 60 | #[inline] 61 | pub fn unctrl_upper(ch: char) -> char { 62 | ((ch as u8) | UNCTRL_BIT) as char 63 | } 64 | 65 | /// Returns the lowercase ASCII character corresponding to the given control character. 66 | /// 67 | /// If `ch` is not a control character, the result is unspecified. 68 | /// 69 | /// # Examples 70 | /// 71 | /// ``` 72 | /// # use mortal::util::unctrl_lower; 73 | /// 74 | /// // Ctrl-C 75 | /// assert_eq!(unctrl_lower('\x03'), 'c'); 76 | /// ``` 77 | #[inline] 78 | pub fn unctrl_lower(ch: char) -> char { 79 | unctrl_upper(ch).to_ascii_lowercase() 80 | } 81 | 82 | /// Iterator over string prefixes. 83 | /// 84 | /// An instance of this type is returned by the free function [`prefixes`]. 85 | /// 86 | /// [`prefixes`]: fn.prefixes.html 87 | pub struct Prefixes<'a> { 88 | s: &'a str, 89 | iter: CharIndices<'a>, 90 | } 91 | 92 | /// Returns an iterator over all non-empty prefixes of `s`, beginning with 93 | /// the shortest. 94 | /// 95 | /// If `s` is an empty string, the iterator will yield no elements. 96 | /// 97 | /// # Examples 98 | /// 99 | /// ``` 100 | /// # use mortal::util::prefixes; 101 | /// let mut pfxs = prefixes("foo"); 102 | /// 103 | /// assert_eq!(pfxs.next(), Some("f")); 104 | /// assert_eq!(pfxs.next(), Some("fo")); 105 | /// assert_eq!(pfxs.next(), Some("foo")); 106 | /// assert_eq!(pfxs.next(), None); 107 | /// ``` 108 | #[inline] 109 | pub fn prefixes(s: &str) -> Prefixes { 110 | Prefixes{ 111 | s, 112 | iter: s.char_indices(), 113 | } 114 | } 115 | 116 | impl<'a> Iterator for Prefixes<'a> { 117 | type Item = &'a str; 118 | 119 | fn next(&mut self) -> Option<&'a str> { 120 | self.iter.next().map(|(idx, ch)| &self.s[..idx + ch.len_utf8()]) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod test { 126 | use super::{ctrl, is_ctrl, unctrl_lower, unctrl_upper, prefixes}; 127 | 128 | #[test] 129 | fn test_unctrl() { 130 | for ch in 0u8..255 { 131 | let ch = ch as char; 132 | 133 | if is_ctrl(ch) { 134 | assert_eq!(ch, ctrl(unctrl_lower(ch))); 135 | assert_eq!(ch, ctrl(unctrl_upper(ch))); 136 | } 137 | } 138 | } 139 | 140 | #[test] 141 | fn test_prefix_iter() { 142 | let mut pfxs = prefixes("foobar"); 143 | 144 | assert_eq!(pfxs.next(), Some("f")); 145 | assert_eq!(pfxs.next(), Some("fo")); 146 | assert_eq!(pfxs.next(), Some("foo")); 147 | assert_eq!(pfxs.next(), Some("foob")); 148 | assert_eq!(pfxs.next(), Some("fooba")); 149 | assert_eq!(pfxs.next(), Some("foobar")); 150 | assert_eq!(pfxs.next(), None); 151 | 152 | let mut pfxs = prefixes("a"); 153 | 154 | assert_eq!(pfxs.next(), Some("a")); 155 | assert_eq!(pfxs.next(), None); 156 | 157 | let mut pfxs = prefixes(""); 158 | 159 | assert_eq!(pfxs.next(), None); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/windows/ext.rs: -------------------------------------------------------------------------------- 1 | //! Windows console extension trait 2 | 3 | use std::io; 4 | use std::time::Duration; 5 | 6 | use winapi::um::wincon::INPUT_RECORD; 7 | 8 | use crate::priv_util::Private; 9 | use crate::terminal::Event; 10 | 11 | /// Implements Windows-only extensions for terminal interfaces. 12 | pub trait TerminalExt: Private { 13 | /// Reads raw data from the console. 14 | /// 15 | /// Data read will be UTF-16 encoded, but may be incomplete. The caller may 16 | /// consume any valid UTF-16 data before performing another `read_raw` call 17 | /// to complete previously read data. 18 | /// 19 | /// If `timeout` elapses without an event occurring, this method will return 20 | /// `Ok(None)`. If `timeout` is `None`, this method will wait indefinitely. 21 | fn read_raw(&mut self, buf: &mut [u16], timeout: Option) 22 | -> io::Result>; 23 | 24 | /// Reads raw event data from the console. 25 | /// 26 | /// If `timeout` elapses without an event occurring, this method will return 27 | /// `Ok(None)`. If `timeout` is `None`, this method will wait indefinitely. 28 | fn read_raw_event(&mut self, events: &mut [INPUT_RECORD], 29 | timeout: Option) -> io::Result>; 30 | } 31 | -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::screen::{ 2 | Screen, ScreenReadGuard, ScreenWriteGuard, 3 | }; 4 | pub use self::terminal::{ 5 | PrepareState, 6 | Terminal, TerminalReadGuard, TerminalWriteGuard, 7 | }; 8 | 9 | pub mod ext; 10 | mod terminal; 11 | mod screen; 12 | -------------------------------------------------------------------------------- /src/windows/screen.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::sync::{LockResult, Mutex, MutexGuard, TryLockResult}; 3 | use std::time::Duration; 4 | 5 | use winapi::shared::ntdef::HANDLE; 6 | use winapi::um::wincon::INPUT_RECORD; 7 | 8 | use crate::buffer::ScreenBuffer; 9 | use crate::priv_util::{ 10 | map_lock_result, map_try_lock_result, 11 | map2_lock_result, map2_try_lock_result, 12 | }; 13 | use crate::sys::terminal::{ 14 | size_event, PrepareState, 15 | Terminal, TerminalReadGuard, TerminalWriteGuard, 16 | }; 17 | use crate::terminal::{Color, Cursor, CursorMode, Event, PrepareConfig, Size, Style}; 18 | 19 | pub struct Screen { 20 | term: Terminal, 21 | 22 | state: Option, 23 | old_handle: HANDLE, 24 | writer: Mutex, 25 | } 26 | 27 | pub struct ScreenReadGuard<'a> { 28 | screen: &'a Screen, 29 | reader: TerminalReadGuard<'a>, 30 | } 31 | 32 | pub struct ScreenWriteGuard<'a> { 33 | writer: TerminalWriteGuard<'a>, 34 | data: MutexGuard<'a, Writer>, 35 | } 36 | 37 | struct Writer { 38 | buffer: ScreenBuffer, 39 | clear_screen: bool, 40 | real_cursor: Cursor, 41 | } 42 | 43 | impl Screen { 44 | pub fn new(term: Terminal, config: PrepareConfig) -> io::Result { 45 | let size = term.size()?; 46 | 47 | let old_handle = term.enter_screen()?; 48 | let state = term.prepare(config)?; 49 | 50 | Ok(Screen{ 51 | term, 52 | state: Some(state), 53 | writer: Mutex::new(Writer{ 54 | buffer: ScreenBuffer::new(size), 55 | clear_screen: false, 56 | real_cursor: Cursor::default(), 57 | }), 58 | old_handle, 59 | }) 60 | } 61 | 62 | pub fn stdout(config: PrepareConfig) -> io::Result { 63 | Screen::new(Terminal::stdout()?, config) 64 | } 65 | 66 | pub fn stderr(config: PrepareConfig) -> io::Result { 67 | Screen::new(Terminal::stderr()?, config) 68 | } 69 | 70 | forward_screen_buffer_methods!{ |slf| slf.lock_write_data().buffer } 71 | 72 | pub fn lock_read(&self) -> LockResult { 73 | map_lock_result(self.term.lock_read(), 74 | |r| ScreenReadGuard::new(self, r)) 75 | } 76 | 77 | pub fn try_lock_read(&self) -> TryLockResult { 78 | map_try_lock_result(self.term.try_lock_read(), 79 | |r| ScreenReadGuard::new(self, r)) 80 | } 81 | 82 | pub fn lock_write(&self) -> LockResult { 83 | map2_lock_result(self.term.lock_write(), self.writer.lock(), 84 | |a, b| ScreenWriteGuard::new(a, b)) 85 | } 86 | 87 | pub fn try_lock_write(&self) -> TryLockResult { 88 | map2_try_lock_result(self.term.try_lock_write(), self.writer.try_lock(), 89 | |a, b| ScreenWriteGuard::new(a, b)) 90 | } 91 | 92 | fn lock_reader(&self) -> ScreenReadGuard { 93 | self.lock_read().expect("Screen::lock_reader") 94 | } 95 | 96 | fn lock_writer(&self) -> ScreenWriteGuard { 97 | self.lock_write().expect("Screen::lock_writer") 98 | } 99 | 100 | fn lock_write_data(&self) -> MutexGuard { 101 | self.writer.lock().expect("Screen::lock_writer") 102 | } 103 | 104 | pub fn name(&self) -> &str { 105 | self.term.name() 106 | } 107 | 108 | pub fn set_cursor_mode(&self, mode: CursorMode) -> io::Result<()> { 109 | self.term.set_cursor_mode(mode) 110 | } 111 | 112 | pub fn wait_event(&self, timeout: Option) -> io::Result { 113 | self.lock_reader().wait_event(timeout) 114 | } 115 | 116 | pub fn read_event(&self, timeout: Option) -> io::Result> { 117 | self.lock_reader().read_event(timeout) 118 | } 119 | 120 | pub fn read_raw(&self, buf: &mut [u16], timeout: Option) -> io::Result> { 121 | self.lock_reader().read_raw(buf, timeout) 122 | } 123 | 124 | pub fn read_raw_event(&self, events: &mut [INPUT_RECORD], 125 | timeout: Option) -> io::Result> { 126 | self.lock_reader().read_raw_event(events, timeout) 127 | } 128 | 129 | pub fn refresh(&self) -> io::Result<()> { 130 | self.lock_writer().refresh() 131 | } 132 | } 133 | 134 | impl Drop for Screen { 135 | fn drop(&mut self) { 136 | let res = if let Some(state) = self.state.take() { 137 | self.term.restore(state) 138 | } else { 139 | Ok(()) 140 | }; 141 | 142 | if let Err(e) = res.and_then( 143 | |_| unsafe { self.term.exit_screen(self.old_handle) }) { 144 | eprintln!("failed to restore terminal: {}", e); 145 | } 146 | } 147 | } 148 | 149 | unsafe impl Send for Screen {} 150 | unsafe impl Sync for Screen {} 151 | 152 | impl<'a> ScreenReadGuard<'a> { 153 | fn new(screen: &'a Screen, reader: TerminalReadGuard<'a>) -> ScreenReadGuard<'a> { 154 | ScreenReadGuard{screen, reader} 155 | } 156 | 157 | pub fn wait_event(&mut self, timeout: Option) -> io::Result { 158 | self.reader.wait_event(timeout) 159 | } 160 | 161 | pub fn read_event(&mut self, timeout: Option) -> io::Result> { 162 | let r = self.reader.read_event(timeout)?; 163 | 164 | if let Some(Event::Resize(size)) = r { 165 | self.screen.lock_write_data().update_size(size); 166 | } 167 | 168 | Ok(r) 169 | } 170 | 171 | pub fn read_raw(&mut self, buf: &mut [u16], timeout: Option) -> io::Result> { 172 | let r = self.reader.read_raw(buf, timeout)?; 173 | 174 | if let Some(Event::Resize(size)) = r { 175 | self.screen.lock_write_data().update_size(size); 176 | } 177 | 178 | Ok(r) 179 | } 180 | 181 | pub fn read_raw_event(&mut self, events: &mut [INPUT_RECORD], 182 | timeout: Option) -> io::Result> { 183 | let r = self.reader.read_raw_event(events, timeout)?; 184 | 185 | if let Some(Event::Raw(n)) = r { 186 | for ev in events[..n].iter().rev() { 187 | if let Some(size) = size_event(ev) { 188 | self.screen.lock_write_data().update_size(size); 189 | break; 190 | } 191 | } 192 | } 193 | 194 | Ok(r) 195 | } 196 | } 197 | 198 | impl<'a> ScreenWriteGuard<'a> { 199 | fn new(writer: TerminalWriteGuard<'a>, data: MutexGuard<'a, Writer>) 200 | -> ScreenWriteGuard<'a> { 201 | ScreenWriteGuard{writer, data} 202 | } 203 | 204 | forward_screen_buffer_mut_methods!{ |slf| slf.data.buffer } 205 | 206 | pub fn set_cursor_mode(&mut self, mode: CursorMode) -> io::Result<()> { 207 | self.writer.set_cursor_mode(mode) 208 | } 209 | 210 | pub fn refresh(&mut self) -> io::Result<()> { 211 | if self.data.clear_screen { 212 | self.writer.clear_screen()?; 213 | self.data.clear_screen = false; 214 | } 215 | 216 | let mut real_attrs = (None, None, Style::empty()); 217 | 218 | self.writer.clear_attributes()?; 219 | 220 | let mut indices = self.data.buffer.indices(); 221 | 222 | while let Some((pos, cell)) = self.data.buffer.next_cell(&mut indices) { 223 | self.move_cursor(pos)?; 224 | 225 | self.apply_attrs(real_attrs, cell.attrs())?; 226 | self.writer.write_str(cell.text())?; 227 | self.data.real_cursor.column += 1; 228 | 229 | real_attrs = cell.attrs(); 230 | } 231 | 232 | self.writer.clear_attributes()?; 233 | 234 | let size = self.data.buffer.size(); 235 | let pos = self.data.buffer.cursor(); 236 | 237 | if pos.is_out_of_bounds(size) { 238 | self.move_cursor(Cursor::last(size))?; 239 | } else { 240 | self.move_cursor(pos)?; 241 | } 242 | 243 | Ok(()) 244 | } 245 | 246 | fn apply_attrs(&mut self, 247 | src: (Option, Option, Style), 248 | dest: (Option, Option, Style)) -> io::Result<()> { 249 | if src != dest { 250 | self.writer.set_attributes(dest.0, dest.1, dest.2)?; 251 | } 252 | Ok(()) 253 | } 254 | 255 | fn move_cursor(&mut self, pos: Cursor) -> io::Result<()> { 256 | if self.data.real_cursor != pos { 257 | self.writer.move_cursor(pos)?; 258 | self.data.real_cursor = pos; 259 | } 260 | Ok(()) 261 | } 262 | } 263 | 264 | impl<'a> Drop for ScreenWriteGuard<'a> { 265 | fn drop(&mut self) { 266 | if let Err(e) = self.refresh() { 267 | eprintln!("failed to refresh screen: {}", e); 268 | } 269 | } 270 | } 271 | 272 | impl Writer { 273 | fn update_size(&mut self, new_size: Size) { 274 | if self.real_cursor.is_out_of_bounds(new_size) { 275 | // Force cursor move on next refresh 276 | self.real_cursor = (!0, !0).into(); 277 | } 278 | self.buffer.resize(new_size); 279 | self.clear_screen = true; 280 | } 281 | } 282 | --------------------------------------------------------------------------------