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"
|
||||
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]]
|
||||
name = "clap"
|
||||
version = "4.3.19"
|
||||
@ -47,12 +71,66 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
@ -69,12 +147,46 @@ dependencies = [
|
||||
"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]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
@ -87,6 +199,18 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -97,12 +221,50 @@ dependencies = [
|
||||
"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]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
@ -121,6 +283,52 @@ dependencies = [
|
||||
"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]]
|
||||
name = "shell"
|
||||
version = "0.1.0"
|
||||
@ -132,16 +340,65 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
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 = [
|
||||
"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 = "sysutils"
|
||||
version = "0.1.0"
|
||||
@ -151,12 +408,169 @@ dependencies = [
|
||||
"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]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "yggdrasil-abi"
|
||||
version = "0.1.0"
|
||||
|
@ -3,7 +3,8 @@ resolver = "1"
|
||||
members = [
|
||||
"init",
|
||||
"shell",
|
||||
"sysutils"
|
||||
"sysutils",
|
||||
"red"
|
||||
]
|
||||
|
||||
[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}/ls ${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}/
|
||||
|
||||
|
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]]
|
||||
name = "hexd"
|
||||
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