aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Vander Stoep <jeffv@google.com>2023-03-06 13:09:34 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-03-06 13:09:34 +0000
commit711a2c2fbc1a394d8741749a774285b4be85b9df (patch)
tree89adabe06a1be36a3671f51328fa6d10b5780a06
parent6ad53421c8b98154d373f40eea12e6b0f0e72454 (diff)
parent0eb2bcfda76746571385e2addcc3b612abdee3a2 (diff)
downloadanes-711a2c2fbc1a394d8741749a774285b4be85b9df.tar.gz
Import anes 0.1.6 am: 0eb2bcfda7
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/anes/+/2449525 Change-Id: Ibfd060ee4d34a652c71169ee2f5d930dd2e21c75 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--Android.bp74
-rw-r--r--Cargo.lock511
-rw-r--r--Cargo.toml48
-rw-r--r--Cargo.toml.orig37
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--LICENSE-MIT23
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md129
-rw-r--r--benches/bench_main.rs5
-rw-r--r--benches/benchmarks/mod.rs1
-rw-r--r--benches/benchmarks/parser.rs20
-rw-r--r--cargo2android.json6
-rw-r--r--examples/execute.rs15
-rw-r--r--examples/parser.rs106
-rw-r--r--examples/queue.rs18
-rw-r--r--examples/sequence.rs42
-rw-r--r--examples/stdout.rs12
-rw-r--r--examples/string.rs7
-rw-r--r--src/lib.rs76
-rw-r--r--src/macros.rs437
-rw-r--r--src/parser.rs252
-rw-r--r--src/parser/engine.rs614
-rw-r--r--src/parser/parsers.rs239
-rw-r--r--src/parser/types.rs79
-rw-r--r--src/sequences.rs5
-rw-r--r--src/sequences/attribute.rs133
-rw-r--r--src/sequences/buffer.rs145
-rw-r--r--src/sequences/color.rs189
-rw-r--r--src/sequences/cursor.rs352
-rw-r--r--src/sequences/terminal.rs54
-rw-r--r--tests/parser/cursor.rs8
-rw-r--r--tests/parser/key.rs158
-rw-r--r--tests/parser/mod.rs25
-rw-r--r--tests/parser/mouse/mod.rs2
-rw-r--r--tests/parser/mouse/rxvt.rs152
-rw-r--r--tests/parser/mouse/xterm.rs154
-rw-r--r--tests/tests.rs2
41 files changed, 4358 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..d2d567b
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "378ac7461ecac29fde9e10df7b08359d1315a9ad"
+ }
+}
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..8049644
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,74 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+
+
+rust_test {
+ name: "anes_test_src_lib",
+ host_supported: true,
+ crate_name: "anes",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.6",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2018",
+ features: [
+ "bitflags",
+ "parser",
+ ],
+ rustlibs: [
+ "libbitflags",
+ "libcriterion",
+ "liblibc",
+ ],
+}
+
+rust_test {
+ name: "anes_test_tests_tests",
+ host_supported: true,
+ crate_name: "tests",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.6",
+ srcs: ["tests/tests.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2018",
+ features: [
+ "bitflags",
+ "parser",
+ ],
+ rustlibs: [
+ "libanes",
+ "libbitflags",
+ "libcriterion",
+ "liblibc",
+ ],
+}
+
+rust_library {
+ name: "libanes",
+ host_supported: true,
+ crate_name: "anes",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.6",
+ srcs: ["src/lib.rs"],
+ edition: "2018",
+ features: [
+ "bitflags",
+ "parser",
+ ],
+ rustlibs: [
+ "libbitflags",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..b359a6d
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,511 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "anes"
+version = "0.1.6"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "autocfg"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bstr"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cast"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "clap"
+version = "2.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "criterion"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "csv"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "either"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "getrandom"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xoshiro"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rayon"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "same-file"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tinytemplate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "walkdir"
+version = "2.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasi"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90"
+"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245"
+"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
+"checksum cast 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
+"checksum criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "938703e165481c8d612ea3479ac8342e5615185db37765162e762ec3523e2fc6"
+"checksum criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccdc6ce8bbe352ca89025bee672aa6d24f4eb8c53e3a8b5d1bc58011da072a2"
+"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca"
+"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac"
+"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700"
+"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4"
+"checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d"
+"checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c"
+"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
+"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
+"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120"
+"checksum itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+"checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558"
+"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
+"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
+"checksum num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c81ffc11c212fa327657cb19dd85eb7419e163b5b076bede2bdb5c974c07e4"
+"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72"
+"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+"checksum rand_os 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a788ae3edb696cfcba1c19bfd388cc4b8c21f8a408432b199c072825084da58a"
+"checksum rand_xoshiro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e18c91676f670f6f0312764c759405f13afb98d5d73819840cf72a518487bff"
+"checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd"
+"checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791"
+"checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9"
+"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+"checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421"
+"checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d"
+"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+"checksum serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "1217f97ab8e8904b57dd22eb61cde455fa7446a9c1cf43966066da047c1f3702"
+"checksum serde_derive 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c6faef9a2e64b0064f48570289b4bf8823b7581f1d6157c1b52152306651d0"
+"checksum serde_json 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)" = "1a3351dcbc1f067e2c92ab7c3c1f288ad1a4cffc470b5aaddb4c2e0a3ae80043"
+"checksum syn 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f89693ae015201f8de93fd96bde2d065f8bfc3f97ce006d5bc9f900b97c0c7c0"
+"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+"checksum tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4574b75faccaacddb9b284faecdf0b544b80b6b294f3d062d325c5726a209c20"
+"checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e"
+"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..9ae35a0
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,48 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "anes"
+version = "0.1.6"
+authors = ["Robert Vojta <rvojta@me.com>"]
+exclude = ["target", "Cargo.lock"]
+description = "ANSI Escape Sequences provider & parser"
+documentation = "https://docs.rs/anes/"
+readme = "README.md"
+keywords = ["terminal", "ansi", "sequence", "code", "parser"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/zrzka/anes-rs"
+[package.metadata.docs.rs]
+all-features = true
+
+[lib]
+bench = false
+
+[[bench]]
+name = "bench_main"
+harness = false
+required-features = ["parser"]
+[dependencies.bitflags]
+version = "1.2"
+optional = true
+[dev-dependencies.criterion]
+version = "0.3"
+
+[dev-dependencies.libc]
+version = "0.2.66"
+
+[features]
+default = []
+parser = ["bitflags"]
+[badges.maintenance]
+status = "actively-developed"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..51fd1d3
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,37 @@
+[package]
+name = "anes"
+version = "0.1.6"
+authors = ["Robert Vojta <rvojta@me.com>"]
+edition = "2018"
+description = "ANSI Escape Sequences provider & parser"
+repository = "https://github.com/zrzka/anes-rs"
+documentation = "https://docs.rs/anes/"
+license = "MIT OR Apache-2.0"
+keywords = ["terminal", "ansi", "sequence", "code", "parser"]
+exclude = ["target", "Cargo.lock"]
+readme = "README.md"
+
+[lib]
+bench = false
+
+[badges]
+maintenance = { status = "actively-developed" }
+
+[package.metadata.docs.rs]
+all-features = true
+
+[features]
+default = []
+parser = ["bitflags"]
+
+[dependencies]
+bitflags = { version = "1.2", optional = true }
+
+[dev-dependencies]
+criterion = "0.3"
+libc = "0.2.66"
+
+[[bench]]
+name = "bench_main"
+harness = false
+required-features = ["parser"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1 @@
+LICENSE-APACHE \ No newline at end of file
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..fc87cce
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "anes"
+description: "()"
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/anes"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/anes/anes-0.1.6.crate"
+ }
+ version: "0.1.6"
+ # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same.
+ license_type: NOTICE
+ last_upgrade_date {
+ year: 2023
+ month: 2
+ day: 16
+ }
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+include platform/prebuilts/rust:master:/OWNERS
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6a3c236
--- /dev/null
+++ b/README.md
@@ -0,0 +1,129 @@
+[![Stable Status][actions-stable-badge]][actions-link]
+[![Beta Status][actions-beta-badge]][actions-link]
+[![Nightly Status][actions-nightly-badge]][actions-link]
+[![crates.io][crates-badge]][crates-link]
+[![docs.rs][docs-badge]][docs-link]
+[![MIT][mit-license-badge]][mit-license-link]
+[![Apache 2.0][apache-license-badge]][apache-license-link]
+![LOC][loc-badge]
+
+# ANSI Escape Sequences provider & parser
+
+A Rust library which provides an ANSI escape sequences (or codes, whatever you like more)
+and a parser allowing you to parse data from the STDIN (or `/dev/tty`) in the raw mode.
+
+## Sequences provider
+
+### Terminal support
+
+Not all ANSI escape sequences are supported by all terminals. You can use the
+[interactive-test](https://github.com/zrzka/anes-rs/tree/master/interactive-test) to test them.
+Checkout the repository and then:
+
+```bash
+$ cd anes-rs
+$ cargo run --bin interactive-test
+```
+
+### Examples
+
+<details>
+<summary>
+Click to show Cargo.toml.
+</summary>
+
+```toml
+[dependencies]
+anes = "0.1"
+```
+
+</details>
+<p></p>
+
+
+An example how to retrieve the ANSI escape sequence as a `String`:
+
+```rust
+use anes::SaveCursorPosition;
+
+fn main() {
+ let string = format!("{}", SaveCursorPosition);
+ assert_eq!(&string, "\x1B7");
+}
+```
+
+An example how to use the ANSI escape sequence:
+
+```rust
+use std::io::{Result, Write};
+
+use anes::execute;
+
+fn main() -> Result<()> {
+ let mut stdout = std::io::stdout();
+ execute!(
+ &mut stdout,
+ anes::SaveCursorPosition,
+ anes::MoveCursorTo(10, 10),
+ anes::RestoreCursorPosition
+ )?;
+ Ok(())
+}
+```
+
+## Sequences parser
+
+You have to enable `parser` feature in order to use the parser. It's disabled by default.
+
+### Examples
+
+<details>
+<summary>
+Click to show Cargo.toml.
+</summary>
+
+```toml
+[dependencies]
+anes = { version = "0.1", features = ["parser"] }
+```
+
+</details>
+<p></p>
+
+An example how to parse cursor position:
+
+```rust
+use anes::parser::{Parser, Sequence};
+
+let mut parser = Parser::default();
+parser.advance(b"\x1B[20;10R", false);
+
+assert_eq!(Some(Sequence::CursorPosition(10, 20)), parser.next());
+assert!(parser.next().is_none());
+```
+
+## License
+
+The ANES crate is dual-licensed under [Apache 2.0][apache-license-link] and
+[MIT][mit-license-link] terms.
+
+Copyrights in the ANES project are retained by their contributors. No
+copyright assignment is required to contribute to the ANES project.
+
+[actions-stable-badge]: https://github.com/zrzka/anes-rs/workflows/stable/badge.svg
+[actions-beta-badge]: https://github.com/zrzka/anes-rs/workflows/beta/badge.svg
+[actions-nightly-badge]: https://github.com/zrzka/anes-rs/workflows/nightly/badge.svg
+[actions-link]: https://github.com/zrzka/anes-rs/actions
+
+[crates-badge]: https://img.shields.io/crates/v/anes.svg
+[crates-link]: https://crates.io/crates/anes
+
+[docs-badge]: https://docs.rs/anes/badge.svg
+[docs-link]: https://docs.rs/anes
+
+[mit-license-badge]: https://img.shields.io/badge/license-MIT-blue.svg
+[mit-license-link]: ./LICENSE-MIT
+[apache-license-badge]: https://img.shields.io/badge/license-Apache2-blue.svg
+[apache-license-link]: /LICENSE-APACHE
+
+[loc-badge]: https://tokei.rs/b1/github/zrzka/anes-rs?category=code
diff --git a/benches/bench_main.rs b/benches/bench_main.rs
new file mode 100644
index 0000000..79aea4a
--- /dev/null
+++ b/benches/bench_main.rs
@@ -0,0 +1,5 @@
+use criterion::criterion_main;
+
+mod benchmarks;
+
+criterion_main!(benchmarks::parser::benches);
diff --git a/benches/benchmarks/mod.rs b/benches/benchmarks/mod.rs
new file mode 100644
index 0000000..67c567f
--- /dev/null
+++ b/benches/benchmarks/mod.rs
@@ -0,0 +1 @@
+pub mod parser;
diff --git a/benches/benchmarks/parser.rs b/benches/benchmarks/parser.rs
new file mode 100644
index 0000000..2aa63aa
--- /dev/null
+++ b/benches/benchmarks/parser.rs
@@ -0,0 +1,20 @@
+use criterion::{black_box, criterion_group, Criterion};
+
+use anes::parser::Parser;
+
+pub fn parser(c: &mut Criterion) {
+ const XTERM_MOUSE: &str = "\x1B[<28;20;10;m";
+
+ let mut parser = Parser::default();
+
+ c.bench_function("advance and consume", |b| {
+ let input = XTERM_MOUSE.as_bytes();
+
+ b.iter(|| {
+ parser.advance(black_box(input), black_box(true));
+ while let Some(_) = parser.next() {}
+ })
+ });
+}
+
+criterion_group!(benches, parser);
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 0000000..fb02735
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,6 @@
+{
+ "device": true,
+ "run": true,
+ "features": "parser",
+ "tests": true
+}
diff --git a/examples/execute.rs b/examples/execute.rs
new file mode 100644
index 0000000..df781fc
--- /dev/null
+++ b/examples/execute.rs
@@ -0,0 +1,15 @@
+/// An example how to execute the ANSI escape sequence.
+use std::io::{Result, Write};
+
+use anes::execute;
+
+fn main() -> Result<()> {
+ let mut stdout = std::io::stdout();
+ execute!(
+ &mut stdout,
+ anes::SaveCursorPosition,
+ anes::MoveCursorTo(10, 10),
+ anes::RestoreCursorPosition
+ )?;
+ Ok(())
+}
diff --git a/examples/parser.rs b/examples/parser.rs
new file mode 100644
index 0000000..a5ec5b6
--- /dev/null
+++ b/examples/parser.rs
@@ -0,0 +1,106 @@
+/// An example how to use the ANSI escape sequence parser.
+use std::io::{Read, Result, Write};
+
+use anes::{
+ self, execute,
+ parser::{KeyCode, Parser, Sequence},
+ queue,
+};
+use libc::termios as Termios;
+
+const HELP: &str = r#"ANES parser example
+
+* Hit `Esc` to quit
+* Hit 'c' to ask for cursor position
+* Use your mouse or type anything
+"#;
+
+fn main() -> Result<()> {
+ let mut w = std::io::stdout();
+ queue!(
+ w,
+ anes::SwitchBufferToAlternate,
+ anes::HideCursor,
+ anes::EnableMouseEvents
+ )?;
+ for line in HELP.split('\n') {
+ queue!(w, line, anes::MoveCursorToNextLine(1))?;
+ }
+ w.flush()?;
+
+ let saved_attributes = get_termios()?;
+ let mut attributes = saved_attributes;
+ make_raw(&mut attributes);
+ set_termios(attributes)?;
+
+ let mut stdin = std::io::stdin();
+ let mut stdin_buffer = [0u8; 1024];
+ let mut parser = Parser::default();
+
+ loop {
+ if let Ok(size) = stdin.read(&mut stdin_buffer) {
+ parser.advance(&stdin_buffer[..size], false);
+
+ let mut break_outer_loop = false;
+
+ while let Some(sequence) = parser.next() {
+ match sequence {
+ Sequence::Key(KeyCode::Esc, _) => {
+ break_outer_loop = true;
+ break;
+ }
+ Sequence::Key(KeyCode::Char('c'), _) => {
+ execute!(w, anes::ReportCursorPosition)?
+ }
+ _ => execute!(
+ w,
+ anes::ClearLine::Left,
+ anes::MoveCursorToColumn(1),
+ format!("{:?}", sequence),
+ )?,
+ }
+ }
+
+ if break_outer_loop {
+ break;
+ }
+ }
+ }
+
+ set_termios(saved_attributes)?;
+
+ execute!(
+ w,
+ anes::DisableMouseEvents,
+ anes::ShowCursor,
+ anes::SwitchBufferToNormal
+ )?;
+ Ok(())
+}
+
+//
+// RAW mode
+//
+
+fn get_termios() -> Result<Termios> {
+ unsafe {
+ let mut termios = std::mem::zeroed();
+ if libc::tcgetattr(libc::STDIN_FILENO, &mut termios) != -1 {
+ Ok(termios)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+}
+
+fn set_termios(termios: Termios) -> Result<()> {
+ if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &termios) } != -1 {
+ Ok(())
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+}
+
+fn make_raw(termios: &mut Termios) {
+ unsafe { libc::cfmakeraw(termios) }
+}
diff --git a/examples/queue.rs b/examples/queue.rs
new file mode 100644
index 0000000..0b2c712
--- /dev/null
+++ b/examples/queue.rs
@@ -0,0 +1,18 @@
+/// An example how to queue & flush the ANSI escape sequence.
+use std::io::{Result, Write};
+
+use anes::queue;
+
+fn main() -> Result<()> {
+ let mut stdout = std::io::stdout();
+ queue!(
+ &mut stdout,
+ anes::SaveCursorPosition,
+ anes::MoveCursorTo(10, 10)
+ )?;
+
+ queue!(&mut stdout, anes::RestoreCursorPosition,)?;
+
+ // ANSI sequences are not executed until you flush it!
+ stdout.flush()
+}
diff --git a/examples/sequence.rs b/examples/sequence.rs
new file mode 100644
index 0000000..d038f58
--- /dev/null
+++ b/examples/sequence.rs
@@ -0,0 +1,42 @@
+/// An example how to create custom ANSI sequences.
+use anes::{csi, esc, sequence};
+
+fn static_unit_struct() {
+ sequence!(
+ /// Documentation string is also supported.
+ struct Foo => csi!("foo")
+ );
+
+ assert_eq!(&format!("{}", Foo), "\x1B[foo");
+}
+
+fn dynamic_struct() {
+ sequence!(
+ /// Documentation string is also supported.
+ struct Foo(u16, u16) =>
+ |this, f| write!(f, esc!("{};{}"), this.0, this.1)
+ );
+
+ assert_eq!(&format!("{}", Foo(5, 10)), "\x1B5;10");
+}
+
+fn static_enum() {
+ sequence!(
+ /// Documentation string is also supported.
+ enum Foo {
+ /// Documentation string is also supported.
+ Bar => esc!("bar"),
+ /// Documentation string is also supported.
+ Baz => csi!("baz"),
+ }
+ );
+
+ assert_eq!(&format!("{}", Foo::Bar), "\x1Bbar");
+ assert_eq!(&format!("{}", Foo::Baz), "\x1B[baz");
+}
+
+fn main() {
+ static_unit_struct();
+ dynamic_struct();
+ static_enum();
+}
diff --git a/examples/stdout.rs b/examples/stdout.rs
new file mode 100644
index 0000000..9b5be72
--- /dev/null
+++ b/examples/stdout.rs
@@ -0,0 +1,12 @@
+/// An example how to use the ANSI escape sequence.
+use std::io::{Result, Write};
+
+use anes;
+
+fn main() -> Result<()> {
+ let mut stdout = std::io::stdout();
+ write!(stdout, "{}", anes::SaveCursorPosition)?;
+ write!(stdout, "{}", anes::RestoreCursorPosition)?;
+ stdout.flush()?;
+ Ok(())
+}
diff --git a/examples/string.rs b/examples/string.rs
new file mode 100644
index 0000000..bd81c36
--- /dev/null
+++ b/examples/string.rs
@@ -0,0 +1,7 @@
+//! An example how to retrieve the ANSI escape sequence as a `String`.
+use anes::SaveCursorPosition;
+
+fn main() {
+ let string = format!("{}", SaveCursorPosition);
+ assert_eq!(&string, "\x1B7");
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..99dfc8c
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,76 @@
+//! # ANSI Escape Sequences provider & parser
+//!
+//! ## Sequences provider
+//!
+//! The `anes` crate provides ANSI escape sequences you can use to control the terminal
+//! cursor (show, hide, ...), colors (foreground, background), display attributes (bold, ...)
+//! and many others.
+//!
+//! Every sequence implements the standard library [`Display`](https://doc.rust-lang.org/std/fmt/trait.Display.html)
+//! trait. It means that these sequences can be used in macros like
+//! [`format!`](https://doc.rust-lang.org/std/macro.format.html) or
+//! [`write!`](https://doc.rust-lang.org/std/macro.write.html).
+//!
+//! Ask if you need more sequences or use the [`sequence!`](macro.sequence.html) macro to create
+//! your own sequences.
+//!
+//!
+//! ### Terminal Support
+//!
+//! Not all ANSI escape sequences are supported by all terminals. You can use the
+//! [interactive-test](https://github.com/zrzka/anes-rs/tree/master/interactive-test) to test them.
+//!
+//! ### Examples
+//!
+//! Retrieve the sequence as a `String`:
+//!
+//! ```rust
+//! use anes::SaveCursorPosition;
+//!
+//! let string = format!("{}", SaveCursorPosition);
+//! assert_eq!(&string, "\x1B7");
+//! ```
+//!
+//! Execute the sequence on the standard output:
+//!
+//! ```rust
+//! use std::io::{Result, Write};
+//!
+//! use anes::execute;
+//!
+//! fn main() -> Result<()> {
+//! let mut stdout = std::io::stdout();
+//! execute!(&mut stdout, anes::ResetAttributes)
+//! }
+//! ```
+//!
+//! ## Sequences parser
+//!
+//! Parser isn't available with default features. You have to enable `parser` feature if you'd like to use it.
+//! You can learn more about this feature in the [`parser`](parser/index.html) module documentation.
+#![warn(rust_2018_idioms)]
+#![deny(unused_imports, unused_must_use)]
+
+// Keep it first to load all the macros before other modules.
+#[macro_use]
+mod macros;
+
+pub use self::sequences::{
+ attribute::{Attribute, ResetAttributes, SetAttribute},
+ buffer::{
+ ClearBuffer, ClearLine, ScrollBufferDown, ScrollBufferUp, SwitchBufferToAlternate,
+ SwitchBufferToNormal,
+ },
+ color::{Color, SetBackgroundColor, SetForegroundColor},
+ cursor::{
+ DisableCursorBlinking, EnableCursorBlinking, HideCursor, MoveCursorDown, MoveCursorLeft,
+ MoveCursorRight, MoveCursorTo, MoveCursorToColumn, MoveCursorToNextLine,
+ MoveCursorToPreviousLine, MoveCursorUp, ReportCursorPosition, RestoreCursorPosition,
+ SaveCursorPosition, ShowCursor,
+ },
+ terminal::{DisableMouseEvents, EnableMouseEvents, ResizeTextArea},
+};
+
+#[cfg(feature = "parser")]
+pub mod parser;
+mod sequences;
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644
index 0000000..1955214
--- /dev/null
+++ b/src/macros.rs
@@ -0,0 +1,437 @@
+/// Creates a control sequence.
+///
+/// This macro prepends provided sequence with the control sequence introducer `ESC [` (`\x1B[`).
+///
+/// # Examples
+///
+/// ```
+/// use anes::csi;
+///
+/// assert_eq!(csi!("?1049h"), "\x1B[?1049h");
+/// ```
+#[macro_export]
+macro_rules! csi {
+ ($($arg:expr),*) => { concat!("\x1B[", $($arg),*) };
+}
+
+/// Creates an escape sequence.
+///
+/// This macro prepends provided sequence with the `ESC` (`\x1B`) character.
+///
+/// # Examples
+///
+/// ```
+/// use anes::esc;
+///
+/// assert_eq!(esc!("7"), "\x1B7");
+/// ```
+#[macro_export]
+macro_rules! esc {
+ ($($arg:expr),*) => { concat!("\x1B", $($arg),*) };
+}
+
+/// Creates a select graphic rendition sequence.
+///
+/// This macro prepends provided sequence with the `ESC[` (`\x1B[`) character and appends `m` character.
+///
+/// Also known as Set Graphics Rendition on Linux.
+///
+/// # Examples
+///
+/// ```
+/// use anes::sgr;
+///
+/// assert_eq!(sgr!("0"), "\x1B[0m");
+/// ```
+#[macro_export]
+macro_rules! sgr {
+ ($($arg:expr),*) => { concat!("\x1B[", $($arg),* , "m") };
+}
+
+/// Creates an ANSI sequence.
+///
+/// You can use this macro to create your own ANSI sequence. All `anes` sequences are
+/// created with this macro.
+///
+/// # Examples
+///
+/// An unit struct:
+///
+/// ```
+/// use anes::{esc, sequence};
+///
+/// sequence!(
+/// /// Saves the cursor position.
+/// struct SaveCursorPosition => esc!("7")
+/// );
+///
+/// assert_eq!(&format!("{}", SaveCursorPosition), "\x1B7");
+/// ```
+///
+/// An enum:
+///
+/// ```
+/// use anes::{csi, sequence};
+///
+/// sequence!(
+/// /// Clears part of the buffer.
+/// enum ClearBuffer {
+/// /// Clears from the cursor position to end of the screen.
+/// Below => csi!("J"),
+/// /// Clears from the cursor position to beginning of the screen.
+/// Above => csi!("1J"),
+/// /// Clears the entire buffer.
+/// All => csi!("2J"),
+/// /// Clears the entire buffer and all saved lines in the scrollback buffer.
+/// SavedLines => csi!("3J"),
+/// }
+/// );
+///
+/// assert_eq!(&format!("{}", ClearBuffer::Below), "\x1B[J");
+/// assert_eq!(&format!("{}", ClearBuffer::Above), "\x1B[1J");
+/// assert_eq!(&format!("{}", ClearBuffer::All), "\x1B[2J");
+/// assert_eq!(&format!("{}", ClearBuffer::SavedLines), "\x1B[3J");
+/// ```
+///
+/// A struct:
+///
+/// ```
+/// use anes::{csi, sequence};
+///
+/// sequence!(
+/// /// Moves the cursor to the given location (column, row).
+/// ///
+/// /// # Notes
+/// ///
+/// /// Top/left cell is represented as `1, 1`.
+/// struct MoveCursorTo(u16, u16) =>
+/// |this, f| write!(f, csi!("{};{}H"), this.0, this.1)
+/// );
+///
+/// assert_eq!(&format!("{}", MoveCursorTo(10, 5)), "\x1B[10;5H");
+/// ```
+#[macro_export]
+macro_rules! sequence {
+ // Static unit struct
+ (
+ $(#[$meta:meta])*
+ struct $name:ident => $value:expr
+ ) => {
+ $(#[$meta])*
+ #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+ pub struct $name;
+
+ impl ::std::fmt::Display for $name {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(f, $value)
+ }
+ }
+ };
+ // Static enum
+ (
+ $(#[$meta:meta])*
+ enum $name:ident {
+ $(
+ $(#[$variant_meta:meta])*
+ $variant:ident => $variant_value:expr
+ ),*
+ $(,)?
+ }
+ ) => {
+ $(#[$meta])*
+ #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+ pub enum $name {
+ $(
+ $(#[$variant_meta])*
+ $variant,
+ )*
+ }
+
+ impl ::std::fmt::Display for $name {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(f, "{}", match self {
+ $(
+ $name::$variant => $variant_value,
+ )*
+ })
+ }
+ }
+ };
+ // Dynamic struct
+ (
+ $(#[$meta:meta])*
+ struct $type:ident(
+ $($fields:ty),*
+ $(,)?
+ )
+ =>
+ $write:expr
+ ) => {
+ $(#[$meta])*
+ #[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+ pub struct $type($(pub $fields),*);
+
+ impl ::std::fmt::Display for $type {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ let write: &dyn Fn(&Self, &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result =
+ &$write;
+ write(self, f)
+ }
+ }
+ };
+}
+
+/// Queues ANSI escape sequence(s).
+///
+/// What does queue mean exactly? All sequences are queued with the
+/// `write!($dst, "{}", $sequence)` macro without calling the
+/// [`flush`](https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush) method.
+///
+/// Check the [`execute!`](macro.execute.html) macro if you'd like execute them
+/// immediately (call the `flush` method after all sequences were queued).
+///
+/// # Examples
+///
+/// ```no_run
+/// use std::io::{Result, Write};
+///
+/// use anes::queue;
+///
+/// fn main() -> Result<()> {
+/// let mut stdout = std::io::stdout();
+/// queue!(
+/// &mut stdout,
+/// anes::SaveCursorPosition,
+/// anes::MoveCursorTo(10, 10)
+/// )?;
+///
+/// queue!(&mut stdout, anes::RestoreCursorPosition,)?;
+///
+/// // ANSI sequences are not executed until you flush it!
+/// stdout.flush()
+/// }
+/// ```
+#[macro_export]
+macro_rules! queue {
+ ($dst:expr, $($sequence:expr),* $(,)?) => {{
+ let mut error = None;
+
+ $(
+ if let Err(e) = write!($dst, "{}", $sequence) {
+ error = Some(e);
+ }
+ )*
+
+ if let Some(error) = error {
+ Err(error)
+ } else {
+ Ok(())
+ }
+ }}
+}
+
+/// Executes ANSI escape sequence(s).
+///
+/// What does execute mean exactly? All sequences are queued with the
+/// `write!($dst, "{}", $sequence)` macro and then the
+/// [`flush`](https://doc.rust-lang.org/std/io/trait.Write.html#tymethod.flush) method
+/// is called.
+///
+/// Check the [`queue!`](macro.queue.html) macro if you'd like queue sequences
+/// and execute them later.
+///
+/// ```no_run
+/// use std::io::{Result, Write};
+///
+/// use anes::execute;
+///
+/// fn main() -> Result<()> {
+/// let mut stdout = std::io::stdout();
+/// execute!(
+/// &mut stdout,
+/// anes::SaveCursorPosition,
+/// anes::MoveCursorTo(10, 10),
+/// anes::RestoreCursorPosition
+/// )?;
+/// Ok(())
+/// }
+/// ```
+#[macro_export]
+macro_rules! execute {
+ ($dst:expr, $($sequence:expr),* $(,)?) => {{
+ if let Err(e) = $crate::queue!($dst, $($sequence),*) {
+ Err(e)
+ } else {
+ $dst.flush()
+ }
+ }}
+}
+
+#[cfg(test)]
+macro_rules! test_sequences {
+ (
+ $(
+ $name:ident(
+ $($left:expr => $right:expr),*
+ $(,)?
+ )
+ ),*
+ $(,)?
+ ) => {
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ $(
+ #[test]
+ fn $name() {
+ $(
+ assert_eq!(&format!("{}", $left), $right);
+ )*
+ }
+ )*
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::io::{Error, ErrorKind, Write};
+
+ #[test]
+ fn csi() {
+ assert_eq!(csi!("foo"), "\x1B[foo");
+ }
+
+ #[test]
+ fn esc() {
+ assert_eq!(esc!("bar"), "\x1Bbar");
+ }
+
+ #[test]
+ fn sgr() {
+ assert_eq!(sgr!("bar"), "\x1B[barm");
+ }
+
+ #[test]
+ fn static_struct_sequence() {
+ sequence!(
+ struct TestSeq => csi!("foo")
+ );
+
+ assert_eq!(&format!("{}", TestSeq), "\x1B[foo");
+ }
+
+ #[test]
+ fn static_enum_sequence() {
+ sequence!(
+ enum TestSeq {
+ Foo => csi!("foo"),
+ Bar => esc!("bar"),
+ }
+ );
+
+ assert_eq!(&format!("{}", TestSeq::Foo), "\x1B[foo");
+ assert_eq!(&format!("{}", TestSeq::Bar), "\x1Bbar");
+ }
+
+ #[test]
+ fn dynamic_struct_sequence() {
+ sequence!(
+ struct TestSeq(u16) =>
+ |this, f| write!(f, csi!("foo{}bar"), this.0)
+ );
+
+ assert_eq!(&format!("{}", TestSeq(10)), "\x1B[foo10bar");
+ }
+
+ #[test]
+ fn queue_allows_trailing_comma() {
+ let mut writer = Writer::default();
+
+ assert!(queue!(&mut writer, "foo",).is_ok());
+ assert_eq!(&writer.buffer, "foo");
+ }
+
+ #[test]
+ fn queue_writes_single_sequence() {
+ let mut writer = Writer::default();
+
+ assert!(queue!(&mut writer, "foo").is_ok());
+ assert_eq!(&writer.buffer, "foo");
+ }
+
+ #[test]
+ fn queue_writes_multiple_sequences() {
+ let mut writer = Writer::default();
+
+ assert!(queue!(&mut writer, "foo", "bar", "baz").is_ok());
+ assert_eq!(&writer.buffer, "foobarbaz");
+ }
+
+ #[test]
+ fn queue_does_not_flush() {
+ let mut writer = Writer::default();
+
+ assert!(queue!(&mut writer, "foo").is_ok());
+ assert!(!writer.flushed);
+ assert!(writer.flushed_buffer.is_empty());
+ }
+
+ #[test]
+ fn execute_allows_trailing_comma() {
+ let mut writer = Writer::default();
+
+ assert!(execute!(&mut writer, "foo",).is_ok());
+ assert_eq!(&writer.flushed_buffer, "foo");
+ }
+
+ #[test]
+ fn execute_writes_single_sequence() {
+ let mut writer = Writer::default();
+
+ assert!(execute!(&mut writer, "foo").is_ok());
+ assert_eq!(&writer.flushed_buffer, "foo");
+ }
+
+ #[test]
+ fn execute_writes_multiple_sequences() {
+ let mut writer = Writer::default();
+
+ assert!(execute!(&mut writer, "foo", "bar", "baz").is_ok());
+ assert_eq!(&writer.flushed_buffer, "foobarbaz");
+ }
+
+ #[test]
+ fn execute_does_flush() {
+ let mut writer = Writer::default();
+
+ assert!(execute!(&mut writer, "foo").is_ok());
+ assert!(writer.flushed);
+ assert_eq!(&writer.flushed_buffer, "foo");
+ assert!(writer.buffer.is_empty());
+ }
+
+ #[derive(Default)]
+ struct Writer {
+ buffer: String,
+ flushed_buffer: String,
+ flushed: bool,
+ }
+
+ impl Write for Writer {
+ fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
+ let s = std::str::from_utf8(buf).map_err(|_| ErrorKind::InvalidData)?;
+
+ self.buffer.push_str(s);
+ Ok(s.len())
+ }
+
+ fn flush(&mut self) -> Result<(), Error> {
+ self.flushed_buffer = self.buffer.clone();
+ self.buffer = String::new();
+ self.flushed = true;
+ Ok(())
+ }
+ }
+}
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..be87b29
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,252 @@
+//! An ANSI escape sequence parser module.
+//!
+//! **This module is not available with default features. You have to enable `parser` feature
+//! if you'd like to use it.**
+//!
+//! # Parser
+//!
+//! The ANSI escape sequence parser parses input data in two steps:
+//!
+//! * transforms input data into valid characters, generic csi & escape sequences, throws away invalid data,
+//! * give them meaning, throws away sequences without known meaning.
+//!
+//! ## First step
+//!
+//! State machine implementation for the first step is inspired by the
+//! [A parser for DEC’s ANSI-compatible video terminals](https://vt100.net/emu/dec_ansi_parser) article
+//! and the [vte](https://crates.io/crates/vte) crate. The goal of this step is to transform an input
+//! data into characters, generic csi & escape sequences, validate them and throw away malformed input.
+//!
+//! An example of valid csi sequence: `b"\x1B[20;10R"`. Output of the first step will be:
+//!
+//! * valid csi sequence
+//! * with two parameters `[20, 10]`
+//! * and the final character `R`.
+//!
+//! ## Second step
+//!
+//! An input of this step is output of the first one. We know that the final character `R` represents
+//! cursor position and two parameters should be provided. They were provided, we can give it a
+//! meaning and return `Sequence::CursorPosition(10, 20)`.
+//!
+//! All sequences without known meaning are discarded.
+//!
+//! ## Implementation
+//!
+//! Both steps are considered as an implementation detail and there's no plan to make them
+//! publicly available.
+//!
+//! The `parser` module provides the [`Parser`](struct.Parser.html) structure you can feed with
+//! the [`advance`](struct.Parser.html#method.advance) method. It also implements the standard
+//! library `Iterator<Item = Sequence>` trait which allows you to consume valid sequences with
+//! known meaning via the `next()` method. Check the [`Sequence`](enum.Sequence.html) enum to learn
+//! what this module can parse.
+use std::collections::VecDeque;
+
+use engine::{Engine, Provide};
+pub use types::{KeyCode, KeyModifiers, Mouse, MouseButton, Sequence};
+
+mod engine;
+mod parsers;
+pub(crate) mod types;
+
+/// An ANSI escape sequence parser.
+///
+/// `Parser` implements the `Iterator<Item = Sequence>` trait, thus you can use the
+/// `next()` method to consume all valid sequences with known meaning.
+///
+/// # Examples
+///
+/// Parse cursor position:
+///
+/// ```
+/// use anes::parser::{Parser, Sequence};
+///
+/// let mut parser = Parser::default();
+/// parser.advance(b"\x1B[20;10R", false);
+///
+/// assert_eq!(Some(Sequence::CursorPosition(10, 20)), parser.next());
+/// assert!(parser.next().is_none());
+/// ```
+///
+/// Parse keyboard event:
+///
+/// ```
+/// use anes::parser::{KeyCode, KeyModifiers, Parser, Sequence};
+///
+/// let mut parser = Parser::default();
+/// parser.advance("𐌼a".as_bytes(), false);
+///
+/// assert_eq!(Some(Sequence::Key(KeyCode::Char('𐌼'), KeyModifiers::empty())), parser.next());
+/// assert_eq!(Some(Sequence::Key(KeyCode::Char('a'), KeyModifiers::empty())), parser.next());
+/// assert!(parser.next().is_none());
+/// ```
+#[derive(Default)]
+pub struct Parser {
+ engine: Engine,
+ provider: SequenceProvider,
+}
+
+impl Parser {
+ /// Advances parser state machine with additional input data.
+ ///
+ /// # Arguments
+ ///
+ /// * `buffer` - input data (stdin in raw mode, etc.)
+ /// * `more` - more input data available right now
+ ///
+ /// It's crucial to provide correct `more` value in order to receive `KeyCode::Esc` events
+ /// as soon as possible.
+ ///
+ /// # Examples
+ ///
+ /// Esc key:
+ ///
+ /// ```
+ /// use anes::parser::{KeyCode, KeyModifiers, Parser, Sequence};
+ ///
+ /// let mut parser = Parser::default();
+ /// // User pressed Esc key & nothing else which means that there's no additional input available
+ /// // aka no possible escape sequence = `KeyCode::Esc` dispatched.
+ /// parser.advance(&[0x1b], false);
+ ///
+ /// assert_eq!(Some(Sequence::Key(KeyCode::Esc, KeyModifiers::empty())), parser.next());
+ /// assert!(parser.next().is_none());
+ /// ```
+ ///
+ /// Possible escape sequence:
+ ///
+ /// ```
+ /// use anes::parser::{KeyCode, KeyModifiers, Parser, Sequence};
+ ///
+ /// let mut parser = Parser::default();
+ /// // User pressed F1 = b"\x1BOP"
+ ///
+ /// // Every escape sequence starts with Esc (0x1b). There's more input available
+ /// // aka possible escape sequence = `KeyCode::Esc` isn't dispatched even when the parser
+ /// // doesn't know rest of the sequence.
+ /// parser.advance(&[0x1b], true);
+ /// assert!(parser.next().is_none());
+ ///
+ /// // Advance parser with rest of the sequence
+ /// parser.advance(&[b'O', b'P'], false);
+ /// assert_eq!(Some(Sequence::Key(KeyCode::F(1), KeyModifiers::empty())), parser.next());
+ /// assert!(parser.next().is_none());
+ /// ```
+ pub fn advance(&mut self, buffer: &[u8], more: bool) {
+ let len = buffer.len();
+ for (idx, byte) in buffer.iter().enumerate() {
+ self.engine
+ .advance(&mut self.provider, *byte, idx < len - 1 || more);
+ }
+ }
+}
+
+impl Iterator for Parser {
+ type Item = Sequence;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.provider.next()
+ }
+}
+
+#[derive(Default)]
+struct SequenceProvider {
+ esc_o: bool,
+ seqs: VecDeque<Sequence>,
+}
+
+impl Iterator for SequenceProvider {
+ type Item = Sequence;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.seqs.pop_front()
+ }
+}
+
+impl Provide for SequenceProvider {
+ fn provide_char(&mut self, ch: char) {
+ // eprintln!("dispatch_char: {}", ch);
+
+ if let Some(seq) = parsers::parse_char(ch, self.esc_o) {
+ self.seqs.push_back(seq);
+ }
+ self.esc_o = false;
+ }
+
+ fn provide_esc_sequence(&mut self, ch: char) {
+ if ch == 'O' {
+ // Exception
+ //
+ // Esc O - dispatched as an escape sequence followed by single character (P-S) representing
+ // F1-F4 keys. We store Esc O flag only which is then used in the dispatch_char method.
+ self.esc_o = true;
+ } else {
+ self.esc_o = false;
+ if let Some(seq) = parsers::parse_esc_sequence(ch) {
+ self.seqs.push_back(seq);
+ }
+ }
+ }
+
+ fn provide_csi_sequence(&mut self, parameters: &[u64], ignored_count: usize, ch: char) {
+ if let Some(seq) = parsers::parse_csi_sequence(parameters, ignored_count, ch) {
+ self.seqs.push_back(seq);
+ }
+
+ self.esc_o = false;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::Parser;
+
+ #[test]
+ fn dispatch_char() {
+ let mut parser = Parser::default();
+ parser.advance(&[b'a'], false);
+ assert!(parser.next().is_some());
+ }
+
+ #[test]
+ fn dispatch_esc_sequence() {
+ let mut parser = Parser::default();
+ parser.advance(&[b'\x1B'], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'a'], false);
+ assert!(parser.next().is_some());
+ }
+
+ #[test]
+ fn does_not_dispatch_esc_sequence_with_upper_case_o() {
+ let mut parser = Parser::default();
+ parser.advance(&[b'\x1B'], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'O'], true);
+ assert!(parser.next().is_none());
+ }
+
+ #[test]
+ fn dispatch_esc_with_upper_case_o_followed_by_char_as_single_sequence() {
+ let mut parser = Parser::default();
+ parser.advance(&[b'\x1B'], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'O'], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'P'], false);
+ assert!(parser.next().is_some());
+ assert!(parser.next().is_none());
+ }
+
+ #[test]
+ fn dispatch_csi_sequence() {
+ let mut parser = Parser::default();
+ parser.advance(&[b'\x1B'], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'['], true);
+ assert!(parser.next().is_none());
+ parser.advance(&[b'D'], false);
+ assert!(parser.next().is_some());
+ }
+}
diff --git a/src/parser/engine.rs b/src/parser/engine.rs
new file mode 100644
index 0000000..645208d
--- /dev/null
+++ b/src/parser/engine.rs
@@ -0,0 +1,614 @@
+//
+// https://vt100.net/emu/dec_ansi_parser
+//
+// The parser is heavily inspired by the vte (https://crates.io/crates/vte) crate.
+// Tried to use this crate, but it doesn't work for opposite way (terminal -> sequence),
+// because there're couple of exceptions we have to handle and it doesn't make much
+// sense to add them to the vte crate. An example is Esc key where we need to know if
+// there's additional input available or not and then the decision is made if the
+// Esc char is dispatched immediately (user hits just Esc key) or if it's an escape/csi/...
+// sequence.
+//
+const MAX_PARAMETERS: usize = 30;
+const DEFAULT_PARAMETER_VALUE: u64 = 0;
+const MAX_UTF8_CODE_POINTS: usize = 4;
+
+/// A parser engine state.
+///
+/// All these variant names come from the
+/// [A parser for DEC’s ANSI-compatible video terminals](https://vt100.net/emu/dec_ansi_parser)
+/// description.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+enum State {
+ /// Initial state.
+ Ground,
+ /// Escape sequence started.
+ ///
+ /// `Esc` received with a flag that there's more data available.
+ Escape,
+ /// Escape sequence and we're collecting intermediates.
+ ///
+ /// # Notes
+ ///
+ /// This implementation doesn't collect intermediates. It just handles the state
+ /// to distinguish between (im)proper sequences.
+ EscapeIntermediate,
+ /// CSI sequence started.
+ ///
+ /// `Esc` followed by the `[` received.
+ CsiEntry,
+ /// CSI sequence should be consumed, but not dispatched.
+ CsiIgnore,
+ /// CSI sequence and we're collecting parameters.
+ CsiParameter,
+ /// CSI sequence and we're collecting intermediates.
+ ///
+ /// # Notes
+ ///
+ /// This implementation doesn't collect intermediates. It just handles the state
+ /// to distinguish between (im)proper sequences.
+ CsiIntermediate,
+ /// Possible UTF-8 sequence and we're collecting UTF-8 code points.
+ Utf8,
+}
+
+pub(crate) trait Provide {
+ fn provide_char(&mut self, ch: char);
+
+ fn provide_esc_sequence(&mut self, ch: char);
+
+ fn provide_csi_sequence(&mut self, parameters: &[u64], ignored_count: usize, ch: char);
+}
+
+pub(crate) struct Engine {
+ parameters: [u64; MAX_PARAMETERS],
+ parameters_count: usize,
+ parameter: u64,
+ ignored_parameters_count: usize,
+ state: State,
+ utf8_points: [u8; MAX_UTF8_CODE_POINTS],
+ utf8_points_count: usize,
+ utf8_points_expected_count: usize,
+}
+
+impl Default for Engine {
+ fn default() -> Self {
+ Engine {
+ parameters: [DEFAULT_PARAMETER_VALUE; MAX_PARAMETERS],
+ parameters_count: 0,
+ parameter: DEFAULT_PARAMETER_VALUE,
+ ignored_parameters_count: 0,
+ state: State::Ground,
+ utf8_points: [0; MAX_UTF8_CODE_POINTS],
+ utf8_points_count: 0,
+ utf8_points_expected_count: 0,
+ }
+ }
+}
+
+impl Engine {
+ fn set_state(&mut self, state: State) {
+ if let State::Ground = state {
+ self.parameters_count = 0;
+ self.parameter = DEFAULT_PARAMETER_VALUE;
+ self.ignored_parameters_count = 0;
+ self.utf8_points_count = 0;
+ self.utf8_points_expected_count = 0;
+ }
+ self.state = state;
+ }
+
+ fn store_parameter(&mut self) {
+ if self.parameters_count < MAX_PARAMETERS {
+ self.parameters[self.parameters_count] = self.parameter;
+ self.parameters_count += 1;
+ } else {
+ self.ignored_parameters_count += 1;
+ }
+ self.parameter = DEFAULT_PARAMETER_VALUE;
+ }
+
+ fn handle_possible_esc(&mut self, provider: &mut dyn Provide, byte: u8, more: bool) -> bool {
+ if byte != 0x1B {
+ return false;
+ }
+
+ match (self.state, more) {
+ // More input means possible Esc sequence, just switch state and wait
+ (State::Ground, true) => self.set_state(State::Escape),
+
+ // No more input means Esc key, dispatch it
+ (State::Ground, false) => provider.provide_char('\x1B'),
+
+ // More input means possible Esc sequence, dispatch the previous Esc char
+ (State::Escape, true) => provider.provide_char('\x1B'),
+
+ // No more input means Esc key, dispatch the previous & current Esc char
+ (State::Escape, false) => {
+ provider.provide_char('\x1B');
+ provider.provide_char('\x1B');
+ self.set_state(State::Ground);
+ }
+
+ // Discard any state
+ // More input means possible Esc sequence
+ (_, true) => self.set_state(State::Escape),
+
+ // Discard any state
+ // No more input means Esc key, dispatch it
+ (_, false) => {
+ provider.provide_char('\x1B');
+ self.set_state(State::Ground);
+ }
+ }
+
+ true
+ }
+
+ fn handle_possible_utf8_code_points(&mut self, provider: &mut dyn Provide, byte: u8) -> bool {
+ if byte & 0b1000_0000 == 0b0000_0000 {
+ provider.provide_char(byte as char);
+ true
+ } else if byte & 0b1110_0000 == 0b1100_0000 {
+ self.utf8_points_count = 1;
+ self.utf8_points[0] = byte;
+ self.utf8_points_expected_count = 2;
+ self.set_state(State::Utf8);
+ true
+ } else if byte & 0b1111_0000 == 0b1110_0000 {
+ self.utf8_points_count = 1;
+ self.utf8_points[0] = byte;
+ self.utf8_points_expected_count = 3;
+ self.set_state(State::Utf8);
+ true
+ } else if byte & 0b1111_1000 == 0b1111_0000 {
+ self.utf8_points_count = 1;
+ self.utf8_points[0] = byte;
+ self.utf8_points_expected_count = 4;
+ self.set_state(State::Utf8);
+ true
+ } else {
+ false
+ }
+ }
+
+ fn advance_ground_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ if self.handle_possible_utf8_code_points(provider, byte) {
+ return;
+ }
+
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // Print
+ 0x20..=0x7F => provider.provide_char(byte as char),
+
+ _ => {}
+ };
+ }
+
+ fn advance_escape_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Intermediate bytes to collect
+ 0x20..=0x2F => {
+ self.set_state(State::EscapeIntermediate);
+ }
+
+ // Escape followed by '[' (0x5B)
+ // -> CSI sequence start
+ 0x5B => self.set_state(State::CsiEntry),
+
+ // Escape sequence final character
+ 0x30..=0x4F | 0x51..=0x57 | 0x59 | 0x5A | 0x5C | 0x60..=0x7E => {
+ provider.provide_esc_sequence(byte as char);
+ self.set_state(State::Ground);
+ }
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x7F => {}
+
+ // Other bytes are considered as invalid -> cancel whatever we have
+ _ => self.set_state(State::Ground),
+ };
+ }
+
+ fn advance_escape_intermediate_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Intermediate bytes to collect
+ 0x20..=0x2F => {}
+
+ // Escape followed by '[' (0x5B)
+ // -> CSI sequence start
+ 0x5B => self.set_state(State::CsiEntry),
+
+ // Escape sequence final character
+ 0x30..=0x5A | 0x5C..=0x7E => {
+ provider.provide_esc_sequence(byte as char);
+ self.set_state(State::Ground);
+ }
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x7F => {}
+
+ // Other bytes are considered as invalid -> cancel whatever we have
+ _ => self.set_state(State::Ground),
+ };
+ }
+
+ fn advance_csi_entry_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Semicolon = parameter delimiter
+ 0x3B => {
+ self.store_parameter();
+ self.set_state(State::CsiParameter);
+ }
+
+ // '0' ..= '9' = parameter value
+ 0x30..=0x39 => {
+ self.parameter = (byte as u64) - 0x30;
+ self.set_state(State::CsiParameter);
+ }
+
+ 0x3A => self.set_state(State::CsiIgnore),
+
+ // CSI sequence final character
+ // -> dispatch CSI sequence
+ 0x40..=0x7E => {
+ provider.provide_csi_sequence(
+ &self.parameters[..self.parameters_count],
+ self.ignored_parameters_count,
+ byte as char,
+ );
+
+ self.set_state(State::Ground);
+ }
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x7F => {}
+
+ // Collect rest as parameters
+ _ => {
+ self.parameter = byte as u64;
+ self.store_parameter();
+ }
+ };
+ }
+
+ fn advance_csi_ignore_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x20..=0x3F | 0x7F => {}
+
+ 0x40..=0x7E => self.set_state(State::Ground),
+
+ // Other bytes are considered as invalid -> cancel whatever we have
+ _ => self.set_state(State::Ground),
+ };
+ }
+
+ fn advance_csi_parameter_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // '0' ..= '9' = parameter value
+ 0x30..=0x39 => {
+ self.parameter = self.parameter.saturating_mul(10);
+ self.parameter = self.parameter.saturating_add((byte as u64) - 0x30);
+ }
+
+ // Semicolon = parameter delimiter
+ 0x3B => self.store_parameter(),
+
+ // CSI sequence final character
+ // -> dispatch CSI sequence
+ 0x40..=0x7E => {
+ self.store_parameter();
+ provider.provide_csi_sequence(
+ &self.parameters[..self.parameters_count],
+ self.ignored_parameters_count,
+ byte as char,
+ );
+
+ self.set_state(State::Ground);
+ }
+
+ // Intermediates to collect
+ 0x20..=0x2F => {
+ self.store_parameter();
+ self.set_state(State::CsiIntermediate);
+ }
+
+ // Ignore
+ 0x3A | 0x3C..=0x3F => self.set_state(State::CsiIgnore),
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x7F => {}
+
+ // Other bytes are considered as invalid -> cancel whatever we have
+ _ => self.set_state(State::Ground),
+ };
+ }
+
+ fn advance_csi_intermediate_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ match byte {
+ 0x1B => unreachable!(),
+
+ // Intermediates to collect
+ 0x20..=0x2F => {}
+
+ // CSI sequence final character
+ // -> dispatch CSI sequence
+ 0x40..=0x7E => {
+ provider.provide_csi_sequence(
+ &self.parameters[..self.parameters_count],
+ self.ignored_parameters_count,
+ byte as char,
+ );
+
+ self.set_state(State::Ground);
+ }
+
+ // Execute
+ 0x00..=0x17 | 0x19 | 0x1C..=0x1F => provider.provide_char(byte as char),
+
+ // TODO Does it mean we should ignore the whole sequence?
+ // Ignore
+ 0x7F => {}
+
+ // Other bytes are considered as invalid -> cancel whatever we have
+ _ => self.set_state(State::Ground),
+ }
+ }
+
+ fn advance_utf8_state(&mut self, provider: &mut dyn Provide, byte: u8) {
+ if byte & 0b1100_0000 != 0b1000_0000 {
+ self.set_state(State::Ground);
+ return;
+ }
+
+ self.utf8_points[self.utf8_points_count] = byte;
+ self.utf8_points_count += 1;
+
+ if self.utf8_points_count == self.utf8_points_expected_count {
+ if let Some(ch) = std::str::from_utf8(&self.utf8_points[..self.utf8_points_count])
+ .ok()
+ .and_then(|s| s.chars().next())
+ {
+ provider.provide_char(ch);
+ }
+ self.set_state(State::Ground);
+ }
+ }
+
+ pub(crate) fn advance(&mut self, provider: &mut dyn Provide, byte: u8, more: bool) {
+ // eprintln!("advance: {:?} {} {}", self.state, byte, more);
+
+ if self.handle_possible_esc(provider, byte, more) {
+ return;
+ }
+
+ match self.state {
+ State::Ground => self.advance_ground_state(provider, byte),
+ State::Escape => self.advance_escape_state(provider, byte),
+ State::EscapeIntermediate => self.advance_escape_intermediate_state(provider, byte),
+ State::CsiEntry => self.advance_csi_entry_state(provider, byte),
+ State::CsiIgnore => self.advance_csi_ignore_state(provider, byte),
+ State::CsiParameter => self.advance_csi_parameter_state(provider, byte),
+ State::CsiIntermediate => self.advance_csi_intermediate_state(provider, byte),
+ State::Utf8 => self.advance_utf8_state(provider, byte),
+ };
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn esc_char() {
+ let mut engine = Engine::default();
+ let mut provider = CharProvider::default();
+
+ // No more input means that the Esc character should be dispatched immediately
+ engine.advance(&mut provider, 0x1B, false);
+ assert_eq!(provider.chars, &['\x1B']);
+
+ // There's more input so the machine should wait before dispatching Esc character
+ engine.advance(&mut provider, 0x1B, true);
+ assert_eq!(provider.chars, &['\x1B']);
+
+ // Another Esc character, but no more input, machine should dispatch the postponed Esc
+ // character and the new one too.
+ engine.advance(&mut provider, 0x1B, false);
+ assert_eq!(provider.chars, &['\x1B', '\x1B', '\x1B']);
+ }
+
+ #[test]
+ fn esc_without_intermediates() {
+ let mut engine = Engine::default();
+ let mut provider = EscProvider::default();
+
+ let input = b"\x1B0\x1B~";
+ advance(&mut engine, &mut provider, input, false);
+
+ assert_eq!(provider.chars.len(), 2);
+
+ assert_eq!(provider.chars[0], '0');
+
+ assert_eq!(provider.chars[1], '~');
+ }
+
+ #[test]
+ fn csi_without_parameters() {
+ let mut engine = Engine::default();
+ let mut provider = CsiProvider::default();
+
+ let input = b"\x1B\x5Bm";
+ advance(&mut engine, &mut provider, input, false);
+
+ assert_eq!(provider.parameters.len(), 1);
+ assert_eq!(provider.parameters[0], &[]);
+ assert_eq!(provider.chars.len(), 1);
+ assert_eq!(provider.chars[0], 'm');
+ }
+
+ #[test]
+ fn csi_with_two_default_parameters() {
+ let mut engine = Engine::default();
+ let mut provider = CsiProvider::default();
+
+ let input = b"\x1B\x5B;m";
+ advance(&mut engine, &mut provider, input, false);
+
+ assert_eq!(provider.parameters.len(), 1);
+ assert_eq!(
+ provider.parameters[0],
+ &[DEFAULT_PARAMETER_VALUE, DEFAULT_PARAMETER_VALUE]
+ );
+ assert_eq!(provider.chars.len(), 1);
+ assert_eq!(provider.chars[0], 'm');
+ }
+
+ #[test]
+ fn csi_with_trailing_semicolon() {
+ let mut engine = Engine::default();
+ let mut provider = CsiProvider::default();
+
+ let input = b"\x1B\x5B123;m";
+ advance(&mut engine, &mut provider, input, false);
+
+ assert_eq!(provider.parameters.len(), 1);
+ assert_eq!(provider.parameters[0], &[123, DEFAULT_PARAMETER_VALUE]);
+ assert_eq!(provider.chars.len(), 1);
+ assert_eq!(provider.chars[0], 'm');
+ }
+
+ #[test]
+ fn csi_max_parameters() {
+ let mut engine = Engine::default();
+ let mut provider = CsiProvider::default();
+
+ let input = b"\x1B\x5B1;2;3;4;5;6;7;8;9;10;11;12;13;14;15;16;17;18;19;20;21;22;23;24;25;26;27;28;29;30m";
+ advance(&mut engine, &mut provider, input, false);
+
+ assert_eq!(provider.parameters.len(), 1);
+ assert_eq!(provider.parameters[0].len(), MAX_PARAMETERS);
+ assert_eq!(
+ provider.parameters[0],
+ &[
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30
+ ]
+ );
+ assert_eq!(provider.chars.len(), 1);
+ assert_eq!(provider.chars[0], 'm');
+ }
+
+ #[test]
+ fn test_parse_utf8_character() {
+ let mut engine = Engine::default();
+ let mut provider = CharProvider::default();
+
+ advance(&mut engine, &mut provider, &['a' as u8], false);
+ assert_eq!(provider.chars.len(), 1);
+ assert_eq!(provider.chars[0], 'a');
+
+ advance(&mut engine, &mut provider, &[0xC3, 0xB1], false);
+ assert_eq!(provider.chars.len(), 2);
+ assert_eq!(provider.chars[1], 'ñ');
+
+ advance(&mut engine, &mut provider, &[0xE2, 0x81, 0xA1], false);
+ assert_eq!(provider.chars.len(), 3);
+ assert_eq!(provider.chars[2], '\u{2061}');
+
+ advance(&mut engine, &mut provider, &[0xF0, 0x90, 0x8C, 0xBC], false);
+ assert_eq!(provider.chars.len(), 4);
+ assert_eq!(provider.chars[3], '𐌼');
+ }
+
+ fn advance(engine: &mut Engine, provider: &mut dyn Provide, bytes: &[u8], more: bool) {
+ let len = bytes.len();
+
+ for (i, byte) in bytes.iter().enumerate() {
+ engine.advance(provider, *byte, i < len - 1 || more);
+ }
+ }
+
+ #[derive(Default)]
+ struct CharProvider {
+ chars: Vec<char>,
+ }
+
+ impl Provide for CharProvider {
+ fn provide_char(&mut self, ch: char) {
+ self.chars.push(ch);
+ }
+
+ fn provide_esc_sequence(&mut self, _ch: char) {}
+
+ fn provide_csi_sequence(&mut self, _parameters: &[u64], _ignored_count: usize, _ch: char) {}
+ }
+
+ #[derive(Default)]
+ struct CsiProvider {
+ parameters: Vec<Vec<u64>>,
+ chars: Vec<char>,
+ }
+
+ impl Provide for CsiProvider {
+ fn provide_char(&mut self, _ch: char) {}
+
+ fn provide_esc_sequence(&mut self, _ch: char) {}
+
+ fn provide_csi_sequence(&mut self, parameters: &[u64], _ignored_count: usize, ch: char) {
+ self.parameters.push(parameters.to_vec());
+ self.chars.push(ch);
+ }
+ }
+
+ #[derive(Default)]
+ struct EscProvider {
+ chars: Vec<char>,
+ }
+
+ impl Provide for EscProvider {
+ fn provide_char(&mut self, _ch: char) {}
+
+ fn provide_esc_sequence(&mut self, ch: char) {
+ self.chars.push(ch);
+ }
+
+ fn provide_csi_sequence(&mut self, _parameters: &[u64], _ignored_count: usize, _ch: char) {}
+ }
+}
diff --git a/src/parser/parsers.rs b/src/parser/parsers.rs
new file mode 100644
index 0000000..9bb9acb
--- /dev/null
+++ b/src/parser/parsers.rs
@@ -0,0 +1,239 @@
+use super::types::{KeyCode, KeyModifiers, Mouse, MouseButton, Sequence};
+
+pub(crate) fn parse_char(ch: char, esc_o: bool) -> Option<Sequence> {
+ if esc_o {
+ return match ch {
+ 'P'..='S' => Some(Sequence::Key(
+ KeyCode::F(ch as u8 - b'P' + 1),
+ KeyModifiers::empty(),
+ )),
+ _ => None,
+ };
+ }
+
+ let code = match ch {
+ '\r' | '\n' => KeyCode::Enter,
+ '\t' => KeyCode::Tab,
+ '\x7F' => KeyCode::BackTab,
+ '\x1B' => KeyCode::Esc,
+ '\0' => KeyCode::Null,
+ _ => KeyCode::Char(ch),
+ };
+ Some(Sequence::Key(code, KeyModifiers::empty()))
+}
+
+pub(crate) fn parse_esc_sequence(ch: char) -> Option<Sequence> {
+ // EscO[P-S] is handled in the Performer, see parse_char & esc_o argument
+ // No need to handle other cases here? It's just Alt+$char
+ Some(Sequence::Key(KeyCode::Char(ch), KeyModifiers::ALT))
+}
+
+pub(crate) fn parse_csi_sequence(
+ parameters: &[u64],
+ _ignored_count: usize,
+ ch: char,
+) -> Option<Sequence> {
+ match ch {
+ 'A' => Some(Sequence::Key(
+ KeyCode::Up,
+ parse_csi_arrow_key_modifiers(parameters.first().cloned()),
+ )),
+ 'B' => Some(Sequence::Key(
+ KeyCode::Down,
+ parse_csi_arrow_key_modifiers(parameters.first().cloned()),
+ )),
+ 'C' => Some(Sequence::Key(
+ KeyCode::Right,
+ parse_csi_arrow_key_modifiers(parameters.first().cloned()),
+ )),
+ 'D' => Some(Sequence::Key(
+ KeyCode::Left,
+ parse_csi_arrow_key_modifiers(parameters.first().cloned()),
+ )),
+ 'H' => Some(Sequence::Key(KeyCode::Home, KeyModifiers::empty())),
+ 'F' => Some(Sequence::Key(KeyCode::End, KeyModifiers::empty())),
+ 'Z' => Some(Sequence::Key(KeyCode::BackTab, KeyModifiers::empty())),
+ 'R' => parse_csi_cursor_position(parameters),
+ 'm' => parse_csi_xterm_mouse(parameters, ch),
+ 'M' if parameters.first() == Some(&0x3C) => parse_csi_xterm_mouse(parameters, ch),
+ 'M' => parse_csi_rxvt_mouse(parameters),
+ '~' => parse_csi_tilde_key_code(parameters),
+ _ => None,
+ }
+}
+
+fn parse_csi_arrow_key_modifiers(parameter: Option<u64>) -> KeyModifiers {
+ parse_key_modifiers(parameter.map(|x| x.saturating_sub(48)))
+}
+
+fn parse_key_modifiers(parameter: Option<u64>) -> KeyModifiers {
+ if let Some(parameter) = parameter {
+ match parameter {
+ 2 => KeyModifiers::SHIFT,
+ 3 => KeyModifiers::ALT,
+ 4 => KeyModifiers::SHIFT | KeyModifiers::ALT,
+ 5 => KeyModifiers::CONTROL,
+ 6 => KeyModifiers::SHIFT | KeyModifiers::CONTROL,
+ 7 => KeyModifiers::ALT | KeyModifiers::CONTROL,
+ 8 => KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL,
+ 9 => KeyModifiers::META,
+ 10 => KeyModifiers::META | KeyModifiers::SHIFT,
+ 11 => KeyModifiers::META | KeyModifiers::ALT,
+ 12 => KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::ALT,
+ 13 => KeyModifiers::META | KeyModifiers::CONTROL,
+ 14 => KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::CONTROL,
+ 15 => KeyModifiers::META | KeyModifiers::ALT | KeyModifiers::CONTROL,
+ 16 => {
+ KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL
+ }
+ _ => KeyModifiers::empty(),
+ }
+ } else {
+ KeyModifiers::empty()
+ }
+}
+
+fn parse_csi_tilde_key_code(parameters: &[u64]) -> Option<Sequence> {
+ if parameters.is_empty() {
+ return None;
+ }
+
+ let modifiers = parse_key_modifiers(parameters.get(1).cloned());
+
+ let code = match parameters[0] {
+ 1 | 7 => KeyCode::Home,
+ 2 => KeyCode::Insert,
+ 3 => KeyCode::Delete,
+ 4 | 8 => KeyCode::End,
+ 5 => KeyCode::PageUp,
+ 6 => KeyCode::PageDown,
+ p @ 11..=15 => KeyCode::F(p as u8 - 10),
+ p @ 17..=21 => KeyCode::F(p as u8 - 11),
+ p @ 23..=24 => KeyCode::F(p as u8 - 12),
+ _ => return None,
+ };
+
+ Some(Sequence::Key(code, modifiers))
+}
+
+fn parse_csi_cursor_position(parameters: &[u64]) -> Option<Sequence> {
+ // ESC [ Cy ; Cx R
+
+ if parameters.len() < 2 {
+ return None;
+ }
+
+ let y = parameters[0] as u16;
+ let x = parameters[1] as u16;
+
+ Some(Sequence::CursorPosition(x, y))
+}
+
+fn parse_csi_xterm_mouse(parameters: &[u64], ch: char) -> Option<Sequence> {
+ // ESC [ < Cb ; Cx ; Cy (;) (M or m)
+
+ if parameters.len() < 4 {
+ return None;
+ }
+
+ let cb = parameters[1] as u8;
+ let cx = parameters[2] as u16;
+ let cy = parameters[3] as u16;
+
+ let up = match ch {
+ 'm' => true,
+ 'M' => false,
+ _ => return None,
+ };
+
+ let mut modifiers = KeyModifiers::empty();
+
+ if cb & 0b0000_0100 == 0b0000_0100 {
+ modifiers |= KeyModifiers::SHIFT;
+ }
+
+ if cb & 0b0000_1000 == 0b0000_1000 {
+ modifiers |= KeyModifiers::ALT;
+ }
+
+ if cb & 0b0001_0000 == 0b0001_0000 {
+ modifiers |= KeyModifiers::CONTROL;
+ }
+
+ let mouse = if cb & 0b0100_0000 == 0b0100_0000 {
+ if cb & 0b0000_0001 == 0b0000_0001 {
+ Mouse::ScrollDown(cx, cy)
+ } else {
+ Mouse::ScrollUp(cx, cy)
+ }
+ } else {
+ let drag = cb & 0b0010_0000 == 0b0010_0000;
+
+ match (cb & 0b0000_0011, up, drag) {
+ (0, true, _) => Mouse::Up(MouseButton::Left, cx, cy),
+ (0, false, false) => Mouse::Down(MouseButton::Left, cx, cy),
+ (0, false, true) => Mouse::Drag(MouseButton::Left, cx, cy),
+ (1, true, _) => Mouse::Up(MouseButton::Middle, cx, cy),
+ (1, false, false) => Mouse::Down(MouseButton::Middle, cx, cy),
+ (1, false, true) => Mouse::Drag(MouseButton::Middle, cx, cy),
+ (2, true, _) => Mouse::Up(MouseButton::Right, cx, cy),
+ (2, false, false) => Mouse::Down(MouseButton::Right, cx, cy),
+ (2, false, true) => Mouse::Drag(MouseButton::Right, cx, cy),
+ _ => return None,
+ }
+ };
+
+ Some(Sequence::Mouse(mouse, modifiers))
+}
+
+fn parse_csi_rxvt_mouse(parameters: &[u64]) -> Option<Sequence> {
+ // ESC [ Cb ; Cx ; Cy ; M
+
+ if parameters.len() < 3 {
+ return None;
+ }
+
+ let cb = parameters[0];
+ let cx = parameters[1] as u16;
+ let cy = parameters[2] as u16;
+
+ let mut modifiers = KeyModifiers::empty();
+
+ if cb & 0b0000_0100 == 0b0000_0100 {
+ modifiers |= KeyModifiers::SHIFT;
+ }
+
+ if cb & 0b0000_1000 == 0b0000_1000 {
+ modifiers |= KeyModifiers::ALT;
+ }
+
+ if cb & 0b0001_0000 == 0b0001_0000 {
+ modifiers |= KeyModifiers::CONTROL;
+ }
+
+ let mouse = if cb & 0b0110_0000 == 0b0110_0000 {
+ if cb & 0b0000_0001 == 0b0000_0001 {
+ Mouse::ScrollDown(cx, cy)
+ } else {
+ Mouse::ScrollUp(cx, cy)
+ }
+ } else {
+ let drag = cb & 0b0100_0000 == 0b0100_0000;
+
+ match (cb & 0b0000_0011, drag) {
+ (0b0000_0000, false) => Mouse::Down(MouseButton::Left, cx, cy),
+ (0b0000_0010, false) => Mouse::Down(MouseButton::Right, cx, cy),
+ (0b0000_0001, false) => Mouse::Down(MouseButton::Middle, cx, cy),
+
+ (0b0000_0000, true) => Mouse::Drag(MouseButton::Left, cx, cy),
+ (0b0000_0010, true) => Mouse::Drag(MouseButton::Right, cx, cy),
+ (0b0000_0001, true) => Mouse::Drag(MouseButton::Middle, cx, cy),
+
+ (0b0000_0011, false) => Mouse::Up(MouseButton::Any, cx, cy),
+
+ _ => return None,
+ }
+ };
+
+ Some(Sequence::Mouse(mouse, modifiers))
+}
diff --git a/src/parser/types.rs b/src/parser/types.rs
new file mode 100644
index 0000000..66b6561
--- /dev/null
+++ b/src/parser/types.rs
@@ -0,0 +1,79 @@
+use bitflags::bitflags;
+
+/// A parsed ANSI escape sequence.
+///
+/// Check the [`Parser`](struct.Parser.html) structure documentation for examples
+/// how to retrieve these values.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Sequence {
+ /// A keyboard event sequence.
+ Key(KeyCode, KeyModifiers),
+ /// A mouse event sequence.
+ Mouse(Mouse, KeyModifiers),
+ /// A cursor position (`x`, `y`).
+ ///
+ /// Top/left cell is represented as `Sequence::CursorPosition(1, 1)`.
+ CursorPosition(u16, u16),
+}
+
+bitflags! {
+ /// A key modifiers.
+ pub struct KeyModifiers: u8 {
+ const SHIFT = 0b0000_0001;
+ const CONTROL = 0b0000_0010;
+ const ALT = 0b0000_0100;
+ const META = 0b0000_1000;
+ }
+}
+
+/// A key code.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum KeyCode {
+ Backspace,
+ Enter,
+ Left,
+ Right,
+ Up,
+ Down,
+ Home,
+ End,
+ PageUp,
+ PageDown,
+ Tab,
+ BackTab,
+ Delete,
+ Insert,
+ F(u8),
+ Char(char),
+ Null,
+ Esc,
+}
+
+/// A mouse event.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum Mouse {
+ /// A mouse button press.
+ Down(MouseButton, u16, u16),
+ /// A mouse button release.
+ Up(MouseButton, u16, u16),
+ /// A mouse movement with pressed button.
+ Drag(MouseButton, u16, u16),
+ /// A mouse wheel scrolled up.
+ ScrollUp(u16, u16),
+ /// A mouse wheel scrolled down.
+ ScrollDown(u16, u16),
+}
+
+/// A mouse button.
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum MouseButton {
+ Left,
+ Right,
+ Middle,
+ /// This variant is provided only if [`Parser`](struct.Parser.html) doesn't know which
+ /// mouse button was pressed/released.
+ ///
+ /// An example is [rxvt](https://en.wikipedia.org/wiki/Rxvt) - it provides which mouse
+ /// button was pressed, but doesn't provide which mouse button was released.
+ Any,
+}
diff --git a/src/sequences.rs b/src/sequences.rs
new file mode 100644
index 0000000..c660800
--- /dev/null
+++ b/src/sequences.rs
@@ -0,0 +1,5 @@
+pub(crate) mod attribute;
+pub(crate) mod buffer;
+pub(crate) mod color;
+pub(crate) mod cursor;
+pub(crate) mod terminal;
diff --git a/src/sequences/attribute.rs b/src/sequences/attribute.rs
new file mode 100644
index 0000000..30961ff
--- /dev/null
+++ b/src/sequences/attribute.rs
@@ -0,0 +1,133 @@
+use std::fmt;
+
+sequence!(
+ /// Resets all attributes.
+ ///
+ /// This sequence resets all attributes previously set by the:
+ ///
+ /// * [`SetAttribute`](struct.SetAttribute.html)
+ /// * [`SetForegroundColor`](struct.SetBackgroundColor.html)
+ /// * [`SetBackgroundColor`](struct.SetForegroundColor.html)
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ResetAttributes;
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}", ResetAttributes);
+ /// ```
+ struct ResetAttributes => sgr!("0")
+);
+
+/// A display attribute.
+///
+/// This is **NOT** a full ANSI sequence. `Attribute` must be used along with
+/// the [`SetAttribute`](struct.SetAttribute.html).
+///
+/// # Examples
+///
+/// ```no_run
+/// use std::io::{stdout, Write};
+/// use anes::{Attribute, SetAttribute};
+///
+/// let mut stdout = stdout();
+/// write!(stdout, "{}Bold text", SetAttribute(Attribute::Bold));
+/// ```
+#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+pub enum Attribute {
+ /// Bold (increased) intensity.
+ Bold = 1,
+ /// Faint (decreased) intensity.
+ Faint = 2,
+ /// Normal intensity (turns off `Bold` and/or `Faint`).
+ Normal = 22,
+
+ /// Italic.
+ Italic = 3,
+ /// Turns off `Italic`.
+ ItalicOff = 23,
+
+ /// Underlined text.
+ Underline = 4,
+ /// Turns off `Underline`.
+ UnderlineOff = 24,
+
+ /// Blinking text.
+ Blink = 5,
+ /// Turns off blinking text (`Blink`).
+ BlinkOff = 25,
+
+ /// Reverse foreground & background colors.
+ Reverse = 7,
+ /// Turns off `Reverse`.
+ ReverseOff = 27,
+
+ /// Concealed (hidden).
+ Conceal = 8,
+ /// Turns off `Conceal`.
+ ConcealOff = 28,
+
+ /// Crossed.
+ Crossed = 9,
+ /// Turns off `Crossed`.
+ CrossedOff = 29,
+}
+
+impl fmt::Display for Attribute {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", *self as i32)
+ }
+}
+
+sequence!(
+ /// Sets the display attribute.
+ ///
+ /// See the [`Attribute`](enum.Attribute.html) enum for a list of attributes you can (un)set.
+ ///
+ /// The [`ResetAttributes`](struct.ResetAttributes.html) sequence can be used to turn off all
+ /// attributes.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{Attribute, SetAttribute};
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}Blinking text", SetAttribute(Attribute::Blink));
+ /// ```
+ struct SetAttribute(Attribute) =>
+ |this, f| write!(f, sgr!("{}"), this.0)
+);
+
+#[cfg(test)]
+test_sequences!(
+ set_attribute(
+ SetAttribute(Attribute::Bold) => "\x1B[1m",
+ SetAttribute(Attribute::Faint) => "\x1B[2m",
+ SetAttribute(Attribute::Normal) => "\x1B[22m",
+
+ SetAttribute(Attribute::Italic) => "\x1B[3m",
+ SetAttribute(Attribute::ItalicOff) => "\x1B[23m",
+
+ SetAttribute(Attribute::Underline) => "\x1B[4m",
+ SetAttribute(Attribute::UnderlineOff) => "\x1B[24m",
+
+ SetAttribute(Attribute::Blink) => "\x1B[5m",
+ SetAttribute(Attribute::BlinkOff) => "\x1B[25m",
+
+ SetAttribute(Attribute::Reverse) => "\x1B[7m",
+ SetAttribute(Attribute::ReverseOff) => "\x1B[27m",
+
+ SetAttribute(Attribute::Conceal) => "\x1B[8m",
+ SetAttribute(Attribute::ConcealOff) => "\x1B[28m",
+
+ SetAttribute(Attribute::Crossed) => "\x1B[9m",
+ SetAttribute(Attribute::CrossedOff) => "\x1B[29m",
+ ),
+ reset_attributes(
+ ResetAttributes => "\x1B[0m",
+ )
+);
diff --git a/src/sequences/buffer.rs b/src/sequences/buffer.rs
new file mode 100644
index 0000000..053195a
--- /dev/null
+++ b/src/sequences/buffer.rs
@@ -0,0 +1,145 @@
+sequence!(
+ /// Switches to the alternate buffer.
+ ///
+ /// Use the [`SwitchBufferToNormal`](struct.SwitchBufferToNormal.html) sequence to switch
+ /// back to the normal buffer.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{SwitchBufferToAlternate, SwitchBufferToNormal};
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}", SwitchBufferToAlternate);
+ /// // Your app on alternate screen
+ /// write!(stdout, "{}", SwitchBufferToNormal);
+ /// ```
+ struct SwitchBufferToAlternate => csi!("?1049h")
+);
+
+sequence!(
+ /// Switches to the normal buffer.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{SwitchBufferToAlternate, SwitchBufferToNormal};
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}", SwitchBufferToAlternate);
+ /// // Your app on alternate screen
+ /// write!(stdout, "{}", SwitchBufferToNormal);
+ /// ```
+ struct SwitchBufferToNormal => csi!("?1049l")
+);
+
+sequence!(
+ /// Scrolls up by the given number of rows.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ScrollBufferUp;
+ ///
+ /// let mut stdout = stdout();
+ /// // Scroll up by 5 lines
+ /// write!(stdout, "{}", ScrollBufferUp(5));
+ /// ```
+ struct ScrollBufferUp(u16) =>
+ |this, f| write!(f, csi!("{}S"), this.0)
+);
+
+sequence!(
+ /// Scrolls down by the given number of rows.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ScrollBufferDown;
+ ///
+ /// let mut stdout = stdout();
+ /// // Scroll down by 10 lines
+ /// write!(stdout, "{}", ScrollBufferDown(10));
+ /// ```
+ struct ScrollBufferDown(u16) =>
+ |this, f| write!(f, csi!("{}T"), this.0)
+);
+
+sequence!(
+ /// Clears part of the line.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ClearLine;
+ ///
+ /// let mut stdout = stdout();
+ /// // Clear the whole line
+ /// write!(stdout, "{}", ClearLine::All);
+ /// ```
+ enum ClearLine {
+ /// Clears from the cursor position to end of the line.
+ Right => csi!("K"),
+ /// Clears from the cursor position to beginning of the line.
+ Left => csi!("1K"),
+ /// Clears the whole line.
+ All => csi!("2K"),
+ }
+);
+
+sequence!(
+ /// Clears part of the buffer.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ClearBuffer;
+ ///
+ /// let mut stdout = stdout();
+ /// // Clear the entire buffer
+ /// write!(stdout, "{}", ClearBuffer::All);
+ /// ```
+ enum ClearBuffer {
+ /// Clears from the cursor position to end of the screen.
+ Below => csi!("J"),
+ /// Clears from the cursor position to beginning of the screen.
+ Above => csi!("1J"),
+ /// Clears the entire buffer.
+ All => csi!("2J"),
+ /// Clears the entire buffer and all saved lines in the scrollback buffer.
+ SavedLines => csi!("3J"),
+ }
+);
+
+#[cfg(test)]
+test_sequences!(
+ switch_buffer_to_alternate(
+ SwitchBufferToAlternate => "\x1B[?1049h",
+ ),
+ switch_buffer_to_main(
+ SwitchBufferToNormal => "\x1B[?1049l",
+ ),
+ scroll_buffer_up(
+ ScrollBufferUp(10) => "\x1B[10S",
+ ),
+ scroll_buffer_down(
+ ScrollBufferDown(10) => "\x1B[10T",
+ ),
+ clear_line(
+ ClearLine::Right => "\x1B[K",
+ ClearLine::Left => "\x1B[1K",
+ ClearLine::All => "\x1B[2K",
+ ),
+ clear_buffer(
+ ClearBuffer::Below => "\x1B[J",
+ ClearBuffer::Above => "\x1B[1J",
+ ClearBuffer::All => "\x1B[2J",
+ ClearBuffer::SavedLines => "\x1B[3J",
+ ),
+);
diff --git a/src/sequences/color.rs b/src/sequences/color.rs
new file mode 100644
index 0000000..b019f0f
--- /dev/null
+++ b/src/sequences/color.rs
@@ -0,0 +1,189 @@
+use std::fmt;
+
+/// A color.
+///
+/// This is **NOT** a full ANSI sequence. `Color` must be used along with
+/// the:
+///
+/// * [`SetBackgroundColor`](struct.SetBackgroundColor.html)
+/// * [`SetForegroundColor`](struct.SetForegroundColor.html)
+///
+/// # Examples
+///
+/// ```no_run
+/// use std::io::{stdout, Write};
+/// use anes::{Color, SetForegroundColor};
+///
+/// let mut stdout = stdout();
+/// // Set the foreground color to red
+/// write!(stdout, "{}", SetForegroundColor(Color::Red));
+/// ```
+#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
+pub enum Color {
+ /// Default color.
+ Default,
+ /// Black color.
+ Black,
+ /// Dark red color.
+ DarkRed,
+ /// Dark green color.
+ DarkGreen,
+ /// Dark yellow color.
+ DarkYellow,
+ /// Dark blue color.
+ DarkBlue,
+ /// Dark magenta color.
+ DarkMagenta,
+ /// Dark cyan color.
+ DarkCyan,
+ /// Dark gray color.
+ ///
+ /// Also knows as light (bright) black.
+ DarkGray,
+ /// Light (bright) gray color.
+ ///
+ /// Also known as dark white.
+ Gray,
+ /// Light (bright) red color.
+ Red,
+ /// Light (bright) green color.
+ Green,
+ /// Light (bright) yellow color.
+ Yellow,
+ /// Light (bright) blue color.
+ Blue,
+ /// Light (bright) magenta color.
+ Magenta,
+ /// Light (bright) cyan color.
+ Cyan,
+ /// White color.
+ White,
+ /// A color from the predefined set of ANSI colors.
+ ///
+ /// ```text
+ /// 0 - 7: standard colors (as in ESC [ 30–37 m)
+ /// 8- 15: high intensity colors (as in ESC [ 90–97 m)
+ /// 16-231: 6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
+ /// 232-255: grayscale from black to white in 24 steps
+ /// ```
+ ///
+ /// See [8-bit](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) for more information.
+ Ansi(u8),
+ /// An RGB color.
+ ///
+ /// See [24-bit](https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit) for more information.
+ Rgb(u8, u8, u8),
+}
+
+impl fmt::Display for Color {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ // Color::Default is handled in the SetBackgroundColor & SetForegroundColor
+ Color::Default => Ok(()),
+ Color::Black => write!(f, "5;0"),
+ Color::DarkRed => write!(f, "5;1"),
+ Color::DarkGreen => write!(f, "5;2"),
+ Color::DarkYellow => write!(f, "5;3"),
+ Color::DarkBlue => write!(f, "5;4"),
+ Color::DarkMagenta => write!(f, "5;5"),
+ Color::DarkCyan => write!(f, "5;6"),
+ Color::Gray => write!(f, "5;7"),
+ Color::DarkGray => write!(f, "5;8"),
+ Color::Red => write!(f, "5;9"),
+ Color::Green => write!(f, "5;10"),
+ Color::Yellow => write!(f, "5;11"),
+ Color::Blue => write!(f, "5;12"),
+ Color::Magenta => write!(f, "5;13"),
+ Color::Cyan => write!(f, "5;14"),
+ Color::White => write!(f, "5;15"),
+ Color::Ansi(value) => write!(f, "5;{}", value),
+ Color::Rgb(r, g, b) => write!(f, "2;{};{};{}", r, g, b),
+ }
+ }
+}
+
+sequence! {
+ /// Sets the foreground color.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{Color, SetForegroundColor};
+ ///
+ /// let mut stdout = stdout();
+ /// // Set the foreground color to blue
+ /// write!(stdout, "{}", SetForegroundColor(Color::Blue));
+ /// ```
+ struct SetForegroundColor(Color) =>
+ |this, f| match this.0 {
+ Color::Default => write!(f, sgr!("39")),
+ _ => write!(f, sgr!("38;{}"), this.0),
+ }
+}
+
+sequence! {
+ /// Sets the background color.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{Color, SetBackgroundColor};
+ ///
+ /// let mut stdout = stdout();
+ /// // Set the background color to yellow
+ /// write!(stdout, "{}", SetBackgroundColor(Color::Yellow));
+ /// ```
+ struct SetBackgroundColor(Color) =>
+ |this, f| match this.0 {
+ Color::Default => write!(f, sgr!("49")),
+ _ => write!(f, sgr!("48;{}"), this.0),
+ }
+}
+
+#[cfg(test)]
+test_sequences!(
+ set_foreground_color(
+ SetForegroundColor(Color::Default) => "\x1B[39m",
+ SetForegroundColor(Color::Black) => "\x1B[38;5;0m",
+ SetForegroundColor(Color::DarkRed) => "\x1B[38;5;1m",
+ SetForegroundColor(Color::DarkGreen) => "\x1B[38;5;2m",
+ SetForegroundColor(Color::DarkYellow) => "\x1B[38;5;3m",
+ SetForegroundColor(Color::DarkBlue) => "\x1B[38;5;4m",
+ SetForegroundColor(Color::DarkMagenta) => "\x1B[38;5;5m",
+ SetForegroundColor(Color::DarkCyan) => "\x1B[38;5;6m",
+ SetForegroundColor(Color::DarkGray) => "\x1B[38;5;8m",
+ SetForegroundColor(Color::Gray) => "\x1B[38;5;7m",
+ SetForegroundColor(Color::Red) => "\x1B[38;5;9m",
+ SetForegroundColor(Color::Green) => "\x1B[38;5;10m",
+ SetForegroundColor(Color::Yellow) => "\x1B[38;5;11m",
+ SetForegroundColor(Color::Blue) => "\x1B[38;5;12m",
+ SetForegroundColor(Color::Magenta) => "\x1B[38;5;13m",
+ SetForegroundColor(Color::Cyan) => "\x1B[38;5;14m",
+ SetForegroundColor(Color::White) => "\x1B[38;5;15m",
+ SetForegroundColor(Color::Ansi(200)) => "\x1B[38;5;200m",
+ SetForegroundColor(Color::Rgb(1, 2, 3)) => "\x1B[38;2;1;2;3m",
+ ),
+ set_background_color(
+ SetBackgroundColor(Color::Default) => "\x1B[49m",
+ SetBackgroundColor(Color::Black) => "\x1B[48;5;0m",
+ SetBackgroundColor(Color::DarkRed) => "\x1B[48;5;1m",
+ SetBackgroundColor(Color::DarkGreen) => "\x1B[48;5;2m",
+ SetBackgroundColor(Color::DarkYellow) => "\x1B[48;5;3m",
+ SetBackgroundColor(Color::DarkBlue) => "\x1B[48;5;4m",
+ SetBackgroundColor(Color::DarkMagenta) => "\x1B[48;5;5m",
+ SetBackgroundColor(Color::DarkCyan) => "\x1B[48;5;6m",
+ SetBackgroundColor(Color::DarkGray) => "\x1B[48;5;8m",
+ SetBackgroundColor(Color::Gray) => "\x1B[48;5;7m",
+ SetBackgroundColor(Color::Red) => "\x1B[48;5;9m",
+ SetBackgroundColor(Color::Green) => "\x1B[48;5;10m",
+ SetBackgroundColor(Color::Yellow) => "\x1B[48;5;11m",
+ SetBackgroundColor(Color::Blue) => "\x1B[48;5;12m",
+ SetBackgroundColor(Color::Magenta) => "\x1B[48;5;13m",
+ SetBackgroundColor(Color::Cyan) => "\x1B[48;5;14m",
+ SetBackgroundColor(Color::White) => "\x1B[48;5;15m",
+ SetBackgroundColor(Color::Ansi(200)) => "\x1B[48;5;200m",
+ SetBackgroundColor(Color::Rgb(1, 2, 3)) => "\x1B[48;2;1;2;3m",
+ )
+);
diff --git a/src/sequences/cursor.rs b/src/sequences/cursor.rs
new file mode 100644
index 0000000..37abc3b
--- /dev/null
+++ b/src/sequences/cursor.rs
@@ -0,0 +1,352 @@
+//! A terminal cursor related ANSI escape sequences.
+
+sequence!(
+ /// Saves the cursor position.
+ ///
+ /// Use the [`RestoreCursorPosition`](struct.RestoreCursorPosition.html) sequence to
+ /// restore the cursor position.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{SaveCursorPosition, RestoreCursorPosition};
+ ///
+ /// let mut stdout = stdout();
+ /// // Save cursor position
+ /// write!(stdout, "{}", SaveCursorPosition);
+ ///
+ /// // Your app
+ ///
+ /// // Restore cursor position
+ /// write!(stdout, "{}", RestoreCursorPosition);
+ /// ```
+ struct SaveCursorPosition => esc!("7")
+);
+
+sequence!(
+ /// Restores the cursor position.
+ ///
+ /// Use the [`SaveCursorPosition`](struct.SaveCursorPosition.html) sequence to
+ /// save the cursor position.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{SaveCursorPosition, RestoreCursorPosition};
+ ///
+ /// let mut stdout = stdout();
+ /// // Save cursor position
+ /// write!(stdout, "{}", SaveCursorPosition);
+ ///
+ /// // Your app
+ ///
+ /// // Restore cursor position
+ /// write!(stdout, "{}", RestoreCursorPosition);
+ /// ```
+ struct RestoreCursorPosition => esc!("8")
+);
+
+sequence!(
+ /// Hides the cursor.
+ ///
+ /// Use the [`ShowCursor`](struct.ShowCursor.html) sequence to show the cursor.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::HideCursor;
+ ///
+ /// let mut stdout = stdout();
+ /// // Hide cursor
+ /// write!(stdout, "{}", HideCursor);
+ /// ```
+ struct HideCursor => csi!("?25l")
+);
+
+sequence!(
+ /// Shows the cursor.
+ ///
+ /// Use the [`HideCursor`](struct.HideCursor.html) sequence to hide the cursor.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ShowCursor;
+ ///
+ /// let mut stdout = stdout();
+ /// // Show cursor
+ /// write!(stdout, "{}", ShowCursor);
+ /// ```
+ struct ShowCursor => csi!("?25h")
+);
+
+sequence!(
+ /// Enables the cursor blinking.
+ ///
+ /// Use the [`DisableCursorBlinking`](struct.DisableCursorBlinking.html) sequence to disable
+ /// cursor blinking.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::EnableCursorBlinking;
+ ///
+ /// let mut stdout = stdout();
+ /// // Enable cursor blinking
+ /// write!(stdout, "{}", EnableCursorBlinking);
+ /// ```
+ struct EnableCursorBlinking => csi!("?12h")
+);
+
+sequence!(
+ /// Disables the cursor blinking.
+ ///
+ /// Use the [`EnableCursorBlinking`](struct.EnableCursorBlinking.html) sequence to enable
+ /// cursor blinking.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::DisableCursorBlinking;
+ ///
+ /// let mut stdout = stdout();
+ /// // Disable cursor blinking
+ /// write!(stdout, "{}", DisableCursorBlinking);
+ /// ```
+ struct DisableCursorBlinking => csi!("?12l")
+);
+
+sequence!(
+ /// Moves the cursor to the given location (column, row).
+ ///
+ /// # Notes
+ ///
+ /// Top/left cell is represented as `1, 1` (`column, row`).
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorTo;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor to top left cell
+ /// write!(stdout, "{}", MoveCursorTo(1, 1));
+ /// ```
+ struct MoveCursorTo(u16, u16) =>
+ |this, f| write!(f, csi!("{};{}H"), this.1, this.0)
+);
+
+sequence!(
+ /// Moves the cursor up by the given number of rows.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorUp;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor up by 5 rows
+ /// write!(stdout, "{}", MoveCursorUp(5));
+ /// ```
+ struct MoveCursorUp(u16) =>
+ |this, f| write!(f, csi!("{}A"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor down by the given number of rows.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorDown;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor down by 5 rows
+ /// write!(stdout, "{}", MoveCursorDown(5));
+ /// ```
+ struct MoveCursorDown(u16) =>
+ |this, f| write!(f, csi!("{}B"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor right by the given number of columns.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorRight;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor right by 5 columns
+ /// write!(stdout, "{}", MoveCursorRight(5));
+ /// ```
+ struct MoveCursorRight(u16) =>
+ |this, f| write!(f, csi!("{}C"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor left by the given number of columns.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorLeft;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor left by 5 columns
+ /// write!(stdout, "{}", MoveCursorLeft(5));
+ /// ```
+ struct MoveCursorLeft(u16) =>
+ |this, f| write!(f, csi!("{}D"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor to beginning of line the given number of lines down.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorToNextLine;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor down by 2 rows and the move it to the first column
+ /// write!(stdout, "{}", MoveCursorToNextLine(2));
+ /// ```
+ ///
+ /// The previous example does the same thing as the following one:
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{MoveCursorDown, MoveCursorToColumn};
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}{}", MoveCursorDown(2), MoveCursorToColumn(1));
+ /// ```
+ struct MoveCursorToNextLine(u16) =>
+ |this, f| write!(f, csi!("{}E"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor to beginning of line the given number of lines up.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorToPreviousLine;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor up by 2 rows and the move it to the first column
+ /// write!(stdout, "{}", MoveCursorToPreviousLine(2));
+ /// ```
+ ///
+ /// The previous example does the same thing as the following one:
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::{MoveCursorUp, MoveCursorToColumn};
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}{}", MoveCursorUp(2), MoveCursorToColumn(1));
+ /// ```
+ struct MoveCursorToPreviousLine(u16) =>
+ |this, f| write!(f, csi!("{}F"), this.0)
+);
+
+sequence!(
+ /// Moves the cursor to the given column.
+ ///
+ /// # Notes
+ ///
+ /// Beginning of the line (left cell) is represented as `1`.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::MoveCursorToColumn;
+ ///
+ /// let mut stdout = stdout();
+ /// // Move cursor to the 10th column (same row)
+ /// write!(stdout, "{}", MoveCursorToColumn(10));
+ /// ```
+ struct MoveCursorToColumn(u16) =>
+ |this, f| write!(f, csi!("{}G"), this.0)
+);
+
+// TODO Enhance example with Parser to show how to retrieve it
+sequence!(
+ /// Asks for the current cursor position.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ReportCursorPosition;
+ ///
+ /// let mut stdout = stdout();
+ /// write!(stdout, "{}", ReportCursorPosition);
+ /// ```
+ struct ReportCursorPosition => csi!("6n")
+);
+
+#[cfg(test)]
+test_sequences!(
+ save_cursor_position(
+ SaveCursorPosition => "\x1B7",
+ ),
+ restore_cursor_position(
+ RestoreCursorPosition => "\x1B8",
+ ),
+ hide_cursor(
+ HideCursor => "\x1B[?25l",
+ ),
+ show_cursor(
+ ShowCursor => "\x1B[?25h",
+ ),
+ disable_cursor_blinking(
+ DisableCursorBlinking => "\x1B[?12l",
+ ),
+ enable_cursor_blinking(
+ EnableCursorBlinking => "\x1B[?12h",
+ ),
+ move_cursor_up(
+ MoveCursorUp(10) => "\x1B[10A",
+ ),
+ move_cursor_down(
+ MoveCursorDown(10) => "\x1B[10B",
+ ),
+ move_cursor_right(
+ MoveCursorRight(10) => "\x1B[10C",
+ ),
+ move_cursor_left(
+ MoveCursorLeft(10) => "\x1B[10D",
+ ),
+ move_cursor_to(
+ MoveCursorTo(5, 10) => "\x1B[10;5H",
+ ),
+ move_cursor_to_next_line(
+ MoveCursorToNextLine(5) => "\x1B[5E",
+ ),
+ move_cursor_to_previous_line(
+ MoveCursorToPreviousLine(5) => "\x1B[5F",
+ ),
+ move_cursor_to_column(
+ MoveCursorToColumn(1) => "\x1B[1G",
+ ),
+ report_cursor_position(
+ ReportCursorPosition => "\x1B[6n",
+ )
+);
diff --git a/src/sequences/terminal.rs b/src/sequences/terminal.rs
new file mode 100644
index 0000000..74eada6
--- /dev/null
+++ b/src/sequences/terminal.rs
@@ -0,0 +1,54 @@
+//! A terminal related ANSI escape sequences.
+
+sequence!(
+ /// Resizes the text area to the given width and height in characters.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use std::io::{stdout, Write};
+ /// use anes::ResizeTextArea;
+ ///
+ /// let mut stdout = stdout();
+ /// // Resize the terminal to 80x25
+ /// write!(stdout, "{}", ResizeTextArea(80, 25));
+ /// ```
+ struct ResizeTextArea(u16, u16) =>
+ |this, f| write!(f, csi!("8;{};{}t"), this.1, this.0)
+);
+
+sequence!(
+ /// Tells the terminal to start reporting mouse events.
+ ///
+ /// Mouse events are not reported by default.
+ struct EnableMouseEvents => concat!(
+ csi!("?1000h"),
+ csi!("?1002h"),
+ csi!("?1015h"),
+ csi!("?1006h")
+ )
+);
+
+sequence!(
+ /// Tells the terminal to stop reporting mouse events.
+ struct DisableMouseEvents => concat!(
+ csi!("?1006l"),
+ csi!("?1015l"),
+ csi!("?1002l"),
+ csi!("?1000l")
+ )
+);
+
+#[cfg(test)]
+test_sequences!(
+ resize_text_area(
+ ResizeTextArea(80, 25) => "\x1B[8;25;80t",
+ ResizeTextArea(1, 1) => "\x1B[8;1;1t",
+ ),
+ enable_mouse_events(
+ EnableMouseEvents => "\x1B[?1000h\x1B[?1002h\x1B[?1015h\x1B[?1006h",
+ ),
+ disable_mouse_events(
+ DisableMouseEvents => "\x1B[?1006l\x1B[?1015l\x1B[?1002l\x1B[?1000l",
+ )
+);
diff --git a/tests/parser/cursor.rs b/tests/parser/cursor.rs
new file mode 100644
index 0000000..250ee48
--- /dev/null
+++ b/tests/parser/cursor.rs
@@ -0,0 +1,8 @@
+use anes::parser::Sequence;
+
+use crate::test_sequences;
+
+#[test]
+fn position() {
+ test_sequences!(b"\x1B[20;10R", Sequence::CursorPosition(10, 20),);
+}
diff --git a/tests/parser/key.rs b/tests/parser/key.rs
new file mode 100644
index 0000000..cd87c9d
--- /dev/null
+++ b/tests/parser/key.rs
@@ -0,0 +1,158 @@
+use anes::parser::{KeyCode, KeyModifiers, Sequence};
+
+use crate::test_sequences;
+
+#[test]
+fn esc_o_f_keys() {
+ test_sequences!(
+ b"\x1BOP",
+ Sequence::Key(KeyCode::F(1), KeyModifiers::empty()),
+ b"\x1BOQ",
+ Sequence::Key(KeyCode::F(2), KeyModifiers::empty()),
+ b"\x1BOR",
+ Sequence::Key(KeyCode::F(3), KeyModifiers::empty()),
+ b"\x1BOS",
+ Sequence::Key(KeyCode::F(4), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn csi_key_codes() {
+ test_sequences!(
+ b"\x1B[A",
+ Sequence::Key(KeyCode::Up, KeyModifiers::empty()),
+ b"\x1B[B",
+ Sequence::Key(KeyCode::Down, KeyModifiers::empty()),
+ b"\x1B[C",
+ Sequence::Key(KeyCode::Right, KeyModifiers::empty()),
+ b"\x1B[D",
+ Sequence::Key(KeyCode::Left, KeyModifiers::empty()),
+ b"\x1B[H",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[F",
+ Sequence::Key(KeyCode::End, KeyModifiers::empty()),
+ b"\x1B[Z",
+ Sequence::Key(KeyCode::BackTab, KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn csi_arrow_key_modifiers() {
+ test_sequences!(
+ b"\x1B[50A",
+ Sequence::Key(KeyCode::Up, KeyModifiers::SHIFT),
+ b"\x1B[53A",
+ Sequence::Key(KeyCode::Up, KeyModifiers::CONTROL),
+ );
+}
+
+#[test]
+fn csi_tilde_key_modifiers() {
+ test_sequences!(
+ b"\x1B[1~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[1;0~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[1;1~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[1;2~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::SHIFT),
+ b"\x1B[1;3~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::ALT),
+ b"\x1B[1;4~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::SHIFT | KeyModifiers::ALT),
+ b"\x1B[1;5~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::CONTROL),
+ b"\x1B[1;6~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::SHIFT | KeyModifiers::CONTROL),
+ b"\x1B[1;7~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::ALT | KeyModifiers::CONTROL),
+ b"\x1B[1;8~",
+ Sequence::Key(
+ KeyCode::Home,
+ KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[1;9~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::META),
+ b"\x1B[1;10~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::META | KeyModifiers::SHIFT),
+ b"\x1B[1;11~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::META | KeyModifiers::ALT),
+ b"\x1B[1;12~",
+ Sequence::Key(
+ KeyCode::Home,
+ KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::ALT
+ ),
+ b"\x1B[1;13~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::META | KeyModifiers::CONTROL),
+ b"\x1B[1;14~",
+ Sequence::Key(
+ KeyCode::Home,
+ KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[1;15~",
+ Sequence::Key(
+ KeyCode::Home,
+ KeyModifiers::META | KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[1;16~",
+ Sequence::Key(
+ KeyCode::Home,
+ KeyModifiers::META | KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[1;17~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn csi_tilde_f_keys() {
+ test_sequences!(
+ b"\x1B[11~",
+ Sequence::Key(KeyCode::F(1), KeyModifiers::empty()),
+ b"\x1B[12~",
+ Sequence::Key(KeyCode::F(2), KeyModifiers::empty()),
+ b"\x1B[13~",
+ Sequence::Key(KeyCode::F(3), KeyModifiers::empty()),
+ b"\x1B[14~",
+ Sequence::Key(KeyCode::F(4), KeyModifiers::empty()),
+ b"\x1B[15~",
+ Sequence::Key(KeyCode::F(5), KeyModifiers::empty()),
+ b"\x1B[17~",
+ Sequence::Key(KeyCode::F(6), KeyModifiers::empty()),
+ b"\x1B[18~",
+ Sequence::Key(KeyCode::F(7), KeyModifiers::empty()),
+ b"\x1B[19~",
+ Sequence::Key(KeyCode::F(8), KeyModifiers::empty()),
+ b"\x1B[20~",
+ Sequence::Key(KeyCode::F(9), KeyModifiers::empty()),
+ b"\x1B[21~",
+ Sequence::Key(KeyCode::F(10), KeyModifiers::empty()),
+ b"\x1B[23~",
+ Sequence::Key(KeyCode::F(11), KeyModifiers::empty()),
+ b"\x1B[24~",
+ Sequence::Key(KeyCode::F(12), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn csi_tilde_key_codes() {
+ test_sequences!(
+ b"\x1B[1~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[2~",
+ Sequence::Key(KeyCode::Insert, KeyModifiers::empty()),
+ b"\x1B[3~",
+ Sequence::Key(KeyCode::Delete, KeyModifiers::empty()),
+ b"\x1B[4~",
+ Sequence::Key(KeyCode::End, KeyModifiers::empty()),
+ b"\x1B[5~",
+ Sequence::Key(KeyCode::PageUp, KeyModifiers::empty()),
+ b"\x1B[6~",
+ Sequence::Key(KeyCode::PageDown, KeyModifiers::empty()),
+ b"\x1B[7~",
+ Sequence::Key(KeyCode::Home, KeyModifiers::empty()),
+ b"\x1B[8~",
+ Sequence::Key(KeyCode::End, KeyModifiers::empty()),
+ );
+}
diff --git a/tests/parser/mod.rs b/tests/parser/mod.rs
new file mode 100644
index 0000000..ecddcdd
--- /dev/null
+++ b/tests/parser/mod.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! test_sequence {
+ ($bytes:expr, $seq:expr) => {
+ let mut parser = ::anes::parser::Parser::default();
+ parser.advance($bytes, false);
+ assert_eq!(parser.next(), Some($seq));
+ };
+}
+
+#[macro_export]
+macro_rules! test_sequences {
+ (
+ $(
+ $bytes:expr, $seq:expr,
+ )*
+ ) => {
+ $(
+ test_sequence!($bytes, $seq);
+ )*
+ };
+}
+
+mod cursor;
+mod key;
+mod mouse;
diff --git a/tests/parser/mouse/mod.rs b/tests/parser/mouse/mod.rs
new file mode 100644
index 0000000..dc53361
--- /dev/null
+++ b/tests/parser/mouse/mod.rs
@@ -0,0 +1,2 @@
+mod rxvt;
+mod xterm;
diff --git a/tests/parser/mouse/rxvt.rs b/tests/parser/mouse/rxvt.rs
new file mode 100644
index 0000000..47f43a5
--- /dev/null
+++ b/tests/parser/mouse/rxvt.rs
@@ -0,0 +1,152 @@
+use anes::parser::{KeyModifiers, Mouse, MouseButton, Sequence};
+
+use crate::test_sequences;
+
+#[test]
+fn button_down() {
+ test_sequences!(
+ b"\x1B[0;30;40;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[1;30;40;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Middle, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[2;30;40;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Right, 30, 40),
+ KeyModifiers::empty()
+ ),
+ );
+}
+
+#[test]
+fn button_down_with_modifiers() {
+ test_sequences!(
+ b"\x1B[4;30;40;M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Left, 30, 40), KeyModifiers::SHIFT),
+ b"\x1B[5;30;40;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Middle, 30, 40),
+ KeyModifiers::SHIFT
+ ),
+ b"\x1B[6;30;40;M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Right, 30, 40), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn button_up() {
+ test_sequences!(
+ b"\x1B[3;30;40;M",
+ Sequence::Mouse(Mouse::Up(MouseButton::Any, 30, 40), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn button_up_with_modifiers() {
+ test_sequences!(
+ b"\x1B[7;30;40;M",
+ Sequence::Mouse(Mouse::Up(MouseButton::Any, 30, 40), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn scroll() {
+ test_sequences!(
+ b"\x1B[96;30;40;M",
+ Sequence::Mouse(Mouse::ScrollUp(30, 40), KeyModifiers::empty()),
+ b"\x1B[97;30;40;M",
+ Sequence::Mouse(Mouse::ScrollDown(30, 40), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn scroll_with_modifiers() {
+ test_sequences!(
+ b"\x1B[100;30;40;M",
+ Sequence::Mouse(Mouse::ScrollUp(30, 40), KeyModifiers::SHIFT),
+ b"\x1B[101;30;40;M",
+ Sequence::Mouse(Mouse::ScrollDown(30, 40), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn drag() {
+ test_sequences!(
+ b"\x1B[64;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Left, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[65;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Middle, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[66;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Right, 30, 40),
+ KeyModifiers::empty()
+ ),
+ );
+}
+
+#[test]
+fn drag_with_modifiers() {
+ test_sequences!(
+ b"\x1B[64;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Left, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[65;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Middle, 30, 40),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[66;30;40;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Right, 30, 40),
+ KeyModifiers::empty()
+ ),
+ );
+}
+
+#[test]
+fn key_modifier_combinations() {
+ test_sequences!(
+ b"\x1B[4;20;10M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Left, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[8;20;10M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Left, 20, 10), KeyModifiers::ALT),
+ b"\x1B[16;20;10M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::CONTROL
+ ),
+ b"\x1B[12;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::ALT
+ ),
+ b"\x1B[20;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[24;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[28;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ );
+}
diff --git a/tests/parser/mouse/xterm.rs b/tests/parser/mouse/xterm.rs
new file mode 100644
index 0000000..47293d6
--- /dev/null
+++ b/tests/parser/mouse/xterm.rs
@@ -0,0 +1,154 @@
+use anes::parser::{KeyModifiers, Mouse, MouseButton, Sequence};
+
+use crate::test_sequences;
+
+#[test]
+fn button_down() {
+ test_sequences!(
+ b"\x1B[<0;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Left, 20, 10),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[<1;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Middle, 20, 10),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[<2;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Right, 20, 10),
+ KeyModifiers::empty()
+ ),
+ );
+}
+
+#[test]
+fn button_down_with_key_modifiers() {
+ test_sequences!(
+ b"\x1B[<4;20;10;M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Left, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<5;20;10;M",
+ Sequence::Mouse(
+ Mouse::Down(MouseButton::Middle, 20, 10),
+ KeyModifiers::SHIFT
+ ),
+ b"\x1B[<6;20;10;M",
+ Sequence::Mouse(Mouse::Down(MouseButton::Right, 20, 10), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn button_up() {
+ test_sequences!(
+ b"\x1B[<0;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Left, 20, 10), KeyModifiers::empty()),
+ b"\x1B[<1;20;10;m",
+ Sequence::Mouse(
+ Mouse::Up(MouseButton::Middle, 20, 10),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[<2;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Right, 20, 10), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn button_up_with_key_modifiers() {
+ test_sequences!(
+ b"\x1B[<4;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Left, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<5;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Middle, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<6;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Right, 20, 10), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn scroll() {
+ test_sequences!(
+ b"\x1B[<64;20;10;m",
+ Sequence::Mouse(Mouse::ScrollUp(20, 10), KeyModifiers::empty()),
+ b"\x1B[<65;20;10;m",
+ Sequence::Mouse(Mouse::ScrollDown(20, 10), KeyModifiers::empty()),
+ );
+}
+
+#[test]
+fn scroll_with_key_modifiers() {
+ test_sequences!(
+ b"\x1B[<68;20;10;m",
+ Sequence::Mouse(Mouse::ScrollUp(20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<69;20;10;m",
+ Sequence::Mouse(Mouse::ScrollDown(20, 10), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn drag() {
+ test_sequences!(
+ b"\x1B[<32;20;10;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Left, 20, 10),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[<33;20;10;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Middle, 20, 10),
+ KeyModifiers::empty()
+ ),
+ b"\x1B[<34;20;10;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Right, 20, 10),
+ KeyModifiers::empty()
+ ),
+ );
+}
+
+#[test]
+fn drag_with_key_modifiers() {
+ test_sequences!(
+ b"\x1B[<36;20;10;M",
+ Sequence::Mouse(Mouse::Drag(MouseButton::Left, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<37;20;10;M",
+ Sequence::Mouse(
+ Mouse::Drag(MouseButton::Middle, 20, 10),
+ KeyModifiers::SHIFT,
+ ),
+ b"\x1B[<38;20;10;M",
+ Sequence::Mouse(Mouse::Drag(MouseButton::Right, 20, 10), KeyModifiers::SHIFT),
+ );
+}
+
+#[test]
+fn key_modifier_combinations() {
+ test_sequences!(
+ b"\x1B[<4;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Left, 20, 10), KeyModifiers::SHIFT),
+ b"\x1B[<8;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Left, 20, 10), KeyModifiers::ALT),
+ b"\x1B[<16;20;10;m",
+ Sequence::Mouse(Mouse::Up(MouseButton::Left, 20, 10), KeyModifiers::CONTROL),
+ b"\x1B[<12;20;10;m",
+ Sequence::Mouse(
+ Mouse::Up(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::ALT
+ ),
+ b"\x1B[<20;20;10;m",
+ Sequence::Mouse(
+ Mouse::Up(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[<24;20;10;m",
+ Sequence::Mouse(
+ Mouse::Up(MouseButton::Left, 20, 10),
+ KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ b"\x1B[<28;20;10;m",
+ Sequence::Mouse(
+ Mouse::Up(MouseButton::Left, 20, 10),
+ KeyModifiers::SHIFT | KeyModifiers::ALT | KeyModifiers::CONTROL
+ ),
+ );
+}
diff --git a/tests/tests.rs b/tests/tests.rs
new file mode 100644
index 0000000..f60dbff
--- /dev/null
+++ b/tests/tests.rs
@@ -0,0 +1,2 @@
+#[cfg(feature = "parser")]
+mod parser;