[tui] Add edtui editor for basic vim emulation.

This commit is contained in:
Shaun Reed 2026-01-18 10:09:28 -05:00
parent a8de77f370
commit fe6390c1cd
8 changed files with 494 additions and 411 deletions

538
Cargo.lock generated
View File

@ -45,14 +45,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "async-trait" name = "arboard"
version = "0.1.89" version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [ dependencies = [
"proc-macro2", "clipboard-win",
"quote", "image",
"syn 2.0.114", "log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation",
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"x11rb",
] ]
[[package]] [[package]]
@ -145,20 +154,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4"
[[package]] [[package]]
name = "byteorder" name = "byteorder-lite"
version = "1.5.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
dependencies = [
"byteorder",
"iovec",
]
[[package]] [[package]]
name = "bytes" name = "bytes"
@ -259,8 +258,8 @@ dependencies = [
"cxx-qt-build", "cxx-qt-build",
"cxx-qt-lib", "cxx-qt-lib",
"dirs", "dirs",
"edtui",
"log", "log",
"nvim-rs",
"ratatui", "ratatui",
"structopt", "structopt",
"syntect", "syntect",
@ -269,6 +268,15 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]] [[package]]
name = "codespan-reporting" name = "codespan-reporting"
version = "0.11.1" version = "0.11.1"
@ -367,6 +375,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.7"
@ -633,6 +647,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags 2.10.0",
"objc2",
]
[[package]] [[package]]
name = "document-features" name = "document-features"
version = "0.2.12" version = "0.2.12"
@ -642,12 +666,47 @@ dependencies = [
"litrs", "litrs",
] ]
[[package]]
name = "edtui"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "417b85aa75bedb1da51eeed2d7a9241a061ddc6a0212e80057968ba34256fad8"
dependencies = [
"arboard",
"crossterm",
"edtui-jagged",
"enum_dispatch",
"once_cell",
"ratatui-core",
"ratatui-widgets",
"syntect",
"unicode-width 0.2.2",
]
[[package]]
name = "edtui-jagged"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6818b2d6b8b3da52f7491b6331e27d45ae34e5baaffeb1edfde43911fe63dd6"
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@ -664,6 +723,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "error-code"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]] [[package]]
name = "euclid" name = "euclid"
version = "0.22.11" version = "0.22.11"
@ -683,6 +748,35 @@ dependencies = [
"regex", "regex",
] ]
[[package]]
name = "fax"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
dependencies = [
"fax_derive",
]
[[package]]
name = "fax_derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]] [[package]]
name = "filedescriptor" name = "filedescriptor"
version = "0.8.3" version = "0.8.3"
@ -734,103 +828,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "futures"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures 0.1.31",
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
"tokio-io",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@ -841,6 +838,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "gethostname"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"windows-link",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@ -864,6 +871,17 @@ dependencies = [
"wasip2", "wasip2",
] ]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.16.1"
@ -911,6 +929,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "image"
version = "0.25.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png",
"tiff",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.13.0"
@ -943,15 +975,6 @@ dependencies = [
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.13.0"
@ -1154,6 +1177,16 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "moxcms"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97"
dependencies = [
"num-traits",
"pxfm",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.29.0" version = "0.29.0"
@ -1213,18 +1246,76 @@ dependencies = [
] ]
[[package]] [[package]]
name = "nvim-rs" name = "objc2"
version = "0.9.2" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "010294fd782a554d4b9b17608305f7498809e857a6ed7a3e858588ad8e5691f5" checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05"
dependencies = [ dependencies = [
"async-trait", "objc2-encode",
"futures 0.3.31", ]
"log",
"rmp", [[package]]
"rmpv", name = "objc2-app-kit"
"tokio", version = "0.3.2"
"tokio-util", source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-graphics",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.10.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.10.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.10.0",
"objc2",
"objc2-core-foundation",
] ]
[[package]] [[package]]
@ -1293,6 +1384,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.5" version = "2.8.5"
@ -1394,12 +1491,6 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.32" version = "0.3.32"
@ -1419,6 +1510,19 @@ dependencies = [
"time", "time",
] ]
[[package]]
name = "png"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0"
dependencies = [
"bitflags 2.10.0",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.0" version = "1.13.0"
@ -1464,6 +1568,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "pxfm"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7186d3822593aa4393561d186d1393b3923e9d6163d3fbfd6e825e3e6cf3e6a8"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "qt-build-utils" name = "qt-build-utils"
version = "0.7.3" version = "0.7.3"
@ -1475,6 +1588,12 @@ dependencies = [
"versions", "versions",
] ]
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.4" version = "0.38.4"
@ -1648,24 +1767,6 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rmp"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
dependencies = [
"num-traits",
]
[[package]]
name = "rmpv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4e1d4b9b938a26d2996af33229f0ca0956c652c1375067f0b45291c1df8417"
dependencies = [
"rmp",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@ -1830,28 +1931,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "static_assertions" name = "static_assertions"
version = "1.1.0" version = "1.1.0"
@ -2079,6 +2164,20 @@ dependencies = [
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]]
name = "tiff"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af9605de7fee8d9551863fd692cce7637f548dbd9db9180fcc07ccc6d26c336f"
dependencies = [
"fax",
"flate2",
"half",
"quick-error",
"weezl",
"zune-jpeg",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.45" version = "0.3.45"
@ -2118,28 +2217,15 @@ version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [ dependencies = [
"bytes 1.11.0", "bytes",
"libc", "libc",
"mio", "mio",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"futures 0.1.31",
"log",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.6.0" version = "2.6.0"
@ -2151,20 +2237,6 @@ dependencies = [
"syn 2.0.114", "syn 2.0.114",
] ]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes 1.11.0",
"futures-core",
"futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tui-tree-widget" name = "tui-tree-widget"
version = "0.24.0" version = "0.24.0"
@ -2342,6 +2414,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "wezterm-bidi" name = "wezterm-bidi"
version = "0.2.3" version = "0.2.3"
@ -2540,6 +2618,23 @@ version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
[[package]]
name = "x11rb"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
dependencies = [
"gethostname",
"rustix",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
[[package]] [[package]]
name = "yaml-rust" name = "yaml-rust"
version = "0.4.5" version = "0.4.5"
@ -2549,8 +2644,43 @@ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "zerocopy"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.114",
]
[[package]] [[package]]
name = "zmij" name = "zmij"
version = "1.0.14" version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
[[package]]
name = "zune-jpeg"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
dependencies = [
"zune-core",
]

View File

@ -15,8 +15,8 @@ ratatui = "0.30.0"
anyhow = "1.0.100" anyhow = "1.0.100"
tui-tree-widget = "0.24.0" tui-tree-widget = "0.24.0"
uuid = { version = "1.19.0", features = ["v4"] } uuid = { version = "1.19.0", features = ["v4"] }
nvim-rs = { version = "0.9.2", features = ["use_tokio"] }
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "process"] } tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "process"] }
edtui = "0.11.0"
[build-dependencies] [build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6. # The link_qt_object_files feature is required for statically linking Qt 6.

View File

@ -41,7 +41,7 @@ fn main() -> Result<(), Box<dyn Error>> {
true => gui::run(root_path), true => gui::run(root_path),
false => match args.tui { false => match args.tui {
// Open the TUI editor if requested, otherwise use the QML GUI by default. // Open the TUI editor if requested, otherwise use the QML GUI by default.
true => Ok(tui::start(root_path)?), true => Ok(tui::Tui::new(root_path).start()?),
false => { false => {
// Relaunch the CLIDE GUI in a separate process. // Relaunch the CLIDE GUI in a separate process.
Command::new(std::env::current_exe()?) Command::new(std::env::current_exe()?)

View File

@ -1,16 +1,58 @@
pub mod app; pub mod app;
mod component; mod component;
mod explorer;
mod editor; mod editor;
mod explorer;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::crossterm::event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
};
use ratatui::crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use std::io::{Stdout, stdout};
pub fn start(root_path: std::path::PathBuf) -> Result<()> { pub struct Tui {
println!("Starting the TUI editor at {:?}", root_path); terminal: Terminal<CrosstermBackend<Stdout>>,
let terminal = ratatui::init(); root_path: std::path::PathBuf,
let app_result = app::App::new(&root_path) }
.run(terminal)
.context("Failed to start the TUI editor."); impl Tui {
ratatui::restore(); pub fn new(root_path: std::path::PathBuf) -> Self {
app_result Self {
terminal: Terminal::new(CrosstermBackend::new(stdout()))
.expect("Failed to initialize terminal"),
root_path,
}
}
pub fn start(self) -> Result<()> {
println!("Starting the TUI editor at {:?}", self.root_path);
ratatui::crossterm::execute!(
stdout(),
EnterAlternateScreen,
EnableMouseCapture,
EnableBracketedPaste
)?;
enable_raw_mode()?;
let app_result = app::App::new(&self.root_path)
.run(self.terminal)
.context("Failed to start the TUI editor.");
Self::stop()?;
app_result
}
fn stop() -> Result<()> {
disable_raw_mode()?;
ratatui::crossterm::execute!(
stdout(),
LeaveAlternateScreen,
DisableMouseCapture,
DisableBracketedPaste
)?;
Ok(())
}
} }

View File

@ -1,34 +1,63 @@
use crate::tui::component::{Action, ClideComponent}; use crate::tui::component::{Action, ClideComponent};
use crate::tui::editor::Editor;
use crate::tui::explorer::Explorer; use crate::tui::explorer::Explorer;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; use ratatui::crossterm::event;
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::prelude::{Color, Style, Widget}; use ratatui::prelude::{Color, Style, Widget};
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap}; use ratatui::widgets::{Block, Borders, Padding, Paragraph, Tabs, Wrap};
use ratatui::{DefaultTerminal, symbols}; use ratatui::{DefaultTerminal, symbols};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct App<'a> { pub struct App<'a> {
explorer: Explorer<'a>, explorer: Explorer<'a>,
editor: Editor,
} }
impl<'a> App<'a> { impl<'a> App<'a> {
pub(crate) fn new(root_path: &'a std::path::Path) -> Self { pub(crate) fn new(root_path: &'a std::path::Path) -> Self {
Self { Self {
explorer: Explorer::new(root_path), explorer: Explorer::new(root_path),
editor: Editor::new(),
} }
} }
fn get_event(&mut self) -> Option<Event> {
if !event::poll(Duration::from_millis(250)).expect("event poll failed") {
return None;
}
event::read().ok()
}
pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> { pub fn run(mut self, mut terminal: DefaultTerminal) -> anyhow::Result<()> {
loop { loop {
terminal.draw(|f| { terminal.draw(|f| {
f.render_widget(&self, f.area()); f.render_widget(&mut self, f.area());
})?; })?;
// TODO: Handle events based on which component is active. // TODO: Handle events based on which component is active.
// match self.explorer.handle_events() { ... } if let Some(event) = self.get_event() {
match self.handle_events() { self.editor
Action::Quit => break, .event_handler
_ => {} .on_event(event.clone(), &mut self.editor.state);
match event {
Event::FocusGained => {}
Event::FocusLost => {}
Event::Key(key_event) => {
// Handle main application key events.
match self.handle_key_events(key_event) {
Action::Noop => {}
Action::Quit => break,
Action::Pass => {}
}
}
Event::Mouse(_) => {}
Event::Paste(_) => {}
Event::Resize(_, _) => {}
}
} }
} }
Ok(()) Ok(())
@ -55,25 +84,6 @@ impl<'a> App<'a> {
.render(area, buf); .render(area, buf);
} }
fn draw_editor(&self, area: Rect, buf: &mut Buffer) {
// TODO: Title should be detected programming language name
// TODO: Content should be file contents
// TODO: Contents should use vim in rendered TTY
// TODO: Vimrc should be used
Paragraph::new("This is an example of the TUI interface (press 'q' to quit)")
.style(Style::default())
.block(
Block::default()
.title("Rust")
.title_style(Style::default().fg(Color::Yellow))
.title_alignment(Alignment::Right)
.borders(Borders::ALL)
.padding(Padding::new(0, 0, 0, 1)),
)
.wrap(Wrap { trim: false })
.render(area, buf);
}
fn draw_terminal(&self, area: Rect, buf: &mut Buffer) { fn draw_terminal(&self, area: Rect, buf: &mut Buffer) {
// TODO: Title should be detected shell name // TODO: Title should be detected shell name
// TODO: Contents should be shell output // TODO: Contents should be shell output
@ -91,7 +101,7 @@ impl<'a> App<'a> {
} }
// TODO: Separate complex components into their own widgets. // TODO: Separate complex components into their own widgets.
impl<'a> Widget for &App<'a> { impl<'a> Widget for &mut App<'a> {
fn render(self, area: Rect, buf: &mut Buffer) fn render(self, area: Rect, buf: &mut Buffer)
where where
Self: Sized, Self: Sized,
@ -127,8 +137,23 @@ impl<'a> Widget for &App<'a> {
self.explorer.render(horizontal[0], buf); self.explorer.render(horizontal[0], buf);
self.draw_tabs(editor_layout[0], buf); self.draw_tabs(editor_layout[0], buf);
self.draw_editor(editor_layout[1], buf); self.editor.render(editor_layout[1], buf);
} }
} }
impl<'a> ClideComponent for App<'a> {} impl<'a> ClideComponent for App<'a> {
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
match key {
KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
kind: KeyEventKind::Press,
state: _state,
} => Action::Quit,
key_event => {
// Pass the key event to each component that can handle it.
self.explorer.handle_key_events(key_event)
}
}
}
}

View File

@ -1,38 +1,23 @@
use ratatui::crossterm::event; use ratatui::crossterm::event::{KeyEvent, MouseEvent};
use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent};
use std::time::Duration;
pub enum Action { pub enum Action {
Noop, Noop,
Quit, Quit,
Pass, // Pass input to another component.
} }
pub trait ClideComponent { pub trait ClideComponent {
fn handle_events(&mut self) -> Action { fn handle_key_events(&mut self, _key: KeyEvent) -> Action {
if !event::poll(Duration::from_millis(250)).expect("event poll failed") {
return Action::Noop;
}
let key_event = event::read().expect("event read failed");
match key_event {
Event::Key(key_event) => self.handle_key_events(key_event),
Event::Mouse(mouse_event) => self.handle_mouse_events(mouse_event),
_ => Action::Noop,
}
}
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
match key.code {
KeyCode::Char('q') => Action::Quit,
_ => Action::Noop,
}
}
fn handle_mouse_events(&mut self, mouse: MouseEvent) -> Action {
Action::Noop Action::Noop
} }
fn update(&mut self, action: Action) -> Action { #[allow(dead_code)]
fn handle_mouse_events(&mut self, _mouse: MouseEvent) -> Action {
Action::Noop
}
#[allow(dead_code)]
fn update(&mut self, _action: Action) -> Action {
Action::Noop Action::Noop
} }
} }

