├── .builds └── linux.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── doc └── modifyOtherKeys-example.txt ├── examples └── parselog.rs ├── rustfmt.toml ├── src ├── ansi.rs ├── lib.rs └── params.rs └── tests └── demo.vte /.builds/linux.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | sources: 3 | - https://github.com/alacritty/vte 4 | tasks: 5 | - rustup: | 6 | curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --profile minimal -c clippy 7 | - stable: | 8 | cd vte 9 | $HOME/.cargo/bin/cargo +stable test 10 | $HOME/.cargo/bin/cargo +stable test --features=ansi 11 | $HOME/.cargo/bin/cargo +stable test --features=ansi --no-default-features 12 | - clippy: | 13 | cd vte 14 | $HOME/.cargo/bin/cargo +stable clippy 15 | $HOME/.cargo/bin/cargo +stable clippy --features=ansi 16 | - rustfmt: | 17 | $HOME/.cargo/bin/rustup toolchain install nightly -c rustfmt 18 | cd vte 19 | $HOME/.cargo/bin/cargo +nightly fmt -- --check 20 | - msrv: | 21 | cd vte 22 | msrv=$(cat Cargo.toml | grep "rust-version" | sed 's/.*"\(.*\)".*/\1/') 23 | $HOME/.cargo/bin/rustup toolchain install --profile minimal $msrv 24 | rm Cargo.lock 25 | $HOME/.cargo/bin/cargo +$msrv test 26 | - rustdoc: | 27 | $HOME/.cargo/bin/rustup toolchain install nightly -c rust-docs 28 | cd vte 29 | RUSTDOCFLAGS="--cfg docsrs -Dwarnings" $HOME/.cargo/bin/cargo +nightly doc --all-features --no-deps 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | ## 0.15.0 5 | 6 | - Support `CSI ? 5 W` to reset tabs stops to every 8th column 7 | - Replaced `no_std` with a new `std` feature 8 | - Changed default features to include `std` 9 | 10 | ## 0.14.1 11 | 12 | - Crash when partial advance buffer stopped inside some grapheme boundaries 13 | 14 | ## 0.14.0 15 | 16 | - `Parser::advance` now takes byte slices, instead of individual bytes 17 | - `Parser::advance_until_terminated` allows premature termination, 18 | by checking for `Perform::terminated` after each dispatch 19 | 20 | ## 0.13.1 21 | 22 | - Add SCP control support 23 | - Improve SGR performance 24 | 25 | ## 0.13.0 26 | 27 | - Reexport `cursor_icon` crate in `ansi` 28 | - Split-out private modes from `Mode` into `PrivateMode` 29 | - Add `unset_private_mode` and `set_private_mode` 30 | - Add `report_mode` and `report_private_mode` to handle DECRPM/DECRQM 31 | 32 | ## 0.12.0 33 | 34 | - Add support for OSC 22 35 | - Add support for kitty keyboard protocol 36 | - Add support for XTerm's modifyOtherKeys protocol 37 | 38 | ## 0.11.1 39 | 40 | - Minimum rust version has been bumped to 1.62.1 41 | - Support for ANSI terminal stream parsing under the `ansi` feature. 42 | - Addition of the `serde` feature which derives `Serialize` and `Deserialize` 43 | for the types provided in the `ansi` module. 44 | 45 | ## 0.11.0 46 | 47 | - Minimum rust version has been bumped to 1.56.0 48 | - Fixed infinite loop in `Params` iterator when 32nd parameter is a subparameter 49 | 50 | ## 0.10.1 51 | 52 | - Fixed invalid intermediates when transitioning from DCS to ESC 53 | 54 | ## 0.10.0 55 | 56 | - Changed the type of CSI parameters from i64 to u16 57 | - All methods of the `Perform` trait are now optional 58 | 59 | ## 0.9.0 60 | 61 | - Added CSI subparameter support; required changes can be seen in Alacritty: 62 | https://github.com/alacritty/alacritty/commit/576252294d09c1f52ec73bde03652349bdf5a529#diff-49ac9e6f6e6a855312bfcd393201f18ca53e6148c4a22a3a4949f1f9d1d137a8 63 | 64 | ## 0.8.0 65 | 66 | - Remove C1 ST support in OSCs, fixing OSCs with ST in the payload 67 | 68 | ## 0.7.1 69 | 70 | - Out of bounds when parsing a DCS with more than 16 parameters 71 | 72 | ## 0.7.0 73 | 74 | - Fix params reset between escapes 75 | - Removed unused parameter from `esc_dispatch` 76 | 77 | ## 0.6.0 78 | 79 | - Fix build failure on Rust 1.36.0 80 | - Add `bool_terminated` parameter to osc dispatch 81 | 82 | ## 0.5.0 83 | 84 | - Support for dynamically sized escape buffers without feature `no_std` 85 | - Improved UTF8 parser performance 86 | - Migrate to Rust 2018 87 | 88 | ## 0.4.0 89 | 90 | - Fix handling of DCS escapes 91 | 92 | ## 0.3.3 93 | 94 | - Fix off-by-one error in CSI parsing when params list was at max length 95 | (previously caused a panic). 96 | - Support no_std 97 | 98 | ## 0.2.0 99 | 100 | - Removes `osc_start`, `osc_put`, and `osc_end` 101 | - Adds `osc_dispatch` which simply receives a list of parameters 102 | - Removes `byte: u8` parameter from `hook` and `unhook` because it's always 103 | zero. 104 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Joe Wilm ", "Christian Duerr "] 3 | description = "Parser for implementing terminal emulators" 4 | repository = "https://github.com/alacritty/vte" 5 | documentation = "https://docs.rs/vte/" 6 | keywords = ["ansi", "vte", "parser", "terminal"] 7 | categories = ["parsing", "no-std"] 8 | exclude = ["/.travis.yml"] 9 | readme = "README.md" 10 | license = "Apache-2.0 OR MIT" 11 | version = "0.15.0" 12 | name = "vte" 13 | edition = "2021" 14 | rust-version = "1.62.1" 15 | 16 | [features] 17 | ansi = ["log", "cursor-icon", "bitflags"] 18 | default = ["std"] 19 | std = ["memchr/std"] 20 | serde = ["dep:serde"] 21 | 22 | [dependencies] 23 | arrayvec = { version = "0.7.2", default-features = false } 24 | bitflags = { version = "2.3.3", default-features = false, optional = true } 25 | cursor-icon = { version = "1.0.0", default-features = false, optional = true } 26 | log = { version = "0.4.17", optional = true } 27 | memchr = { version = "2.7.4", default-features = false } 28 | serde = { version = "1.0.160", features = ["derive"], optional = true } 29 | 30 | [package.metadata.docs.rs] 31 | all-features = true 32 | rustdoc-args = ["--cfg", "docsrs"] 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Joe Wilm 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 | vte 2 | === 3 | 4 | [![Build Status](https://travis-ci.org/alacritty/vte.svg?branch=master)](https://travis-ci.org/alacritty/vte) 5 | [![Crates.io Version](https://img.shields.io/crates/v/vte.svg)](https://crates.io/crates/vte/) 6 | 7 | Parser for implementing virtual terminal emulators in Rust. 8 | 9 | The parser is implemented according to [Paul Williams' ANSI parser state 10 | machine]. The state machine doesn't assign meaning to the parsed data and is 11 | thus not itself sufficient for writing a terminal emulator. Instead, it is 12 | expected that an implementation of the `Perform` trait which does something 13 | useful with the parsed data. The `Parser` handles the book keeping, and the 14 | `Perform` gets to simply handle actions. 15 | 16 | See the [docs] for more info. 17 | 18 | [Paul Williams' ANSI parser state machine]: https://vt100.net/emu/dec_ansi_parser 19 | [docs]: https://docs.rs/crate/vte/ 20 | -------------------------------------------------------------------------------- /examples/parselog.rs: -------------------------------------------------------------------------------- 1 | //! Parse input from stdin and log actions on stdout 2 | use std::io::{self, Read}; 3 | 4 | use vte::{Params, Parser, Perform}; 5 | 6 | /// A type implementing Perform that just logs actions 7 | struct Log; 8 | 9 | impl Perform for Log { 10 | fn print(&mut self, c: char) { 11 | println!("[print] {:?}", c); 12 | } 13 | 14 | fn execute(&mut self, byte: u8) { 15 | println!("[execute] {:02x}", byte); 16 | } 17 | 18 | fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { 19 | println!( 20 | "[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}", 21 | params, intermediates, ignore, c 22 | ); 23 | } 24 | 25 | fn put(&mut self, byte: u8) { 26 | println!("[put] {:02x}", byte); 27 | } 28 | 29 | fn unhook(&mut self) { 30 | println!("[unhook]"); 31 | } 32 | 33 | fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { 34 | println!("[osc_dispatch] params={:?} bell_terminated={}", params, bell_terminated); 35 | } 36 | 37 | fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { 38 | println!( 39 | "[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}", 40 | params, intermediates, ignore, c 41 | ); 42 | } 43 | 44 | fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { 45 | println!( 46 | "[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}", 47 | intermediates, ignore, byte 48 | ); 49 | } 50 | } 51 | 52 | fn main() { 53 | let input = io::stdin(); 54 | let mut handle = input.lock(); 55 | 56 | let mut statemachine = Parser::new(); 57 | let mut performer = Log; 58 | 59 | let mut buf = [0; 2048]; 60 | 61 | loop { 62 | match handle.read(&mut buf) { 63 | Ok(0) => break, 64 | Ok(n) => statemachine.advance(&mut performer, &buf[..n]), 65 | Err(err) => { 66 | println!("err: {}", err); 67 | break; 68 | }, 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | group_imports = "StdExternalCrate" 3 | match_block_trailing_comma = true 4 | condense_wildcard_suffixes = true 5 | use_field_init_shorthand = true 6 | normalize_doc_attributes = true 7 | overflow_delimited_expr = true 8 | imports_granularity = "Module" 9 | format_macro_matchers = true 10 | use_small_heuristics = "Max" 11 | hex_literal_case = "Upper" 12 | normalize_comments = true 13 | reorder_impl_items = true 14 | use_try_shorthand = true 15 | newline_style = "Unix" 16 | format_strings = true 17 | wrap_comments = true 18 | -------------------------------------------------------------------------------- /src/ansi.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // 3 | // This module was originally part of the `alacritty_terminal` crate, which is 4 | // licensed under the Apache License, Version 2.0 and is part of the Alacritty 5 | // project (https://github.com/alacritty/alacritty). 6 | 7 | //! ANSI Terminal Stream Parsing. 8 | 9 | extern crate alloc; 10 | 11 | use alloc::borrow::ToOwned; 12 | use alloc::string::{String, ToString}; 13 | use alloc::vec::Vec; 14 | use core::convert::TryFrom; 15 | use core::fmt::{self, Display, Formatter, Write}; 16 | #[cfg(feature = "std")] 17 | use core::ops::Mul; 18 | use core::ops::{Add, Sub}; 19 | use core::str::FromStr; 20 | use core::time::Duration; 21 | use core::{iter, mem, str}; 22 | #[cfg(feature = "std")] 23 | use std::time::Instant; 24 | 25 | use bitflags::bitflags; 26 | #[doc(inline)] 27 | pub use cursor_icon; 28 | use cursor_icon::CursorIcon; 29 | use log::debug; 30 | #[cfg(feature = "serde")] 31 | use serde::{Deserialize, Serialize}; 32 | 33 | use crate::{Params, ParamsIter}; 34 | 35 | /// Maximum time before a synchronized update is aborted. 36 | const SYNC_UPDATE_TIMEOUT: Duration = Duration::from_millis(150); 37 | 38 | /// Maximum number of bytes read in one synchronized update (2MiB). 39 | const SYNC_BUFFER_SIZE: usize = 0x20_0000; 40 | 41 | /// Number of bytes in the BSU/ESU CSI sequences. 42 | const SYNC_ESCAPE_LEN: usize = 8; 43 | 44 | /// BSU CSI sequence for beginning or extending synchronized updates. 45 | const BSU_CSI: [u8; SYNC_ESCAPE_LEN] = *b"\x1b[?2026h"; 46 | 47 | /// ESU CSI sequence for terminating synchronized updates. 48 | const ESU_CSI: [u8; SYNC_ESCAPE_LEN] = *b"\x1b[?2026l"; 49 | 50 | #[derive(Debug, PartialEq, Eq, Hash)] 51 | pub struct Hyperlink { 52 | /// Identifier for the given hyperlink. 53 | pub id: Option, 54 | /// Resource identifier of the hyperlink. 55 | pub uri: String, 56 | } 57 | 58 | #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] 59 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 60 | pub struct Rgb { 61 | pub r: u8, 62 | pub g: u8, 63 | pub b: u8, 64 | } 65 | 66 | impl Rgb { 67 | /// Implementation of [W3C's luminance algorithm]. 68 | /// 69 | /// [W3C's luminance algorithm]: https://www.w3.org/TR/WCAG20/#relativeluminancedef 70 | #[cfg(feature = "std")] 71 | pub fn luminance(self) -> f64 { 72 | let channel_luminance = |channel| { 73 | let channel = channel as f64 / 255.; 74 | if channel <= 0.03928 { 75 | channel / 12.92 76 | } else { 77 | f64::powf((channel + 0.055) / 1.055, 2.4) 78 | } 79 | }; 80 | 81 | let r_luminance = channel_luminance(self.r); 82 | let g_luminance = channel_luminance(self.g); 83 | let b_luminance = channel_luminance(self.b); 84 | 85 | 0.2126 * r_luminance + 0.7152 * g_luminance + 0.0722 * b_luminance 86 | } 87 | 88 | /// Implementation of [W3C's contrast algorithm]. 89 | /// 90 | /// [W3C's contrast algorithm]: https://www.w3.org/TR/WCAG20/#contrast-ratiodef 91 | #[cfg(feature = "std")] 92 | pub fn contrast(self, other: Rgb) -> f64 { 93 | let self_luminance = self.luminance(); 94 | let other_luminance = other.luminance(); 95 | 96 | let (darker, lighter) = if self_luminance > other_luminance { 97 | (other_luminance, self_luminance) 98 | } else { 99 | (self_luminance, other_luminance) 100 | }; 101 | 102 | (lighter + 0.05) / (darker + 0.05) 103 | } 104 | } 105 | 106 | // A multiply function for Rgb, as the default dim is just *2/3. 107 | #[cfg(feature = "std")] 108 | impl Mul for Rgb { 109 | type Output = Rgb; 110 | 111 | fn mul(self, rhs: f32) -> Rgb { 112 | let result = Rgb { 113 | r: (f32::from(self.r) * rhs).clamp(0.0, 255.0) as u8, 114 | g: (f32::from(self.g) * rhs).clamp(0.0, 255.0) as u8, 115 | b: (f32::from(self.b) * rhs).clamp(0.0, 255.0) as u8, 116 | }; 117 | 118 | log::trace!("Scaling RGB by {} from {:?} to {:?}", rhs, self, result); 119 | result 120 | } 121 | } 122 | 123 | impl Add for Rgb { 124 | type Output = Rgb; 125 | 126 | fn add(self, rhs: Rgb) -> Rgb { 127 | Rgb { 128 | r: self.r.saturating_add(rhs.r), 129 | g: self.g.saturating_add(rhs.g), 130 | b: self.b.saturating_add(rhs.b), 131 | } 132 | } 133 | } 134 | 135 | impl Sub for Rgb { 136 | type Output = Rgb; 137 | 138 | fn sub(self, rhs: Rgb) -> Rgb { 139 | Rgb { 140 | r: self.r.saturating_sub(rhs.r), 141 | g: self.g.saturating_sub(rhs.g), 142 | b: self.b.saturating_sub(rhs.b), 143 | } 144 | } 145 | } 146 | 147 | impl Display for Rgb { 148 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 149 | write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) 150 | } 151 | } 152 | 153 | impl FromStr for Rgb { 154 | type Err = (); 155 | 156 | fn from_str(s: &str) -> Result { 157 | let chars = if s.starts_with("0x") && s.len() == 8 { 158 | &s[2..] 159 | } else if s.starts_with('#') && s.len() == 7 { 160 | &s[1..] 161 | } else { 162 | return Err(()); 163 | }; 164 | 165 | match u32::from_str_radix(chars, 16) { 166 | Ok(mut color) => { 167 | let b = (color & 0xFF) as u8; 168 | color >>= 8; 169 | let g = (color & 0xFF) as u8; 170 | color >>= 8; 171 | let r = color as u8; 172 | Ok(Rgb { r, g, b }) 173 | }, 174 | Err(_) => Err(()), 175 | } 176 | } 177 | } 178 | 179 | /// Parse colors in XParseColor format. 180 | fn xparse_color(color: &[u8]) -> Option { 181 | if !color.is_empty() && color[0] == b'#' { 182 | parse_legacy_color(&color[1..]) 183 | } else if color.len() >= 4 && &color[..4] == b"rgb:" { 184 | parse_rgb_color(&color[4..]) 185 | } else { 186 | None 187 | } 188 | } 189 | 190 | /// Parse colors in `rgb:r(rrr)/g(ggg)/b(bbb)` format. 191 | fn parse_rgb_color(color: &[u8]) -> Option { 192 | let colors = str::from_utf8(color).ok()?.split('/').collect::>(); 193 | 194 | if colors.len() != 3 { 195 | return None; 196 | } 197 | 198 | // Scale values instead of filling with `0`s. 199 | let scale = |input: &str| { 200 | if input.len() > 4 { 201 | None 202 | } else { 203 | let max = u32::pow(16, input.len() as u32) - 1; 204 | let value = u32::from_str_radix(input, 16).ok()?; 205 | Some((255 * value / max) as u8) 206 | } 207 | }; 208 | 209 | Some(Rgb { r: scale(colors[0])?, g: scale(colors[1])?, b: scale(colors[2])? }) 210 | } 211 | 212 | /// Parse colors in `#r(rrr)g(ggg)b(bbb)` format. 213 | fn parse_legacy_color(color: &[u8]) -> Option { 214 | let item_len = color.len() / 3; 215 | 216 | // Truncate/Fill to two byte precision. 217 | let color_from_slice = |slice: &[u8]| { 218 | let col = usize::from_str_radix(str::from_utf8(slice).ok()?, 16).ok()? << 4; 219 | Some((col >> (4 * slice.len().saturating_sub(1))) as u8) 220 | }; 221 | 222 | Some(Rgb { 223 | r: color_from_slice(&color[0..item_len])?, 224 | g: color_from_slice(&color[item_len..item_len * 2])?, 225 | b: color_from_slice(&color[item_len * 2..])?, 226 | }) 227 | } 228 | 229 | fn parse_number(input: &[u8]) -> Option { 230 | if input.is_empty() { 231 | return None; 232 | } 233 | let mut num: u8 = 0; 234 | for c in input { 235 | let c = *c as char; 236 | let digit = c.to_digit(10)?; 237 | num = num.checked_mul(10).and_then(|v| v.checked_add(digit as u8))?; 238 | } 239 | Some(num) 240 | } 241 | 242 | /// Internal state for VTE processor. 243 | #[derive(Debug, Default)] 244 | struct ProcessorState { 245 | /// Last processed character for repetition. 246 | preceding_char: Option, 247 | 248 | /// State for synchronized terminal updates. 249 | sync_state: SyncState, 250 | } 251 | 252 | #[derive(Debug)] 253 | struct SyncState { 254 | /// Handler for synchronized updates. 255 | timeout: T, 256 | 257 | /// Bytes read during the synchronized update. 258 | buffer: Vec, 259 | } 260 | 261 | impl Default for SyncState { 262 | fn default() -> Self { 263 | Self { buffer: Vec::with_capacity(SYNC_BUFFER_SIZE), timeout: Default::default() } 264 | } 265 | } 266 | 267 | /// The processor wraps a `crate::Parser` to ultimately call methods on a 268 | /// Handler. 269 | #[cfg(feature = "std")] 270 | #[derive(Default)] 271 | pub struct Processor { 272 | state: ProcessorState, 273 | parser: crate::Parser, 274 | } 275 | 276 | /// The processor wraps a `crate::Parser` to ultimately call methods on a 277 | /// Handler. 278 | #[cfg(not(feature = "std"))] 279 | #[derive(Default)] 280 | pub struct Processor { 281 | state: ProcessorState, 282 | parser: crate::Parser, 283 | } 284 | 285 | impl Processor { 286 | #[inline] 287 | pub fn new() -> Self { 288 | Self::default() 289 | } 290 | 291 | /// Synchronized update timeout. 292 | pub fn sync_timeout(&self) -> &T { 293 | &self.state.sync_state.timeout 294 | } 295 | 296 | /// Process a new byte from the PTY. 297 | #[inline] 298 | pub fn advance(&mut self, handler: &mut H, bytes: &[u8]) 299 | where 300 | H: Handler, 301 | { 302 | let mut processed = 0; 303 | while processed != bytes.len() { 304 | if self.state.sync_state.timeout.pending_timeout() { 305 | processed += self.advance_sync(handler, &bytes[processed..]); 306 | } else { 307 | let mut performer = Performer::new(&mut self.state, handler); 308 | processed += 309 | self.parser.advance_until_terminated(&mut performer, &bytes[processed..]); 310 | } 311 | } 312 | } 313 | 314 | /// End a synchronized update. 315 | pub fn stop_sync(&mut self, handler: &mut H) 316 | where 317 | H: Handler, 318 | { 319 | self.stop_sync_internal(handler, None); 320 | } 321 | 322 | /// End a synchronized update. 323 | /// 324 | /// The `bsu_offset` parameter should be passed if the sync buffer contains 325 | /// a new BSU escape that is not part of the current synchronized 326 | /// update. 327 | fn stop_sync_internal(&mut self, handler: &mut H, bsu_offset: Option) 328 | where 329 | H: Handler, 330 | { 331 | // Process all synchronized bytes. 332 | // 333 | // NOTE: We do not use `advance_until_terminated` here since BSU sequences are 334 | // processed automatically during the synchronized update. 335 | let buffer = mem::take(&mut self.state.sync_state.buffer); 336 | let offset = bsu_offset.unwrap_or(buffer.len()); 337 | let mut performer = Performer::new(&mut self.state, handler); 338 | self.parser.advance(&mut performer, &buffer[..offset]); 339 | self.state.sync_state.buffer = buffer; 340 | 341 | match bsu_offset { 342 | // Just clear processed bytes if there is a new BSU. 343 | // 344 | // NOTE: We do not need to re-process for a new ESU since the `advance_sync` 345 | // function checks for BSUs in reverse. 346 | Some(bsu_offset) => { 347 | let new_len = self.state.sync_state.buffer.len() - bsu_offset; 348 | self.state.sync_state.buffer.copy_within(bsu_offset.., 0); 349 | self.state.sync_state.buffer.truncate(new_len); 350 | }, 351 | // Report mode and clear state if no new BSU is present. 352 | None => { 353 | handler.unset_private_mode(NamedPrivateMode::SyncUpdate.into()); 354 | self.state.sync_state.timeout.clear_timeout(); 355 | self.state.sync_state.buffer.clear(); 356 | }, 357 | } 358 | } 359 | 360 | /// Number of bytes in the synchronization buffer. 361 | #[inline] 362 | pub fn sync_bytes_count(&self) -> usize { 363 | self.state.sync_state.buffer.len() 364 | } 365 | 366 | /// Process a new byte during a synchronized update. 367 | /// 368 | /// Returns the number of bytes processed. 369 | #[cold] 370 | fn advance_sync(&mut self, handler: &mut H, bytes: &[u8]) -> usize 371 | where 372 | H: Handler, 373 | { 374 | // Advance sync parser or stop sync if we'd exceed the maximum buffer size. 375 | if self.state.sync_state.buffer.len() + bytes.len() >= SYNC_BUFFER_SIZE - 1 { 376 | // Terminate the synchronized update. 377 | self.stop_sync_internal(handler, None); 378 | 379 | // Just parse the bytes normally. 380 | let mut performer = Performer::new(&mut self.state, handler); 381 | self.parser.advance_until_terminated(&mut performer, bytes) 382 | } else { 383 | self.state.sync_state.buffer.extend(bytes); 384 | self.advance_sync_csi(handler, bytes.len()); 385 | bytes.len() 386 | } 387 | } 388 | 389 | /// Handle BSU/ESU CSI sequences during synchronized update. 390 | fn advance_sync_csi(&mut self, handler: &mut H, new_bytes: usize) 391 | where 392 | H: Handler, 393 | { 394 | // Get constraints within which a new escape character might be relevant. 395 | let buffer_len = self.state.sync_state.buffer.len(); 396 | let start_offset = (buffer_len - new_bytes).saturating_sub(SYNC_ESCAPE_LEN - 1); 397 | let end_offset = buffer_len.saturating_sub(SYNC_ESCAPE_LEN - 1); 398 | let search_buffer = &self.state.sync_state.buffer[start_offset..end_offset]; 399 | 400 | // Search for termination/extension escapes in the added bytes. 401 | // 402 | // NOTE: It is technically legal to specify multiple private modes in the same 403 | // escape, but we only allow EXACTLY `\e[?2026h`/`\e[?2026l` to keep the parser 404 | // more simple. 405 | let mut bsu_offset = None; 406 | for index in memchr::memchr_iter(0x1B, search_buffer).rev() { 407 | let offset = start_offset + index; 408 | let escape = &self.state.sync_state.buffer[offset..offset + SYNC_ESCAPE_LEN]; 409 | 410 | if escape == BSU_CSI { 411 | self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); 412 | bsu_offset = Some(offset); 413 | } else if escape == ESU_CSI { 414 | self.stop_sync_internal(handler, bsu_offset); 415 | break; 416 | } 417 | } 418 | } 419 | } 420 | 421 | /// Helper type that implements `crate::Perform`. 422 | /// 423 | /// Processor creates a Performer when running advance and passes the Performer 424 | /// to `crate::Parser`. 425 | struct Performer<'a, H: Handler, T: Timeout> { 426 | state: &'a mut ProcessorState, 427 | handler: &'a mut H, 428 | 429 | /// Whether the parser should be prematurely terminated. 430 | terminated: bool, 431 | } 432 | 433 | impl<'a, H: Handler + 'a, T: Timeout> Performer<'a, H, T> { 434 | /// Create a performer. 435 | #[inline] 436 | pub fn new<'b>(state: &'b mut ProcessorState, handler: &'b mut H) -> Performer<'b, H, T> { 437 | Performer { state, handler, terminated: Default::default() } 438 | } 439 | } 440 | 441 | #[cfg(feature = "std")] 442 | #[derive(Default)] 443 | pub struct StdSyncHandler { 444 | timeout: Option, 445 | } 446 | 447 | #[cfg(feature = "std")] 448 | impl StdSyncHandler { 449 | /// Synchronized update expiration time. 450 | #[inline] 451 | pub fn sync_timeout(&self) -> Option { 452 | self.timeout 453 | } 454 | } 455 | 456 | #[cfg(feature = "std")] 457 | impl Timeout for StdSyncHandler { 458 | #[inline] 459 | fn set_timeout(&mut self, duration: Duration) { 460 | self.timeout = Some(Instant::now() + duration); 461 | } 462 | 463 | #[inline] 464 | fn clear_timeout(&mut self) { 465 | self.timeout = None; 466 | } 467 | 468 | #[inline] 469 | fn pending_timeout(&self) -> bool { 470 | self.timeout.is_some() 471 | } 472 | } 473 | 474 | /// Interface for creating timeouts and checking their expiry. 475 | /// 476 | /// This is internally used by the [`Processor`] to handle synchronized 477 | /// updates. 478 | pub trait Timeout: Default { 479 | /// Sets the timeout for the next synchronized update. 480 | /// 481 | /// The `duration` parameter specifies the duration of the timeout. Once the 482 | /// specified duration has elapsed, the synchronized update rotuine can be 483 | /// performed. 484 | fn set_timeout(&mut self, duration: Duration); 485 | /// Clear the current timeout. 486 | fn clear_timeout(&mut self); 487 | /// Returns whether a timeout is currently active and has not yet expired. 488 | fn pending_timeout(&self) -> bool; 489 | } 490 | 491 | /// Type that handles actions from the parser. 492 | /// 493 | /// XXX Should probably not provide default impls for everything, but it makes 494 | /// writing specific handler impls for tests far easier. 495 | pub trait Handler { 496 | /// OSC to set window title. 497 | fn set_title(&mut self, _: Option) {} 498 | 499 | /// Set the cursor style. 500 | fn set_cursor_style(&mut self, _: Option) {} 501 | 502 | /// Set the cursor shape. 503 | fn set_cursor_shape(&mut self, _shape: CursorShape) {} 504 | 505 | /// A character to be displayed. 506 | fn input(&mut self, _c: char) {} 507 | 508 | /// Set cursor to position. 509 | fn goto(&mut self, _line: i32, _col: usize) {} 510 | 511 | /// Set cursor to specific row. 512 | fn goto_line(&mut self, _line: i32) {} 513 | 514 | /// Set cursor to specific column. 515 | fn goto_col(&mut self, _col: usize) {} 516 | 517 | /// Insert blank characters in current line starting from cursor. 518 | fn insert_blank(&mut self, _: usize) {} 519 | 520 | /// Move cursor up `rows`. 521 | fn move_up(&mut self, _: usize) {} 522 | 523 | /// Move cursor down `rows`. 524 | fn move_down(&mut self, _: usize) {} 525 | 526 | /// Identify the terminal (should write back to the pty stream). 527 | fn identify_terminal(&mut self, _intermediate: Option) {} 528 | 529 | /// Report device status. 530 | fn device_status(&mut self, _: usize) {} 531 | 532 | /// Move cursor forward `cols`. 533 | fn move_forward(&mut self, _col: usize) {} 534 | 535 | /// Move cursor backward `cols`. 536 | fn move_backward(&mut self, _col: usize) {} 537 | 538 | /// Move cursor down `rows` and set to column 1. 539 | fn move_down_and_cr(&mut self, _row: usize) {} 540 | 541 | /// Move cursor up `rows` and set to column 1. 542 | fn move_up_and_cr(&mut self, _row: usize) {} 543 | 544 | /// Put `count` tabs. 545 | fn put_tab(&mut self, _count: u16) {} 546 | 547 | /// Backspace `count` characters. 548 | fn backspace(&mut self) {} 549 | 550 | /// Carriage return. 551 | fn carriage_return(&mut self) {} 552 | 553 | /// Linefeed. 554 | fn linefeed(&mut self) {} 555 | 556 | /// Ring the bell. 557 | /// 558 | /// Hopefully this is never implemented. 559 | fn bell(&mut self) {} 560 | 561 | /// Substitute char under cursor. 562 | fn substitute(&mut self) {} 563 | 564 | /// Newline. 565 | fn newline(&mut self) {} 566 | 567 | /// Set current position as a tabstop. 568 | fn set_horizontal_tabstop(&mut self) {} 569 | 570 | /// Scroll up `rows` rows. 571 | fn scroll_up(&mut self, _: usize) {} 572 | 573 | /// Scroll down `rows` rows. 574 | fn scroll_down(&mut self, _: usize) {} 575 | 576 | /// Insert `count` blank lines. 577 | fn insert_blank_lines(&mut self, _: usize) {} 578 | 579 | /// Delete `count` lines. 580 | fn delete_lines(&mut self, _: usize) {} 581 | 582 | /// Erase `count` chars in current line following cursor. 583 | /// 584 | /// Erase means resetting to the default state (default colors, no content, 585 | /// no mode flags). 586 | fn erase_chars(&mut self, _: usize) {} 587 | 588 | /// Delete `count` chars. 589 | /// 590 | /// Deleting a character is like the delete key on the keyboard - everything 591 | /// to the right of the deleted things is shifted left. 592 | fn delete_chars(&mut self, _: usize) {} 593 | 594 | /// Move backward `count` tabs. 595 | fn move_backward_tabs(&mut self, _count: u16) {} 596 | 597 | /// Move forward `count` tabs. 598 | fn move_forward_tabs(&mut self, _count: u16) {} 599 | 600 | /// Save current cursor position. 601 | fn save_cursor_position(&mut self) {} 602 | 603 | /// Restore cursor position. 604 | fn restore_cursor_position(&mut self) {} 605 | 606 | /// Clear current line. 607 | fn clear_line(&mut self, _mode: LineClearMode) {} 608 | 609 | /// Clear screen. 610 | fn clear_screen(&mut self, _mode: ClearMode) {} 611 | 612 | /// Clear tab stops. 613 | fn clear_tabs(&mut self, _mode: TabulationClearMode) {} 614 | 615 | /// Set tab stops at every `interval`. 616 | fn set_tabs(&mut self, _interval: u16) {} 617 | 618 | /// Reset terminal state. 619 | fn reset_state(&mut self) {} 620 | 621 | /// Reverse Index. 622 | /// 623 | /// Move the active position to the same horizontal position on the 624 | /// preceding line. If the active position is at the top margin, a scroll 625 | /// down is performed. 626 | fn reverse_index(&mut self) {} 627 | 628 | /// Set a terminal attribute. 629 | fn terminal_attribute(&mut self, _attr: Attr) {} 630 | 631 | /// Set mode. 632 | fn set_mode(&mut self, _mode: Mode) {} 633 | 634 | /// Unset mode. 635 | fn unset_mode(&mut self, _mode: Mode) {} 636 | 637 | /// DECRPM - report mode. 638 | fn report_mode(&mut self, _mode: Mode) {} 639 | 640 | /// Set private mode. 641 | fn set_private_mode(&mut self, _mode: PrivateMode) {} 642 | 643 | /// Unset private mode. 644 | fn unset_private_mode(&mut self, _mode: PrivateMode) {} 645 | 646 | /// DECRPM - report private mode. 647 | fn report_private_mode(&mut self, _mode: PrivateMode) {} 648 | 649 | /// DECSTBM - Set the terminal scrolling region. 650 | fn set_scrolling_region(&mut self, _top: usize, _bottom: Option) {} 651 | 652 | /// DECKPAM - Set keypad to applications mode (ESCape instead of digits). 653 | fn set_keypad_application_mode(&mut self) {} 654 | 655 | /// DECKPNM - Set keypad to numeric mode (digits instead of ESCape seq). 656 | fn unset_keypad_application_mode(&mut self) {} 657 | 658 | /// Set one of the graphic character sets, G0 to G3, as the active charset. 659 | /// 660 | /// 'Invoke' one of G0 to G3 in the GL area. Also referred to as shift in, 661 | /// shift out and locking shift depending on the set being activated. 662 | fn set_active_charset(&mut self, _: CharsetIndex) {} 663 | 664 | /// Assign a graphic character set to G0, G1, G2 or G3. 665 | /// 666 | /// 'Designate' a graphic character set as one of G0 to G3, so that it can 667 | /// later be 'invoked' by `set_active_charset`. 668 | fn configure_charset(&mut self, _: CharsetIndex, _: StandardCharset) {} 669 | 670 | /// Set an indexed color value. 671 | fn set_color(&mut self, _: usize, _: Rgb) {} 672 | 673 | /// Respond to a color query escape sequence. 674 | fn dynamic_color_sequence(&mut self, _: String, _: usize, _: &str) {} 675 | 676 | /// Reset an indexed color to original value. 677 | fn reset_color(&mut self, _: usize) {} 678 | 679 | /// Store data into clipboard. 680 | fn clipboard_store(&mut self, _: u8, _: &[u8]) {} 681 | 682 | /// Load data from clipboard. 683 | fn clipboard_load(&mut self, _: u8, _: &str) {} 684 | 685 | /// Run the decaln routine. 686 | fn decaln(&mut self) {} 687 | 688 | /// Push a title onto the stack. 689 | fn push_title(&mut self) {} 690 | 691 | /// Pop the last title from the stack. 692 | fn pop_title(&mut self) {} 693 | 694 | /// Report text area size in pixels. 695 | fn text_area_size_pixels(&mut self) {} 696 | 697 | /// Report text area size in characters. 698 | fn text_area_size_chars(&mut self) {} 699 | 700 | /// Set hyperlink. 701 | fn set_hyperlink(&mut self, _: Option) {} 702 | 703 | /// Set mouse cursor icon. 704 | fn set_mouse_cursor_icon(&mut self, _: CursorIcon) {} 705 | 706 | /// Report current keyboard mode. 707 | fn report_keyboard_mode(&mut self) {} 708 | 709 | /// Push keyboard mode into the keyboard mode stack. 710 | fn push_keyboard_mode(&mut self, _mode: KeyboardModes) {} 711 | 712 | /// Pop the given amount of keyboard modes from the 713 | /// keyboard mode stack. 714 | fn pop_keyboard_modes(&mut self, _to_pop: u16) {} 715 | 716 | /// Set the [`keyboard mode`] using the given [`behavior`]. 717 | /// 718 | /// [`keyboard mode`]: crate::ansi::KeyboardModes 719 | /// [`behavior`]: crate::ansi::KeyboardModesApplyBehavior 720 | fn set_keyboard_mode(&mut self, _mode: KeyboardModes, _behavior: KeyboardModesApplyBehavior) {} 721 | 722 | /// Set XTerm's [`ModifyOtherKeys`] option. 723 | fn set_modify_other_keys(&mut self, _mode: ModifyOtherKeys) {} 724 | 725 | /// Report XTerm's [`ModifyOtherKeys`] state. 726 | /// 727 | /// The output is of form `CSI > 4 ; mode m`. 728 | fn report_modify_other_keys(&mut self) {} 729 | 730 | // Set SCP control. 731 | fn set_scp(&mut self, _char_path: ScpCharPath, _update_mode: ScpUpdateMode) {} 732 | } 733 | 734 | bitflags! { 735 | /// A set of [`kitty keyboard protocol'] modes. 736 | /// 737 | /// [`kitty keyboard protocol']: https://sw.kovidgoyal.net/kitty/keyboard-protocol 738 | #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] 739 | pub struct KeyboardModes : u8 { 740 | /// No keyboard protocol mode is set. 741 | const NO_MODE = 0b0000_0000; 742 | /// Report `Esc`, `alt` + `key`, `ctrl` + `key`, `ctrl` + `alt` + `key`, `shift` 743 | /// + `alt` + `key` keys using `CSI u` sequence instead of raw ones. 744 | const DISAMBIGUATE_ESC_CODES = 0b0000_0001; 745 | /// Report key presses, release, and repetition alongside the escape. Key events 746 | /// that result in text are reported as plain UTF-8, unless the 747 | /// [`Self::REPORT_ALL_KEYS_AS_ESC`] is enabled. 748 | const REPORT_EVENT_TYPES = 0b0000_0010; 749 | /// Additionally report shifted key an dbase layout key. 750 | const REPORT_ALTERNATE_KEYS = 0b0000_0100; 751 | /// Report every key as an escape sequence. 752 | const REPORT_ALL_KEYS_AS_ESC = 0b0000_1000; 753 | /// Report the text generated by the key event. 754 | const REPORT_ASSOCIATED_TEXT = 0b0001_0000; 755 | } 756 | } 757 | 758 | /// XTMODKEYS modifyOtherKeys state. 759 | /// 760 | /// This only applies to keys corresponding to ascii characters. 761 | /// 762 | /// For the details on how to implement the mode handling correctly, consult 763 | /// [`XTerm's implementation`] and the [`output`] of XTerm's provided [`perl 764 | /// script`]. Some libraries and implementations also use the [`fixterms`] 765 | /// definition of the `CSI u`. 766 | /// 767 | /// The end escape sequence has a `CSI char; modifiers u` form while the 768 | /// original `CSI 27 ; modifier ; char ~`. The clients should prefer the `CSI 769 | /// u`, since it has more adoption. 770 | /// 771 | /// [`XTerm's implementation`]: https://invisible-island.net/xterm/modified-keys.html 772 | /// [`perl script`]: https://github.com/ThomasDickey/xterm-snapshots/blob/master/vttests/modify-keys.pl 773 | /// [`output`]: https://github.com/alacritty/vte/blob/master/doc/modifyOtherKeys-example.txt 774 | /// [`fixterms`]: http://www.leonerd.org.uk/hacks/fixterms/ 775 | #[repr(u8)] 776 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 777 | pub enum ModifyOtherKeys { 778 | /// Reset the state. 779 | Reset, 780 | /// Enables this feature except for keys with well-known behavior, e.g., 781 | /// Tab, Backspace and some special control character cases which are 782 | /// built into the X11 library (e.g., Control-Space to make a NUL, or 783 | /// Control-3 to make an Escape character). 784 | /// 785 | /// Escape sequences shouldn't be emitted under the following circumstances: 786 | /// - When the key is in range of `[64;127]` and the modifier is either 787 | /// Control or Shift 788 | /// - When the key combination is a known control combination alias 789 | /// 790 | /// For more details, consult the [`example`] for the suggested translation. 791 | /// 792 | /// [`example`]: https://github.com/alacritty/vte/blob/master/doc/modifyOtherKeys-example.txt 793 | EnableExceptWellDefined, 794 | /// Enables this feature for all keys including the exceptions of 795 | /// [`Self::EnableExceptWellDefined`]. XTerm still ignores the special 796 | /// cases built into the X11 library. Any shifted (modified) ordinary 797 | /// key send an escape sequence. The Alt- and Meta- modifiers cause 798 | /// XTerm to send escape sequences. 799 | /// 800 | /// For more details, consult the [`example`] for the suggested translation. 801 | /// 802 | /// [`example`]: https://github.com/alacritty/vte/blob/master/doc/modifyOtherKeys-example.txt 803 | EnableAll, 804 | } 805 | 806 | /// Describes how the new [`KeyboardModes`] should be applied. 807 | #[repr(u8)] 808 | #[derive(Default, Clone, Copy, PartialEq, Eq)] 809 | pub enum KeyboardModesApplyBehavior { 810 | /// Replace the active flags with the new ones. 811 | #[default] 812 | Replace, 813 | /// Merge the given flags with currently active ones. 814 | Union, 815 | /// Remove the given flags from the active ones. 816 | Difference, 817 | } 818 | 819 | /// Terminal cursor configuration. 820 | #[derive(Default, Debug, Eq, PartialEq, Copy, Clone, Hash)] 821 | pub struct CursorStyle { 822 | pub shape: CursorShape, 823 | pub blinking: bool, 824 | } 825 | 826 | /// Terminal cursor shape. 827 | #[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash)] 828 | pub enum CursorShape { 829 | /// Cursor is a block like `▒`. 830 | #[default] 831 | Block, 832 | 833 | /// Cursor is an underscore like `_`. 834 | Underline, 835 | 836 | /// Cursor is a vertical bar `⎸`. 837 | Beam, 838 | 839 | /// Cursor is a box like `☐`. 840 | HollowBlock, 841 | 842 | /// Invisible cursor. 843 | Hidden, 844 | } 845 | 846 | /// Wrapper for the ANSI modes. 847 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 848 | pub enum Mode { 849 | /// Known ANSI mode. 850 | Named(NamedMode), 851 | /// Unidentified publc mode. 852 | Unknown(u16), 853 | } 854 | 855 | impl Mode { 856 | fn new(mode: u16) -> Self { 857 | match mode { 858 | 4 => Self::Named(NamedMode::Insert), 859 | 20 => Self::Named(NamedMode::LineFeedNewLine), 860 | _ => Self::Unknown(mode), 861 | } 862 | } 863 | 864 | /// Get the raw value of the mode. 865 | pub fn raw(self) -> u16 { 866 | match self { 867 | Self::Named(named) => named as u16, 868 | Self::Unknown(mode) => mode, 869 | } 870 | } 871 | } 872 | 873 | impl From for Mode { 874 | fn from(value: NamedMode) -> Self { 875 | Self::Named(value) 876 | } 877 | } 878 | 879 | /// ANSI modes. 880 | #[repr(u16)] 881 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 882 | pub enum NamedMode { 883 | /// IRM Insert Mode. 884 | Insert = 4, 885 | LineFeedNewLine = 20, 886 | } 887 | 888 | /// Wrapper for the private DEC modes. 889 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 890 | pub enum PrivateMode { 891 | /// Known private mode. 892 | Named(NamedPrivateMode), 893 | /// Unknown private mode. 894 | Unknown(u16), 895 | } 896 | 897 | impl PrivateMode { 898 | fn new(mode: u16) -> Self { 899 | match mode { 900 | 1 => Self::Named(NamedPrivateMode::CursorKeys), 901 | 3 => Self::Named(NamedPrivateMode::ColumnMode), 902 | 6 => Self::Named(NamedPrivateMode::Origin), 903 | 7 => Self::Named(NamedPrivateMode::LineWrap), 904 | 12 => Self::Named(NamedPrivateMode::BlinkingCursor), 905 | 25 => Self::Named(NamedPrivateMode::ShowCursor), 906 | 1000 => Self::Named(NamedPrivateMode::ReportMouseClicks), 907 | 1002 => Self::Named(NamedPrivateMode::ReportCellMouseMotion), 908 | 1003 => Self::Named(NamedPrivateMode::ReportAllMouseMotion), 909 | 1004 => Self::Named(NamedPrivateMode::ReportFocusInOut), 910 | 1005 => Self::Named(NamedPrivateMode::Utf8Mouse), 911 | 1006 => Self::Named(NamedPrivateMode::SgrMouse), 912 | 1007 => Self::Named(NamedPrivateMode::AlternateScroll), 913 | 1042 => Self::Named(NamedPrivateMode::UrgencyHints), 914 | 1049 => Self::Named(NamedPrivateMode::SwapScreenAndSetRestoreCursor), 915 | 2004 => Self::Named(NamedPrivateMode::BracketedPaste), 916 | 2026 => Self::Named(NamedPrivateMode::SyncUpdate), 917 | _ => Self::Unknown(mode), 918 | } 919 | } 920 | 921 | /// Get the raw value of the mode. 922 | pub fn raw(self) -> u16 { 923 | match self { 924 | Self::Named(named) => named as u16, 925 | Self::Unknown(mode) => mode, 926 | } 927 | } 928 | } 929 | 930 | impl From for PrivateMode { 931 | fn from(value: NamedPrivateMode) -> Self { 932 | Self::Named(value) 933 | } 934 | } 935 | 936 | /// Private DEC modes. 937 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 938 | pub enum NamedPrivateMode { 939 | CursorKeys = 1, 940 | /// Select 80 or 132 columns per page (DECCOLM). 941 | /// 942 | /// CSI ? 3 h -> set 132 column font. 943 | /// CSI ? 3 l -> reset 80 column font. 944 | /// 945 | /// Additionally, 946 | /// 947 | /// * set margins to default positions 948 | /// * erases all data in page memory 949 | /// * resets DECLRMM to unavailable 950 | /// * clears data from the status line (if set to host-writable) 951 | ColumnMode = 3, 952 | Origin = 6, 953 | LineWrap = 7, 954 | BlinkingCursor = 12, 955 | ShowCursor = 25, 956 | ReportMouseClicks = 1000, 957 | ReportCellMouseMotion = 1002, 958 | ReportAllMouseMotion = 1003, 959 | ReportFocusInOut = 1004, 960 | Utf8Mouse = 1005, 961 | SgrMouse = 1006, 962 | AlternateScroll = 1007, 963 | UrgencyHints = 1042, 964 | SwapScreenAndSetRestoreCursor = 1049, 965 | BracketedPaste = 2004, 966 | /// The mode is handled automatically by [`Processor`]. 967 | SyncUpdate = 2026, 968 | } 969 | 970 | /// Mode for clearing line. 971 | /// 972 | /// Relative to cursor. 973 | #[derive(Debug)] 974 | pub enum LineClearMode { 975 | /// Clear right of cursor. 976 | Right, 977 | /// Clear left of cursor. 978 | Left, 979 | /// Clear entire line. 980 | All, 981 | } 982 | 983 | /// Mode for clearing terminal. 984 | /// 985 | /// Relative to cursor. 986 | #[derive(Debug)] 987 | pub enum ClearMode { 988 | /// Clear below cursor. 989 | Below, 990 | /// Clear above cursor. 991 | Above, 992 | /// Clear entire terminal. 993 | All, 994 | /// Clear 'saved' lines (scrollback). 995 | Saved, 996 | } 997 | 998 | /// Mode for clearing tab stops. 999 | #[derive(Debug)] 1000 | pub enum TabulationClearMode { 1001 | /// Clear stop under cursor. 1002 | Current, 1003 | /// Clear all stops. 1004 | All, 1005 | } 1006 | 1007 | /// Standard colors. 1008 | /// 1009 | /// The order here matters since the enum should be castable to a `usize` for 1010 | /// indexing a color list. 1011 | #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] 1012 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 1013 | pub enum NamedColor { 1014 | /// Black. 1015 | Black = 0, 1016 | /// Red. 1017 | Red, 1018 | /// Green. 1019 | Green, 1020 | /// Yellow. 1021 | Yellow, 1022 | /// Blue. 1023 | Blue, 1024 | /// Magenta. 1025 | Magenta, 1026 | /// Cyan. 1027 | Cyan, 1028 | /// White. 1029 | White, 1030 | /// Bright black. 1031 | BrightBlack, 1032 | /// Bright red. 1033 | BrightRed, 1034 | /// Bright green. 1035 | BrightGreen, 1036 | /// Bright yellow. 1037 | BrightYellow, 1038 | /// Bright blue. 1039 | BrightBlue, 1040 | /// Bright magenta. 1041 | BrightMagenta, 1042 | /// Bright cyan. 1043 | BrightCyan, 1044 | /// Bright white. 1045 | BrightWhite, 1046 | /// The foreground color. 1047 | Foreground = 256, 1048 | /// The background color. 1049 | Background, 1050 | /// Color for the cursor itself. 1051 | Cursor, 1052 | /// Dim black. 1053 | DimBlack, 1054 | /// Dim red. 1055 | DimRed, 1056 | /// Dim green. 1057 | DimGreen, 1058 | /// Dim yellow. 1059 | DimYellow, 1060 | /// Dim blue. 1061 | DimBlue, 1062 | /// Dim magenta. 1063 | DimMagenta, 1064 | /// Dim cyan. 1065 | DimCyan, 1066 | /// Dim white. 1067 | DimWhite, 1068 | /// The bright foreground color. 1069 | BrightForeground, 1070 | /// Dim foreground. 1071 | DimForeground, 1072 | } 1073 | 1074 | impl NamedColor { 1075 | #[must_use] 1076 | pub fn to_bright(self) -> Self { 1077 | match self { 1078 | NamedColor::Foreground => NamedColor::BrightForeground, 1079 | NamedColor::Black => NamedColor::BrightBlack, 1080 | NamedColor::Red => NamedColor::BrightRed, 1081 | NamedColor::Green => NamedColor::BrightGreen, 1082 | NamedColor::Yellow => NamedColor::BrightYellow, 1083 | NamedColor::Blue => NamedColor::BrightBlue, 1084 | NamedColor::Magenta => NamedColor::BrightMagenta, 1085 | NamedColor::Cyan => NamedColor::BrightCyan, 1086 | NamedColor::White => NamedColor::BrightWhite, 1087 | NamedColor::DimForeground => NamedColor::Foreground, 1088 | NamedColor::DimBlack => NamedColor::Black, 1089 | NamedColor::DimRed => NamedColor::Red, 1090 | NamedColor::DimGreen => NamedColor::Green, 1091 | NamedColor::DimYellow => NamedColor::Yellow, 1092 | NamedColor::DimBlue => NamedColor::Blue, 1093 | NamedColor::DimMagenta => NamedColor::Magenta, 1094 | NamedColor::DimCyan => NamedColor::Cyan, 1095 | NamedColor::DimWhite => NamedColor::White, 1096 | val => val, 1097 | } 1098 | } 1099 | 1100 | #[must_use] 1101 | pub fn to_dim(self) -> Self { 1102 | match self { 1103 | NamedColor::Black => NamedColor::DimBlack, 1104 | NamedColor::Red => NamedColor::DimRed, 1105 | NamedColor::Green => NamedColor::DimGreen, 1106 | NamedColor::Yellow => NamedColor::DimYellow, 1107 | NamedColor::Blue => NamedColor::DimBlue, 1108 | NamedColor::Magenta => NamedColor::DimMagenta, 1109 | NamedColor::Cyan => NamedColor::DimCyan, 1110 | NamedColor::White => NamedColor::DimWhite, 1111 | NamedColor::Foreground => NamedColor::DimForeground, 1112 | NamedColor::BrightBlack => NamedColor::Black, 1113 | NamedColor::BrightRed => NamedColor::Red, 1114 | NamedColor::BrightGreen => NamedColor::Green, 1115 | NamedColor::BrightYellow => NamedColor::Yellow, 1116 | NamedColor::BrightBlue => NamedColor::Blue, 1117 | NamedColor::BrightMagenta => NamedColor::Magenta, 1118 | NamedColor::BrightCyan => NamedColor::Cyan, 1119 | NamedColor::BrightWhite => NamedColor::White, 1120 | NamedColor::BrightForeground => NamedColor::Foreground, 1121 | val => val, 1122 | } 1123 | } 1124 | } 1125 | 1126 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 1127 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 1128 | pub enum Color { 1129 | Named(NamedColor), 1130 | Spec(Rgb), 1131 | Indexed(u8), 1132 | } 1133 | 1134 | /// Terminal character attributes. 1135 | #[derive(Debug, Eq, PartialEq)] 1136 | pub enum Attr { 1137 | /// Clear all special abilities. 1138 | Reset, 1139 | /// Bold text. 1140 | Bold, 1141 | /// Dim or secondary color. 1142 | Dim, 1143 | /// Italic text. 1144 | Italic, 1145 | /// Underline text. 1146 | Underline, 1147 | /// Underlined twice. 1148 | DoubleUnderline, 1149 | /// Undercurled text. 1150 | Undercurl, 1151 | /// Dotted underlined text. 1152 | DottedUnderline, 1153 | /// Dashed underlined text. 1154 | DashedUnderline, 1155 | /// Blink cursor slowly. 1156 | BlinkSlow, 1157 | /// Blink cursor fast. 1158 | BlinkFast, 1159 | /// Invert colors. 1160 | Reverse, 1161 | /// Do not display characters. 1162 | Hidden, 1163 | /// Strikeout text. 1164 | Strike, 1165 | /// Cancel bold. 1166 | CancelBold, 1167 | /// Cancel bold and dim. 1168 | CancelBoldDim, 1169 | /// Cancel italic. 1170 | CancelItalic, 1171 | /// Cancel all underlines. 1172 | CancelUnderline, 1173 | /// Cancel blink. 1174 | CancelBlink, 1175 | /// Cancel inversion. 1176 | CancelReverse, 1177 | /// Cancel text hiding. 1178 | CancelHidden, 1179 | /// Cancel strikeout. 1180 | CancelStrike, 1181 | /// Set indexed foreground color. 1182 | Foreground(Color), 1183 | /// Set indexed background color. 1184 | Background(Color), 1185 | /// Underline color. 1186 | UnderlineColor(Option), 1187 | } 1188 | 1189 | /// Identifiers which can be assigned to a graphic character set. 1190 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 1191 | pub enum CharsetIndex { 1192 | /// Default set, is designated as ASCII at startup. 1193 | #[default] 1194 | G0, 1195 | G1, 1196 | G2, 1197 | G3, 1198 | } 1199 | 1200 | /// Standard or common character sets which can be designated as G0-G3. 1201 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] 1202 | pub enum StandardCharset { 1203 | #[default] 1204 | Ascii, 1205 | SpecialCharacterAndLineDrawing, 1206 | } 1207 | 1208 | impl StandardCharset { 1209 | /// Switch/Map character to the active charset. Ascii is the common case and 1210 | /// for that we want to do as little as possible. 1211 | #[inline] 1212 | pub fn map(self, c: char) -> char { 1213 | match self { 1214 | StandardCharset::Ascii => c, 1215 | StandardCharset::SpecialCharacterAndLineDrawing => match c { 1216 | '_' => ' ', 1217 | '`' => '◆', 1218 | 'a' => '▒', 1219 | 'b' => '\u{2409}', // Symbol for horizontal tabulation 1220 | 'c' => '\u{240c}', // Symbol for form feed 1221 | 'd' => '\u{240d}', // Symbol for carriage return 1222 | 'e' => '\u{240a}', // Symbol for line feed 1223 | 'f' => '°', 1224 | 'g' => '±', 1225 | 'h' => '\u{2424}', // Symbol for newline 1226 | 'i' => '\u{240b}', // Symbol for vertical tabulation 1227 | 'j' => '┘', 1228 | 'k' => '┐', 1229 | 'l' => '┌', 1230 | 'm' => '└', 1231 | 'n' => '┼', 1232 | 'o' => '⎺', 1233 | 'p' => '⎻', 1234 | 'q' => '─', 1235 | 'r' => '⎼', 1236 | 's' => '⎽', 1237 | 't' => '├', 1238 | 'u' => '┤', 1239 | 'v' => '┴', 1240 | 'w' => '┬', 1241 | 'x' => '│', 1242 | 'y' => '≤', 1243 | 'z' => '≥', 1244 | '{' => 'π', 1245 | '|' => '≠', 1246 | '}' => '£', 1247 | '~' => '·', 1248 | _ => c, 1249 | }, 1250 | } 1251 | } 1252 | } 1253 | 1254 | /// SCP control's first parameter which determines character path. 1255 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 1256 | pub enum ScpCharPath { 1257 | /// SCP's first parameter value of 0. Behavior is implementation defined. 1258 | Default, 1259 | /// SCP's first parameter value of 1 which sets character path to 1260 | /// LEFT-TO-RIGHT. 1261 | LTR, 1262 | /// SCP's first parameter value of 2 which sets character path to 1263 | /// RIGHT-TO-LEFT. 1264 | RTL, 1265 | } 1266 | 1267 | /// SCP control's second parameter which determines update mode/direction 1268 | /// between components. 1269 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 1270 | pub enum ScpUpdateMode { 1271 | /// SCP's second parameter value of 0 (the default). Implementation 1272 | /// dependant update. 1273 | ImplementationDependant, 1274 | /// SCP's second parameter value of 1. 1275 | /// 1276 | /// Reflect data component changes in the presentation component. 1277 | DataToPresentation, 1278 | /// SCP's second parameter value of 2. 1279 | /// 1280 | /// Reflect presentation component changes in the data component. 1281 | PresentationToData, 1282 | } 1283 | 1284 | impl<'a, H, T> crate::Perform for Performer<'a, H, T> 1285 | where 1286 | H: Handler + 'a, 1287 | T: Timeout, 1288 | { 1289 | #[inline] 1290 | fn print(&mut self, c: char) { 1291 | self.handler.input(c); 1292 | self.state.preceding_char = Some(c); 1293 | } 1294 | 1295 | #[inline] 1296 | fn execute(&mut self, byte: u8) { 1297 | match byte { 1298 | C0::HT => self.handler.put_tab(1), 1299 | C0::BS => self.handler.backspace(), 1300 | C0::CR => self.handler.carriage_return(), 1301 | C0::LF | C0::VT | C0::FF => self.handler.linefeed(), 1302 | C0::BEL => self.handler.bell(), 1303 | C0::SUB => self.handler.substitute(), 1304 | C0::SI => self.handler.set_active_charset(CharsetIndex::G0), 1305 | C0::SO => self.handler.set_active_charset(CharsetIndex::G1), 1306 | _ => debug!("[unhandled] execute byte={:02x}", byte), 1307 | } 1308 | } 1309 | 1310 | #[inline] 1311 | fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, action: char) { 1312 | debug!( 1313 | "[unhandled hook] params={:?}, ints: {:?}, ignore: {:?}, action: {:?}", 1314 | params, intermediates, ignore, action 1315 | ); 1316 | } 1317 | 1318 | #[inline] 1319 | fn put(&mut self, byte: u8) { 1320 | debug!("[unhandled put] byte={:?}", byte); 1321 | } 1322 | 1323 | #[inline] 1324 | fn unhook(&mut self) { 1325 | debug!("[unhandled unhook]"); 1326 | } 1327 | 1328 | #[inline] 1329 | fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { 1330 | let terminator = if bell_terminated { "\x07" } else { "\x1b\\" }; 1331 | 1332 | fn unhandled(params: &[&[u8]]) { 1333 | let mut buf = String::new(); 1334 | for items in params { 1335 | buf.push('['); 1336 | for item in *items { 1337 | let _ = write!(buf, "{:?}", *item as char); 1338 | } 1339 | buf.push_str("],"); 1340 | } 1341 | debug!("[unhandled osc_dispatch]: [{}] at line {}", &buf, line!()); 1342 | } 1343 | 1344 | if params.is_empty() || params[0].is_empty() { 1345 | return; 1346 | } 1347 | 1348 | match params[0] { 1349 | // Set window title. 1350 | b"0" | b"2" => { 1351 | if params.len() >= 2 { 1352 | let title = params[1..] 1353 | .iter() 1354 | .flat_map(|x| str::from_utf8(x)) 1355 | .collect::>() 1356 | .join(";") 1357 | .trim() 1358 | .to_owned(); 1359 | self.handler.set_title(Some(title)); 1360 | return; 1361 | } 1362 | unhandled(params); 1363 | }, 1364 | 1365 | // Set color index. 1366 | b"4" => { 1367 | if params.len() <= 1 || params.len() % 2 == 0 { 1368 | unhandled(params); 1369 | return; 1370 | } 1371 | 1372 | for chunk in params[1..].chunks(2) { 1373 | let index = match parse_number(chunk[0]) { 1374 | Some(index) => index, 1375 | None => { 1376 | unhandled(params); 1377 | continue; 1378 | }, 1379 | }; 1380 | 1381 | if let Some(c) = xparse_color(chunk[1]) { 1382 | self.handler.set_color(index as usize, c); 1383 | } else if chunk[1] == b"?" { 1384 | let prefix = alloc::format!("4;{index}"); 1385 | self.handler.dynamic_color_sequence(prefix, index as usize, terminator); 1386 | } else { 1387 | unhandled(params); 1388 | } 1389 | } 1390 | }, 1391 | 1392 | // Hyperlink. 1393 | b"8" if params.len() > 2 => { 1394 | let link_params = params[1]; 1395 | 1396 | // NOTE: The escape sequence is of form 'OSC 8 ; params ; URI ST', where 1397 | // URI is URL-encoded. However `;` is a special character and might be 1398 | // passed as is, thus we need to rebuild the URI. 1399 | let mut uri = str::from_utf8(params[2]).unwrap_or_default().to_string(); 1400 | for param in params[3..].iter() { 1401 | uri.push(';'); 1402 | uri.push_str(str::from_utf8(param).unwrap_or_default()); 1403 | } 1404 | 1405 | // The OSC 8 escape sequence must be stopped when getting an empty `uri`. 1406 | if uri.is_empty() { 1407 | self.handler.set_hyperlink(None); 1408 | return; 1409 | } 1410 | 1411 | // Link parameters are in format of `key1=value1:key2=value2`. Currently only 1412 | // key `id` is defined. 1413 | let id = link_params 1414 | .split(|&b| b == b':') 1415 | .find_map(|kv| kv.strip_prefix(b"id=")) 1416 | .and_then(|kv| str::from_utf8(kv).ok().map(|e| e.to_owned())); 1417 | 1418 | self.handler.set_hyperlink(Some(Hyperlink { id, uri })); 1419 | }, 1420 | 1421 | // Get/set Foreground, Background, Cursor colors. 1422 | b"10" | b"11" | b"12" => { 1423 | if params.len() >= 2 { 1424 | if let Some(mut dynamic_code) = parse_number(params[0]) { 1425 | for param in ¶ms[1..] { 1426 | // 10 is the first dynamic color, also the foreground. 1427 | let offset = dynamic_code as usize - 10; 1428 | let index = NamedColor::Foreground as usize + offset; 1429 | 1430 | // End of setting dynamic colors. 1431 | if index > NamedColor::Cursor as usize { 1432 | unhandled(params); 1433 | break; 1434 | } 1435 | 1436 | if let Some(color) = xparse_color(param) { 1437 | self.handler.set_color(index, color); 1438 | } else if param == b"?" { 1439 | self.handler.dynamic_color_sequence( 1440 | dynamic_code.to_string(), 1441 | index, 1442 | terminator, 1443 | ); 1444 | } else { 1445 | unhandled(params); 1446 | } 1447 | dynamic_code += 1; 1448 | } 1449 | return; 1450 | } 1451 | } 1452 | unhandled(params); 1453 | }, 1454 | 1455 | // Set mouse cursor shape. 1456 | b"22" if params.len() == 2 => { 1457 | let shape = String::from_utf8_lossy(params[1]); 1458 | match CursorIcon::from_str(&shape) { 1459 | Ok(cursor_icon) => self.handler.set_mouse_cursor_icon(cursor_icon), 1460 | Err(_) => debug!("[osc 22] unrecognized cursor icon shape: {shape:?}"), 1461 | } 1462 | }, 1463 | 1464 | // Set cursor style. 1465 | b"50" => { 1466 | if params.len() >= 2 1467 | && params[1].len() >= 13 1468 | && params[1][0..12] == *b"CursorShape=" 1469 | { 1470 | let shape = match params[1][12] as char { 1471 | '0' => CursorShape::Block, 1472 | '1' => CursorShape::Beam, 1473 | '2' => CursorShape::Underline, 1474 | _ => return unhandled(params), 1475 | }; 1476 | self.handler.set_cursor_shape(shape); 1477 | return; 1478 | } 1479 | unhandled(params); 1480 | }, 1481 | 1482 | // Set clipboard. 1483 | b"52" => { 1484 | if params.len() < 3 { 1485 | return unhandled(params); 1486 | } 1487 | 1488 | let clipboard = params[1].first().unwrap_or(&b'c'); 1489 | match params[2] { 1490 | b"?" => self.handler.clipboard_load(*clipboard, terminator), 1491 | base64 => self.handler.clipboard_store(*clipboard, base64), 1492 | } 1493 | }, 1494 | 1495 | // Reset color index. 1496 | b"104" => { 1497 | // Reset all color indexes when no parameters are given. 1498 | if params.len() == 1 || params[1].is_empty() { 1499 | for i in 0..256 { 1500 | self.handler.reset_color(i); 1501 | } 1502 | return; 1503 | } 1504 | 1505 | // Reset color indexes given as parameters. 1506 | for param in ¶ms[1..] { 1507 | match parse_number(param) { 1508 | Some(index) => self.handler.reset_color(index as usize), 1509 | None => unhandled(params), 1510 | } 1511 | } 1512 | }, 1513 | 1514 | // Reset foreground color. 1515 | b"110" => self.handler.reset_color(NamedColor::Foreground as usize), 1516 | 1517 | // Reset background color. 1518 | b"111" => self.handler.reset_color(NamedColor::Background as usize), 1519 | 1520 | // Reset text cursor color. 1521 | b"112" => self.handler.reset_color(NamedColor::Cursor as usize), 1522 | 1523 | _ => unhandled(params), 1524 | } 1525 | } 1526 | 1527 | #[allow(clippy::cognitive_complexity)] 1528 | #[inline] 1529 | fn csi_dispatch( 1530 | &mut self, 1531 | params: &Params, 1532 | intermediates: &[u8], 1533 | has_ignored_intermediates: bool, 1534 | action: char, 1535 | ) { 1536 | macro_rules! unhandled { 1537 | () => {{ 1538 | debug!( 1539 | "[Unhandled CSI] action={:?}, params={:?}, intermediates={:?}", 1540 | action, params, intermediates 1541 | ); 1542 | }}; 1543 | } 1544 | 1545 | if has_ignored_intermediates || intermediates.len() > 2 { 1546 | unhandled!(); 1547 | return; 1548 | } 1549 | 1550 | let mut params_iter = params.iter(); 1551 | let handler = &mut self.handler; 1552 | 1553 | let mut next_param_or = |default: u16| match params_iter.next() { 1554 | Some(&[param, ..]) if param != 0 => param, 1555 | _ => default, 1556 | }; 1557 | 1558 | match (action, intermediates) { 1559 | ('@', []) => handler.insert_blank(next_param_or(1) as usize), 1560 | ('A', []) => handler.move_up(next_param_or(1) as usize), 1561 | ('B', []) | ('e', []) => handler.move_down(next_param_or(1) as usize), 1562 | ('b', []) => { 1563 | if let Some(c) = self.state.preceding_char { 1564 | for _ in 0..next_param_or(1) { 1565 | handler.input(c); 1566 | } 1567 | } else { 1568 | debug!("tried to repeat with no preceding char"); 1569 | } 1570 | }, 1571 | ('C', []) | ('a', []) => handler.move_forward(next_param_or(1) as usize), 1572 | ('c', intermediates) if next_param_or(0) == 0 => { 1573 | handler.identify_terminal(intermediates.first().map(|&i| i as char)) 1574 | }, 1575 | ('D', []) => handler.move_backward(next_param_or(1) as usize), 1576 | ('d', []) => handler.goto_line(next_param_or(1) as i32 - 1), 1577 | ('E', []) => handler.move_down_and_cr(next_param_or(1) as usize), 1578 | ('F', []) => handler.move_up_and_cr(next_param_or(1) as usize), 1579 | ('G', []) | ('`', []) => handler.goto_col(next_param_or(1) as usize - 1), 1580 | ('W', [b'?']) if next_param_or(0) == 5 => handler.set_tabs(8), 1581 | ('g', []) => { 1582 | let mode = match next_param_or(0) { 1583 | 0 => TabulationClearMode::Current, 1584 | 3 => TabulationClearMode::All, 1585 | _ => { 1586 | unhandled!(); 1587 | return; 1588 | }, 1589 | }; 1590 | 1591 | handler.clear_tabs(mode); 1592 | }, 1593 | ('H', []) | ('f', []) => { 1594 | let y = next_param_or(1) as i32; 1595 | let x = next_param_or(1) as usize; 1596 | handler.goto(y - 1, x - 1); 1597 | }, 1598 | ('h', []) => { 1599 | for param in params_iter.map(|param| param[0]) { 1600 | handler.set_mode(Mode::new(param)) 1601 | } 1602 | }, 1603 | ('h', [b'?']) => { 1604 | for param in params_iter.map(|param| param[0]) { 1605 | // Handle sync updates opaquely. 1606 | if param == NamedPrivateMode::SyncUpdate as u16 { 1607 | self.state.sync_state.timeout.set_timeout(SYNC_UPDATE_TIMEOUT); 1608 | self.terminated = true; 1609 | } 1610 | 1611 | handler.set_private_mode(PrivateMode::new(param)) 1612 | } 1613 | }, 1614 | ('I', []) => handler.move_forward_tabs(next_param_or(1)), 1615 | ('J', []) => { 1616 | let mode = match next_param_or(0) { 1617 | 0 => ClearMode::Below, 1618 | 1 => ClearMode::Above, 1619 | 2 => ClearMode::All, 1620 | 3 => ClearMode::Saved, 1621 | _ => { 1622 | unhandled!(); 1623 | return; 1624 | }, 1625 | }; 1626 | 1627 | handler.clear_screen(mode); 1628 | }, 1629 | ('K', []) => { 1630 | let mode = match next_param_or(0) { 1631 | 0 => LineClearMode::Right, 1632 | 1 => LineClearMode::Left, 1633 | 2 => LineClearMode::All, 1634 | _ => { 1635 | unhandled!(); 1636 | return; 1637 | }, 1638 | }; 1639 | 1640 | handler.clear_line(mode); 1641 | }, 1642 | ('k', [b' ']) => { 1643 | // SCP control. 1644 | let char_path = match next_param_or(0) { 1645 | 0 => ScpCharPath::Default, 1646 | 1 => ScpCharPath::LTR, 1647 | 2 => ScpCharPath::RTL, 1648 | _ => { 1649 | unhandled!(); 1650 | return; 1651 | }, 1652 | }; 1653 | 1654 | let update_mode = match next_param_or(0) { 1655 | 0 => ScpUpdateMode::ImplementationDependant, 1656 | 1 => ScpUpdateMode::DataToPresentation, 1657 | 2 => ScpUpdateMode::PresentationToData, 1658 | _ => { 1659 | unhandled!(); 1660 | return; 1661 | }, 1662 | }; 1663 | 1664 | handler.set_scp(char_path, update_mode); 1665 | }, 1666 | ('L', []) => handler.insert_blank_lines(next_param_or(1) as usize), 1667 | ('l', []) => { 1668 | for param in params_iter.map(|param| param[0]) { 1669 | handler.unset_mode(Mode::new(param)) 1670 | } 1671 | }, 1672 | ('l', [b'?']) => { 1673 | for param in params_iter.map(|param| param[0]) { 1674 | handler.unset_private_mode(PrivateMode::new(param)) 1675 | } 1676 | }, 1677 | ('M', []) => handler.delete_lines(next_param_or(1) as usize), 1678 | ('m', []) => { 1679 | if params.is_empty() { 1680 | handler.terminal_attribute(Attr::Reset); 1681 | } else { 1682 | attrs_from_sgr_parameters(*handler, &mut params_iter); 1683 | } 1684 | }, 1685 | ('m', [b'>']) => { 1686 | let mode = match (next_param_or(1) == 4).then(|| next_param_or(0)) { 1687 | Some(0) => ModifyOtherKeys::Reset, 1688 | Some(1) => ModifyOtherKeys::EnableExceptWellDefined, 1689 | Some(2) => ModifyOtherKeys::EnableAll, 1690 | _ => return unhandled!(), 1691 | }; 1692 | handler.set_modify_other_keys(mode); 1693 | }, 1694 | ('m', [b'?']) => { 1695 | if params_iter.next() == Some(&[4]) { 1696 | handler.report_modify_other_keys(); 1697 | } else { 1698 | unhandled!() 1699 | } 1700 | }, 1701 | ('n', []) => handler.device_status(next_param_or(0) as usize), 1702 | ('P', []) => handler.delete_chars(next_param_or(1) as usize), 1703 | ('p', [b'$']) => { 1704 | let mode = next_param_or(0); 1705 | handler.report_mode(Mode::new(mode)); 1706 | }, 1707 | ('p', [b'?', b'$']) => { 1708 | let mode = next_param_or(0); 1709 | handler.report_private_mode(PrivateMode::new(mode)); 1710 | }, 1711 | ('q', [b' ']) => { 1712 | // DECSCUSR (CSI Ps SP q) -- Set Cursor Style. 1713 | let cursor_style_id = next_param_or(0); 1714 | let shape = match cursor_style_id { 1715 | 0 => None, 1716 | 1 | 2 => Some(CursorShape::Block), 1717 | 3 | 4 => Some(CursorShape::Underline), 1718 | 5 | 6 => Some(CursorShape::Beam), 1719 | _ => { 1720 | unhandled!(); 1721 | return; 1722 | }, 1723 | }; 1724 | let cursor_style = 1725 | shape.map(|shape| CursorStyle { shape, blinking: cursor_style_id % 2 == 1 }); 1726 | 1727 | handler.set_cursor_style(cursor_style); 1728 | }, 1729 | ('r', []) => { 1730 | let top = next_param_or(1) as usize; 1731 | let bottom = 1732 | params_iter.next().map(|param| param[0] as usize).filter(|¶m| param != 0); 1733 | 1734 | handler.set_scrolling_region(top, bottom); 1735 | }, 1736 | ('S', []) => handler.scroll_up(next_param_or(1) as usize), 1737 | ('s', []) => handler.save_cursor_position(), 1738 | ('T', []) => handler.scroll_down(next_param_or(1) as usize), 1739 | ('t', []) => match next_param_or(1) as usize { 1740 | 14 => handler.text_area_size_pixels(), 1741 | 18 => handler.text_area_size_chars(), 1742 | 22 => handler.push_title(), 1743 | 23 => handler.pop_title(), 1744 | _ => unhandled!(), 1745 | }, 1746 | ('u', [b'?']) => handler.report_keyboard_mode(), 1747 | ('u', [b'=']) => { 1748 | let mode = KeyboardModes::from_bits_truncate(next_param_or(0) as u8); 1749 | let behavior = match next_param_or(1) { 1750 | 3 => KeyboardModesApplyBehavior::Difference, 1751 | 2 => KeyboardModesApplyBehavior::Union, 1752 | // Default is replace. 1753 | _ => KeyboardModesApplyBehavior::Replace, 1754 | }; 1755 | handler.set_keyboard_mode(mode, behavior); 1756 | }, 1757 | ('u', [b'>']) => { 1758 | let mode = KeyboardModes::from_bits_truncate(next_param_or(0) as u8); 1759 | handler.push_keyboard_mode(mode); 1760 | }, 1761 | ('u', [b'<']) => { 1762 | // The default is 1. 1763 | handler.pop_keyboard_modes(next_param_or(1)); 1764 | }, 1765 | ('u', []) => handler.restore_cursor_position(), 1766 | ('X', []) => handler.erase_chars(next_param_or(1) as usize), 1767 | ('Z', []) => handler.move_backward_tabs(next_param_or(1)), 1768 | _ => unhandled!(), 1769 | } 1770 | } 1771 | 1772 | #[inline] 1773 | fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) { 1774 | macro_rules! unhandled { 1775 | () => {{ 1776 | debug!( 1777 | "[unhandled] esc_dispatch ints={:?}, byte={:?} ({:02x})", 1778 | intermediates, byte as char, byte 1779 | ); 1780 | }}; 1781 | } 1782 | 1783 | macro_rules! configure_charset { 1784 | ($charset:path, $intermediates:expr) => {{ 1785 | let index: CharsetIndex = match $intermediates { 1786 | [b'('] => CharsetIndex::G0, 1787 | [b')'] => CharsetIndex::G1, 1788 | [b'*'] => CharsetIndex::G2, 1789 | [b'+'] => CharsetIndex::G3, 1790 | _ => { 1791 | unhandled!(); 1792 | return; 1793 | }, 1794 | }; 1795 | self.handler.configure_charset(index, $charset) 1796 | }}; 1797 | } 1798 | 1799 | match (byte, intermediates) { 1800 | (b'B', intermediates) => configure_charset!(StandardCharset::Ascii, intermediates), 1801 | (b'D', []) => self.handler.linefeed(), 1802 | (b'E', []) => { 1803 | self.handler.linefeed(); 1804 | self.handler.carriage_return(); 1805 | }, 1806 | (b'H', []) => self.handler.set_horizontal_tabstop(), 1807 | (b'M', []) => self.handler.reverse_index(), 1808 | (b'Z', []) => self.handler.identify_terminal(None), 1809 | (b'c', []) => self.handler.reset_state(), 1810 | (b'0', intermediates) => { 1811 | configure_charset!(StandardCharset::SpecialCharacterAndLineDrawing, intermediates) 1812 | }, 1813 | (b'7', []) => self.handler.save_cursor_position(), 1814 | (b'8', [b'#']) => self.handler.decaln(), 1815 | (b'8', []) => self.handler.restore_cursor_position(), 1816 | (b'=', []) => self.handler.set_keypad_application_mode(), 1817 | (b'>', []) => self.handler.unset_keypad_application_mode(), 1818 | // String terminator, do nothing (parser handles as string terminator). 1819 | (b'\\', []) => (), 1820 | _ => unhandled!(), 1821 | } 1822 | } 1823 | 1824 | #[inline] 1825 | fn terminated(&self) -> bool { 1826 | self.terminated 1827 | } 1828 | } 1829 | 1830 | #[inline] 1831 | fn attrs_from_sgr_parameters(handler: &mut H, params: &mut ParamsIter<'_>) { 1832 | while let Some(param) = params.next() { 1833 | let attr = match param { 1834 | [0] => Some(Attr::Reset), 1835 | [1] => Some(Attr::Bold), 1836 | [2] => Some(Attr::Dim), 1837 | [3] => Some(Attr::Italic), 1838 | [4, 0] => Some(Attr::CancelUnderline), 1839 | [4, 2] => Some(Attr::DoubleUnderline), 1840 | [4, 3] => Some(Attr::Undercurl), 1841 | [4, 4] => Some(Attr::DottedUnderline), 1842 | [4, 5] => Some(Attr::DashedUnderline), 1843 | [4, ..] => Some(Attr::Underline), 1844 | [5] => Some(Attr::BlinkSlow), 1845 | [6] => Some(Attr::BlinkFast), 1846 | [7] => Some(Attr::Reverse), 1847 | [8] => Some(Attr::Hidden), 1848 | [9] => Some(Attr::Strike), 1849 | [21] => Some(Attr::CancelBold), 1850 | [22] => Some(Attr::CancelBoldDim), 1851 | [23] => Some(Attr::CancelItalic), 1852 | [24] => Some(Attr::CancelUnderline), 1853 | [25] => Some(Attr::CancelBlink), 1854 | [27] => Some(Attr::CancelReverse), 1855 | [28] => Some(Attr::CancelHidden), 1856 | [29] => Some(Attr::CancelStrike), 1857 | [30] => Some(Attr::Foreground(Color::Named(NamedColor::Black))), 1858 | [31] => Some(Attr::Foreground(Color::Named(NamedColor::Red))), 1859 | [32] => Some(Attr::Foreground(Color::Named(NamedColor::Green))), 1860 | [33] => Some(Attr::Foreground(Color::Named(NamedColor::Yellow))), 1861 | [34] => Some(Attr::Foreground(Color::Named(NamedColor::Blue))), 1862 | [35] => Some(Attr::Foreground(Color::Named(NamedColor::Magenta))), 1863 | [36] => Some(Attr::Foreground(Color::Named(NamedColor::Cyan))), 1864 | [37] => Some(Attr::Foreground(Color::Named(NamedColor::White))), 1865 | [38] => { 1866 | let mut iter = params.map(|param| param[0]); 1867 | parse_sgr_color(&mut iter).map(Attr::Foreground) 1868 | }, 1869 | [38, params @ ..] => handle_colon_rgb(params).map(Attr::Foreground), 1870 | [39] => Some(Attr::Foreground(Color::Named(NamedColor::Foreground))), 1871 | [40] => Some(Attr::Background(Color::Named(NamedColor::Black))), 1872 | [41] => Some(Attr::Background(Color::Named(NamedColor::Red))), 1873 | [42] => Some(Attr::Background(Color::Named(NamedColor::Green))), 1874 | [43] => Some(Attr::Background(Color::Named(NamedColor::Yellow))), 1875 | [44] => Some(Attr::Background(Color::Named(NamedColor::Blue))), 1876 | [45] => Some(Attr::Background(Color::Named(NamedColor::Magenta))), 1877 | [46] => Some(Attr::Background(Color::Named(NamedColor::Cyan))), 1878 | [47] => Some(Attr::Background(Color::Named(NamedColor::White))), 1879 | [48] => { 1880 | let mut iter = params.map(|param| param[0]); 1881 | parse_sgr_color(&mut iter).map(Attr::Background) 1882 | }, 1883 | [48, params @ ..] => handle_colon_rgb(params).map(Attr::Background), 1884 | [49] => Some(Attr::Background(Color::Named(NamedColor::Background))), 1885 | [58] => { 1886 | let mut iter = params.map(|param| param[0]); 1887 | parse_sgr_color(&mut iter).map(|color| Attr::UnderlineColor(Some(color))) 1888 | }, 1889 | [58, params @ ..] => { 1890 | handle_colon_rgb(params).map(|color| Attr::UnderlineColor(Some(color))) 1891 | }, 1892 | [59] => Some(Attr::UnderlineColor(None)), 1893 | [90] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlack))), 1894 | [91] => Some(Attr::Foreground(Color::Named(NamedColor::BrightRed))), 1895 | [92] => Some(Attr::Foreground(Color::Named(NamedColor::BrightGreen))), 1896 | [93] => Some(Attr::Foreground(Color::Named(NamedColor::BrightYellow))), 1897 | [94] => Some(Attr::Foreground(Color::Named(NamedColor::BrightBlue))), 1898 | [95] => Some(Attr::Foreground(Color::Named(NamedColor::BrightMagenta))), 1899 | [96] => Some(Attr::Foreground(Color::Named(NamedColor::BrightCyan))), 1900 | [97] => Some(Attr::Foreground(Color::Named(NamedColor::BrightWhite))), 1901 | [100] => Some(Attr::Background(Color::Named(NamedColor::BrightBlack))), 1902 | [101] => Some(Attr::Background(Color::Named(NamedColor::BrightRed))), 1903 | [102] => Some(Attr::Background(Color::Named(NamedColor::BrightGreen))), 1904 | [103] => Some(Attr::Background(Color::Named(NamedColor::BrightYellow))), 1905 | [104] => Some(Attr::Background(Color::Named(NamedColor::BrightBlue))), 1906 | [105] => Some(Attr::Background(Color::Named(NamedColor::BrightMagenta))), 1907 | [106] => Some(Attr::Background(Color::Named(NamedColor::BrightCyan))), 1908 | [107] => Some(Attr::Background(Color::Named(NamedColor::BrightWhite))), 1909 | _ => None, 1910 | }; 1911 | 1912 | match attr { 1913 | Some(attr) => handler.terminal_attribute(attr), 1914 | None => continue, 1915 | } 1916 | } 1917 | } 1918 | 1919 | /// Handle colon separated rgb color escape sequence. 1920 | #[inline] 1921 | fn handle_colon_rgb(params: &[u16]) -> Option { 1922 | let rgb_start = if params.len() > 4 { 2 } else { 1 }; 1923 | let rgb_iter = params[rgb_start..].iter().copied(); 1924 | let mut iter = iter::once(params[0]).chain(rgb_iter); 1925 | 1926 | parse_sgr_color(&mut iter) 1927 | } 1928 | 1929 | /// Parse a color specifier from list of attributes. 1930 | fn parse_sgr_color(params: &mut dyn Iterator) -> Option { 1931 | match params.next() { 1932 | Some(2) => Some(Color::Spec(Rgb { 1933 | r: u8::try_from(params.next()?).ok()?, 1934 | g: u8::try_from(params.next()?).ok()?, 1935 | b: u8::try_from(params.next()?).ok()?, 1936 | })), 1937 | Some(5) => Some(Color::Indexed(u8::try_from(params.next()?).ok()?)), 1938 | _ => None, 1939 | } 1940 | } 1941 | 1942 | /// C0 set of 7-bit control characters (from ANSI X3.4-1977). 1943 | #[allow(non_snake_case)] 1944 | pub mod C0 { 1945 | /// Null filler, terminal should ignore this character. 1946 | pub const NUL: u8 = 0x00; 1947 | /// Start of Header. 1948 | pub const SOH: u8 = 0x01; 1949 | /// Start of Text, implied end of header. 1950 | pub const STX: u8 = 0x02; 1951 | /// End of Text, causes some terminal to respond with ACK or NAK. 1952 | pub const ETX: u8 = 0x03; 1953 | /// End of Transmission. 1954 | pub const EOT: u8 = 0x04; 1955 | /// Enquiry, causes terminal to send ANSWER-BACK ID. 1956 | pub const ENQ: u8 = 0x05; 1957 | /// Acknowledge, usually sent by terminal in response to ETX. 1958 | pub const ACK: u8 = 0x06; 1959 | /// Bell, triggers the bell, buzzer, or beeper on the terminal. 1960 | pub const BEL: u8 = 0x07; 1961 | /// Backspace, can be used to define overstruck characters. 1962 | pub const BS: u8 = 0x08; 1963 | /// Horizontal Tabulation, move to next predetermined position. 1964 | pub const HT: u8 = 0x09; 1965 | /// Linefeed, move to same position on next line (see also NL). 1966 | pub const LF: u8 = 0x0A; 1967 | /// Vertical Tabulation, move to next predetermined line. 1968 | pub const VT: u8 = 0x0B; 1969 | /// Form Feed, move to next form or page. 1970 | pub const FF: u8 = 0x0C; 1971 | /// Carriage Return, move to first character of current line. 1972 | pub const CR: u8 = 0x0D; 1973 | /// Shift Out, switch to G1 (other half of character set). 1974 | pub const SO: u8 = 0x0E; 1975 | /// Shift In, switch to G0 (normal half of character set). 1976 | pub const SI: u8 = 0x0F; 1977 | /// Data Link Escape, interpret next control character specially. 1978 | pub const DLE: u8 = 0x10; 1979 | /// (DC1) Terminal is allowed to resume transmitting. 1980 | pub const XON: u8 = 0x11; 1981 | /// Device Control 2, causes ASR-33 to activate paper-tape reader. 1982 | pub const DC2: u8 = 0x12; 1983 | /// (DC2) Terminal must pause and refrain from transmitting. 1984 | pub const XOFF: u8 = 0x13; 1985 | /// Device Control 4, causes ASR-33 to deactivate paper-tape reader. 1986 | pub const DC4: u8 = 0x14; 1987 | /// Negative Acknowledge, used sometimes with ETX and ACK. 1988 | pub const NAK: u8 = 0x15; 1989 | /// Synchronous Idle, used to maintain timing in Sync communication. 1990 | pub const SYN: u8 = 0x16; 1991 | /// End of Transmission block. 1992 | pub const ETB: u8 = 0x17; 1993 | /// Cancel (makes VT100 abort current escape sequence if any). 1994 | pub const CAN: u8 = 0x18; 1995 | /// End of Medium. 1996 | pub const EM: u8 = 0x19; 1997 | /// Substitute (VT100 uses this to display parity errors). 1998 | pub const SUB: u8 = 0x1A; 1999 | /// Prefix to an escape sequence. 2000 | pub const ESC: u8 = 0x1B; 2001 | /// File Separator. 2002 | pub const FS: u8 = 0x1C; 2003 | /// Group Separator. 2004 | pub const GS: u8 = 0x1D; 2005 | /// Record Separator (sent by VT132 in block-transfer mode). 2006 | pub const RS: u8 = 0x1E; 2007 | /// Unit Separator. 2008 | pub const US: u8 = 0x1F; 2009 | /// Delete, should be ignored by terminal. 2010 | pub const DEL: u8 = 0x7F; 2011 | } 2012 | 2013 | // Tests for parsing escape sequences. 2014 | // 2015 | // Byte sequences used in these tests are recording of pty stdout. 2016 | #[cfg(test)] 2017 | mod tests { 2018 | use super::*; 2019 | 2020 | #[derive(Default)] 2021 | pub struct TestSyncHandler { 2022 | is_sync: usize, 2023 | } 2024 | 2025 | impl Timeout for TestSyncHandler { 2026 | #[inline] 2027 | fn set_timeout(&mut self, _: Duration) { 2028 | self.is_sync += 1; 2029 | } 2030 | 2031 | #[inline] 2032 | fn clear_timeout(&mut self) { 2033 | self.is_sync = 0; 2034 | } 2035 | 2036 | #[inline] 2037 | fn pending_timeout(&self) -> bool { 2038 | self.is_sync != 0 2039 | } 2040 | } 2041 | 2042 | struct MockHandler { 2043 | index: CharsetIndex, 2044 | charset: StandardCharset, 2045 | attr: Option, 2046 | identity_reported: bool, 2047 | color: Option, 2048 | reset_colors: Vec, 2049 | } 2050 | 2051 | impl Handler for MockHandler { 2052 | fn terminal_attribute(&mut self, attr: Attr) { 2053 | self.attr = Some(attr); 2054 | } 2055 | 2056 | fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { 2057 | self.index = index; 2058 | self.charset = charset; 2059 | } 2060 | 2061 | fn set_active_charset(&mut self, index: CharsetIndex) { 2062 | self.index = index; 2063 | } 2064 | 2065 | fn identify_terminal(&mut self, _intermediate: Option) { 2066 | self.identity_reported = true; 2067 | } 2068 | 2069 | fn reset_state(&mut self) { 2070 | *self = Self::default(); 2071 | } 2072 | 2073 | fn set_color(&mut self, _: usize, c: Rgb) { 2074 | self.color = Some(c); 2075 | } 2076 | 2077 | fn reset_color(&mut self, index: usize) { 2078 | self.reset_colors.push(index) 2079 | } 2080 | } 2081 | 2082 | impl Default for MockHandler { 2083 | fn default() -> MockHandler { 2084 | MockHandler { 2085 | index: CharsetIndex::G0, 2086 | charset: StandardCharset::Ascii, 2087 | attr: None, 2088 | identity_reported: false, 2089 | color: None, 2090 | reset_colors: Vec::new(), 2091 | } 2092 | } 2093 | } 2094 | 2095 | #[test] 2096 | fn parse_control_attribute() { 2097 | static BYTES: &[u8] = &[0x1B, b'[', b'1', b'm']; 2098 | 2099 | let mut parser = Processor::::new(); 2100 | let mut handler = MockHandler::default(); 2101 | 2102 | parser.advance(&mut handler, BYTES); 2103 | 2104 | assert_eq!(handler.attr, Some(Attr::Bold)); 2105 | } 2106 | 2107 | #[test] 2108 | fn parse_terminal_identity_csi() { 2109 | let bytes: &[u8] = &[0x1B, b'[', b'1', b'c']; 2110 | 2111 | let mut parser = Processor::::new(); 2112 | let mut handler = MockHandler::default(); 2113 | 2114 | parser.advance(&mut handler, bytes); 2115 | 2116 | assert!(!handler.identity_reported); 2117 | handler.reset_state(); 2118 | 2119 | let bytes: &[u8] = &[0x1B, b'[', b'c']; 2120 | 2121 | parser.advance(&mut handler, bytes); 2122 | 2123 | assert!(handler.identity_reported); 2124 | handler.reset_state(); 2125 | 2126 | let bytes: &[u8] = &[0x1B, b'[', b'0', b'c']; 2127 | 2128 | parser.advance(&mut handler, bytes); 2129 | 2130 | assert!(handler.identity_reported); 2131 | } 2132 | 2133 | #[test] 2134 | fn parse_terminal_identity_esc() { 2135 | let bytes: &[u8] = &[0x1B, b'Z']; 2136 | 2137 | let mut parser = Processor::::new(); 2138 | let mut handler = MockHandler::default(); 2139 | 2140 | parser.advance(&mut handler, bytes); 2141 | 2142 | assert!(handler.identity_reported); 2143 | handler.reset_state(); 2144 | 2145 | let bytes: &[u8] = &[0x1B, b'#', b'Z']; 2146 | 2147 | let mut parser = Processor::::new(); 2148 | let mut handler = MockHandler::default(); 2149 | 2150 | parser.advance(&mut handler, bytes); 2151 | 2152 | assert!(!handler.identity_reported); 2153 | handler.reset_state(); 2154 | } 2155 | 2156 | #[test] 2157 | fn parse_truecolor_attr() { 2158 | static BYTES: &[u8] = &[ 2159 | 0x1B, b'[', b'3', b'8', b';', b'2', b';', b'1', b'2', b'8', b';', b'6', b'6', b';', 2160 | b'2', b'5', b'5', b'm', 2161 | ]; 2162 | 2163 | let mut parser = Processor::::new(); 2164 | let mut handler = MockHandler::default(); 2165 | 2166 | parser.advance(&mut handler, BYTES); 2167 | 2168 | let spec = Rgb { r: 128, g: 66, b: 255 }; 2169 | 2170 | assert_eq!(handler.attr, Some(Attr::Foreground(Color::Spec(spec)))); 2171 | } 2172 | 2173 | /// No exactly a test; useful for debugging. 2174 | #[test] 2175 | fn parse_zsh_startup() { 2176 | static BYTES: &[u8] = &[ 2177 | 0x1B, b'[', b'1', b'm', 0x1B, b'[', b'7', b'm', b'%', 0x1B, b'[', b'2', b'7', b'm', 2178 | 0x1B, b'[', b'1', b'm', 0x1B, b'[', b'0', b'm', b' ', b' ', b' ', b' ', b' ', b' ', 2179 | b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', 2180 | b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', 2181 | b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', 2182 | b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', 2183 | b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', b' ', 2184 | b' ', b' ', b' ', b'\r', b' ', b'\r', b'\r', 0x1B, b'[', b'0', b'm', 0x1B, b'[', b'2', 2185 | b'7', b'm', 0x1B, b'[', b'2', b'4', b'm', 0x1B, b'[', b'J', b'j', b'w', b'i', b'l', 2186 | b'm', b'@', b'j', b'w', b'i', b'l', b'm', b'-', b'd', b'e', b's', b'k', b' ', 0x1B, 2187 | b'[', b'0', b'1', b';', b'3', b'2', b'm', 0xE2, 0x9E, 0x9C, b' ', 0x1B, b'[', b'0', 2188 | b'1', b';', b'3', b'2', b'm', b' ', 0x1B, b'[', b'3', b'6', b'm', b'~', b'/', b'c', 2189 | b'o', b'd', b'e', 2190 | ]; 2191 | 2192 | let mut handler = MockHandler::default(); 2193 | let mut parser = Processor::::new(); 2194 | 2195 | parser.advance(&mut handler, BYTES); 2196 | } 2197 | 2198 | #[test] 2199 | fn parse_designate_g0_as_line_drawing() { 2200 | static BYTES: &[u8] = &[0x1B, b'(', b'0']; 2201 | let mut parser = Processor::::new(); 2202 | let mut handler = MockHandler::default(); 2203 | 2204 | parser.advance(&mut handler, BYTES); 2205 | 2206 | assert_eq!(handler.index, CharsetIndex::G0); 2207 | assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); 2208 | } 2209 | 2210 | #[test] 2211 | fn parse_designate_g1_as_line_drawing_and_invoke() { 2212 | static BYTES: &[u8] = &[0x1B, b')', b'0', 0x0E]; 2213 | let mut parser = Processor::::new(); 2214 | let mut handler = MockHandler::default(); 2215 | 2216 | parser.advance(&mut handler, &BYTES[..3]); 2217 | 2218 | assert_eq!(handler.index, CharsetIndex::G1); 2219 | assert_eq!(handler.charset, StandardCharset::SpecialCharacterAndLineDrawing); 2220 | 2221 | let mut handler = MockHandler::default(); 2222 | parser.advance(&mut handler, &[BYTES[3]]); 2223 | 2224 | assert_eq!(handler.index, CharsetIndex::G1); 2225 | } 2226 | 2227 | #[test] 2228 | fn parse_valid_rgb_colors() { 2229 | assert_eq!(xparse_color(b"rgb:f/e/d"), Some(Rgb { r: 0xFF, g: 0xEE, b: 0xDD })); 2230 | assert_eq!(xparse_color(b"rgb:11/aa/ff"), Some(Rgb { r: 0x11, g: 0xAA, b: 0xFF })); 2231 | assert_eq!(xparse_color(b"rgb:f/ed1/cb23"), Some(Rgb { r: 0xFF, g: 0xEC, b: 0xCA })); 2232 | assert_eq!(xparse_color(b"rgb:ffff/0/0"), Some(Rgb { r: 0xFF, g: 0x0, b: 0x0 })); 2233 | } 2234 | 2235 | #[test] 2236 | fn parse_valid_legacy_rgb_colors() { 2237 | assert_eq!(xparse_color(b"#1af"), Some(Rgb { r: 0x10, g: 0xA0, b: 0xF0 })); 2238 | assert_eq!(xparse_color(b"#11aaff"), Some(Rgb { r: 0x11, g: 0xAA, b: 0xFF })); 2239 | assert_eq!(xparse_color(b"#110aa0ff0"), Some(Rgb { r: 0x11, g: 0xAA, b: 0xFF })); 2240 | assert_eq!(xparse_color(b"#1100aa00ff00"), Some(Rgb { r: 0x11, g: 0xAA, b: 0xFF })); 2241 | } 2242 | 2243 | #[test] 2244 | fn parse_invalid_rgb_colors() { 2245 | assert_eq!(xparse_color(b"rgb:0//"), None); 2246 | assert_eq!(xparse_color(b"rgb://///"), None); 2247 | } 2248 | 2249 | #[test] 2250 | fn parse_invalid_legacy_rgb_colors() { 2251 | assert_eq!(xparse_color(b"#"), None); 2252 | assert_eq!(xparse_color(b"#f"), None); 2253 | } 2254 | 2255 | #[test] 2256 | fn parse_invalid_number() { 2257 | assert_eq!(parse_number(b"1abc"), None); 2258 | } 2259 | 2260 | #[test] 2261 | fn parse_valid_number() { 2262 | assert_eq!(parse_number(b"123"), Some(123)); 2263 | } 2264 | 2265 | #[test] 2266 | fn parse_number_too_large() { 2267 | assert_eq!(parse_number(b"321"), None); 2268 | } 2269 | 2270 | #[test] 2271 | fn parse_osc4_set_color() { 2272 | let bytes: &[u8] = b"\x1b]4;0;#fff\x1b\\"; 2273 | 2274 | let mut parser = Processor::::new(); 2275 | let mut handler = MockHandler::default(); 2276 | 2277 | parser.advance(&mut handler, bytes); 2278 | 2279 | assert_eq!(handler.color, Some(Rgb { r: 0xF0, g: 0xF0, b: 0xF0 })); 2280 | } 2281 | 2282 | #[test] 2283 | fn parse_osc104_reset_color() { 2284 | let bytes: &[u8] = b"\x1b]104;1;\x1b\\"; 2285 | 2286 | let mut parser = Processor::::new(); 2287 | let mut handler = MockHandler::default(); 2288 | 2289 | parser.advance(&mut handler, bytes); 2290 | 2291 | assert_eq!(handler.reset_colors, vec![1]); 2292 | } 2293 | 2294 | #[test] 2295 | fn parse_osc104_reset_all_colors() { 2296 | let bytes: &[u8] = b"\x1b]104;\x1b\\"; 2297 | 2298 | let mut parser = Processor::::new(); 2299 | let mut handler = MockHandler::default(); 2300 | 2301 | parser.advance(&mut handler, bytes); 2302 | 2303 | let expected: Vec = (0..256).collect(); 2304 | assert_eq!(handler.reset_colors, expected); 2305 | } 2306 | 2307 | #[test] 2308 | fn parse_osc104_reset_all_colors_no_semicolon() { 2309 | let bytes: &[u8] = b"\x1b]104\x1b\\"; 2310 | 2311 | let mut parser = Processor::::new(); 2312 | let mut handler = MockHandler::default(); 2313 | 2314 | parser.advance(&mut handler, bytes); 2315 | 2316 | let expected: Vec = (0..256).collect(); 2317 | assert_eq!(handler.reset_colors, expected); 2318 | } 2319 | 2320 | #[test] 2321 | fn partial_sync_updates() { 2322 | let mut parser = Processor::::new(); 2323 | let mut handler = MockHandler::default(); 2324 | 2325 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2326 | assert!(handler.attr.is_none()); 2327 | 2328 | // Start synchronized update. 2329 | 2330 | parser.advance(&mut handler, b"\x1b[?20"); 2331 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2332 | assert!(handler.attr.is_none()); 2333 | 2334 | parser.advance(&mut handler, b"26h"); 2335 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2336 | assert!(handler.attr.is_none()); 2337 | 2338 | // Dispatch some data. 2339 | 2340 | parser.advance(&mut handler, b"random \x1b[31m stuff"); 2341 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2342 | assert!(handler.attr.is_none()); 2343 | 2344 | // Extend synchronized update. 2345 | 2346 | parser.advance(&mut handler, b"\x1b[?20"); 2347 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2348 | assert!(handler.attr.is_none()); 2349 | 2350 | parser.advance(&mut handler, b"26h"); 2351 | assert_eq!(parser.state.sync_state.timeout.is_sync, 2); 2352 | assert!(handler.attr.is_none()); 2353 | 2354 | // Terminate synchronized update. 2355 | 2356 | parser.advance(&mut handler, b"\x1b[?20"); 2357 | assert_eq!(parser.state.sync_state.timeout.is_sync, 2); 2358 | assert!(handler.attr.is_none()); 2359 | 2360 | parser.advance(&mut handler, b"26l"); 2361 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2362 | assert!(handler.attr.is_some()); 2363 | } 2364 | 2365 | #[test] 2366 | fn sync_bursts_buffer() { 2367 | let mut parser = Processor::::new(); 2368 | let mut handler = MockHandler::default(); 2369 | 2370 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2371 | assert!(handler.attr.is_none()); 2372 | 2373 | // Repeat test twice to ensure internal state is reset properly. 2374 | for _ in 0..2 { 2375 | // Start synchronized update. 2376 | parser.advance(&mut handler, b"\x1b[?2026h"); 2377 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2378 | assert!(handler.attr.is_none()); 2379 | 2380 | // Ensure sync works. 2381 | parser.advance(&mut handler, b"\x1b[31m"); 2382 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2383 | assert!(handler.attr.is_none()); 2384 | 2385 | // Exceed sync buffer dimensions. 2386 | parser.advance(&mut handler, "a".repeat(SYNC_BUFFER_SIZE).as_bytes()); 2387 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2388 | assert!(handler.attr.take().is_some()); 2389 | 2390 | // Ensure new events are dispatched directly. 2391 | parser.advance(&mut handler, b"\x1b[31m"); 2392 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2393 | assert!(handler.attr.take().is_some()); 2394 | } 2395 | } 2396 | 2397 | #[test] 2398 | fn mixed_sync_escape() { 2399 | let mut parser = Processor::::new(); 2400 | let mut handler = MockHandler::default(); 2401 | 2402 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2403 | assert!(handler.attr.is_none()); 2404 | 2405 | // Start synchronized update with immediate SGR. 2406 | parser.advance(&mut handler, b"\x1b[?2026h\x1b[31m"); 2407 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2408 | assert!(handler.attr.is_none()); 2409 | 2410 | // Terminate synchronized update and check for SGR. 2411 | parser.advance(&mut handler, b"\x1b[?2026l"); 2412 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2413 | assert!(handler.attr.is_some()); 2414 | } 2415 | 2416 | #[test] 2417 | fn sync_bsu_with_esu() { 2418 | let mut parser = Processor::::new(); 2419 | let mut handler = MockHandler::default(); 2420 | 2421 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2422 | assert!(handler.attr.is_none()); 2423 | 2424 | // Start synchronized update with immediate SGR. 2425 | parser.advance(&mut handler, b"\x1b[?2026h\x1b[1m"); 2426 | assert_eq!(parser.state.sync_state.timeout.is_sync, 1); 2427 | assert!(handler.attr.is_none()); 2428 | 2429 | // Terminate synchronized update, but immediately start a new one. 2430 | parser.advance(&mut handler, b"\x1b[?2026l\x1b[?2026h\x1b[4m"); 2431 | assert_eq!(parser.state.sync_state.timeout.is_sync, 2); 2432 | assert_eq!(handler.attr.take(), Some(Attr::Bold)); 2433 | 2434 | // Terminate again, expecting one buffered SGR. 2435 | parser.advance(&mut handler, b"\x1b[?2026l"); 2436 | assert_eq!(parser.state.sync_state.timeout.is_sync, 0); 2437 | assert_eq!(handler.attr.take(), Some(Attr::Underline)); 2438 | } 2439 | 2440 | #[test] 2441 | #[cfg(feature = "std")] 2442 | fn contrast() { 2443 | let rgb1 = Rgb { r: 0xFF, g: 0xFF, b: 0xFF }; 2444 | let rgb2 = Rgb { r: 0x00, g: 0x00, b: 0x00 }; 2445 | assert!((rgb1.contrast(rgb2) - 21.).abs() < f64::EPSILON); 2446 | 2447 | let rgb1 = Rgb { r: 0xFF, g: 0xFF, b: 0xFF }; 2448 | assert!((rgb1.contrast(rgb1) - 1.).abs() < f64::EPSILON); 2449 | 2450 | let rgb1 = Rgb { r: 0xFF, g: 0x00, b: 0xFF }; 2451 | let rgb2 = Rgb { r: 0x00, g: 0xFF, b: 0x00 }; 2452 | assert!((rgb1.contrast(rgb2) - 2.285_543_608_124_253_3).abs() < f64::EPSILON); 2453 | 2454 | let rgb1 = Rgb { r: 0x12, g: 0x34, b: 0x56 }; 2455 | let rgb2 = Rgb { r: 0xFE, g: 0xDC, b: 0xBA }; 2456 | assert!((rgb1.contrast(rgb2) - 9.786_558_997_257_74).abs() < f64::EPSILON); 2457 | } 2458 | } 2459 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parser for implementing virtual terminal emulators 2 | //! 3 | //! [`Parser`] is implemented according to [Paul Williams' ANSI parser state 4 | //! machine]. The state machine doesn't assign meaning to the parsed data and is 5 | //! thus not itself sufficient for writing a terminal emulator. Instead, it is 6 | //! expected that an implementation of [`Perform`] is provided which does 7 | //! something useful with the parsed data. The [`Parser`] handles the book 8 | //! keeping, and the [`Perform`] gets to simply handle actions. 9 | //! 10 | //! # Examples 11 | //! 12 | //! For an example of using the [`Parser`] please see the examples folder. The 13 | //! example included there simply logs all the actions [`Perform`] does. One 14 | //! quick way to see it in action is to pipe `printf` into it 15 | //! 16 | //! ```sh 17 | //! printf '\x1b[31mExample' | cargo run --example parselog 18 | //! ``` 19 | //! 20 | //! # Differences from original state machine description 21 | //! 22 | //! * UTF-8 Support for Input 23 | //! * OSC Strings can be terminated by 0x07 24 | //! * Only supports 7-bit codes 25 | //! 26 | //! [`Parser`]: struct.Parser.html 27 | //! [`Perform`]: trait.Perform.html 28 | //! [Paul Williams' ANSI parser state machine]: https://vt100.net/emu/dec_ansi_parser 29 | #![deny(clippy::all, clippy::if_not_else, clippy::enum_glob_use)] 30 | #![cfg_attr(not(feature = "std"), no_std)] 31 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 32 | 33 | use core::mem::MaybeUninit; 34 | use core::str; 35 | 36 | #[cfg(not(feature = "std"))] 37 | use arrayvec::ArrayVec; 38 | 39 | mod params; 40 | 41 | #[cfg(feature = "ansi")] 42 | pub mod ansi; 43 | pub use params::{Params, ParamsIter}; 44 | 45 | const MAX_INTERMEDIATES: usize = 2; 46 | const MAX_OSC_PARAMS: usize = 16; 47 | const MAX_OSC_RAW: usize = 1024; 48 | 49 | /// Parser for raw _VTE_ protocol which delegates actions to a [`Perform`] 50 | /// 51 | /// [`Perform`]: trait.Perform.html 52 | /// 53 | /// Generic over the value for the size of the raw Operating System Command 54 | /// buffer. Only used when the `std` feature is not enabled. 55 | #[derive(Default)] 56 | pub struct Parser { 57 | state: State, 58 | intermediates: [u8; MAX_INTERMEDIATES], 59 | intermediate_idx: usize, 60 | params: Params, 61 | param: u16, 62 | #[cfg(not(feature = "std"))] 63 | osc_raw: ArrayVec, 64 | #[cfg(feature = "std")] 65 | osc_raw: Vec, 66 | osc_params: [(usize, usize); MAX_OSC_PARAMS], 67 | osc_num_params: usize, 68 | ignoring: bool, 69 | partial_utf8: [u8; 4], 70 | partial_utf8_len: usize, 71 | } 72 | 73 | impl Parser { 74 | /// Create a new Parser 75 | pub fn new() -> Parser { 76 | Default::default() 77 | } 78 | } 79 | 80 | impl Parser { 81 | /// Create a new Parser with a custom size for the Operating System Command 82 | /// buffer. 83 | /// 84 | /// Call with a const-generic param on `Parser`, like: 85 | /// 86 | /// ```rust 87 | /// let mut p = vte::Parser::<64>::new_with_size(); 88 | /// ``` 89 | #[cfg(not(feature = "std"))] 90 | pub fn new_with_size() -> Parser { 91 | Default::default() 92 | } 93 | 94 | #[inline] 95 | fn params(&self) -> &Params { 96 | &self.params 97 | } 98 | 99 | #[inline] 100 | fn intermediates(&self) -> &[u8] { 101 | &self.intermediates[..self.intermediate_idx] 102 | } 103 | 104 | /// Advance the parser state. 105 | /// 106 | /// Requires a [`Perform`] implementation to handle the triggered actions. 107 | /// 108 | /// [`Perform`]: trait.Perform.html 109 | #[inline] 110 | pub fn advance(&mut self, performer: &mut P, bytes: &[u8]) { 111 | let mut i = 0; 112 | 113 | // Handle partial codepoints from previous calls to `advance`. 114 | if self.partial_utf8_len != 0 { 115 | i += self.advance_partial_utf8(performer, bytes); 116 | } 117 | 118 | while i != bytes.len() { 119 | match self.state { 120 | State::Ground => i += self.advance_ground(performer, &bytes[i..]), 121 | _ => { 122 | // Inlining it results in worse codegen. 123 | let byte = bytes[i]; 124 | self.change_state(performer, byte); 125 | i += 1; 126 | }, 127 | } 128 | } 129 | } 130 | 131 | /// Partially advance the parser state. 132 | /// 133 | /// This is equivalent to [`Self::advance`], but stops when 134 | /// [`Perform::terminated`] is true after reading a byte. 135 | /// 136 | /// Returns the number of bytes read before termination. 137 | /// 138 | /// See [`Self::advance`] for more details. 139 | #[inline] 140 | #[must_use = "Returned value should be used to processs the remaining bytes"] 141 | pub fn advance_until_terminated( 142 | &mut self, 143 | performer: &mut P, 144 | bytes: &[u8], 145 | ) -> usize { 146 | let mut i = 0; 147 | 148 | // Handle partial codepoints from previous calls to `advance`. 149 | if self.partial_utf8_len != 0 { 150 | i += self.advance_partial_utf8(performer, bytes); 151 | } 152 | 153 | while i != bytes.len() && !performer.terminated() { 154 | match self.state { 155 | State::Ground => i += self.advance_ground(performer, &bytes[i..]), 156 | _ => { 157 | // Inlining it results in worse codegen. 158 | let byte = bytes[i]; 159 | self.change_state(performer, byte); 160 | i += 1; 161 | }, 162 | } 163 | } 164 | 165 | i 166 | } 167 | 168 | #[inline(always)] 169 | fn change_state(&mut self, performer: &mut P, byte: u8) { 170 | match self.state { 171 | State::CsiEntry => self.advance_csi_entry(performer, byte), 172 | State::CsiIgnore => self.advance_csi_ignore(performer, byte), 173 | State::CsiIntermediate => self.advance_csi_intermediate(performer, byte), 174 | State::CsiParam => self.advance_csi_param(performer, byte), 175 | State::DcsEntry => self.advance_dcs_entry(performer, byte), 176 | State::DcsIgnore => self.anywhere(performer, byte), 177 | State::DcsIntermediate => self.advance_dcs_intermediate(performer, byte), 178 | State::DcsParam => self.advance_dcs_param(performer, byte), 179 | State::DcsPassthrough => self.advance_dcs_passthrough(performer, byte), 180 | State::Escape => self.advance_esc(performer, byte), 181 | State::EscapeIntermediate => self.advance_esc_intermediate(performer, byte), 182 | State::OscString => self.advance_osc_string(performer, byte), 183 | State::SosPmApcString => self.anywhere(performer, byte), 184 | State::Ground => unreachable!(), 185 | } 186 | } 187 | 188 | #[inline(always)] 189 | fn advance_csi_entry(&mut self, performer: &mut P, byte: u8) { 190 | match byte { 191 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 192 | 0x20..=0x2F => { 193 | self.action_collect(byte); 194 | self.state = State::CsiIntermediate 195 | }, 196 | 0x30..=0x39 => { 197 | self.action_paramnext(byte); 198 | self.state = State::CsiParam 199 | }, 200 | 0x3A => { 201 | self.action_subparam(); 202 | self.state = State::CsiParam 203 | }, 204 | 0x3B => { 205 | self.action_param(); 206 | self.state = State::CsiParam 207 | }, 208 | 0x3C..=0x3F => { 209 | self.action_collect(byte); 210 | self.state = State::CsiParam 211 | }, 212 | 0x40..=0x7E => self.action_csi_dispatch(performer, byte), 213 | _ => self.anywhere(performer, byte), 214 | } 215 | } 216 | 217 | #[inline(always)] 218 | fn advance_csi_ignore(&mut self, performer: &mut P, byte: u8) { 219 | match byte { 220 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 221 | 0x20..=0x3F => (), 222 | 0x40..=0x7E => self.state = State::Ground, 223 | 0x7F => (), 224 | _ => self.anywhere(performer, byte), 225 | } 226 | } 227 | 228 | #[inline(always)] 229 | fn advance_csi_intermediate(&mut self, performer: &mut P, byte: u8) { 230 | match byte { 231 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 232 | 0x20..=0x2F => self.action_collect(byte), 233 | 0x30..=0x3F => self.state = State::CsiIgnore, 234 | 0x40..=0x7E => self.action_csi_dispatch(performer, byte), 235 | _ => self.anywhere(performer, byte), 236 | } 237 | } 238 | 239 | #[inline(always)] 240 | fn advance_csi_param(&mut self, performer: &mut P, byte: u8) { 241 | match byte { 242 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 243 | 0x20..=0x2F => { 244 | self.action_collect(byte); 245 | self.state = State::CsiIntermediate 246 | }, 247 | 0x30..=0x39 => self.action_paramnext(byte), 248 | 0x3A => self.action_subparam(), 249 | 0x3B => self.action_param(), 250 | 0x3C..=0x3F => self.state = State::CsiIgnore, 251 | 0x40..=0x7E => self.action_csi_dispatch(performer, byte), 252 | 0x7F => (), 253 | _ => self.anywhere(performer, byte), 254 | } 255 | } 256 | 257 | #[inline(always)] 258 | fn advance_dcs_entry(&mut self, performer: &mut P, byte: u8) { 259 | match byte { 260 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => (), 261 | 0x20..=0x2F => { 262 | self.action_collect(byte); 263 | self.state = State::DcsIntermediate 264 | }, 265 | 0x30..=0x39 => { 266 | self.action_paramnext(byte); 267 | self.state = State::DcsParam 268 | }, 269 | 0x3A => { 270 | self.action_subparam(); 271 | self.state = State::DcsParam 272 | }, 273 | 0x3B => { 274 | self.action_param(); 275 | self.state = State::DcsParam 276 | }, 277 | 0x3C..=0x3F => { 278 | self.action_collect(byte); 279 | self.state = State::DcsParam 280 | }, 281 | 0x40..=0x7E => self.action_hook(performer, byte), 282 | 0x7F => (), 283 | _ => self.anywhere(performer, byte), 284 | } 285 | } 286 | 287 | #[inline(always)] 288 | fn advance_dcs_intermediate(&mut self, performer: &mut P, byte: u8) { 289 | match byte { 290 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => (), 291 | 0x20..=0x2F => self.action_collect(byte), 292 | 0x30..=0x3F => self.state = State::DcsIgnore, 293 | 0x40..=0x7E => self.action_hook(performer, byte), 294 | 0x7F => (), 295 | _ => self.anywhere(performer, byte), 296 | } 297 | } 298 | 299 | #[inline(always)] 300 | fn advance_dcs_param(&mut self, performer: &mut P, byte: u8) { 301 | match byte { 302 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => (), 303 | 0x20..=0x2F => { 304 | self.action_collect(byte); 305 | self.state = State::DcsIntermediate 306 | }, 307 | 0x30..=0x39 => self.action_paramnext(byte), 308 | 0x3A => self.action_subparam(), 309 | 0x3B => self.action_param(), 310 | 0x3C..=0x3F => self.state = State::DcsIgnore, 311 | 0x40..=0x7E => self.action_hook(performer, byte), 312 | 0x7F => (), 313 | _ => self.anywhere(performer, byte), 314 | } 315 | } 316 | 317 | #[inline(always)] 318 | fn advance_dcs_passthrough(&mut self, performer: &mut P, byte: u8) { 319 | match byte { 320 | 0x00..=0x17 | 0x19 | 0x1C..=0x7E => performer.put(byte), 321 | 0x18 | 0x1A => { 322 | performer.unhook(); 323 | performer.execute(byte); 324 | self.state = State::Ground 325 | }, 326 | 0x1B => { 327 | performer.unhook(); 328 | self.reset_params(); 329 | self.state = State::Escape 330 | }, 331 | 0x7F => (), 332 | 0x9C => { 333 | performer.unhook(); 334 | self.state = State::Ground 335 | }, 336 | _ => (), 337 | } 338 | } 339 | 340 | #[inline(always)] 341 | fn advance_esc(&mut self, performer: &mut P, byte: u8) { 342 | match byte { 343 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 344 | 0x20..=0x2F => { 345 | self.action_collect(byte); 346 | self.state = State::EscapeIntermediate 347 | }, 348 | 0x30..=0x4F => { 349 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 350 | self.state = State::Ground 351 | }, 352 | 0x50 => { 353 | self.reset_params(); 354 | self.state = State::DcsEntry 355 | }, 356 | 0x51..=0x57 => { 357 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 358 | self.state = State::Ground 359 | }, 360 | 0x58 => self.state = State::SosPmApcString, 361 | 0x59..=0x5A => { 362 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 363 | self.state = State::Ground 364 | }, 365 | 0x5B => { 366 | self.reset_params(); 367 | self.state = State::CsiEntry 368 | }, 369 | 0x5C => { 370 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 371 | self.state = State::Ground 372 | }, 373 | 0x5D => { 374 | self.osc_raw.clear(); 375 | self.osc_num_params = 0; 376 | self.state = State::OscString 377 | }, 378 | 0x5E..=0x5F => self.state = State::SosPmApcString, 379 | 0x60..=0x7E => { 380 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 381 | self.state = State::Ground 382 | }, 383 | // Anywhere. 384 | 0x18 | 0x1A => { 385 | performer.execute(byte); 386 | self.state = State::Ground 387 | }, 388 | 0x1B => (), 389 | _ => (), 390 | } 391 | } 392 | 393 | #[inline(always)] 394 | fn advance_esc_intermediate(&mut self, performer: &mut P, byte: u8) { 395 | match byte { 396 | 0x00..=0x17 | 0x19 | 0x1C..=0x1F => performer.execute(byte), 397 | 0x20..=0x2F => self.action_collect(byte), 398 | 0x30..=0x7E => { 399 | performer.esc_dispatch(self.intermediates(), self.ignoring, byte); 400 | self.state = State::Ground 401 | }, 402 | 0x7F => (), 403 | _ => self.anywhere(performer, byte), 404 | } 405 | } 406 | 407 | #[inline(always)] 408 | fn advance_osc_string(&mut self, performer: &mut P, byte: u8) { 409 | match byte { 410 | 0x00..=0x06 | 0x08..=0x17 | 0x19 | 0x1C..=0x1F => (), 411 | 0x07 => { 412 | self.osc_end(performer, byte); 413 | self.state = State::Ground 414 | }, 415 | 0x18 | 0x1A => { 416 | self.osc_end(performer, byte); 417 | performer.execute(byte); 418 | self.state = State::Ground 419 | }, 420 | 0x1B => { 421 | self.osc_end(performer, byte); 422 | self.reset_params(); 423 | self.state = State::Escape 424 | }, 425 | 0x3B => { 426 | #[cfg(not(feature = "std"))] 427 | { 428 | if self.osc_raw.is_full() { 429 | return; 430 | } 431 | } 432 | self.action_osc_put_param() 433 | }, 434 | _ => self.action_osc_put(byte), 435 | } 436 | } 437 | 438 | #[inline(always)] 439 | fn anywhere(&mut self, performer: &mut P, byte: u8) { 440 | match byte { 441 | 0x18 | 0x1A => { 442 | performer.execute(byte); 443 | self.state = State::Ground 444 | }, 445 | 0x1B => { 446 | self.reset_params(); 447 | self.state = State::Escape 448 | }, 449 | _ => (), 450 | } 451 | } 452 | 453 | #[inline] 454 | fn action_csi_dispatch(&mut self, performer: &mut P, byte: u8) { 455 | if self.params.is_full() { 456 | self.ignoring = true; 457 | } else { 458 | self.params.push(self.param); 459 | } 460 | performer.csi_dispatch(self.params(), self.intermediates(), self.ignoring, byte as char); 461 | 462 | self.state = State::Ground 463 | } 464 | 465 | #[inline] 466 | fn action_hook(&mut self, performer: &mut P, byte: u8) { 467 | if self.params.is_full() { 468 | self.ignoring = true; 469 | } else { 470 | self.params.push(self.param); 471 | } 472 | performer.hook(self.params(), self.intermediates(), self.ignoring, byte as char); 473 | self.state = State::DcsPassthrough; 474 | } 475 | 476 | #[inline] 477 | fn action_collect(&mut self, byte: u8) { 478 | if self.intermediate_idx == MAX_INTERMEDIATES { 479 | self.ignoring = true; 480 | } else { 481 | self.intermediates[self.intermediate_idx] = byte; 482 | self.intermediate_idx += 1; 483 | } 484 | } 485 | 486 | /// Advance to the next subparameter. 487 | #[inline] 488 | fn action_subparam(&mut self) { 489 | if self.params.is_full() { 490 | self.ignoring = true; 491 | } else { 492 | self.params.extend(self.param); 493 | self.param = 0; 494 | } 495 | } 496 | 497 | /// Advance to the next parameter. 498 | #[inline] 499 | fn action_param(&mut self) { 500 | if self.params.is_full() { 501 | self.ignoring = true; 502 | } else { 503 | self.params.push(self.param); 504 | self.param = 0; 505 | } 506 | } 507 | 508 | /// Advance inside the parameter without terminating it. 509 | #[inline] 510 | fn action_paramnext(&mut self, byte: u8) { 511 | if self.params.is_full() { 512 | self.ignoring = true; 513 | } else { 514 | // Continue collecting bytes into param. 515 | self.param = self.param.saturating_mul(10); 516 | self.param = self.param.saturating_add((byte - b'0') as u16); 517 | } 518 | } 519 | 520 | /// Add OSC param separator. 521 | #[inline] 522 | fn action_osc_put_param(&mut self) { 523 | let idx = self.osc_raw.len(); 524 | 525 | let param_idx = self.osc_num_params; 526 | match param_idx { 527 | // First param is special - 0 to current byte index. 528 | 0 => self.osc_params[param_idx] = (0, idx), 529 | 530 | // Only process up to MAX_OSC_PARAMS. 531 | MAX_OSC_PARAMS => return, 532 | 533 | // All other params depend on previous indexing. 534 | _ => { 535 | let prev = self.osc_params[param_idx - 1]; 536 | let begin = prev.1; 537 | self.osc_params[param_idx] = (begin, idx); 538 | }, 539 | } 540 | 541 | self.osc_num_params += 1; 542 | } 543 | 544 | #[inline(always)] 545 | fn action_osc_put(&mut self, byte: u8) { 546 | #[cfg(not(feature = "std"))] 547 | { 548 | if self.osc_raw.is_full() { 549 | return; 550 | } 551 | } 552 | self.osc_raw.push(byte); 553 | } 554 | 555 | fn osc_end(&mut self, performer: &mut P, byte: u8) { 556 | self.action_osc_put_param(); 557 | self.osc_dispatch(performer, byte); 558 | self.osc_raw.clear(); 559 | self.osc_num_params = 0; 560 | } 561 | 562 | /// Reset escape sequence parameters and intermediates. 563 | #[inline] 564 | fn reset_params(&mut self) { 565 | self.intermediate_idx = 0; 566 | self.ignoring = false; 567 | self.param = 0; 568 | 569 | self.params.clear(); 570 | } 571 | 572 | /// Separate method for osc_dispatch that borrows self as read-only 573 | /// 574 | /// The aliasing is needed here for multiple slices into self.osc_raw 575 | #[inline] 576 | fn osc_dispatch(&self, performer: &mut P, byte: u8) { 577 | let mut slices: [MaybeUninit<&[u8]>; MAX_OSC_PARAMS] = 578 | unsafe { MaybeUninit::uninit().assume_init() }; 579 | 580 | for (i, slice) in slices.iter_mut().enumerate().take(self.osc_num_params) { 581 | let indices = self.osc_params[i]; 582 | *slice = MaybeUninit::new(&self.osc_raw[indices.0..indices.1]); 583 | } 584 | 585 | unsafe { 586 | let num_params = self.osc_num_params; 587 | let params = &slices[..num_params] as *const [MaybeUninit<&[u8]>] as *const [&[u8]]; 588 | performer.osc_dispatch(&*params, byte == 0x07); 589 | } 590 | } 591 | 592 | /// Advance the parser state from ground. 593 | /// 594 | /// The ground state is handled separately since it can only be left using 595 | /// the escape character (`\x1b`). This allows more efficient parsing by 596 | /// using SIMD search with [`memchr`]. 597 | #[inline] 598 | fn advance_ground(&mut self, performer: &mut P, bytes: &[u8]) -> usize { 599 | // Find the next escape character. 600 | let num_bytes = bytes.len(); 601 | let plain_chars = memchr::memchr(0x1B, bytes).unwrap_or(num_bytes); 602 | 603 | // If the next character is ESC, just process it and short-circuit. 604 | if plain_chars == 0 { 605 | self.state = State::Escape; 606 | self.reset_params(); 607 | return 1; 608 | } 609 | 610 | match str::from_utf8(&bytes[..plain_chars]) { 611 | Ok(parsed) => { 612 | Self::ground_dispatch(performer, parsed); 613 | let mut processed = plain_chars; 614 | 615 | // If there's another character, it must be escape so process it directly. 616 | if processed < num_bytes { 617 | self.state = State::Escape; 618 | self.reset_params(); 619 | processed += 1; 620 | } 621 | 622 | processed 623 | }, 624 | // Handle invalid and partial utf8. 625 | Err(err) => { 626 | // Dispatch all the valid bytes. 627 | let valid_bytes = err.valid_up_to(); 628 | let parsed = unsafe { str::from_utf8_unchecked(&bytes[..valid_bytes]) }; 629 | Self::ground_dispatch(performer, parsed); 630 | 631 | match err.error_len() { 632 | Some(len) => { 633 | // Execute C1 escapes or emit replacement character. 634 | if len == 1 && bytes[valid_bytes] <= 0x9F { 635 | performer.execute(bytes[valid_bytes]); 636 | } else { 637 | performer.print('�'); 638 | } 639 | 640 | // Restart processing after the invalid bytes. 641 | // 642 | // While we could theoretically try to just re-parse 643 | // `bytes[valid_bytes + len..plain_chars]`, it's easier 644 | // to just skip it and invalid utf8 is pretty rare anyway. 645 | valid_bytes + len 646 | }, 647 | None => { 648 | if plain_chars < num_bytes { 649 | // Process bytes cut off by escape. 650 | performer.print('�'); 651 | self.state = State::Escape; 652 | self.reset_params(); 653 | plain_chars + 1 654 | } else { 655 | // Process bytes cut off by the buffer end. 656 | let extra_bytes = num_bytes - valid_bytes; 657 | let partial_len = self.partial_utf8_len + extra_bytes; 658 | self.partial_utf8[self.partial_utf8_len..partial_len] 659 | .copy_from_slice(&bytes[valid_bytes..valid_bytes + extra_bytes]); 660 | self.partial_utf8_len = partial_len; 661 | num_bytes 662 | } 663 | }, 664 | } 665 | }, 666 | } 667 | } 668 | 669 | /// Advance the parser while processing a partial utf8 codepoint. 670 | #[inline] 671 | fn advance_partial_utf8(&mut self, performer: &mut P, bytes: &[u8]) -> usize { 672 | // Try to copy up to 3 more characters, to ensure the codepoint is complete. 673 | let old_bytes = self.partial_utf8_len; 674 | let to_copy = bytes.len().min(self.partial_utf8.len() - old_bytes); 675 | self.partial_utf8[old_bytes..old_bytes + to_copy].copy_from_slice(&bytes[..to_copy]); 676 | self.partial_utf8_len += to_copy; 677 | 678 | // Parse the unicode character. 679 | match str::from_utf8(&self.partial_utf8[..self.partial_utf8_len]) { 680 | // If the entire buffer is valid, use the first character and continue parsing. 681 | Ok(parsed) => { 682 | let c = unsafe { parsed.chars().next().unwrap_unchecked() }; 683 | performer.print(c); 684 | 685 | self.partial_utf8_len = 0; 686 | c.len_utf8() - old_bytes 687 | }, 688 | Err(err) => { 689 | let valid_bytes = err.valid_up_to(); 690 | // If we have any valid bytes, that means we partially copied another 691 | // utf8 character into `partial_utf8`. Since we only care about the 692 | // first character, we just ignore the rest. 693 | if valid_bytes > 0 { 694 | let c = unsafe { 695 | let parsed = str::from_utf8_unchecked(&self.partial_utf8[..valid_bytes]); 696 | parsed.chars().next().unwrap_unchecked() 697 | }; 698 | 699 | performer.print(c); 700 | 701 | self.partial_utf8_len = 0; 702 | return valid_bytes - old_bytes; 703 | } 704 | 705 | match err.error_len() { 706 | // If the partial character was also invalid, emit the replacement 707 | // character. 708 | Some(invalid_len) => { 709 | performer.print('�'); 710 | 711 | self.partial_utf8_len = 0; 712 | invalid_len - old_bytes 713 | }, 714 | // If the character still isn't complete, wait for more data. 715 | None => to_copy, 716 | } 717 | }, 718 | } 719 | } 720 | 721 | /// Handle ground dispatch of print/execute for all characters in a string. 722 | #[inline] 723 | fn ground_dispatch(performer: &mut P, text: &str) { 724 | for c in text.chars() { 725 | match c { 726 | '\x00'..='\x1f' | '\u{80}'..='\u{9f}' => performer.execute(c as u8), 727 | _ => performer.print(c), 728 | } 729 | } 730 | } 731 | } 732 | 733 | #[derive(PartialEq, Eq, Debug, Default, Copy, Clone)] 734 | enum State { 735 | CsiEntry, 736 | CsiIgnore, 737 | CsiIntermediate, 738 | CsiParam, 739 | DcsEntry, 740 | DcsIgnore, 741 | DcsIntermediate, 742 | DcsParam, 743 | DcsPassthrough, 744 | Escape, 745 | EscapeIntermediate, 746 | OscString, 747 | SosPmApcString, 748 | #[default] 749 | Ground, 750 | } 751 | 752 | /// Performs actions requested by the Parser 753 | /// 754 | /// Actions in this case mean, for example, handling a CSI escape sequence 755 | /// describing cursor movement, or simply printing characters to the screen. 756 | /// 757 | /// The methods on this type correspond to actions described in 758 | /// . I've done my best to describe them in 759 | /// a useful way in my own words for completeness, but the site should be 760 | /// referenced if something isn't clear. If the site disappears at some point in 761 | /// the future, consider checking archive.org. 762 | pub trait Perform { 763 | /// Draw a character to the screen and update states. 764 | fn print(&mut self, _c: char) {} 765 | 766 | /// Execute a C0 or C1 control function. 767 | fn execute(&mut self, _byte: u8) {} 768 | 769 | /// Invoked when a final character arrives in first part of device control 770 | /// string. 771 | /// 772 | /// The control function should be determined from the private marker, final 773 | /// character, and execute with a parameter list. A handler should be 774 | /// selected for remaining characters in the string; the handler 775 | /// function should subsequently be called by `put` for every character in 776 | /// the control string. 777 | /// 778 | /// The `ignore` flag indicates that more than two intermediates arrived and 779 | /// subsequent characters were ignored. 780 | fn hook(&mut self, _params: &Params, _intermediates: &[u8], _ignore: bool, _action: char) {} 781 | 782 | /// Pass bytes as part of a device control string to the handle chosen in 783 | /// `hook`. C0 controls will also be passed to the handler. 784 | fn put(&mut self, _byte: u8) {} 785 | 786 | /// Called when a device control string is terminated. 787 | /// 788 | /// The previously selected handler should be notified that the DCS has 789 | /// terminated. 790 | fn unhook(&mut self) {} 791 | 792 | /// Dispatch an operating system command. 793 | fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {} 794 | 795 | /// A final character has arrived for a CSI sequence 796 | /// 797 | /// The `ignore` flag indicates that either more than two intermediates 798 | /// arrived or the number of parameters exceeded the maximum supported 799 | /// length, and subsequent characters were ignored. 800 | fn csi_dispatch( 801 | &mut self, 802 | _params: &Params, 803 | _intermediates: &[u8], 804 | _ignore: bool, 805 | _action: char, 806 | ) { 807 | } 808 | 809 | /// The final character of an escape sequence has arrived. 810 | /// 811 | /// The `ignore` flag indicates that more than two intermediates arrived and 812 | /// subsequent characters were ignored. 813 | fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {} 814 | 815 | /// Whether the parser should terminate prematurely. 816 | /// 817 | /// This can be used in conjunction with 818 | /// [`Parser::advance_until_terminated`] to terminate the parser after 819 | /// receiving certain escape sequences like synchronized updates. 820 | /// 821 | /// This is checked after every parsed byte, so no expensive computation 822 | /// should take place in this function. 823 | #[inline(always)] 824 | fn terminated(&self) -> bool { 825 | false 826 | } 827 | } 828 | 829 | #[cfg(all(test, not(feature = "std")))] 830 | #[macro_use] 831 | extern crate std; 832 | 833 | #[cfg(test)] 834 | mod tests { 835 | use std::vec::Vec; 836 | 837 | use super::*; 838 | 839 | const OSC_BYTES: &[u8] = &[ 840 | 0x1B, 0x5D, // Begin OSC 841 | b'2', b';', b'j', b'w', b'i', b'l', b'm', b'@', b'j', b'w', b'i', b'l', b'm', b'-', b'd', 842 | b'e', b's', b'k', b':', b' ', b'~', b'/', b'c', b'o', b'd', b'e', b'/', b'a', b'l', b'a', 843 | b'c', b'r', b'i', b't', b't', b'y', 0x07, // End OSC 844 | ]; 845 | 846 | #[derive(Default)] 847 | struct Dispatcher { 848 | dispatched: Vec, 849 | } 850 | 851 | #[derive(Debug, PartialEq, Eq)] 852 | enum Sequence { 853 | Osc(Vec>, bool), 854 | Csi(Vec>, Vec, bool, char), 855 | Esc(Vec, bool, u8), 856 | DcsHook(Vec>, Vec, bool, char), 857 | DcsPut(u8), 858 | Print(char), 859 | Execute(u8), 860 | DcsUnhook, 861 | } 862 | 863 | impl Perform for Dispatcher { 864 | fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { 865 | let params = params.iter().map(|p| p.to_vec()).collect(); 866 | self.dispatched.push(Sequence::Osc(params, bell_terminated)); 867 | } 868 | 869 | fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { 870 | let params = params.iter().map(|subparam| subparam.to_vec()).collect(); 871 | let intermediates = intermediates.to_vec(); 872 | self.dispatched.push(Sequence::Csi(params, intermediates, ignore, c)); 873 | } 874 | 875 | fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { 876 | let intermediates = intermediates.to_vec(); 877 | self.dispatched.push(Sequence::Esc(intermediates, ignore, byte)); 878 | } 879 | 880 | fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) { 881 | let params = params.iter().map(|subparam| subparam.to_vec()).collect(); 882 | let intermediates = intermediates.to_vec(); 883 | self.dispatched.push(Sequence::DcsHook(params, intermediates, ignore, c)); 884 | } 885 | 886 | fn put(&mut self, byte: u8) { 887 | self.dispatched.push(Sequence::DcsPut(byte)); 888 | } 889 | 890 | fn unhook(&mut self) { 891 | self.dispatched.push(Sequence::DcsUnhook); 892 | } 893 | 894 | fn print(&mut self, c: char) { 895 | self.dispatched.push(Sequence::Print(c)); 896 | } 897 | 898 | fn execute(&mut self, byte: u8) { 899 | self.dispatched.push(Sequence::Execute(byte)); 900 | } 901 | } 902 | 903 | #[test] 904 | fn parse_osc() { 905 | let mut dispatcher = Dispatcher::default(); 906 | let mut parser = Parser::new(); 907 | 908 | parser.advance(&mut dispatcher, OSC_BYTES); 909 | 910 | assert_eq!(dispatcher.dispatched.len(), 1); 911 | match &dispatcher.dispatched[0] { 912 | Sequence::Osc(params, _) => { 913 | assert_eq!(params.len(), 2); 914 | assert_eq!(params[0], &OSC_BYTES[2..3]); 915 | assert_eq!(params[1], &OSC_BYTES[4..(OSC_BYTES.len() - 1)]); 916 | }, 917 | _ => panic!("expected osc sequence"), 918 | } 919 | } 920 | 921 | #[test] 922 | fn parse_empty_osc() { 923 | let mut dispatcher = Dispatcher::default(); 924 | let mut parser = Parser::new(); 925 | 926 | parser.advance(&mut dispatcher, &[0x1B, 0x5D, 0x07]); 927 | 928 | assert_eq!(dispatcher.dispatched.len(), 1); 929 | match &dispatcher.dispatched[0] { 930 | Sequence::Osc(..) => (), 931 | _ => panic!("expected osc sequence"), 932 | } 933 | } 934 | 935 | #[test] 936 | fn parse_osc_max_params() { 937 | let params = ";".repeat(params::MAX_PARAMS + 1); 938 | let input = format!("\x1b]{}\x1b", ¶ms[..]).into_bytes(); 939 | let mut dispatcher = Dispatcher::default(); 940 | let mut parser = Parser::new(); 941 | 942 | parser.advance(&mut dispatcher, &input); 943 | 944 | assert_eq!(dispatcher.dispatched.len(), 1); 945 | match &dispatcher.dispatched[0] { 946 | Sequence::Osc(params, _) => { 947 | assert_eq!(params.len(), MAX_OSC_PARAMS); 948 | assert!(params.iter().all(Vec::is_empty)); 949 | }, 950 | _ => panic!("expected osc sequence"), 951 | } 952 | } 953 | 954 | #[test] 955 | fn osc_bell_terminated() { 956 | const INPUT: &[u8] = b"\x1b]11;ff/00/ff\x07"; 957 | let mut dispatcher = Dispatcher::default(); 958 | let mut parser = Parser::new(); 959 | 960 | parser.advance(&mut dispatcher, INPUT); 961 | 962 | assert_eq!(dispatcher.dispatched.len(), 1); 963 | match &dispatcher.dispatched[0] { 964 | Sequence::Osc(_, true) => (), 965 | _ => panic!("expected osc with bell terminator"), 966 | } 967 | } 968 | 969 | #[test] 970 | fn osc_c0_st_terminated() { 971 | const INPUT: &[u8] = b"\x1b]11;ff/00/ff\x1b\\"; 972 | let mut dispatcher = Dispatcher::default(); 973 | let mut parser = Parser::new(); 974 | 975 | parser.advance(&mut dispatcher, INPUT); 976 | 977 | assert_eq!(dispatcher.dispatched.len(), 2); 978 | match &dispatcher.dispatched[0] { 979 | Sequence::Osc(_, false) => (), 980 | _ => panic!("expected osc with ST terminator"), 981 | } 982 | } 983 | 984 | #[test] 985 | fn parse_osc_with_utf8_arguments() { 986 | const INPUT: &[u8] = &[ 987 | 0x0D, 0x1B, 0x5D, 0x32, 0x3B, 0x65, 0x63, 0x68, 0x6F, 0x20, 0x27, 0xC2, 0xAF, 0x5C, 988 | 0x5F, 0x28, 0xE3, 0x83, 0x84, 0x29, 0x5F, 0x2F, 0xC2, 0xAF, 0x27, 0x20, 0x26, 0x26, 989 | 0x20, 0x73, 0x6C, 0x65, 0x65, 0x70, 0x20, 0x31, 0x07, 990 | ]; 991 | let mut dispatcher = Dispatcher::default(); 992 | let mut parser = Parser::new(); 993 | 994 | parser.advance(&mut dispatcher, INPUT); 995 | 996 | assert_eq!(dispatcher.dispatched[0], Sequence::Execute(b'\r')); 997 | let osc_data = INPUT[5..(INPUT.len() - 1)].into(); 998 | assert_eq!(dispatcher.dispatched[1], Sequence::Osc(vec![vec![b'2'], osc_data], true)); 999 | assert_eq!(dispatcher.dispatched.len(), 2); 1000 | } 1001 | 1002 | #[test] 1003 | fn osc_containing_string_terminator() { 1004 | const INPUT: &[u8] = b"\x1b]2;\xe6\x9c\xab\x1b\\"; 1005 | let mut dispatcher = Dispatcher::default(); 1006 | let mut parser = Parser::new(); 1007 | 1008 | parser.advance(&mut dispatcher, INPUT); 1009 | 1010 | assert_eq!(dispatcher.dispatched.len(), 2); 1011 | match &dispatcher.dispatched[0] { 1012 | Sequence::Osc(params, _) => { 1013 | assert_eq!(params[1], &INPUT[4..(INPUT.len() - 2)]); 1014 | }, 1015 | _ => panic!("expected osc sequence"), 1016 | } 1017 | } 1018 | 1019 | #[test] 1020 | fn exceed_max_buffer_size() { 1021 | const NUM_BYTES: usize = MAX_OSC_RAW + 100; 1022 | const INPUT_START: &[u8] = b"\x1b]52;s"; 1023 | const INPUT_END: &[u8] = b"\x07"; 1024 | 1025 | let mut dispatcher = Dispatcher::default(); 1026 | let mut parser = Parser::new(); 1027 | 1028 | // Create valid OSC escape 1029 | parser.advance(&mut dispatcher, INPUT_START); 1030 | 1031 | // Exceed max buffer size 1032 | parser.advance(&mut dispatcher, &[b'a'; NUM_BYTES]); 1033 | 1034 | // Terminate escape for dispatch 1035 | parser.advance(&mut dispatcher, INPUT_END); 1036 | 1037 | assert_eq!(dispatcher.dispatched.len(), 1); 1038 | match &dispatcher.dispatched[0] { 1039 | Sequence::Osc(params, _) => { 1040 | assert_eq!(params.len(), 2); 1041 | assert_eq!(params[0], b"52"); 1042 | 1043 | #[cfg(feature = "std")] 1044 | assert_eq!(params[1].len(), NUM_BYTES + INPUT_END.len()); 1045 | 1046 | #[cfg(not(feature = "std"))] 1047 | assert_eq!(params[1].len(), MAX_OSC_RAW - params[0].len()); 1048 | }, 1049 | _ => panic!("expected osc sequence"), 1050 | } 1051 | } 1052 | 1053 | #[test] 1054 | fn parse_csi_max_params() { 1055 | // This will build a list of repeating '1;'s 1056 | // The length is MAX_PARAMS - 1 because the last semicolon is interpreted 1057 | // as an implicit zero, making the total number of parameters MAX_PARAMS 1058 | let params = "1;".repeat(params::MAX_PARAMS - 1); 1059 | let input = format!("\x1b[{}p", ¶ms[..]).into_bytes(); 1060 | 1061 | let mut dispatcher = Dispatcher::default(); 1062 | let mut parser = Parser::new(); 1063 | 1064 | parser.advance(&mut dispatcher, &input); 1065 | 1066 | assert_eq!(dispatcher.dispatched.len(), 1); 1067 | match &dispatcher.dispatched[0] { 1068 | Sequence::Csi(params, _, ignore, _) => { 1069 | assert_eq!(params.len(), params::MAX_PARAMS); 1070 | assert!(!ignore); 1071 | }, 1072 | _ => panic!("expected csi sequence"), 1073 | } 1074 | } 1075 | 1076 | #[test] 1077 | fn parse_csi_params_ignore_long_params() { 1078 | // This will build a list of repeating '1;'s 1079 | // The length is MAX_PARAMS because the last semicolon is interpreted 1080 | // as an implicit zero, making the total number of parameters MAX_PARAMS + 1 1081 | let params = "1;".repeat(params::MAX_PARAMS); 1082 | let input = format!("\x1b[{}p", ¶ms[..]).into_bytes(); 1083 | 1084 | let mut dispatcher = Dispatcher::default(); 1085 | let mut parser = Parser::new(); 1086 | 1087 | parser.advance(&mut dispatcher, &input); 1088 | 1089 | assert_eq!(dispatcher.dispatched.len(), 1); 1090 | match &dispatcher.dispatched[0] { 1091 | Sequence::Csi(params, _, ignore, _) => { 1092 | assert_eq!(params.len(), params::MAX_PARAMS); 1093 | assert!(ignore); 1094 | }, 1095 | _ => panic!("expected csi sequence"), 1096 | } 1097 | } 1098 | 1099 | #[test] 1100 | fn parse_csi_params_trailing_semicolon() { 1101 | let mut dispatcher = Dispatcher::default(); 1102 | let mut parser = Parser::new(); 1103 | 1104 | parser.advance(&mut dispatcher, b"\x1b[4;m"); 1105 | 1106 | assert_eq!(dispatcher.dispatched.len(), 1); 1107 | match &dispatcher.dispatched[0] { 1108 | Sequence::Csi(params, ..) => assert_eq!(params, &[[4], [0]]), 1109 | _ => panic!("expected csi sequence"), 1110 | } 1111 | } 1112 | 1113 | #[test] 1114 | fn parse_csi_params_leading_semicolon() { 1115 | // Create dispatcher and check state 1116 | let mut dispatcher = Dispatcher::default(); 1117 | let mut parser = Parser::new(); 1118 | 1119 | parser.advance(&mut dispatcher, b"\x1b[;4m"); 1120 | 1121 | assert_eq!(dispatcher.dispatched.len(), 1); 1122 | match &dispatcher.dispatched[0] { 1123 | Sequence::Csi(params, ..) => assert_eq!(params, &[[0], [4]]), 1124 | _ => panic!("expected csi sequence"), 1125 | } 1126 | } 1127 | 1128 | #[test] 1129 | fn parse_long_csi_param() { 1130 | // The important part is the parameter, which is (i64::MAX + 1) 1131 | const INPUT: &[u8] = b"\x1b[9223372036854775808m"; 1132 | let mut dispatcher = Dispatcher::default(); 1133 | let mut parser = Parser::new(); 1134 | 1135 | parser.advance(&mut dispatcher, INPUT); 1136 | 1137 | assert_eq!(dispatcher.dispatched.len(), 1); 1138 | match &dispatcher.dispatched[0] { 1139 | Sequence::Csi(params, ..) => assert_eq!(params, &[[u16::MAX]]), 1140 | _ => panic!("expected csi sequence"), 1141 | } 1142 | } 1143 | 1144 | #[test] 1145 | fn csi_reset() { 1146 | const INPUT: &[u8] = b"\x1b[3;1\x1b[?1049h"; 1147 | let mut dispatcher = Dispatcher::default(); 1148 | let mut parser = Parser::new(); 1149 | 1150 | parser.advance(&mut dispatcher, INPUT); 1151 | 1152 | assert_eq!(dispatcher.dispatched.len(), 1); 1153 | match &dispatcher.dispatched[0] { 1154 | Sequence::Csi(params, intermediates, ignore, _) => { 1155 | assert_eq!(intermediates, b"?"); 1156 | assert_eq!(params, &[[1049]]); 1157 | assert!(!ignore); 1158 | }, 1159 | _ => panic!("expected csi sequence"), 1160 | } 1161 | } 1162 | 1163 | #[test] 1164 | fn csi_subparameters() { 1165 | const INPUT: &[u8] = b"\x1b[38:2:255:0:255;1m"; 1166 | let mut dispatcher = Dispatcher::default(); 1167 | let mut parser = Parser::new(); 1168 | 1169 | parser.advance(&mut dispatcher, INPUT); 1170 | 1171 | assert_eq!(dispatcher.dispatched.len(), 1); 1172 | match &dispatcher.dispatched[0] { 1173 | Sequence::Csi(params, intermediates, ignore, _) => { 1174 | assert_eq!(params, &[vec![38, 2, 255, 0, 255], vec![1]]); 1175 | assert_eq!(intermediates, &[]); 1176 | assert!(!ignore); 1177 | }, 1178 | _ => panic!("expected csi sequence"), 1179 | } 1180 | } 1181 | 1182 | #[test] 1183 | fn parse_dcs_max_params() { 1184 | let params = "1;".repeat(params::MAX_PARAMS + 1); 1185 | let input = format!("\x1bP{}p", ¶ms[..]).into_bytes(); 1186 | let mut dispatcher = Dispatcher::default(); 1187 | let mut parser = Parser::new(); 1188 | 1189 | parser.advance(&mut dispatcher, &input); 1190 | 1191 | assert_eq!(dispatcher.dispatched.len(), 1); 1192 | match &dispatcher.dispatched[0] { 1193 | Sequence::DcsHook(params, _, ignore, _) => { 1194 | assert_eq!(params.len(), params::MAX_PARAMS); 1195 | assert!(params.iter().all(|param| param == &[1])); 1196 | assert!(ignore); 1197 | }, 1198 | _ => panic!("expected dcs sequence"), 1199 | } 1200 | } 1201 | 1202 | #[test] 1203 | fn dcs_reset() { 1204 | const INPUT: &[u8] = b"\x1b[3;1\x1bP1$tx\x9c"; 1205 | let mut dispatcher = Dispatcher::default(); 1206 | let mut parser = Parser::new(); 1207 | 1208 | parser.advance(&mut dispatcher, INPUT); 1209 | 1210 | assert_eq!(dispatcher.dispatched.len(), 3); 1211 | match &dispatcher.dispatched[0] { 1212 | Sequence::DcsHook(params, intermediates, ignore, _) => { 1213 | assert_eq!(intermediates, b"$"); 1214 | assert_eq!(params, &[[1]]); 1215 | assert!(!ignore); 1216 | }, 1217 | _ => panic!("expected dcs sequence"), 1218 | } 1219 | assert_eq!(dispatcher.dispatched[1], Sequence::DcsPut(b'x')); 1220 | assert_eq!(dispatcher.dispatched[2], Sequence::DcsUnhook); 1221 | } 1222 | 1223 | #[test] 1224 | fn parse_dcs() { 1225 | const INPUT: &[u8] = b"\x1bP0;1|17/ab\x9c"; 1226 | let mut dispatcher = Dispatcher::default(); 1227 | let mut parser = Parser::new(); 1228 | 1229 | parser.advance(&mut dispatcher, INPUT); 1230 | 1231 | assert_eq!(dispatcher.dispatched.len(), 7); 1232 | match &dispatcher.dispatched[0] { 1233 | Sequence::DcsHook(params, _, _, c) => { 1234 | assert_eq!(params, &[[0], [1]]); 1235 | assert_eq!(c, &'|'); 1236 | }, 1237 | _ => panic!("expected dcs sequence"), 1238 | } 1239 | for (i, byte) in b"17/ab".iter().enumerate() { 1240 | assert_eq!(dispatcher.dispatched[1 + i], Sequence::DcsPut(*byte)); 1241 | } 1242 | assert_eq!(dispatcher.dispatched[6], Sequence::DcsUnhook); 1243 | } 1244 | 1245 | #[test] 1246 | fn intermediate_reset_on_dcs_exit() { 1247 | const INPUT: &[u8] = b"\x1bP=1sZZZ\x1b+\x5c"; 1248 | let mut dispatcher = Dispatcher::default(); 1249 | let mut parser = Parser::new(); 1250 | 1251 | parser.advance(&mut dispatcher, INPUT); 1252 | 1253 | assert_eq!(dispatcher.dispatched.len(), 6); 1254 | match &dispatcher.dispatched[5] { 1255 | Sequence::Esc(intermediates, ..) => assert_eq!(intermediates, b"+"), 1256 | _ => panic!("expected esc sequence"), 1257 | } 1258 | } 1259 | 1260 | #[test] 1261 | fn esc_reset() { 1262 | const INPUT: &[u8] = b"\x1b[3;1\x1b(A"; 1263 | let mut dispatcher = Dispatcher::default(); 1264 | let mut parser = Parser::new(); 1265 | 1266 | parser.advance(&mut dispatcher, INPUT); 1267 | 1268 | assert_eq!(dispatcher.dispatched.len(), 1); 1269 | match &dispatcher.dispatched[0] { 1270 | Sequence::Esc(intermediates, ignore, byte) => { 1271 | assert_eq!(intermediates, b"("); 1272 | assert_eq!(*byte, b'A'); 1273 | assert!(!ignore); 1274 | }, 1275 | _ => panic!("expected esc sequence"), 1276 | } 1277 | } 1278 | 1279 | #[test] 1280 | fn esc_reset_intermediates() { 1281 | const INPUT: &[u8] = b"\x1b[?2004l\x1b#8"; 1282 | let mut dispatcher = Dispatcher::default(); 1283 | let mut parser = Parser::new(); 1284 | 1285 | parser.advance(&mut dispatcher, INPUT); 1286 | 1287 | assert_eq!(dispatcher.dispatched.len(), 2); 1288 | assert_eq!(dispatcher.dispatched[0], Sequence::Csi(vec![vec![2004]], vec![63], false, 'l')); 1289 | assert_eq!(dispatcher.dispatched[1], Sequence::Esc(vec![35], false, 56)); 1290 | } 1291 | 1292 | #[test] 1293 | fn params_buffer_filled_with_subparam() { 1294 | const INPUT: &[u8] = b"\x1b[::::::::::::::::::::::::::::::::x\x1b"; 1295 | let mut dispatcher = Dispatcher::default(); 1296 | let mut parser = Parser::new(); 1297 | 1298 | parser.advance(&mut dispatcher, INPUT); 1299 | 1300 | assert_eq!(dispatcher.dispatched.len(), 1); 1301 | match &dispatcher.dispatched[0] { 1302 | Sequence::Csi(params, intermediates, ignore, c) => { 1303 | assert_eq!(intermediates, &[]); 1304 | assert_eq!(params, &[[0; 32]]); 1305 | assert_eq!(c, &'x'); 1306 | assert!(ignore); 1307 | }, 1308 | _ => panic!("expected csi sequence"), 1309 | } 1310 | } 1311 | 1312 | #[cfg(not(feature = "std"))] 1313 | #[test] 1314 | fn build_with_fixed_size() { 1315 | const INPUT: &[u8] = b"\x1b[3;1\x1b[?1049h"; 1316 | let mut dispatcher = Dispatcher::default(); 1317 | let mut parser: Parser<30> = Parser::new_with_size(); 1318 | 1319 | parser.advance(&mut dispatcher, INPUT); 1320 | 1321 | assert_eq!(dispatcher.dispatched.len(), 1); 1322 | match &dispatcher.dispatched[0] { 1323 | Sequence::Csi(params, intermediates, ignore, _) => { 1324 | assert_eq!(intermediates, b"?"); 1325 | assert_eq!(params, &[[1049]]); 1326 | assert!(!ignore); 1327 | }, 1328 | _ => panic!("expected csi sequence"), 1329 | } 1330 | } 1331 | 1332 | #[cfg(not(feature = "std"))] 1333 | #[test] 1334 | fn exceed_fixed_osc_buffer_size() { 1335 | const OSC_BUFFER_SIZE: usize = 32; 1336 | const NUM_BYTES: usize = OSC_BUFFER_SIZE + 100; 1337 | const INPUT_START: &[u8] = b"\x1b]52;"; 1338 | const INPUT_END: &[u8] = b"\x07"; 1339 | 1340 | let mut dispatcher = Dispatcher::default(); 1341 | let mut parser: Parser = Parser::new_with_size(); 1342 | 1343 | // Create valid OSC escape 1344 | parser.advance(&mut dispatcher, INPUT_START); 1345 | 1346 | // Exceed max buffer size 1347 | parser.advance(&mut dispatcher, &[b'a'; NUM_BYTES]); 1348 | 1349 | // Terminate escape for dispatch 1350 | parser.advance(&mut dispatcher, INPUT_END); 1351 | 1352 | assert_eq!(dispatcher.dispatched.len(), 1); 1353 | match &dispatcher.dispatched[0] { 1354 | Sequence::Osc(params, _) => { 1355 | assert_eq!(params.len(), 2); 1356 | assert_eq!(params[0], b"52"); 1357 | assert_eq!(params[1].len(), OSC_BUFFER_SIZE - params[0].len()); 1358 | for item in params[1].iter() { 1359 | assert_eq!(*item, b'a'); 1360 | } 1361 | }, 1362 | _ => panic!("expected osc sequence"), 1363 | } 1364 | } 1365 | 1366 | #[cfg(not(feature = "std"))] 1367 | #[test] 1368 | fn fixed_size_osc_containing_string_terminator() { 1369 | const INPUT_START: &[u8] = b"\x1b]2;"; 1370 | const INPUT_MIDDLE: &[u8] = b"s\xe6\x9c\xab"; 1371 | const INPUT_END: &[u8] = b"\x1b\\"; 1372 | 1373 | let mut dispatcher = Dispatcher::default(); 1374 | let mut parser: Parser<5> = Parser::new_with_size(); 1375 | 1376 | parser.advance(&mut dispatcher, INPUT_START); 1377 | parser.advance(&mut dispatcher, INPUT_MIDDLE); 1378 | parser.advance(&mut dispatcher, INPUT_END); 1379 | 1380 | assert_eq!(dispatcher.dispatched.len(), 2); 1381 | match &dispatcher.dispatched[0] { 1382 | Sequence::Osc(params, false) => { 1383 | assert_eq!(params[0], b"2"); 1384 | assert_eq!(params[1], INPUT_MIDDLE); 1385 | }, 1386 | _ => panic!("expected osc sequence"), 1387 | } 1388 | } 1389 | 1390 | #[test] 1391 | fn unicode() { 1392 | const INPUT: &[u8] = b"\xF0\x9F\x8E\x89_\xF0\x9F\xA6\x80\xF0\x9F\xA6\x80_\xF0\x9F\x8E\x89"; 1393 | 1394 | let mut dispatcher = Dispatcher::default(); 1395 | let mut parser = Parser::new(); 1396 | 1397 | parser.advance(&mut dispatcher, INPUT); 1398 | 1399 | assert_eq!(dispatcher.dispatched.len(), 6); 1400 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('🎉')); 1401 | assert_eq!(dispatcher.dispatched[1], Sequence::Print('_')); 1402 | assert_eq!(dispatcher.dispatched[2], Sequence::Print('🦀')); 1403 | assert_eq!(dispatcher.dispatched[3], Sequence::Print('🦀')); 1404 | assert_eq!(dispatcher.dispatched[4], Sequence::Print('_')); 1405 | assert_eq!(dispatcher.dispatched[5], Sequence::Print('🎉')); 1406 | } 1407 | 1408 | #[test] 1409 | fn invalid_utf8() { 1410 | const INPUT: &[u8] = b"a\xEF\xBCb"; 1411 | 1412 | let mut dispatcher = Dispatcher::default(); 1413 | let mut parser = Parser::new(); 1414 | 1415 | parser.advance(&mut dispatcher, INPUT); 1416 | 1417 | assert_eq!(dispatcher.dispatched.len(), 3); 1418 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('a')); 1419 | assert_eq!(dispatcher.dispatched[1], Sequence::Print('�')); 1420 | assert_eq!(dispatcher.dispatched[2], Sequence::Print('b')); 1421 | } 1422 | 1423 | #[test] 1424 | fn partial_utf8() { 1425 | const INPUT: &[u8] = b"\xF0\x9F\x9A\x80"; 1426 | 1427 | let mut dispatcher = Dispatcher::default(); 1428 | let mut parser = Parser::new(); 1429 | 1430 | parser.advance(&mut dispatcher, &INPUT[..1]); 1431 | parser.advance(&mut dispatcher, &INPUT[1..2]); 1432 | parser.advance(&mut dispatcher, &INPUT[2..3]); 1433 | parser.advance(&mut dispatcher, &INPUT[3..]); 1434 | 1435 | assert_eq!(dispatcher.dispatched.len(), 1); 1436 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('🚀')); 1437 | } 1438 | 1439 | #[test] 1440 | fn partial_utf8_separating_utf8() { 1441 | // This is different from the `partial_utf8` test since it has a multi-byte UTF8 1442 | // character after the partial UTF8 state, causing a partial byte to be present 1443 | // in the `partial_utf8` buffer after the 2-byte codepoint. 1444 | 1445 | // "ĸ🎉" 1446 | const INPUT: &[u8] = b"\xC4\xB8\xF0\x9F\x8E\x89"; 1447 | 1448 | let mut dispatcher = Dispatcher::default(); 1449 | let mut parser = Parser::new(); 1450 | 1451 | parser.advance(&mut dispatcher, &INPUT[..1]); 1452 | parser.advance(&mut dispatcher, &INPUT[1..]); 1453 | 1454 | assert_eq!(dispatcher.dispatched.len(), 2); 1455 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('ĸ')); 1456 | assert_eq!(dispatcher.dispatched[1], Sequence::Print('🎉')); 1457 | } 1458 | 1459 | #[test] 1460 | fn partial_invalid_utf8() { 1461 | const INPUT: &[u8] = b"a\xEF\xBCb"; 1462 | 1463 | let mut dispatcher = Dispatcher::default(); 1464 | let mut parser = Parser::new(); 1465 | 1466 | parser.advance(&mut dispatcher, &INPUT[..1]); 1467 | parser.advance(&mut dispatcher, &INPUT[1..2]); 1468 | parser.advance(&mut dispatcher, &INPUT[2..3]); 1469 | parser.advance(&mut dispatcher, &INPUT[3..]); 1470 | 1471 | assert_eq!(dispatcher.dispatched.len(), 3); 1472 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('a')); 1473 | assert_eq!(dispatcher.dispatched[1], Sequence::Print('�')); 1474 | assert_eq!(dispatcher.dispatched[2], Sequence::Print('b')); 1475 | } 1476 | 1477 | #[test] 1478 | fn partial_invalid_utf8_split() { 1479 | const INPUT: &[u8] = b"\xE4\xBF\x99\xB5"; 1480 | 1481 | let mut dispatcher = Dispatcher::default(); 1482 | let mut parser = Parser::new(); 1483 | 1484 | parser.advance(&mut dispatcher, &INPUT[..2]); 1485 | parser.advance(&mut dispatcher, &INPUT[2..]); 1486 | 1487 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('俙')); 1488 | assert_eq!(dispatcher.dispatched[1], Sequence::Print('�')); 1489 | } 1490 | 1491 | #[test] 1492 | fn partial_utf8_into_esc() { 1493 | const INPUT: &[u8] = b"\xD8\x1b012"; 1494 | 1495 | let mut dispatcher = Dispatcher::default(); 1496 | let mut parser = Parser::new(); 1497 | 1498 | parser.advance(&mut dispatcher, INPUT); 1499 | 1500 | assert_eq!(dispatcher.dispatched.len(), 4); 1501 | assert_eq!(dispatcher.dispatched[0], Sequence::Print('�')); 1502 | assert_eq!(dispatcher.dispatched[1], Sequence::Esc(Vec::new(), false, b'0')); 1503 | assert_eq!(dispatcher.dispatched[2], Sequence::Print('1')); 1504 | assert_eq!(dispatcher.dispatched[3], Sequence::Print('2')); 1505 | } 1506 | 1507 | #[test] 1508 | fn c1s() { 1509 | const INPUT: &[u8] = b"\x00\x1f\x80\x90\x98\x9b\x9c\x9d\x9e\x9fa"; 1510 | 1511 | let mut dispatcher = Dispatcher::default(); 1512 | let mut parser = Parser::new(); 1513 | 1514 | parser.advance(&mut dispatcher, INPUT); 1515 | 1516 | assert_eq!(dispatcher.dispatched.len(), 11); 1517 | assert_eq!(dispatcher.dispatched[0], Sequence::Execute(0)); 1518 | assert_eq!(dispatcher.dispatched[1], Sequence::Execute(31)); 1519 | assert_eq!(dispatcher.dispatched[2], Sequence::Execute(128)); 1520 | assert_eq!(dispatcher.dispatched[3], Sequence::Execute(144)); 1521 | assert_eq!(dispatcher.dispatched[4], Sequence::Execute(152)); 1522 | assert_eq!(dispatcher.dispatched[5], Sequence::Execute(155)); 1523 | assert_eq!(dispatcher.dispatched[6], Sequence::Execute(156)); 1524 | assert_eq!(dispatcher.dispatched[7], Sequence::Execute(157)); 1525 | assert_eq!(dispatcher.dispatched[8], Sequence::Execute(158)); 1526 | assert_eq!(dispatcher.dispatched[9], Sequence::Execute(159)); 1527 | assert_eq!(dispatcher.dispatched[10], Sequence::Print('a')); 1528 | } 1529 | 1530 | #[test] 1531 | fn execute_anywhere() { 1532 | const INPUT: &[u8] = b"\x18\x1a"; 1533 | 1534 | let mut dispatcher = Dispatcher::default(); 1535 | let mut parser = Parser::new(); 1536 | 1537 | parser.advance(&mut dispatcher, INPUT); 1538 | 1539 | assert_eq!(dispatcher.dispatched.len(), 2); 1540 | assert_eq!(dispatcher.dispatched[0], Sequence::Execute(0x18)); 1541 | assert_eq!(dispatcher.dispatched[1], Sequence::Execute(0x1A)); 1542 | } 1543 | } 1544 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | //! Fixed size parameters list with optional subparameters. 2 | 3 | use core::fmt::{self, Debug, Formatter}; 4 | 5 | pub(crate) const MAX_PARAMS: usize = 32; 6 | 7 | #[derive(Default)] 8 | pub struct Params { 9 | /// Number of subparameters for each parameter. 10 | /// 11 | /// For each entry in the `params` slice, this stores the length of the 12 | /// param as number of subparams at the same index as the param in the 13 | /// `params` slice. 14 | /// 15 | /// At the subparam positions the length will always be `0`. 16 | subparams: [u8; MAX_PARAMS], 17 | 18 | /// All parameters and subparameters. 19 | params: [u16; MAX_PARAMS], 20 | 21 | /// Number of suparameters in the current parameter. 22 | current_subparams: u8, 23 | 24 | /// Total number of parameters and subparameters. 25 | len: usize, 26 | } 27 | 28 | impl Params { 29 | /// Returns the number of parameters. 30 | #[inline] 31 | pub fn len(&self) -> usize { 32 | self.len 33 | } 34 | 35 | /// Returns `true` if there are no parameters present. 36 | #[inline] 37 | pub fn is_empty(&self) -> bool { 38 | self.len == 0 39 | } 40 | 41 | /// Returns an iterator over all parameters and subparameters. 42 | #[inline] 43 | pub fn iter(&self) -> ParamsIter<'_> { 44 | ParamsIter::new(self) 45 | } 46 | 47 | /// Returns `true` if there is no more space for additional parameters. 48 | #[inline] 49 | pub(crate) fn is_full(&self) -> bool { 50 | self.len == MAX_PARAMS 51 | } 52 | 53 | /// Clear all parameters. 54 | #[inline] 55 | pub(crate) fn clear(&mut self) { 56 | self.current_subparams = 0; 57 | self.len = 0; 58 | } 59 | 60 | /// Add an additional parameter. 61 | #[inline] 62 | pub(crate) fn push(&mut self, item: u16) { 63 | self.subparams[self.len - self.current_subparams as usize] = self.current_subparams + 1; 64 | self.params[self.len] = item; 65 | self.current_subparams = 0; 66 | self.len += 1; 67 | } 68 | 69 | /// Add an additional subparameter to the current parameter. 70 | #[inline] 71 | pub(crate) fn extend(&mut self, item: u16) { 72 | self.subparams[self.len - self.current_subparams as usize] = self.current_subparams + 1; 73 | self.params[self.len] = item; 74 | self.current_subparams += 1; 75 | self.len += 1; 76 | } 77 | } 78 | 79 | impl<'a> IntoIterator for &'a Params { 80 | type IntoIter = ParamsIter<'a>; 81 | type Item = &'a [u16]; 82 | 83 | fn into_iter(self) -> Self::IntoIter { 84 | self.iter() 85 | } 86 | } 87 | 88 | /// Immutable subparameter iterator. 89 | pub struct ParamsIter<'a> { 90 | params: &'a Params, 91 | index: usize, 92 | } 93 | 94 | impl<'a> ParamsIter<'a> { 95 | fn new(params: &'a Params) -> Self { 96 | Self { params, index: 0 } 97 | } 98 | } 99 | 100 | impl<'a> Iterator for ParamsIter<'a> { 101 | type Item = &'a [u16]; 102 | 103 | fn next(&mut self) -> Option { 104 | if self.index >= self.params.len() { 105 | return None; 106 | } 107 | 108 | // Get all subparameters for the current parameter. 109 | let num_subparams = self.params.subparams[self.index]; 110 | let param = &self.params.params[self.index..self.index + num_subparams as usize]; 111 | 112 | // Jump to the next parameter. 113 | self.index += num_subparams as usize; 114 | 115 | Some(param) 116 | } 117 | 118 | fn size_hint(&self) -> (usize, Option) { 119 | let remaining = self.params.len() - self.index; 120 | (remaining, Some(remaining)) 121 | } 122 | } 123 | 124 | impl Debug for Params { 125 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 126 | write!(f, "[")?; 127 | 128 | for (i, param) in self.iter().enumerate() { 129 | if i != 0 { 130 | write!(f, ";")?; 131 | } 132 | 133 | for (i, subparam) in param.iter().enumerate() { 134 | if i != 0 { 135 | write!(f, ":")?; 136 | } 137 | 138 | subparam.fmt(f)?; 139 | } 140 | } 141 | 142 | write!(f, "]") 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tests/demo.vte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alacritty/vte/ca3dd62eb4a79c9c728bdb6ec67a853cfbb801bf/tests/demo.vte --------------------------------------------------------------------------------