mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
Compare commits
3 Commits
syntax-sym
...
termina
Author | SHA1 | Date | |
---|---|---|---|
|
4dab0b5406 | ||
|
609a661207 | ||
|
f1b3e4bb0d |
231
Cargo.lock
generated
231
Cargo.lock
generated
@@ -259,34 +259,6 @@ version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm_winapi",
|
||||
"filedescriptor",
|
||||
"futures-core",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -426,17 +398,6 @@ dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror 1.0.69",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
@@ -603,7 +564,7 @@ dependencies = [
|
||||
"gix-worktree",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -616,7 +577,7 @@ dependencies = [
|
||||
"gix-date",
|
||||
"gix-utils",
|
||||
"itoa",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -633,7 +594,7 @@ dependencies = [
|
||||
"gix-trace",
|
||||
"kstring",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"unicode-bom",
|
||||
]
|
||||
|
||||
@@ -643,7 +604,7 @@ version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -652,7 +613,7 @@ version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -678,7 +639,7 @@ dependencies = [
|
||||
"gix-chunk",
|
||||
"gix-hash",
|
||||
"memmap2",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -697,7 +658,7 @@ dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"unicode-bom",
|
||||
"winnow",
|
||||
]
|
||||
@@ -712,7 +673,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"gix-path",
|
||||
"libc",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -725,7 +686,7 @@ dependencies = [
|
||||
"itoa",
|
||||
"jiff",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -749,7 +710,7 @@ dependencies = [
|
||||
"gix-traverse",
|
||||
"gix-worktree",
|
||||
"imara-diff 0.1.8",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -769,7 +730,7 @@ dependencies = [
|
||||
"gix-trace",
|
||||
"gix-utils",
|
||||
"gix-worktree",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -785,7 +746,7 @@ dependencies = [
|
||||
"gix-path",
|
||||
"gix-ref",
|
||||
"gix-sec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -802,7 +763,7 @@ dependencies = [
|
||||
"libc",
|
||||
"once_cell",
|
||||
"prodash",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@@ -824,7 +785,7 @@ dependencies = [
|
||||
"gix-trace",
|
||||
"gix-utils",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -838,7 +799,7 @@ dependencies = [
|
||||
"gix-features",
|
||||
"gix-path",
|
||||
"gix-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -862,7 +823,7 @@ dependencies = [
|
||||
"faster-hex",
|
||||
"gix-features",
|
||||
"sha1-checked",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -912,9 +873,9 @@ dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"memmap2",
|
||||
"rustix 1.0.7",
|
||||
"rustix",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -925,7 +886,7 @@ checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796"
|
||||
dependencies = [
|
||||
"gix-tempfile",
|
||||
"gix-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -945,7 +906,7 @@ dependencies = [
|
||||
"gix-validate",
|
||||
"itoa",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -967,7 +928,7 @@ dependencies = [
|
||||
"gix-quote",
|
||||
"parking_lot",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -985,7 +946,7 @@ dependencies = [
|
||||
"gix-path",
|
||||
"memmap2",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -997,7 +958,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"faster-hex",
|
||||
"gix-trace",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1009,7 +970,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"faster-hex",
|
||||
"gix-trace",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1023,7 +984,7 @@ dependencies = [
|
||||
"gix-validate",
|
||||
"home",
|
||||
"once_cell",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1038,7 +999,7 @@ dependencies = [
|
||||
"gix-config-value",
|
||||
"gix-glob",
|
||||
"gix-path",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1056,7 +1017,7 @@ dependencies = [
|
||||
"gix-transport",
|
||||
"gix-utils",
|
||||
"maybe-async",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -1068,7 +1029,7 @@ checksum = "4a375a75b4d663e8bafe3bf4940a18a23755644c13582fa326e99f8f987d83fd"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"gix-utils",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1088,7 +1049,7 @@ dependencies = [
|
||||
"gix-utils",
|
||||
"gix-validate",
|
||||
"memmap2",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
@@ -1103,7 +1064,7 @@ dependencies = [
|
||||
"gix-revision",
|
||||
"gix-validate",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1118,7 +1079,7 @@ dependencies = [
|
||||
"gix-hash",
|
||||
"gix-object",
|
||||
"gix-revwalk",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1133,7 +1094,7 @@ dependencies = [
|
||||
"gix-hashtable",
|
||||
"gix-object",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1157,7 +1118,7 @@ dependencies = [
|
||||
"bstr",
|
||||
"gix-hash",
|
||||
"gix-lock",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1180,7 +1141,7 @@ dependencies = [
|
||||
"gix-pathspec",
|
||||
"gix-worktree",
|
||||
"portable-atomic",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1195,7 +1156,7 @@ dependencies = [
|
||||
"gix-pathspec",
|
||||
"gix-refspec",
|
||||
"gix-url",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1231,7 +1192,7 @@ dependencies = [
|
||||
"gix-quote",
|
||||
"gix-sec",
|
||||
"gix-url",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1248,7 +1209,7 @@ dependencies = [
|
||||
"gix-object",
|
||||
"gix-revwalk",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1261,7 +1222,7 @@ dependencies = [
|
||||
"gix-features",
|
||||
"gix-path",
|
||||
"percent-encoding",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -1283,7 +1244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1447,7 +1408,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slotmap",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
@@ -1501,7 +1462,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slotmap",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
]
|
||||
@@ -1531,7 +1492,7 @@ dependencies = [
|
||||
"regex-automata",
|
||||
"regex-cursor",
|
||||
"ropey",
|
||||
"rustix 1.0.7",
|
||||
"rustix",
|
||||
"tempfile",
|
||||
"unicode-segmentation",
|
||||
"which",
|
||||
@@ -1546,7 +1507,6 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"chrono",
|
||||
"content_inspector",
|
||||
"crossterm",
|
||||
"fern",
|
||||
"futures-util",
|
||||
"grep-regex",
|
||||
@@ -1576,8 +1536,9 @@ dependencies = [
|
||||
"signal-hook-tokio",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"termina",
|
||||
"termini",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
@@ -1590,11 +1551,11 @@ version = "25.1.1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cassowary",
|
||||
"crossterm",
|
||||
"helix-core",
|
||||
"helix-view",
|
||||
"log",
|
||||
"once_cell",
|
||||
"termina",
|
||||
"termini",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -1624,7 +1585,6 @@ dependencies = [
|
||||
"bitflags",
|
||||
"chardetng",
|
||||
"clipboard-win",
|
||||
"crossterm",
|
||||
"futures-util",
|
||||
"helix-core",
|
||||
"helix-dap",
|
||||
@@ -1638,12 +1598,13 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rustix 1.0.7",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slotmap",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"termina",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
@@ -2009,12 +1970,6 @@ dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.2"
|
||||
@@ -2095,7 +2050,6 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -2381,19 +2335,6 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.14",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
@@ -2403,7 +2344,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2512,17 +2453,6 @@ dependencies = [
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
@@ -2644,7 +2574,20 @@ dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termina"
|
||||
version = "0.1.0-beta.1"
|
||||
source = "git+https://github.com/helix-editor/termina?rev=3f861342e0de0b8f0cead5248b41083d8b3506da#3f861342e0de0b8f0cead5248b41083d8b3506da"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-core",
|
||||
"parking_lot",
|
||||
"rustix",
|
||||
"signal-hook",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
@@ -2668,33 +2611,13 @@ dependencies = [
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2852,7 +2775,7 @@ dependencies = [
|
||||
"libloading",
|
||||
"regex-cursor",
|
||||
"ropey",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3038,26 +2961,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
||||
dependencies = [
|
||||
"env_home",
|
||||
"rustix 1.0.7",
|
||||
"rustix",
|
||||
"winsafe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
@@ -3067,12 +2974,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
@@ -50,6 +50,10 @@ parking_lot = "0.12"
|
||||
futures-executor = "0.3"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
tokio-stream = "0.1.17"
|
||||
# TODO: publish v0.1.0.
|
||||
# termina = "0.1.0-beta.1"
|
||||
termina = { git = "https://github.com/helix-editor/termina", rev = "3f861342e0de0b8f0cead5248b41083d8b3506da" }
|
||||
# termina.path = "../termina"
|
||||
|
||||
[workspace.package]
|
||||
version = "25.1.1"
|
||||
|
@@ -54,8 +54,8 @@ anyhow = "1"
|
||||
once_cell = "1.21"
|
||||
|
||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
|
||||
crossterm = { version = "0.28", features = ["event-stream"] }
|
||||
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["termina"] }
|
||||
termina = { workspace = true, features = ["event-stream"] }
|
||||
signal-hook = "0.3"
|
||||
tokio-stream = "0.1"
|
||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||
@@ -95,9 +95,6 @@ grep-searcher = "0.1.14"
|
||||
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
|
||||
libc = "0.2.174"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { version = "0.28", features = ["event-stream", "use-dev-tty", "libc"] }
|
||||
|
||||
[build-dependencies]
|
||||
helix-loader = { path = "../helix-loader" }
|
||||
|
||||
|
@@ -30,28 +30,27 @@ use crate::{
|
||||
};
|
||||
|
||||
use log::{debug, error, info, warn};
|
||||
#[cfg(not(feature = "integration"))]
|
||||
use std::io::stdout;
|
||||
use std::{io::stdin, path::Path, sync::Arc};
|
||||
use std::{
|
||||
io::{stdin, IsTerminal},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
use anyhow::Context;
|
||||
use anyhow::Error;
|
||||
use anyhow::{Context, Error};
|
||||
|
||||
use crossterm::{event::Event as CrosstermEvent, tty::IsTty};
|
||||
#[cfg(not(windows))]
|
||||
use {signal_hook::consts::signal, signal_hook_tokio::Signals};
|
||||
#[cfg(windows)]
|
||||
type Signals = futures_util::stream::Empty<()>;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
use tui::backend::CrosstermBackend;
|
||||
use tui::backend::TerminaBackend;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
use tui::backend::TestBackend;
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
type TerminalBackend = CrosstermBackend<std::io::Stdout>;
|
||||
type TerminalBackend = TerminaBackend;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
type TerminalBackend = TestBackend;
|
||||
@@ -104,7 +103,8 @@ impl Application {
|
||||
let theme_loader = theme::Loader::new(&theme_parent_dirs);
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
let backend = CrosstermBackend::new(stdout(), &config.editor);
|
||||
let backend = TerminaBackend::new((&config.editor).into())
|
||||
.context("failed to create terminal backend")?;
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
let backend = TestBackend::new(120, 150);
|
||||
@@ -123,7 +123,11 @@ impl Application {
|
||||
})),
|
||||
handlers,
|
||||
);
|
||||
Self::load_configured_theme(&mut editor, &config.load());
|
||||
Self::load_configured_theme(
|
||||
&mut editor,
|
||||
&config.load(),
|
||||
terminal.backend().supports_true_color(),
|
||||
);
|
||||
|
||||
let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| {
|
||||
&config.keys
|
||||
@@ -214,7 +218,7 @@ impl Application {
|
||||
} else {
|
||||
editor.new_file(Action::VerticalSplit);
|
||||
}
|
||||
} else if stdin().is_tty() || cfg!(feature = "integration") {
|
||||
} else if stdin().is_terminal() || cfg!(feature = "integration") {
|
||||
editor.new_file(Action::VerticalSplit);
|
||||
} else {
|
||||
editor
|
||||
@@ -282,7 +286,7 @@ impl Application {
|
||||
|
||||
pub async fn event_loop<S>(&mut self, input_stream: &mut S)
|
||||
where
|
||||
S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
{
|
||||
self.render().await;
|
||||
|
||||
@@ -295,7 +299,7 @@ impl Application {
|
||||
|
||||
pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool
|
||||
where
|
||||
S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
{
|
||||
loop {
|
||||
if self.editor.should_close() {
|
||||
@@ -367,7 +371,7 @@ impl Application {
|
||||
ConfigEvent::Update(editor_config) => {
|
||||
let mut app_config = (*self.config.load().clone()).clone();
|
||||
app_config.editor = *editor_config;
|
||||
if let Err(err) = self.terminal.reconfigure(app_config.editor.clone().into()) {
|
||||
if let Err(err) = self.terminal.reconfigure((&app_config.editor).into()) {
|
||||
self.editor.set_error(err.to_string());
|
||||
};
|
||||
self.config.store(Arc::new(app_config));
|
||||
@@ -396,7 +400,11 @@ impl Application {
|
||||
// the sake of locals highlighting.
|
||||
let lang_loader = helix_core::config::user_lang_loader()?;
|
||||
self.editor.syn_loader.store(Arc::new(lang_loader));
|
||||
Self::load_configured_theme(&mut self.editor, &default_config);
|
||||
Self::load_configured_theme(
|
||||
&mut self.editor,
|
||||
&default_config,
|
||||
self.terminal.backend().supports_true_color(),
|
||||
);
|
||||
|
||||
// Re-parse any open documents with the new language config.
|
||||
let lang_loader = self.editor.syn_loader.load();
|
||||
@@ -412,8 +420,7 @@ impl Application {
|
||||
document.replace_diagnostics(diagnostics, &[], None);
|
||||
}
|
||||
|
||||
self.terminal
|
||||
.reconfigure(default_config.editor.clone().into())?;
|
||||
self.terminal.reconfigure((&default_config.editor).into())?;
|
||||
// Store new config
|
||||
self.config.store(Arc::new(default_config));
|
||||
Ok(())
|
||||
@@ -430,8 +437,8 @@ impl Application {
|
||||
}
|
||||
|
||||
/// Load the theme set in configuration
|
||||
fn load_configured_theme(editor: &mut Editor, config: &Config) {
|
||||
let true_color = config.editor.true_color || crate::true_color();
|
||||
fn load_configured_theme(editor: &mut Editor, config: &Config, terminal_true_color: bool) {
|
||||
let true_color = terminal_true_color || config.editor.true_color || crate::true_color();
|
||||
let theme = config
|
||||
.theme
|
||||
.as_ref()
|
||||
@@ -503,7 +510,7 @@ impl Application {
|
||||
// https://github.com/neovim/neovim/issues/12322
|
||||
// https://github.com/neovim/neovim/pull/13084
|
||||
for retries in 1..=10 {
|
||||
match self.claim_term().await {
|
||||
match self.terminal.claim() {
|
||||
Ok(()) => break,
|
||||
Err(err) if retries == 10 => panic!("Failed to claim terminal: {}", err),
|
||||
Err(_) => continue,
|
||||
@@ -635,7 +642,7 @@ impl Application {
|
||||
false
|
||||
}
|
||||
|
||||
pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) {
|
||||
pub async fn handle_terminal_events(&mut self, event: std::io::Result<termina::Event>) {
|
||||
let mut cx = crate::compositor::Context {
|
||||
editor: &mut self.editor,
|
||||
jobs: &mut self.jobs,
|
||||
@@ -643,9 +650,9 @@ impl Application {
|
||||
};
|
||||
// Handle key events
|
||||
let should_redraw = match event.unwrap() {
|
||||
CrosstermEvent::Resize(width, height) => {
|
||||
termina::Event::WindowResized(termina::WindowSize { rows, cols, .. }) => {
|
||||
self.terminal
|
||||
.resize(Rect::new(0, 0, width, height))
|
||||
.resize(Rect::new(0, 0, cols, rows))
|
||||
.expect("Unable to resize terminal");
|
||||
|
||||
let area = self.terminal.size().expect("couldn't get terminal size");
|
||||
@@ -653,11 +660,11 @@ impl Application {
|
||||
self.compositor.resize(area);
|
||||
|
||||
self.compositor
|
||||
.handle_event(&Event::Resize(width, height), &mut cx)
|
||||
.handle_event(&Event::Resize(cols, rows), &mut cx)
|
||||
}
|
||||
// Ignore keyboard release events.
|
||||
CrosstermEvent::Key(crossterm::event::KeyEvent {
|
||||
kind: crossterm::event::KeyEventKind::Release,
|
||||
termina::Event::Key(termina::event::KeyEvent {
|
||||
kind: termina::event::KeyEventKind::Release,
|
||||
..
|
||||
}) => false,
|
||||
event => self.compositor.handle_event(&event.into(), &mut cx),
|
||||
@@ -1099,36 +1106,48 @@ impl Application {
|
||||
lsp::ShowDocumentResult { success: true }
|
||||
}
|
||||
|
||||
async fn claim_term(&mut self) -> std::io::Result<()> {
|
||||
let terminal_config = self.config.load().editor.clone().into();
|
||||
self.terminal.claim(terminal_config)
|
||||
}
|
||||
|
||||
fn restore_term(&mut self) -> std::io::Result<()> {
|
||||
let terminal_config = self.config.load().editor.clone().into();
|
||||
use helix_view::graphics::CursorKind;
|
||||
self.terminal
|
||||
.backend_mut()
|
||||
.show_cursor(CursorKind::Block)
|
||||
.ok();
|
||||
self.terminal.restore(terminal_config)
|
||||
self.terminal.restore()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "integration"))]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
|
||||
use termina::Terminal as _;
|
||||
let reader = self.terminal.backend().terminal().event_reader();
|
||||
termina::EventStream::new(reader, |event| !event.is_escape())
|
||||
}
|
||||
|
||||
#[cfg(feature = "integration")]
|
||||
pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<termina::Event>> + Unpin {
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/// A dummy stream that never polls as ready.
|
||||
pub struct DummyEventStream;
|
||||
|
||||
impl Stream for DummyEventStream {
|
||||
type Item = std::io::Result<termina::Event>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
DummyEventStream
|
||||
}
|
||||
|
||||
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
||||
where
|
||||
S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
|
||||
S: Stream<Item = std::io::Result<termina::Event>> + Unpin,
|
||||
{
|
||||
self.claim_term().await?;
|
||||
|
||||
// Exit the alternate screen and disable raw mode before panicking
|
||||
let hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
// We can't handle errors properly inside this closure. And it's
|
||||
// probably not a good idea to `unwrap()` inside a panic handler.
|
||||
// So we just ignore the `Result`.
|
||||
let _ = TerminalBackend::force_restore();
|
||||
hook(info);
|
||||
}));
|
||||
self.terminal.claim()?;
|
||||
|
||||
self.event_loop(input_stream).await;
|
||||
|
||||
|
@@ -1,11 +1,11 @@
|
||||
use crate::config::{Config, ConfigLoadError};
|
||||
use crossterm::{
|
||||
style::{Color, StyledContent, Stylize},
|
||||
tty::IsTty,
|
||||
};
|
||||
use helix_core::config::{default_lang_config, user_lang_config};
|
||||
use helix_loader::grammar::load_runtime_file;
|
||||
use std::io::Write;
|
||||
use std::io::{IsTerminal, Write};
|
||||
use termina::{
|
||||
style::{ColorSpec, StyleExt as _, Stylized},
|
||||
Terminal as _,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum TsFeature {
|
||||
@@ -160,21 +160,24 @@ pub fn languages_all() -> std::io::Result<()> {
|
||||
headings.push(feat.short_title())
|
||||
}
|
||||
|
||||
let terminal_cols = crossterm::terminal::size().map(|(c, _)| c).unwrap_or(80);
|
||||
let terminal_cols = termina::PlatformTerminal::new()
|
||||
.and_then(|terminal| terminal.get_dimensions())
|
||||
.map(|size| size.cols)
|
||||
.unwrap_or(80);
|
||||
let column_width = terminal_cols as usize / headings.len();
|
||||
let is_terminal = std::io::stdout().is_tty();
|
||||
let is_terminal = std::io::stdout().is_terminal();
|
||||
|
||||
let fit = |s: &str| -> StyledContent<String> {
|
||||
let fit = |s: &str| -> Stylized<'static> {
|
||||
format!(
|
||||
"{:column_width$}",
|
||||
s.get(..column_width - 2)
|
||||
.map(|s| format!("{}…", s))
|
||||
.unwrap_or_else(|| s.to_string())
|
||||
)
|
||||
.stylize()
|
||||
.stylized()
|
||||
};
|
||||
let color = |s: StyledContent<String>, c: Color| if is_terminal { s.with(c) } else { s };
|
||||
let bold = |s: StyledContent<String>| if is_terminal { s.bold() } else { s };
|
||||
let color = |s: Stylized<'static>, c: ColorSpec| if is_terminal { s.foreground(c) } else { s };
|
||||
let bold = |s: Stylized<'static>| if is_terminal { s.bold() } else { s };
|
||||
|
||||
for heading in headings {
|
||||
write!(stdout, "{}", bold(fit(heading)))?;
|
||||
@@ -187,10 +190,10 @@ pub fn languages_all() -> std::io::Result<()> {
|
||||
|
||||
let check_binary_with_name = |cmd: Option<(&str, &str)>| match cmd {
|
||||
Some((name, cmd)) => match helix_stdx::env::which(cmd) {
|
||||
Ok(_) => color(fit(&format!("✓ {}", name)), Color::Green),
|
||||
Err(_) => color(fit(&format!("✘ {}", name)), Color::Red),
|
||||
Ok(_) => color(fit(&format!("✓ {}", name)), ColorSpec::BRIGHT_GREEN),
|
||||
Err(_) => color(fit(&format!("✘ {}", name)), ColorSpec::BRIGHT_RED),
|
||||
},
|
||||
None => color(fit("None"), Color::Yellow),
|
||||
None => color(fit("None"), ColorSpec::BRIGHT_YELLOW),
|
||||
};
|
||||
|
||||
let check_binary = |cmd: Option<&str>| check_binary_with_name(cmd.map(|cmd| (cmd, cmd)));
|
||||
@@ -217,8 +220,8 @@ pub fn languages_all() -> std::io::Result<()> {
|
||||
|
||||
for ts_feat in TsFeature::all() {
|
||||
match load_runtime_file(&lang.language_id, ts_feat.runtime_filename()).is_ok() {
|
||||
true => write!(stdout, "{}", color(fit("✓"), Color::Green))?,
|
||||
false => write!(stdout, "{}", color(fit("✘"), Color::Red))?,
|
||||
true => write!(stdout, "{}", color(fit("✓"), ColorSpec::BRIGHT_GREEN))?,
|
||||
false => write!(stdout, "{}", color(fit("✘"), ColorSpec::BRIGHT_RED))?,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
use anyhow::{Context, Error, Result};
|
||||
use crossterm::event::EventStream;
|
||||
use helix_loader::VERSION_AND_GIT_HASH;
|
||||
use helix_term::application::Application;
|
||||
use helix_term::args::Args;
|
||||
@@ -149,8 +148,9 @@ FLAGS:
|
||||
|
||||
// TODO: use the thread local executor to spawn the application task separately from the work pool
|
||||
let mut app = Application::new(args, config, lang_loader).context("unable to start Helix")?;
|
||||
let mut events = app.event_stream();
|
||||
|
||||
let exit_code = app.run(&mut EventStream::new()).await?;
|
||||
let exit_code = app.run(&mut events).await?;
|
||||
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
@@ -505,7 +505,7 @@ impl EditorView {
|
||||
};
|
||||
spans.push((selection_scope, range.anchor..selection_end));
|
||||
// add block cursors
|
||||
// skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
|
||||
// skip primary cursor if terminal is unfocused - terminal cursor is used in that case
|
||||
if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
|
||||
spans.push((cursor_scope, cursor_start..range.head));
|
||||
}
|
||||
@@ -513,7 +513,7 @@ impl EditorView {
|
||||
// Reverse case.
|
||||
let cursor_end = next_grapheme_boundary(text, range.head);
|
||||
// add block cursors
|
||||
// skip primary cursor if terminal is unfocused - crossterm cursor is used in that case
|
||||
// skip primary cursor if terminal is unfocused - terminal cursor is used in that case
|
||||
if !selection_is_primary || (cursor_is_block && is_terminal_focused) {
|
||||
spans.push((cursor_scope, range.head..cursor_end));
|
||||
}
|
||||
@@ -1597,7 +1597,7 @@ impl Component for EditorView {
|
||||
if self.terminal_focused {
|
||||
(pos, CursorKind::Hidden)
|
||||
} else {
|
||||
// use crossterm cursor when terminal loses focus
|
||||
// use terminal cursor when terminal loses focus
|
||||
(pos, CursorKind::Underline)
|
||||
}
|
||||
}
|
||||
|
@@ -6,11 +6,11 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use crossterm::event::{Event, KeyEvent};
|
||||
use helix_core::{diagnostic::Severity, test, Selection, Transaction};
|
||||
use helix_term::{application::Application, args::Args, config::Config, keymap::merge_keys};
|
||||
use helix_view::{current_ref, doc, editor::LspConfig, input::parse_macro, Editor};
|
||||
use tempfile::NamedTempFile;
|
||||
use termina::event::{Event, KeyEvent};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
|
||||
/// Specify how to set up the input text with line feeds
|
||||
|
@@ -12,7 +12,7 @@ repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["crossterm"]
|
||||
default = ["termina"]
|
||||
|
||||
[dependencies]
|
||||
helix-view = { path = "../helix-view", features = ["term"] }
|
||||
@@ -21,7 +21,7 @@ helix-core = { path = "../helix-core" }
|
||||
bitflags.workspace = true
|
||||
cassowary = "0.3"
|
||||
unicode-segmentation.workspace = true
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
termina = { workspace = true, optional = true }
|
||||
termini = "1.0"
|
||||
once_cell = "1.21"
|
||||
log = "~0.4"
|
||||
|
@@ -14,10 +14,7 @@ use crossterm::{
|
||||
terminal::{self, Clear, ClearType},
|
||||
Command,
|
||||
};
|
||||
use helix_view::{
|
||||
editor::Config as EditorConfig,
|
||||
graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle},
|
||||
};
|
||||
use helix_view::graphics::{Color, CursorKind, Modifier, Rect, UnderlineStyle};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::{
|
||||
fmt,
|
||||
@@ -74,17 +71,17 @@ impl Capabilities {
|
||||
/// on the $TERM environment variable. If detection fails, returns
|
||||
/// a default value where no capability is supported, or just undercurl
|
||||
/// if config.undercurl is set.
|
||||
pub fn from_env_or_default(config: &EditorConfig) -> Self {
|
||||
pub fn from_env_or_default(config: &Config) -> Self {
|
||||
match termini::TermInfo::from_env() {
|
||||
Err(_) => Capabilities {
|
||||
has_extended_underlines: config.undercurl,
|
||||
has_extended_underlines: config.force_enable_extended_underlines,
|
||||
..Capabilities::default()
|
||||
},
|
||||
Ok(t) => Capabilities {
|
||||
// Smulx, VTE: https://unix.stackexchange.com/a/696253/246284
|
||||
// Su (used by kitty): https://sw.kovidgoyal.net/kitty/underlines
|
||||
// WezTerm supports underlines but a lot of distros don't properly install its terminfo
|
||||
has_extended_underlines: config.undercurl
|
||||
has_extended_underlines: config.force_enable_extended_underlines
|
||||
|| t.extended_cap("Smulx").is_some()
|
||||
|| t.extended_cap("Su").is_some()
|
||||
|| vte_version() >= Some(5102)
|
||||
@@ -98,6 +95,7 @@ impl Capabilities {
|
||||
/// Terminal backend supporting a wide variety of terminals
|
||||
pub struct CrosstermBackend<W: Write> {
|
||||
buffer: W,
|
||||
config: Config,
|
||||
capabilities: Capabilities,
|
||||
supports_keyboard_enhancement_protocol: OnceCell<bool>,
|
||||
mouse_capture_enabled: bool,
|
||||
@@ -108,14 +106,15 @@ impl<W> CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
pub fn new(buffer: W, config: &EditorConfig) -> CrosstermBackend<W> {
|
||||
pub fn new(buffer: W, config: Config) -> CrosstermBackend<W> {
|
||||
// helix is not usable without colors, but crossterm will disable
|
||||
// them by default if NO_COLOR is set in the environment. Override
|
||||
// this behaviour.
|
||||
crossterm::style::force_color_output(true);
|
||||
CrosstermBackend {
|
||||
buffer,
|
||||
capabilities: Capabilities::from_env_or_default(config),
|
||||
capabilities: Capabilities::from_env_or_default(&config),
|
||||
config,
|
||||
supports_keyboard_enhancement_protocol: OnceCell::new(),
|
||||
mouse_capture_enabled: false,
|
||||
supports_bracketed_paste: true,
|
||||
@@ -157,7 +156,7 @@ impl<W> Backend for CrosstermBackend<W>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
fn claim(&mut self, config: Config) -> io::Result<()> {
|
||||
fn claim(&mut self) -> io::Result<()> {
|
||||
terminal::enable_raw_mode()?;
|
||||
execute!(
|
||||
self.buffer,
|
||||
@@ -173,7 +172,7 @@ where
|
||||
Ok(_) => (),
|
||||
};
|
||||
execute!(self.buffer, terminal::Clear(terminal::ClearType::All))?;
|
||||
if config.enable_mouse_capture {
|
||||
if self.config.enable_mouse_capture {
|
||||
execute!(self.buffer, EnableMouseCapture)?;
|
||||
self.mouse_capture_enabled = true;
|
||||
}
|
||||
@@ -198,15 +197,16 @@ where
|
||||
}
|
||||
self.mouse_capture_enabled = config.enable_mouse_capture;
|
||||
}
|
||||
self.config = config;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore(&mut self, config: Config) -> io::Result<()> {
|
||||
fn restore(&mut self) -> io::Result<()> {
|
||||
// reset cursor shape
|
||||
self.buffer
|
||||
.write_all(self.capabilities.reset_cursor_command.as_bytes())?;
|
||||
if config.enable_mouse_capture {
|
||||
if self.config.enable_mouse_capture {
|
||||
execute!(self.buffer, DisableMouseCapture)?;
|
||||
}
|
||||
if self.supports_keyboard_enhancement_protocol() {
|
||||
|
@@ -6,10 +6,10 @@ use crate::{buffer::Cell, terminal::Config};
|
||||
|
||||
use helix_view::graphics::{CursorKind, Rect};
|
||||
|
||||
#[cfg(feature = "crossterm")]
|
||||
mod crossterm;
|
||||
#[cfg(feature = "crossterm")]
|
||||
pub use self::crossterm::CrosstermBackend;
|
||||
#[cfg(feature = "termina")]
|
||||
mod termina;
|
||||
#[cfg(feature = "termina")]
|
||||
pub use self::termina::TerminaBackend;
|
||||
|
||||
mod test;
|
||||
pub use self::test::TestBackend;
|
||||
@@ -17,13 +17,11 @@ pub use self::test::TestBackend;
|
||||
/// Representation of a terminal backend.
|
||||
pub trait Backend {
|
||||
/// Claims the terminal for TUI use.
|
||||
fn claim(&mut self, config: Config) -> Result<(), io::Error>;
|
||||
fn claim(&mut self) -> Result<(), io::Error>;
|
||||
/// Update terminal configuration.
|
||||
fn reconfigure(&mut self, config: Config) -> Result<(), io::Error>;
|
||||
/// Restores the terminal to a normal state, undoes `claim`
|
||||
fn restore(&mut self, config: Config) -> Result<(), io::Error>;
|
||||
/// Forcibly resets the terminal, ignoring errors and configuration
|
||||
fn force_restore() -> Result<(), io::Error>;
|
||||
fn restore(&mut self) -> Result<(), io::Error>;
|
||||
/// Draws styled text to the terminal
|
||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||
where
|
||||
@@ -32,8 +30,6 @@ pub trait Backend {
|
||||
fn hide_cursor(&mut self) -> Result<(), io::Error>;
|
||||
/// Sets the cursor to the given shape
|
||||
fn show_cursor(&mut self, kind: CursorKind) -> Result<(), io::Error>;
|
||||
/// Gets the current position of the cursor
|
||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>;
|
||||
/// Sets the cursor to the given position
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>;
|
||||
/// Clears the terminal
|
||||
@@ -42,4 +38,5 @@ pub trait Backend {
|
||||
fn size(&self) -> Result<Rect, io::Error>;
|
||||
/// Flushes the terminal buffer
|
||||
fn flush(&mut self) -> Result<(), io::Error>;
|
||||
fn supports_true_color(&self) -> bool;
|
||||
}
|
||||
|
622
helix-tui/src/backend/termina.rs
Normal file
622
helix-tui/src/backend/termina.rs
Normal file
@@ -0,0 +1,622 @@
|
||||
use std::io::{self, Write as _};
|
||||
|
||||
use helix_view::{
|
||||
graphics::{CursorKind, Rect, UnderlineStyle},
|
||||
theme::{Color, Modifier},
|
||||
};
|
||||
use termina::{
|
||||
escape::{
|
||||
csi::{self, Csi, SgrAttributes, SgrModifiers},
|
||||
dcs::{self, Dcs},
|
||||
},
|
||||
style::{CursorStyle, RgbColor},
|
||||
Event, OneBased, PlatformTerminal, Terminal as _, WindowSize,
|
||||
};
|
||||
use termini::TermInfo;
|
||||
|
||||
use crate::{buffer::Cell, terminal::Config};
|
||||
|
||||
use super::Backend;
|
||||
|
||||
// These macros are helpers to set/unset modes like bracketed paste or enter/exit the alternate
|
||||
// screen.
|
||||
macro_rules! decset {
|
||||
($mode:ident) => {
|
||||
Csi::Mode(csi::Mode::SetDecPrivateMode(csi::DecPrivateMode::Code(
|
||||
csi::DecPrivateModeCode::$mode,
|
||||
)))
|
||||
};
|
||||
}
|
||||
macro_rules! decreset {
|
||||
($mode:ident) => {
|
||||
Csi::Mode(csi::Mode::ResetDecPrivateMode(csi::DecPrivateMode::Code(
|
||||
csi::DecPrivateModeCode::$mode,
|
||||
)))
|
||||
};
|
||||
}
|
||||
|
||||
fn term_program() -> Option<String> {
|
||||
// Some terminals don't set $TERM_PROGRAM
|
||||
match std::env::var("TERM_PROGRAM") {
|
||||
Err(_) => std::env::var("TERM").ok(),
|
||||
Ok(term_program) => Some(term_program),
|
||||
}
|
||||
}
|
||||
fn vte_version() -> Option<usize> {
|
||||
std::env::var("VTE_VERSION").ok()?.parse().ok()
|
||||
}
|
||||
fn reset_cursor_approach(terminfo: TermInfo) -> String {
|
||||
let mut reset_str = Csi::Cursor(csi::Cursor::CursorStyle(CursorStyle::Default)).to_string();
|
||||
|
||||
if let Some(termini::Value::Utf8String(se_str)) = terminfo.extended_cap("Se") {
|
||||
reset_str.push_str(se_str);
|
||||
};
|
||||
|
||||
reset_str.push_str(
|
||||
terminfo
|
||||
.utf8_string_cap(termini::StringCapability::CursorNormal)
|
||||
.unwrap_or(""),
|
||||
);
|
||||
|
||||
reset_str
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
struct Capabilities {
|
||||
kitty_keyboard: KittyKeyboardSupport,
|
||||
synchronized_output: bool,
|
||||
true_color: bool,
|
||||
extended_underlines: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||
enum KittyKeyboardSupport {
|
||||
/// The terminal doesn't support the protocol.
|
||||
#[default]
|
||||
None,
|
||||
/// The terminal supports the protocol but we haven't checked yet whether it has full or
|
||||
/// partial support for the flags we require.
|
||||
Some,
|
||||
/// The terminal only supports some of the flags we require.
|
||||
Partial,
|
||||
/// The terminal supports all flags require.
|
||||
Full,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TerminaBackend {
|
||||
terminal: PlatformTerminal,
|
||||
config: Config,
|
||||
capabilities: Capabilities,
|
||||
reset_cursor_command: String,
|
||||
is_synchronized_output_set: bool,
|
||||
}
|
||||
|
||||
impl TerminaBackend {
|
||||
pub fn new(config: Config) -> io::Result<Self> {
|
||||
let mut terminal = PlatformTerminal::new()?;
|
||||
let (capabilities, reset_cursor_command) =
|
||||
Self::detect_capabilities(&mut terminal, &config)?;
|
||||
|
||||
// In the case of a panic, reset the terminal eagerly. If we didn't do this and instead
|
||||
// relied on `Drop`, the backtrace would be lost because it is printed before we would
|
||||
// clear and exit the alternate screen.
|
||||
let hook_reset_cursor_command = reset_cursor_command.clone();
|
||||
terminal.set_panic_hook(move |term| {
|
||||
let _ = write!(
|
||||
term,
|
||||
"{}{}{}{}{}{}{}{}{}{}{}",
|
||||
Csi::Keyboard(csi::Keyboard::PopFlags(1)),
|
||||
decreset!(MouseTracking),
|
||||
decreset!(ButtonEventMouse),
|
||||
decreset!(AnyEventMouse),
|
||||
decreset!(RXVTMouse),
|
||||
decreset!(SGRMouse),
|
||||
&hook_reset_cursor_command,
|
||||
decreset!(BracketedPaste),
|
||||
decreset!(FocusTracking),
|
||||
Csi::Edit(csi::Edit::EraseInDisplay(csi::EraseInDisplay::EraseDisplay)),
|
||||
decreset!(ClearAndEnableAlternateScreen),
|
||||
);
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
terminal,
|
||||
config,
|
||||
capabilities,
|
||||
reset_cursor_command,
|
||||
is_synchronized_output_set: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn terminal(&self) -> &PlatformTerminal {
|
||||
&self.terminal
|
||||
}
|
||||
|
||||
fn detect_capabilities(
|
||||
terminal: &mut PlatformTerminal,
|
||||
config: &Config,
|
||||
) -> io::Result<(Capabilities, String)> {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// Colibri "midnight"
|
||||
const TEST_COLOR: RgbColor = RgbColor::new(59, 34, 76);
|
||||
|
||||
terminal.enter_raw_mode()?;
|
||||
|
||||
let mut capabilities = Capabilities::default();
|
||||
let start = Instant::now();
|
||||
|
||||
// Many terminal extensions can be detected by querying the terminal for the state of the
|
||||
// extension and then sending a request for the primary device attributes (which is
|
||||
// consistently supported by all terminals). If we receive the status of the feature (for
|
||||
// example the current Kitty keyboard flags) then we know that the feature is supported.
|
||||
// If we only receive the device attributes then we know it is not.
|
||||
write!(
|
||||
terminal,
|
||||
"{}{}{}{}{}{}{}",
|
||||
// Kitty keyboard
|
||||
Csi::Keyboard(csi::Keyboard::QueryFlags),
|
||||
// Synchronized output
|
||||
Csi::Mode(csi::Mode::QueryDecPrivateMode(csi::DecPrivateMode::Code(
|
||||
csi::DecPrivateModeCode::SynchronizedOutput
|
||||
))),
|
||||
// True color and while we're at it, extended underlines:
|
||||
// <https://github.com/termstandard/colors?tab=readme-ov-file#querying-the-terminal>
|
||||
Csi::Sgr(csi::Sgr::Background(TEST_COLOR.into())),
|
||||
Csi::Sgr(csi::Sgr::UnderlineColor(TEST_COLOR.into())),
|
||||
Dcs::Request(dcs::DcsRequest::GraphicRendition),
|
||||
Csi::Sgr(csi::Sgr::Reset),
|
||||
// Finally request the primary device attributes
|
||||
Csi::Device(csi::Device::RequestPrimaryDeviceAttributes),
|
||||
)?;
|
||||
terminal.flush()?;
|
||||
|
||||
// TODO: tune this poll constant?
|
||||
let device_attributes = |event: &Event| {
|
||||
matches!(
|
||||
event,
|
||||
Event::Csi(Csi::Device(csi::Device::DeviceAttributes(_)))
|
||||
)
|
||||
};
|
||||
if terminal.poll(device_attributes, Some(Duration::from_millis(20)))? {
|
||||
while terminal.poll(Event::is_escape, Some(Duration::ZERO))? {
|
||||
match terminal.read(Event::is_escape)? {
|
||||
Event::Csi(Csi::Keyboard(csi::Keyboard::ReportFlags(_))) => {
|
||||
capabilities.kitty_keyboard = KittyKeyboardSupport::Some;
|
||||
}
|
||||
Event::Csi(Csi::Mode(csi::Mode::ReportDecPrivateMode {
|
||||
mode: csi::DecPrivateMode::Code(csi::DecPrivateModeCode::SynchronizedOutput),
|
||||
setting: csi::DecModeSetting::Set | csi::DecModeSetting::Reset,
|
||||
})) => {
|
||||
capabilities.synchronized_output = true;
|
||||
}
|
||||
Event::Dcs(dcs::Dcs::Response {
|
||||
value: dcs::DcsResponse::GraphicRendition(sgrs),
|
||||
..
|
||||
}) => {
|
||||
capabilities.true_color =
|
||||
sgrs.contains(&csi::Sgr::Background(TEST_COLOR.into()));
|
||||
capabilities.extended_underlines =
|
||||
sgrs.contains(&csi::Sgr::UnderlineColor(TEST_COLOR.into()));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let end = Instant::now();
|
||||
log::debug!(
|
||||
"Detected terminal capabilities in {:?}: {capabilities:?}",
|
||||
end.duration_since(start)
|
||||
);
|
||||
} else {
|
||||
log::debug!("Failed to detect terminal capabilities within 20ms. Using default capabilities only");
|
||||
}
|
||||
|
||||
capabilities.extended_underlines |= config.force_enable_extended_underlines;
|
||||
|
||||
let reset_cursor_approach = if let Ok(t) = termini::TermInfo::from_env() {
|
||||
capabilities.extended_underlines |= t.extended_cap("Smulx").is_some()
|
||||
|| t.extended_cap("Su").is_some()
|
||||
|| vte_version() >= Some(5102)
|
||||
// HACK: once WezTerm can support DECRQSS/DECRPSS for SGR we can remove this line.
|
||||
// <https://github.com/wezterm/wezterm/pull/6856>
|
||||
|| matches!(term_program().as_deref(), Some("WezTerm"));
|
||||
|
||||
reset_cursor_approach(t)
|
||||
} else {
|
||||
Csi::Cursor(csi::Cursor::CursorStyle(CursorStyle::Default)).to_string()
|
||||
};
|
||||
|
||||
terminal.enter_cooked_mode()?;
|
||||
|
||||
Ok((capabilities, reset_cursor_approach))
|
||||
}
|
||||
|
||||
fn enable_mouse_capture(&mut self) -> io::Result<()> {
|
||||
if self.config.enable_mouse_capture {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}{}{}{}",
|
||||
decset!(MouseTracking),
|
||||
decset!(ButtonEventMouse),
|
||||
decset!(AnyEventMouse),
|
||||
decset!(RXVTMouse),
|
||||
decset!(SGRMouse),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable_mouse_capture(&mut self) -> io::Result<()> {
|
||||
if self.config.enable_mouse_capture {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}{}{}{}",
|
||||
decreset!(MouseTracking),
|
||||
decreset!(ButtonEventMouse),
|
||||
decreset!(AnyEventMouse),
|
||||
decreset!(RXVTMouse),
|
||||
decreset!(SGRMouse),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn enable_extensions(&mut self) -> io::Result<()> {
|
||||
const KEYBOARD_FLAGS: csi::KittyKeyboardFlags =
|
||||
csi::KittyKeyboardFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||
.union(csi::KittyKeyboardFlags::REPORT_ALTERNATE_KEYS);
|
||||
|
||||
match self.capabilities.kitty_keyboard {
|
||||
KittyKeyboardSupport::None | KittyKeyboardSupport::Partial => (),
|
||||
KittyKeyboardSupport::Full => {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Keyboard(csi::Keyboard::PushFlags(KEYBOARD_FLAGS))
|
||||
)?;
|
||||
}
|
||||
KittyKeyboardSupport::Some => {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}",
|
||||
// Enable the flags we need.
|
||||
Csi::Keyboard(csi::Keyboard::PushFlags(KEYBOARD_FLAGS)),
|
||||
// Then request the current flags. We need to check if the terminal enabled
|
||||
// all of the flags we require.
|
||||
Csi::Keyboard(csi::Keyboard::QueryFlags),
|
||||
)?;
|
||||
self.terminal.flush()?;
|
||||
|
||||
let event = self.terminal.read(|event| {
|
||||
matches!(
|
||||
event,
|
||||
Event::Csi(Csi::Keyboard(csi::Keyboard::ReportFlags(_)))
|
||||
)
|
||||
})?;
|
||||
let Event::Csi(Csi::Keyboard(csi::Keyboard::ReportFlags(flags))) = event else {
|
||||
unreachable!();
|
||||
};
|
||||
if flags != KEYBOARD_FLAGS {
|
||||
log::info!("Turning off enhanced keyboard support because the terminal enabled different flags. Requested {KEYBOARD_FLAGS:?} but got {flags:?}");
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Keyboard(csi::Keyboard::PopFlags(1))
|
||||
)?;
|
||||
self.terminal.flush()?;
|
||||
self.capabilities.kitty_keyboard = KittyKeyboardSupport::Partial;
|
||||
} else {
|
||||
log::debug!(
|
||||
"The terminal fully supports the requested keyboard enhancement flags"
|
||||
);
|
||||
self.capabilities.kitty_keyboard = KittyKeyboardSupport::Full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn disable_extensions(&mut self) -> io::Result<()> {
|
||||
if self.capabilities.kitty_keyboard == KittyKeyboardSupport::Full {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Keyboard(csi::Keyboard::PopFlags(1))
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// See <https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036>.
|
||||
// Synchronized output sequences tell the terminal when we are "starting to render" and
|
||||
// stopping, enabling to make better choices about when it draws a frame. This avoids all
|
||||
// kinds of ugly visual artifacts like tearing and flashing (i.e. the background color
|
||||
// after clearing the terminal).
|
||||
|
||||
fn start_synchronized_render(&mut self) -> io::Result<()> {
|
||||
if self.capabilities.synchronized_output && !self.is_synchronized_output_set {
|
||||
write!(self.terminal, "{}", decset!(SynchronizedOutput))?;
|
||||
self.is_synchronized_output_set = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn end_sychronized_render(&mut self) -> io::Result<()> {
|
||||
if self.is_synchronized_output_set {
|
||||
write!(self.terminal, "{}", decreset!(SynchronizedOutput))?;
|
||||
self.is_synchronized_output_set = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for TerminaBackend {
|
||||
fn claim(&mut self) -> io::Result<()> {
|
||||
self.terminal.enter_raw_mode()?;
|
||||
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}{}{}",
|
||||
// Enter an alternate screen.
|
||||
decset!(ClearAndEnableAlternateScreen),
|
||||
decset!(BracketedPaste),
|
||||
decset!(FocusTracking),
|
||||
// Clear the buffer. `ClearAndEnableAlternateScreen` **should** do this but some
|
||||
// things like mosh are buggy. See <https://github.com/helix-editor/helix/pull/1944>.
|
||||
Csi::Edit(csi::Edit::EraseInDisplay(csi::EraseInDisplay::EraseDisplay)),
|
||||
)?;
|
||||
self.enable_mouse_capture()?;
|
||||
self.enable_extensions()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reconfigure(&mut self, mut config: Config) -> io::Result<()> {
|
||||
std::mem::swap(&mut self.config, &mut config);
|
||||
if self.config.enable_mouse_capture != config.enable_mouse_capture {
|
||||
if self.config.enable_mouse_capture {
|
||||
self.enable_mouse_capture()?;
|
||||
} else {
|
||||
self.disable_mouse_capture()?;
|
||||
}
|
||||
}
|
||||
self.capabilities.extended_underlines |= self.config.force_enable_extended_underlines;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore(&mut self) -> io::Result<()> {
|
||||
self.disable_extensions()?;
|
||||
self.disable_mouse_capture()?;
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}{}{}",
|
||||
&self.reset_cursor_command,
|
||||
decreset!(BracketedPaste),
|
||||
decreset!(FocusTracking),
|
||||
decreset!(ClearAndEnableAlternateScreen),
|
||||
)?;
|
||||
self.terminal.flush()?;
|
||||
self.terminal.enter_cooked_mode()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||
where
|
||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||
{
|
||||
self.start_synchronized_render()?;
|
||||
|
||||
let mut fg = Color::Reset;
|
||||
let mut bg = Color::Reset;
|
||||
let mut underline_color = Color::Reset;
|
||||
let mut underline_style = UnderlineStyle::Reset;
|
||||
let mut modifier = Modifier::empty();
|
||||
let mut last_pos: Option<(u16, u16)> = None;
|
||||
for (x, y, cell) in content {
|
||||
// Move the cursor if the previous location was not (x - 1, y)
|
||||
if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Cursor(csi::Cursor::Position {
|
||||
col: OneBased::from_zero_based(x),
|
||||
line: OneBased::from_zero_based(y),
|
||||
})
|
||||
)?;
|
||||
}
|
||||
last_pos = Some((x, y));
|
||||
|
||||
let mut attributes = SgrAttributes::default();
|
||||
if cell.fg != fg {
|
||||
attributes.foreground = Some(cell.fg.into());
|
||||
fg = cell.fg;
|
||||
}
|
||||
if cell.bg != bg {
|
||||
attributes.background = Some(cell.bg.into());
|
||||
bg = cell.bg;
|
||||
}
|
||||
if cell.modifier != modifier {
|
||||
attributes.modifiers = diff_modifiers(modifier, cell.modifier);
|
||||
modifier = cell.modifier;
|
||||
}
|
||||
|
||||
// Set underline style and color separately from SgrAttributes. Some terminals seem
|
||||
// to not like underline colors and styles being intermixed with other SGRs.
|
||||
let mut new_underline_style = cell.underline_style;
|
||||
if self.capabilities.extended_underlines {
|
||||
if cell.underline_color != underline_color {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Sgr(csi::Sgr::UnderlineColor(cell.underline_color.into()))
|
||||
)?;
|
||||
underline_color = cell.underline_color;
|
||||
}
|
||||
} else {
|
||||
match new_underline_style {
|
||||
UnderlineStyle::Reset | UnderlineStyle::Line => (),
|
||||
_ => new_underline_style = UnderlineStyle::Line,
|
||||
}
|
||||
}
|
||||
if new_underline_style != underline_style {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Sgr(csi::Sgr::Underline(new_underline_style.into()))
|
||||
)?;
|
||||
underline_style = new_underline_style;
|
||||
}
|
||||
|
||||
// `attributes` will be empty if nothing changed between two cells. Empty
|
||||
// `SgrAttributes` behave the same as a `Sgr::Reset` rather than a 'no-op' though so
|
||||
// we should avoid writing them if they're empty.
|
||||
if !attributes.is_empty() {
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Sgr(csi::Sgr::Attributes(attributes))
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(self.terminal, "{}", &cell.symbol)?;
|
||||
}
|
||||
|
||||
write!(self.terminal, "{}", Csi::Sgr(csi::Sgr::Reset))?;
|
||||
|
||||
self.end_sychronized_render()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn hide_cursor(&mut self) -> io::Result<()> {
|
||||
write!(self.terminal, "{}", decreset!(ShowCursor))?;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
|
||||
let style = match kind {
|
||||
CursorKind::Block => CursorStyle::SteadyBlock,
|
||||
CursorKind::Bar => CursorStyle::SteadyBar,
|
||||
CursorKind::Underline => CursorStyle::SteadyUnderline,
|
||||
CursorKind::Hidden => unreachable!(),
|
||||
};
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}{}",
|
||||
decset!(ShowCursor),
|
||||
Csi::Cursor(csi::Cursor::CursorStyle(style)),
|
||||
)?;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
let col = OneBased::from_zero_based(x);
|
||||
let line = OneBased::from_zero_based(y);
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Cursor(csi::Cursor::Position { line, col })
|
||||
)?;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
fn clear(&mut self) -> io::Result<()> {
|
||||
self.start_synchronized_render()?;
|
||||
write!(
|
||||
self.terminal,
|
||||
"{}",
|
||||
Csi::Edit(csi::Edit::EraseInDisplay(csi::EraseInDisplay::EraseDisplay))
|
||||
)?;
|
||||
self.flush()
|
||||
}
|
||||
|
||||
fn size(&self) -> io::Result<Rect> {
|
||||
let WindowSize { rows, cols, .. } = self.terminal.get_dimensions()?;
|
||||
Ok(Rect::new(0, 0, cols, rows))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.terminal.flush()
|
||||
}
|
||||
|
||||
fn supports_true_color(&self) -> bool {
|
||||
self.capabilities.true_color
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TerminaBackend {
|
||||
fn drop(&mut self) {
|
||||
// Avoid resetting the terminal while panicking because we set a panic hook above in
|
||||
// `Self::new`.
|
||||
if !std::thread::panicking() {
|
||||
let _ = self.disable_extensions();
|
||||
let _ = self.disable_mouse_capture();
|
||||
let _ = write!(
|
||||
self.terminal,
|
||||
"{}{}{}{}",
|
||||
&self.reset_cursor_command,
|
||||
decreset!(BracketedPaste),
|
||||
decreset!(FocusTracking),
|
||||
decreset!(ClearAndEnableAlternateScreen),
|
||||
);
|
||||
// NOTE: Drop for Platform terminal resets the mode and flushes the buffer when not
|
||||
// panicking.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_modifiers(from: Modifier, to: Modifier) -> SgrModifiers {
|
||||
let mut modifiers = SgrModifiers::default();
|
||||
|
||||
let removed = from - to;
|
||||
if removed.contains(Modifier::REVERSED) {
|
||||
modifiers |= SgrModifiers::NO_REVERSE;
|
||||
}
|
||||
if removed.contains(Modifier::BOLD) || removed.contains(Modifier::DIM) {
|
||||
modifiers |= SgrModifiers::INTENSITY_NORMAL;
|
||||
}
|
||||
if removed.contains(Modifier::ITALIC) {
|
||||
modifiers |= SgrModifiers::NO_ITALIC;
|
||||
}
|
||||
if removed.contains(Modifier::CROSSED_OUT) {
|
||||
modifiers |= SgrModifiers::NO_STRIKE_THROUGH;
|
||||
}
|
||||
if removed.contains(Modifier::HIDDEN) {
|
||||
modifiers |= SgrModifiers::NO_INVISIBLE;
|
||||
}
|
||||
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
|
||||
modifiers |= SgrModifiers::BLINK_NONE;
|
||||
}
|
||||
|
||||
let added = to - from;
|
||||
if added.contains(Modifier::REVERSED) {
|
||||
modifiers |= SgrModifiers::REVERSE;
|
||||
}
|
||||
if added.contains(Modifier::BOLD) {
|
||||
modifiers |= SgrModifiers::INTENSITY_BOLD;
|
||||
}
|
||||
if added.contains(Modifier::DIM) {
|
||||
modifiers |= SgrModifiers::INTENSITY_DIM;
|
||||
}
|
||||
if added.contains(Modifier::ITALIC) {
|
||||
modifiers |= SgrModifiers::ITALIC;
|
||||
}
|
||||
if added.contains(Modifier::CROSSED_OUT) {
|
||||
modifiers |= SgrModifiers::STRIKE_THROUGH;
|
||||
}
|
||||
if added.contains(Modifier::HIDDEN) {
|
||||
modifiers |= SgrModifiers::INVISIBLE;
|
||||
}
|
||||
if added.contains(Modifier::SLOW_BLINK) {
|
||||
modifiers |= SgrModifiers::BLINK_SLOW;
|
||||
}
|
||||
if added.contains(Modifier::RAPID_BLINK) {
|
||||
modifiers |= SgrModifiers::BLINK_RAPID;
|
||||
}
|
||||
|
||||
modifiers
|
||||
}
|
@@ -107,7 +107,7 @@ impl TestBackend {
|
||||
}
|
||||
|
||||
impl Backend for TestBackend {
|
||||
fn claim(&mut self, _config: Config) -> Result<(), io::Error> {
|
||||
fn claim(&mut self) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -115,11 +115,7 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore(&mut self, _config: Config) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn force_restore() -> Result<(), io::Error> {
|
||||
fn restore(&mut self) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -143,10 +139,6 @@ impl Backend for TestBackend {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_cursor(&mut self) -> Result<(u16, u16), io::Error> {
|
||||
Ok(self.pos)
|
||||
}
|
||||
|
||||
fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error> {
|
||||
self.pos = (x, y);
|
||||
Ok(())
|
||||
@@ -164,4 +156,8 @@ impl Backend for TestBackend {
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn supports_true_color(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
@@ -1,133 +1,3 @@
|
||||
//! [tui](https://github.com/fdehau/tui-rs) is a library used to build rich
|
||||
//! terminal users interfaces and dashboards.
|
||||
//!
|
||||
//! 
|
||||
//!
|
||||
//! # Get started
|
||||
//!
|
||||
//! ## Adding `tui` as a dependency
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! tui = "0.15"
|
||||
//! crossterm = "0.19"
|
||||
//! ```
|
||||
//!
|
||||
//! The same logic applies for all other available backends.
|
||||
//!
|
||||
//! ## Creating a `Terminal`
|
||||
//!
|
||||
//! Every application using `tui` should start by instantiating a `Terminal`. It is a light
|
||||
//! abstraction over available backends that provides basic functionalities such as clearing the
|
||||
//! screen, hiding the cursor, etc.
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use std::io;
|
||||
//! use helix_tui::Terminal;
|
||||
//! use helix_tui::backend::CrosstermBackend;
|
||||
//! use helix_view::editor::Config;
|
||||
//!
|
||||
//! fn main() -> Result<(), io::Error> {
|
||||
//! let stdout = io::stdout();
|
||||
//! let config = Config::default();
|
||||
//! let backend = CrosstermBackend::new(stdout, &config);
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! You may also refer to the examples to find out how to create a `Terminal` for each available
|
||||
//! backend.
|
||||
//!
|
||||
//! ## Building a User Interface (UI)
|
||||
//!
|
||||
//! Every component of your interface will be implementing the `Widget` trait. The library comes
|
||||
//! with a predefined set of widgets that should meet most of your use cases. You are also free to
|
||||
//! implement your own.
|
||||
//!
|
||||
//! Each widget follows a builder pattern API providing a default configuration along with methods
|
||||
//! to customize them. The widget is then rendered using the `Frame::render_widget` which take
|
||||
//! your widget instance an area to draw to.
|
||||
//!
|
||||
//! The following example renders a block of the size of the terminal:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use std::io;
|
||||
//! use crossterm::terminal;
|
||||
//! use helix_tui::Terminal;
|
||||
//! use helix_tui::backend::CrosstermBackend;
|
||||
//! use helix_tui::widgets::{Widget, Block, Borders};
|
||||
//! use helix_tui::layout::{Layout, Constraint, Direction};
|
||||
//! use helix_view::editor::Config;
|
||||
//!
|
||||
//! fn main() -> Result<(), io::Error> {
|
||||
//! terminal::enable_raw_mode().unwrap();
|
||||
//! let stdout = io::stdout();
|
||||
//! let config = Config::default();
|
||||
//! let backend = CrosstermBackend::new(stdout, &config);
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! // terminal.draw(|f| {
|
||||
//! // let size = f.size();
|
||||
//! // let block = Block::default()
|
||||
//! // .title("Block")
|
||||
//! // .borders(Borders::ALL);
|
||||
//! // f.render_widget(block, size);
|
||||
//! // })?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Layout
|
||||
//!
|
||||
//! The library comes with a basic yet useful layout management object called `Layout`. As you may
|
||||
//! see below and in the examples, the library makes heavy use of the builder pattern to provide
|
||||
//! full customization. And `Layout` is no exception:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use std::io;
|
||||
//! use crossterm::terminal;
|
||||
//! use helix_tui::Terminal;
|
||||
//! use helix_tui::backend::CrosstermBackend;
|
||||
//! use helix_tui::widgets::{Widget, Block, Borders};
|
||||
//! use helix_tui::layout::{Layout, Constraint, Direction};
|
||||
//! use helix_view::editor::Config;
|
||||
//!
|
||||
//! fn main() -> Result<(), io::Error> {
|
||||
//! terminal::enable_raw_mode().unwrap();
|
||||
//! let stdout = io::stdout();
|
||||
//! let config = Config::default();
|
||||
//! let backend = CrosstermBackend::new(stdout, &config);
|
||||
//! let mut terminal = Terminal::new(backend)?;
|
||||
//! // terminal.draw(|f| {
|
||||
//! // let chunks = Layout::default()
|
||||
//! // .direction(Direction::Vertical)
|
||||
//! // .margin(1)
|
||||
//! // .constraints(
|
||||
//! // [
|
||||
//! // Constraint::Percentage(10),
|
||||
//! // Constraint::Percentage(80),
|
||||
//! // Constraint::Percentage(10)
|
||||
//! // ].as_ref()
|
||||
//! // )
|
||||
//! // .split(f.size());
|
||||
//! // let block = Block::default()
|
||||
//! // .title("Block")
|
||||
//! // .borders(Borders::ALL);
|
||||
//! // f.render_widget(block, chunks[0]);
|
||||
//! // let block = Block::default()
|
||||
//! // .title("Block 2")
|
||||
//! // .borders(Borders::ALL);
|
||||
//! // f.render_widget(block, chunks[1]);
|
||||
//! // })?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! This let you describe responsive terminal UI by nesting layouts. You should note that by
|
||||
//! default the computed layout tries to fill the available space completely. So if for any reason
|
||||
//! you might need a blank space somewhere, try to pass an additional constraint and don't use the
|
||||
//! corresponding area.
|
||||
|
||||
pub mod backend;
|
||||
pub mod buffer;
|
||||
pub mod layout;
|
||||
|
@@ -24,12 +24,14 @@ pub struct Viewport {
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub enable_mouse_capture: bool,
|
||||
pub force_enable_extended_underlines: bool,
|
||||
}
|
||||
|
||||
impl From<EditorConfig> for Config {
|
||||
fn from(config: EditorConfig) -> Self {
|
||||
impl From<&EditorConfig> for Config {
|
||||
fn from(config: &EditorConfig) -> Self {
|
||||
Self {
|
||||
enable_mouse_capture: config.mouse,
|
||||
force_enable_extended_underlines: config.undercurl,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,16 +104,16 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
pub fn claim(&mut self, config: Config) -> io::Result<()> {
|
||||
self.backend.claim(config)
|
||||
pub fn claim(&mut self) -> io::Result<()> {
|
||||
self.backend.claim()
|
||||
}
|
||||
|
||||
pub fn reconfigure(&mut self, config: Config) -> io::Result<()> {
|
||||
self.backend.reconfigure(config)
|
||||
}
|
||||
|
||||
pub fn restore(&mut self, config: Config) -> io::Result<()> {
|
||||
self.backend.restore(config)
|
||||
pub fn restore(&mut self) -> io::Result<()> {
|
||||
self.backend.restore()
|
||||
}
|
||||
|
||||
// /// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
||||
@@ -218,10 +220,6 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_cursor(&mut self) -> io::Result<(u16, u16)> {
|
||||
self.backend.get_cursor()
|
||||
}
|
||||
|
||||
pub fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> {
|
||||
self.backend.set_cursor(x, y)
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ homepage.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
term = ["crossterm"]
|
||||
term = ["termina"]
|
||||
unicode-lines = []
|
||||
|
||||
[dependencies]
|
||||
@@ -26,7 +26,7 @@ helix-vcs = { path = "../helix-vcs" }
|
||||
|
||||
bitflags.workspace = true
|
||||
anyhow = "1"
|
||||
crossterm = { version = "0.28", optional = true }
|
||||
termina = { workspace = true, optional = true }
|
||||
|
||||
tempfile.workspace = true
|
||||
|
||||
|
@@ -1,163 +0,0 @@
|
||||
// A minimal base64 implementation to keep from pulling in a crate for just that. It's based on
|
||||
// https://github.com/marshallpierce/rust-base64 but without all the customization options.
|
||||
// The biggest portion comes from
|
||||
// https://github.com/marshallpierce/rust-base64/blob/a675443d327e175f735a37f574de803d6a332591/src/engine/naive.rs#L42
|
||||
// Thanks, rust-base64!
|
||||
|
||||
// The MIT License (MIT)
|
||||
|
||||
// Copyright (c) 2015 Alice Maz
|
||||
|
||||
// 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.
|
||||
|
||||
use std::ops::{BitAnd, BitOr, Shl, Shr};
|
||||
|
||||
const PAD_BYTE: u8 = b'=';
|
||||
const ENCODE_TABLE: &[u8] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".as_bytes();
|
||||
const LOW_SIX_BITS: u32 = 0x3F;
|
||||
|
||||
pub fn encode(input: &[u8]) -> String {
|
||||
let rem = input.len() % 3;
|
||||
let complete_chunks = input.len() / 3;
|
||||
let remainder_chunk = usize::from(rem != 0);
|
||||
let encoded_size = (complete_chunks + remainder_chunk) * 4;
|
||||
|
||||
let mut output = vec![0; encoded_size];
|
||||
|
||||
// complete chunks first
|
||||
let complete_chunk_len = input.len() - rem;
|
||||
|
||||
let mut input_index = 0_usize;
|
||||
let mut output_index = 0_usize;
|
||||
while input_index < complete_chunk_len {
|
||||
let chunk = &input[input_index..input_index + 3];
|
||||
|
||||
// populate low 24 bits from 3 bytes
|
||||
let chunk_int: u32 =
|
||||
(chunk[0] as u32).shl(16) | (chunk[1] as u32).shl(8) | (chunk[2] as u32);
|
||||
// encode 4x 6-bit output bytes
|
||||
output[output_index] = ENCODE_TABLE[chunk_int.shr(18) as usize];
|
||||
output[output_index + 1] = ENCODE_TABLE[chunk_int.shr(12_u8).bitand(LOW_SIX_BITS) as usize];
|
||||
output[output_index + 2] = ENCODE_TABLE[chunk_int.shr(6_u8).bitand(LOW_SIX_BITS) as usize];
|
||||
output[output_index + 3] = ENCODE_TABLE[chunk_int.bitand(LOW_SIX_BITS) as usize];
|
||||
|
||||
input_index += 3;
|
||||
output_index += 4;
|
||||
}
|
||||
|
||||
// then leftovers
|
||||
if rem == 2 {
|
||||
let chunk = &input[input_index..input_index + 2];
|
||||
|
||||
// high six bits of chunk[0]
|
||||
output[output_index] = ENCODE_TABLE[chunk[0].shr(2) as usize];
|
||||
// bottom 2 bits of [0], high 4 bits of [1]
|
||||
output[output_index + 1] = ENCODE_TABLE
|
||||
[(chunk[0].shl(4_u8).bitor(chunk[1].shr(4_u8)) as u32).bitand(LOW_SIX_BITS) as usize];
|
||||
// bottom 4 bits of [1], with the 2 bottom bits as zero
|
||||
output[output_index + 2] =
|
||||
ENCODE_TABLE[(chunk[1].shl(2_u8) as u32).bitand(LOW_SIX_BITS) as usize];
|
||||
output[output_index + 3] = PAD_BYTE;
|
||||
} else if rem == 1 {
|
||||
let byte = input[input_index];
|
||||
output[output_index] = ENCODE_TABLE[byte.shr(2) as usize];
|
||||
output[output_index + 1] =
|
||||
ENCODE_TABLE[(byte.shl(4_u8) as u32).bitand(LOW_SIX_BITS) as usize];
|
||||
output[output_index + 2] = PAD_BYTE;
|
||||
output[output_index + 3] = PAD_BYTE;
|
||||
}
|
||||
String::from_utf8(output).expect("Invalid UTF8")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn compare_encode(expected: &str, target: &[u8]) {
|
||||
assert_eq!(expected, super::encode(target));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_0() {
|
||||
compare_encode("", b"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_1() {
|
||||
compare_encode("Zg==", b"f");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_2() {
|
||||
compare_encode("Zm8=", b"fo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_3() {
|
||||
compare_encode("Zm9v", b"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_4() {
|
||||
compare_encode("Zm9vYg==", b"foob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_5() {
|
||||
compare_encode("Zm9vYmE=", b"fooba");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_rfc4648_6() {
|
||||
compare_encode("Zm9vYmFy", b"foobar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_all_ascii() {
|
||||
let mut ascii = Vec::<u8>::with_capacity(128);
|
||||
|
||||
for i in 0..128 {
|
||||
ascii.push(i);
|
||||
}
|
||||
|
||||
compare_encode(
|
||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
|
||||
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn8\
|
||||
=",
|
||||
&ascii,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_all_bytes() {
|
||||
let mut bytes = Vec::<u8>::with_capacity(256);
|
||||
|
||||
for i in 0..255 {
|
||||
bytes.push(i);
|
||||
}
|
||||
bytes.push(255); //bug with "overflowing" ranges?
|
||||
|
||||
compare_encode(
|
||||
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7P\
|
||||
D0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn\
|
||||
+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6\
|
||||
/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==",
|
||||
&bytes,
|
||||
);
|
||||
}
|
||||
}
|
@@ -292,10 +292,17 @@ mod external {
|
||||
},
|
||||
#[cfg(feature = "term")]
|
||||
Self::Termcode => {
|
||||
crossterm::queue!(
|
||||
std::io::stdout(),
|
||||
osc52::SetClipboardCommand::new(content, clipboard_type)
|
||||
)?;
|
||||
use std::io::Write;
|
||||
use termina::escape::osc::{self, Osc};
|
||||
let selection = match clipboard_type {
|
||||
ClipboardType::Clipboard => osc::Selection::CLIPBOARD,
|
||||
ClipboardType::Selection => osc::Selection::PRIMARY,
|
||||
};
|
||||
// NOTE: it would be ideal to have the terminal execute this but it _should_
|
||||
// work to send this over stdout instead.
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
write!(stdout, "{}", Osc::SetSelection(selection, content))?;
|
||||
stdout.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
Self::Custom(command_provider) => match clipboard_type {
|
||||
@@ -400,43 +407,6 @@ mod external {
|
||||
paste => "termux-clipboard-set";
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
mod osc52 {
|
||||
use {super::ClipboardType, crate::base64};
|
||||
|
||||
pub struct SetClipboardCommand {
|
||||
encoded_content: String,
|
||||
clipboard_type: ClipboardType,
|
||||
}
|
||||
|
||||
impl SetClipboardCommand {
|
||||
pub fn new(content: &str, clipboard_type: ClipboardType) -> Self {
|
||||
Self {
|
||||
encoded_content: base64::encode(content.as_bytes()),
|
||||
clipboard_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crossterm::Command for SetClipboardCommand {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
let kind = match &self.clipboard_type {
|
||||
ClipboardType::Clipboard => "c",
|
||||
ClipboardType::Selection => "p",
|
||||
};
|
||||
// Send an OSC 52 set command: https://terminalguide.namepad.de/seq/osc-52/
|
||||
write!(f, "\x1b]52;{};{}\x1b\\", kind, &self.encoded_content)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::result::Result<(), std::io::Error> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"OSC clipboard codes not supported by winapi.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_command(
|
||||
cmd: &Command,
|
||||
input: Option<&str>,
|
||||
|
@@ -289,30 +289,28 @@ impl Color {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<Color> for crossterm::style::Color {
|
||||
impl From<Color> for termina::style::ColorSpec {
|
||||
fn from(color: Color) -> Self {
|
||||
use crossterm::style::Color as CColor;
|
||||
|
||||
match color {
|
||||
Color::Reset => CColor::Reset,
|
||||
Color::Black => CColor::Black,
|
||||
Color::Red => CColor::DarkRed,
|
||||
Color::Green => CColor::DarkGreen,
|
||||
Color::Yellow => CColor::DarkYellow,
|
||||
Color::Blue => CColor::DarkBlue,
|
||||
Color::Magenta => CColor::DarkMagenta,
|
||||
Color::Cyan => CColor::DarkCyan,
|
||||
Color::Gray => CColor::DarkGrey,
|
||||
Color::LightRed => CColor::Red,
|
||||
Color::LightGreen => CColor::Green,
|
||||
Color::LightBlue => CColor::Blue,
|
||||
Color::LightYellow => CColor::Yellow,
|
||||
Color::LightMagenta => CColor::Magenta,
|
||||
Color::LightCyan => CColor::Cyan,
|
||||
Color::LightGray => CColor::Grey,
|
||||
Color::White => CColor::White,
|
||||
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||
Color::Reset => Self::Reset,
|
||||
Color::Black => Self::BLACK,
|
||||
Color::Red => Self::RED,
|
||||
Color::Green => Self::GREEN,
|
||||
Color::Yellow => Self::YELLOW,
|
||||
Color::Blue => Self::BLUE,
|
||||
Color::Magenta => Self::MAGENTA,
|
||||
Color::Cyan => Self::CYAN,
|
||||
Color::Gray => Self::BRIGHT_BLACK,
|
||||
Color::White => Self::WHITE,
|
||||
Color::LightRed => Self::BRIGHT_RED,
|
||||
Color::LightGreen => Self::BRIGHT_GREEN,
|
||||
Color::LightBlue => Self::BRIGHT_BLUE,
|
||||
Color::LightYellow => Self::BRIGHT_YELLOW,
|
||||
Color::LightMagenta => Self::BRIGHT_MAGENTA,
|
||||
Color::LightCyan => Self::BRIGHT_CYAN,
|
||||
Color::LightGray => Self::BRIGHT_WHITE,
|
||||
Color::Indexed(i) => Self::PaletteIndex(i),
|
||||
Color::Rgb(r, g, b) => termina::style::RgbColor::new(r, g, b).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,15 +341,15 @@ impl FromStr for UnderlineStyle {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<UnderlineStyle> for crossterm::style::Attribute {
|
||||
impl From<UnderlineStyle> for termina::style::Underline {
|
||||
fn from(style: UnderlineStyle) -> Self {
|
||||
match style {
|
||||
UnderlineStyle::Line => crossterm::style::Attribute::Underlined,
|
||||
UnderlineStyle::Curl => crossterm::style::Attribute::Undercurled,
|
||||
UnderlineStyle::Dotted => crossterm::style::Attribute::Underdotted,
|
||||
UnderlineStyle::Dashed => crossterm::style::Attribute::Underdashed,
|
||||
UnderlineStyle::DoubleLine => crossterm::style::Attribute::DoubleUnderlined,
|
||||
UnderlineStyle::Reset => crossterm::style::Attribute::NoUnderline,
|
||||
UnderlineStyle::Reset => Self::None,
|
||||
UnderlineStyle::Line => Self::Single,
|
||||
UnderlineStyle::Curl => Self::Curly,
|
||||
UnderlineStyle::Dotted => Self::Dotted,
|
||||
UnderlineStyle::Dashed => Self::Dashed,
|
||||
UnderlineStyle::DoubleLine => Self::Double,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! Input event handling, currently backed by crossterm.
|
||||
//! Input event handling, currently backed by termina.
|
||||
use anyhow::{anyhow, Error};
|
||||
use helix_core::unicode::{segmentation::UnicodeSegmentation, width::UnicodeWidthStr};
|
||||
use serde::de::{self, Deserialize, Deserializer};
|
||||
@@ -65,7 +65,7 @@ pub enum MouseButton {
|
||||
pub struct KeyEvent {
|
||||
pub code: KeyCode,
|
||||
pub modifiers: KeyModifiers,
|
||||
// TODO: crossterm now supports kind & state if terminal supports kitty's extended protocol
|
||||
// TODO: termina now supports kind & state if terminal supports kitty's extended protocol
|
||||
}
|
||||
|
||||
impl KeyEvent {
|
||||
@@ -459,28 +459,31 @@ impl<'de> Deserialize<'de> for KeyEvent {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::Event> for Event {
|
||||
fn from(event: crossterm::event::Event) -> Self {
|
||||
impl From<termina::event::Event> for Event {
|
||||
fn from(event: termina::event::Event) -> Self {
|
||||
match event {
|
||||
crossterm::event::Event::Key(key) => Self::Key(key.into()),
|
||||
crossterm::event::Event::Mouse(mouse) => Self::Mouse(mouse.into()),
|
||||
crossterm::event::Event::Resize(w, h) => Self::Resize(w, h),
|
||||
crossterm::event::Event::FocusGained => Self::FocusGained,
|
||||
crossterm::event::Event::FocusLost => Self::FocusLost,
|
||||
crossterm::event::Event::Paste(s) => Self::Paste(s),
|
||||
termina::event::Event::Key(key) => Self::Key(key.into()),
|
||||
termina::event::Event::Mouse(mouse) => Self::Mouse(mouse.into()),
|
||||
termina::event::Event::WindowResized(termina::WindowSize { rows, cols, .. }) => {
|
||||
Self::Resize(cols, rows)
|
||||
}
|
||||
termina::event::Event::FocusIn => Self::FocusGained,
|
||||
termina::event::Event::FocusOut => Self::FocusLost,
|
||||
termina::event::Event::Paste(s) => Self::Paste(s),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::MouseEvent> for MouseEvent {
|
||||
impl From<termina::event::MouseEvent> for MouseEvent {
|
||||
fn from(
|
||||
crossterm::event::MouseEvent {
|
||||
termina::event::MouseEvent {
|
||||
kind,
|
||||
column,
|
||||
row,
|
||||
modifiers,
|
||||
}: crossterm::event::MouseEvent,
|
||||
}: termina::event::MouseEvent,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind: kind.into(),
|
||||
@@ -492,40 +495,40 @@ impl From<crossterm::event::MouseEvent> for MouseEvent {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::MouseEventKind> for MouseEventKind {
|
||||
fn from(kind: crossterm::event::MouseEventKind) -> Self {
|
||||
impl From<termina::event::MouseEventKind> for MouseEventKind {
|
||||
fn from(kind: termina::event::MouseEventKind) -> Self {
|
||||
match kind {
|
||||
crossterm::event::MouseEventKind::Down(button) => Self::Down(button.into()),
|
||||
crossterm::event::MouseEventKind::Up(button) => Self::Up(button.into()),
|
||||
crossterm::event::MouseEventKind::Drag(button) => Self::Drag(button.into()),
|
||||
crossterm::event::MouseEventKind::Moved => Self::Moved,
|
||||
crossterm::event::MouseEventKind::ScrollDown => Self::ScrollDown,
|
||||
crossterm::event::MouseEventKind::ScrollUp => Self::ScrollUp,
|
||||
crossterm::event::MouseEventKind::ScrollLeft => Self::ScrollLeft,
|
||||
crossterm::event::MouseEventKind::ScrollRight => Self::ScrollRight,
|
||||
termina::event::MouseEventKind::Down(button) => Self::Down(button.into()),
|
||||
termina::event::MouseEventKind::Up(button) => Self::Up(button.into()),
|
||||
termina::event::MouseEventKind::Drag(button) => Self::Drag(button.into()),
|
||||
termina::event::MouseEventKind::Moved => Self::Moved,
|
||||
termina::event::MouseEventKind::ScrollDown => Self::ScrollDown,
|
||||
termina::event::MouseEventKind::ScrollUp => Self::ScrollUp,
|
||||
termina::event::MouseEventKind::ScrollLeft => Self::ScrollLeft,
|
||||
termina::event::MouseEventKind::ScrollRight => Self::ScrollRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::MouseButton> for MouseButton {
|
||||
fn from(button: crossterm::event::MouseButton) -> Self {
|
||||
impl From<termina::event::MouseButton> for MouseButton {
|
||||
fn from(button: termina::event::MouseButton) -> Self {
|
||||
match button {
|
||||
crossterm::event::MouseButton::Left => MouseButton::Left,
|
||||
crossterm::event::MouseButton::Right => MouseButton::Right,
|
||||
crossterm::event::MouseButton::Middle => MouseButton::Middle,
|
||||
termina::event::MouseButton::Left => MouseButton::Left,
|
||||
termina::event::MouseButton::Right => MouseButton::Right,
|
||||
termina::event::MouseButton::Middle => MouseButton::Middle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::KeyEvent> for KeyEvent {
|
||||
impl From<termina::event::KeyEvent> for KeyEvent {
|
||||
fn from(
|
||||
crossterm::event::KeyEvent {
|
||||
termina::event::KeyEvent {
|
||||
code, modifiers, ..
|
||||
}: crossterm::event::KeyEvent,
|
||||
}: termina::event::KeyEvent,
|
||||
) -> Self {
|
||||
if code == crossterm::event::KeyCode::BackTab {
|
||||
if code == termina::event::KeyCode::BackTab {
|
||||
// special case for BackTab -> Shift-Tab
|
||||
let mut modifiers: KeyModifiers = modifiers.into();
|
||||
modifiers.insert(KeyModifiers::SHIFT);
|
||||
@@ -543,24 +546,24 @@ impl From<crossterm::event::KeyEvent> for KeyEvent {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<KeyEvent> for crossterm::event::KeyEvent {
|
||||
impl From<KeyEvent> for termina::event::KeyEvent {
|
||||
fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
|
||||
if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) {
|
||||
// special case for Shift-Tab -> BackTab
|
||||
let mut modifiers = modifiers;
|
||||
modifiers.remove(KeyModifiers::SHIFT);
|
||||
crossterm::event::KeyEvent {
|
||||
code: crossterm::event::KeyCode::BackTab,
|
||||
termina::event::KeyEvent {
|
||||
code: termina::event::KeyCode::BackTab,
|
||||
modifiers: modifiers.into(),
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE,
|
||||
kind: termina::event::KeyEventKind::Press,
|
||||
state: termina::event::KeyEventState::NONE,
|
||||
}
|
||||
} else {
|
||||
crossterm::event::KeyEvent {
|
||||
termina::event::KeyEvent {
|
||||
code: code.into(),
|
||||
modifiers: modifiers.into(),
|
||||
kind: crossterm::event::KeyEventKind::Press,
|
||||
state: crossterm::event::KeyEventState::NONE,
|
||||
kind: termina::event::KeyEventKind::Press,
|
||||
state: termina::event::KeyEventState::NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,9 +13,9 @@ bitflags! {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<KeyModifiers> for crossterm::event::KeyModifiers {
|
||||
impl From<KeyModifiers> for termina::event::Modifiers {
|
||||
fn from(key_modifiers: KeyModifiers) -> Self {
|
||||
use crossterm::event::KeyModifiers as CKeyModifiers;
|
||||
use termina::event::Modifiers as CKeyModifiers;
|
||||
|
||||
let mut result = CKeyModifiers::NONE;
|
||||
|
||||
@@ -37,9 +37,9 @@ impl From<KeyModifiers> for crossterm::event::KeyModifiers {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::KeyModifiers> for KeyModifiers {
|
||||
fn from(val: crossterm::event::KeyModifiers) -> Self {
|
||||
use crossterm::event::KeyModifiers as CKeyModifiers;
|
||||
impl From<termina::event::Modifiers> for KeyModifiers {
|
||||
fn from(val: termina::event::Modifiers) -> Self {
|
||||
use termina::event::Modifiers as CKeyModifiers;
|
||||
|
||||
let mut result = KeyModifiers::NONE;
|
||||
|
||||
@@ -92,9 +92,9 @@ pub enum MediaKeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
|
||||
impl From<MediaKeyCode> for termina::event::MediaKeyCode {
|
||||
fn from(media_key_code: MediaKeyCode) -> Self {
|
||||
use crossterm::event::MediaKeyCode as CMediaKeyCode;
|
||||
use termina::event::MediaKeyCode as CMediaKeyCode;
|
||||
|
||||
match media_key_code {
|
||||
MediaKeyCode::Play => CMediaKeyCode::Play,
|
||||
@@ -115,9 +115,9 @@ impl From<MediaKeyCode> for crossterm::event::MediaKeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::MediaKeyCode> for MediaKeyCode {
|
||||
fn from(val: crossterm::event::MediaKeyCode) -> Self {
|
||||
use crossterm::event::MediaKeyCode as CMediaKeyCode;
|
||||
impl From<termina::event::MediaKeyCode> for MediaKeyCode {
|
||||
fn from(val: termina::event::MediaKeyCode) -> Self {
|
||||
use termina::event::MediaKeyCode as CMediaKeyCode;
|
||||
|
||||
match val {
|
||||
CMediaKeyCode::Play => MediaKeyCode::Play,
|
||||
@@ -171,9 +171,9 @@ pub enum ModifierKeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
|
||||
impl From<ModifierKeyCode> for termina::event::ModifierKeyCode {
|
||||
fn from(modifier_key_code: ModifierKeyCode) -> Self {
|
||||
use crossterm::event::ModifierKeyCode as CModifierKeyCode;
|
||||
use termina::event::ModifierKeyCode as CModifierKeyCode;
|
||||
|
||||
match modifier_key_code {
|
||||
ModifierKeyCode::LeftShift => CModifierKeyCode::LeftShift,
|
||||
@@ -195,9 +195,9 @@ impl From<ModifierKeyCode> for crossterm::event::ModifierKeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::ModifierKeyCode> for ModifierKeyCode {
|
||||
fn from(val: crossterm::event::ModifierKeyCode) -> Self {
|
||||
use crossterm::event::ModifierKeyCode as CModifierKeyCode;
|
||||
impl From<termina::event::ModifierKeyCode> for ModifierKeyCode {
|
||||
fn from(val: termina::event::ModifierKeyCode) -> Self {
|
||||
use termina::event::ModifierKeyCode as CModifierKeyCode;
|
||||
|
||||
match val {
|
||||
CModifierKeyCode::LeftShift => ModifierKeyCode::LeftShift,
|
||||
@@ -280,9 +280,9 @@ pub enum KeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<KeyCode> for crossterm::event::KeyCode {
|
||||
impl From<KeyCode> for termina::event::KeyCode {
|
||||
fn from(key_code: KeyCode) -> Self {
|
||||
use crossterm::event::KeyCode as CKeyCode;
|
||||
use termina::event::KeyCode as CKeyCode;
|
||||
|
||||
match key_code {
|
||||
KeyCode::Backspace => CKeyCode::Backspace,
|
||||
@@ -298,10 +298,10 @@ impl From<KeyCode> for crossterm::event::KeyCode {
|
||||
KeyCode::Tab => CKeyCode::Tab,
|
||||
KeyCode::Delete => CKeyCode::Delete,
|
||||
KeyCode::Insert => CKeyCode::Insert,
|
||||
KeyCode::F(f_number) => CKeyCode::F(f_number),
|
||||
KeyCode::F(f_number) => CKeyCode::Function(f_number),
|
||||
KeyCode::Char(character) => CKeyCode::Char(character),
|
||||
KeyCode::Null => CKeyCode::Null,
|
||||
KeyCode::Esc => CKeyCode::Esc,
|
||||
KeyCode::Esc => CKeyCode::Escape,
|
||||
KeyCode::CapsLock => CKeyCode::CapsLock,
|
||||
KeyCode::ScrollLock => CKeyCode::ScrollLock,
|
||||
KeyCode::NumLock => CKeyCode::NumLock,
|
||||
@@ -316,9 +316,9 @@ impl From<KeyCode> for crossterm::event::KeyCode {
|
||||
}
|
||||
|
||||
#[cfg(feature = "term")]
|
||||
impl From<crossterm::event::KeyCode> for KeyCode {
|
||||
fn from(val: crossterm::event::KeyCode) -> Self {
|
||||
use crossterm::event::KeyCode as CKeyCode;
|
||||
impl From<termina::event::KeyCode> for KeyCode {
|
||||
fn from(val: termina::event::KeyCode) -> Self {
|
||||
use termina::event::KeyCode as CKeyCode;
|
||||
|
||||
match val {
|
||||
CKeyCode::Backspace => KeyCode::Backspace,
|
||||
@@ -335,10 +335,10 @@ impl From<crossterm::event::KeyCode> for KeyCode {
|
||||
CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"),
|
||||
CKeyCode::Delete => KeyCode::Delete,
|
||||
CKeyCode::Insert => KeyCode::Insert,
|
||||
CKeyCode::F(f_number) => KeyCode::F(f_number),
|
||||
CKeyCode::Function(f_number) => KeyCode::F(f_number),
|
||||
CKeyCode::Char(character) => KeyCode::Char(character),
|
||||
CKeyCode::Null => KeyCode::Null,
|
||||
CKeyCode::Esc => KeyCode::Esc,
|
||||
CKeyCode::Escape => KeyCode::Esc,
|
||||
CKeyCode::CapsLock => KeyCode::CapsLock,
|
||||
CKeyCode::ScrollLock => KeyCode::ScrollLock,
|
||||
CKeyCode::NumLock => KeyCode::NumLock,
|
||||
|
@@ -2,7 +2,6 @@
|
||||
pub mod macros;
|
||||
|
||||
pub mod annotations;
|
||||
pub mod base64;
|
||||
pub mod clipboard;
|
||||
pub mod document;
|
||||
pub mod editor;
|
||||
|
Reference in New Issue
Block a user