diff --git a/Cargo.lock b/Cargo.lock index 80645e1ea..b4b4e29c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,12 +29,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "0.2.17" @@ -70,6 +85,12 @@ version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -82,7 +103,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "encoding_rs", "memchr", ] @@ -119,13 +140,22 @@ dependencies = [ "memchr", ] +[[package]] +name = "cpufeatures" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-utils" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "lazy_static", ] @@ -141,7 +171,7 @@ dependencies = [ "libc", "mio 0.7.14", "parking_lot", - "signal-hook", + "signal-hook 0.3.13", "signal-hook-mio", "winapi", ] @@ -155,16 +185,46 @@ dependencies = [ "winapi", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -188,7 +248,7 @@ version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -216,7 +276,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "016b04fd1e94fb833d432634245c9bb61cf1c7409668a0e7d4c3ab00c5172dec" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-next", "thiserror", ] @@ -230,6 +290,17 @@ dependencies = [ "log", ] +[[package]] +name = "filedescriptor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" +dependencies = [ + "libc", + "thiserror", + "winapi", +] + [[package]] name = "fnv" version = "1.0.7" @@ -291,13 +362,34 @@ dependencies = [ "thread_local", ] +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi 0.10.2+wasi-snapshot-preview1", ] @@ -457,8 +549,9 @@ dependencies = [ "retain_mut", "serde", "serde_json", - "signal-hook", + "signal-hook 0.3.13", "signal-hook-tokio", + "termwiz", "tokio", "tokio-stream", "toml", @@ -475,6 +568,7 @@ dependencies = [ "helix-core", "helix-view", "serde", + "termwiz", "unicode-segmentation", ] @@ -496,6 +590,7 @@ dependencies = [ "once_cell", "serde", "slotmap", + "termwiz", "tokio", "tokio-stream", "toml", @@ -512,6 +607,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "idna" version = "0.2.3" @@ -578,7 +679,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "winapi", ] @@ -597,7 +698,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -634,6 +735,12 @@ dependencies = [ "libc", ] +[[package]] +name = "memmem" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" + [[package]] name = "mio" version = "0.7.14" @@ -670,6 +777,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.7" @@ -679,6 +796,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-integer" version = "0.1.44" @@ -714,6 +842,21 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + [[package]] name = "parking_lot" version = "0.12.0" @@ -730,7 +873,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", @@ -743,6 +886,53 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.8" @@ -755,6 +945,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.36" @@ -781,7 +977,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -793,13 +989,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -808,7 +1037,25 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom", + "getrandom 0.2.5", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -826,7 +1073,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom", + "getrandom 0.2.5", "redox_syscall", ] @@ -889,6 +1136,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.136" @@ -931,6 +1196,29 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook" version = "0.3.13" @@ -949,7 +1237,7 @@ checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", "mio 0.7.14", - "signal-hook", + "signal-hook 0.3.13", ] [[package]] @@ -969,7 +1257,7 @@ checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e" dependencies = [ "futures-core", "libc", - "signal-hook", + "signal-hook 0.3.13", "tokio", ] @@ -979,6 +1267,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.5" @@ -1042,6 +1336,60 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "terminfo" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" +dependencies = [ + "dirs", + "fnv", + "nom", + "phf", + "phf_codegen", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "termwiz" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ef6892cc0348a9b3b8c377addba91e0f6365863d92354bf27559dca81ee8c5" +dependencies = [ + "anyhow", + "base64", + "bitflags", + "cfg-if 1.0.0", + "filedescriptor", + "hex", + "lazy_static", + "libc", + "log", + "memmem", + "num-derive", + "num-traits", + "ordered-float", + "regex", + "semver", + "sha2", + "signal-hook 0.1.17", + "terminfo", + "termios", + "thiserror", + "ucd-trie", + "unicode-segmentation", + "vtparse", + "winapi", +] + [[package]] name = "thiserror" version = "1.0.30" @@ -1156,6 +1504,18 @@ dependencies = [ "regex", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicase" version = "2.6.0" @@ -1217,12 +1577,27 @@ dependencies = [ "serde", ] +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vtparse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f41c9314c4dde1f43dd0c46c67bb5ae73850ce11eebaf7d8b912e178bda5401" +dependencies = [ + "utf8parse", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -1234,6 +1609,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 48365743f..fd939df72 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -35,8 +35,9 @@ which = "4.2" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" -tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } +tui = { path = "../helix-tui", package = "helix-tui" } crossterm = { version = "0.23", features = ["event-stream"] } +termwiz = "0.15" signal-hook = "0.3" tokio-stream = "0.1" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 269ce13d1..df7ce871e 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -254,7 +254,7 @@ impl Application { use helix_view::graphics::Rect; match signal { signal::SIGTSTP => { - self.compositor.save_cursor(); + self.compositor.restore_cursor(); self.restore_term().unwrap(); low_level::emulate_default_handler(signal::SIGTSTP).unwrap(); } diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 4f988acee..ce29eb499 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -71,51 +71,52 @@ pub trait Component: Any + AnyComponent { } } -use anyhow::Error; -use std::io::stdout; -use tui::backend::{Backend, CrosstermBackend}; -type Terminal = tui::terminal::Terminal>; +use termwiz::{ + caps::Capabilities, surface::CursorVisibility, terminal::buffered::BufferedTerminal, + terminal::SystemTerminal, +}; +type Terminal = BufferedTerminal; pub struct Compositor { layers: Vec>, terminal: Terminal, + surface: Surface, pub(crate) last_picker: Option>, } impl Compositor { - pub fn new() -> Result { - let backend = CrosstermBackend::new(stdout()); - let terminal = Terminal::new(backend)?; + pub fn new() -> Result { + let terminal = BufferedTerminal::new(SystemTerminal::new(Capabilities::new_from_env()?)?)?; + let (width, height) = terminal.dimensions(); + let surface = Surface::new(width, height); Ok(Self { layers: Vec::new(), terminal, + surface, last_picker: None, }) } pub fn size(&self) -> Rect { - self.terminal.size().expect("couldn't get terminal size") + let (width, height) = self.terminal.dimensions(); + Rect::new(0, 0, width as u16, height as u16) } + // TODO: pass in usize pub fn resize(&mut self, width: u16, height: u16) { - self.terminal - .resize(Rect::new(0, 0, width, height)) - .expect("Unable to resize terminal") + self.terminal.resize(width as usize, height as usize) } - pub fn save_cursor(&mut self) { - if self.terminal.cursor_kind() == CursorKind::Hidden { - self.terminal - .backend_mut() - .show_cursor(CursorKind::Block) - .ok(); + pub fn restore_cursor(&mut self) { + if self.terminal.cursor_visibility() == CursorVisibility::Hidden { + // TODO: set cursor to block } } pub fn load_cursor(&mut self) { - if self.terminal.cursor_kind() == CursorKind::Hidden { - self.terminal.backend_mut().hide_cursor().ok(); + if self.terminal.cursor_visibility() == CursorVisibility::Hidden { + // TODO: hide cursor again } } @@ -177,24 +178,45 @@ impl Compositor { } pub fn render(&mut self, cx: &mut Context) { - self.terminal - .autoresize() - .expect("Unable to determine terminal size"); + // self.terminal + // .autoresize() + // .expect("Unable to determine terminal size"); // TODO: need to recalculate view tree if necessary - let surface = self.terminal.current_buffer_mut(); + let area = self.size(); - let area = *surface.area(); + if (area.width as usize, area.height as usize) != self.surface.dimensions() { + self.surface + .resize(area.width as usize, area.height as usize); + } for layer in &mut self.layers { - layer.render(area, surface, cx); + layer.render(area, &mut self.surface, cx) } + // TODO use kind let (pos, kind) = self.cursor(area, cx.editor); - let pos = pos.map(|pos| (pos.col as u16, pos.row as u16)); + let pos = pos.map(|pos| (pos.col, pos.row)); + + use termwiz::surface::{Change, Position}; + if let Some(pos) = pos { + self.terminal + .add_change(Change::CursorVisibility(CursorVisibility::Visible)); + self.terminal.add_change(Change::CursorPosition { + x: Position::Absolute(pos.0), + y: Position::Absolute(pos.1), + }); + } else { + self.terminal + .add_change(Change::CursorVisibility(CursorVisibility::Hidden)); + } + + self.terminal.draw_from_screen(&self.surface, 0, 0); + self.terminal.flush().expect("failed to flush"); - self.terminal.draw(pos, kind).unwrap(); + self.surface + .flush_changes_older_than(self.surface.current_seqno()); } pub fn cursor(&self, area: Rect, editor: &Editor) -> (Option, CursorKind) { diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 1ee4a01a9..2881dd603 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,7 +1,7 @@ use crate::compositor::{Component, Context, EventResult}; use crossterm::event::{Event, KeyCode, KeyEvent}; use helix_view::editor::CompleteAction; -use tui::buffer::Buffer as Surface; +use tui::buffer::{Buffer as Surface, SurfaceExt}; use std::borrow::Cow; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 31a9bfc86..967c51937 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -28,7 +28,7 @@ use helix_view::{ use std::borrow::Cow; use crossterm::event::{Event, MouseButton, MouseEvent, MouseEventKind}; -use tui::buffer::Buffer as Surface; +use tui::buffer::{Buffer as Surface, SurfaceExt}; pub struct EditorView { pub keymaps: Keymaps, @@ -136,7 +136,8 @@ impl EditorView { let x = area.right(); let border_style = theme.get("ui.window"); for y in area.top()..area.bottom() { - surface[(x, y)] + surface + .get_mut(x, y) .set_symbol(tui::symbols::line::VERTICAL) //.set_symbol(" ") .set_style(border_style); @@ -437,7 +438,8 @@ impl EditorView { .add_modifier(Modifier::DIM) }); - surface[(viewport.x + pos.col as u16, viewport.y + pos.row as u16)] + surface + .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16) .set_style(style); } } @@ -1207,7 +1209,10 @@ impl Component for EditorView { fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) { // clear with background color - surface.set_style(area, cx.editor.theme.get("ui.background")); + let bg = cx.editor.theme.get("ui.background"); + surface.add_change(termwiz::surface::Change::ClearScreen( + bg.bg.expect("no bg color set!").into(), + )); // if the terminal size suddenly changed, we need to trigger a resize cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline diff --git a/helix-term/src/ui/info.rs b/helix-term/src/ui/info.rs index 272244c1c..ce1bf6962 100644 --- a/helix-term/src/ui/info.rs +++ b/helix-term/src/ui/info.rs @@ -1,7 +1,7 @@ use crate::compositor::{Component, Context}; use helix_view::graphics::{Margin, Rect}; use helix_view::info::Info; -use tui::buffer::Buffer as Surface; +use tui::buffer::{Buffer as Surface, SurfaceExt}; use tui::widgets::{Block, Borders, Paragraph, Widget}; impl Component for Info { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index d67a429e3..8133cba8a 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -3,7 +3,10 @@ use crate::{ ctrl, key, shift, }; use crossterm::event::Event; -use tui::{buffer::Buffer as Surface, widgets::Table}; +use tui::{ + buffer::{Buffer as Surface, SurfaceExt}, + widgets::Table, +}; pub use tui::widgets::{Cell, Row}; @@ -320,7 +323,7 @@ impl Component for Menu { let is_marked = i >= scroll_line && i < scroll_line + scroll_height; if !fits && is_marked { - let cell = &mut surface[(area.x + area.width - 2, area.y + i as u16)]; + let cell = surface.get_mut(area.x + area.width - 2, area.y + i as u16); cell.set_symbol("▐"); // cell.set_style(selected); // cell.set_style(if is_marked { selected } else { style }); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 3f2da92fa..e316fca2c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -5,7 +5,7 @@ use crate::{ }; use crossterm::event::Event; use tui::{ - buffer::Buffer as Surface, + buffer::{Buffer as Surface, SurfaceExt}, widgets::{Block, BorderType, Borders}, }; @@ -579,9 +579,8 @@ impl Component for Picker { let sep_style = Style::default().fg(Color::Rgb(90, 89, 119)); let borders = BorderType::line_symbols(BorderType::Plain); for x in inner.left()..inner.right() { - if let Some(cell) = surface.get_mut(x, inner.y + 1) { - cell.set_symbol(borders.horizontal).set_style(sep_style); - } + let cell = surface.get_mut(x, inner.y + 1); + cell.set_symbol(borders.horizontal).set_style(sep_style); } // -- Render the contents: diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 455274822..6199723dd 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -3,7 +3,7 @@ use crate::{ ctrl, key, }; use crossterm::event::Event; -use tui::buffer::Buffer as Surface; +use tui::buffer::{Buffer as Surface, SurfaceExt}; use helix_core::Position; use helix_view::graphics::{Margin, Rect}; diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index c3402f023..e224180a8 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -4,7 +4,7 @@ use crossterm::event::Event; use helix_view::input::KeyEvent; use helix_view::keyboard::{KeyCode, KeyModifiers}; use std::{borrow::Cow, ops::RangeFrom}; -use tui::buffer::Buffer as Surface; +use tui::buffer::{Buffer as Surface, SurfaceExt}; use tui::widgets::{Block, Borders, Widget}; use helix_core::{ diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index e4cfbe4cd..29557f82a 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -12,14 +12,12 @@ repository = "https://github.com/helix-editor/helix" homepage = "https://helix-editor.com" include = ["src/**/*", "README.md"] -[features] -default = ["crossterm"] - [dependencies] bitflags = "1.3" cassowary = "0.3" unicode-segmentation = "1.9" crossterm = { version = "0.23", optional = true } +termwiz = "0.15" serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.6", path = "../helix-view", features = ["term"] } helix-core = { version = "0.6", path = "../helix-core" } diff --git a/helix-tui/src/backend/crossterm.rs b/helix-tui/src/backend/crossterm.rs deleted file mode 100644 index eff098b35..000000000 --- a/helix-tui/src/backend/crossterm.rs +++ /dev/null @@ -1,197 +0,0 @@ -use crate::{backend::Backend, buffer::Cell}; -use crossterm::{ - cursor::{CursorShape, Hide, MoveTo, SetCursorShape, Show}, - execute, queue, - style::{ - Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor, - SetForegroundColor, - }, - terminal::{self, Clear, ClearType}, -}; -use helix_view::graphics::{Color, CursorKind, Modifier, Rect}; -use std::io::{self, Write}; - -pub struct CrosstermBackend { - buffer: W, -} - -impl CrosstermBackend -where - W: Write, -{ - pub fn new(buffer: W) -> CrosstermBackend { - CrosstermBackend { buffer } - } -} - -impl Write for CrosstermBackend -where - W: Write, -{ - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buffer.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.buffer.flush() - } -} - -impl Backend for CrosstermBackend -where - W: Write, -{ - fn draw<'a, I>(&mut self, content: I) -> io::Result<()> - where - I: Iterator, - { - let mut fg = Color::Reset; - let mut bg = Color::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) { - map_error(queue!(self.buffer, MoveTo(x, y)))?; - } - last_pos = Some((x, y)); - if cell.modifier != modifier { - let diff = ModifierDiff { - from: modifier, - to: cell.modifier, - }; - diff.queue(&mut self.buffer)?; - modifier = cell.modifier; - } - if cell.fg != fg { - let color = CColor::from(cell.fg); - map_error(queue!(self.buffer, SetForegroundColor(color)))?; - fg = cell.fg; - } - if cell.bg != bg { - let color = CColor::from(cell.bg); - map_error(queue!(self.buffer, SetBackgroundColor(color)))?; - bg = cell.bg; - } - - map_error(queue!(self.buffer, Print(&cell.symbol)))?; - } - - map_error(queue!( - self.buffer, - SetForegroundColor(CColor::Reset), - SetBackgroundColor(CColor::Reset), - SetAttribute(CAttribute::Reset) - )) - } - - fn hide_cursor(&mut self) -> io::Result<()> { - map_error(execute!(self.buffer, Hide)) - } - - fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { - let shape = match kind { - CursorKind::Block => CursorShape::Block, - CursorKind::Bar => CursorShape::Line, - CursorKind::Underline => CursorShape::UnderScore, - CursorKind::Hidden => unreachable!(), - }; - map_error(execute!(self.buffer, Show, SetCursorShape(shape))) - } - - fn get_cursor(&mut self) -> io::Result<(u16, u16)> { - crossterm::cursor::position() - .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) - } - - fn set_cursor(&mut self, x: u16, y: u16) -> io::Result<()> { - map_error(execute!(self.buffer, MoveTo(x, y))) - } - - fn clear(&mut self) -> io::Result<()> { - map_error(execute!(self.buffer, Clear(ClearType::All))) - } - - fn size(&self) -> io::Result { - let (width, height) = - terminal::size().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; - - Ok(Rect::new(0, 0, width, height)) - } - - fn flush(&mut self) -> io::Result<()> { - self.buffer.flush() - } -} - -fn map_error(error: crossterm::Result<()>) -> io::Result<()> { - error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string())) -} - -#[derive(Debug)] -struct ModifierDiff { - pub from: Modifier, - pub to: Modifier, -} - -impl ModifierDiff { - fn queue(&self, mut w: W) -> io::Result<()> - where - W: io::Write, - { - //use crossterm::Attribute; - let removed = self.from - self.to; - if removed.contains(Modifier::REVERSED) { - map_error(queue!(w, SetAttribute(CAttribute::NoReverse)))?; - } - if removed.contains(Modifier::BOLD) { - map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?; - if self.to.contains(Modifier::DIM) { - map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; - } - } - if removed.contains(Modifier::ITALIC) { - map_error(queue!(w, SetAttribute(CAttribute::NoItalic)))?; - } - if removed.contains(Modifier::UNDERLINED) { - map_error(queue!(w, SetAttribute(CAttribute::NoUnderline)))?; - } - if removed.contains(Modifier::DIM) { - map_error(queue!(w, SetAttribute(CAttribute::NormalIntensity)))?; - } - if removed.contains(Modifier::CROSSED_OUT) { - map_error(queue!(w, SetAttribute(CAttribute::NotCrossedOut)))?; - } - if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) { - map_error(queue!(w, SetAttribute(CAttribute::NoBlink)))?; - } - - let added = self.to - self.from; - if added.contains(Modifier::REVERSED) { - map_error(queue!(w, SetAttribute(CAttribute::Reverse)))?; - } - if added.contains(Modifier::BOLD) { - map_error(queue!(w, SetAttribute(CAttribute::Bold)))?; - } - if added.contains(Modifier::ITALIC) { - map_error(queue!(w, SetAttribute(CAttribute::Italic)))?; - } - if added.contains(Modifier::UNDERLINED) { - map_error(queue!(w, SetAttribute(CAttribute::Underlined)))?; - } - if added.contains(Modifier::DIM) { - map_error(queue!(w, SetAttribute(CAttribute::Dim)))?; - } - if added.contains(Modifier::CROSSED_OUT) { - map_error(queue!(w, SetAttribute(CAttribute::CrossedOut)))?; - } - if added.contains(Modifier::SLOW_BLINK) { - map_error(queue!(w, SetAttribute(CAttribute::SlowBlink)))?; - } - if added.contains(Modifier::RAPID_BLINK) { - map_error(queue!(w, SetAttribute(CAttribute::RapidBlink)))?; - } - - Ok(()) - } -} diff --git a/helix-tui/src/backend/mod.rs b/helix-tui/src/backend/mod.rs deleted file mode 100644 index c6c11019d..000000000 --- a/helix-tui/src/backend/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::io; - -use crate::buffer::Cell; - -use helix_view::graphics::{CursorKind, Rect}; - -#[cfg(feature = "crossterm")] -mod crossterm; -#[cfg(feature = "crossterm")] -pub use self::crossterm::CrosstermBackend; - -mod test; -pub use self::test::TestBackend; - -pub trait Backend { - fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error> - where - I: Iterator; - fn hide_cursor(&mut self) -> Result<(), io::Error>; - fn show_cursor(&mut self, kind: CursorKind) -> Result<(), io::Error>; - fn get_cursor(&mut self) -> Result<(u16, u16), io::Error>; - fn set_cursor(&mut self, x: u16, y: u16) -> Result<(), io::Error>; - fn clear(&mut self) -> Result<(), io::Error>; - fn size(&self) -> Result; - fn flush(&mut self) -> Result<(), io::Error>; -} diff --git a/helix-tui/src/backend/test.rs b/helix-tui/src/backend/test.rs deleted file mode 100644 index 52474148e..000000000 --- a/helix-tui/src/backend/test.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::{ - backend::Backend, - buffer::{Buffer, Cell}, -}; -use helix_core::unicode::width::UnicodeWidthStr; -use helix_view::graphics::{CursorKind, Rect}; -use std::{fmt::Write, io}; - -/// A backend used for the integration tests. -#[derive(Debug)] -pub struct TestBackend { - width: u16, - buffer: Buffer, - height: u16, - cursor: bool, - pos: (u16, u16), -} - -/// Returns a string representation of the given buffer for debugging purpose. -fn buffer_view(buffer: &Buffer) -> String { - let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3); - for cells in buffer.content.chunks(buffer.area.width as usize) { - let mut overwritten = vec![]; - let mut skip: usize = 0; - view.push('"'); - for (x, c) in cells.iter().enumerate() { - if skip == 0 { - view.push_str(&c.symbol); - } else { - overwritten.push((x, &c.symbol)) - } - skip = std::cmp::max(skip, c.symbol.width()).saturating_sub(1); - } - view.push('"'); - if !overwritten.is_empty() { - write!( - &mut view, - " Hidden by multi-width symbols: {:?}", - overwritten - ) - .unwrap(); - } - view.push('\n'); - } - view -} - -impl TestBackend { - pub fn new(width: u16, height: u16) -> TestBackend { - TestBackend { - width, - height, - buffer: Buffer::empty(Rect::new(0, 0, width, height)), - cursor: false, - pos: (0, 0), - } - } - - pub fn buffer(&self) -> &Buffer { - &self.buffer - } - - pub fn resize(&mut self, width: u16, height: u16) { - self.buffer.resize(Rect::new(0, 0, width, height)); - self.width = width; - self.height = height; - } - - pub fn assert_buffer(&self, expected: &Buffer) { - assert_eq!(expected.area, self.buffer.area); - let diff = expected.diff(&self.buffer); - if diff.is_empty() { - return; - } - - let mut debug_info = String::from("Buffers are not equal"); - debug_info.push('\n'); - debug_info.push_str("Expected:"); - debug_info.push('\n'); - let expected_view = buffer_view(expected); - debug_info.push_str(&expected_view); - debug_info.push('\n'); - debug_info.push_str("Got:"); - debug_info.push('\n'); - let view = buffer_view(&self.buffer); - debug_info.push_str(&view); - debug_info.push('\n'); - - debug_info.push_str("Diff:"); - debug_info.push('\n'); - let nice_diff = diff - .iter() - .enumerate() - .map(|(i, (x, y, cell))| { - let expected_cell = expected.get(*x, *y); - format!( - "{}: at ({}, {}) expected {:?} got {:?}", - i, x, y, expected_cell, cell - ) - }) - .collect::>() - .join("\n"); - debug_info.push_str(&nice_diff); - panic!("{}", debug_info); - } -} - -impl Backend for TestBackend { - fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error> - where - I: Iterator, - { - for (x, y, c) in content { - self.buffer[(x, y)] = c.clone(); - } - Ok(()) - } - - fn hide_cursor(&mut self) -> Result<(), io::Error> { - self.cursor = false; - Ok(()) - } - - fn show_cursor(&mut self, _kind: CursorKind) -> Result<(), io::Error> { - self.cursor = true; - 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(()) - } - - fn clear(&mut self) -> Result<(), io::Error> { - self.buffer.reset(); - Ok(()) - } - - fn size(&self) -> Result { - Ok(Rect::new(0, 0, self.width, self.height)) - } - - fn flush(&mut self) -> Result<(), io::Error> { - Ok(()) - } -} diff --git a/helix-tui/src/buffer.rs b/helix-tui/src/buffer.rs index 22956b04c..b55c22ade 100644 --- a/helix-tui/src/buffer.rs +++ b/helix-tui/src/buffer.rs @@ -1,282 +1,97 @@ use crate::text::{Span, Spans}; -use helix_core::unicode::width::UnicodeWidthStr; -use std::cmp::min; -use unicode_segmentation::UnicodeSegmentation; use helix_view::graphics::{Color, Modifier, Rect, Style}; -/// A buffer cell -#[derive(Debug, Clone, PartialEq)] -pub struct Cell { - pub symbol: String, - pub fg: Color, - pub bg: Color, - pub modifier: Modifier, -} - -impl Cell { - pub fn set_symbol(&mut self, symbol: &str) -> &mut Cell { - self.symbol.clear(); - self.symbol.push_str(symbol); - self - } - - pub fn set_char(&mut self, ch: char) -> &mut Cell { - self.symbol.clear(); - self.symbol.push(ch); - self - } +pub use termwiz::surface::Surface as Buffer; +use termwiz::{cell::*, surface::*}; - pub fn set_fg(&mut self, color: Color) -> &mut Cell { - self.fg = color; - self - } +pub struct Cell<'a> { + surface: &'a mut Surface, +} - pub fn set_bg(&mut self, color: Color) -> &mut Cell { - self.bg = color; +impl<'a> Cell<'a> { + pub fn set_symbol<'b>(self, symbol: &'b str) -> Cell<'a> { + self.surface.add_change(Change::Text(symbol.into())); self } - - pub fn set_style(&mut self, style: Style) -> &mut Cell { - if let Some(c) = style.fg { - self.fg = c; - } - if let Some(c) = style.bg { - self.bg = c; - } - self.modifier.insert(style.add_modifier); - self.modifier.remove(style.sub_modifier); + pub fn set_style(self, style: Style) -> Cell<'a> { + if let Some(fg) = style.fg { + self.surface + .add_change(Change::Attribute(AttributeChange::Foreground(fg.into()))); + } + if let Some(bg) = style.bg { + self.surface + .add_change(Change::Attribute(AttributeChange::Background(bg.into()))); + } + + self.surface + .add_change(Change::Attribute(AttributeChange::Intensity( + if style.add_modifier.contains(Modifier::BOLD) { + Intensity::Bold + } else if style.add_modifier.contains(Modifier::DIM) { + Intensity::Half + } else { + Intensity::Normal + }, + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Italic( + style.add_modifier.contains(Modifier::ITALIC), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Underline( + if style.add_modifier.contains(Modifier::UNDERLINED) { + Underline::Single + } else { + Underline::None + }, + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Reverse( + style.add_modifier.contains(Modifier::REVERSED), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Invisible( + style.add_modifier.contains(Modifier::HIDDEN), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::StrikeThrough( + style.add_modifier.contains(Modifier::CROSSED_OUT), + ))); + + self.surface + .add_change(Change::Attribute(AttributeChange::Blink( + if style.add_modifier.contains(Modifier::SLOW_BLINK) { + Blink::Slow + } else if style.add_modifier.contains(Modifier::RAPID_BLINK) { + Blink::Rapid + } else { + Blink::None + }, + ))); self } - - pub fn style(&self) -> Style { - Style::default() - .fg(self.fg) - .bg(self.bg) - .add_modifier(self.modifier) - } - - pub fn reset(&mut self) { - self.symbol.clear(); - self.symbol.push(' '); - self.fg = Color::Reset; - self.bg = Color::Reset; - self.modifier = Modifier::empty(); - } -} - -impl Default for Cell { - fn default() -> Cell { - Cell { - symbol: " ".into(), - fg: Color::Reset, - bg: Color::Reset, - modifier: Modifier::empty(), - } - } } -/// A buffer that maps to the desired content of the terminal after the draw call -/// -/// No widget in the library interacts directly with the terminal. Instead each of them is required -/// to draw their state to an intermediate buffer. It is basically a grid where each cell contains -/// a grapheme, a foreground color and a background color. This grid will then be used to output -/// the appropriate escape sequences and characters to draw the UI as the user has defined it. -/// -/// # Examples: -/// -/// ``` -/// use helix_tui::buffer::{Buffer, Cell}; -/// use helix_view::graphics::{Rect, Color, Style, Modifier}; -/// -/// let mut buf = Buffer::empty(Rect{x: 0, y: 0, width: 10, height: 5}); -/// buf[(0, 2)].set_symbol("x"); -/// assert_eq!(buf[(0, 2)].symbol, "x"); -/// buf.set_string(3, 0, "string", Style::default().fg(Color::Red).bg(Color::White)); -/// assert_eq!(buf[(5, 0)], Cell{ -/// symbol: String::from("r"), -/// fg: Color::Red, -/// bg: Color::White, -/// modifier: Modifier::empty() -/// }); -/// buf[(5, 0)].set_char('x'); -/// assert_eq!(buf[(5, 0)].symbol, "x"); -/// ``` -#[derive(Debug, Default, Clone, PartialEq)] -pub struct Buffer { - /// The area represented by this buffer - pub area: Rect, - /// The content of the buffer. The length of this Vec should always be equal to area.width * - /// area.height - pub content: Vec, -} +pub trait SurfaceExt { + // + fn set_style(&mut self, area: Rect, style: Style) {} -impl Buffer { - /// Returns a Buffer with all cells set to the default one - pub fn empty(area: Rect) -> Buffer { - let cell: Cell = Default::default(); - Buffer::filled(area, &cell) - } + fn clear_with(&mut self, area: Rect, style: Style) {} - /// Returns a Buffer with all cells initialized with the attributes of the given Cell - pub fn filled(area: Rect, cell: &Cell) -> Buffer { - let size = area.area() as usize; - let mut content = Vec::with_capacity(size); - for _ in 0..size { - content.push(cell.clone()); - } - Buffer { area, content } - } - - /// Returns a Buffer containing the given lines - pub fn with_lines(lines: Vec) -> Buffer - where - S: AsRef, - { - let height = lines.len() as u16; - let width = lines - .iter() - .map(|i| i.as_ref().width() as u16) - .max() - .unwrap_or_default(); - let mut buffer = Buffer::empty(Rect { - x: 0, - y: 0, - width, - height, - }); - for (y, line) in lines.iter().enumerate() { - buffer.set_string(0, y as u16, line, Style::default()); - } - buffer - } - - /// Returns the content of the buffer as a slice - pub fn content(&self) -> &[Cell] { - &self.content - } - - /// Returns the area covered by this buffer - pub fn area(&self) -> &Rect { - &self.area - } - - /// Returns a reference to Cell at the given coordinates - pub fn get(&self, x: u16, y: u16) -> Option<&Cell> { - self.index_of_opt(x, y).map(|i| &self.content[i]) - } - - /// Returns a mutable reference to Cell at the given coordinates - pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> { - self.index_of_opt(x, y).map(|i| &mut self.content[i]) - } - - /// Tells whether the global (x, y) coordinates are inside the Buffer's area. - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// // Global coordinates inside the Buffer's area - /// assert!(buffer.in_bounds(209, 100)); - /// // Global coordinates outside the Buffer's area - /// assert!(!buffer.in_bounds(210, 100)); - /// ``` - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - pub fn in_bounds(&self, x: u16, y: u16) -> bool { - x >= self.area.left() - && x < self.area.right() - && y >= self.area.top() - && y < self.area.bottom() - } - - /// Returns the index in the Vec for the given global (x, y) coordinates. - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// // Global coordinates to the top corner of this Buffer's area - /// assert_eq!(buffer.index_of(200, 100), 0); - /// ``` - /// - /// # Panics - /// - /// Panics when given an coordinate that is outside of this Buffer's area. - pub fn index_of(&self, x: u16, y: u16) -> usize { - debug_assert!( - self.in_bounds(x, y), - "Trying to access position outside the buffer: x={}, y={}, area={:?}", - x, - y, - self.area - ); - ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize - } - - /// Returns the index in the Vec for the given global (x, y) coordinates, - /// or `None` if the coordinates are outside the buffer's area. - fn index_of_opt(&self, x: u16, y: u16) -> Option { - if self.in_bounds(x, y) { - Some(self.index_of(x, y)) - } else { - None - } - } - - /// Returns the (global) coordinates of a cell given its index - /// - /// Global coordinates are offset by the Buffer's area offset (`x`/`y`). - /// - /// # Examples - /// - /// ``` - /// # use helix_tui::buffer::Buffer; - /// # use helix_view::graphics::Rect; - /// let rect = Rect::new(200, 100, 10, 10); - /// let buffer = Buffer::empty(rect); - /// assert_eq!(buffer.pos_of(0), (200, 100)); - /// assert_eq!(buffer.pos_of(14), (204, 101)); - /// ``` - /// - /// # Panics - /// - /// Panics when given an index that is outside the Buffer's content. - pub fn pos_of(&self, i: usize) -> (u16, u16) { - debug_assert!( - i < self.content.len(), - "Trying to get the coords of a cell outside the buffer: i={} len={}", - i, - self.content.len() - ); - ( - self.area.x + i as u16 % self.area.width, - self.area.y + i as u16 / self.area.width, - ) - } - - /// Print a string, starting at the position (x, y) - pub fn set_string(&mut self, x: u16, y: u16, string: S, style: Style) + fn set_string(&mut self, x: u16, y: u16, string: S, style: Style) where S: AsRef, { self.set_stringn(x, y, string, usize::MAX, style); } - /// Print at most the first n characters of a string if enough space is available - /// until the end of the line - pub fn set_stringn( + fn set_stringn( &mut self, x: u16, y: u16, @@ -285,137 +100,9 @@ impl Buffer { style: Style, ) -> (u16, u16) where - S: AsRef, - { - self.set_string_truncated_at_end(x, y, string.as_ref(), width, style) - } - - /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. If `ellipsis` is true appends a `…` at the end of - /// truncated lines. If `truncate_start` is `true`, truncate the beginning of the string - /// instead of the end. - #[allow(clippy::too_many_arguments)] - pub fn set_string_truncated( - &mut self, - x: u16, - y: u16, - string: &str, - width: usize, - style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style - ellipsis: bool, - truncate_start: bool, - ) -> (u16, u16) { - // prevent panic if out of range - if !self.in_bounds(x, y) || width == 0 { - return (x, y); - } - - let mut index = self.index_of(x, y); - let mut x_offset = x as usize; - let width = if ellipsis { width - 1 } else { width }; - let graphemes = string.grapheme_indices(true); - let max_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - if !truncate_start { - for (byte_offset, s) in graphemes { - let width = s.width(); - if width == 0 { - continue; - } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimenstions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_offset.saturating_sub(x_offset) { - break; - } - - self.content[index].set_symbol(s); - self.content[index].set_style(style(byte_offset)); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); - } - index += width; - x_offset += width; - } - if ellipsis && x_offset - (x as usize) < string.width() { - self.content[index].set_symbol("…"); - } - } else { - let mut start_index = self.index_of(x, y); - let mut index = self.index_of(max_offset as u16, y); - - let total_width = string.width(); - let truncated = total_width > width; - if ellipsis && truncated { - self.content[start_index].set_symbol("…"); - start_index += 1; - } - if !truncated { - index -= width - total_width; - } - for (byte_offset, s) in graphemes.rev() { - let width = s.width(); - if width == 0 { - continue; - } - let start = index - width; - if start < start_index { - break; - } - self.content[start].set_symbol(s); - self.content[start].set_style(style(byte_offset)); - for i in start + 1..index { - self.content[i].reset(); - } - index -= width; - } - } - (x_offset as u16, y) - } + S: AsRef; - /// Print at most the first `width` characters of a string if enough space is available - /// until the end of the line. - pub fn set_string_truncated_at_end( - &mut self, - x: u16, - y: u16, - string: &str, - width: usize, - style: Style, - ) -> (u16, u16) { - // prevent panic if out of range - if !self.in_bounds(x, y) { - return (x, y); - } - - let mut index = self.index_of(x, y); - let mut x_offset = x as usize; - let max_x_offset = min(self.area.right() as usize, width.saturating_add(x as usize)); - - for s in string.graphemes(true) { - let width = s.width(); - if width == 0 { - continue; - } - // `x_offset + width > max_offset` could be integer overflow on 32-bit machines if we - // change dimensions to usize or u32 and someone resizes the terminal to 1x2^32. - if width > max_x_offset.saturating_sub(x_offset) { - break; - } - - self.content[index].set_symbol(s); - self.content[index].set_style(style); - // Reset following cells if multi-width (they would be hidden by the grapheme), - for i in index + 1..index + width { - self.content[i].reset(); - } - index += width; - x_offset += width; - } - - (x_offset as u16, y) - } - - pub fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) { + fn set_spans<'a>(&mut self, x: u16, y: u16, spans: &Spans<'a>, width: u16) -> (u16, u16) { let mut remaining_width = width; let mut x = x; for span in &spans.0 { @@ -436,449 +123,62 @@ impl Buffer { (x, y) } - pub fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) { + fn set_span<'a>(&mut self, x: u16, y: u16, span: &Span<'a>, width: u16) -> (u16, u16) { self.set_stringn(x, y, span.content.as_ref(), width as usize, span.style) } - #[deprecated( - since = "0.10.0", - note = "You should use styling capabilities of `Buffer::set_style`" - )] - pub fn set_background(&mut self, area: Rect, color: Color) { - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - self[(x, y)].set_bg(color); - } - } - } - - pub fn set_style(&mut self, area: Rect, style: Style) { - for y in area.top()..area.bottom() { - for x in area.left()..area.right() { - self[(x, y)].set_style(style); - } - } - } - - /// Resize the buffer so that the mapped area matches the given area and that the buffer - /// length is equal to area.width * area.height - pub fn resize(&mut self, area: Rect) { - let length = area.area() as usize; - if self.content.len() > length { - self.content.truncate(length); - } else { - self.content.resize(length, Default::default()); - } - self.area = area; - } - - /// Reset all cells in the buffer - pub fn reset(&mut self) { - for c in &mut self.content { - c.reset(); - } - } - - /// Clear an area in the buffer - pub fn clear(&mut self, area: Rect) { - for x in area.left()..area.right() { - for y in area.top()..area.bottom() { - self[(x, y)].reset(); - } - } - } - - /// Clear an area in the buffer with a default style. - pub fn clear_with(&mut self, area: Rect, style: Style) { - for x in area.left()..area.right() { - for y in area.top()..area.bottom() { - let cell = &mut self[(x, y)]; - cell.reset(); - cell.set_style(style); - } - } - } - - /// Merge an other buffer into this one - pub fn merge(&mut self, other: &Buffer) { - let area = self.area.union(other.area); - let cell: Cell = Default::default(); - self.content.resize(area.area() as usize, cell.clone()); - - // Move original content to the appropriate space - let size = self.area.area() as usize; - for i in (0..size).rev() { - let (x, y) = self.pos_of(i); - // New index in content - let k = ((y - area.y) * area.width + x - area.x) as usize; - if i != k { - self.content[k] = self.content[i].clone(); - self.content[i] = cell.clone(); - } - } - - // Push content of the other buffer into this one (may erase previous - // data) - let size = other.area.area() as usize; - for i in 0..size { - let (x, y) = other.pos_of(i); - // New index in content - let k = ((y - area.y) * area.width + x - area.x) as usize; - self.content[k] = other.content[i].clone(); - } - self.area = area; - } - - /// Builds a minimal sequence of coordinates and Cells necessary to update the UI from - /// self to other. - /// - /// We're assuming that buffers are well-formed, that is no double-width cell is followed by - /// a non-blank cell. - /// - /// # Multi-width characters handling: - /// - /// ```text - /// (Index:) `01` - /// Prev: `コ` - /// Next: `aa` - /// Updates: `0: a, 1: a' - /// ``` - /// - /// ```text - /// (Index:) `01` - /// Prev: `a ` - /// Next: `コ` - /// Updates: `0: コ` (double width symbol at index 0 - skip index 1) - /// ``` - /// - /// ```text - /// (Index:) `012` - /// Prev: `aaa` - /// Next: `aコ` - /// Updates: `0: a, 1: コ` (double width symbol at index 1 - skip index 2) - /// ``` - pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(u16, u16, &'a Cell)> { - let previous_buffer = &self.content; - let next_buffer = &other.content; - let width = self.area.width; - - let mut updates: Vec<(u16, u16, &Cell)> = vec![]; - // Cells invalidated by drawing/replacing preceeding multi-width characters: - let mut invalidated: usize = 0; - // Cells from the current buffer to skip due to preceeding multi-width characters taking their - // place (the skipped cells should be blank anyway): - let mut to_skip: usize = 0; - for (i, (current, previous)) in next_buffer.iter().zip(previous_buffer.iter()).enumerate() { - if (current != previous || invalidated > 0) && to_skip == 0 { - let x = i as u16 % width; - let y = i as u16 / width; - updates.push((x, y, &next_buffer[i])); - } - - let current_width = current.symbol.width(); - to_skip = current_width.saturating_sub(1); - - let affected_width = std::cmp::max(current_width, previous.symbol.width()); - invalidated = std::cmp::max(affected_width, invalidated).saturating_sub(1); - } - updates - } -} - -impl std::ops::Index<(u16, u16)> for Buffer { - type Output = Cell; - - fn index(&self, (x, y): (u16, u16)) -> &Self::Output { - let i = self.index_of(x, y); - &self.content[i] + fn set_string_truncated( + &mut self, + x: u16, + y: u16, + string: &str, + width: usize, + style: impl Fn(usize) -> Style, // Map a grapheme's string offset to a style + ellipsis: bool, + truncate_start: bool, + ) { + // TODO } -} -impl std::ops::IndexMut<(u16, u16)> for Buffer { - fn index_mut(&mut self, (x, y): (u16, u16)) -> &mut Self::Output { - let i = self.index_of(x, y); - &mut self.content[i] - } + fn get_mut(&mut self, x: u16, y: u16) -> Cell; } -#[cfg(test)] -mod tests { - use super::*; - - fn cell(s: &str) -> Cell { - let mut cell = Cell::default(); - cell.set_symbol(s); - cell - } - - #[test] - fn it_translates_to_and_from_coordinates() { - let rect = Rect::new(200, 100, 50, 80); - let buf = Buffer::empty(rect); - - // First cell is at the upper left corner. - assert_eq!(buf.pos_of(0), (200, 100)); - assert_eq!(buf.index_of(200, 100), 0); - - // Last cell is in the lower right. - assert_eq!(buf.pos_of(buf.content.len() - 1), (249, 179)); - assert_eq!(buf.index_of(249, 179), buf.content.len() - 1); - } - - #[test] - #[should_panic(expected = "outside the buffer")] - #[cfg(debug_assertions)] - fn pos_of_panics_on_out_of_bounds() { - let rect = Rect::new(0, 0, 10, 10); - let buf = Buffer::empty(rect); - - // There are a total of 100 cells; zero-indexed means that 100 would be the 101st cell. - buf.pos_of(100); - } - - #[test] - #[should_panic(expected = "outside the buffer")] - #[cfg(debug_assertions)] - fn index_of_panics_on_out_of_bounds() { - let rect = Rect::new(0, 0, 10, 10); - let buf = Buffer::empty(rect); - - // width is 10; zero-indexed means that 10 would be the 11th cell. - buf.index_of(10, 0); - } - - #[test] - fn buffer_set_string() { - let area = Rect::new(0, 0, 5, 1); - let mut buffer = Buffer::empty(area); - - // Zero-width - buffer.set_stringn(0, 0, "aaa", 0, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec![" "])); - - buffer.set_string(0, 0, "aaa", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["aaa "])); - - // Width limit: - buffer.set_stringn(0, 0, "bbbbbbbbbbbbbb", 4, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["bbbb "])); - - buffer.set_string(0, 0, "12345", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["12345"])); - - // Width truncation: - buffer.set_string(0, 0, "123456", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["12345"])); - } - - #[test] - fn buffer_set_string_zero_width() { - let area = Rect::new(0, 0, 1, 1); - let mut buffer = Buffer::empty(area); - - // Leading grapheme with zero width - let s = "\u{1}a"; - buffer.set_stringn(0, 0, s, 1, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["a"])); - - // Trailing grapheme with zero with - let s = "a\u{1}"; - buffer.set_stringn(0, 0, s, 1, Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["a"])); - } - - #[test] - fn buffer_set_string_double_width() { - let area = Rect::new(0, 0, 5, 1); - let mut buffer = Buffer::empty(area); - buffer.set_string(0, 0, "コン", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["コン "])); - - // Only 1 space left. - buffer.set_string(0, 0, "コンピ", Style::default()); - assert_eq!(buffer, Buffer::with_lines(vec!["コン "])); - } - - #[test] - fn buffer_with_lines() { - let buffer = - Buffer::with_lines(vec!["┌────────┐", "│コンピュ│", "│ーa 上で│", "└────────┘"]); - assert_eq!(buffer.area.x, 0); - assert_eq!(buffer.area.y, 0); - assert_eq!(buffer.area.width, 10); - assert_eq!(buffer.area.height, 4); - } - - #[test] - fn buffer_diffing_empty_empty() { - let area = Rect::new(0, 0, 40, 40); - let prev = Buffer::empty(area); - let next = Buffer::empty(area); - let diff = prev.diff(&next); - assert_eq!(diff, vec![]); - } - - #[test] - fn buffer_diffing_empty_filled() { - let area = Rect::new(0, 0, 40, 40); - let prev = Buffer::empty(area); - let next = Buffer::filled(area, Cell::default().set_symbol("a")); - let diff = prev.diff(&next); - assert_eq!(diff.len(), 40 * 40); - } - - #[test] - fn buffer_diffing_filled_filled() { - let area = Rect::new(0, 0, 40, 40); - let prev = Buffer::filled(area, Cell::default().set_symbol("a")); - let next = Buffer::filled(area, Cell::default().set_symbol("a")); - let diff = prev.diff(&next); - assert_eq!(diff, vec![]); - } - - #[test] - fn buffer_diffing_single_width() { - let prev = Buffer::with_lines(vec![ - " ", - "┌Title─┐ ", - "│ │ ", - "│ │ ", - "└──────┘ ", - ]); - let next = Buffer::with_lines(vec![ - " ", - "┌TITLE─┐ ", - "│ │ ", - "│ │ ", - "└──────┘ ", - ]); - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![ - (2, 1, &cell("I")), - (3, 1, &cell("T")), - (4, 1, &cell("L")), - (5, 1, &cell("E")), - ] - ); - } - - #[test] - #[rustfmt::skip] - fn buffer_diffing_multi_width() { - let prev = Buffer::with_lines(vec![ - "┌Title─┐ ", - "└──────┘ ", - ]); - let next = Buffer::with_lines(vec![ - "┌称号──┐ ", - "└──────┘ ", - ]); - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![ - (1, 0, &cell("称")), - // Skipped "i" - (3, 0, &cell("号")), - // Skipped "l" - (5, 0, &cell("─")), - ] - ); - } - - #[test] - fn buffer_diffing_multi_width_offset() { - let prev = Buffer::with_lines(vec!["┌称号──┐"]); - let next = Buffer::with_lines(vec!["┌─称号─┐"]); +impl SurfaceExt for termwiz::surface::Surface { + // + //fn set_style(&mut self, area: Rect, style: Style) { + // // + //} - let diff = prev.diff(&next); - assert_eq!( - diff, - vec![(1, 0, &cell("─")), (2, 0, &cell("称")), (4, 0, &cell("号")),] - ); - } - - #[test] - fn buffer_merge() { - let mut one = Buffer::filled( - Rect { - x: 0, - y: 0, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 0, - y: 2, - width: 2, - height: 2, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - assert_eq!(one, Buffer::with_lines(vec!["11", "11", "22", "22"])); - } + fn set_stringn( + &mut self, + x: u16, + y: u16, + string: S, + width: usize, + style: Style, + ) -> (u16, u16) + where + S: AsRef, + { + // TODO: style and limit to width + self.add_change(Change::CursorPosition { + x: Position::Absolute(x as usize), + y: Position::Absolute(y as usize), + }); + let fg = style.fg.unwrap_or(Color::Reset); + self.add_change(Change::Attribute(AttributeChange::Foreground(fg.into()))); + let bg = style.bg.unwrap_or(Color::Reset); + self.add_change(Change::Attribute(AttributeChange::Background(bg.into()))); + self.add_change(Change::Text(string.as_ref().to_owned())); - #[test] - fn buffer_merge2() { - let mut one = Buffer::filled( - Rect { - x: 2, - y: 2, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 0, - y: 0, - width: 2, - height: 2, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - assert_eq!( - one, - Buffer::with_lines(vec!["22 ", "22 ", " 11", " 11"]) - ); + (0, 0) } - #[test] - fn buffer_merge3() { - let mut one = Buffer::filled( - Rect { - x: 3, - y: 3, - width: 2, - height: 2, - }, - Cell::default().set_symbol("1"), - ); - let two = Buffer::filled( - Rect { - x: 1, - y: 1, - width: 3, - height: 4, - }, - Cell::default().set_symbol("2"), - ); - one.merge(&two); - let mut merged = Buffer::with_lines(vec!["222 ", "222 ", "2221", "2221"]); - merged.area = Rect { - x: 1, - y: 1, - width: 4, - height: 4, - }; - assert_eq!(one, merged); + fn get_mut(&mut self, x: u16, y: u16) -> Cell { + self.add_change(Change::CursorPosition { + x: Position::Absolute(x as usize), + y: Position::Absolute(y as usize), + }); + Cell { surface: self } } } diff --git a/helix-tui/src/lib.rs b/helix-tui/src/lib.rs index 2636b268f..8648cfbea 100644 --- a/helix-tui/src/lib.rs +++ b/helix-tui/src/lib.rs @@ -122,12 +122,8 @@ //! 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; pub mod symbols; -pub mod terminal; pub mod text; pub mod widgets; - -pub use self::terminal::{Terminal, TerminalOptions, Viewport}; diff --git a/helix-tui/src/terminal.rs b/helix-tui/src/terminal.rs deleted file mode 100644 index 22e9232f3..000000000 --- a/helix-tui/src/terminal.rs +++ /dev/null @@ -1,225 +0,0 @@ -use crate::{backend::Backend, buffer::Buffer}; -use helix_view::graphics::{CursorKind, Rect}; -use std::io; - -#[derive(Debug, Clone, PartialEq)] -/// UNSTABLE -enum ResizeBehavior { - Fixed, - Auto, -} - -#[derive(Debug, Clone, PartialEq)] -/// UNSTABLE -pub struct Viewport { - area: Rect, - resize_behavior: ResizeBehavior, -} - -impl Viewport { - /// UNSTABLE - pub fn fixed(area: Rect) -> Viewport { - Viewport { - area, - resize_behavior: ResizeBehavior::Fixed, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -/// Options to pass to [`Terminal::with_options`] -pub struct TerminalOptions { - /// Viewport used to draw to the terminal - pub viewport: Viewport, -} - -/// Interface to the terminal backed by Termion -#[derive(Debug)] -pub struct Terminal -where - B: Backend, -{ - backend: B, - /// Holds the results of the current and previous draw calls. The two are compared at the end - /// of each draw pass to output the necessary updates to the terminal - buffers: [Buffer; 2], - /// Index of the current buffer in the previous array - current: usize, - /// Kind of cursor (hidden or others) - cursor_kind: CursorKind, - /// Viewport - viewport: Viewport, -} - -impl Drop for Terminal -where - B: Backend, -{ - fn drop(&mut self) { - // Attempt to restore the cursor state - if self.cursor_kind == CursorKind::Hidden { - if let Err(err) = self.show_cursor(CursorKind::Block) { - eprintln!("Failed to show the cursor: {}", err); - } - } - } -} - -impl Terminal -where - B: Backend, -{ - /// Wrapper around Terminal initialization. Each buffer is initialized with a blank string and - /// default colors for the foreground and the background - pub fn new(backend: B) -> io::Result> { - let size = backend.size()?; - Terminal::with_options( - backend, - TerminalOptions { - viewport: Viewport { - area: size, - resize_behavior: ResizeBehavior::Auto, - }, - }, - ) - } - - /// UNSTABLE - pub fn with_options(backend: B, options: TerminalOptions) -> io::Result> { - Ok(Terminal { - backend, - buffers: [ - Buffer::empty(options.viewport.area), - Buffer::empty(options.viewport.area), - ], - current: 0, - cursor_kind: CursorKind::Block, - viewport: options.viewport, - }) - } - - // /// Get a Frame object which provides a consistent view into the terminal state for rendering. - // pub fn get_frame(&mut self) -> Frame { - // Frame { - // terminal: self, - // cursor_position: None, - // } - // } - - pub fn current_buffer_mut(&mut self) -> &mut Buffer { - &mut self.buffers[self.current] - } - - pub fn backend(&self) -> &B { - &self.backend - } - - pub fn backend_mut(&mut self) -> &mut B { - &mut self.backend - } - - /// Obtains a difference between the previous and the current buffer and passes it to the - /// current backend for drawing. - pub fn flush(&mut self) -> io::Result<()> { - let previous_buffer = &self.buffers[1 - self.current]; - let current_buffer = &self.buffers[self.current]; - let updates = previous_buffer.diff(current_buffer); - self.backend.draw(updates.into_iter()) - } - - /// Updates the Terminal so that internal buffers match the requested size. Requested size will - /// be saved so the size can remain consistent when rendering. - /// This leads to a full clear of the screen. - pub fn resize(&mut self, area: Rect) -> io::Result<()> { - self.buffers[self.current].resize(area); - self.buffers[1 - self.current].resize(area); - self.viewport.area = area; - self.clear() - } - - /// Queries the backend for size and resizes if it doesn't match the previous size. - pub fn autoresize(&mut self) -> io::Result { - let size = self.size()?; - if size != self.viewport.area { - self.resize(size)?; - }; - Ok(size) - } - - /// Synchronizes terminal size, calls the rendering closure, flushes the current internal state - /// and prepares for the next draw call. - pub fn draw( - &mut self, - cursor_position: Option<(u16, u16)>, - cursor_kind: CursorKind, - ) -> io::Result<()> { - // // Autoresize - otherwise we get glitches if shrinking or potential desync between widgets - // // and the terminal (if growing), which may OOB. - // self.autoresize()?; - - // let mut frame = self.get_frame(); - // f(&mut frame); - // // We can't change the cursor position right away because we have to flush the frame to - // // stdout first. But we also can't keep the frame around, since it holds a &mut to - // // Terminal. Thus, we're taking the important data out of the Frame and dropping it. - // let cursor_position = frame.cursor_position; - - // Draw to stdout - self.flush()?; - - if let Some((x, y)) = cursor_position { - self.set_cursor(x, y)?; - } - - match cursor_kind { - CursorKind::Hidden => self.hide_cursor()?, - kind => self.show_cursor(kind)?, - } - - // Swap buffers - self.buffers[1 - self.current].reset(); - self.current = 1 - self.current; - - // Flush - self.backend.flush()?; - Ok(()) - } - - #[inline] - pub fn cursor_kind(&self) -> CursorKind { - self.cursor_kind - } - - pub fn hide_cursor(&mut self) -> io::Result<()> { - self.backend.hide_cursor()?; - self.cursor_kind = CursorKind::Hidden; - Ok(()) - } - - pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { - self.backend.show_cursor(kind)?; - self.cursor_kind = kind; - 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) - } - - /// Clear the terminal and force a full redraw on the next draw call. - pub fn clear(&mut self) -> io::Result<()> { - self.backend.clear()?; - // Reset the back buffer to make sure the next update will redraw everything. - self.buffers[1 - self.current].reset(); - Ok(()) - } - - /// Queries the real size of the backend. - pub fn size(&self) -> io::Result { - self.backend.size() - } -} diff --git a/helix-tui/src/widgets/block.rs b/helix-tui/src/widgets/block.rs index 26223c3eb..f1576bc11 100644 --- a/helix-tui/src/widgets/block.rs +++ b/helix-tui/src/widgets/block.rs @@ -1,5 +1,5 @@ use crate::{ - buffer::Buffer, + buffer::{Buffer, SurfaceExt}, symbols::line, text::{Span, Spans}, widgets::{Borders, Widget}, @@ -134,62 +134,62 @@ impl<'a> Widget for Block<'a> { if area.area() == 0 { return; } - buf.set_style(area, self.style); + // buf.set_style(area, self.style); let symbols = BorderType::line_symbols(self.border_type); - // Sides - if self.borders.intersects(Borders::LEFT) { - for y in area.top()..area.bottom() { - buf[(area.left(), y)] - .set_symbol(symbols.vertical) - .set_style(self.border_style); - } - } - if self.borders.intersects(Borders::TOP) { - for x in area.left()..area.right() { - buf[(x, area.top())] - .set_symbol(symbols.horizontal) - .set_style(self.border_style); - } - } - if self.borders.intersects(Borders::RIGHT) { - let x = area.right() - 1; - for y in area.top()..area.bottom() { - buf[(x, y)] - .set_symbol(symbols.vertical) - .set_style(self.border_style); - } - } - if self.borders.intersects(Borders::BOTTOM) { - let y = area.bottom() - 1; - for x in area.left()..area.right() { - buf[(x, y)] - .set_symbol(symbols.horizontal) - .set_style(self.border_style); - } - } + // // Sides + // if self.borders.intersects(Borders::LEFT) { + // for y in area.top()..area.bottom() { + // buf[(area.left(), y)] + // .set_symbol(symbols.vertical) + // .set_style(self.border_style); + // } + // } + // if self.borders.intersects(Borders::TOP) { + // for x in area.left()..area.right() { + // buf[(x, area.top())] + // .set_symbol(symbols.horizontal) + // .set_style(self.border_style); + // } + // } + // if self.borders.intersects(Borders::RIGHT) { + // let x = area.right() - 1; + // for y in area.top()..area.bottom() { + // buf[(x, y)] + // .set_symbol(symbols.vertical) + // .set_style(self.border_style); + // } + // } + // if self.borders.intersects(Borders::BOTTOM) { + // let y = area.bottom() - 1; + // for x in area.left()..area.right() { + // buf[(x, y)] + // .set_symbol(symbols.horizontal) + // .set_style(self.border_style); + // } + // } - // Corners - if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) { - buf[(area.right() - 1, area.bottom() - 1)] - .set_symbol(symbols.bottom_right) - .set_style(self.border_style); - } - if self.borders.contains(Borders::RIGHT | Borders::TOP) { - buf[(area.right() - 1, area.top())] - .set_symbol(symbols.top_right) - .set_style(self.border_style); - } - if self.borders.contains(Borders::LEFT | Borders::BOTTOM) { - buf[(area.left(), area.bottom() - 1)] - .set_symbol(symbols.bottom_left) - .set_style(self.border_style); - } - if self.borders.contains(Borders::LEFT | Borders::TOP) { - buf[(area.left(), area.top())] - .set_symbol(symbols.top_left) - .set_style(self.border_style); - } + // // Corners + // if self.borders.contains(Borders::RIGHT | Borders::BOTTOM) { + // buf[(area.right() - 1, area.bottom() - 1)] + // .set_symbol(symbols.bottom_right) + // .set_style(self.border_style); + // } + // if self.borders.contains(Borders::RIGHT | Borders::TOP) { + // buf[(area.right() - 1, area.top())] + // .set_symbol(symbols.top_right) + // .set_style(self.border_style); + // } + // if self.borders.contains(Borders::LEFT | Borders::BOTTOM) { + // buf[(area.left(), area.bottom() - 1)] + // .set_symbol(symbols.bottom_left) + // .set_style(self.border_style); + // } + // if self.borders.contains(Borders::LEFT | Borders::TOP) { + // buf[(area.left(), area.top())] + // .set_symbol(symbols.top_left) + // .set_style(self.border_style); + // } if let Some(title) = self.title { let lx = if self.borders.intersects(Borders::LEFT) { diff --git a/helix-tui/src/widgets/paragraph.rs b/helix-tui/src/widgets/paragraph.rs index 4e8391621..7f388d741 100644 --- a/helix-tui/src/widgets/paragraph.rs +++ b/helix-tui/src/widgets/paragraph.rs @@ -1,5 +1,5 @@ use crate::{ - buffer::Buffer, + buffer::{Buffer, SurfaceExt}, layout::Alignment, text::{StyledGrapheme, Text}, widgets::{ @@ -134,7 +134,7 @@ impl<'a> Paragraph<'a> { impl<'a> Widget for Paragraph<'a> { fn render(mut self, area: Rect, buf: &mut Buffer) { - buf.set_style(area, self.style); + // buf.set_style(area, self.style); let text_area = match self.block.take() { Some(b) => { let inner_area = b.inner(area); @@ -176,15 +176,15 @@ impl<'a> Widget for Paragraph<'a> { if y >= self.scroll.0 { let mut x = get_line_offset(current_line_width, text_area.width, self.alignment); for StyledGrapheme { symbol, style } in current_line { - buf[(text_area.left() + x, text_area.top() + y - self.scroll.0)] - .set_symbol(if symbol.is_empty() { - // If the symbol is empty, the last char which rendered last time will - // leave on the line. It's a quick fix. - " " - } else { - symbol - }) - .set_style(*style); + // buf[(text_area.left() + x, text_area.top() + y - self.scroll.0)] + // .set_symbol(if symbol.is_empty() { + // // If the symbol is empty, the last char which rendered last time will + // // leave on the line. It's a quick fix. + // " " + // } else { + // symbol + // }) + // .set_style(*style); x += symbol.width() as u16; } } diff --git a/helix-tui/src/widgets/table.rs b/helix-tui/src/widgets/table.rs index 6aee5988c..bfe267b43 100644 --- a/helix-tui/src/widgets/table.rs +++ b/helix-tui/src/widgets/table.rs @@ -1,5 +1,5 @@ use crate::{ - buffer::Buffer, + buffer::{Buffer, SurfaceExt}, layout::Constraint, text::Text, widgets::{Block, Widget}, @@ -390,7 +390,7 @@ impl<'a> Table<'a> { if area.area() == 0 { return; } - buf.set_style(area, self.style); + // buf.set_style(area, self.style); let table_area = match self.block.take() { Some(b) => { let inner_area = b.inner(area); @@ -410,15 +410,15 @@ impl<'a> Table<'a> { // Draw header if let Some(ref header) = self.header { let max_header_height = table_area.height.min(header.total_height()); - buf.set_style( - Rect { - x: table_area.left(), - y: table_area.top(), - width: table_area.width, - height: table_area.height.min(header.height), - }, - header.style, - ); + // buf.set_style( + // Rect { + // x: table_area.left(), + // y: table_area.top(), + // width: table_area.width, + // height: table_area.height.min(header.height), + // }, + // header.style, + // ); let mut col = table_area.left(); if has_selection { col += (highlight_symbol.width() as u16).min(table_area.width); @@ -461,7 +461,7 @@ impl<'a> Table<'a> { width: table_area.width, height: table_row.height, }; - buf.set_style(table_row_area, table_row.style); + // buf.set_style(table_row_area, table_row.style); let is_selected = state.selected.map(|s| s == i).unwrap_or(false); let table_row_start_col = if has_selection { let symbol = if is_selected { @@ -490,14 +490,14 @@ impl<'a> Table<'a> { col += *width + self.column_spacing; } if is_selected { - buf.set_style(table_row_area, self.highlight_style); + // buf.set_style(table_row_area, self.highlight_style); } } } } fn render_cell(buf: &mut Buffer, cell: &Cell, area: Rect) { - buf.set_style(area, cell.style); + // buf.set_style(area, cell.style); for (i, spans) in cell.content.lines.iter().enumerate() { if i as u16 >= area.height { break; diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index a4fa256d7..c595d4141 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -10,8 +10,8 @@ repository = "https://github.com/helix-editor/helix" homepage = "https://helix-editor.com" [features] -default = [] -term = ["crossterm"] +default = ["term"] +term = ["termwiz", "crossterm"] [dependencies] bitflags = "1.3" @@ -20,6 +20,7 @@ helix-core = { version = "0.6", path = "../helix-core" } helix-lsp = { version = "0.6", path = "../helix-lsp"} helix-dap = { version = "0.6", path = "../helix-dap"} crossterm = { version = "0.23", optional = true } +termwiz = { version = "0.15", optional = true } # Conversion traits once_cell = "1.10" diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 6d0a92928..5c203d40b 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -234,30 +234,30 @@ pub enum Color { } #[cfg(feature = "term")] -impl From for crossterm::style::Color { - 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 }, +impl Into for Color { + fn into(self) -> termwiz::color::ColorAttribute { + use termwiz::color::{AnsiColor, ColorAttribute, RgbColor}; + match self { + Color::Reset => ColorAttribute::Default, + Color::Black => AnsiColor::Black.into(), + Color::Gray | Color::LightGray => AnsiColor::Grey.into(), + Color::Red => AnsiColor::Maroon.into(), + Color::LightRed => AnsiColor::Red.into(), + Color::Green => AnsiColor::Green.into(), + Color::LightGreen => AnsiColor::Lime.into(), + Color::Yellow => AnsiColor::Olive.into(), + Color::LightYellow => AnsiColor::Yellow.into(), + Color::Magenta => AnsiColor::Purple.into(), + Color::LightMagenta => AnsiColor::Fuchsia.into(), + Color::Cyan => AnsiColor::Teal.into(), + Color::LightCyan => AnsiColor::Aqua.into(), + Color::White => AnsiColor::White.into(), + Color::Blue => AnsiColor::Navy.into(), + Color::LightBlue => AnsiColor::Blue.into(), + Color::Indexed(i) => ColorAttribute::PaletteIndex(i), + Color::Rgb(r, g, b) => { + ColorAttribute::TrueColorWithDefaultFallback(RgbColor::new_8bpc(r, g, b)) + } } } }