View File

@ -1,156 +1,59 @@
use crate::tui::component::ClideComponent; use crate::tui::component::{Action, ClideComponent};
use anyhow::Result; use edtui::{
use nvim_rs::compat::tokio::Compat; EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, SyntaxHighlighter,
use nvim_rs::{Handler, Neovim, UiAttachOptions, Value}; };
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::Rect; use ratatui::crossterm::event::KeyEvent;
use ratatui::text::Line; use ratatui::layout::{Alignment, Rect};
use ratatui::widgets::{Paragraph, Widget}; use ratatui::prelude::{Color, Style};
use std::process::Stdio; use ratatui::widgets::{Block, Borders, Padding, Widget};
use std::sync::{Arc, Mutex};
use tokio::process::Command;
struct Editor { // TODO: Consider using editor-command https://docs.rs/editor-command/latest/editor_command/
ui: Arc<NvimUI>, // TODO: Title should be detected programming language name
height: usize, // TODO: Content should be file contents
width: usize, // TODO: Vimrc should be used
pub struct Editor {
pub state: EditorState,
pub event_handler: EditorEventHandler,
} }
impl Editor { impl Editor {
fn new(height: usize, width: usize) -> Self { pub fn new() -> Self {
let editor = Editor { Editor {
ui: Arc::new(NvimUI::default()), state: EditorState::default(),
height, event_handler: EditorEventHandler::default(),
width, }
};
editor
.ui
.grid
.lock()
.unwrap()
.resize(height, vec![' '; width]);
editor
} }
} }
impl<'a> Widget for &Editor { impl Widget for &mut Editor {
fn render(self, area: Rect, buf: &mut Buffer) fn render(self, area: Rect, buf: &mut Buffer)
where where
Self: Sized, Self: Sized,
{ {
let grid = self.ui.grid.lock().unwrap(); // TODO: Use current file extension for syntax highlighting here.
EditorView::new(&mut self.state)
let lines: Vec<Line> = grid .wrap(true)
.iter() .theme(
.map(|row| Line::from(row.iter().collect::<String>())) EditorTheme::default().block(
.collect(); Block::default()
.title("Rust")
Paragraph::new(lines).render(area, buf); .title_style(Style::default().fg(Color::Yellow))
.title_alignment(Alignment::Right)
.borders(Borders::ALL)
.padding(Padding::new(0, 0, 0, 1)),
),
)
.syntax_highlighter(SyntaxHighlighter::new("dracula", "rs").ok())
.tab_width(2)
.line_numbers(LineNumbers::Absolute)
.render(area, buf);
} }
} }
impl ClideComponent for Editor {} impl ClideComponent for Editor {
fn handle_key_events(&mut self, key: KeyEvent) -> Action {
#[derive(Default, Clone)] self.event_handler.on_key_event(key, &mut self.state);
pub struct NvimUI { Action::Pass
pub grid: Arc<Mutex<Vec<Vec<char>>>>,
pub cursor: Arc<Mutex<(usize, usize)>>,
}
impl Handler for NvimUI {
type Writer = Compat<tokio::process::ChildStdin>;
async fn handle_notify(
&self,
_name: String,
_args: Vec<Value>,
_neovim: Neovim<Compat<tokio::process::ChildStdin>>,
) -> Result<()> {
if _name != "redraw" {
return Ok(());
}
for event in _args {
if let Value::Array(items) = event {
if items.is_empty() {
continue;
}
let event_name = items[0].as_str().unwrap_or("");
match event_name {
"grid_line" => self.handle_grid_line(&items),
"cursor_goto" => self.handle_cursor(&items),
_ => {}
}
}
}
Ok(())
}
}
impl NvimUI {
fn handle_grid_line(&self, items: &[Value]) {
// ["grid_line", grid, row, col, cells]
let row = items[2].as_u64().unwrap() as usize;
let col = items[3].as_u64().unwrap() as usize;
let cells = items[4].as_array().unwrap();
let mut grid = self.grid.lock().unwrap();
let mut c = col;
for cell in cells {
let cell_arr = cell.as_array().unwrap();
let text = cell_arr[0].as_str().unwrap();
let repeat = cell_arr.get(2).and_then(|v| v.as_u64()).unwrap_or(1);
for _ in 0..repeat {
if let Some(ch) = text.chars().next() {
grid[row][c] = ch;
}
c += 1;
}
}
}
fn handle_cursor(&self, items: &[Value]) {
let row = items[2].as_u64().unwrap() as usize;
let col = items[3].as_u64().unwrap() as usize;
*self.cursor.lock().unwrap() = (row, col);
}
pub async fn spawn_nvim<H: Handler + Send + 'static>(
handler: H,
) -> Result<Neovim<Compat<tokio::process::ChildStdin>>> {
let mut child = Command::new("nvim")
.arg("--embed")
.arg("--headless")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
let stdin = child.stdin.take().unwrap();
let mut stdout = child.stdout.take().unwrap();
let (nvim, io_handler) = Neovim::new(stdout, stdin, handler);
tokio::spawn(async move {
nvim_rs::compat::tokio::spawn(stdout.compat(), io_handler).await;
});
Ok(nvim)
}
pub async fn init_ui(
nvim: &Neovim<nvim_rs::compat::tokio::Compat<tokio::process::ChildStdin>>,
w: i64,
h: i64,
) -> anyhow::Result<()> {
let mut opts = UiAttachOptions::default();
opts.set_rgb(true).set_linegrid_external(true);
nvim.ui_attach(w, h, &opts).await?;
Ok(())
} }
} }

View File

@ -17,15 +17,13 @@ pub struct Explorer<'a> {
impl<'a> Explorer<'a> { impl<'a> Explorer<'a> {
pub fn new(path: &'a std::path::Path) -> Self { pub fn new(path: &'a std::path::Path) -> Self {
let mut explorer = Explorer { let explorer = Explorer {
root_path: path, root_path: path,
tree_items: Self::build_tree_from_path(path.into()), tree_items: Self::build_tree_from_path(path.into()),
}; };
explorer explorer
} }
pub fn draw(&self, area: Rect, buf: &mut Buffer) {}
fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> { fn build_tree_from_path(path: std::path::PathBuf) -> TreeItem<'static, String> {
let mut children = vec![]; let mut children = vec![];
if let Ok(entries) = fs::read_dir(&path) { if let Ok(entries) = fs::read_dir(&path) {