red: add red, the text editor
This commit is contained in:
parent
5447306fa6
commit
546010762f
420
Cargo.lock
generated
420
Cargo.lock
generated
@ -8,6 +8,30 @@ 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 = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.3.19"
|
version = "4.3.19"
|
||||||
@ -47,12 +71,66 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-chain"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"match_cfg",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humansize"
|
name = "humansize"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
@ -69,12 +147,46 @@ dependencies = [
|
|||||||
"yggdrasil-rt",
|
"yggdrasil-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "match_cfg"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.6.4"
|
version = "2.6.4"
|
||||||
@ -87,6 +199,18 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@ -97,12 +221,50 @@ dependencies = [
|
|||||||
"minimal-lexical",
|
"minimal-lexical",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
@ -121,6 +283,52 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "red"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"libc",
|
||||||
|
"syslog",
|
||||||
|
"thiserror",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shell"
|
name = "shell"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -132,16 +340,65 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "signal-hook"
|
||||||
version = "2.0.27"
|
version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0"
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syslog"
|
||||||
|
version = "6.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7434e95bcccce1215d30f4bf84fe8c00e8de1b9be4fb736d747ca53d36e7f96f"
|
||||||
|
dependencies = [
|
||||||
|
"error-chain",
|
||||||
|
"hostname",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysutils"
|
name = "sysutils"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -151,12 +408,169 @@ dependencies = [
|
|||||||
"yggdrasil-rt",
|
"yggdrasil-rt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||||
|
dependencies = [
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yggdrasil-abi"
|
name = "yggdrasil-abi"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -3,7 +3,8 @@ resolver = "1"
|
|||||||
members = [
|
members = [
|
||||||
"init",
|
"init",
|
||||||
"shell",
|
"shell",
|
||||||
"sysutils"
|
"sysutils",
|
||||||
|
"red"
|
||||||
]
|
]
|
||||||
|
|
||||||
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-abi.git']
|
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-abi.git']
|
||||||
|
4
build.sh
4
build.sh
@ -54,6 +54,10 @@ pack_initrd() {
|
|||||||
cp ${build_dir}/login ${root_dir}/sbin/
|
cp ${build_dir}/login ${root_dir}/sbin/
|
||||||
cp ${build_dir}/ls ${root_dir}/bin/
|
cp ${build_dir}/ls ${root_dir}/bin/
|
||||||
cp ${build_dir}/hexd ${root_dir}/bin/
|
cp ${build_dir}/hexd ${root_dir}/bin/
|
||||||
|
cp ${build_dir}/colors ${root_dir}/bin/
|
||||||
|
|
||||||
|
# red
|
||||||
|
cp ${build_dir}/red ${root_dir}/bin/red
|
||||||
|
|
||||||
cp -r ${workspace_dir}/etc ${root_dir}/
|
cp -r ${workspace_dir}/etc ${root_dir}/
|
||||||
|
|
||||||
|
424
red/Cargo.lock
generated
Normal file
424
red/Cargo.lock
generated
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-chain"
|
||||||
|
version = "0.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"match_cfg",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.150"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "match_cfg"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "red"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"crossterm",
|
||||||
|
"libc",
|
||||||
|
"syslog",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.192"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syslog"
|
||||||
|
version = "6.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7434e95bcccce1215d30f4bf84fe8c00e8de1b9be4fb736d747ca53d36e7f96f"
|
||||||
|
dependencies = [
|
||||||
|
"error-chain",
|
||||||
|
"hostname",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||||
|
dependencies = [
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
15
red/Cargo.toml
Normal file
15
red/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "red"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1.0.50"
|
||||||
|
unicode-width = "0.1.11"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_os = "yggdrasil"))'.dependencies]
|
||||||
|
libc = "0.2.150"
|
||||||
|
crossterm = "0.27.0"
|
||||||
|
syslog = "6.1.0"
|
483
red/src/buffer/mod.rs
Normal file
483
red/src/buffer/mod.rs
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
fs::File,
|
||||||
|
io::{self, BufRead, BufReader, BufWriter, Write},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
error::Error,
|
||||||
|
line::{Line, TextLike},
|
||||||
|
term::{Color, CursorStyle, Term, Terminal},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct View {
|
||||||
|
cursor_column: usize,
|
||||||
|
cursor_row: usize,
|
||||||
|
column_offset: usize,
|
||||||
|
row_offset: usize,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
offset_x: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum Mode {
|
||||||
|
Normal,
|
||||||
|
Insert,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SetMode {
|
||||||
|
Normal,
|
||||||
|
InsertBefore,
|
||||||
|
InsertAfter,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Buffer {
|
||||||
|
lines: Vec<Line>,
|
||||||
|
dirty: bool,
|
||||||
|
mode_dirty: bool,
|
||||||
|
mode: Mode,
|
||||||
|
view: View,
|
||||||
|
name: Option<String>,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
modified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mode {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Normal => "NORMAL",
|
||||||
|
Self::Insert => "INSERT",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn set_row(&mut self, row: usize) {
|
||||||
|
self.cursor_row = row;
|
||||||
|
|
||||||
|
if self.cursor_row < self.row_offset {
|
||||||
|
self.row_offset = self.cursor_row;
|
||||||
|
} else if self.cursor_row >= self.row_offset + self.height {
|
||||||
|
self.row_offset = self.cursor_row - self.height + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) {
|
||||||
|
let Some(line) = line else {
|
||||||
|
self.column_offset = 0;
|
||||||
|
self.cursor_column = 0;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cursor_column = col;
|
||||||
|
|
||||||
|
if line.display_width(config.tab_width) + 1 <= self.width {
|
||||||
|
self.column_offset = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width_to_cursor = line
|
||||||
|
.span(..self.cursor_column)
|
||||||
|
.display_width(config.tab_width);
|
||||||
|
|
||||||
|
if width_to_cursor < self.column_offset {
|
||||||
|
self.column_offset = width_to_cursor;
|
||||||
|
} else if width_to_cursor >= self.column_offset + self.width {
|
||||||
|
self.column_offset = width_to_cursor - self.width + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.column_offset = 0;
|
||||||
|
self.row_offset = 0;
|
||||||
|
self.cursor_row = 0;
|
||||||
|
self.cursor_column = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn resize(&mut self, width: usize, height: usize) {
|
||||||
|
// self.width = width;
|
||||||
|
// self.height = height;
|
||||||
|
|
||||||
|
// self.column_offset = 0;
|
||||||
|
// self.row_offset = 0;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buffer {
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
lines: vec![],
|
||||||
|
dirty: true,
|
||||||
|
mode_dirty: true,
|
||||||
|
view: View::default(),
|
||||||
|
mode: Mode::Normal,
|
||||||
|
name: None,
|
||||||
|
path: None,
|
||||||
|
modified: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_lines<P: AsRef<Path>>(path: P) -> io::Result<Vec<Line>> {
|
||||||
|
let input = BufReader::new(File::open(path)?);
|
||||||
|
let lines = input.lines().collect::<Result<Vec<_>, _>>()?;
|
||||||
|
let lines = lines
|
||||||
|
.into_iter()
|
||||||
|
.map(|line| Line::from_str(line.trim_end_matches('\n')))
|
||||||
|
.collect();
|
||||||
|
Ok(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
|
||||||
|
let lines = if path.exists() {
|
||||||
|
Self::read_lines(path)?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
lines,
|
||||||
|
name,
|
||||||
|
path: Some(path.into()),
|
||||||
|
mode: Mode::Normal,
|
||||||
|
dirty: true,
|
||||||
|
mode_dirty: true,
|
||||||
|
view: View::default(),
|
||||||
|
modified: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reopen<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
|
||||||
|
let lines = if path.exists() {
|
||||||
|
Self::read_lines(path)?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
self.lines = lines;
|
||||||
|
self.modified = false;
|
||||||
|
self.mode = Mode::Normal;
|
||||||
|
self.dirty = true;
|
||||||
|
self.mode_dirty = true;
|
||||||
|
self.view.reset();
|
||||||
|
|
||||||
|
self.path = Some(path.into());
|
||||||
|
self.name = name;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&mut self) -> Result<(), Error> {
|
||||||
|
let path = self.path.as_ref().ok_or(Error::NoPath)?;
|
||||||
|
let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?);
|
||||||
|
|
||||||
|
for line in self.lines.iter() {
|
||||||
|
writer
|
||||||
|
.write_all(line.to_string().as_ref())
|
||||||
|
.map_err(Error::WriteError)?;
|
||||||
|
writer.write_all(b"\n").map_err(Error::WriteError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.modified = false;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_path<P: AsRef<Path>>(&mut self, path: P) {
|
||||||
|
let path = PathBuf::from(path.as_ref());
|
||||||
|
let name = path.file_name().and_then(OsStr::to_str).map(String::from);
|
||||||
|
|
||||||
|
self.path = Some(path);
|
||||||
|
self.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_mode(&mut self, config: &Config, mode: SetMode) {
|
||||||
|
let dst_mode = match mode {
|
||||||
|
SetMode::Normal => Mode::Normal,
|
||||||
|
SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert,
|
||||||
|
};
|
||||||
|
|
||||||
|
if dst_mode == self.mode {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mode = dst_mode;
|
||||||
|
self.mode_dirty = true;
|
||||||
|
match mode {
|
||||||
|
SetMode::Normal => self.move_cursor(config, -1, 0),
|
||||||
|
SetMode::InsertBefore => self.move_cursor(config, 0, 0),
|
||||||
|
SetMode::InsertAfter => self.move_cursor(config, 1, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode(&self) -> Mode {
|
||||||
|
self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<&String> {
|
||||||
|
self.name.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> Option<&PathBuf> {
|
||||||
|
self.path.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn row_offset(&self) -> usize {
|
||||||
|
self.view.row_offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.lines.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_row(&self) -> usize {
|
||||||
|
self.view.cursor_row
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_position(&mut self, config: &Config, px: usize, py: usize) {
|
||||||
|
self.dirty = true;
|
||||||
|
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
self.view.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to row
|
||||||
|
self.view.set_row(py.min(self.lines.len() - 1));
|
||||||
|
|
||||||
|
// Set mode- and line-len-adjusted column
|
||||||
|
if let Some(line) = self.lines.get(self.view.cursor_row) && !line.is_empty() {
|
||||||
|
match self.mode {
|
||||||
|
// Limited by line.len()
|
||||||
|
Mode::Normal => self.view.set_column(config, px.min(line.len() - 1), Some(line)),
|
||||||
|
Mode::Insert => self.view.set_column(config, px.min(line.len()), Some(line)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.view.set_column(config, 0, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_line_end(&mut self, config: &Config) {
|
||||||
|
let len = self
|
||||||
|
.lines
|
||||||
|
.get(self.view.cursor_row)
|
||||||
|
.map(Line::len)
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
self.set_position(config, len, self.view.cursor_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_column(&mut self, config: &Config, x: usize) {
|
||||||
|
self.set_position(config, x, self.view.cursor_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) {
|
||||||
|
let px = (self.view.cursor_column as isize + dx).max(0) as usize;
|
||||||
|
let py = (self.view.cursor_row as isize + dy).max(0) as usize;
|
||||||
|
|
||||||
|
self.set_position(config, px, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) {
|
||||||
|
self.dirty = true;
|
||||||
|
self.view.height = height;
|
||||||
|
self.view.width = width;
|
||||||
|
self.view.offset_x = offset_x;
|
||||||
|
|
||||||
|
self.view.set_row(self.view.cursor_row);
|
||||||
|
self.view.set_column(
|
||||||
|
config,
|
||||||
|
self.view.cursor_column,
|
||||||
|
self.lines.get(self.view.cursor_row),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_cursor(&self, config: &Config) -> (usize, usize) {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
return (0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert!(self.view.column_offset <= self.view.cursor_column);
|
||||||
|
assert!(self.view.row_offset <= self.view.cursor_row);
|
||||||
|
|
||||||
|
let line = &self.lines[self.view.cursor_row];
|
||||||
|
assert!(self.view.cursor_column <= line.len());
|
||||||
|
|
||||||
|
let column = line
|
||||||
|
.span(..self.view.cursor_column)
|
||||||
|
.display_width(config.tab_width);
|
||||||
|
assert!(self.view.column_offset <= column);
|
||||||
|
|
||||||
|
(
|
||||||
|
column - self.view.column_offset,
|
||||||
|
self.view.cursor_row - self.view.row_offset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
self.view.height
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_modified(&self) -> bool {
|
||||||
|
self.modified
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_line(&self, config: &Config, term: &mut Term, row: usize, line: &Line) {
|
||||||
|
let mut pos = 0;
|
||||||
|
term.set_cursor_position(row, self.view.offset_x);
|
||||||
|
|
||||||
|
let span = line.skip_to_width(self.view.column_offset, config.tab_width);
|
||||||
|
let long_line = span.display_width(config.tab_width) > self.view.width;
|
||||||
|
|
||||||
|
for &ch in span.iter() {
|
||||||
|
if pos >= self.view.width {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ch == '\t' {
|
||||||
|
let old_pos = pos;
|
||||||
|
let new_pos = (pos + config.tab_width) & !(config.tab_width - 1);
|
||||||
|
pos = new_pos;
|
||||||
|
|
||||||
|
for i in old_pos..new_pos {
|
||||||
|
if i >= self.view.width {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if i == old_pos {
|
||||||
|
term.set_foreground(Color::Blue);
|
||||||
|
term.put_byte(b'>');
|
||||||
|
term.set_foreground(Color::Default);
|
||||||
|
} else {
|
||||||
|
term.put_byte(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO optimize later
|
||||||
|
let s = std::iter::once(ch).collect::<String>();
|
||||||
|
term.put_bytes(s.as_str());
|
||||||
|
pos += ch.width().unwrap_or(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if long_line {
|
||||||
|
term.set_cursor_position(row, self.view.width + self.view.offset_x);
|
||||||
|
term.set_foreground(Color::Black);
|
||||||
|
term.set_background(Color::White);
|
||||||
|
term.put_byte(b'>');
|
||||||
|
term.set_foreground(Color::Default);
|
||||||
|
term.set_background(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display(&mut self, config: &Config, term: &mut Term) {
|
||||||
|
for (row, line) in self
|
||||||
|
.lines
|
||||||
|
.iter()
|
||||||
|
.skip(self.view.row_offset)
|
||||||
|
.take(self.view.height)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
self.display_line(config, term, row, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newline_before(&mut self) {
|
||||||
|
self.lines.insert(self.view.cursor_row, Line::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newline_after(&mut self, break_line: bool) {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
self.lines.push(Line::new());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newline = if break_line {
|
||||||
|
self.lines[self.view.cursor_row].split_off(self.view.cursor_column)
|
||||||
|
} else {
|
||||||
|
Line::new()
|
||||||
|
};
|
||||||
|
self.lines.insert(self.view.cursor_row + 1, newline);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, config: &Config, ch: char) {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
assert_eq!(self.view.cursor_row, 0);
|
||||||
|
self.lines.push(Line::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = &mut self.lines[self.view.cursor_row];
|
||||||
|
line.insert(self.view.cursor_column, ch);
|
||||||
|
self.move_cursor(config, 1, 0);
|
||||||
|
self.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase_backward(&mut self, config: &Config) {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.view.cursor_column == 0 {
|
||||||
|
if self.view.cursor_row != 0 {
|
||||||
|
let line = self.lines.remove(self.view.cursor_row);
|
||||||
|
let prev_line = &mut self.lines[self.view.cursor_row - 1];
|
||||||
|
|
||||||
|
let len = prev_line.len();
|
||||||
|
prev_line.extend(line);
|
||||||
|
self.set_position(config, len, self.view.cursor_row - 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = &mut self.lines[self.view.cursor_row];
|
||||||
|
line.remove(self.view.cursor_column - 1);
|
||||||
|
self.move_cursor(config, -1, 0);
|
||||||
|
self.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn erase_forward(&mut self) {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = &mut self.lines[self.view.cursor_row];
|
||||||
|
if self.view.cursor_column == line.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line.remove(self.view.cursor_column);
|
||||||
|
self.dirty = true;
|
||||||
|
self.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) {
|
||||||
|
let (x, y) = self.display_cursor(config);
|
||||||
|
if self.mode_dirty {
|
||||||
|
match self.mode {
|
||||||
|
Mode::Normal => term.set_cursor_style(CursorStyle::Default),
|
||||||
|
Mode::Insert => term.set_cursor_style(CursorStyle::Line),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
term.set_cursor_position(y, x + self.view.offset_x);
|
||||||
|
self.mode_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn number_width(&mut self) -> usize {
|
||||||
|
if self.lines.len() == 0 {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
self.lines.len().ilog10() as usize + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
red/src/command.rs
Normal file
124
red/src/command.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
use crate::{error::Error, State, buffer::{Buffer, SetMode}, config::Config};
|
||||||
|
|
||||||
|
pub type CommandFn = fn(&mut State, &[&str]) -> Result<(), Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Action {
|
||||||
|
// Editing
|
||||||
|
EraseBackward,
|
||||||
|
InsertBefore,
|
||||||
|
InsertAfter,
|
||||||
|
NewlineBefore,
|
||||||
|
NewlineAfter,
|
||||||
|
BreakLine,
|
||||||
|
|
||||||
|
// Movement
|
||||||
|
MoveCharPrev,
|
||||||
|
MoveCharNext,
|
||||||
|
MoveLinePrev,
|
||||||
|
MoveLineNext,
|
||||||
|
MoveLineStart,
|
||||||
|
MoveLineEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
static COMMANDS: &[(&str, RangeInclusive<usize>, CommandFn)] = &[
|
||||||
|
("w", 0..=1, cmd_write),
|
||||||
|
("w!", 0..=1, cmd_force_write),
|
||||||
|
("q", 0..=0, cmd_exit),
|
||||||
|
("q!", 0..=0, cmd_force_exit),
|
||||||
|
("e", 1..=1, cmd_edit),
|
||||||
|
("e!", 0..=1, cmd_force_edit),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
fn cmd_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||||
|
if args.len() == 1 && state.buffer().is_modified() && state.buffer().path().is_some() {
|
||||||
|
return Err(Error::UnsavedBuffer("Use :w! FILE to force write to another file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_force_write(state, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_force_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||||
|
let buffer = state.buffer_mut();
|
||||||
|
if let Some(&path) = args.first() {
|
||||||
|
buffer.set_path(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||||
|
if state.buffer().is_modified() {
|
||||||
|
return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file"));
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_force_edit(state, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_force_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||||
|
if let Some(&path) = args.first() {
|
||||||
|
state.buffer_mut().reopen(path).map_err(Error::OpenError)
|
||||||
|
} else if let Some(path) = state.buffer().path().cloned() {
|
||||||
|
state.buffer_mut().reopen(path).map_err(Error::OpenError)
|
||||||
|
} else {
|
||||||
|
Err(Error::NoPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
|
||||||
|
let buffer = state.buffer();
|
||||||
|
|
||||||
|
if buffer.is_modified() {
|
||||||
|
return Err(Error::UnsavedBuffer("Use :q! to force exit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
state.exit();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_force_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
|
||||||
|
state.exit();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(state: &mut State, command: String) -> Result<(), Error> {
|
||||||
|
let words = command.split(' ').collect::<Vec<_>>();
|
||||||
|
let Some((&cmd, args)) = words.split_first() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for (name, nargs, f) in COMMANDS.iter() {
|
||||||
|
if *name == cmd {
|
||||||
|
if !nargs.contains(&args.len()) {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
return f(state, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::UnknownCommand(cmd.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform(buffer: &mut Buffer, config: &Config, action: Action) -> Result<(), Error> {
|
||||||
|
match action {
|
||||||
|
// Editing
|
||||||
|
Action::EraseBackward => buffer.erase_backward(config),
|
||||||
|
Action::InsertBefore => buffer.set_mode(config, SetMode::InsertBefore),
|
||||||
|
Action::InsertAfter => buffer.set_mode(config, SetMode::InsertAfter),
|
||||||
|
Action::NewlineBefore => buffer.newline_before(),
|
||||||
|
Action::NewlineAfter => buffer.newline_after(false),
|
||||||
|
Action::BreakLine => buffer.newline_after(true),
|
||||||
|
// Movement
|
||||||
|
Action::MoveCharPrev => buffer.move_cursor(config, -1, 0),
|
||||||
|
Action::MoveCharNext => buffer.move_cursor(config, 1, 0),
|
||||||
|
Action::MoveLinePrev => buffer.move_cursor(config, 0, -1),
|
||||||
|
Action::MoveLineNext => buffer.move_cursor(config, 0, 1),
|
||||||
|
Action::MoveLineStart => buffer.set_column(config, 0),
|
||||||
|
Action::MoveLineEnd => buffer.to_line_end(config),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
54
red/src/config.rs
Normal file
54
red/src/config.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{buffer::Mode, command::Action};
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
// TODO must be a power of 2, lol
|
||||||
|
pub tab_width: usize,
|
||||||
|
pub number: bool,
|
||||||
|
|
||||||
|
pub nmap: HashMap<char, Vec<Action>>,
|
||||||
|
pub imap: HashMap<char, Vec<Action>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bind<I: IntoIterator<Item = Action>>(key: char, items: I) -> (char, Vec<Action>) {
|
||||||
|
(key, items.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
use Action::*;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
tab_width: 4,
|
||||||
|
number: true,
|
||||||
|
nmap: HashMap::from_iter([
|
||||||
|
bind('i', [InsertBefore]),
|
||||||
|
bind('a', [InsertAfter]),
|
||||||
|
bind('h', [MoveCharPrev]),
|
||||||
|
bind('l', [MoveCharNext]),
|
||||||
|
bind('j', [MoveLineNext]),
|
||||||
|
bind('k', [MoveLinePrev]),
|
||||||
|
bind('I', [MoveLineStart, InsertBefore]),
|
||||||
|
bind('A', [MoveLineEnd, InsertAfter]),
|
||||||
|
bind('o', [NewlineAfter, MoveLineNext, InsertBefore]),
|
||||||
|
bind('O', [NewlineBefore, MoveLinePrev, InsertBefore]),
|
||||||
|
]),
|
||||||
|
imap: HashMap::from_iter([
|
||||||
|
bind('\x7F', [EraseBackward]),
|
||||||
|
bind('\n', [BreakLine, MoveLineNext, MoveLineStart, InsertBefore]),
|
||||||
|
bind('\x0D', [BreakLine, MoveLineNext, MoveLineStart, InsertBefore]),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn key(&self, mode: Mode, key: char) -> Option<&[Action]> {
|
||||||
|
match mode {
|
||||||
|
Mode::Normal => self.nmap.get(&key),
|
||||||
|
Mode::Insert => self.imap.get(&key)
|
||||||
|
}.map(AsRef::as_ref)
|
||||||
|
}
|
||||||
|
}
|
18
red/src/error.rs
Normal file
18
red/src/error.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
use std::io;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
// I/O errors
|
||||||
|
#[error("Could not open file: {0}")]
|
||||||
|
OpenError(io::Error),
|
||||||
|
#[error("Could not write file: {0}")]
|
||||||
|
WriteError(io::Error),
|
||||||
|
#[error("Buffer does not have a path")]
|
||||||
|
NoPath,
|
||||||
|
#[error("Buffer has unsaved changes: {0}")]
|
||||||
|
UnsavedBuffer(&'static str),
|
||||||
|
#[error("Invalid command, usage: {0}")]
|
||||||
|
InvalidCommand(&'static str),
|
||||||
|
#[error("Unknown command: {0:?}")]
|
||||||
|
UnknownCommand(String),
|
||||||
|
}
|
226
red/src/line.rs
Normal file
226
red/src/line.rs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
use std::{ops::Index, slice::SliceIndex};
|
||||||
|
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Default)]
|
||||||
|
pub struct Line {
|
||||||
|
data: Vec<char>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Span<'a>(&'a [char]);
|
||||||
|
|
||||||
|
pub trait TextLike: Index<usize, Output = char> + ToString {
|
||||||
|
type Iter<'a>: Iterator<Item = &'a char> where Self: 'a;
|
||||||
|
type Span<'a>: TextLike + 'a
|
||||||
|
where
|
||||||
|
Self: 'a;
|
||||||
|
|
||||||
|
fn display_width(&self, tab_width: usize) -> usize;
|
||||||
|
fn span<R: SliceIndex<[char], Output = [char]>>(&self, range: R) -> Self::Span<'_>;
|
||||||
|
fn skip_to_width(&self, offset: usize, tab_width: usize) -> Self::Span<'_>;
|
||||||
|
|
||||||
|
fn iter(&self) -> Self::Iter<'_>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line
|
||||||
|
impl Line {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { data: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str<S: AsRef<str>>(s: S) -> Self {
|
||||||
|
let chars = s.as_ref().chars();
|
||||||
|
Self {
|
||||||
|
data: Vec::from_iter(chars),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_span(&self) -> Span {
|
||||||
|
Span(self.data.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.data.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.data.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_off(&mut self, at: usize) -> Line {
|
||||||
|
let data = self.data.split_off(at);
|
||||||
|
Line { data }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, at: usize, ch: char) {
|
||||||
|
self.data.insert(at, ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, at: usize) {
|
||||||
|
self.data.remove(at);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extend(&mut self, other: Line) {
|
||||||
|
self.data.extend(other.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Line {
|
||||||
|
type Item = char;
|
||||||
|
type IntoIter = std::vec::IntoIter<char>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.data.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextLike for Line {
|
||||||
|
type Span<'a> = Span<'a>;
|
||||||
|
type Iter<'a> = std::slice::Iter<'a, char>;
|
||||||
|
|
||||||
|
fn display_width(&self, tab_width: usize) -> usize {
|
||||||
|
self.as_span().display_width(tab_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span<R: SliceIndex<[char], Output = [char]>>(&self, range: R) -> Self::Span<'_> {
|
||||||
|
self.as_span().span(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_to_width(&self, offset: usize, tab_width: usize) -> Self::Span<'_> {
|
||||||
|
self.as_span().skip_to_width(offset, tab_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&self) -> Self::Iter<'_> {
|
||||||
|
self.data.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Line {
|
||||||
|
type Output = char;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.data[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Line {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.as_span().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Span
|
||||||
|
impl<'s> TextLike for Span<'s> {
|
||||||
|
type Iter<'a> = std::slice::Iter<'a, char> where 's: 'a;
|
||||||
|
type Span<'a> = Span<'s> where 's: 'a;
|
||||||
|
|
||||||
|
fn display_width(&self, tab_width: usize) -> usize {
|
||||||
|
self.0.iter().fold(0, |pos, &ch| match ch {
|
||||||
|
'\t' => (pos + tab_width) & !(tab_width - 1),
|
||||||
|
_ => pos + ch.width().unwrap_or(1),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span<R: SliceIndex<[char], Output = [char]>>(&self, range: R) -> Self::Span<'_> {
|
||||||
|
Span(&self.0[range])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_to_width(&self, offset: usize, tab_width: usize) -> Self::Span<'_> {
|
||||||
|
let mut index = 0;
|
||||||
|
let mut pos = 0;
|
||||||
|
for &ch in self.0.iter() {
|
||||||
|
if pos >= offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match ch {
|
||||||
|
'\t' => pos = (pos + tab_width) & !(tab_width - 1),
|
||||||
|
_ => pos += ch.width().unwrap_or(1),
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.span(index..)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter(&self) -> Self::Iter<'_> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Span<'_> {
|
||||||
|
type Output = char;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.0[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for Span<'_> {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
self.0.iter().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::line::{Span, TextLike};
|
||||||
|
|
||||||
|
use super::Line;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_from_str() {
|
||||||
|
// pure ASCII
|
||||||
|
let text = "abc123\n\t xyz";
|
||||||
|
let line = Line::from_str(text);
|
||||||
|
assert_eq!(
|
||||||
|
line.data,
|
||||||
|
vec!['a', 'b', 'c', '1', '2', '3', '\n', '\t', ' ', 'x', 'y', 'z']
|
||||||
|
);
|
||||||
|
|
||||||
|
// cyrillic unicode
|
||||||
|
let text = "це тест123";
|
||||||
|
let line = Line::from_str(text);
|
||||||
|
assert_eq!(
|
||||||
|
line.data,
|
||||||
|
vec!['ц', 'е', ' ', 'т', 'е', 'с', 'т', '1', '2', '3']
|
||||||
|
);
|
||||||
|
|
||||||
|
// japanese unicode
|
||||||
|
let text = "1日本2";
|
||||||
|
let line = Line::from_str(text);
|
||||||
|
assert_eq!(line.data, vec!['1', '日', '本', '2']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_to_string() {
|
||||||
|
let line = Line {
|
||||||
|
data: vec!['a', 'b', 'c', 'т', 'е', 'с', 'т', '1', '2', '3', '\n'],
|
||||||
|
};
|
||||||
|
assert_eq!(line.to_string().as_str(), "abcтест123\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_span() {
|
||||||
|
// All span
|
||||||
|
let line = Line::from_str("abcdef");
|
||||||
|
assert_eq!(line.as_span(), Span(&['a', 'b', 'c', 'd', 'e', 'f']));
|
||||||
|
|
||||||
|
assert_eq!(line.span(..3), Span(&['a', 'b', 'c']));
|
||||||
|
assert_eq!(line.span(..=3), Span(&['a', 'b', 'c', 'd']));
|
||||||
|
|
||||||
|
assert_eq!(line.span(..=3).span(2..), Span(&['c', 'd']));
|
||||||
|
assert_eq!(line.span(2..=3), Span(&['c', 'd']));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_width() {
|
||||||
|
// No tabs
|
||||||
|
let line = Line::from_str("abcdef");
|
||||||
|
assert_eq!(line.display_width(4), line.len());
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
let line = Line::from_str("\ta\tbcdef");
|
||||||
|
assert_eq!(line.display_width(4), 8 + 5);
|
||||||
|
}
|
||||||
|
}
|
334
red/src/main.rs
Normal file
334
red/src/main.rs
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
#![feature(let_chains, rustc_private)]
|
||||||
|
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_raw_fd, yggdrasil_os))]
|
||||||
|
|
||||||
|
use std::{env, path::Path, fmt::Write};
|
||||||
|
|
||||||
|
use buffer::{Buffer, Mode, SetMode};
|
||||||
|
use config::Config;
|
||||||
|
use error::Error;
|
||||||
|
use term::{Clear, Color, Term};
|
||||||
|
|
||||||
|
use crate::term::Terminal;
|
||||||
|
|
||||||
|
pub mod buffer;
|
||||||
|
pub mod command;
|
||||||
|
pub mod config;
|
||||||
|
pub mod error;
|
||||||
|
pub mod line;
|
||||||
|
pub mod term;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum TopMode {
|
||||||
|
Normal,
|
||||||
|
Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
term: Term,
|
||||||
|
buffer: Buffer,
|
||||||
|
command: String,
|
||||||
|
message: Option<String>,
|
||||||
|
top_mode: TopMode,
|
||||||
|
config: Config,
|
||||||
|
running: bool,
|
||||||
|
number_width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_modeline(term: &mut Term, top_mode: TopMode, buf: &Buffer) {
|
||||||
|
term.set_cursor_position(buf.height(), 0);
|
||||||
|
|
||||||
|
let bg = match (top_mode, buf.mode()) {
|
||||||
|
(TopMode::Normal, Mode::Normal) => Color::Yellow,
|
||||||
|
(TopMode::Normal, Mode::Insert) => Color::Cyan,
|
||||||
|
(TopMode::Command, _) => Color::Green,
|
||||||
|
};
|
||||||
|
|
||||||
|
term.set_background(bg);
|
||||||
|
term.set_foreground(Color::Black);
|
||||||
|
|
||||||
|
match top_mode {
|
||||||
|
TopMode::Normal => {
|
||||||
|
term.put_byte(b' ');
|
||||||
|
term.put_bytes(buf.mode().as_str());
|
||||||
|
term.put_byte(b' ');
|
||||||
|
|
||||||
|
if buf.is_modified() {
|
||||||
|
term.set_background(Color::Magenta);
|
||||||
|
term.set_foreground(Color::Default);
|
||||||
|
} else {
|
||||||
|
term.set_foreground(Color::Green);
|
||||||
|
term.set_background(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TopMode::Command => {
|
||||||
|
term.put_bytes(b" COMMAND ");
|
||||||
|
|
||||||
|
term.set_foreground(Color::Green);
|
||||||
|
term.set_background(Color::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
term.put_byte(b' ');
|
||||||
|
term.put_bytes(buf.name().map(String::as_str).unwrap_or("<unnamed>"));
|
||||||
|
term.clear(Clear::LineToEnd);
|
||||||
|
|
||||||
|
term.reset_style();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn open<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
|
||||||
|
let config = Config::default();
|
||||||
|
let mut buffer = match path {
|
||||||
|
Some(path) => Buffer::open(path).unwrap(),
|
||||||
|
None => Buffer::empty(),
|
||||||
|
};
|
||||||
|
let term = Term::open();
|
||||||
|
|
||||||
|
let (w, h) = term.size();
|
||||||
|
if config.number {
|
||||||
|
let nw = buffer.number_width() + 2;
|
||||||
|
buffer.resize(&config, nw, w - nw - 1, h - 2);
|
||||||
|
} else {
|
||||||
|
buffer.resize(&config, 0, w - 1, h - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
number_width: buffer.number_width(),
|
||||||
|
top_mode: TopMode::Normal,
|
||||||
|
message: None,
|
||||||
|
command: String::new(),
|
||||||
|
running: true,
|
||||||
|
buffer,
|
||||||
|
term,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer(&self) -> &Buffer {
|
||||||
|
&self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_mut(&mut self) -> &mut Buffer {
|
||||||
|
&mut self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exit(&mut self) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_number(&mut self) -> Result<(), Error> {
|
||||||
|
let start = self.buffer.row_offset();
|
||||||
|
let end = self.buffer.len();
|
||||||
|
|
||||||
|
for i in 0.. {
|
||||||
|
self.term.set_cursor_position(i, 0);
|
||||||
|
|
||||||
|
if i + start == self.buffer.cursor_row() {
|
||||||
|
self.term.set_bright(true);
|
||||||
|
self.term.set_foreground(Color::Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if i + start < end {
|
||||||
|
write!(self.term, " {0:1$} ", i + start + 1, self.number_width).ok();
|
||||||
|
} else {
|
||||||
|
for _ in 0..self.number_width + 2 {
|
||||||
|
self.term.put_byte(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == self.buffer.height() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i + start == self.buffer.cursor_row() {
|
||||||
|
self.term.reset_style();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.term.reset_style();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&mut self) -> Result<(), Error> {
|
||||||
|
if self.buffer.is_dirty() {
|
||||||
|
self.term.clear(Clear::All);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.config.number && self.buffer.is_dirty() {
|
||||||
|
self.display_number()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer.display(&self.config, &mut self.term);
|
||||||
|
|
||||||
|
if let Some(msg) = &self.message {
|
||||||
|
self.term.set_cursor_position(self.buffer.height(), 0);
|
||||||
|
self.term.put_bytes(msg);
|
||||||
|
self.term.flush();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
display_modeline(&mut self.term, self.top_mode, &self.buffer);
|
||||||
|
match self.top_mode {
|
||||||
|
TopMode::Normal => self
|
||||||
|
.buffer
|
||||||
|
.set_terminal_cursor(&self.config, &mut self.term),
|
||||||
|
TopMode::Command => {
|
||||||
|
self.term.set_cursor_position(self.buffer.height() + 1, 0);
|
||||||
|
self.term.put_byte(b':');
|
||||||
|
self.term.put_bytes(self.command.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.term.flush();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_command(&mut self) -> Result<(), Error> {
|
||||||
|
let cmd = self.command.clone();
|
||||||
|
command::execute(self, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_command_key(&mut self, key: char) -> Result<(), Error> {
|
||||||
|
match key {
|
||||||
|
'\n' | '\x0D' => {
|
||||||
|
self.top_mode = TopMode::Normal;
|
||||||
|
self.handle_command()?;
|
||||||
|
}
|
||||||
|
'\x7F' => {
|
||||||
|
if self.command.is_empty() {
|
||||||
|
self.top_mode = TopMode::Normal;
|
||||||
|
} else {
|
||||||
|
self.command.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\x1B' => {
|
||||||
|
self.top_mode = TopMode::Normal;
|
||||||
|
}
|
||||||
|
c if c.is_ascii_graphic() => self.command.push(c),
|
||||||
|
' ' => self.command.push(' '),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_normal_key(&mut self, key: char) -> Result<(), Error> {
|
||||||
|
match key {
|
||||||
|
'\x1B' => {
|
||||||
|
self.buffer.set_mode(&self.config, SetMode::Normal);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
':' => {
|
||||||
|
self.command.clear();
|
||||||
|
self.top_mode = TopMode::Command;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let buffer = &mut self.buffer;
|
||||||
|
if let Some(actions) = self.config.key(Mode::Normal, key) {
|
||||||
|
for &action in actions {
|
||||||
|
command::perform(buffer, &self.config, action)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_insert_key(&mut self, key: char) -> Result<(), Error> {
|
||||||
|
match key {
|
||||||
|
'\x1B' => {
|
||||||
|
self.buffer.set_mode(&self.config, SetMode::Normal);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
key if !key.is_ascii() || key == ' ' || key == '\t' || key.is_ascii_graphic() => {
|
||||||
|
self.buffer.insert(&self.config, key);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let buffer = &mut self.buffer;
|
||||||
|
if let Some(actions) = self.config.key(Mode::Insert, key) {
|
||||||
|
for &action in actions {
|
||||||
|
command::perform(buffer, &self.config, action)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) -> Result<(), Error> {
|
||||||
|
if self.config.number {
|
||||||
|
let nw = self.buffer.number_width();
|
||||||
|
if nw != self.number_width {
|
||||||
|
self.number_width = nw;
|
||||||
|
let nw = nw + 2;
|
||||||
|
let (w, h) = self.term.size();
|
||||||
|
self.buffer.resize(&self.config, nw, w - nw - 1, h - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.display()?;
|
||||||
|
|
||||||
|
let Some(key) = self.term.read_key() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.message.is_some() {
|
||||||
|
self.message = None;
|
||||||
|
if key != ':' {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = match (self.top_mode, self.buffer.mode()) {
|
||||||
|
(TopMode::Normal, Mode::Normal) => self.handle_normal_key(key),
|
||||||
|
(TopMode::Normal, Mode::Insert) => self.handle_insert_key(key),
|
||||||
|
(TopMode::Command, _) => self.handle_command_key(key),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
self.message = Some(format!("Error: {}", e));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup(&mut self) {
|
||||||
|
self.term.clear(Clear::All);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = env::args().collect::<Vec<_>>();
|
||||||
|
if args.len() > 2 {
|
||||||
|
eprintln!("Usage: red [FILE]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Term::is_tty() {
|
||||||
|
eprintln!("Not a tty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = args.get(1);
|
||||||
|
let mut state = State::open(path).unwrap();
|
||||||
|
let error = loop {
|
||||||
|
if !state.running {
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(error) = state.update() {
|
||||||
|
break Some(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
state.cleanup();
|
||||||
|
|
||||||
|
if let Some(error) = error {
|
||||||
|
eprintln!("Error: {:?}", error);
|
||||||
|
}
|
||||||
|
}
|
85
red/src/term/common.rs
Normal file
85
red/src/term/common.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use std::io::{stdin, stdout, Read, Stdin, Stdout, Write};
|
||||||
|
|
||||||
|
use crossterm::{
|
||||||
|
cursor, execute, queue, style,
|
||||||
|
terminal::{
|
||||||
|
self, disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen,
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
},
|
||||||
|
tty::IsTty,
|
||||||
|
ExecutableCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Terminal;
|
||||||
|
|
||||||
|
pub struct Term {
|
||||||
|
stdin: Stdin,
|
||||||
|
stdout: Stdout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Terminal for Term {
|
||||||
|
fn is_tty() -> bool {
|
||||||
|
stdout().is_tty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open() -> Self {
|
||||||
|
let stdin = stdin();
|
||||||
|
let mut stdout = stdout();
|
||||||
|
|
||||||
|
execute!(
|
||||||
|
stdout,
|
||||||
|
EnterAlternateScreen,
|
||||||
|
Clear(ClearType::All),
|
||||||
|
cursor::MoveTo(0, 0)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
enable_raw_mode().unwrap();
|
||||||
|
|
||||||
|
Self { stdin, stdout }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_position(&mut self, row: usize, column: usize) {
|
||||||
|
queue!(self.stdout, cursor::MoveTo(column as _, row as _)).ok();
|
||||||
|
}
|
||||||
|
fn set_cursor_visible(&mut self, visible: bool) {
|
||||||
|
if visible {
|
||||||
|
queue!(self.stdout, cursor::Show).ok();
|
||||||
|
} else {
|
||||||
|
queue!(self.stdout, cursor::Hide).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn size(&self) -> (usize, usize) {
|
||||||
|
let (w, h) = terminal::size().unwrap();
|
||||||
|
(w as _, h as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_bytes<B: AsRef<[u8]>>(&mut self, s: B) {
|
||||||
|
self.stdout.write(s.as_ref()).ok();
|
||||||
|
}
|
||||||
|
fn put_byte(&mut self, ch: u8) {
|
||||||
|
self.put_bytes(&[ch]);
|
||||||
|
}
|
||||||
|
fn flush(&mut self) {
|
||||||
|
self.stdout.flush().ok();
|
||||||
|
}
|
||||||
|
fn clear(&mut self) {
|
||||||
|
queue!(self.stdout, Clear(ClearType::All)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_key(&mut self) -> Option<u8> {
|
||||||
|
let mut buf = [0; 1];
|
||||||
|
let len = self.stdin.read(&mut buf).unwrap();
|
||||||
|
if len != 0 {
|
||||||
|
Some(buf[0])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Term {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
disable_raw_mode().ok();
|
||||||
|
execute!(self.stdout, LeaveAlternateScreen).ok();
|
||||||
|
}
|
||||||
|
}
|
59
red/src/term/mod.rs
Normal file
59
red/src/term/mod.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// #[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
// pub mod common;
|
||||||
|
//
|
||||||
|
// #[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
// pub use common::Term;
|
||||||
|
|
||||||
|
pub mod simple;
|
||||||
|
|
||||||
|
pub use simple::Term;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CursorStyle {
|
||||||
|
Default,
|
||||||
|
Line,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum Color {
|
||||||
|
Black = 0,
|
||||||
|
Red = 1,
|
||||||
|
Green = 2,
|
||||||
|
Yellow = 3,
|
||||||
|
Blue = 4,
|
||||||
|
Magenta = 5,
|
||||||
|
Cyan = 6,
|
||||||
|
White = 7,
|
||||||
|
Default = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Clear {
|
||||||
|
All,
|
||||||
|
LineToEnd,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Terminal {
|
||||||
|
fn is_tty() -> bool;
|
||||||
|
fn open() -> Self;
|
||||||
|
|
||||||
|
// Cursor & size
|
||||||
|
fn set_cursor_position(&mut self, row: usize, column: usize);
|
||||||
|
fn set_cursor_visible(&mut self, visible: bool);
|
||||||
|
fn set_cursor_style(&mut self, style: CursorStyle);
|
||||||
|
fn size(&self) -> (usize, usize);
|
||||||
|
|
||||||
|
// Display
|
||||||
|
fn put_bytes<B: AsRef<[u8]>>(&mut self, s: B);
|
||||||
|
fn put_byte(&mut self, ch: u8);
|
||||||
|
fn set_foreground(&mut self, color: Color);
|
||||||
|
fn set_background(&mut self, color: Color);
|
||||||
|
fn set_bright(&mut self, bright: bool);
|
||||||
|
fn reset_style(&mut self);
|
||||||
|
fn flush(&mut self);
|
||||||
|
fn clear(&mut self, clear: Clear);
|
||||||
|
|
||||||
|
// Input
|
||||||
|
fn read_key(&mut self) -> Option<char>;
|
||||||
|
}
|
285
red/src/term/simple.rs
Normal file
285
red/src/term/simple.rs
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
use std::{
|
||||||
|
io::{stdin, stdout, Read, Stdin, Stdout, Write},
|
||||||
|
mem::MaybeUninit, fmt,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{Clear, Color, CursorStyle, Terminal};
|
||||||
|
|
||||||
|
struct RawMode {
|
||||||
|
#[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
saved_termios: libc::termios,
|
||||||
|
#[cfg(target_os = "yggdrasil")]
|
||||||
|
saved_termios: std::os::yggdrasil::io::TerminalOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Term {
|
||||||
|
stdin: Stdin,
|
||||||
|
stdout: Stdout,
|
||||||
|
raw: RawMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "yggdrasil")]
|
||||||
|
impl RawMode {
|
||||||
|
unsafe fn enter(stdin: &Stdin) -> Option<Self> {
|
||||||
|
use std::os::yggdrasil::io::TerminalOptions;
|
||||||
|
|
||||||
|
let saved_termios = std::os::yggdrasil::io::update_terminal_options(stdin, |_| {
|
||||||
|
TerminalOptions::raw_input()
|
||||||
|
})
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(Self { saved_termios })
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn leave(&self, stdin: &Stdin) {
|
||||||
|
std::os::yggdrasil::io::update_terminal_options(stdin, |_| self.saved_termios.clone()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
impl RawMode {
|
||||||
|
unsafe fn enter(stdin: &Stdin) -> Option<Self> {
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
|
||||||
|
let mut old = MaybeUninit::uninit();
|
||||||
|
|
||||||
|
if libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let old = old.assume_init();
|
||||||
|
let mut new = old;
|
||||||
|
new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
|
||||||
|
new.c_iflag &= !(libc::IGNBRK
|
||||||
|
| libc::BRKINT
|
||||||
|
| libc::PARMRK
|
||||||
|
| libc::ISTRIP
|
||||||
|
| libc::INLCR
|
||||||
|
| libc::IGNCR
|
||||||
|
| libc::ICRNL
|
||||||
|
| libc::IXON);
|
||||||
|
new.c_oflag &= !libc::OPOST;
|
||||||
|
new.c_cflag &= !(libc::PARENB | libc::CSIZE);
|
||||||
|
new.c_cflag |= libc::CS8;
|
||||||
|
|
||||||
|
if libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self { saved_termios: old })
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn leave(&self, stdin: &Stdin) {
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
unsafe fn terminal_size(stdout: &Stdout) -> std::io::Result<(usize, usize)> {
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
let mut size: MaybeUninit<libc::winsize> = MaybeUninit::uninit();
|
||||||
|
if libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) != 0 {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
let size = size.assume_init();
|
||||||
|
Ok((size.ws_col as _, size.ws_row as _))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "yggdrasil")]
|
||||||
|
unsafe fn terminal_size(stdout: &Stdout) -> std::io::Result<(usize, usize)> {
|
||||||
|
use std::os::yggdrasil::io::{DeviceRequest, FdDeviceRequest};
|
||||||
|
let mut req = DeviceRequest::GetTerminalSize(MaybeUninit::uninit());
|
||||||
|
if let Err(_) = stdout.device_request(&mut req) {
|
||||||
|
// Fallback
|
||||||
|
return Ok((60, 20));
|
||||||
|
}
|
||||||
|
let DeviceRequest::GetTerminalSize(size) = req else {
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let size = size.assume_init();
|
||||||
|
Ok((size.columns, size.rows))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReadChar {
|
||||||
|
fn read_char(&mut self) -> Option<char>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadChar for Stdin {
|
||||||
|
fn read_char(&mut self) -> Option<char> {
|
||||||
|
let mut buf = [0; 4];
|
||||||
|
self.read_exact(&mut buf[..1]).ok()?;
|
||||||
|
|
||||||
|
let len = utf8_len_prefix(buf[0])?;
|
||||||
|
|
||||||
|
if len != 0 {
|
||||||
|
self.read_exact(&mut buf[1..=len]).ok()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO optimize
|
||||||
|
let s = core::str::from_utf8(&buf[..len + 1]).ok()?;
|
||||||
|
s.chars().next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn utf8_len_prefix(l: u8) -> Option<usize> {
|
||||||
|
let mask0 = 0b10000000;
|
||||||
|
let val0 = 0;
|
||||||
|
let mask1 = 0b11100000;
|
||||||
|
let val1 = 0b11000000;
|
||||||
|
let mask2 = 0b11110000;
|
||||||
|
let val2 = 0b11100000;
|
||||||
|
let mask3 = 0b11111000;
|
||||||
|
let val3 = 0b11110000;
|
||||||
|
|
||||||
|
if l & mask3 == val3 {
|
||||||
|
Some(3)
|
||||||
|
} else if l & mask2 == val2 {
|
||||||
|
Some(2)
|
||||||
|
} else if l & mask1 == val1 {
|
||||||
|
Some(1)
|
||||||
|
} else if l & mask0 == val0 {
|
||||||
|
Some(0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Term {
|
||||||
|
fn enter_alternate_mode<O: Write>(out: &mut O) {
|
||||||
|
out.write_all(b"\x1B[?1049h").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leave_alternate_mode<O: Write>(out: &mut O) {
|
||||||
|
out.write_all(b"\x1B[?1049l").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_all<O: Write>(out: &mut O) {
|
||||||
|
out.write_all(b"\x1B[2J").ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_line<O: Write>(out: &mut O, what: u32) {
|
||||||
|
out.write_all(format!("\x1B[{}K", what).as_bytes()).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_cursor<O: Write>(out: &mut O, row: usize, column: usize) {
|
||||||
|
out.write_all(format!("\x1B[{};{}f", row + 1, column + 1).as_bytes())
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_style_raw<O: Write>(out: &mut O, style: CursorStyle) {
|
||||||
|
// TODO yggdrasil support for cursor styles
|
||||||
|
#[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
{
|
||||||
|
match style {
|
||||||
|
CursorStyle::Default => out.write_all(b"\x1B[0 q"),
|
||||||
|
CursorStyle::Line => out.write_all(b"\x1B[6 q"),
|
||||||
|
}
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_color<O: Write>(out: &mut O, fgbg: u32, color: Color) {
|
||||||
|
out.write_all(format!("\x1B[{}{}m", fgbg, color as u32).as_bytes())
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Terminal for Term {
|
||||||
|
fn is_tty() -> bool {
|
||||||
|
// TODO
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open() -> Self {
|
||||||
|
let stdin = stdin();
|
||||||
|
let mut stdout = stdout();
|
||||||
|
|
||||||
|
// Set stdin to raw mode
|
||||||
|
let raw = unsafe { RawMode::enter(&stdin).unwrap() }; // unsafe { Self::enable_raw(&stdin).unwrap() };
|
||||||
|
Self::enter_alternate_mode(&mut stdout);
|
||||||
|
Self::clear_all(&mut stdout);
|
||||||
|
Self::move_cursor(&mut stdout, 0, 0);
|
||||||
|
|
||||||
|
Self { stdin, stdout, raw }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_position(&mut self, row: usize, column: usize) {
|
||||||
|
Self::move_cursor(&mut self.stdout, row, column)
|
||||||
|
}
|
||||||
|
fn set_cursor_visible(&mut self, _visible: bool) {}
|
||||||
|
fn set_cursor_style(&mut self, style: CursorStyle) {
|
||||||
|
Self::set_cursor_style_raw(&mut self.stdout, style);
|
||||||
|
}
|
||||||
|
fn size(&self) -> (usize, usize) {
|
||||||
|
unsafe { terminal_size(&self.stdout).unwrap() }
|
||||||
|
// #[cfg(target_os = "yggdrasil")]
|
||||||
|
// {
|
||||||
|
// (80, 30)
|
||||||
|
// }
|
||||||
|
// #[cfg(not(target_os = "yggdrasil"))]
|
||||||
|
// {
|
||||||
|
// (80, 25)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn put_bytes<B: AsRef<[u8]>>(&mut self, s: B) {
|
||||||
|
self.stdout.write_all(s.as_ref()).ok();
|
||||||
|
}
|
||||||
|
fn put_byte(&mut self, ch: u8) {
|
||||||
|
self.put_bytes([ch]);
|
||||||
|
}
|
||||||
|
fn set_foreground(&mut self, color: Color) {
|
||||||
|
Self::set_color(&mut self.stdout, 3, color)
|
||||||
|
}
|
||||||
|
fn set_background(&mut self, color: Color) {
|
||||||
|
Self::set_color(&mut self.stdout, 4, color)
|
||||||
|
}
|
||||||
|
fn set_bright(&mut self, bright: bool) {
|
||||||
|
if bright {
|
||||||
|
self.stdout.write_all(b"\x1B[1m").ok();
|
||||||
|
} else {
|
||||||
|
self.stdout.write_all(b"\x1B[22m").ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn reset_style(&mut self) {
|
||||||
|
self.stdout.write_all(b"\x1B[0m").ok();
|
||||||
|
}
|
||||||
|
fn flush(&mut self) {
|
||||||
|
self.stdout.flush().ok();
|
||||||
|
}
|
||||||
|
fn clear(&mut self, clear: Clear) {
|
||||||
|
match clear {
|
||||||
|
Clear::All => Self::clear_all(&mut self.stdout),
|
||||||
|
Clear::LineToEnd => Self::clear_line(&mut self.stdout, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_key(&mut self) -> Option<char> {
|
||||||
|
self.stdin.read_char()
|
||||||
|
|
||||||
|
// let mut buf = [0; 1];
|
||||||
|
// let len = self.stdin.read(&mut buf).unwrap();
|
||||||
|
// if len != 0 {
|
||||||
|
// Some(buf[0])
|
||||||
|
// } else {
|
||||||
|
// None
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for Term {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
self.put_bytes(s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Term {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
self.raw.leave(&self.stdin);
|
||||||
|
}
|
||||||
|
Self::leave_alternate_mode(&mut self.stdout);
|
||||||
|
}
|
||||||
|
}
|
@ -31,3 +31,7 @@ path = "src/ls.rs"
|
|||||||
[[bin]]
|
[[bin]]
|
||||||
name = "hexd"
|
name = "hexd"
|
||||||
path = "src/hexd.rs"
|
path = "src/hexd.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "colors"
|
||||||
|
path = "src/colors.rs"
|
||||||
|
11
sysutils/src/colors.rs
Normal file
11
sysutils/src/colors.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
fn main() {
|
||||||
|
for bg in 40..=49 {
|
||||||
|
if bg == 48 { continue; }
|
||||||
|
for fg in 30..=39 {
|
||||||
|
if fg == 48 { continue; }
|
||||||
|
print!("\x1B[{}m\x1B[{}m@\x1B[0m", bg, fg);
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user