vt100-0.15.2/.cargo_vcs_info.json0000644000000001360000000000100117730ustar { "git": { "sha1": "4c9e5da3de10d838d792929f1691024d6bf59c85" }, "path_in_vcs": "" }vt100-0.15.2/CHANGELOG.md000064400000000000000000000225111046102023000123750ustar 00000000000000# Changelog ## [0.15.2] - 2023-02-05 ### Changed * Bumped dependencies ## [0.15.1] - 2021-12-21 ### Changed * Removed a lot of unnecessary test data from the packaged crate, making downloads faster ## [0.15.0] - 2021-12-15 ### Added * `Screen::errors` to track the number of parsing errors seen so far ### Fixed * No longer generate spurious diffs in some cases where the cursor is past the end of a row * Fix restoring the cursor position when scrolled back ### Changed * Various internal refactorings ## [0.14.0] - 2021-12-06 ### Changed * Unknown UTF-8 characters default to a width of 1, rather than 0 (except for control characters, as mentioned below) ### Fixed * Ignore C1 control characters rather than adding them to the cell data, since they are non-printable ## [0.13.2] - 2021-12-05 ### Changed * Delay allocation of the alternate screen until it is used (saves a bit of memory in basic cases) ## [0.13.1] - 2021-12-04 ### Fixed * Fixed various line wrapping state issues * Fixed cursor positioning after writing zero width characters at the end of the line * Fixed `Screen::cursor_state_formatted` to draw the last character in a line with the appropriate drawing attributes if it needs to redraw it ## [0.13.0] - 2021-11-17 ### Added * `Screen::alternate_screen` to determine if the alternate screen is in use * `Screen::row_wrapped` to determine whether the row at the given index should wrap its text * `Screen::cursor_state_formatted` to set the cursor position and hidden state (including internal state like the one-past-the-end state which isn't visible in the return value of `cursor_position`) ### Fixed * `Screen::rows_formatted` now outputs correct escape codes in some edge cases at the beginning of a row when the previous row was wrapped * VPA escape sequence can no longer position the cursor off the screen ## [0.12.0] - 2021-03-09 ### Added * `Screen::state_formatted` and `Screen::state_diff` convenience wrappers ### Fixed * `Screen::attributes_formatted` now correctly resets previously set attributes where necessary ### Removed * Removed `Screen::attributes_diff`, since I can't actually think of any situation where it does a thing that makes sense. ## [0.11.1] - 2021-03-07 ### Changed * Drop dependency on `enumset` ## [0.11.0] - 2021-03-07 ### Added * `Screen::attributes_formatted` and `Screen::attributes_diff` to retrieve the current state of the drawing attributes as escape sequences * `Screen::fgcolor`, `Screen::bgcolor`, `Screen::bold`, `Screen::italic`, `Screen::underline`, and `Screen::inverse` to retrieve the current state of the drawing attributes directly ## [0.10.0] - 2021-03-06 ### Added * Implementation of `std::io::Write` for `Parser` ## [0.9.0] - 2021-03-05 ### Added * `Screen::contents_between`, for returning the contents logically between two given cells (for things like clipboard selection) * Support SGR subparameters (so `\e[38:2:255:0:0m` behaves the same way as `\e[38;2;255;0;0m`) ### Fixed * Bump `enumset` to fix a dependency which fails to build ## [0.8.1] - 2020-02-09 ### Changed * Bumped `vte` dep to 0.6. ## [0.8.0] - 2019-12-07 ### Removed * Removed the unicode-normalization feature altogether - it turns out that it still has a couple edge cases where it causes incorrect behavior, and fixing those would be a lot more effort. ### Fixed * Fix a couple more end-of-line/wrapping bugs, especially around cursor positioning. * Fix applying combining characters to wide characters. * Ensure cells can't have contents with width zero (to avoid ambiguity). If an empty cell gets a combining character applied to it, default that cell to a (normal-width) space first. ## [0.7.0] - 2019-11-23 ### Added * New (default-on) cargo feature `unicode-normalization` which can be disabled to disable normalizing cell contents to NFC - it's a pretty small edge case, and the data tables required to support it are quite large, which affects size-sensitive targets like wasm ## [0.6.3] - 2019-11-20 ### Fixed * Fix output of `contents_formatted` and `contents_diff` when the cursor position ends at one past the end of a row. * If the cursor position is one past the end of a row, any char, even a combining char, needs to cause the cursor position to wrap. ## [0.6.2] - 2019-11-13 ### Fixed * Fix zero-width characters when the cursor is at the end of a row. ## [0.6.1] - 2019-11-13 ### Added * Add more debug logging for unhandled escape sequences. ### Changed * Unhandled escape sequence warnings are now at the `debug` log level. ## [0.6.0] - 2019-11-13 ### Added * `Screen::input_mode_formatted` and `Screen::input_mode_diff` give escape codes to set the current terminal input modes. * `Screen::title_formatted` and `Screen::title_diff` give escape codes to set the terminal window title. * `Screen::bells_diff` gives escape codes to trigger any audible or visual bells which have been seen since the previous state. ### Changed * `Screen::contents_diff` no longer includes audible or visual bells (see `Screen::bells_diff` instead). ## [0.5.1] - 2019-11-12 ### Fixed * `Screen::set_size` now actually resizes when requested (previously the underlying storage was not being resized, leading to panics when writing outside of the original screen). ## [0.5.0] - 2019-11-12 ### Added * Scrollback support. * `Default` impl for `Parser` which creates an 80x24 terminal with no scrollback. ### Removed * `Parser::screen_mut` (and the `pub` `&mut self` methods on `Screen`). The few things you can do to change the screen state directly are now exposed as methods on `Parser` itself. ### Changed * `Cell::contents` now returns a `String` instead of a `&str`. * `Screen::check_audible_bell` and `Screen::check_visual_bell` have been replaced with `Screen::audible_bell_count` and `Screen::visual_bell_count`. You should keep track of the "since the last method call" state yourself instead of having the screen track it for you. ### Fixed * Lots of performance and output optimizations. * Clearing a cell now sets all of that cell's attributes to the current attribute set, since different terminals render different things for an empty cell based on the attributes. * `Screen::contents_diff` now includes audible and visual bells when appropriate. ## [0.4.0] - 2019-11-08 ### Removed * `Screen::fgcolor`, `Screen::bgcolor`, `Screen::bold`, `Screen::italic`, `Screen::underline`, `Screen::inverse`, and `Screen::alternate_screen`: these are just implementation details that people shouldn't need to care about. ### Fixed * Fixed cursor movement when the cursor position is already outside of an active scroll region. ## [0.3.2] - 2019-11-08 ### Fixed * Clearing cells now correctly sets the cell background color. * Fixed a couple bugs in wide character handling in `contents_formatted` and `contents_diff`. * Fixed RI when the cursor is at the top of the screen (fixes scrolling up in `less`, for instance). * Fixed VPA incorrectly being clamped to the scroll region. * Stop treating soft hyphen specially (as far as i can tell, no other terminals do this, and i'm not sure why i thought it was necessary to begin with). * `contents_formatted` now also resets attributes at the start, like `contents_diff` does. ## [0.3.1] - 2019-11-06 ### Fixed * Make `contents_formatted` explicitly show the cursor when necessary, in case the cursor was previously hidden. ## [0.3.0] - 2019-11-06 ### Added * `Screen::rows` which is like `Screen::contents` except that it returns the data by row instead of all at once, and also allows you to restrict the region returned to a subset of columns. * `Screen::rows_formatted` which is like `Screen::rows`, but returns escape sequences sufficient to draw the requested subset of each row. * `Screen::contents_diff` and `Screen::rows_diff` which return escape sequences sufficient to turn the visible state of one screen (or a subset of the screen in the case of `rows_diff`) into another. ### Changed * The screen is now exposed separately from the parser, and is cloneable. * `contents_formatted` now returns `Vec` instead of `String`. * `contents` and `contents_formatted` now only allow getting the contents of the entire screen rather than a subset (but see the entry for `rows` and `rows_formatted` above). ### Removed * `Cell::new`, since there's not really any reason that this is useful for someone to do from outside of the crate. ### Fixed * `contents_formatted` now preserves the state of empty cells instead of filling them with spaces. * We now clear the row wrapping state when the number of columns in the terminal is changed. * `contents_formatted` now ensures that the cursor has the correct hidden state and location. * `contents_formatted` now clears the screen before starting to draw. ## [0.2.0] - 2019-11-04 ### Changed * Reimplemented in pure safe rust, with a much more accurate parser * A bunch of minor API tweaks, some backwards-incompatible ## [0.1.2] - 2016-06-04 ### Fixed * Fix returning uninit memory in get_string_formatted/get_string_plaintext * Handle emoji and zero width unicode characters properly * Fix cursor positioning with regards to scroll regions and wrapping * Fix parsing of (ignored) character set escapes * Explicitly suppress status report escapes ## [0.1.1] - 2016-04-28 ### Fixed * Fix builds ## [0.1.0] - 2016-04-28 ### Added * Initial release vt100-0.15.2/Cargo.lock0000644000000313700000000000100077520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] name = "arrayvec" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "env_logger" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "log", "regex", ] [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "io-lifetimes" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ "libc", "windows-sys 0.45.0", ] [[package]] name = "itoa" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linux-raw-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", "libc", "memoffset", "pin-utils", "static_assertions", ] [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ "env_logger", "log", "rand", "rand_core", ] [[package]] name = "quote" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "regex" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rustix" version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.45.0", ] [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "terminal_size" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", "windows-sys 0.42.0", ] [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "utf8parse" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "vt100" version = "0.15.2" dependencies = [ "itoa", "log", "nix", "quickcheck", "rand", "serde", "serde_json", "terminal_size", "unicode-width", "vte", ] [[package]] name = "vte" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aae21c12ad2ec2d168c236f369c38ff332bc1134f7246350dca641437365045" dependencies = [ "arrayvec", "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte_generate_state_changes" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" vt100-0.15.2/Cargo.toml0000644000000026540000000000100100000ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "vt100" version = "0.15.2" authors = ["Jesse Luehrs "] include = [ "src/**/*", "LICENSE", "README.md", "CHANGELOG.md", ] description = "Library for parsing terminal data" homepage = "https://github.com/doy/vt100-rust" readme = "README.md" keywords = [ "terminal", "vt100", ] categories = [ "command-line-interface", "encoding", ] license = "MIT" repository = "https://github.com/doy/vt100-rust" [dependencies.itoa] version = "1.0.5" [dependencies.log] version = "0.4.17" [dependencies.unicode-width] version = "0.1.10" [dependencies.vte] version = "0.11.0" [dev-dependencies.nix] version = "0.26.2" [dev-dependencies.quickcheck] version = "0.9" [dev-dependencies.rand] version = "0.7" [dev-dependencies.serde] version = "1.0.152" features = ["derive"] [dev-dependencies.serde_json] version = "1.0.92" [dev-dependencies.terminal_size] version = "0.2.3" [dev-dependencies.vte] version = "0.11.0" vt100-0.15.2/Cargo.toml.orig000064400000000000000000000013001046102023000134440ustar 00000000000000[package] name = "vt100" version = "0.15.2" authors = ["Jesse Luehrs "] edition = "2021" description = "Library for parsing terminal data" homepage = "https://github.com/doy/vt100-rust" repository = "https://github.com/doy/vt100-rust" readme = "README.md" keywords = ["terminal", "vt100"] categories = ["command-line-interface", "encoding"] license = "MIT" include = ["src/**/*", "LICENSE", "README.md", "CHANGELOG.md"] [dependencies] itoa = "1.0.5" log = "0.4.17" unicode-width = "0.1.10" vte = "0.11.0" [dev-dependencies] nix = "0.26.2" quickcheck = "0.9" rand = "0.7" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.92" terminal_size = "0.2.3" vte = "0.11.0" vt100-0.15.2/LICENSE000064400000000000000000000020671046102023000115750ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Jesse Luehrs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vt100-0.15.2/README.md000064400000000000000000000020321046102023000120370ustar 00000000000000# vt100 This crate parses a terminal byte stream and provides an in-memory representation of the rendered contents. ## Overview This is essentially the terminal parser component of a graphical terminal emulator pulled out into a separate crate. Although you can use this crate to build a graphical terminal emulator, it also contains functionality necessary for implementing terminal applications that want to run other terminal applications - programs like `screen` or `tmux` for example. ## Synopsis ```rust let mut parser = vt100::Parser::new(24, 80, 0); let screen = parser.screen().clone(); parser.process(b"this text is \x1b[31mRED\x1b[m"); assert_eq!( parser.screen().cell(0, 13).unwrap().fgcolor(), vt100::Color::Idx(1), ); let screen = parser.screen().clone(); parser.process(b"\x1b[3D\x1b[32mGREEN"); assert_eq!( parser.screen().contents_formatted(), &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jthis text is \x1b[32mGREEN"[..], ); assert_eq!( parser.screen().contents_diff(&screen), &b"\x1b[1;14H\x1b[32mGREEN"[..], ); ``` vt100-0.15.2/src/attrs.rs000064400000000000000000000060221046102023000130550ustar 00000000000000use crate::term::BufWrite as _; /// Represents a foreground or background color for cells. #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum Color { /// The default terminal color. Default, /// An indexed terminal color. Idx(u8), /// An RGB terminal color. The parameters are (red, green, blue). Rgb(u8, u8, u8), } impl Default for Color { fn default() -> Self { Self::Default } } const TEXT_MODE_BOLD: u8 = 0b0000_0001; const TEXT_MODE_ITALIC: u8 = 0b0000_0010; const TEXT_MODE_UNDERLINE: u8 = 0b0000_0100; const TEXT_MODE_INVERSE: u8 = 0b0000_1000; #[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] pub struct Attrs { pub fgcolor: Color, pub bgcolor: Color, pub mode: u8, } impl Attrs { pub fn bold(&self) -> bool { self.mode & TEXT_MODE_BOLD != 0 } pub fn set_bold(&mut self, bold: bool) { if bold { self.mode |= TEXT_MODE_BOLD; } else { self.mode &= !TEXT_MODE_BOLD; } } pub fn italic(&self) -> bool { self.mode & TEXT_MODE_ITALIC != 0 } pub fn set_italic(&mut self, italic: bool) { if italic { self.mode |= TEXT_MODE_ITALIC; } else { self.mode &= !TEXT_MODE_ITALIC; } } pub fn underline(&self) -> bool { self.mode & TEXT_MODE_UNDERLINE != 0 } pub fn set_underline(&mut self, underline: bool) { if underline { self.mode |= TEXT_MODE_UNDERLINE; } else { self.mode &= !TEXT_MODE_UNDERLINE; } } pub fn inverse(&self) -> bool { self.mode & TEXT_MODE_INVERSE != 0 } pub fn set_inverse(&mut self, inverse: bool) { if inverse { self.mode |= TEXT_MODE_INVERSE; } else { self.mode &= !TEXT_MODE_INVERSE; } } pub fn write_escape_code_diff( &self, contents: &mut Vec, other: &Self, ) { if self != other && self == &Self::default() { crate::term::ClearAttrs::default().write_buf(contents); return; } let attrs = crate::term::Attrs::default(); let attrs = if self.fgcolor == other.fgcolor { attrs } else { attrs.fgcolor(self.fgcolor) }; let attrs = if self.bgcolor == other.bgcolor { attrs } else { attrs.bgcolor(self.bgcolor) }; let attrs = if self.bold() == other.bold() { attrs } else { attrs.bold(self.bold()) }; let attrs = if self.italic() == other.italic() { attrs } else { attrs.italic(self.italic()) }; let attrs = if self.underline() == other.underline() { attrs } else { attrs.underline(self.underline()) }; let attrs = if self.inverse() == other.inverse() { attrs } else { attrs.inverse(self.inverse()) }; attrs.write_buf(contents); } } vt100-0.15.2/src/cell.rs000064400000000000000000000101051046102023000126340ustar 00000000000000use unicode_width::UnicodeWidthChar as _; const CODEPOINTS_IN_CELL: usize = 6; /// Represents a single terminal cell. #[derive(Clone, Debug, Default, Eq)] pub struct Cell { contents: [char; CODEPOINTS_IN_CELL], len: u8, attrs: crate::attrs::Attrs, } impl PartialEq for Cell { fn eq(&self, other: &Self) -> bool { if self.len != other.len { return false; } if self.attrs != other.attrs { return false; } let len = self.len(); // self.len() always returns a valid value self.contents[..len] == other.contents[..len] } } impl Cell { #[inline] fn len(&self) -> usize { usize::from(self.len & 0x0f) } pub(crate) fn set(&mut self, c: char, a: crate::attrs::Attrs) { self.contents[0] = c; self.len = 1; // strings in this context should always be an arbitrary character // followed by zero or more zero-width characters, so we should only // have to look at the first character self.set_wide(c.width().unwrap_or(1) > 1); self.attrs = a; } pub(crate) fn append(&mut self, c: char) { let len = self.len(); if len >= CODEPOINTS_IN_CELL { return; } if len == 0 { // 0 is always less than 6 self.contents[0] = ' '; self.len += 1; } let len = self.len(); // we already checked that len < CODEPOINTS_IN_CELL self.contents[len] = c; self.len += 1; } pub(crate) fn clear(&mut self, attrs: crate::attrs::Attrs) { self.len = 0; self.attrs = attrs; } /// Returns the text contents of the cell. /// /// Can include multiple unicode characters if combining characters are /// used, but will contain at most one character with a non-zero character /// width. #[must_use] pub fn contents(&self) -> String { let mut s = String::with_capacity(CODEPOINTS_IN_CELL * 4); for c in self.contents.iter().take(self.len()) { s.push(*c); } s } /// Returns whether the cell contains any text data. #[must_use] pub fn has_contents(&self) -> bool { self.len > 0 } /// Returns whether the text data in the cell represents a wide character. #[must_use] pub fn is_wide(&self) -> bool { self.len & 0x80 == 0x80 } /// Returns whether the cell contains the second half of a wide character /// (in other words, whether the previous cell in the row contains a wide /// character) #[must_use] pub fn is_wide_continuation(&self) -> bool { self.len & 0x40 == 0x40 } fn set_wide(&mut self, wide: bool) { if wide { self.len |= 0x80; } else { self.len &= 0x7f; } } pub(crate) fn set_wide_continuation(&mut self, wide: bool) { if wide { self.len |= 0x40; } else { self.len &= 0xbf; } } pub(crate) fn attrs(&self) -> &crate::attrs::Attrs { &self.attrs } /// Returns the foreground color of the cell. #[must_use] pub fn fgcolor(&self) -> crate::attrs::Color { self.attrs.fgcolor } /// Returns the background color of the cell. #[must_use] pub fn bgcolor(&self) -> crate::attrs::Color { self.attrs.bgcolor } /// Returns whether the cell should be rendered with the bold text /// attribute. #[must_use] pub fn bold(&self) -> bool { self.attrs.bold() } /// Returns whether the cell should be rendered with the italic text /// attribute. #[must_use] pub fn italic(&self) -> bool { self.attrs.italic() } /// Returns whether the cell should be rendered with the underlined text /// attribute. #[must_use] pub fn underline(&self) -> bool { self.attrs.underline() } /// Returns whether the cell should be rendered with the inverse text /// attribute. #[must_use] pub fn inverse(&self) -> bool { self.attrs.inverse() } } vt100-0.15.2/src/grid.rs000064400000000000000000000611331046102023000126510ustar 00000000000000use crate::term::BufWrite as _; #[derive(Clone, Debug)] pub struct Grid { size: Size, pos: Pos, saved_pos: Pos, rows: Vec, scroll_top: u16, scroll_bottom: u16, origin_mode: bool, saved_origin_mode: bool, scrollback: std::collections::VecDeque, scrollback_len: usize, scrollback_offset: usize, } impl Grid { pub fn new(size: Size, scrollback_len: usize) -> Self { Self { size, pos: Pos::default(), saved_pos: Pos::default(), rows: vec![], scroll_top: 0, scroll_bottom: size.rows - 1, origin_mode: false, saved_origin_mode: false, scrollback: std::collections::VecDeque::new(), scrollback_len, scrollback_offset: 0, } } pub fn allocate_rows(&mut self) { if self.rows.is_empty() { self.rows.extend( std::iter::repeat_with(|| { crate::row::Row::new(self.size.cols) }) .take(usize::from(self.size.rows)), ); } } fn new_row(&self) -> crate::row::Row { crate::row::Row::new(self.size.cols) } pub fn clear(&mut self) { self.pos = Pos::default(); self.saved_pos = Pos::default(); for row in self.drawing_rows_mut() { row.clear(crate::attrs::Attrs::default()); } self.scroll_top = 0; self.scroll_bottom = self.size.rows - 1; self.origin_mode = false; self.saved_origin_mode = false; } pub fn size(&self) -> Size { self.size } pub fn set_size(&mut self, size: Size) { if size.cols != self.size.cols { for row in &mut self.rows { row.wrap(false); } } if self.scroll_bottom == self.size.rows - 1 { self.scroll_bottom = size.rows - 1; } self.size = size; for row in &mut self.rows { row.resize(size.cols, crate::cell::Cell::default()); } self.rows.resize(usize::from(size.rows), self.new_row()); if self.scroll_bottom >= size.rows { self.scroll_bottom = size.rows - 1; } if self.scroll_bottom < self.scroll_top { self.scroll_top = 0; } self.row_clamp_top(false); self.row_clamp_bottom(false); self.col_clamp(); } pub fn pos(&self) -> Pos { self.pos } pub fn set_pos(&mut self, mut pos: Pos) { if self.origin_mode { pos.row = pos.row.saturating_add(self.scroll_top); } self.pos = pos; self.row_clamp_top(self.origin_mode); self.row_clamp_bottom(self.origin_mode); self.col_clamp(); } pub fn save_cursor(&mut self) { self.saved_pos = self.pos; self.saved_origin_mode = self.origin_mode; } pub fn restore_cursor(&mut self) { self.pos = self.saved_pos; self.origin_mode = self.saved_origin_mode; } pub fn visible_rows(&self) -> impl Iterator { let scrollback_len = self.scrollback.len(); let rows_len = self.rows.len(); self.scrollback .iter() .skip(scrollback_len - self.scrollback_offset) .chain(self.rows.iter().take(rows_len - self.scrollback_offset)) } pub fn drawing_rows(&self) -> impl Iterator { self.rows.iter() } pub fn drawing_rows_mut( &mut self, ) -> impl Iterator { self.rows.iter_mut() } pub fn visible_row(&self, row: u16) -> Option<&crate::row::Row> { self.visible_rows().nth(usize::from(row)) } pub fn drawing_row(&self, row: u16) -> Option<&crate::row::Row> { self.drawing_rows().nth(usize::from(row)) } pub fn drawing_row_mut( &mut self, row: u16, ) -> Option<&mut crate::row::Row> { self.drawing_rows_mut().nth(usize::from(row)) } pub fn current_row_mut(&mut self) -> &mut crate::row::Row { self.drawing_row_mut(self.pos.row) // we assume self.pos.row is always valid .unwrap() } pub fn visible_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { self.visible_row(pos.row).and_then(|r| r.get(pos.col)) } pub fn drawing_cell(&self, pos: Pos) -> Option<&crate::cell::Cell> { self.drawing_row(pos.row).and_then(|r| r.get(pos.col)) } pub fn drawing_cell_mut( &mut self, pos: Pos, ) -> Option<&mut crate::cell::Cell> { self.drawing_row_mut(pos.row) .and_then(|r| r.get_mut(pos.col)) } pub fn scrollback_len(&self) -> usize { self.scrollback_len } pub fn scrollback(&self) -> usize { self.scrollback_offset } pub fn set_scrollback(&mut self, rows: usize) { self.scrollback_offset = rows.min(self.scrollback.len()); } pub fn write_contents(&self, contents: &mut String) { let mut wrapping = false; for row in self.visible_rows() { row.write_contents(contents, 0, self.size.cols, wrapping); if !row.wrapped() { contents.push('\n'); } wrapping = row.wrapped(); } while contents.ends_with('\n') { contents.truncate(contents.len() - 1); } } pub fn write_contents_formatted( &self, contents: &mut Vec, ) -> crate::attrs::Attrs { crate::term::ClearAttrs::default().write_buf(contents); crate::term::ClearScreen::default().write_buf(contents); let mut prev_attrs = crate::attrs::Attrs::default(); let mut prev_pos = Pos::default(); let mut wrapping = false; for (i, row) in self.visible_rows().enumerate() { // we limit the number of cols to a u16 (see Size), so // visible_rows() can never return more rows than will fit let i = i.try_into().unwrap(); let (new_pos, new_attrs) = row.write_contents_formatted( contents, 0, self.size.cols, i, wrapping, Some(prev_pos), Some(prev_attrs), ); prev_pos = new_pos; prev_attrs = new_attrs; wrapping = row.wrapped(); } self.write_cursor_position_formatted( contents, Some(prev_pos), Some(prev_attrs), ); prev_attrs } pub fn write_contents_diff( &self, contents: &mut Vec, prev: &Self, mut prev_attrs: crate::attrs::Attrs, ) -> crate::attrs::Attrs { let mut prev_pos = prev.pos; let mut wrapping = false; let mut prev_wrapping = false; for (i, (row, prev_row)) in self.visible_rows().zip(prev.visible_rows()).enumerate() { // we limit the number of cols to a u16 (see Size), so // visible_rows() can never return more rows than will fit let i = i.try_into().unwrap(); let (new_pos, new_attrs) = row.write_contents_diff( contents, prev_row, 0, self.size.cols, i, wrapping, prev_wrapping, prev_pos, prev_attrs, ); prev_pos = new_pos; prev_attrs = new_attrs; wrapping = row.wrapped(); prev_wrapping = prev_row.wrapped(); } self.write_cursor_position_formatted( contents, Some(prev_pos), Some(prev_attrs), ); prev_attrs } pub fn write_cursor_position_formatted( &self, contents: &mut Vec, prev_pos: Option, prev_attrs: Option, ) { let prev_attrs = prev_attrs.unwrap_or_default(); // writing a character to the last column of a row doesn't wrap the // cursor immediately - it waits until the next character is actually // drawn. it is only possible for the cursor to have this kind of // position after drawing a character though, so if we end in this // position, we need to redraw the character at the end of the row. if prev_pos != Some(self.pos) && self.pos.col >= self.size.cols { let mut pos = Pos { row: self.pos.row, col: self.size.cols - 1, }; if self .drawing_cell(pos) // we assume self.pos.row is always valid, and self.size.cols // - 1 is always a valid column .unwrap() .is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = // we assume self.pos.row is always valid, and self.size.cols // - 2 must be a valid column because self.size.cols - 1 is // always valid and we just checked that the cell at // self.size.cols - 1 is a wide continuation character, which // means that the first half of the wide character must be // before it self.drawing_cell(pos).unwrap(); if cell.has_contents() { if let Some(prev_pos) = prev_pos { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); } else { crate::term::MoveTo::new(pos).write_buf(contents); } cell.attrs().write_escape_code_diff(contents, &prev_attrs); contents.extend(cell.contents().as_bytes()); prev_attrs.write_escape_code_diff(contents, cell.attrs()); } else { // if the cell doesn't have contents, we can't have gotten // here by drawing a character in the last column. this means // that as far as i'm aware, we have to have reached here from // a newline when we were already after the end of an earlier // row. in the case where we are already after the end of an // earlier row, we can just write a few newlines, otherwise we // also need to do the same as above to get ourselves to after // the end of a row. let mut found = false; for i in (0..self.pos.row).rev() { pos.row = i; pos.col = self.size.cols - 1; if self .drawing_cell(pos) // i is always less than self.pos.row, which we assume // to be always valid, so it must also be valid. // self.size.cols - 1 is always a valid col. .unwrap() .is_wide_continuation() { pos.col = self.size.cols - 2; } let cell = self .drawing_cell(pos) // i is always less than self.pos.row, which we assume // to be always valid, so it must also be valid. // self.size.cols - 2 is valid because self.size.cols // - 1 is always valid, and col gets set to // self.size.cols - 2 when the cell at self.size.cols // - 1 is a wide continuation character, meaning that // the first half of the wide character must be before // it .unwrap(); if cell.has_contents() { if let Some(prev_pos) = prev_pos { if prev_pos.row != i || prev_pos.col < self.size.cols { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); cell.attrs().write_escape_code_diff( contents, &prev_attrs, ); contents.extend(cell.contents().as_bytes()); prev_attrs.write_escape_code_diff( contents, cell.attrs(), ); } } else { crate::term::MoveTo::new(pos).write_buf(contents); cell.attrs().write_escape_code_diff( contents, &prev_attrs, ); contents.extend(cell.contents().as_bytes()); prev_attrs.write_escape_code_diff( contents, cell.attrs(), ); } contents.extend( "\n".repeat(usize::from(self.pos.row - i)) .as_bytes(), ); found = true; break; } } // this can happen if you get the cursor off the end of a row, // and then do something to clear the end of the current row // without moving the cursor (IL, DL, ED, EL, etc). we know // there can't be something in the last column because we // would have caught that above, so it should be safe to // overwrite it. if !found { pos = Pos { row: self.pos.row, col: self.size.cols - 1, }; if let Some(prev_pos) = prev_pos { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); } else { crate::term::MoveTo::new(pos).write_buf(contents); } contents.push(b' '); // we know that the cell has no contents, but it still may // have drawing attributes (background color, etc) let end_cell = self .drawing_cell(pos) // we assume self.pos.row is always valid, and // self.size.cols - 1 is always a valid column .unwrap(); end_cell .attrs() .write_escape_code_diff(contents, &prev_attrs); crate::term::SaveCursor::default().write_buf(contents); crate::term::Backspace::default().write_buf(contents); crate::term::EraseChar::new(1).write_buf(contents); crate::term::RestoreCursor::default().write_buf(contents); prev_attrs .write_escape_code_diff(contents, end_cell.attrs()); } } } else if let Some(prev_pos) = prev_pos { crate::term::MoveFromTo::new(prev_pos, self.pos) .write_buf(contents); } else { crate::term::MoveTo::new(self.pos).write_buf(contents); } } pub fn erase_all(&mut self, attrs: crate::attrs::Attrs) { for row in self.drawing_rows_mut() { row.clear(attrs); } } pub fn erase_all_forward(&mut self, attrs: crate::attrs::Attrs) { let pos = self.pos; for row in self.drawing_rows_mut().skip(usize::from(pos.row) + 1) { row.clear(attrs); } self.erase_row_forward(attrs); } pub fn erase_all_backward(&mut self, attrs: crate::attrs::Attrs) { let pos = self.pos; for row in self.drawing_rows_mut().take(usize::from(pos.row)) { row.clear(attrs); } self.erase_row_backward(attrs); } pub fn erase_row(&mut self, attrs: crate::attrs::Attrs) { self.current_row_mut().clear(attrs); } pub fn erase_row_forward(&mut self, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in pos.col..size.cols { row.erase(col, attrs); } } pub fn erase_row_backward(&mut self, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in 0..=pos.col.min(size.cols - 1) { row.erase(col, attrs); } } pub fn insert_cells(&mut self, count: u16) { let size = self.size; let pos = self.pos; let wide = pos.col < size.cols && self .drawing_cell(pos) // we assume self.pos.row is always valid, and we know we are // not off the end of a row because we just checked pos.col < // size.cols .unwrap() .is_wide_continuation(); let row = self.current_row_mut(); for _ in 0..count { if wide { row.get_mut(pos.col).unwrap().set_wide_continuation(false); } row.insert(pos.col, crate::cell::Cell::default()); if wide { row.get_mut(pos.col).unwrap().set_wide_continuation(true); } } row.truncate(size.cols); } pub fn delete_cells(&mut self, count: u16) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for _ in 0..(count.min(size.cols - pos.col)) { row.remove(pos.col); } row.resize(size.cols, crate::cell::Cell::default()); } pub fn erase_cells(&mut self, count: u16, attrs: crate::attrs::Attrs) { let size = self.size; let pos = self.pos; let row = self.current_row_mut(); for col in pos.col..((pos.col.saturating_add(count)).min(size.cols)) { row.erase(col, attrs); } } pub fn insert_lines(&mut self, count: u16) { for _ in 0..count { self.rows.remove(usize::from(self.scroll_bottom)); self.rows.insert(usize::from(self.pos.row), self.new_row()); // self.scroll_bottom is maintained to always be a valid row self.rows[usize::from(self.scroll_bottom)].wrap(false); } } pub fn delete_lines(&mut self, count: u16) { for _ in 0..(count.min(self.size.rows - self.pos.row)) { self.rows .insert(usize::from(self.scroll_bottom) + 1, self.new_row()); self.rows.remove(usize::from(self.pos.row)); } } pub fn scroll_up(&mut self, count: u16) { for _ in 0..(count.min(self.size.rows - self.scroll_top)) { self.rows .insert(usize::from(self.scroll_bottom) + 1, self.new_row()); let removed = self.rows.remove(usize::from(self.scroll_top)); if self.scrollback_len > 0 && !self.scroll_region_active() { self.scrollback.push_back(removed); while self.scrollback.len() > self.scrollback_len { self.scrollback.pop_front(); } if self.scrollback_offset > 0 { self.scrollback_offset = self.scrollback.len().min(self.scrollback_offset + 1); } } } } pub fn scroll_down(&mut self, count: u16) { for _ in 0..count { self.rows.remove(usize::from(self.scroll_bottom)); self.rows .insert(usize::from(self.scroll_top), self.new_row()); // self.scroll_bottom is maintained to always be a valid row self.rows[usize::from(self.scroll_bottom)].wrap(false); } } pub fn set_scroll_region(&mut self, top: u16, bottom: u16) { let bottom = bottom.min(self.size().rows - 1); if top < bottom { self.scroll_top = top; self.scroll_bottom = bottom; } else { self.scroll_top = 0; self.scroll_bottom = self.size().rows - 1; } self.pos.row = self.scroll_top; self.pos.col = 0; } fn in_scroll_region(&self) -> bool { self.pos.row >= self.scroll_top && self.pos.row <= self.scroll_bottom } fn scroll_region_active(&self) -> bool { self.scroll_top != 0 || self.scroll_bottom != self.size.rows - 1 } pub fn set_origin_mode(&mut self, mode: bool) { self.origin_mode = mode; self.set_pos(Pos { row: 0, col: 0 }); } pub fn row_inc_clamp(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_add(count); self.row_clamp_bottom(in_scroll_region); } pub fn row_inc_scroll(&mut self, count: u16) -> u16 { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_add(count); let lines = self.row_clamp_bottom(in_scroll_region); if in_scroll_region { self.scroll_up(lines); lines } else { 0 } } pub fn row_dec_clamp(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); self.pos.row = self.pos.row.saturating_sub(count); self.row_clamp_top(in_scroll_region); } pub fn row_dec_scroll(&mut self, count: u16) { let in_scroll_region = self.in_scroll_region(); // need to account for clamping by both row_clamp_top and by // saturating_sub let extra_lines = if count > self.pos.row { count - self.pos.row } else { 0 }; self.pos.row = self.pos.row.saturating_sub(count); let lines = self.row_clamp_top(in_scroll_region); self.scroll_down(lines + extra_lines); } pub fn row_set(&mut self, i: u16) { self.pos.row = i; self.row_clamp(); } pub fn col_inc(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_add(count); } pub fn col_inc_clamp(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_add(count); self.col_clamp(); } pub fn col_dec(&mut self, count: u16) { self.pos.col = self.pos.col.saturating_sub(count); } pub fn col_tab(&mut self) { self.pos.col -= self.pos.col % 8; self.pos.col += 8; self.col_clamp(); } pub fn col_set(&mut self, i: u16) { self.pos.col = i; self.col_clamp(); } pub fn col_wrap(&mut self, width: u16, wrap: bool) { if self.pos.col > self.size.cols - width { let mut prev_pos = self.pos; self.pos.col = 0; let scrolled = self.row_inc_scroll(1); prev_pos.row -= scrolled; let new_pos = self.pos; self.drawing_row_mut(prev_pos.row) // we assume self.pos.row is always valid, and so prev_pos.row // must be valid because it is always less than or equal to // self.pos.row .unwrap() .wrap(wrap && prev_pos.row + 1 == new_pos.row); } } fn row_clamp_top(&mut self, limit_to_scroll_region: bool) -> u16 { if limit_to_scroll_region && self.pos.row < self.scroll_top { let rows = self.scroll_top - self.pos.row; self.pos.row = self.scroll_top; rows } else { 0 } } fn row_clamp_bottom(&mut self, limit_to_scroll_region: bool) -> u16 { let bottom = if limit_to_scroll_region { self.scroll_bottom } else { self.size.rows - 1 }; if self.pos.row > bottom { let rows = self.pos.row - bottom; self.pos.row = bottom; rows } else { 0 } } fn row_clamp(&mut self) { if self.pos.row > self.size.rows - 1 { self.pos.row = self.size.rows - 1; } } fn col_clamp(&mut self) { if self.pos.col > self.size.cols - 1 { self.pos.col = self.size.cols - 1; } } } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Size { pub rows: u16, pub cols: u16, } #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct Pos { pub row: u16, pub col: u16, } vt100-0.15.2/src/lib.rs000064400000000000000000000033611046102023000124710ustar 00000000000000//! This crate parses a terminal byte stream and provides an in-memory //! representation of the rendered contents. //! //! # Overview //! //! This is essentially the terminal parser component of a graphical terminal //! emulator pulled out into a separate crate. Although you can use this crate //! to build a graphical terminal emulator, it also contains functionality //! necessary for implementing terminal applications that want to run other //! terminal applications - programs like `screen` or `tmux` for example. //! //! # Synopsis //! //! ``` //! let mut parser = vt100::Parser::new(24, 80, 0); //! //! let screen = parser.screen().clone(); //! parser.process(b"this text is \x1b[31mRED\x1b[m"); //! assert_eq!( //! parser.screen().cell(0, 13).unwrap().fgcolor(), //! vt100::Color::Idx(1), //! ); //! //! let screen = parser.screen().clone(); //! parser.process(b"\x1b[3D\x1b[32mGREEN"); //! assert_eq!( //! parser.screen().contents_formatted(), //! &b"\x1b[?25h\x1b[m\x1b[H\x1b[Jthis text is \x1b[32mGREEN"[..], //! ); //! assert_eq!( //! parser.screen().contents_diff(&screen), //! &b"\x1b[1;14H\x1b[32mGREEN"[..], //! ); //! ``` #![warn(clippy::cargo)] #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![warn(clippy::as_conversions)] #![warn(clippy::get_unwrap)] #![allow(clippy::cognitive_complexity)] #![allow(clippy::missing_const_for_fn)] #![allow(clippy::similar_names)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_lines)] #![allow(clippy::type_complexity)] mod attrs; mod cell; mod grid; mod parser; mod row; mod screen; mod term; pub use attrs::Color; pub use cell::Cell; pub use parser::Parser; pub use screen::{MouseProtocolEncoding, MouseProtocolMode, Screen}; vt100-0.15.2/src/parser.rs000064400000000000000000000043101046102023000132120ustar 00000000000000/// A parser for terminal output which produces an in-memory representation of /// the terminal contents. pub struct Parser { parser: vte::Parser, screen: crate::screen::Screen, } impl Parser { /// Creates a new terminal parser of the given size and with the given /// amount of scrollback. #[must_use] pub fn new(rows: u16, cols: u16, scrollback_len: usize) -> Self { Self { parser: vte::Parser::new(), screen: crate::screen::Screen::new( crate::grid::Size { rows, cols }, scrollback_len, ), } } /// Processes the contents of the given byte string, and updates the /// in-memory terminal state. pub fn process(&mut self, bytes: &[u8]) { for byte in bytes { self.parser.advance(&mut self.screen, *byte); } } /// Resizes the terminal. pub fn set_size(&mut self, rows: u16, cols: u16) { self.screen.set_size(rows, cols); } /// Scrolls to the given position in the scrollback. /// /// This position indicates the offset from the top of the screen, and /// should be `0` to put the normal screen in view. /// /// This affects the return values of methods called on `parser.screen()`: /// for instance, `parser.screen().cell(0, 0)` will return the top left /// corner of the screen after taking the scrollback offset into account. /// It does not affect `parser.process()` at all. /// /// The value given will be clamped to the actual size of the scrollback. pub fn set_scrollback(&mut self, rows: usize) { self.screen.set_scrollback(rows); } /// Returns a reference to a `Screen` object containing the terminal /// state. #[must_use] pub fn screen(&self) -> &crate::screen::Screen { &self.screen } } impl Default for Parser { /// Returns a parser with dimensions 80x24 and no scrollback. fn default() -> Self { Self::new(24, 80, 0) } } impl std::io::Write for Parser { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.process(buf); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { Ok(()) } } vt100-0.15.2/src/row.rs000064400000000000000000000407201046102023000125320ustar 00000000000000use crate::term::BufWrite as _; #[derive(Clone, Debug)] pub struct Row { cells: Vec, wrapped: bool, } impl Row { pub fn new(cols: u16) -> Self { Self { cells: vec![crate::cell::Cell::default(); usize::from(cols)], wrapped: false, } } fn cols(&self) -> u16 { self.cells .len() .try_into() // we limit the number of cols to a u16 (see Size) .unwrap() } pub fn clear(&mut self, attrs: crate::attrs::Attrs) { for cell in &mut self.cells { cell.clear(attrs); } self.wrapped = false; } fn cells(&self) -> impl Iterator { self.cells.iter() } pub fn get(&self, col: u16) -> Option<&crate::cell::Cell> { self.cells.get(usize::from(col)) } pub fn get_mut(&mut self, col: u16) -> Option<&mut crate::cell::Cell> { self.cells.get_mut(usize::from(col)) } pub fn insert(&mut self, i: u16, cell: crate::cell::Cell) { self.cells.insert(usize::from(i), cell); self.wrapped = false; } pub fn remove(&mut self, i: u16) { self.clear_wide(i); self.cells.remove(usize::from(i)); self.wrapped = false; } pub fn erase(&mut self, i: u16, attrs: crate::attrs::Attrs) { let wide = self.cells[usize::from(i)].is_wide(); self.clear_wide(i); self.cells[usize::from(i)].clear(attrs); if i == self.cols() - if wide { 2 } else { 1 } { self.wrapped = false; } } pub fn truncate(&mut self, len: u16) { self.cells.truncate(usize::from(len)); self.wrapped = false; let last_cell = &mut self.cells[usize::from(len) - 1]; if last_cell.is_wide() { last_cell.clear(*last_cell.attrs()); } } pub fn resize(&mut self, len: u16, cell: crate::cell::Cell) { self.cells.resize(usize::from(len), cell); self.wrapped = false; } pub fn wrap(&mut self, wrap: bool) { self.wrapped = wrap; } pub fn wrapped(&self) -> bool { self.wrapped } pub fn clear_wide(&mut self, col: u16) { let cell = &self.cells[usize::from(col)]; let other = if cell.is_wide() { &mut self.cells[usize::from(col + 1)] } else if cell.is_wide_continuation() { &mut self.cells[usize::from(col - 1)] } else { return; }; other.clear(*other.attrs()); } pub fn write_contents( &self, contents: &mut String, start: u16, width: u16, wrapping: bool, ) { let mut prev_was_wide = false; let mut prev_col = start; for (col, cell) in self .cells() .enumerate() .skip(usize::from(start)) .take(usize::from(width)) { if prev_was_wide { prev_was_wide = false; continue; } prev_was_wide = cell.is_wide(); // we limit the number of cols to a u16 (see Size) let col: u16 = col.try_into().unwrap(); if cell.has_contents() { for _ in 0..(col - prev_col) { contents.push(' '); } prev_col += col - prev_col; contents.push_str(&cell.contents()); prev_col += if cell.is_wide() { 2 } else { 1 }; } } if prev_col == start && wrapping { contents.push('\n'); } } pub fn write_contents_formatted( &self, contents: &mut Vec, start: u16, width: u16, row: u16, wrapping: bool, prev_pos: Option, prev_attrs: Option, ) -> (crate::grid::Pos, crate::attrs::Attrs) { let mut prev_was_wide = false; let default_cell = crate::cell::Cell::default(); let mut prev_pos = prev_pos.unwrap_or_else(|| { if wrapping { crate::grid::Pos { row: row - 1, col: self.cols(), } } else { crate::grid::Pos { row, col: start } } }); let mut prev_attrs = prev_attrs.unwrap_or_default(); let first_cell = &self.cells[usize::from(start)]; if wrapping && first_cell == &default_cell { let default_attrs = default_cell.attrs(); if &prev_attrs != default_attrs { default_attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *default_attrs; } contents.push(b' '); crate::term::Backspace::default().write_buf(contents); crate::term::EraseChar::new(1).write_buf(contents); prev_pos = crate::grid::Pos { row, col: 0 }; } let mut erase: Option<(u16, &crate::attrs::Attrs)> = None; for (col, cell) in self .cells() .enumerate() .skip(usize::from(start)) .take(usize::from(width)) { if prev_was_wide { prev_was_wide = false; continue; } prev_was_wide = cell.is_wide(); // we limit the number of cols to a u16 (see Size) let col: u16 = col.try_into().unwrap(); let pos = crate::grid::Pos { row, col }; if let Some((prev_col, attrs)) = erase { if cell.has_contents() || cell.attrs() != attrs { let new_pos = crate::grid::Pos { row, col: prev_col }; if wrapping && prev_pos.row + 1 == new_pos.row && prev_pos.col >= self.cols() { if new_pos.col > 0 { contents.extend( " ".repeat(usize::from(new_pos.col)) .as_bytes(), ); } else { contents.extend(b" "); crate::term::Backspace::default() .write_buf(contents); } } else { crate::term::MoveFromTo::new(prev_pos, new_pos) .write_buf(contents); } prev_pos = new_pos; if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } crate::term::EraseChar::new(pos.col - prev_col) .write_buf(contents); erase = None; } } if cell != &default_cell { let attrs = cell.attrs(); if cell.has_contents() { if pos != prev_pos { if !wrapping || prev_pos.row + 1 != pos.row || prev_pos.col < self.cols() - u16::from(cell.is_wide()) || pos.col != 0 { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); } prev_pos = pos; } if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } prev_pos.col += if cell.is_wide() { 2 } else { 1 }; let cell_contents = cell.contents(); contents.extend(cell_contents.as_bytes()); } else if erase.is_none() { erase = Some((pos.col, attrs)); } } } if let Some((prev_col, attrs)) = erase { let new_pos = crate::grid::Pos { row, col: prev_col }; if wrapping && prev_pos.row + 1 == new_pos.row && prev_pos.col >= self.cols() { if new_pos.col > 0 { contents.extend( " ".repeat(usize::from(new_pos.col)).as_bytes(), ); } else { contents.extend(b" "); crate::term::Backspace::default().write_buf(contents); } } else { crate::term::MoveFromTo::new(prev_pos, new_pos) .write_buf(contents); } prev_pos = new_pos; if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } crate::term::ClearRowForward::default().write_buf(contents); } (prev_pos, prev_attrs) } // while it's true that most of the logic in this is identical to // write_contents_formatted, i can't figure out how to break out the // common parts without making things noticeably slower. pub fn write_contents_diff( &self, contents: &mut Vec, prev: &Self, start: u16, width: u16, row: u16, wrapping: bool, prev_wrapping: bool, mut prev_pos: crate::grid::Pos, mut prev_attrs: crate::attrs::Attrs, ) -> (crate::grid::Pos, crate::attrs::Attrs) { let mut prev_was_wide = false; let first_cell = &self.cells[usize::from(start)]; let prev_first_cell = &prev.cells[usize::from(start)]; if wrapping && !prev_wrapping && first_cell == prev_first_cell && prev_pos.row + 1 == row && prev_pos.col >= self.cols() - u16::from(prev_first_cell.is_wide()) { let first_cell_attrs = first_cell.attrs(); if &prev_attrs != first_cell_attrs { first_cell_attrs .write_escape_code_diff(contents, &prev_attrs); prev_attrs = *first_cell_attrs; } let mut cell_contents = prev_first_cell.contents(); let need_erase = if cell_contents.is_empty() { cell_contents = " ".to_string(); true } else { false }; contents.extend(cell_contents.as_bytes()); crate::term::Backspace::default().write_buf(contents); if prev_first_cell.is_wide() { crate::term::Backspace::default().write_buf(contents); } if need_erase { crate::term::EraseChar::new(1).write_buf(contents); } prev_pos = crate::grid::Pos { row, col: 0 }; } let mut erase: Option<(u16, &crate::attrs::Attrs)> = None; for (col, (cell, prev_cell)) in self .cells() .zip(prev.cells()) .enumerate() .skip(usize::from(start)) .take(usize::from(width)) { if prev_was_wide { prev_was_wide = false; continue; } prev_was_wide = cell.is_wide(); // we limit the number of cols to a u16 (see Size) let col: u16 = col.try_into().unwrap(); let pos = crate::grid::Pos { row, col }; if let Some((prev_col, attrs)) = erase { if cell.has_contents() || cell.attrs() != attrs { let new_pos = crate::grid::Pos { row, col: prev_col }; if wrapping && prev_pos.row + 1 == new_pos.row && prev_pos.col >= self.cols() { if new_pos.col > 0 { contents.extend( " ".repeat(usize::from(new_pos.col)) .as_bytes(), ); } else { contents.extend(b" "); crate::term::Backspace::default() .write_buf(contents); } } else { crate::term::MoveFromTo::new(prev_pos, new_pos) .write_buf(contents); } prev_pos = new_pos; if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } crate::term::EraseChar::new(pos.col - prev_col) .write_buf(contents); erase = None; } } if cell != prev_cell { let attrs = cell.attrs(); if cell.has_contents() { if pos != prev_pos { if !wrapping || prev_pos.row + 1 != pos.row || prev_pos.col < self.cols() - u16::from(cell.is_wide()) || pos.col != 0 { crate::term::MoveFromTo::new(prev_pos, pos) .write_buf(contents); } prev_pos = pos; } if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } prev_pos.col += if cell.is_wide() { 2 } else { 1 }; contents.extend(cell.contents().as_bytes()); } else if erase.is_none() { erase = Some((pos.col, attrs)); } } } if let Some((prev_col, attrs)) = erase { let new_pos = crate::grid::Pos { row, col: prev_col }; if wrapping && prev_pos.row + 1 == new_pos.row && prev_pos.col >= self.cols() { if new_pos.col > 0 { contents.extend( " ".repeat(usize::from(new_pos.col)).as_bytes(), ); } else { contents.extend(b" "); crate::term::Backspace::default().write_buf(contents); } } else { crate::term::MoveFromTo::new(prev_pos, new_pos) .write_buf(contents); } prev_pos = new_pos; if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } crate::term::ClearRowForward::default().write_buf(contents); } // if this row is going from wrapped to not wrapped, we need to erase // and redraw the last character to break wrapping. if this row is // wrapped, we need to redraw the last character without erasing it to // position the cursor after the end of the line correctly so that // drawing the next line can just start writing and be wrapped. if (!self.wrapped && prev.wrapped) || (!prev.wrapped && self.wrapped) { let end_pos = if self.cells[usize::from(self.cols() - 1)] .is_wide_continuation() { crate::grid::Pos { row, col: self.cols() - 2, } } else { crate::grid::Pos { row, col: self.cols() - 1, } }; crate::term::MoveFromTo::new(prev_pos, end_pos) .write_buf(contents); prev_pos = end_pos; if !self.wrapped { crate::term::EraseChar::new(1).write_buf(contents); } let end_cell = &self.cells[usize::from(end_pos.col)]; if end_cell.has_contents() { let attrs = end_cell.attrs(); if &prev_attrs != attrs { attrs.write_escape_code_diff(contents, &prev_attrs); prev_attrs = *attrs; } contents.extend(end_cell.contents().as_bytes()); prev_pos.col += if end_cell.is_wide() { 2 } else { 1 }; } } (prev_pos, prev_attrs) } } vt100-0.15.2/src/screen.rs000064400000000000000000001676431046102023000132200ustar 00000000000000use crate::term::BufWrite as _; use unicode_width::UnicodeWidthChar as _; const MODE_APPLICATION_KEYPAD: u8 = 0b0000_0001; const MODE_APPLICATION_CURSOR: u8 = 0b0000_0010; const MODE_HIDE_CURSOR: u8 = 0b0000_0100; const MODE_ALTERNATE_SCREEN: u8 = 0b0000_1000; const MODE_BRACKETED_PASTE: u8 = 0b0001_0000; /// The xterm mouse handling mode currently in use. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum MouseProtocolMode { /// Mouse handling is disabled. None, /// Mouse button events should be reported on button press. Also known as /// X10 mouse mode. Press, /// Mouse button events should be reported on button press and release. /// Also known as VT200 mouse mode. PressRelease, // Highlight, /// Mouse button events should be reported on button press and release, as /// well as when the mouse moves between cells while a button is held /// down. ButtonMotion, /// Mouse button events should be reported on button press and release, /// and mouse motion events should be reported when the mouse moves /// between cells regardless of whether a button is held down or not. AnyMotion, // DecLocator, } impl Default for MouseProtocolMode { fn default() -> Self { Self::None } } /// The encoding to use for the enabled `MouseProtocolMode`. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum MouseProtocolEncoding { /// Default single-printable-byte encoding. Default, /// UTF-8-based encoding. Utf8, /// SGR-like encoding. Sgr, // Urxvt, } impl Default for MouseProtocolEncoding { fn default() -> Self { Self::Default } } /// Represents the overall terminal state. #[derive(Clone, Debug)] pub struct Screen { grid: crate::grid::Grid, alternate_grid: crate::grid::Grid, attrs: crate::attrs::Attrs, saved_attrs: crate::attrs::Attrs, title: String, icon_name: String, modes: u8, mouse_protocol_mode: MouseProtocolMode, mouse_protocol_encoding: MouseProtocolEncoding, audible_bell_count: usize, visual_bell_count: usize, errors: usize, } impl Screen { pub(crate) fn new( size: crate::grid::Size, scrollback_len: usize, ) -> Self { let mut grid = crate::grid::Grid::new(size, scrollback_len); grid.allocate_rows(); Self { grid, alternate_grid: crate::grid::Grid::new(size, 0), attrs: crate::attrs::Attrs::default(), saved_attrs: crate::attrs::Attrs::default(), title: String::default(), icon_name: String::default(), modes: 0, mouse_protocol_mode: MouseProtocolMode::default(), mouse_protocol_encoding: MouseProtocolEncoding::default(), audible_bell_count: 0, visual_bell_count: 0, errors: 0, } } pub(crate) fn set_size(&mut self, rows: u16, cols: u16) { self.grid.set_size(crate::grid::Size { rows, cols }); self.alternate_grid .set_size(crate::grid::Size { rows, cols }); } /// Returns the current size of the terminal. /// /// The return value will be (rows, cols). #[must_use] pub fn size(&self) -> (u16, u16) { let size = self.grid().size(); (size.rows, size.cols) } /// Returns the current position in the scrollback. /// /// This position indicates the offset from the top of the screen, and is /// `0` when the normal screen is in view. #[must_use] pub fn scrollback(&self) -> usize { self.grid().scrollback() } pub(crate) fn set_scrollback(&mut self, rows: usize) { self.grid_mut().set_scrollback(rows); } /// Returns the text contents of the terminal. /// /// This will not include any formatting information, and will be in plain /// text format. #[must_use] pub fn contents(&self) -> String { let mut contents = String::new(); self.write_contents(&mut contents); contents } fn write_contents(&self, contents: &mut String) { self.grid().write_contents(contents); } /// Returns the text contents of the terminal by row, restricted to the /// given subset of columns. /// /// This will not include any formatting information, and will be in plain /// text format. /// /// Newlines will not be included. pub fn rows( &self, start: u16, width: u16, ) -> impl Iterator + '_ { self.grid().visible_rows().map(move |row| { let mut contents = String::new(); row.write_contents(&mut contents, start, width, false); contents }) } /// Returns the text contents of the terminal logically between two cells. /// This will include the remainder of the starting row after `start_col`, /// followed by the entire contents of the rows between `start_row` and /// `end_row`, followed by the beginning of the `end_row` up until /// `end_col`. This is useful for things like determining the contents of /// a clipboard selection. #[must_use] pub fn contents_between( &self, start_row: u16, start_col: u16, end_row: u16, end_col: u16, ) -> String { match start_row.cmp(&end_row) { std::cmp::Ordering::Less => { let (_, cols) = self.size(); let mut contents = String::new(); for (i, row) in self .grid() .visible_rows() .enumerate() .skip(usize::from(start_row)) .take(usize::from(end_row) - usize::from(start_row) + 1) { if i == usize::from(start_row) { row.write_contents( &mut contents, start_col, cols - start_col, false, ); if !row.wrapped() { contents.push('\n'); } } else if i == usize::from(end_row) { row.write_contents(&mut contents, 0, end_col, false); } else { row.write_contents(&mut contents, 0, cols, false); if !row.wrapped() { contents.push('\n'); } } } contents } std::cmp::Ordering::Equal => { if start_col < end_col { self.rows(start_col, end_col - start_col) .nth(usize::from(start_row)) .unwrap_or_default() } else { String::new() } } std::cmp::Ordering::Greater => String::new(), } } /// Return escape codes sufficient to reproduce the entire contents of the /// current terminal state. This is a convenience wrapper around /// `contents_formatted`, `input_mode_formatted`, and `title_formatted`. #[must_use] pub fn state_formatted(&self) -> Vec { let mut contents = vec![]; self.write_contents_formatted(&mut contents); self.write_input_mode_formatted(&mut contents); self.write_title_formatted(&mut contents); contents } /// Return escape codes sufficient to turn the terminal state of the /// screen `prev` into the current terminal state. This is a convenience /// wrapper around `contents_diff`, `input_mode_diff`, `title_diff`, and /// `bells_diff`. #[must_use] pub fn state_diff(&self, prev: &Self) -> Vec { let mut contents = vec![]; self.write_contents_diff(&mut contents, prev); self.write_input_mode_diff(&mut contents, prev); self.write_title_diff(&mut contents, prev); self.write_bells_diff(&mut contents, prev); contents } /// Returns the formatted visible contents of the terminal. /// /// Formatting information will be included inline as terminal escape /// codes. The result will be suitable for feeding directly to a raw /// terminal parser, and will result in the same visual output. #[must_use] pub fn contents_formatted(&self) -> Vec { let mut contents = vec![]; self.write_contents_formatted(&mut contents); contents } fn write_contents_formatted(&self, contents: &mut Vec) { crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents); let prev_attrs = self.grid().write_contents_formatted(contents); self.attrs.write_escape_code_diff(contents, &prev_attrs); } /// Returns the formatted visible contents of the terminal by row, /// restricted to the given subset of columns. /// /// Formatting information will be included inline as terminal escape /// codes. The result will be suitable for feeding directly to a raw /// terminal parser, and will result in the same visual output. /// /// You are responsible for positioning the cursor before printing each /// row, and the final cursor position after displaying each row is /// unspecified. // the unwraps in this method shouldn't be reachable #[allow(clippy::missing_panics_doc)] pub fn rows_formatted( &self, start: u16, width: u16, ) -> impl Iterator> + '_ { let mut wrapping = false; self.grid().visible_rows().enumerate().map(move |(i, row)| { // number of rows in a grid is stored in a u16 (see Size), so // visible_rows can never return enough rows to overflow here let i = i.try_into().unwrap(); let mut contents = vec![]; row.write_contents_formatted( &mut contents, start, width, i, wrapping, None, None, ); if start == 0 && width == self.grid.size().cols { wrapping = row.wrapped(); } contents }) } /// Returns a terminal byte stream sufficient to turn the visible contents /// of the screen described by `prev` into the visible contents of the /// screen described by `self`. /// /// The result of rendering `prev.contents_formatted()` followed by /// `self.contents_diff(prev)` should be equivalent to the result of /// rendering `self.contents_formatted()`. This is primarily useful when /// you already have a terminal parser whose state is described by `prev`, /// since the diff will likely require less memory and cause less /// flickering than redrawing the entire screen contents. #[must_use] pub fn contents_diff(&self, prev: &Self) -> Vec { let mut contents = vec![]; self.write_contents_diff(&mut contents, prev); contents } fn write_contents_diff(&self, contents: &mut Vec, prev: &Self) { if self.hide_cursor() != prev.hide_cursor() { crate::term::HideCursor::new(self.hide_cursor()) .write_buf(contents); } let prev_attrs = self.grid().write_contents_diff( contents, prev.grid(), prev.attrs, ); self.attrs.write_escape_code_diff(contents, &prev_attrs); } /// Returns a sequence of terminal byte streams sufficient to turn the /// visible contents of the subset of each row from `prev` (as described /// by `start` and `width`) into the visible contents of the corresponding /// row subset in `self`. /// /// You are responsible for positioning the cursor before printing each /// row, and the final cursor position after displaying each row is /// unspecified. // the unwraps in this method shouldn't be reachable #[allow(clippy::missing_panics_doc)] pub fn rows_diff<'a>( &'a self, prev: &'a Self, start: u16, width: u16, ) -> impl Iterator> + 'a { self.grid() .visible_rows() .zip(prev.grid().visible_rows()) .enumerate() .map(move |(i, (row, prev_row))| { // number of rows in a grid is stored in a u16 (see Size), so // visible_rows can never return enough rows to overflow here let i = i.try_into().unwrap(); let mut contents = vec![]; row.write_contents_diff( &mut contents, prev_row, start, width, i, false, false, crate::grid::Pos { row: i, col: start }, crate::attrs::Attrs::default(), ); contents }) } /// Returns terminal escape sequences sufficient to set the current /// terminal's input modes. /// /// Supported modes are: /// * application keypad /// * application cursor /// * bracketed paste /// * xterm mouse support #[must_use] pub fn input_mode_formatted(&self) -> Vec { let mut contents = vec![]; self.write_input_mode_formatted(&mut contents); contents } fn write_input_mode_formatted(&self, contents: &mut Vec) { crate::term::ApplicationKeypad::new( self.mode(MODE_APPLICATION_KEYPAD), ) .write_buf(contents); crate::term::ApplicationCursor::new( self.mode(MODE_APPLICATION_CURSOR), ) .write_buf(contents); crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE)) .write_buf(contents); crate::term::MouseProtocolMode::new( self.mouse_protocol_mode, MouseProtocolMode::None, ) .write_buf(contents); crate::term::MouseProtocolEncoding::new( self.mouse_protocol_encoding, MouseProtocolEncoding::Default, ) .write_buf(contents); } /// Returns terminal escape sequences sufficient to change the previous /// terminal's input modes to the input modes enabled in the current /// terminal. #[must_use] pub fn input_mode_diff(&self, prev: &Self) -> Vec { let mut contents = vec![]; self.write_input_mode_diff(&mut contents, prev); contents } fn write_input_mode_diff(&self, contents: &mut Vec, prev: &Self) { if self.mode(MODE_APPLICATION_KEYPAD) != prev.mode(MODE_APPLICATION_KEYPAD) { crate::term::ApplicationKeypad::new( self.mode(MODE_APPLICATION_KEYPAD), ) .write_buf(contents); } if self.mode(MODE_APPLICATION_CURSOR) != prev.mode(MODE_APPLICATION_CURSOR) { crate::term::ApplicationCursor::new( self.mode(MODE_APPLICATION_CURSOR), ) .write_buf(contents); } if self.mode(MODE_BRACKETED_PASTE) != prev.mode(MODE_BRACKETED_PASTE) { crate::term::BracketedPaste::new(self.mode(MODE_BRACKETED_PASTE)) .write_buf(contents); } crate::term::MouseProtocolMode::new( self.mouse_protocol_mode, prev.mouse_protocol_mode, ) .write_buf(contents); crate::term::MouseProtocolEncoding::new( self.mouse_protocol_encoding, prev.mouse_protocol_encoding, ) .write_buf(contents); } /// Returns terminal escape sequences sufficient to set the current /// terminal's window title. #[must_use] pub fn title_formatted(&self) -> Vec { let mut contents = vec![]; self.write_title_formatted(&mut contents); contents } fn write_title_formatted(&self, contents: &mut Vec) { crate::term::ChangeTitle::new(&self.icon_name, &self.title, "", "") .write_buf(contents); } /// Returns terminal escape sequences sufficient to change the previous /// terminal's window title to the window title set in the current /// terminal. #[must_use] pub fn title_diff(&self, prev: &Self) -> Vec { let mut contents = vec![]; self.write_title_diff(&mut contents, prev); contents } fn write_title_diff(&self, contents: &mut Vec, prev: &Self) { crate::term::ChangeTitle::new( &self.icon_name, &self.title, &prev.icon_name, &prev.title, ) .write_buf(contents); } /// Returns terminal escape sequences sufficient to cause audible and /// visual bells to occur if they have been received since the terminal /// described by `prev`. #[must_use] pub fn bells_diff(&self, prev: &Self) -> Vec { let mut contents = vec![]; self.write_bells_diff(&mut contents, prev); contents } fn write_bells_diff(&self, contents: &mut Vec, prev: &Self) { if self.audible_bell_count != prev.audible_bell_count { crate::term::AudibleBell::default().write_buf(contents); } if self.visual_bell_count != prev.visual_bell_count { crate::term::VisualBell::default().write_buf(contents); } } /// Returns terminal escape sequences sufficient to set the current /// terminal's drawing attributes. /// /// Supported drawing attributes are: /// * fgcolor /// * bgcolor /// * bold /// * italic /// * underline /// * inverse /// /// This is not typically necessary, since `contents_formatted` will leave /// the current active drawing attributes in the correct state, but this /// can be useful in the case of drawing additional things on top of a /// terminal output, since you will need to restore the terminal state /// without the terminal contents necessarily being the same. #[must_use] pub fn attributes_formatted(&self) -> Vec { let mut contents = vec![]; self.write_attributes_formatted(&mut contents); contents } fn write_attributes_formatted(&self, contents: &mut Vec) { crate::term::ClearAttrs::default().write_buf(contents); self.attrs.write_escape_code_diff( contents, &crate::attrs::Attrs::default(), ); } /// Returns the current cursor position of the terminal. /// /// The return value will be (row, col). #[must_use] pub fn cursor_position(&self) -> (u16, u16) { let pos = self.grid().pos(); (pos.row, pos.col) } /// Returns terminal escape sequences sufficient to set the current /// cursor state of the terminal. /// /// This is not typically necessary, since `contents_formatted` will leave /// the cursor in the correct state, but this can be useful in the case of /// drawing additional things on top of a terminal output, since you will /// need to restore the terminal state without the terminal contents /// necessarily being the same. /// /// Note that the bytes returned by this function may alter the active /// drawing attributes, because it may require redrawing existing cells in /// order to position the cursor correctly (for instance, in the case /// where the cursor is past the end of a row). Therefore, you should /// ensure to reset the active drawing attributes if necessary after /// processing this data, for instance by using `attributes_formatted`. #[must_use] pub fn cursor_state_formatted(&self) -> Vec { let mut contents = vec![]; self.write_cursor_state_formatted(&mut contents); contents } fn write_cursor_state_formatted(&self, contents: &mut Vec) { crate::term::HideCursor::new(self.hide_cursor()).write_buf(contents); self.grid() .write_cursor_position_formatted(contents, None, None); // we don't just call write_attributes_formatted here, because that // would still be confusing - consider the case where the user sets // their own unrelated drawing attributes (on a different parser // instance) and then calls cursor_state_formatted. just documenting // it and letting the user handle it on their own is more // straightforward. } /// Returns the `Cell` object at the given location in the terminal, if it /// exists. #[must_use] pub fn cell(&self, row: u16, col: u16) -> Option<&crate::cell::Cell> { self.grid().visible_cell(crate::grid::Pos { row, col }) } /// Returns whether the text in row `row` should wrap to the next line. #[must_use] pub fn row_wrapped(&self, row: u16) -> bool { self.grid() .visible_row(row) .map_or(false, crate::row::Row::wrapped) } /// Returns the terminal's window title. #[must_use] pub fn title(&self) -> &str { &self.title } /// Returns the terminal's icon name. #[must_use] pub fn icon_name(&self) -> &str { &self.icon_name } /// Returns a value which changes every time an audible bell is received. /// /// Typically you would store this number after each call to `process`, /// and trigger an audible bell whenever it changes. /// /// You shouldn't rely on the exact value returned here, since the exact /// value will not be maintained by `contents_formatted` or /// `contents_diff`. #[must_use] pub fn audible_bell_count(&self) -> usize { self.audible_bell_count } /// Returns a value which changes every time an visual bell is received. /// /// Typically you would store this number after each call to `process`, /// and trigger an visual bell whenever it changes. /// /// You shouldn't rely on the exact value returned here, since the exact /// value will not be maintained by `contents_formatted` or /// `contents_diff`. #[must_use] pub fn visual_bell_count(&self) -> usize { self.visual_bell_count } /// Returns the number of parsing errors seen so far. /// /// Currently this only tracks invalid UTF-8 and control characters other /// than `0x07`-`0x0f`. This can give an idea of whether the input stream /// being fed to the parser is reasonable or not. #[must_use] pub fn errors(&self) -> usize { self.errors } /// Returns whether the alternate screen is currently in use. #[must_use] pub fn alternate_screen(&self) -> bool { self.mode(MODE_ALTERNATE_SCREEN) } /// Returns whether the terminal should be in application keypad mode. #[must_use] pub fn application_keypad(&self) -> bool { self.mode(MODE_APPLICATION_KEYPAD) } /// Returns whether the terminal should be in application cursor mode. #[must_use] pub fn application_cursor(&self) -> bool { self.mode(MODE_APPLICATION_CURSOR) } /// Returns whether the terminal should be in hide cursor mode. #[must_use] pub fn hide_cursor(&self) -> bool { self.mode(MODE_HIDE_CURSOR) } /// Returns whether the terminal should be in bracketed paste mode. #[must_use] pub fn bracketed_paste(&self) -> bool { self.mode(MODE_BRACKETED_PASTE) } /// Returns the currently active `MouseProtocolMode` #[must_use] pub fn mouse_protocol_mode(&self) -> MouseProtocolMode { self.mouse_protocol_mode } /// Returns the currently active `MouseProtocolEncoding` #[must_use] pub fn mouse_protocol_encoding(&self) -> MouseProtocolEncoding { self.mouse_protocol_encoding } /// Returns the currently active foreground color. #[must_use] pub fn fgcolor(&self) -> crate::attrs::Color { self.attrs.fgcolor } /// Returns the currently active background color. #[must_use] pub fn bgcolor(&self) -> crate::attrs::Color { self.attrs.bgcolor } /// Returns whether newly drawn text should be rendered with the bold text /// attribute. #[must_use] pub fn bold(&self) -> bool { self.attrs.bold() } /// Returns whether newly drawn text should be rendered with the italic /// text attribute. #[must_use] pub fn italic(&self) -> bool { self.attrs.italic() } /// Returns whether newly drawn text should be rendered with the /// underlined text attribute. #[must_use] pub fn underline(&self) -> bool { self.attrs.underline() } /// Returns whether newly drawn text should be rendered with the inverse /// text attribute. #[must_use] pub fn inverse(&self) -> bool { self.attrs.inverse() } fn grid(&self) -> &crate::grid::Grid { if self.mode(MODE_ALTERNATE_SCREEN) { &self.alternate_grid } else { &self.grid } } fn grid_mut(&mut self) -> &mut crate::grid::Grid { if self.mode(MODE_ALTERNATE_SCREEN) { &mut self.alternate_grid } else { &mut self.grid } } fn enter_alternate_grid(&mut self) { self.grid_mut().set_scrollback(0); self.set_mode(MODE_ALTERNATE_SCREEN); self.alternate_grid.allocate_rows(); } fn exit_alternate_grid(&mut self) { self.clear_mode(MODE_ALTERNATE_SCREEN); } fn save_cursor(&mut self) { self.grid_mut().save_cursor(); self.saved_attrs = self.attrs; } fn restore_cursor(&mut self) { self.grid_mut().restore_cursor(); self.attrs = self.saved_attrs; } fn set_mode(&mut self, mode: u8) { self.modes |= mode; } fn clear_mode(&mut self, mode: u8) { self.modes &= !mode; } fn mode(&self, mode: u8) -> bool { self.modes & mode != 0 } fn set_mouse_mode(&mut self, mode: MouseProtocolMode) { self.mouse_protocol_mode = mode; } fn clear_mouse_mode(&mut self, mode: MouseProtocolMode) { if self.mouse_protocol_mode == mode { self.mouse_protocol_mode = MouseProtocolMode::default(); } } fn set_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) { self.mouse_protocol_encoding = encoding; } fn clear_mouse_encoding(&mut self, encoding: MouseProtocolEncoding) { if self.mouse_protocol_encoding == encoding { self.mouse_protocol_encoding = MouseProtocolEncoding::default(); } } } impl Screen { fn text(&mut self, c: char) { let pos = self.grid().pos(); let size = self.grid().size(); let attrs = self.attrs; let width = c.width(); if width.is_none() && (u32::from(c)) < 256 { // don't even try to draw control characters return; } let width = width .unwrap_or(1) .try_into() // width() can only return 0, 1, or 2 .unwrap(); // it doesn't make any sense to wrap if the last column in a row // didn't already have contents. don't try to handle the case where a // character wraps because there was only one column left in the // previous row - literally everything handles this case differently, // and this is tmux behavior (and also the simplest). i'm open to // reconsidering this behavior, but only with a really good reason // (xterm handles this by introducing the concept of triple width // cells, which i really don't want to do). let mut wrap = false; if pos.col > size.cols - width { let last_cell = self .grid() .drawing_cell(crate::grid::Pos { row: pos.row, col: size.cols - 1, }) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always have a valid // row value. size.cols - 1 is also always a valid column. .unwrap(); if last_cell.has_contents() || last_cell.is_wide_continuation() { wrap = true; } } self.grid_mut().col_wrap(width, wrap); let pos = self.grid().pos(); if width == 0 { if pos.col > 0 { let mut prev_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col - 1, }) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always have a // valid row value. pos.col - 1 is valid because we just // checked for pos.col > 0. .unwrap(); if prev_cell.is_wide_continuation() { prev_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col - 2, }) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always have a // valid row value. we know pos.col - 2 is valid // because the cell at pos.col - 1 is a wide // continuation character, which means there must be // the first half of the wide character before it. .unwrap(); } prev_cell.append(c); } else if pos.row > 0 { let prev_row = self .grid() .drawing_row(pos.row - 1) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always have a // valid row value. pos.row - 1 is valid because we just // checked for pos.row > 0. .unwrap(); if prev_row.wrapped() { let mut prev_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row - 1, col: size.cols - 1, }) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always have a // valid row value. pos.row - 1 is valid because we // just checked for pos.row > 0. col of size.cols - 1 // is always valid. .unwrap(); if prev_cell.is_wide_continuation() { prev_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row - 1, col: size.cols - 2, }) // pos.row is valid, since it comes directly from // self.grid().pos() which we assume to always // have a valid row value. pos.row - 1 is valid // because we just checked for pos.row > 0. col of // size.cols - 2 is valid because the cell at // size.cols - 1 is a wide continuation character, // so it must have the first half of the wide // character before it. .unwrap(); } prev_cell.append(c); } } } else { if self .grid() .drawing_cell(pos) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because we // called col_wrap() immediately before this, which ensures // that self.grid().pos().col has a valid value. .unwrap() .is_wide_continuation() { let prev_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col - 1, }) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because // we called col_wrap() immediately before this, which // ensures that self.grid().pos().col has a valid value. // pos.col - 1 is valid because the cell at pos.col is a // wide continuation character, so it must have the first // half of the wide character before it. .unwrap(); prev_cell.clear(attrs); } if self .grid() .drawing_cell(pos) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because we // called col_wrap() immediately before this, which ensures // that self.grid().pos().col has a valid value. .unwrap() .is_wide() { let next_cell = self .grid_mut() .drawing_cell_mut(crate::grid::Pos { row: pos.row, col: pos.col + 1, }) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because // we called col_wrap() immediately before this, which // ensures that self.grid().pos().col has a valid value. // pos.col + 1 is valid because the cell at pos.col is a // wide character, so it must have the second half of the // wide character after it. .unwrap(); next_cell.set(' ', attrs); } let cell = self .grid_mut() .drawing_cell_mut(pos) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because we // called col_wrap() immediately before this, which ensures // that self.grid().pos().col has a valid value. .unwrap(); cell.set(c, attrs); self.grid_mut().col_inc(1); if width > 1 { let pos = self.grid().pos(); if self .grid() .drawing_cell(pos) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because // we called col_wrap() earlier, which ensures that // self.grid().pos().col has a valid value. this is true // even though we just called col_inc, because this branch // only happens if width > 1, and col_wrap takes width // into account. .unwrap() .is_wide() { let next_next_pos = crate::grid::Pos { row: pos.row, col: pos.col + 1, }; let next_next_cell = self .grid_mut() .drawing_cell_mut(next_next_pos) // pos.row is valid because we assume // self.grid().pos() to always have a valid row value. // pos.col is valid because we called col_wrap() // earlier, which ensures that self.grid().pos().col // has a valid value. this is true even though we just // called col_inc, because this branch only happens if // width > 1, and col_wrap takes width into account. // pos.col + 1 is valid because the cell at pos.col is // wide, and so it must have the second half of the // wide character after it. .unwrap(); next_next_cell.clear(attrs); if next_next_pos.col == size.cols - 1 { self.grid_mut() .drawing_row_mut(pos.row) // we assume self.grid().pos().row is always valid .unwrap() .wrap(false); } } let next_cell = self .grid_mut() .drawing_cell_mut(pos) // pos.row is valid because we assume self.grid().pos() to // always have a valid row value. pos.col is valid because // we called col_wrap() earlier, which ensures that // self.grid().pos().col has a valid value. this is true // even though we just called col_inc, because this branch // only happens if width > 1, and col_wrap takes width // into account. .unwrap(); next_cell.clear(crate::attrs::Attrs::default()); next_cell.set_wide_continuation(true); self.grid_mut().col_inc(1); } } } // control codes fn bel(&mut self) { self.audible_bell_count += 1; } fn bs(&mut self) { self.grid_mut().col_dec(1); } fn tab(&mut self) { self.grid_mut().col_tab(); } fn lf(&mut self) { self.grid_mut().row_inc_scroll(1); } fn vt(&mut self) { self.lf(); } fn ff(&mut self) { self.lf(); } fn cr(&mut self) { self.grid_mut().col_set(0); } // escape codes // ESC 7 fn decsc(&mut self) { self.save_cursor(); } // ESC 8 fn decrc(&mut self) { self.restore_cursor(); } // ESC = fn deckpam(&mut self) { self.set_mode(MODE_APPLICATION_KEYPAD); } // ESC > fn deckpnm(&mut self) { self.clear_mode(MODE_APPLICATION_KEYPAD); } // ESC M fn ri(&mut self) { self.grid_mut().row_dec_scroll(1); } // ESC c fn ris(&mut self) { let title = self.title.clone(); let icon_name = self.icon_name.clone(); let audible_bell_count = self.audible_bell_count; let visual_bell_count = self.visual_bell_count; let errors = self.errors; *self = Self::new(self.grid.size(), self.grid.scrollback_len()); self.title = title; self.icon_name = icon_name; self.audible_bell_count = audible_bell_count; self.visual_bell_count = visual_bell_count; self.errors = errors; } // ESC g fn vb(&mut self) { self.visual_bell_count += 1; } // csi codes // CSI @ fn ich(&mut self, count: u16) { self.grid_mut().insert_cells(count); } // CSI A fn cuu(&mut self, offset: u16) { self.grid_mut().row_dec_clamp(offset); } // CSI B fn cud(&mut self, offset: u16) { self.grid_mut().row_inc_clamp(offset); } // CSI C fn cuf(&mut self, offset: u16) { self.grid_mut().col_inc_clamp(offset); } // CSI D fn cub(&mut self, offset: u16) { self.grid_mut().col_dec(offset); } // CSI G fn cha(&mut self, col: u16) { self.grid_mut().col_set(col - 1); } // CSI H fn cup(&mut self, (row, col): (u16, u16)) { self.grid_mut().set_pos(crate::grid::Pos { row: row - 1, col: col - 1, }); } // CSI J fn ed(&mut self, mode: u16) { let attrs = self.attrs; match mode { 0 => self.grid_mut().erase_all_forward(attrs), 1 => self.grid_mut().erase_all_backward(attrs), 2 => self.grid_mut().erase_all(attrs), n => { log::debug!("unhandled ED mode: {n}"); } } } // CSI ? J fn decsed(&mut self, mode: u16) { self.ed(mode); } // CSI K fn el(&mut self, mode: u16) { let attrs = self.attrs; match mode { 0 => self.grid_mut().erase_row_forward(attrs), 1 => self.grid_mut().erase_row_backward(attrs), 2 => self.grid_mut().erase_row(attrs), n => { log::debug!("unhandled EL mode: {n}"); } } } // CSI ? K fn decsel(&mut self, mode: u16) { self.el(mode); } // CSI L fn il(&mut self, count: u16) { self.grid_mut().insert_lines(count); } // CSI M fn dl(&mut self, count: u16) { self.grid_mut().delete_lines(count); } // CSI P fn dch(&mut self, count: u16) { self.grid_mut().delete_cells(count); } // CSI S fn su(&mut self, count: u16) { self.grid_mut().scroll_up(count); } // CSI T fn sd(&mut self, count: u16) { self.grid_mut().scroll_down(count); } // CSI X fn ech(&mut self, count: u16) { let attrs = self.attrs; self.grid_mut().erase_cells(count, attrs); } // CSI d fn vpa(&mut self, row: u16) { self.grid_mut().row_set(row - 1); } // CSI h #[allow(clippy::unused_self)] fn sm(&mut self, params: &vte::Params) { // nothing, i think? if log::log_enabled!(log::Level::Debug) { log::debug!("unhandled SM mode: {}", param_str(params)); } } // CSI ? h fn decset(&mut self, params: &vte::Params) { for param in params { match param { &[1] => self.set_mode(MODE_APPLICATION_CURSOR), &[6] => self.grid_mut().set_origin_mode(true), &[9] => self.set_mouse_mode(MouseProtocolMode::Press), &[25] => self.clear_mode(MODE_HIDE_CURSOR), &[47] => self.enter_alternate_grid(), &[1000] => { self.set_mouse_mode(MouseProtocolMode::PressRelease); } &[1002] => { self.set_mouse_mode(MouseProtocolMode::ButtonMotion); } &[1003] => self.set_mouse_mode(MouseProtocolMode::AnyMotion), &[1005] => { self.set_mouse_encoding(MouseProtocolEncoding::Utf8); } &[1006] => { self.set_mouse_encoding(MouseProtocolEncoding::Sgr); } &[1049] => { self.decsc(); self.alternate_grid.clear(); self.enter_alternate_grid(); } &[2004] => self.set_mode(MODE_BRACKETED_PASTE), ns => { if log::log_enabled!(log::Level::Debug) { let n = if ns.len() == 1 { format!( "{}", // we just checked that ns.len() == 1, so 0 // must be valid ns[0] ) } else { format!("{ns:?}") }; log::debug!("unhandled DECSET mode: {n}"); } } } } } // CSI l #[allow(clippy::unused_self)] fn rm(&mut self, params: &vte::Params) { // nothing, i think? if log::log_enabled!(log::Level::Debug) { log::debug!("unhandled RM mode: {}", param_str(params)); } } // CSI ? l fn decrst(&mut self, params: &vte::Params) { for param in params { match param { &[1] => self.clear_mode(MODE_APPLICATION_CURSOR), &[6] => self.grid_mut().set_origin_mode(false), &[9] => self.clear_mouse_mode(MouseProtocolMode::Press), &[25] => self.set_mode(MODE_HIDE_CURSOR), &[47] => { self.exit_alternate_grid(); } &[1000] => { self.clear_mouse_mode(MouseProtocolMode::PressRelease); } &[1002] => { self.clear_mouse_mode(MouseProtocolMode::ButtonMotion); } &[1003] => { self.clear_mouse_mode(MouseProtocolMode::AnyMotion); } &[1005] => { self.clear_mouse_encoding(MouseProtocolEncoding::Utf8); } &[1006] => { self.clear_mouse_encoding(MouseProtocolEncoding::Sgr); } &[1049] => { self.exit_alternate_grid(); self.decrc(); } &[2004] => self.clear_mode(MODE_BRACKETED_PASTE), ns => { if log::log_enabled!(log::Level::Debug) { let n = if ns.len() == 1 { format!( "{}", // we just checked that ns.len() == 1, so 0 // must be valid ns[0] ) } else { format!("{ns:?}") }; log::debug!("unhandled DECRST mode: {n}"); } } } } } // CSI m fn sgr(&mut self, params: &vte::Params) { // XXX really i want to just be able to pass in a default Params // instance with a 0 in it, but vte doesn't allow creating new Params // instances if params.is_empty() { self.attrs = crate::attrs::Attrs::default(); return; } let mut iter = params.iter(); macro_rules! next_param { () => { match iter.next() { Some(n) => n, _ => return, } }; } macro_rules! to_u8 { ($n:expr) => { if let Some(n) = u16_to_u8($n) { n } else { return; } }; } macro_rules! next_param_u8 { () => { if let &[n] = next_param!() { to_u8!(n) } else { return; } }; } loop { match next_param!() { &[0] => self.attrs = crate::attrs::Attrs::default(), &[1] => self.attrs.set_bold(true), &[3] => self.attrs.set_italic(true), &[4] => self.attrs.set_underline(true), &[7] => self.attrs.set_inverse(true), &[22] => self.attrs.set_bold(false), &[23] => self.attrs.set_italic(false), &[24] => self.attrs.set_underline(false), &[27] => self.attrs.set_inverse(false), &[n] if (30..=37).contains(&n) => { self.attrs.fgcolor = crate::attrs::Color::Idx(to_u8!(n) - 30); } &[38, 2, r, g, b] => { self.attrs.fgcolor = crate::attrs::Color::Rgb( to_u8!(r), to_u8!(g), to_u8!(b), ); } &[38, 5, i] => { self.attrs.fgcolor = crate::attrs::Color::Idx(to_u8!(i)); } &[38] => match next_param!() { &[2] => { let r = next_param_u8!(); let g = next_param_u8!(); let b = next_param_u8!(); self.attrs.fgcolor = crate::attrs::Color::Rgb(r, g, b); } &[5] => { self.attrs.fgcolor = crate::attrs::Color::Idx(next_param_u8!()); } ns => { if log::log_enabled!(log::Level::Debug) { let n = if ns.len() == 1 { format!( "{}", // we just checked that ns.len() == 1, so // 0 must be valid ns[0] ) } else { format!("{ns:?}") }; log::debug!("unhandled SGR mode: 38 {n}"); } return; } }, &[39] => { self.attrs.fgcolor = crate::attrs::Color::Default; } &[n] if (40..=47).contains(&n) => { self.attrs.bgcolor = crate::attrs::Color::Idx(to_u8!(n) - 40); } &[48, 2, r, g, b] => { self.attrs.bgcolor = crate::attrs::Color::Rgb( to_u8!(r), to_u8!(g), to_u8!(b), ); } &[48, 5, i] => { self.attrs.bgcolor = crate::attrs::Color::Idx(to_u8!(i)); } &[48] => match next_param!() { &[2] => { let r = next_param_u8!(); let g = next_param_u8!(); let b = next_param_u8!(); self.attrs.bgcolor = crate::attrs::Color::Rgb(r, g, b); } &[5] => { self.attrs.bgcolor = crate::attrs::Color::Idx(next_param_u8!()); } ns => { if log::log_enabled!(log::Level::Debug) { let n = if ns.len() == 1 { format!( "{}", // we just checked that ns.len() == 1, so // 0 must be valid ns[0] ) } else { format!("{ns:?}") }; log::debug!("unhandled SGR mode: 48 {n}"); } return; } }, &[49] => { self.attrs.bgcolor = crate::attrs::Color::Default; } &[n] if (90..=97).contains(&n) => { self.attrs.fgcolor = crate::attrs::Color::Idx(to_u8!(n) - 82); } &[n] if (100..=107).contains(&n) => { self.attrs.bgcolor = crate::attrs::Color::Idx(to_u8!(n) - 92); } ns => { if log::log_enabled!(log::Level::Debug) { let n = if ns.len() == 1 { format!( "{}", // we just checked that ns.len() == 1, so 0 // must be valid ns[0] ) } else { format!("{ns:?}") }; log::debug!("unhandled SGR mode: {n}"); } } } } } // CSI r fn decstbm(&mut self, (top, bottom): (u16, u16)) { self.grid_mut().set_scroll_region(top - 1, bottom - 1); } // osc codes fn osc0(&mut self, s: &[u8]) { self.osc1(s); self.osc2(s); } fn osc1(&mut self, s: &[u8]) { if let Ok(s) = std::str::from_utf8(s) { self.icon_name = s.to_string(); } } fn osc2(&mut self, s: &[u8]) { if let Ok(s) = std::str::from_utf8(s) { self.title = s.to_string(); } } } impl vte::Perform for Screen { fn print(&mut self, c: char) { if c == '\u{fffd}' || ('\u{80}'..'\u{a0}').contains(&c) { self.errors = self.errors.saturating_add(1); } self.text(c); } fn execute(&mut self, b: u8) { match b { 7 => self.bel(), 8 => self.bs(), 9 => self.tab(), 10 => self.lf(), 11 => self.vt(), 12 => self.ff(), 13 => self.cr(), // we don't implement shift in/out alternate character sets, but // it shouldn't count as an "error" 14 | 15 => {} _ => { self.errors = self.errors.saturating_add(1); log::debug!("unhandled control character: {b}"); } } } fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, b: u8) { intermediates.first().map_or_else( || match b { b'7' => self.decsc(), b'8' => self.decrc(), b'=' => self.deckpam(), b'>' => self.deckpnm(), b'M' => self.ri(), b'c' => self.ris(), b'g' => self.vb(), _ => { log::debug!("unhandled escape code: ESC {b}"); } }, |i| { log::debug!("unhandled escape code: ESC {i} {b}"); }, ); } fn csi_dispatch( &mut self, params: &vte::Params, intermediates: &[u8], _ignore: bool, c: char, ) { match intermediates.first() { None => match c { '@' => self.ich(canonicalize_params_1(params, 1)), 'A' => self.cuu(canonicalize_params_1(params, 1)), 'B' => self.cud(canonicalize_params_1(params, 1)), 'C' => self.cuf(canonicalize_params_1(params, 1)), 'D' => self.cub(canonicalize_params_1(params, 1)), 'G' => self.cha(canonicalize_params_1(params, 1)), 'H' => self.cup(canonicalize_params_2(params, 1, 1)), 'J' => self.ed(canonicalize_params_1(params, 0)), 'K' => self.el(canonicalize_params_1(params, 0)), 'L' => self.il(canonicalize_params_1(params, 1)), 'M' => self.dl(canonicalize_params_1(params, 1)), 'P' => self.dch(canonicalize_params_1(params, 1)), 'S' => self.su(canonicalize_params_1(params, 1)), 'T' => self.sd(canonicalize_params_1(params, 1)), 'X' => self.ech(canonicalize_params_1(params, 1)), 'd' => self.vpa(canonicalize_params_1(params, 1)), 'h' => self.sm(params), 'l' => self.rm(params), 'm' => self.sgr(params), 'r' => self.decstbm(canonicalize_params_decstbm( params, self.grid().size(), )), _ => { if log::log_enabled!(log::Level::Debug) { log::debug!( "unhandled csi sequence: CSI {} {}", param_str(params), c ); } } }, Some(b'?') => match c { 'J' => self.decsed(canonicalize_params_1(params, 0)), 'K' => self.decsel(canonicalize_params_1(params, 0)), 'h' => self.decset(params), 'l' => self.decrst(params), _ => { if log::log_enabled!(log::Level::Debug) { log::debug!( "unhandled csi sequence: CSI ? {} {}", param_str(params), c ); } } }, Some(i) => { if log::log_enabled!(log::Level::Debug) { log::debug!( "unhandled csi sequence: CSI {} {} {}", i, param_str(params), c ); } } } } fn osc_dispatch(&mut self, params: &[&[u8]], _bel_terminated: bool) { match (params.get(0), params.get(1)) { (Some(&b"0"), Some(s)) => self.osc0(s), (Some(&b"1"), Some(s)) => self.osc1(s), (Some(&b"2"), Some(s)) => self.osc2(s), _ => { if log::log_enabled!(log::Level::Debug) { log::debug!( "unhandled osc sequence: OSC {}", osc_param_str(params), ); } } } } fn hook( &mut self, params: &vte::Params, intermediates: &[u8], _ignore: bool, action: char, ) { if log::log_enabled!(log::Level::Debug) { intermediates.first().map_or_else( || { log::debug!( "unhandled dcs sequence: DCS {} {}", param_str(params), action, ); }, |i| { log::debug!( "unhandled dcs sequence: DCS {} {} {}", i, param_str(params), action, ); }, ); } } } fn canonicalize_params_1(params: &vte::Params, default: u16) -> u16 { let first = params.iter().next().map_or(0, |x| *x.first().unwrap_or(&0)); if first == 0 { default } else { first } } fn canonicalize_params_2( params: &vte::Params, default1: u16, default2: u16, ) -> (u16, u16) { let mut iter = params.iter(); let first = iter.next().map_or(0, |x| *x.first().unwrap_or(&0)); let first = if first == 0 { default1 } else { first }; let second = iter.next().map_or(0, |x| *x.first().unwrap_or(&0)); let second = if second == 0 { default2 } else { second }; (first, second) } fn canonicalize_params_decstbm( params: &vte::Params, size: crate::grid::Size, ) -> (u16, u16) { let mut iter = params.iter(); let top = iter.next().map_or(0, |x| *x.first().unwrap_or(&0)); let top = if top == 0 { 1 } else { top }; let bottom = iter.next().map_or(0, |x| *x.first().unwrap_or(&0)); let bottom = if bottom == 0 { size.rows } else { bottom }; (top, bottom) } fn u16_to_u8(i: u16) -> Option { if i > u16::from(u8::max_value()) { None } else { // safe because we just ensured that the value fits in a u8 Some(i.try_into().unwrap()) } } fn param_str(params: &vte::Params) -> String { let strs: Vec<_> = params .iter() .map(|subparams| { let subparam_strs: Vec<_> = subparams .iter() .map(std::string::ToString::to_string) .collect(); subparam_strs.join(" : ") }) .collect(); strs.join(" ; ") } fn osc_param_str(params: &[&[u8]]) -> String { let strs: Vec<_> = params .iter() .map(|b| format!("\"{}\"", std::string::String::from_utf8_lossy(b))) .collect(); strs.join(" ; ") } vt100-0.15.2/src/term.rs000064400000000000000000000371351046102023000127000ustar 00000000000000// TODO: read all of this from terminfo pub trait BufWrite { fn write_buf(&self, buf: &mut Vec); } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct ClearScreen; impl BufWrite for ClearScreen { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1b[H\x1b[J"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct ClearRowForward; impl BufWrite for ClearRowForward { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1b[K"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct Crlf; impl BufWrite for Crlf { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\r\n"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct Backspace; impl BufWrite for Backspace { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x08"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct SaveCursor; impl BufWrite for SaveCursor { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1b7"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct RestoreCursor; impl BufWrite for RestoreCursor { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1b8"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct MoveTo { row: u16, col: u16, } impl MoveTo { pub fn new(pos: crate::grid::Pos) -> Self { Self { row: pos.row, col: pos.col, } } } impl BufWrite for MoveTo { fn write_buf(&self, buf: &mut Vec) { if self.row == 0 && self.col == 0 { buf.extend_from_slice(b"\x1b[H"); } else { buf.extend_from_slice(b"\x1b["); extend_itoa(buf, self.row + 1); buf.push(b';'); extend_itoa(buf, self.col + 1); buf.push(b'H'); } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct ClearAttrs; impl BufWrite for ClearAttrs { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1b[m"); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct Attrs { fgcolor: Option, bgcolor: Option, bold: Option, italic: Option, underline: Option, inverse: Option, } impl Attrs { pub fn fgcolor(mut self, fgcolor: crate::attrs::Color) -> Self { self.fgcolor = Some(fgcolor); self } pub fn bgcolor(mut self, bgcolor: crate::attrs::Color) -> Self { self.bgcolor = Some(bgcolor); self } pub fn bold(mut self, bold: bool) -> Self { self.bold = Some(bold); self } pub fn italic(mut self, italic: bool) -> Self { self.italic = Some(italic); self } pub fn underline(mut self, underline: bool) -> Self { self.underline = Some(underline); self } pub fn inverse(mut self, inverse: bool) -> Self { self.inverse = Some(inverse); self } } impl BufWrite for Attrs { #[allow(unused_assignments)] #[allow(clippy::branches_sharing_code)] fn write_buf(&self, buf: &mut Vec) { if self.fgcolor.is_none() && self.bgcolor.is_none() && self.bold.is_none() && self.italic.is_none() && self.underline.is_none() && self.inverse.is_none() { return; } buf.extend_from_slice(b"\x1b["); let mut first = true; macro_rules! write_param { ($i:expr) => { if first { first = false; } else { buf.push(b';'); } extend_itoa(buf, $i); }; } if let Some(fgcolor) = self.fgcolor { match fgcolor { crate::attrs::Color::Default => { write_param!(39); } crate::attrs::Color::Idx(i) => { if i < 8 { write_param!(i + 30); } else if i < 16 { write_param!(i + 82); } else { write_param!(38); write_param!(5); write_param!(i); } } crate::attrs::Color::Rgb(r, g, b) => { write_param!(38); write_param!(2); write_param!(r); write_param!(g); write_param!(b); } } } if let Some(bgcolor) = self.bgcolor { match bgcolor { crate::attrs::Color::Default => { write_param!(49); } crate::attrs::Color::Idx(i) => { if i < 8 { write_param!(i + 40); } else if i < 16 { write_param!(i + 92); } else { write_param!(48); write_param!(5); write_param!(i); } } crate::attrs::Color::Rgb(r, g, b) => { write_param!(48); write_param!(2); write_param!(r); write_param!(g); write_param!(b); } } } if let Some(bold) = self.bold { if bold { write_param!(1); } else { write_param!(22); } } if let Some(italic) = self.italic { if italic { write_param!(3); } else { write_param!(23); } } if let Some(underline) = self.underline { if underline { write_param!(4); } else { write_param!(24); } } if let Some(inverse) = self.inverse { if inverse { write_param!(7); } else { write_param!(27); } } buf.push(b'm'); } } #[derive(Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct MoveRight { count: u16, } impl MoveRight { pub fn new(count: u16) -> Self { Self { count } } } impl Default for MoveRight { fn default() -> Self { Self { count: 1 } } } impl BufWrite for MoveRight { fn write_buf(&self, buf: &mut Vec) { match self.count { 0 => {} 1 => buf.extend_from_slice(b"\x1b[C"), n => { buf.extend_from_slice(b"\x1b["); extend_itoa(buf, n); buf.push(b'C'); } } } } #[derive(Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct EraseChar { count: u16, } impl EraseChar { pub fn new(count: u16) -> Self { Self { count } } } impl Default for EraseChar { fn default() -> Self { Self { count: 1 } } } impl BufWrite for EraseChar { fn write_buf(&self, buf: &mut Vec) { match self.count { 0 => {} 1 => buf.extend_from_slice(b"\x1b[X"), n => { buf.extend_from_slice(b"\x1b["); extend_itoa(buf, n); buf.push(b'X'); } } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct HideCursor { state: bool, } impl HideCursor { pub fn new(state: bool) -> Self { Self { state } } } impl BufWrite for HideCursor { fn write_buf(&self, buf: &mut Vec) { if self.state { buf.extend_from_slice(b"\x1b[?25l"); } else { buf.extend_from_slice(b"\x1b[?25h"); } } } #[derive(Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct MoveFromTo { from: crate::grid::Pos, to: crate::grid::Pos, } impl MoveFromTo { pub fn new(from: crate::grid::Pos, to: crate::grid::Pos) -> Self { Self { from, to } } } impl BufWrite for MoveFromTo { fn write_buf(&self, buf: &mut Vec) { if self.to.row == self.from.row + 1 && self.to.col == 0 { crate::term::Crlf::default().write_buf(buf); } else if self.from.row == self.to.row && self.from.col < self.to.col { crate::term::MoveRight::new(self.to.col - self.from.col) .write_buf(buf); } else if self.to != self.from { crate::term::MoveTo::new(self.to).write_buf(buf); } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct AudibleBell; impl BufWrite for AudibleBell { fn write_buf(&self, buf: &mut Vec) { buf.push(b'\x07'); } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct VisualBell; impl BufWrite for VisualBell { fn write_buf(&self, buf: &mut Vec) { buf.extend_from_slice(b"\x1bg"); } } #[must_use = "this struct does nothing unless you call write_buf"] pub struct ChangeTitle<'a> { icon_name: &'a str, title: &'a str, prev_icon_name: &'a str, prev_title: &'a str, } impl<'a> ChangeTitle<'a> { pub fn new( icon_name: &'a str, title: &'a str, prev_icon_name: &'a str, prev_title: &'a str, ) -> Self { Self { icon_name, title, prev_icon_name, prev_title, } } } impl<'a> BufWrite for ChangeTitle<'a> { fn write_buf(&self, buf: &mut Vec) { if self.icon_name == self.title && (self.icon_name != self.prev_icon_name || self.title != self.prev_title) { buf.extend_from_slice(b"\x1b]0;"); buf.extend_from_slice(self.icon_name.as_bytes()); buf.push(b'\x07'); } else { if self.icon_name != self.prev_icon_name { buf.extend_from_slice(b"\x1b]1;"); buf.extend_from_slice(self.icon_name.as_bytes()); buf.push(b'\x07'); } if self.title != self.prev_title { buf.extend_from_slice(b"\x1b]2;"); buf.extend_from_slice(self.title.as_bytes()); buf.push(b'\x07'); } } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct ApplicationKeypad { state: bool, } impl ApplicationKeypad { pub fn new(state: bool) -> Self { Self { state } } } impl BufWrite for ApplicationKeypad { fn write_buf(&self, buf: &mut Vec) { if self.state { buf.extend_from_slice(b"\x1b="); } else { buf.extend_from_slice(b"\x1b>"); } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct ApplicationCursor { state: bool, } impl ApplicationCursor { pub fn new(state: bool) -> Self { Self { state } } } impl BufWrite for ApplicationCursor { fn write_buf(&self, buf: &mut Vec) { if self.state { buf.extend_from_slice(b"\x1b[?1h"); } else { buf.extend_from_slice(b"\x1b[?1l"); } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct BracketedPaste { state: bool, } impl BracketedPaste { pub fn new(state: bool) -> Self { Self { state } } } impl BufWrite for BracketedPaste { fn write_buf(&self, buf: &mut Vec) { if self.state { buf.extend_from_slice(b"\x1b[?2004h"); } else { buf.extend_from_slice(b"\x1b[?2004l"); } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct MouseProtocolMode { mode: crate::screen::MouseProtocolMode, prev: crate::screen::MouseProtocolMode, } impl MouseProtocolMode { pub fn new( mode: crate::screen::MouseProtocolMode, prev: crate::screen::MouseProtocolMode, ) -> Self { Self { mode, prev } } } impl BufWrite for MouseProtocolMode { fn write_buf(&self, buf: &mut Vec) { if self.mode == self.prev { return; } match self.mode { crate::screen::MouseProtocolMode::None => match self.prev { crate::screen::MouseProtocolMode::None => {} crate::screen::MouseProtocolMode::Press => { buf.extend_from_slice(b"\x1b[?9l"); } crate::screen::MouseProtocolMode::PressRelease => { buf.extend_from_slice(b"\x1b[?1000l"); } crate::screen::MouseProtocolMode::ButtonMotion => { buf.extend_from_slice(b"\x1b[?1002l"); } crate::screen::MouseProtocolMode::AnyMotion => { buf.extend_from_slice(b"\x1b[?1003l"); } }, crate::screen::MouseProtocolMode::Press => { buf.extend_from_slice(b"\x1b[?9h"); } crate::screen::MouseProtocolMode::PressRelease => { buf.extend_from_slice(b"\x1b[?1000h"); } crate::screen::MouseProtocolMode::ButtonMotion => { buf.extend_from_slice(b"\x1b[?1002h"); } crate::screen::MouseProtocolMode::AnyMotion => { buf.extend_from_slice(b"\x1b[?1003h"); } } } } #[derive(Default, Debug)] #[must_use = "this struct does nothing unless you call write_buf"] pub struct MouseProtocolEncoding { encoding: crate::screen::MouseProtocolEncoding, prev: crate::screen::MouseProtocolEncoding, } impl MouseProtocolEncoding { pub fn new( encoding: crate::screen::MouseProtocolEncoding, prev: crate::screen::MouseProtocolEncoding, ) -> Self { Self { encoding, prev } } } impl BufWrite for MouseProtocolEncoding { fn write_buf(&self, buf: &mut Vec) { if self.encoding == self.prev { return; } match self.encoding { crate::screen::MouseProtocolEncoding::Default => { match self.prev { crate::screen::MouseProtocolEncoding::Default => {} crate::screen::MouseProtocolEncoding::Utf8 => { buf.extend_from_slice(b"\x1b[?1005l"); } crate::screen::MouseProtocolEncoding::Sgr => { buf.extend_from_slice(b"\x1b[?1006l"); } } } crate::screen::MouseProtocolEncoding::Utf8 => { buf.extend_from_slice(b"\x1b[?1005h"); } crate::screen::MouseProtocolEncoding::Sgr => { buf.extend_from_slice(b"\x1b[?1006h"); } } } } fn extend_itoa(buf: &mut Vec, i: I) { let mut itoa_buf = itoa::Buffer::new(); buf.extend_from_slice(itoa_buf.format(i).as_bytes()); }