red: add red, the text editor

This commit is contained in:
Mark Poliakov 2023-11-18 22:44:11 +02:00
parent 5447306fa6
commit 546010762f
16 changed files with 2545 additions and 4 deletions

420
Cargo.lock generated
View File

@ -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"

View File

@ -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']

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}

View File

@ -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
View 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!();
}
}