diff options
34 files changed, 2622 insertions, 1919 deletions
@@ -3,19 +3,25 @@ version = 3 [[package]] -name = "ansi_term" -version = "0.12.1" +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ - "winapi 0.3.9", + "memchr", ] [[package]] name = "anyhow" -version = "1.0.52" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" +checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" [[package]] name = "arrayvec" @@ -24,140 +30,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] -name = "async-channel" -version = "1.6.1" +name = "async-stream" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ - "concurrent-queue", - "event-listener", + "async-stream-impl", "futures-core", ] [[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-mutex", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" -dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi 0.3.9", -] - -[[package]] -name = "async-lock" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-mutex" -version = "1.4.0" +name = "async-stream-impl" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ - "event-listener", -] - -[[package]] -name = "async-process" -version = "1.3.0" -source = "git+https://github.com/doy/async-process#5e25598d6fcf3865f2b9e106ba049a26a490a884" -dependencies = [ - "async-io", - "blocking", - "cfg-if 1.0.0", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi 0.3.9", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "async-std" -version = "1.10.0" +name = "async-trait" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "num_cpus", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "async-task" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" - -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - -[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -165,14 +69,20 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" @@ -211,26 +121,6 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] name = "byte-tools" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -243,75 +133,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] -name = "cache-padded" -version = "1.2.0" +name = "bytes" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] [[package]] name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "2.34.0" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312" dependencies = [ - "ansi_term", "atty", "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", - "term_size", + "termcolor", + "terminal_size", "textwrap", - "unicode-width", - "vec_map", ] [[package]] -name = "concurrent-queue" -version = "1.2.2" +name = "clap_derive" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" dependencies = [ - "cache-padded", + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "crossbeam-utils" -version = "0.8.6" +name = "console-api" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "cc347c19eb5b940f396ac155822caee6662f850d97306890ac3773ed76c90c5a" dependencies = [ - "cfg-if 1.0.0", - "lazy_static", + "prost", + "prost-types", + "tonic", + "tonic-build", + "tracing-core", ] [[package]] -name = "ctor" -version = "0.1.21" +name = "console-subscriber" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +checksum = "565a7dfea2d10dd0e5c57cc394d5d441b1910960d8c9211ed14135e0e6ec3a20" dependencies = [ - "quote", - "syn", + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures", + "hdrhistogram", + "humantime", + "prost-types", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +dependencies = [ + "cfg-if", + "lazy_static", ] [[package]] @@ -324,10 +260,30 @@ dependencies = [ ] [[package]] -name = "event-listener" -version = "2.5.1" +name = "directories" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "fake-simd" @@ -337,9 +293,9 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fastrand" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -350,98 +306,96 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", - "winapi 0.3.9", + "winapi", ] [[package]] -name = "form_urlencoded" -version = "1.0.1" +name = "fixedbitset" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" dependencies = [ - "matches", - "percent-encoding", + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", ] [[package]] -name = "fsevent" -version = "0.4.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ - "bitflags", - "fsevent-sys", + "matches", + "percent-encoding", ] [[package]] name = "fsevent-sys" -version = "2.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "futures" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ - "bitflags", - "fuchsia-zircon-sys", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -449,19 +403,26 @@ dependencies = [ ] [[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-core", "futures-macro", + "futures-sink", "futures-task", "pin-project-lite", "pin-utils", @@ -478,17 +439,26 @@ dependencies = [ ] [[package]] +name = "getrandom" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] name = "git2" -version = "0.13.25" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +checksum = "6e7d3b96ec1fcaa8431cf04a4f1ef5caafe58d5cf7bcc31f09c1626adddb0ffe" dependencies = [ "bitflags", "libc", "libgit2-sys", "log", - "openssl-probe", - "openssl-sys", "url", ] @@ -499,16 +469,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] -name = "gloo-timers" -version = "0.2.2" +name = "h2" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f16c88aa13d2656ef20d1c042086b8767bbe2bdb62526894275a1b062161b2e" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ - "futures-channel", + "bytes", + "fnv", "futures-core", - "js-sys", - "wasm-bindgen", - "web-sys", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.6.9", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hdrhistogram" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +dependencies = [ + "base64", + "byteorder", + "flate2", + "nom", + "num-traits", ] [[package]] @@ -521,6 +516,12 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -537,7 +538,83 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.9", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", ] [[package]] @@ -552,10 +629,20 @@ dependencies = [ ] [[package]] +name = "indexmap" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] name = "inotify" -version = "0.7.1" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ "bitflags", "inotify-sys", @@ -577,26 +664,20 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "itertools" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ - "libc", + "either", ] [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" @@ -611,31 +692,23 @@ dependencies = [ ] [[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.2" +name = "kqueue" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +checksum = "058a107a784f8be94c7d35c1300f4facced2e93d2fbe5b1452b44e905ddca4a9" dependencies = [ - "winapi 0.2.8", - "winapi-build", + "kqueue-sys", + "libc", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "kqueue-sys" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" dependencies = [ - "log", + "bitflags", + "libc", ] [[package]] @@ -645,55 +718,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] name = "libc" -version = "0.2.112" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "libgit2-sys" -version = "0.12.26+1.3.0" +version = "0.13.1+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +checksum = "43e598aa7a4faedf1ea1b4608f582b06f0f40211eec551b7ef36019ae3f62def" dependencies = [ "cc", "libc", - "libssh2-sys", "libz-sys", - "openssl-sys", "pkg-config", ] [[package]] -name = "libssh2-sys" -version = "0.2.23" +name = "libz-sys" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" +checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" dependencies = [ "cc", "libc", - "libz-sys", - "openssl-sys", "pkg-config", "vcpkg", ] [[package]] -name = "libz-sys" -version = "1.1.3" +name = "lock_api" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "scopeguard", ] [[package]] @@ -702,8 +762,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 1.0.0", - "value-bag", + "cfg-if", ] [[package]] @@ -740,57 +799,59 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.6.23" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow", - "net2", - "slab", - "winapi 0.2.8", + "adler", + "autocfg", ] [[package]] -name = "mio-extras" -version = "2.0.6" +name = "mio" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ - "lazycell", + "libc", "log", - "mio", - "slab", + "miow", + "ntapi", + "winapi", ] [[package]] name = "miow" -version = "0.2.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "winapi", ] [[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] name = "nbsh" version = "0.1.0" dependencies = [ "anyhow", - "async-std", "bincode", - "blocking", - "futures-lite", + "bytes", + "clap", + "console-subscriber", + "directories", "futures-util", "git2", "glob", @@ -799,61 +860,77 @@ dependencies = [ "nix", "notify", "once_cell", - "paw", "pest", "pest_derive", "pty-process", "serde", - "signal-hook-async-std", - "structopt", "terminal_size", "textmode", "time", + "tokio", + "tokio-stream", + "tokio-util 0.7.0", + "toml", "unicode-width", "users", "vt100", ] [[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +name = "nix" +version = "0.23.1" +source = "git+https://github.com/nix-rust/nix#9312f1c410e7390f9ccb9ea8f255e09b4bb2a0ee" dependencies = [ - "cfg-if 0.1.10", + "bitflags", + "cfg-if", "libc", - "winapi 0.3.9", + "memoffset", ] [[package]] -name = "nix" -version = "0.23.1" +name = "nom" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", + "memchr", + "minimal-lexical", + "version_check", ] [[package]] name = "notify" -version = "4.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" +version = "5.0.0-pre.13" +source = "git+https://github.com/notify-rs/notify#b1802ecc8051b5c890ae05483513c75a71834f93" dependencies = [ "bitflags", + "crossbeam-channel", "filetime", - "fsevent", "fsevent-sys", "inotify", + "kqueue", "libc", "mio", - "mio-extras", "walkdir", - "winapi 0.3.9", + "winapi", +] + +[[package]] +name = "ntapi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", ] [[package]] @@ -867,10 +944,19 @@ dependencies = [ ] [[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + +[[package]] name = "once_cell" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "opaque-debug" @@ -879,58 +965,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "os_str_bytes" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", + "memchr", ] [[package]] -name = "parking" -version = "2.0.0" +name = "parking_lot" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "paw" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09c0fc9b564dbc3dc2ed7c92c0c144f4de340aa94514ce2b446065417c4084e9" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ - "paw-attributes", - "paw-raw", + "lock_api", + "parking_lot_core", ] [[package]] -name = "paw-attributes" -version = "1.0.2" +name = "parking_lot_core" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f35583365be5d148e959284f42526841917b7bfa09e2d1a7ad5dde2cf0eaa39" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", ] [[package]] -name = "paw-raw" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0b59668fe80c5afe998f0c0bf93322bf2cd66cafeeb80581f291716f3467f2" - -[[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -980,6 +1046,36 @@ dependencies = [ ] [[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "pin-project-lite" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -998,17 +1094,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" [[package]] -name = "polling" -version = "2.2.0" +name = "ppv-lite86" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "log", - "wepoll-ffi", - "winapi 0.3.9", -] +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro-error" @@ -1044,36 +1133,159 @@ dependencies = [ ] [[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +dependencies = [ + "bytes", + "heck 0.3.3", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes", + "prost", +] + +[[package]] name = "pty-process" version = "0.2.0" -source = "git+https://github.com/doy/pty-process#ebcf5f15081f6a84c861eb2aecbf962396a88695" +source = "git+https://github.com/doy/pty-process#b2733e64d900ac237211360848326f3c4caa23f5" dependencies = [ - "async-io", - "async-process", - "futures-io", "libc", "nix", + "tokio", ] [[package]] name = "quote" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] [[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" dependencies = [ "bitflags", ] [[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1083,19 +1295,25 @@ dependencies = [ ] [[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] name = "serde" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.133" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -1103,6 +1321,17 @@ dependencies = [ ] [[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] name = "sha-1" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1115,25 +1344,12 @@ dependencies = [ ] [[package]] -name = "signal-hook" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-async-std" -version = "0.2.2" +name = "sharded-slab" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4aa94397e2023af5b7cff5b8d4785e935cfb77f0e4aab0cae3b26258ace556" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "async-io", - "futures-lite", - "libc", - "signal-hook", + "lazy_static", ] [[package]] @@ -1152,65 +1368,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.25" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" -dependencies = [ - "clap", - "lazy_static", - "paw", - "structopt-derive", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "structopt-derive" -version = "0.4.18" +name = "syn" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ - "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "unicode-xid", ] [[package]] -name = "syn" -version = "1.0.85" +name = "tempfile" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] -name = "term_size" -version = "0.3.2" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "libc", - "winapi 0.3.9", + "winapi-util", ] [[package]] @@ -1220,44 +1430,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "textmode" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf9ecdb23aae465624a900ae8c795e17f46e081b8454f8ea5b3b5c27a9e7884" +source = "git+https://github.com/doy/textmode#193e1963afc4e9e78122573cd5b9831f9a847345" dependencies = [ - "blocking", - "futures-lite", - "itoa 1.0.1", + "itoa", "nix", "terminal_size", + "tokio", "vt100", ] [[package]] name = "textwrap" -version = "0.11.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" dependencies = [ - "term_size", - "unicode-width", + "terminal_size", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", ] [[package]] name = "time" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ - "itoa 0.4.8", + "itoa", "libc", + "num_threads", + "time-macros", ] [[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + +[[package]] name = "tinyvec" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1273,6 +1497,232 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] +name = "tokio" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "tracing", + "winapi", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +dependencies = [ + "async-stream", + "async-trait", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util 0.6.9", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +dependencies = [ + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tower" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util 0.7.0", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +dependencies = [ + "lazy_static", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] name = "typenum" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1301,9 +1751,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" @@ -1346,14 +1796,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] -name = "value-bag" -version = "1.0.0-alpha.8" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" -dependencies = [ - "ctor", - "version_check", -] +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" @@ -1362,12 +1808,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1379,7 +1819,7 @@ version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7541312ce0411d878458abf25175d878e8edc38f9f12ee8eed1d65870cacf540" dependencies = [ - "itoa 1.0.1", + "itoa", "log", "unicode-width", "vte", @@ -1407,156 +1847,113 @@ dependencies = [ ] [[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] name = "walkdir" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", - "winapi 0.3.9", + "winapi", "winapi-util", ] [[package]] -name = "wasm-bindgen" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" +name = "want" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "bumpalo", - "lazy_static", "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", + "try-lock", ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.28" +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "wasm-bindgen", - "web-sys", -] +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" +name = "which" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "either", + "lazy_static", + "libc", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "web-sys" -version = "0.3.55" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "winapi-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "cc", + "winapi", ] [[package]] -name = "winapi" -version = "0.2.8" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-sys" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", ] [[package]] -name = "winapi-build" -version = "0.1.1" +name = "windows_aarch64_msvc" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_i686_gnu" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows_i686_msvc" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi 0.3.9", -] +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" [[package]] -name = "ws2_32-sys" -version = "0.2.1" +name = "windows_x86_64_msvc" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" @@ -6,34 +6,42 @@ edition = "2021" license = "MIT" [dependencies] -anyhow = "1.0.52" -async-std = { version = "1.10.0", features = ["unstable"] } +anyhow = "1.0.55" bincode = "1.3.3" -blocking = "1.1.0" -futures-lite = "1.12.0" -futures-util = "0.3.19" -git2 = "0.13.25" +bytes = "1.1.0" +clap = { version = "3.1.5", features = ["wrap_help", "derive"] } +directories = "4.0.1" +futures-util = "0.3.21" +git2 = { version = "0.14.1", default-features = false } glob = "0.3.0" hostname = "0.3.1" -libc = "0.2.112" +libc = "0.2.119" nix = "0.23.1" -notify = "4.0.17" -once_cell = "1.9.0" -paw = "1.0.0" +notify = "5.0.0-pre.13" +once_cell = "1.10.0" pest = "2.1.3" pest_derive = "2.1.0" pty-process = { version = "0.2.0", features = ["async"] } -serde = { version = "1.0.133", features = ["derive"] } -signal-hook-async-std = "0.2.2" -structopt = { version = "0.3.25", features = ["paw", "wrap_help"] } +serde = { version = "1.0.136", features = ["derive"] } terminal_size = "0.1.17" textmode = { version = "0.3.0", features = ["async"] } -time = { version = "0.3.5", features = ["formatting", "parsing"] } +time = { version = "0.3.7", features = ["formatting", "parsing"] } +tokio = { version = "1.17.0", features = ["full"] } +tokio-stream = { version = "0.1.8", features = ["io-util"] } +tokio-util = { version = "0.7.0", features = ["io"] } +toml = "0.5.8" unicode-width = "0.1.9" users = "0.11.0" vt100 = "0.15.1" +[target.'cfg(nbsh_tokio_console)'.dependencies] +console-subscriber = "0.1.3" + [patch.crates-io] -# https://github.com/smol-rs/async-process/pull/19 -async-process = { git = "https://github.com/doy/async-process" } +nix = { git = "https://github.com/nix-rust/nix" } +notify = { git = "https://github.com/notify-rs/notify" } pty-process = { git = "https://github.com/doy/pty-process" } +textmode = { git = "https://github.com/doy/textmode" } + +[dev-dependencies] +time = { version = "0.3.7", features = ["macros"] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f5641f2 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +build: + cargo build +.PHONY: build + +run: + cargo run +.PHONY: run + +console: + RUSTFLAGS="--cfg tokio_unstable --cfg nbsh_tokio_console" cargo run +.PHONY: console + +release: + cargo build --release +.PHONY: release + +run-release: + cargo run --release +.PHONY: run-release + +console-release: + RUSTFLAGS="--cfg tokio_unstable --cfg nbsh_tokio_console" cargo run --release +.PHONY: console-release @@ -10,5 +10,5 @@ unsound = "deny" [bans] [licenses] -allow = ["MIT", "Apache-2.0"] +allow = ["MIT", "Apache-2.0", "ISC", "CC0-1.0"] copyleft = "deny" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..08fa002 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,25 @@ +use crate::prelude::*; + +#[derive(serde::Deserialize, Default, Debug)] +pub struct Config { + aliases: + std::collections::HashMap<std::path::PathBuf, crate::parse::ast::Exe>, +} + +impl Config { + pub fn load() -> Result<Self> { + let file = crate::dirs::config_file(); + if std::fs::metadata(&file).is_ok() { + Ok(toml::from_slice(&std::fs::read(&file)?)?) + } else { + Ok(Self::default()) + } + } + + pub fn alias_for( + &self, + path: &std::path::Path, + ) -> Option<&crate::parse::ast::Exe> { + self.aliases.get(path) + } +} diff --git a/src/dirs.rs b/src/dirs.rs new file mode 100644 index 0000000..2ffbb33 --- /dev/null +++ b/src/dirs.rs @@ -0,0 +1,20 @@ +static PROJECT_DIRS: once_cell::sync::Lazy<directories::ProjectDirs> = + once_cell::sync::Lazy::new(|| { + directories::ProjectDirs::from("", "", "nbsh").unwrap() + }); + +pub fn config_file() -> std::path::PathBuf { + config_dir().join("config.toml") +} + +pub fn history_file() -> std::path::PathBuf { + data_dir().join("history") +} + +fn config_dir() -> std::path::PathBuf { + PROJECT_DIRS.config_dir().to_path_buf() +} + +fn data_dir() -> std::path::PathBuf { + PROJECT_DIRS.data_dir().to_path_buf() +} @@ -16,7 +16,7 @@ const __NBSH_LATEST_STATUS: &str = "__NBSH_LATEST_STATUS"; const __NBSH_PREV_PWD: &str = "__NBSH_PREV_PWD"; impl Env { - pub fn new() -> anyhow::Result<Self> { + pub fn new() -> Result<Self> { let pwd = std::env::current_dir()?; Ok(Self::V0(V0 { pwd: pwd.clone(), @@ -26,7 +26,7 @@ impl Env { })) } - pub fn new_from_env() -> anyhow::Result<Self> { + pub fn new_from_env() -> Result<Self> { let pwd = std::env::current_dir()?; Ok(Self::V0(V0 { pwd: pwd.clone(), @@ -111,7 +111,7 @@ impl Env { } } - pub fn update(&mut self) -> anyhow::Result<()> { + pub fn update(&mut self) -> Result<()> { let idx = self.idx(); let status = self.latest_status(); let prev_pwd = self.prev_pwd(); diff --git a/src/format.rs b/src/format.rs index 55757c4..115ee6c 100644 --- a/src/format.rs +++ b/src/format.rs @@ -23,8 +23,16 @@ pub fn exit_status(status: std::process::ExitStatus) -> String { } pub fn time(time: time::OffsetDateTime) -> String { - let format = - time::format_description::parse("[hour]:[minute]:[second]").unwrap(); + let format = if time::OffsetDateTime::now_utc() - time + > std::time::Duration::from_secs(60 * 60 * 24) + { + time::format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second]", + ) + .unwrap() + } else { + time::format_description::parse("[hour]:[minute]:[second]").unwrap() + }; time.format(&format).unwrap() } diff --git a/src/history.pest b/src/history.pest new file mode 100644 index 0000000..67597d1 --- /dev/null +++ b/src/history.pest @@ -0,0 +1,5 @@ +time = @{ ASCII_DIGIT+ } +duration = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? } +command = @{ ANY* } + +line = ${ SOI ~ (": " ~ time ~ ":" ~ duration ~ ";")? ~ command ~ "\n"? ~ EOI } diff --git a/src/info.rs b/src/info.rs index bd94205..6a5ad4f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,12 +1,14 @@ -pub fn user() -> anyhow::Result<String> { +use crate::prelude::*; + +pub fn user() -> Result<String> { Ok(users::get_current_username() - .ok_or_else(|| anyhow::anyhow!("couldn't get username"))? + .ok_or_else(|| anyhow!("couldn't get username"))? .to_string_lossy() .into_owned()) } #[allow(clippy::unnecessary_wraps)] -pub fn prompt_char() -> anyhow::Result<String> { +pub fn prompt_char() -> Result<String> { if users::get_current_uid() == 0 { Ok("#".into()) } else { @@ -14,7 +16,7 @@ pub fn prompt_char() -> anyhow::Result<String> { } } -pub fn hostname() -> anyhow::Result<String> { +pub fn hostname() -> Result<String> { let mut hostname = hostname::get()?.to_string_lossy().into_owned(); if let Some(idx) = hostname.find('.') { hostname.truncate(idx); @@ -23,7 +25,7 @@ pub fn hostname() -> anyhow::Result<String> { } #[allow(clippy::unnecessary_wraps)] -pub fn time(offset: time::UtcOffset) -> anyhow::Result<String> { +pub fn time(offset: time::UtcOffset) -> Result<String> { Ok(crate::format::time( time::OffsetDateTime::now_utc().to_offset(offset), )) @@ -33,6 +35,17 @@ pub fn pid() -> String { nix::unistd::getpid().to_string() } +#[cfg(target_os = "linux")] +#[allow(clippy::unnecessary_wraps)] +pub fn current_exe() -> Result<std::path::PathBuf> { + Ok("/proc/self/exe".into()) +} + +#[cfg(not(target_os = "linux"))] +pub fn current_exe() -> Result<std::path::PathBuf> { + Ok(std::env::current_exe()?) +} + // the time crate is currently unable to get the local offset on unix due to // soundness concerns, so we have to do it manually/: // diff --git a/src/main.rs b/src/main.rs index a7d3f4b..d6b2725 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ #![warn(clippy::get_unwrap)] #![allow(clippy::cognitive_complexity)] #![allow(clippy::missing_const_for_fn)] +#![allow(clippy::option_option)] #![allow(clippy::similar_names)] #![allow(clippy::struct_excessive_bools)] #![allow(clippy::too_many_arguments)] @@ -15,10 +16,11 @@ // just get a compilation failure #![allow(clippy::future_not_send)] +mod config; +mod dirs; mod env; mod format; mod info; -mod mutex; mod parse; mod prelude; mod runner; @@ -26,35 +28,40 @@ mod shell; use prelude::*; -#[derive(structopt::StructOpt)] -#[structopt(about = "NoteBook SHell")] +use clap::Parser as _; + +#[derive(clap::Parser)] +#[clap(about = "NoteBook SHell")] struct Opt { - #[structopt(short = "c")] + #[clap(short = 'c')] command: Option<String>, - #[structopt(long)] + #[clap(long)] status_fd: Option<std::os::unix::io::RawFd>, } -async fn async_main(opt: Opt) -> anyhow::Result<i32> { +#[tokio::main] +async fn async_main(opt: Opt) -> Result<i32> { if let Some(command) = opt.command { - let shell_write = opt.status_fd.and_then(|fd| { + let mut shell_write = opt.status_fd.and_then(|fd| { nix::sys::stat::fstat(fd).ok().map(|_| { // Safety: we don't create File instances for or read/write // data on this fd anywhere else - unsafe { async_std::fs::File::from_raw_fd(fd) } + unsafe { tokio::fs::File::from_raw_fd(fd) } }) }); - return runner::run(&command, shell_write.as_ref()).await; + return runner::main(command, &mut shell_write).await; } + #[cfg(nbsh_tokio_console)] + console_subscriber::init(); + shell::main().await } -#[paw::main] -fn main(opt: Opt) { - match async_std::task::block_on(async_main(opt)) { +fn main() { + match async_main(Opt::parse()) { Ok(code) => { std::process::exit(code); } diff --git a/src/mutex.rs b/src/mutex.rs deleted file mode 100644 index df28ffc..0000000 --- a/src/mutex.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub type Mutex<T> = async_std::sync::Arc<async_std::sync::Mutex<T>>; -pub type Guard<T> = async_std::sync::MutexGuardArc<T>; - -pub fn new<T>(t: T) -> async_std::sync::Arc<async_std::sync::Mutex<T>> { - async_std::sync::Arc::new(async_std::sync::Mutex::new(t)) -} - -pub fn clone<T>(m: &Mutex<T>) -> Mutex<T> { - async_std::sync::Arc::clone(m) -} diff --git a/src/parse/ast.rs b/src/parse/ast.rs index e2d5840..5bceed5 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -15,7 +15,7 @@ impl Commands { pub fn parse(full_cmd: &str) -> Result<Self, super::Error> { Ok(Self::build_ast( Shell::parse(Rule::line, full_cmd) - .map_err(|e| super::Error::new(full_cmd, e))? + .map_err(|e| super::Error::new(full_cmd.to_string(), e))? .next() .unwrap() .into_inner() @@ -90,14 +90,14 @@ pub struct Pipeline { } impl Pipeline { - pub async fn eval(self, env: &Env) -> anyhow::Result<super::Pipeline> { + pub async fn eval(self, env: &Env) -> Result<super::Pipeline> { Ok(super::Pipeline { exes: self .exes .into_iter() .map(|exe| exe.eval(env)) .collect::<futures_util::stream::FuturesOrdered<_>>() - .collect::<Result<_, _>>() + .try_collect() .await?, }) } @@ -117,14 +117,14 @@ impl Pipeline { } #[derive(Debug, Clone, PartialEq, Eq)] -struct Exe { +pub struct Exe { exe: Word, args: Vec<Word>, redirects: Vec<Redirect>, } impl Exe { - async fn eval(self, env: &Env) -> anyhow::Result<super::Exe> { + pub async fn eval(self, env: &Env) -> Result<super::Exe> { let exe = self.exe.eval(env).await?; assert_eq!(exe.len(), 1); // TODO let exe = &exe[0]; @@ -137,7 +137,7 @@ impl Exe { arg.eval(env).await.map(IntoIterator::into_iter) }) .collect::<futures_util::stream::FuturesOrdered<_>>() - .collect::<Result<Vec<_>, _>>() + .try_collect::<Vec<_>>() .await? .into_iter() .flatten() @@ -147,11 +147,20 @@ impl Exe { .into_iter() .map(|arg| arg.eval(env)) .collect::<futures_util::stream::FuturesOrdered<_>>() - .collect::<Result<_, _>>() + .try_collect() .await?, }) } + pub fn parse(s: &str) -> Result<Self, super::Error> { + Ok(Self::build_ast( + Shell::parse(Rule::exe, s) + .map_err(|e| super::Error::new(s.to_string(), e))? + .next() + .unwrap(), + )) + } + fn build_ast(pair: pest::iterators::Pair<Rule>) -> Self { assert!(matches!(pair.as_rule(), Rule::subshell | Rule::exe)); if matches!(pair.as_rule(), Rule::subshell) { @@ -162,7 +171,7 @@ impl Exe { return Self { exe: Word { parts: vec![WordPart::SingleQuoted( - std::env::current_exe() + crate::info::current_exe() .unwrap() .to_str() .unwrap() @@ -206,13 +215,43 @@ impl Exe { } } +impl<'de> serde::Deserialize<'de> for Exe { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + struct Visitor; + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = Exe; + + fn expecting( + &self, + f: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + f.write_str("a command") + } + + fn visit_str<E>( + self, + value: &str, + ) -> std::result::Result<Self::Value, E> + where + E: serde::de::Error, + { + Exe::parse(value).map_err(serde::de::Error::custom) + } + } + deserializer.deserialize_string(Visitor) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Word { parts: Vec<WordPart>, } impl Word { - pub async fn eval(self, env: &Env) -> anyhow::Result<Vec<String>> { + pub async fn eval(self, env: &Env) -> Result<Vec<String>> { let mut opts = glob::MatchOptions::new(); opts.require_literal_separator = true; opts.require_literal_leading_dot = true; @@ -330,12 +369,12 @@ impl WordPart { match self { Self::Alternation(_) => unreachable!(), Self::Substitution(commands) => { - let mut cmd = async_std::process::Command::new( - std::env::current_exe().unwrap(), + let mut cmd = tokio::process::Command::new( + crate::info::current_exe().unwrap(), ); cmd.args(&["-c", &commands]); - cmd.stdin(async_std::process::Stdio::inherit()); - cmd.stderr(async_std::process::Stdio::inherit()); + cmd.stdin(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); let mut out = String::from_utf8(cmd.output().await.unwrap().stdout) .unwrap(); @@ -408,15 +447,20 @@ impl Redirect { let mut iter = pair.into_inner(); let prefix = iter.next().unwrap().as_str(); - let (from, dir) = if let Some(from) = prefix.strip_suffix(">>") { - (from, super::Direction::Append) - } else if let Some(from) = prefix.strip_suffix('>') { - (from, super::Direction::Out) - } else if let Some(from) = prefix.strip_suffix('<') { - (from, super::Direction::In) - } else { - unreachable!() - }; + let (from, dir) = prefix.strip_suffix(">>").map_or_else( + || { + prefix.strip_suffix('>').map_or_else( + || { + ( + prefix.strip_suffix('<').unwrap(), + super::Direction::In, + ) + }, + |from| (from, super::Direction::Out), + ) + }, + |from| (from, super::Direction::Append), + ); let from = if from.is_empty() { match dir { super::Direction::In => 0, @@ -431,7 +475,7 @@ impl Redirect { Self { from, to, dir } } - async fn eval(self, env: &Env) -> anyhow::Result<super::Redirect> { + async fn eval(self, env: &Env) -> Result<super::Redirect> { let to = if self.to.parts.len() == 1 { if let WordPart::Bareword(s) = &self.to.parts[0] { if let Some(fd) = s.strip_prefix('&') { @@ -509,7 +553,7 @@ fn parse_fd(s: &str) -> std::os::unix::io::RawFd { } } -fn expand_home(dir: &str) -> anyhow::Result<String> { +fn expand_home(dir: &str) -> Result<String> { if dir.starts_with('~') { let path: std::path::PathBuf = dir.into(); if let std::path::Component::Normal(prefix) = diff --git a/src/parse.rs b/src/parse/mod.rs index cc6d92b..e2b7ec0 100644 --- a/src/parse.rs +++ b/src/parse/mod.rs @@ -11,7 +11,7 @@ impl Pipeline { } } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Exe { exe: std::path::PathBuf, args: Vec<String>, @@ -27,6 +27,16 @@ impl Exe { &self.args } + pub fn append(&mut self, other: Self) { + let Self { + exe: _exe, + args, + redirects, + } = other; + self.args.extend(args); + self.redirects.extend(redirects); + } + pub fn redirects(&self) -> &[Redirect] { &self.redirects } @@ -106,11 +116,8 @@ pub struct Error { } impl Error { - fn new(input: &str, e: pest::error::Error<ast::Rule>) -> Self { - Self { - input: input.to_string(), - e, - } + fn new(input: String, e: pest::error::Error<ast::Rule>) -> Self { + Self { input, e } } } diff --git a/src/parse/test_ast.rs b/src/parse/test_ast.rs index 09d772b..a1f83dd 100644 --- a/src/parse/test_ast.rs +++ b/src/parse/test_ast.rs @@ -157,13 +157,23 @@ macro_rules! eval_eq { _ => continue, }; assert_eq!( - async_std::task::block_on(pipeline.eval(&$env)).unwrap(), + pipeline.eval(&$env).await.unwrap(), expected.remove(0) ); } }}; } +macro_rules! deserialize_eq { + ($line:literal, $parsed:expr) => {{ + use serde::de::IntoDeserializer as _; + use serde::Deserialize as _; + let exe: Result<_, serde::de::value::Error> = + Exe::deserialize($line.into_deserializer()); + assert_eq!(exe.unwrap(), $parsed); + }}; +} + macro_rules! eval_fails { ($line:literal, $env:expr) => {{ let ast = Commands::parse($line).unwrap(); @@ -175,7 +185,7 @@ macro_rules! eval_fails { } _ => continue, }; - if async_std::task::block_on(pipeline.eval(&$env)).is_err() { + if pipeline.eval(&$env).await.is_err() { fail = true; } } @@ -206,7 +216,7 @@ fn test_basic() { ); // XXX this parse may change in the future - let exe = std::env::current_exe() + let exe = crate::info::current_exe() .unwrap() .into_os_string() .into_string() @@ -223,6 +233,9 @@ fn test_basic() { ) )) ); + + parse_eq!("foo ''", cs!(p!((0, 6), e!(w!("foo"), w!())))); + parse_eq!("foo \"\"", cs!(p!((0, 6), e!(w!("foo"), w!())))); } #[test] @@ -426,8 +439,9 @@ fn test_alternation() { ); } +#[tokio::main] #[test] -fn test_eval_alternation() { +async fn test_eval_alternation() { let mut env = Env::new().unwrap(); env.set_var("HOME", "/home/test"); env.set_var("foo", "value-of-foo"); @@ -464,8 +478,9 @@ fn test_eval_alternation() { ); } +#[tokio::main] #[test] -fn test_eval_glob() { +async fn test_eval_glob() { let env = Env::new().unwrap(); eval_eq!( @@ -484,3 +499,9 @@ fn test_eval_glob() { eval_fails!("echo *.doesnotexist", env); eval_fails!("echo *.{toml,doesnotexist}", env); } + +#[test] +fn test_deserialize() { + deserialize_eq!("foo", e!(w!("foo"))); + deserialize_eq!("foo bar baz", e!(w!("foo"), w!("bar"), w!("baz"))); +} diff --git a/src/prelude.rs b/src/prelude.rs index 6789a1f..bc48955 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,11 +1,51 @@ pub use crate::env::Env; +pub use anyhow::{anyhow, Result}; -pub use async_std::io::{ReadExt as _, WriteExt as _}; -pub use async_std::stream::StreamExt as _; -pub use futures_lite::future::FutureExt as _; +pub use std::io::{Read as _, Write as _}; + +pub use futures_util::future::FutureExt as _; +pub use futures_util::stream::StreamExt as _; +pub use futures_util::stream::TryStreamExt as _; +pub use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; -pub use async_std::os::unix::process::CommandExt as _; pub use std::os::unix::ffi::{OsStrExt as _, OsStringExt as _}; pub use std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _}; pub use std::os::unix::process::ExitStatusExt as _; pub use users::os::unix::UserExt as _; + +pub use ext::Result as _; + +mod ext { + pub trait Result { + type T; + type E; + + fn allow(self, allow_e: Self::E) -> Self; + fn allow_with(self, allow_e: Self::E, default_t: Self::T) -> Self; + } + + impl<T, E> Result for std::result::Result<T, E> + where + T: std::default::Default, + E: std::cmp::PartialEq, + { + type T = T; + type E = E; + + fn allow(self, allow_e: Self::E) -> Self { + self.or_else(|e| { + if e == allow_e { + Ok(std::default::Default::default()) + } else { + Err(e) + } + }) + } + + fn allow_with(self, allow_e: Self::E, default_t: Self::T) -> Self { + self.or_else( + |e| if e == allow_e { Ok(default_t) } else { Err(e) }, + ) + } + } +} diff --git a/src/runner/builtins/command.rs b/src/runner/builtins/command.rs index e3a3fce..16d8b40 100644 --- a/src/runner/builtins/command.rs +++ b/src/runner/builtins/command.rs @@ -38,8 +38,8 @@ impl Command { self.cfg.io.set_stderr(fh); } - // Safety: see pre_exec in async_std::os::unix::process::CommandExt (this - // is just a wrapper) + // Safety: see pre_exec in tokio::process::Command (this is just a + // wrapper) pub unsafe fn pre_exec<F>(&mut self, f: F) where F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, @@ -51,7 +51,7 @@ impl Command { self.cfg.io.apply_redirects(redirects); } - pub fn spawn(self, env: &Env) -> anyhow::Result<Child> { + pub fn spawn(self, env: &Env) -> Result<Child> { let Self { f, exe, cfg } = self; (f)(exe, env, cfg) } @@ -73,8 +73,8 @@ impl Cfg { &self.io } - // Safety: see pre_exec in async_std::os::unix::process::CommandExt (this - // is just a wrapper) + // Safety: see pre_exec in tokio::process::Command (this is just a + // wrapper) pub unsafe fn pre_exec<F>(&mut self, f: F) where F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, @@ -187,10 +187,10 @@ impl Io { } } - pub async fn read_line_stdin(&self) -> anyhow::Result<(String, bool)> { - let mut buf = vec![]; - if let Some(fh) = self.stdin() { - if let File::In(fh) = &*fh { + pub fn read_line_stdin(&self) -> Result<(String, bool)> { + let mut line = vec![]; + if let Some(file) = self.stdin() { + if let File::In(fh) = &*file { // we have to read only a single character at a time here // because stdin needs to be shared across all commands in the // command list, some of which may be builtins and others of @@ -199,36 +199,27 @@ impl Io { // no longer be available to the next command, since we have // them buffered in memory rather than them being on the stdin // pipe. - let mut c = [0_u8]; - loop { - match (&*fh).read_exact(&mut c[..]).await { - Ok(()) => {} - Err(e) => { - if e.kind() == std::io::ErrorKind::UnexpectedEof { - break; - } - return Err(e.into()); - } - } - if c[0] == b'\n' { + for byte in fh.bytes() { + let byte = byte?; + line.push(byte); + if byte == b'\n' { break; } - buf.push(c[0]); } } } - let done = buf.is_empty(); - let mut buf = String::from_utf8(buf).unwrap(); - if buf.ends_with('\n') { - buf.truncate(buf.len() - 1); + let done = line.is_empty(); + let mut line = String::from_utf8(line).unwrap(); + if line.ends_with('\n') { + line.truncate(line.len() - 1); } - Ok((buf, done)) + Ok((line, done)) } - pub async fn write_stdout(&self, buf: &[u8]) -> anyhow::Result<()> { - if let Some(fh) = self.stdout() { - if let File::Out(fh) = &*fh { - Ok((&*fh).write_all(buf).await.map(|_| ())?) + pub fn write_stdout(&self, buf: &[u8]) -> Result<()> { + if let Some(file) = self.stdout() { + if let File::Out(fh) = &*file { + Ok((&*fh).write_all(buf)?) } else { Ok(()) } @@ -237,10 +228,10 @@ impl Io { } } - pub async fn write_stderr(&self, buf: &[u8]) -> anyhow::Result<()> { - if let Some(fh) = self.stderr() { - if let File::Out(fh) = &*fh { - Ok((&*fh).write_all(buf).await.map(|_| ())?) + pub fn write_stderr(&self, buf: &[u8]) -> Result<()> { + if let Some(file) = self.stderr() { + if let File::Out(fh) = &*file { + Ok((&*fh).write_all(buf)?) } else { Ok(()) } @@ -299,19 +290,19 @@ impl Drop for Io { #[derive(Debug)] pub enum File { - In(async_std::fs::File), - Out(async_std::fs::File), + In(std::fs::File), + Out(std::fs::File), } impl File { // Safety: fd must not be owned by any other File object pub unsafe fn input(fd: std::os::unix::io::RawFd) -> Self { - Self::In(async_std::fs::File::from_raw_fd(fd)) + Self::In(std::fs::File::from_raw_fd(fd)) } // Safety: fd must not be owned by any other File object pub unsafe fn output(fd: std::os::unix::io::RawFd) -> Self { - Self::Out(async_std::fs::File::from_raw_fd(fd)) + Self::Out(std::fs::File::from_raw_fd(fd)) } fn maybe_drop(file: std::sync::Arc<Self>) { @@ -339,59 +330,43 @@ impl std::os::unix::io::IntoRawFd for File { } } -pub struct Child<'a> { - fut: std::pin::Pin< - Box< - dyn std::future::Future<Output = std::process::ExitStatus> - + Sync - + Send - + 'a, - >, - >, - wrapped_child: Option<Box<crate::runner::Child<'a>>>, +pub enum Child { + Task(tokio::task::JoinHandle<std::process::ExitStatus>), + Wrapped(Box<crate::runner::Child>), } -impl<'a> Child<'a> { - pub fn new_fut<F>(fut: F) -> Self +impl Child { + pub fn new_task<F>(f: F) -> Self where - F: std::future::Future<Output = std::process::ExitStatus> - + Sync - + Send - + 'a, + F: FnOnce() -> std::process::ExitStatus + Send + 'static, { - Self { - fut: Box::pin(fut), - wrapped_child: None, - } + Self::Task(tokio::task::spawn_blocking(f)) } - pub fn new_wrapped(child: crate::runner::Child<'a>) -> Self { - Self { - fut: Box::pin(async move { unreachable!() }), - wrapped_child: Some(Box::new(child)), - } + pub fn new_wrapped(child: crate::runner::Child) -> Self { + Self::Wrapped(Box::new(child)) } pub fn id(&self) -> Option<u32> { - self.wrapped_child.as_ref().and_then(|cmd| cmd.id()) + match self { + Self::Task(_) => None, + Self::Wrapped(child) => child.id(), + } } pub fn status( self, ) -> std::pin::Pin< Box< - dyn std::future::Future< - Output = anyhow::Result<async_std::process::ExitStatus>, - > + Send - + Sync - + 'a, + dyn std::future::Future<Output = Result<std::process::ExitStatus>> + + Send + + Sync, >, > { Box::pin(async move { - if let Some(child) = self.wrapped_child { - child.status().await - } else { - Ok(self.fut.await) + match self { + Self::Task(task) => task.await.map_err(|e| anyhow!(e)), + Self::Wrapped(child) => child.status().await, } }) } diff --git a/src/runner/builtins/mod.rs b/src/runner/builtins/mod.rs index 5205856..b714c58 100644 --- a/src/runner/builtins/mod.rs +++ b/src/runner/builtins/mod.rs @@ -7,7 +7,7 @@ type Builtin = &'static (dyn for<'a> Fn( crate::parse::Exe, &'a Env, command::Cfg, -) -> anyhow::Result<command::Child<'a>> +) -> Result<command::Child> + Sync + Send); @@ -33,7 +33,6 @@ macro_rules! bail { $cfg.io().write_stderr( format!("{}: {}\n", $exe.exe().display(), $msg).as_bytes() ) - .await .unwrap(); return std::process::ExitStatus::from_raw(1 << 8); }; @@ -41,12 +40,10 @@ macro_rules! bail { $cfg.io().write_stderr( format!("{}: ", $exe.exe().display()).as_bytes() ) - .await .unwrap(); $cfg.io().write_stderr(format!($msg, $($arg)*).as_bytes()) - .await .unwrap(); - $cfg.io().write_stderr(b"\n").await.unwrap(); + $cfg.io().write_stderr(b"\n").unwrap(); return std::process::ExitStatus::from_raw(1 << 8); }; } @@ -57,22 +54,20 @@ fn cd( exe: crate::parse::Exe, env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { - async fn async_cd( - exe: crate::parse::Exe, - env: &Env, - cfg: command::Cfg, - ) -> std::process::ExitStatus { +) -> Result<command::Child> { + let prev_pwd = env.prev_pwd(); + let home = env.var("HOME"); + Ok(command::Child::new_task(move || { let dir = if let Some(dir) = exe.args().get(0) { if dir.is_empty() { ".".to_string().into() } else if dir == "-" { - env.prev_pwd() + prev_pwd } else { dir.into() } } else { - let dir = env.var("HOME"); + let dir = home; if let Some(dir) = dir { dir.into() } else { @@ -88,25 +83,17 @@ fn cd( dir.display() ); } - async_std::process::ExitStatus::from_raw(0) - } - - Ok(command::Child::new_fut(async move { - async_cd(exe, env, cfg).await + std::process::ExitStatus::from_raw(0) })) } #[allow(clippy::unnecessary_wraps)] fn set( exe: crate::parse::Exe, - env: &Env, + _env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { - async fn async_set( - exe: crate::parse::Exe, - _env: &Env, - cfg: command::Cfg, - ) -> std::process::ExitStatus { +) -> Result<command::Child> { + Ok(command::Child::new_task(move || { let k = if let Some(k) = exe.args().get(0).map(String::as_str) { k } else { @@ -119,25 +106,17 @@ fn set( }; std::env::set_var(k, v); - async_std::process::ExitStatus::from_raw(0) - } - - Ok(command::Child::new_fut(async move { - async_set(exe, env, cfg).await + std::process::ExitStatus::from_raw(0) })) } #[allow(clippy::unnecessary_wraps)] fn unset( exe: crate::parse::Exe, - env: &Env, + _env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { - async fn async_unset( - exe: crate::parse::Exe, - _env: &Env, - cfg: command::Cfg, - ) -> std::process::ExitStatus { +) -> Result<command::Child> { + Ok(command::Child::new_task(move || { let k = if let Some(k) = exe.args().get(0).map(String::as_str) { k } else { @@ -145,11 +124,7 @@ fn unset( }; std::env::remove_var(k); - async_std::process::ExitStatus::from_raw(0) - } - - Ok(command::Child::new_fut(async move { - async_unset(exe, env, cfg).await + std::process::ExitStatus::from_raw(0) })) } @@ -159,22 +134,17 @@ fn unset( // this later, since the binary seems totally fine fn echo( exe: crate::parse::Exe, - env: &Env, + _env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { - async fn async_echo( - exe: crate::parse::Exe, - _env: &Env, - cfg: command::Cfg, - ) -> std::process::ExitStatus { +) -> Result<command::Child> { + Ok(command::Child::new_task(move || { macro_rules! write_stdout { ($bytes:expr) => { - if let Err(e) = cfg.io().write_stdout($bytes).await { + if let Err(e) = cfg.io().write_stdout($bytes) { cfg.io() .write_stderr(format!("echo: {}", e).as_bytes()) - .await .unwrap(); - return async_std::process::ExitStatus::from_raw(1 << 8); + return std::process::ExitStatus::from_raw(1 << 8); } }; } @@ -188,32 +158,24 @@ fn echo( } } - async_std::process::ExitStatus::from_raw(0) - } - - Ok(command::Child::new_fut(async move { - async_echo(exe, env, cfg).await + std::process::ExitStatus::from_raw(0) })) } #[allow(clippy::unnecessary_wraps)] fn read( exe: crate::parse::Exe, - env: &Env, + _env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { - async fn async_read( - exe: crate::parse::Exe, - _env: &Env, - cfg: command::Cfg, - ) -> std::process::ExitStatus { +) -> Result<command::Child> { + Ok(command::Child::new_task(move || { let var = if let Some(var) = exe.args().get(0).map(String::as_str) { var } else { bail!(cfg, exe, "usage: read var"); }; - let (val, done) = match cfg.io().read_line_stdin().await { + let (val, done) = match cfg.io().read_line_stdin() { Ok((line, done)) => (line, done), Err(e) => { bail!(cfg, exe, e); @@ -221,15 +183,7 @@ fn read( }; std::env::set_var(var, val); - async_std::process::ExitStatus::from_raw(if done { - 1 << 8 - } else { - 0 - }) - } - - Ok(command::Child::new_fut(async move { - async_read(exe, env, cfg).await + std::process::ExitStatus::from_raw(if done { 1 << 8 } else { 0 }) })) } @@ -237,7 +191,7 @@ fn and( mut exe: crate::parse::Exe, env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { +) -> Result<command::Child> { exe.shift(); if env.latest_status().success() { let mut cmd = crate::runner::Command::new(exe, cfg.io().clone()); @@ -245,7 +199,7 @@ fn and( Ok(command::Child::new_wrapped(cmd.spawn(env)?)) } else { let status = env.latest_status(); - Ok(command::Child::new_fut(async move { status })) + Ok(command::Child::new_task(move || status)) } } @@ -253,11 +207,11 @@ fn or( mut exe: crate::parse::Exe, env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { +) -> Result<command::Child> { exe.shift(); if env.latest_status().success() { let status = env.latest_status(); - Ok(command::Child::new_fut(async move { status })) + Ok(command::Child::new_task(move || status)) } else { let mut cmd = crate::runner::Command::new(exe, cfg.io().clone()); cfg.setup_command(&mut cmd); @@ -269,9 +223,9 @@ fn command( mut exe: crate::parse::Exe, env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { +) -> Result<command::Child> { exe.shift(); - let mut cmd = crate::runner::Command::new_binary(exe); + let mut cmd = crate::runner::Command::new_binary(&exe); cfg.setup_command(&mut cmd); Ok(command::Child::new_wrapped(cmd.spawn(env)?)) } @@ -280,7 +234,7 @@ fn builtin( mut exe: crate::parse::Exe, env: &Env, cfg: command::Cfg, -) -> anyhow::Result<command::Child> { +) -> Result<command::Child> { exe.shift(); let mut cmd = crate::runner::Command::new_builtin(exe, cfg.io().clone()); cfg.setup_command(&mut cmd); diff --git a/src/runner/command.rs b/src/runner/command.rs index 5d4c11e..cbc8dee 100644 --- a/src/runner/command.rs +++ b/src/runner/command.rs @@ -8,13 +8,14 @@ pub struct Command { Box<dyn FnMut() -> std::io::Result<()> + Send + Sync + 'static>, >, } + impl Command { pub fn new(exe: crate::parse::Exe, io: super::builtins::Io) -> Self { let exe_path = exe.exe().to_path_buf(); let redirects = exe.redirects().to_vec(); Self { inner: super::builtins::Command::new(exe, io).map_or_else( - |exe| Self::new_binary(exe).inner, + |exe| Self::new_binary(&exe).inner, Inner::Builtin, ), exe: exe_path, @@ -23,11 +24,10 @@ impl Command { } } - #[allow(clippy::needless_pass_by_value)] - pub fn new_binary(exe: crate::parse::Exe) -> Self { + pub fn new_binary(exe: &crate::parse::Exe) -> Self { let exe_path = exe.exe().to_path_buf(); let redirects = exe.redirects().to_vec(); - let mut cmd = async_std::process::Command::new(exe.exe()); + let mut cmd = tokio::process::Command::new(exe.exe()); cmd.args(exe.args()); Self { inner: Inner::Binary(cmd), @@ -85,8 +85,8 @@ impl Command { } } - // Safety: see pre_exec in async_std::os::unix::process::CommandExt (this - // is just a wrapper) + // Safety: see pre_exec in tokio::process::Command (this is just a + // wrapper) pub unsafe fn pre_exec<F>(&mut self, f: F) where F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, @@ -94,7 +94,7 @@ impl Command { self.pre_exec = Some(Box::new(f)); } - pub fn spawn(self, env: &Env) -> anyhow::Result<Child> { + pub fn spawn(self, env: &Env) -> Result<Child> { let Self { inner, exe, @@ -127,7 +127,7 @@ impl Command { // functions unsafe { cmd.pre_exec(pre_exec) }; Ok(Child::Binary(cmd.spawn().map_err(|e| { - anyhow::anyhow!( + anyhow!( "{}: {}", crate::format::io_error(&e), exe.display() @@ -146,19 +146,19 @@ impl Command { } pub enum Inner { - Binary(async_std::process::Command), + Binary(tokio::process::Command), Builtin(super::builtins::Command), } -pub enum Child<'a> { - Binary(async_std::process::Child), - Builtin(super::builtins::Child<'a>), +pub enum Child { + Binary(tokio::process::Child), + Builtin(super::builtins::Child), } -impl<'a> Child<'a> { +impl Child { pub fn id(&self) -> Option<u32> { match self { - Self::Binary(child) => Some(child.id()), + Self::Binary(child) => child.id(), Self::Builtin(child) => child.id(), } } @@ -167,16 +167,15 @@ impl<'a> Child<'a> { self, ) -> std::pin::Pin< Box< - dyn std::future::Future< - Output = anyhow::Result<std::process::ExitStatus>, - > + Send - + Sync - + 'a, + dyn std::future::Future<Output = Result<std::process::ExitStatus>> + + Send + + Sync, >, > { Box::pin(async move { match self { - Self::Binary(child) => Ok(child.status_no_drop().await?), + // this case is handled by waitpid + Self::Binary(_) => unreachable!(), Self::Builtin(child) => Ok(child.status().await?), } }) diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 01a87b9..91e268a 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -4,13 +4,12 @@ mod builtins; mod command; pub use command::{Child, Command}; mod prelude; - -const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0); +mod sys; #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum Event { - RunPipeline(usize, (usize, usize)), - Suspend(usize), + RunPipeline((usize, usize)), + Suspend, Exit(Env), } @@ -69,12 +68,13 @@ enum Frame { For(bool, usize, Vec<String>), } -pub async fn run( - commands: &str, - shell_write: Option<&async_std::fs::File>, -) -> anyhow::Result<i32> { +pub async fn main( + commands: String, + shell_write: &mut Option<tokio::fs::File>, +) -> Result<i32> { let mut env = Env::new_from_env()?; - run_commands(commands, &mut env, shell_write).await?; + let config = crate::config::Config::load()?; + run_commands(commands, &mut env, &config, shell_write).await?; let status = env.latest_status(); write_event(shell_write, Event::Exit(env)).await?; @@ -85,11 +85,12 @@ pub async fn run( } async fn run_commands( - commands: &str, + commands: String, env: &mut Env, - shell_write: Option<&async_std::fs::File>, -) -> anyhow::Result<()> { - let commands = crate::parse::ast::Commands::parse(commands)?; + config: &crate::config::Config, + shell_write: &mut Option<tokio::fs::File>, +) -> Result<()> { + let commands = crate::parse::ast::Commands::parse(&commands)?; let commands = commands.commands(); let mut pc = 0; let mut stack = Stack::new(); @@ -97,7 +98,8 @@ async fn run_commands( match &commands[pc] { crate::parse::ast::Command::Pipeline(pipeline) => { if stack.should_execute() { - run_pipeline(pipeline.clone(), env, shell_write).await?; + run_pipeline(pipeline.clone(), env, config, shell_write) + .await?; } pc += 1; } @@ -108,7 +110,8 @@ async fn run_commands( } if should { let status = env.latest_status(); - run_pipeline(pipeline.clone(), env, shell_write).await?; + run_pipeline(pipeline.clone(), env, config, shell_write) + .await?; if let Some(Frame::If(should, found)) = stack.top_mut() { *should = env.latest_status().success(); if *should { @@ -128,7 +131,8 @@ async fn run_commands( } if should { let status = env.latest_status(); - run_pipeline(pipeline.clone(), env, shell_write).await?; + run_pipeline(pipeline.clone(), env, config, shell_write) + .await?; if let Some(Frame::While(should, _)) = stack.top_mut() { *should = env.latest_status().success(); } else { @@ -153,7 +157,7 @@ async fn run_commands( .map(IntoIterator::into_iter) }) .collect::<futures_util::stream::FuturesOrdered<_>>() - .collect::<Result<Vec<_>, _>>().await? + .try_collect::<Vec<_>>().await? .into_iter() .flatten() .collect() @@ -188,8 +192,13 @@ async fn run_commands( *should = false; } else if let Some(pipeline) = pipeline { let status = env.latest_status(); - run_pipeline(pipeline.clone(), env, shell_write) - .await?; + run_pipeline( + pipeline.clone(), + env, + config, + shell_write, + ) + .await?; *should = env.latest_status().success(); if *should { *found = true; @@ -232,30 +241,54 @@ async fn run_commands( async fn run_pipeline( pipeline: crate::parse::ast::Pipeline, env: &mut Env, - shell_write: Option<&async_std::fs::File>, -) -> anyhow::Result<()> { - write_event(shell_write, Event::RunPipeline(env.idx(), pipeline.span())) - .await?; + config: &crate::config::Config, + shell_write: &mut Option<tokio::fs::File>, +) -> Result<()> { + write_event(shell_write, Event::RunPipeline(pipeline.span())).await?; // Safety: pipelines are run serially, so only one copy of these will ever // exist at once. note that reusing a single copy of these at the top // level would not be safe, because in the case of a command line like // "echo foo; ls", we would pass the stdout fd to the ls process while it // is still open here, and may still have data buffered. - let stdin = unsafe { async_std::fs::File::from_raw_fd(0) }; - let stdout = unsafe { async_std::fs::File::from_raw_fd(1) }; - let stderr = unsafe { async_std::fs::File::from_raw_fd(2) }; + let stdin = unsafe { std::fs::File::from_raw_fd(0) }; + let stdout = unsafe { std::fs::File::from_raw_fd(1) }; + let stderr = unsafe { std::fs::File::from_raw_fd(2) }; let mut io = builtins::Io::new(); io.set_stdin(stdin); io.set_stdout(stdout); io.set_stderr(stderr); let pwd = env.pwd().to_path_buf(); - let pipeline = pipeline.eval(env).await?; let interactive = shell_write.is_some(); - let (children, pg) = spawn_children(pipeline, env, &io, interactive)?; - let status = wait_children(children, pg, env, &io, shell_write).await; + let pipeline = pipeline.eval(env).await?; + let mut exes: Vec<_> = pipeline.into_exes().collect(); + for exe in &mut exes { + let mut seen = std::collections::HashSet::new(); + while let Some(alias) = config.alias_for(exe.exe()) { + let mut new = alias.clone().eval(env).await?; + let override_self = exe.exe() == new.exe(); + if seen.contains(new.exe()) { + return Err(anyhow!( + "recursive alias found: {}", + new.exe().display() + )); + } + seen.insert(new.exe().to_path_buf()); + new.append(exe.clone()); + *exe = new; + if override_self { + break; + } + } + } + let cmds = exes + .into_iter() + .map(|exe| Command::new(exe, io.clone())) + .collect(); + let (children, pg) = spawn_children(cmds, env, interactive)?; + let status = wait_children(children, pg, shell_write).await; if interactive { - set_foreground_pg(nix::unistd::getpid())?; + sys::set_foreground_pg(nix::unistd::getpid())?; } env.update()?; env.set_status(status); @@ -266,28 +299,23 @@ async fn run_pipeline( } async fn write_event( - fh: Option<&async_std::fs::File>, + fh: &mut Option<tokio::fs::File>, event: Event, -) -> anyhow::Result<()> { - if let Some(mut fh) = fh { +) -> Result<()> { + if let Some(fh) = fh { fh.write_all(&bincode::serialize(&event)?).await?; fh.flush().await?; } Ok(()) } -fn spawn_children<'a>( - pipeline: crate::parse::Pipeline, - env: &'a Env, - io: &builtins::Io, +fn spawn_children( + mut cmds: Vec<Command>, + env: &Env, interactive: bool, -) -> anyhow::Result<(Vec<Child<'a>>, Option<nix::unistd::Pid>)> { - let mut cmds: Vec<_> = pipeline - .into_exes() - .map(|exe| Command::new(exe, io.clone())) - .collect(); +) -> Result<(Vec<Child>, Option<nix::unistd::Pid>)> { for i in 0..(cmds.len() - 1) { - let (r, w) = pipe()?; + let (r, w) = sys::pipe()?; cmds[i].stdout(w); cmds[i + 1].stdin(r); } @@ -298,18 +326,18 @@ fn spawn_children<'a>( // Safety: setpgid is an async-signal-safe function unsafe { cmd.pre_exec(move || { - setpgid_child(pg_pid)?; + sys::setpgid_child(pg_pid)?; Ok(()) }); } let child = cmd.spawn(env)?; if let Some(id) = child.id() { - let child_pid = id_to_pid(id); - setpgid_parent(child_pid, pg_pid)?; + let child_pid = sys::id_to_pid(id); + sys::setpgid_parent(child_pid, pg_pid)?; if pg_pid.is_none() { pg_pid = Some(child_pid); if interactive { - set_foreground_pg(child_pid)?; + sys::set_foreground_pg(child_pid)?; } } } @@ -319,24 +347,18 @@ fn spawn_children<'a>( } async fn wait_children( - children: Vec<Child<'_>>, + children: Vec<Child>, pg: Option<nix::unistd::Pid>, - env: &Env, - io: &builtins::Io, - shell_write: Option<&async_std::fs::File>, + shell_write: &mut Option<tokio::fs::File>, ) -> std::process::ExitStatus { enum Res { Child(nix::Result<nix::sys::wait::WaitStatus>), - Builtin(Option<(anyhow::Result<std::process::ExitStatus>, bool)>), + Builtin((Result<std::process::ExitStatus>, bool)), } macro_rules! bail { ($e:expr) => { - // if writing to stderr is not possible, we still want to exit - // normally with a failure exit code - #[allow(clippy::let_underscore_drop)] - let _ = - io.write_stderr(format!("nbsh: {}\n", $e).as_bytes()).await; + eprintln!("nbsh: {}\n", $e); return std::process::ExitStatus::from_raw(1 << 8); }; } @@ -351,10 +373,11 @@ async fn wait_children( let mut children: std::collections::HashMap<_, _> = children .into_iter() .map(|(i, child)| { - (id_to_pid(child.id().unwrap()), (child, i == count - 1)) + (sys::id_to_pid(child.id().unwrap()), (child, i == count - 1)) }) .collect(); - let mut builtins: futures_util::stream::FuturesUnordered<_> = + let mut builtin_count = builtins.len(); + let builtins: futures_util::stream::FuturesUnordered<_> = builtins .into_iter() .map(|(i, child)| async move { @@ -362,47 +385,40 @@ async fn wait_children( }) .collect(); - let (wait_w, wait_r) = async_std::channel::unbounded(); - let new_wait = move || { - if let Some(pg) = pg { - let wait_w = wait_w.clone(); - async_std::task::spawn(async move { - let res = blocking::unblock(move || { - nix::sys::wait::waitpid( - neg_pid(pg), - Some(nix::sys::wait::WaitPidFlag::WUNTRACED), - ) - }) - .await; - if wait_w.is_closed() { - // we shouldn't be able to drop real process terminations + let (wait_w, wait_r) = tokio::sync::mpsc::unbounded_channel(); + if let Some(pg) = pg { + tokio::task::spawn_blocking(move || loop { + let res = nix::sys::wait::waitpid( + sys::neg_pid(pg), + Some(nix::sys::wait::WaitPidFlag::WUNTRACED), + ); + match wait_w.send(res) { + Ok(_) => {} + Err(tokio::sync::mpsc::error::SendError(res)) => { + // we should never drop wait_r while there are still valid + // things to read assert!(res.is_err()); - } else { - wait_w.send(res).await.unwrap(); + break; } - }); - } - }; - - new_wait(); - loop { - if children.is_empty() && builtins.is_empty() { - break; - } + } + }); + } - let child = async { Res::Child(wait_r.recv().await.unwrap()) }; - let builtin = async { - Res::Builtin(if builtins.is_empty() { - std::future::pending().await - } else { - builtins.next().await - }) - }; - match child.race(builtin).await { + let mut stream: futures_util::stream::SelectAll<_> = [ + tokio_stream::wrappers::UnboundedReceiverStream::new(wait_r) + .map(Res::Child) + .boxed(), + builtins.map(Res::Builtin).boxed(), + ] + .into_iter() + .collect(); + while let Some(res) = stream.next().await { + match res { Res::Child(Ok(status)) => { match status { - // we can't call child.status() here to unify these branches - // because our waitpid call already collected the status + // we can't call child.status() here to unify these + // branches because our waitpid call already collected the + // status nix::sys::wait::WaitStatus::Exited(pid, code) => { let (_, last) = children.remove(&pid).unwrap(); if last { @@ -432,11 +448,8 @@ async fn wait_children( } nix::sys::wait::WaitStatus::Stopped(pid, signal) => { if signal == nix::sys::signal::Signal::SIGTSTP { - if let Err(e) = write_event( - shell_write, - Event::Suspend(env.idx()), - ) - .await + if let Err(e) = + write_event(shell_write, Event::Suspend).await { bail!(e); } @@ -450,12 +463,11 @@ async fn wait_children( } _ => {} } - new_wait(); } Res::Child(Err(e)) => { bail!(e); } - Res::Builtin(Some((Ok(status), last))) => { + Res::Builtin((Ok(status), last)) => { // this conversion is safe because the Signal enum is // repr(i32) #[allow(clippy::as_conversions)] @@ -471,99 +483,17 @@ async fn wait_children( if last { final_status = Some(status); } + builtin_count -= 1; } - Res::Builtin(Some((Err(e), _))) => { + Res::Builtin((Err(e), _)) => { bail!(e); } - Res::Builtin(None) => {} } - } - final_status.unwrap() -} - -fn pipe() -> anyhow::Result<(std::fs::File, std::fs::File)> { - let (r, w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; - // Safety: these file descriptors were just returned by pipe2 above, and - // are only available in this function, so nothing else can be accessing - // them - Ok((unsafe { std::fs::File::from_raw_fd(r) }, unsafe { - std::fs::File::from_raw_fd(w) - })) -} - -fn set_foreground_pg(pg: nix::unistd::Pid) -> anyhow::Result<()> { - let pty = nix::fcntl::open( - "/dev/tty", - nix::fcntl::OFlag::empty(), - nix::sys::stat::Mode::empty(), - )?; - - // if a background process calls tcsetpgrp, the kernel will send it - // SIGTTOU which suspends it. if that background process is the session - // leader and doesn't have SIGTTOU blocked, the kernel will instead just - // return ENOTTY from the tcsetpgrp call rather than sending a signal to - // avoid deadlocking the process. therefore, we need to ensure that - // SIGTTOU is blocked here. - - // Safety: setting a signal handler to SigIgn is always safe - unsafe { - nix::sys::signal::signal( - nix::sys::signal::Signal::SIGTTOU, - nix::sys::signal::SigHandler::SigIgn, - )?; - } - let res = nix::unistd::tcsetpgrp(pty, pg); - // Safety: setting a signal handler to SigDfl is always safe - unsafe { - nix::sys::signal::signal( - nix::sys::signal::Signal::SIGTTOU, - nix::sys::signal::SigHandler::SigDfl, - )?; - } - res?; - - nix::unistd::close(pty)?; - - nix::sys::signal::kill(neg_pid(pg), nix::sys::signal::Signal::SIGCONT) - .or_else(|e| { - // the process group has already exited - if e == nix::errno::Errno::ESRCH { - Ok(()) - } else { - Err(e) - } - })?; - - Ok(()) -} - -fn setpgid_child(pg: Option<nix::unistd::Pid>) -> std::io::Result<()> { - nix::unistd::setpgid(PID0, pg.unwrap_or(PID0))?; - Ok(()) -} - -fn setpgid_parent( - pid: nix::unistd::Pid, - pg: Option<nix::unistd::Pid>, -) -> anyhow::Result<()> { - nix::unistd::setpgid(pid, pg.unwrap_or(PID0)).or_else(|e| { - // EACCES means that the child already called exec, but if it did, - // then it also must have already called setpgid itself, so we don't - // care. ESRCH means that the process already exited, which is similar - if e == nix::errno::Errno::EACCES || e == nix::errno::Errno::ESRCH { - Ok(()) - } else { - Err(e) + if children.is_empty() && builtin_count == 0 { + break; } - })?; - Ok(()) -} - -fn id_to_pid(id: u32) -> nix::unistd::Pid { - nix::unistd::Pid::from_raw(id.try_into().unwrap()) -} + } -fn neg_pid(pid: nix::unistd::Pid) -> nix::unistd::Pid { - nix::unistd::Pid::from_raw(-pid.as_raw()) + final_status.unwrap() } diff --git a/src/runner/sys.rs b/src/runner/sys.rs new file mode 100644 index 0000000..b6a9428 --- /dev/null +++ b/src/runner/sys.rs @@ -0,0 +1,79 @@ +use crate::runner::prelude::*; + +const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0); + +pub fn pipe() -> Result<(std::fs::File, std::fs::File)> { + let (r, w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; + // Safety: these file descriptors were just returned by pipe2 above, and + // are only available in this function, so nothing else can be accessing + // them + Ok((unsafe { std::fs::File::from_raw_fd(r) }, unsafe { + std::fs::File::from_raw_fd(w) + })) +} + +pub fn set_foreground_pg(pg: nix::unistd::Pid) -> Result<()> { + let pty = nix::fcntl::open( + "/dev/tty", + nix::fcntl::OFlag::empty(), + nix::sys::stat::Mode::empty(), + )?; + + // if a background process calls tcsetpgrp, the kernel will send it + // SIGTTOU which suspends it. if that background process is the session + // leader and doesn't have SIGTTOU blocked, the kernel will instead just + // return ENOTTY from the tcsetpgrp call rather than sending a signal to + // avoid deadlocking the process. therefore, we need to ensure that + // SIGTTOU is blocked here. + + // Safety: setting a signal handler to SigIgn is always safe + unsafe { + nix::sys::signal::signal( + nix::sys::signal::Signal::SIGTTOU, + nix::sys::signal::SigHandler::SigIgn, + )?; + } + let res = nix::unistd::tcsetpgrp(pty, pg); + // Safety: setting a signal handler to SigDfl is always safe + unsafe { + nix::sys::signal::signal( + nix::sys::signal::Signal::SIGTTOU, + nix::sys::signal::SigHandler::SigDfl, + )?; + } + res?; + + nix::unistd::close(pty)?; + + nix::sys::signal::kill(neg_pid(pg), nix::sys::signal::Signal::SIGCONT) + // the process group has already exited + .allow(nix::errno::Errno::ESRCH)?; + + Ok(()) +} + +pub fn setpgid_child(pg: Option<nix::unistd::Pid>) -> std::io::Result<()> { + nix::unistd::setpgid(PID0, pg.unwrap_or(PID0))?; + Ok(()) +} + +pub fn setpgid_parent( + pid: nix::unistd::Pid, + pg: Option<nix::unistd::Pid>, +) -> Result<()> { + nix::unistd::setpgid(pid, pg.unwrap_or(PID0)) + // the child already called exec, so it must have already called + // setpgid itself + .allow(nix::errno::Errno::EACCES) + // the child already exited, so we don't care + .allow(nix::errno::Errno::ESRCH)?; + Ok(()) +} + +pub fn id_to_pid(id: u32) -> nix::unistd::Pid { + nix::unistd::Pid::from_raw(id.try_into().unwrap()) +} + +pub fn neg_pid(pid: nix::unistd::Pid) -> nix::unistd::Pid { + nix::unistd::Pid::from_raw(-pid.as_raw()) +} diff --git a/src/shell.pest b/src/shell.pest index 0c63802..92b173a 100644 --- a/src/shell.pest +++ b/src/shell.pest @@ -23,8 +23,8 @@ alternation_bareword = @{ alternation_bareword_char+ } alternation_word_part = ${ var | alternation_bareword | - "'" ~ single_string ~ "'" | - "\"" ~ (var | double_string)+ ~ "\"" + "'" ~ single_string? ~ "'" | + "\"" ~ (var | double_string)* ~ "\"" } alternation_word = ${ alternation_word_part* } alternation = ${ "{" ~ alternation_word ~ ("," ~ alternation_word)* ~ "}" } @@ -36,8 +36,8 @@ word_part = ${ substitution | var | bareword | - "'" ~ single_string ~ "'" | - "\"" ~ (substitution | var | double_string)+ ~ "\"" + "'" ~ single_string? ~ "'" | + "\"" ~ (substitution | var | double_string)* ~ "\"" } word = ${ word_part+ } diff --git a/src/shell/event.rs b/src/shell/event.rs index 025f3c4..dc58e6f 100644 --- a/src/shell/event.rs +++ b/src/shell/event.rs @@ -1,53 +1,87 @@ +use crate::prelude::*; + #[derive(Debug)] pub enum Event { Key(textmode::Key), Resize((u16, u16)), PtyOutput, - PtyClose, ChildRunPipeline(usize, (usize, usize)), ChildSuspend(usize), - GitInfo(Option<super::git::Info>), + ChildExit(usize, super::history::ExitInfo, Option<Env>), + GitInfo(Option<super::inputs::GitInfo>), ClockTimer, } -pub struct Reader { - pending: async_std::sync::Mutex<Pending>, - cvar: async_std::sync::Condvar, +pub fn channel() -> (Writer, Reader) { + let (event_w, event_r) = tokio::sync::mpsc::unbounded_channel(); + (Writer::new(event_w), Reader::new(event_r)) +} + +#[derive(Clone)] +pub struct Writer(tokio::sync::mpsc::UnboundedSender<Event>); + +impl Writer { + pub fn new(event_w: tokio::sync::mpsc::UnboundedSender<Event>) -> Self { + Self(event_w) + } + + pub fn send(&self, event: Event) { + // the only time this should ever error is when the application is + // shutting down, at which point we don't actually care about any + // further dropped messages + #[allow(clippy::let_underscore_drop)] + let _ = self.0.send(event); + } } +pub struct Reader(std::sync::Arc<InnerReader>); + impl Reader { pub fn new( - input: async_std::channel::Receiver<Event>, - ) -> async_std::sync::Arc<Self> { - let this = async_std::sync::Arc::new(Self { - pending: async_std::sync::Mutex::new(Pending::new()), - cvar: async_std::sync::Condvar::new(), - }); + mut input: tokio::sync::mpsc::UnboundedReceiver<Event>, + ) -> Self { + let inner = std::sync::Arc::new(InnerReader::new()); { - let this = async_std::sync::Arc::clone(&this); - async_std::task::spawn(async move { - while let Ok(event) = input.recv().await { - this.new_event(Some(event)).await; + let inner = inner.clone(); + tokio::spawn(async move { + while let Some(event) = input.recv().await { + inner.new_event(Some(event)); } - this.new_event(None).await; + inner.new_event(None); }); } - this + Self(inner) } pub async fn recv(&self) -> Option<Event> { - let mut pending = self - .cvar - .wait_until(self.pending.lock().await, |pending| { - pending.has_event() - }) - .await; - pending.get_event() + self.0.recv().await + } +} + +struct InnerReader { + pending: std::sync::Mutex<Pending>, + cvar: tokio::sync::Notify, +} + +impl InnerReader { + fn new() -> Self { + Self { + pending: std::sync::Mutex::new(Pending::new()), + cvar: tokio::sync::Notify::new(), + } + } + + async fn recv(&self) -> Option<Event> { + loop { + if let Some(event) = self.pending.lock().unwrap().get_event() { + return event; + } + self.cvar.notified().await; + } } - async fn new_event(&self, event: Option<Event>) { - let mut pending = self.pending.lock().await; - pending.new_event(event); + fn new_event(&self, event: Option<Event>) { + self.pending.lock().unwrap().new_event(event); self.cvar.notify_one(); } } @@ -58,10 +92,10 @@ struct Pending { key: std::collections::VecDeque<textmode::Key>, size: Option<(u16, u16)>, pty_output: bool, - pty_close: bool, child_run_pipeline: std::collections::VecDeque<(usize, (usize, usize))>, child_suspend: std::collections::VecDeque<usize>, - git_info: Option<Option<super::git::Info>>, + child_exit: Option<(usize, super::history::ExitInfo, Option<Env>)>, + git_info: Option<Option<super::inputs::GitInfo>>, clock_timer: bool, done: bool, } @@ -71,53 +105,40 @@ impl Pending { Self::default() } - fn has_event(&self) -> bool { - self.done - || !self.key.is_empty() - || self.size.is_some() - || self.pty_output - || self.pty_close - || !self.child_run_pipeline.is_empty() - || !self.child_suspend.is_empty() - || self.git_info.is_some() - || self.clock_timer - } - - fn get_event(&mut self) -> Option<Event> { + fn get_event(&mut self) -> Option<Option<Event>> { if self.done { - return None; + return Some(None); } if let Some(key) = self.key.pop_front() { - return Some(Event::Key(key)); + return Some(Some(Event::Key(key))); } if let Some(size) = self.size.take() { - return Some(Event::Resize(size)); - } - if self.pty_close { - self.pty_close = false; - return Some(Event::PtyClose); + return Some(Some(Event::Resize(size))); } if let Some((idx, span)) = self.child_run_pipeline.pop_front() { - return Some(Event::ChildRunPipeline(idx, span)); + return Some(Some(Event::ChildRunPipeline(idx, span))); } if let Some(idx) = self.child_suspend.pop_front() { - return Some(Event::ChildSuspend(idx)); + return Some(Some(Event::ChildSuspend(idx))); + } + if let Some((idx, exit_info, env)) = self.child_exit.take() { + return Some(Some(Event::ChildExit(idx, exit_info, env))); } if let Some(info) = self.git_info.take() { - return Some(Event::GitInfo(info)); + return Some(Some(Event::GitInfo(info))); } if self.clock_timer { self.clock_timer = false; - return Some(Event::ClockTimer); + return Some(Some(Event::ClockTimer)); } // process_output should be last because it will often be the case // that there is ~always new process output (cat on large files, yes, // etc) and that shouldn't prevent other events from happening if self.pty_output { self.pty_output = false; - return Some(Event::PtyOutput); + return Some(Some(Event::PtyOutput)); } - unreachable!() + None } fn new_event(&mut self, event: Option<Event>) { @@ -125,13 +146,15 @@ impl Pending { Some(Event::Key(key)) => self.key.push_back(key), Some(Event::Resize(size)) => self.size = Some(size), Some(Event::PtyOutput) => self.pty_output = true, - Some(Event::PtyClose) => self.pty_close = true, Some(Event::ChildRunPipeline(idx, span)) => { self.child_run_pipeline.push_back((idx, span)); } Some(Event::ChildSuspend(idx)) => { self.child_suspend.push_back(idx); } + Some(Event::ChildExit(idx, exit_info, env)) => { + self.child_exit = Some((idx, exit_info, env)); + } Some(Event::GitInfo(info)) => self.git_info = Some(info), Some(Event::ClockTimer) => self.clock_timer = true, None => self.done = true, diff --git a/src/shell/history/entry.rs b/src/shell/history/entry.rs index a45d99d..0491bf7 100644 --- a/src/shell/history/entry.rs +++ b/src/shell/history/entry.rs @@ -1,25 +1,13 @@ use crate::shell::prelude::*; -enum State { - Running((usize, usize)), - Exited(ExitInfo), -} - pub struct Entry { cmdline: String, env: Env, - state: State, - vt: vt100::Parser, - audible_bell_state: usize, - visual_bell_state: usize, - audible_bell: bool, - visual_bell: bool, - real_bell_pending: bool, + pty: super::pty::Pty, fullscreen: Option<bool>, - input: async_std::channel::Sender<Vec<u8>>, - resize: async_std::channel::Sender<(u16, u16)>, - start_time: time::OffsetDateTime, start_instant: std::time::Instant, + start_time: time::OffsetDateTime, + state: State, } impl Entry { @@ -27,39 +15,37 @@ impl Entry { cmdline: String, env: Env, size: (u16, u16), - input: async_std::channel::Sender<Vec<u8>>, - resize: async_std::channel::Sender<(u16, u16)>, - ) -> Self { - let span = (0, cmdline.len()); - Self { + event_w: crate::shell::event::Writer, + ) -> Result<Self> { + let start_instant = std::time::Instant::now(); + let start_time = time::OffsetDateTime::now_utc(); + + let (pty, pts) = super::pty::Pty::new(size, event_w.clone()).unwrap(); + let (child, fh) = Self::spawn_command(&cmdline, &env, &pts)?; + tokio::spawn(Self::task(child, fh, env.idx(), event_w)); + Ok(Self { cmdline, env, - state: State::Running(span), - vt: vt100::Parser::new(size.0, size.1, 0), - audible_bell_state: 0, - visual_bell_state: 0, - audible_bell: false, - visual_bell: false, - real_bell_pending: false, - input, - resize, + pty, fullscreen: None, - start_time: time::OffsetDateTime::now_utc(), - start_instant: std::time::Instant::now(), - } + start_instant, + start_time, + state: State::Running((0, 0)), + }) } pub fn render( - &mut self, + &self, out: &mut impl textmode::Textmode, - idx: usize, entry_count: usize, - size: (u16, u16), + vt: &mut super::pty::Vt, focused: bool, scrolling: bool, offset: time::UtcOffset, ) { - let time = self.exit_info().map_or_else( + let idx = self.env.idx(); + let size = out.screen().size(); + let time = self.state.exit_info().map_or_else( || { format!( "[{}]", @@ -77,13 +63,11 @@ impl Entry { }, ); - self.bell(out); - if focused { - self.audible_bell = false; - self.visual_bell = false; + if vt.bell(focused) { + out.write(b"\x07"); } - set_bgcolor(out, idx, focused); + Self::set_bgcolor(out, idx, focused); out.set_fgcolor(textmode::color::YELLOW); let entry_count_width = format!("{}", entry_count + 1).len(); let idx_str = format!("{}", idx + 1); @@ -92,8 +76,8 @@ impl Entry { out.write_str(" "); out.reset_attributes(); - set_bgcolor(out, idx, focused); - if let Some(info) = self.exit_info() { + Self::set_bgcolor(out, idx, focused); + if let Some(info) = self.state.exit_info() { if info.status.signal().is_some() { out.set_fgcolor(textmode::color::MAGENTA); } else if info.status.success() { @@ -107,13 +91,13 @@ impl Entry { } out.reset_attributes(); - if self.audible_bell || self.visual_bell { + if vt.is_bell() { out.set_bgcolor(textmode::Color::Rgb(64, 16, 16)); } else { - set_bgcolor(out, idx, focused); + Self::set_bgcolor(out, idx, focused); } out.write_str("$ "); - set_bgcolor(out, idx, focused); + Self::set_bgcolor(out, idx, focused); let start = usize::from(out.screen().cursor_position().1); let end = usize::from(size.1) - time.len() - 2; let max_len = end - start; @@ -130,7 +114,7 @@ impl Entry { if !cmd[span.0..span.1].is_empty() { out.set_bgcolor(textmode::Color::Rgb(16, 64, 16)); out.write_str(&cmd[span.0..span.1]); - set_bgcolor(out, idx, focused); + Self::set_bgcolor(out, idx, focused); } if !cmd[span.1..].is_empty() { out.write_str(&cmd[span.1..]); @@ -155,7 +139,7 @@ impl Entry { } out.reset_attributes(); - set_bgcolor(out, idx, focused); + Self::set_bgcolor(out, idx, focused); let cur_pos = out.screen().cursor_position(); out.write_str(&" ".repeat( usize::from(size.1) - time.len() - 1 - usize::from(cur_pos.1), @@ -164,7 +148,7 @@ impl Entry { out.write_str(" "); out.reset_attributes(); - if self.binary() { + if vt.binary() { let msg = "This appears to be binary data. Fullscreen this entry to view anyway."; let len: u16 = msg.len().try_into().unwrap(); out.move_to( @@ -175,7 +159,8 @@ impl Entry { out.write_str(msg); out.hide_cursor(true); } else { - let last_row = self.output_lines(focused && !scrolling); + let last_row = + vt.output_lines(focused && !scrolling, self.state.running()); let mut max_lines = self.max_lines(entry_count); if last_row > max_lines { out.write(b"\r\n"); @@ -185,7 +170,7 @@ impl Entry { max_lines -= 1; } let mut out_row = out.screen().cursor_position().0 + 1; - let screen = self.vt.screen(); + let screen = vt.screen(); let pos = screen.cursor_position(); let mut wrapped = false; let mut cursor_found = None; @@ -216,66 +201,41 @@ impl Entry { } } } - out.reset_attributes(); - } - pub fn render_fullscreen(&mut self, out: &mut impl textmode::Textmode) { - out.write(&self.vt.screen().state_formatted()); - self.bell(out); - self.audible_bell = false; - self.visual_bell = false; out.reset_attributes(); } - pub async fn send_input(&self, bytes: Vec<u8>) { - if self.running() { - self.input.send(bytes).await.unwrap(); - } - } - - pub async fn resize(&mut self, size: (u16, u16)) { - if self.running() { - self.resize.send(size).await.unwrap(); - self.vt.set_size(size.0, size.1); - } + pub fn render_fullscreen(&self, out: &mut impl textmode::Textmode) { + self.pty.with_vt_mut(|vt| { + out.write(&vt.screen().state_formatted()); + if vt.bell(true) { + out.write(b"\x07"); + } + out.reset_attributes(); + }); } - pub fn size(&self) -> (u16, u16) { - self.vt.screen().size() + pub fn input(&self, bytes: Vec<u8>) { + self.pty.input(bytes); } - pub fn process(&mut self, input: &[u8]) { - self.vt.process(input); - let screen = self.vt.screen(); - - let new_audible_bell_state = screen.audible_bell_count(); - if new_audible_bell_state != self.audible_bell_state { - self.audible_bell = true; - self.real_bell_pending = true; - self.audible_bell_state = new_audible_bell_state; - } - - let new_visual_bell_state = screen.visual_bell_count(); - if new_visual_bell_state != self.visual_bell_state { - self.visual_bell = true; - self.real_bell_pending = true; - self.visual_bell_state = new_visual_bell_state; - } + pub fn resize(&self, size: (u16, u16)) { + self.pty.resize(size); } pub fn cmd(&self) -> &str { &self.cmdline } - pub fn env(&self) -> &Env { - &self.env + pub fn start_time(&self) -> time::OffsetDateTime { + self.start_time } pub fn toggle_fullscreen(&mut self) { if let Some(fullscreen) = self.fullscreen { self.fullscreen = Some(!fullscreen); } else { - self.fullscreen = Some(!self.vt.screen().alternate_screen()); + self.fullscreen = Some(!self.pty.fullscreen()); } } @@ -284,110 +244,186 @@ impl Entry { } pub fn running(&self) -> bool { - matches!(self.state, State::Running(_)) + self.state.running() } - pub fn binary(&self) -> bool { - self.vt.screen().errors() > 5 + pub fn exited(&mut self, exit_info: ExitInfo) { + self.state = State::Exited(exit_info); } pub fn lines(&self, entry_count: usize, focused: bool) -> usize { + let running = self.running(); 1 + std::cmp::min( - self.output_lines(focused), + self.pty.with_vt(|vt| vt.output_lines(focused, running)), self.max_lines(entry_count), ) } + pub fn should_fullscreen(&self) -> bool { + self.fullscreen.unwrap_or_else(|| self.pty.fullscreen()) + } + + pub fn lock_vt(&self) -> std::sync::MutexGuard<super::pty::Vt> { + self.pty.lock_vt() + } + + pub fn set_span(&mut self, new_span: (usize, usize)) { + if let State::Running(ref mut span) = self.state { + *span = new_span; + } + } + fn max_lines(&self, entry_count: usize) -> usize { if self.env.idx() == entry_count - 1 { - usize::from(self.size().0) * 2 / 3 + 15 } else { 5 } } - pub fn output_lines(&self, focused: bool) -> usize { - if self.binary() { - return 1; + fn set_bgcolor( + out: &mut impl textmode::Textmode, + idx: usize, + focus: bool, + ) { + if focus { + out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b)); + } else if idx % 2 == 0 { + out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00)); + } else { + out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); } + } - let screen = self.vt.screen(); - let mut last_row = 0; - for (idx, row) in screen.rows(0, self.size().1).enumerate() { - if !row.is_empty() { - last_row = idx + 1; - } + fn spawn_command( + cmdline: &str, + env: &Env, + pts: &pty_process::Pts, + ) -> Result<(tokio::process::Child, std::fs::File)> { + let mut cmd = pty_process::Command::new(crate::info::current_exe()?); + cmd.args(&["-c", cmdline, "--status-fd", "3"]); + env.apply(&mut cmd); + let (from_r, from_w) = + nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; + // Safety: from_r was just opened above and is not used anywhere else + let fh = unsafe { std::fs::File::from_raw_fd(from_r) }; + // Safety: dup2 is an async-signal-safe function + unsafe { + cmd.pre_exec(move || { + nix::unistd::dup2(from_w, 3)?; + Ok(()) + }); } - if focused && self.running() { - last_row = std::cmp::max( - last_row, - usize::from(screen.cursor_position().0) + 1, - ); - } - last_row + let child = cmd.spawn(pts)?; + nix::unistd::close(from_w)?; + Ok((child, fh)) } - pub fn should_fullscreen(&self) -> bool { - self.fullscreen - .unwrap_or_else(|| self.vt.screen().alternate_screen()) - } + async fn task( + mut child: tokio::process::Child, + fh: std::fs::File, + idx: usize, + event_w: crate::shell::event::Writer, + ) { + enum Res { + Read(crate::runner::Event), + Exit(std::io::Result<std::process::ExitStatus>), + } - pub fn set_span(&mut self, span: (usize, usize)) { - if matches!(self.state, State::Running(_)) { - self.state = State::Running(span); + let (read_w, read_r) = tokio::sync::mpsc::unbounded_channel(); + tokio::task::spawn_blocking(move || loop { + let event = bincode::deserialize_from(&fh); + match event { + Ok(event) => { + read_w.send(event).unwrap(); + } + Err(e) => { + match &*e { + bincode::ErrorKind::Io(io_e) => { + assert!( + io_e.kind() + == std::io::ErrorKind::UnexpectedEof + ); + } + e => { + panic!("{}", e); + } + } + break; + } + } + }); + + let mut stream: futures_util::stream::SelectAll<_> = [ + tokio_stream::wrappers::UnboundedReceiverStream::new(read_r) + .map(Res::Read) + .boxed(), + futures_util::stream::once(child.wait()) + .map(Res::Exit) + .boxed(), + ] + .into_iter() + .collect(); + let mut exit_status = None; + let mut new_env = None; + while let Some(res) = stream.next().await { + match res { + Res::Read(event) => match event { + crate::runner::Event::RunPipeline(new_span) => { + // we could just update the span in place here, but we + // do this as an event so that we can also trigger a + // refresh + event_w.send(Event::ChildRunPipeline(idx, new_span)); + } + crate::runner::Event::Suspend => { + event_w.send(Event::ChildSuspend(idx)); + } + crate::runner::Event::Exit(env) => { + new_env = Some(env); + } + }, + Res::Exit(status) => { + exit_status = Some(status.unwrap()); + } + } } + event_w.send(Event::ChildExit( + idx, + ExitInfo::new(exit_status.unwrap()), + new_env, + )); } +} - pub async fn finish( - &mut self, - env: Env, - event_w: async_std::channel::Sender<Event>, - ) { - self.state = State::Exited(ExitInfo::new(env.latest_status())); - self.env = env; - event_w.send(Event::PtyClose).await.unwrap(); - } +enum State { + Running((usize, usize)), + Exited(ExitInfo), +} +impl State { fn exit_info(&self) -> Option<&ExitInfo> { - match &self.state { - State::Running(..) => None, - State::Exited(exit_info) => Some(exit_info), + match self { + Self::Running(_) => None, + Self::Exited(exit_info) => Some(exit_info), } } - fn bell(&mut self, out: &mut impl textmode::Textmode) { - if self.real_bell_pending { - if self.audible_bell { - out.write(b"\x07"); - } - if self.visual_bell { - out.write(b"\x1bg"); - } - self.real_bell_pending = false; - } + fn running(&self) -> bool { + self.exit_info().is_none() } } -struct ExitInfo { - status: async_std::process::ExitStatus, +#[derive(Debug)] +pub struct ExitInfo { + status: std::process::ExitStatus, instant: std::time::Instant, } impl ExitInfo { - fn new(status: async_std::process::ExitStatus) -> Self { + fn new(status: std::process::ExitStatus) -> Self { Self { status, instant: std::time::Instant::now(), } } } - -fn set_bgcolor(out: &mut impl textmode::Textmode, idx: usize, focus: bool) { - if focus { - out.set_bgcolor(textmode::Color::Rgb(0x56, 0x1b, 0x8b)); - } else if idx % 2 == 0 { - out.set_bgcolor(textmode::Color::Rgb(0x24, 0x21, 0x00)); - } else { - out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); - } -} diff --git a/src/shell/history/mod.rs b/src/shell/history/mod.rs index 1bc4e62..91149c1 100644 --- a/src/shell/history/mod.rs +++ b/src/shell/history/mod.rs @@ -1,12 +1,12 @@ use crate::shell::prelude::*; mod entry; -pub use entry::Entry; +pub use entry::{Entry, ExitInfo}; mod pty; pub struct History { size: (u16, u16), - entries: Vec<crate::mutex::Mutex<Entry>>, + entries: Vec<Entry>, scroll_pos: usize, } @@ -19,31 +19,27 @@ impl History { } } - pub async fn render( + pub fn render( &self, out: &mut impl textmode::Textmode, repl_lines: usize, focus: Option<usize>, scrolling: bool, offset: time::UtcOffset, - ) -> anyhow::Result<()> { - let mut used_lines = repl_lines; + ) { let mut cursor = None; - for (idx, mut entry) in - self.visible(repl_lines, focus, scrolling).await.rev() + for (idx, used_lines, mut vt) in + self.visible(repl_lines, focus, scrolling).rev() { let focused = focus.map_or(false, |focus| idx == focus); - used_lines += - entry.lines(self.entry_count(), focused && !scrolling); out.move_to( (usize::from(self.size.0) - used_lines).try_into().unwrap(), 0, ); - entry.render( + self.entries[idx].render( out, - idx, self.entry_count(), - self.size, + &mut *vt, focused, scrolling, offset, @@ -59,67 +55,38 @@ impl History { out.move_to(pos.0, pos.1); out.hide_cursor(hide); } - Ok(()) } - pub async fn render_fullscreen( - &self, - out: &mut impl textmode::Textmode, - idx: usize, - ) { - let mut entry = self.entries[idx].lock_arc().await; - entry.render_fullscreen(out); + pub fn entry(&self, idx: usize) -> &Entry { + &self.entries[idx] } - pub async fn send_input(&mut self, idx: usize, input: Vec<u8>) { - self.entry(idx).await.send_input(input).await; + pub fn entry_mut(&mut self, idx: usize) -> &mut Entry { + &mut self.entries[idx] } - pub async fn resize(&mut self, size: (u16, u16)) { + pub fn resize(&mut self, size: (u16, u16)) { self.size = size; for entry in &self.entries { - entry.lock_arc().await.resize(size).await; + entry.resize(size); } } - pub async fn run( + pub fn run( &mut self, - cmdline: &str, - env: &Env, - event_w: async_std::channel::Sender<Event>, - ) -> anyhow::Result<usize> { - let (input_w, input_r) = async_std::channel::unbounded(); - let (resize_w, resize_r) = async_std::channel::unbounded(); - - let entry = crate::mutex::new(Entry::new( - cmdline.to_string(), - env.clone(), - self.size, - input_w, - resize_w, - )); - run_commands( - cmdline.to_string(), - crate::mutex::clone(&entry), - env.clone(), - input_r, - resize_r, - event_w, - ); - - self.entries.push(entry); - Ok(self.entries.len() - 1) - } - - pub async fn entry(&self, idx: usize) -> crate::mutex::Guard<Entry> { - self.entries[idx].lock_arc().await + cmdline: String, + env: Env, + event_w: crate::shell::event::Writer, + ) { + self.entries + .push(Entry::new(cmdline, env, self.size, event_w).unwrap()); } pub fn entry_count(&self) -> usize { self.entries.len() } - pub async fn make_focus_visible( + pub fn make_focus_visible( &mut self, repl_lines: usize, focus: Option<usize>, @@ -134,8 +101,7 @@ impl History { while focus < self .visible(repl_lines, Some(focus), scrolling) - .await - .map(|(idx, _)| idx) + .map(|(idx, ..)| idx) .next() .unwrap() { @@ -149,8 +115,7 @@ impl History { while focus > self .visible(repl_lines, Some(focus), scrolling) - .await - .map(|(idx, _)| idx) + .map(|(idx, ..)| idx) .last() .unwrap() { @@ -158,225 +123,86 @@ impl History { } } - async fn visible( + pub async fn save(&self) { + // TODO: we'll probably want some amount of flock or something here + let mut fh = tokio::fs::OpenOptions::new() + .append(true) + .open(crate::dirs::history_file()) + .await + .unwrap(); + for entry in &self.entries { + fh.write_all( + format!( + ": {}:0;{}\n", + entry.start_time().unix_timestamp(), + entry.cmd() + ) + .as_bytes(), + ) + .await + .unwrap(); + } + } + + fn visible( &self, repl_lines: usize, focus: Option<usize>, scrolling: bool, ) -> VisibleEntries { let mut iter = VisibleEntries::new(); - if self.entries.is_empty() { - return iter; - } - let mut used_lines = repl_lines; for (idx, entry) in self.entries.iter().enumerate().rev().skip(self.scroll_pos) { - let entry = entry.lock_arc().await; let focused = focus.map_or(false, |focus| idx == focus); used_lines += entry.lines(self.entry_count(), focused && !scrolling); if used_lines > usize::from(self.size.0) { break; } - iter.add(idx, entry); + iter.add(idx, used_lines, entry.lock_vt()); } iter } } -struct VisibleEntries { - entries: std::collections::VecDeque<(usize, crate::mutex::Guard<Entry>)>, +struct VisibleEntries<'a> { + entries: std::collections::VecDeque<( + usize, + usize, + std::sync::MutexGuard<'a, pty::Vt>, + )>, } -impl VisibleEntries { +impl<'a> VisibleEntries<'a> { fn new() -> Self { Self { entries: std::collections::VecDeque::new(), } } - fn add(&mut self, idx: usize, entry: crate::mutex::Guard<Entry>) { + fn add( + &mut self, + idx: usize, + offset: usize, + vt: std::sync::MutexGuard<'a, pty::Vt>, + ) { // push_front because we are adding them in reverse order - self.entries.push_front((idx, entry)); + self.entries.push_front((idx, offset, vt)); } } -impl std::iter::Iterator for VisibleEntries { - type Item = (usize, crate::mutex::Guard<Entry>); +impl<'a> std::iter::Iterator for VisibleEntries<'a> { + type Item = (usize, usize, std::sync::MutexGuard<'a, pty::Vt>); fn next(&mut self) -> Option<Self::Item> { self.entries.pop_front() } } -impl std::iter::DoubleEndedIterator for VisibleEntries { +impl<'a> std::iter::DoubleEndedIterator for VisibleEntries<'a> { fn next_back(&mut self) -> Option<Self::Item> { self.entries.pop_back() } } - -fn run_commands( - cmdline: String, - entry: crate::mutex::Mutex<Entry>, - mut env: Env, - input_r: async_std::channel::Receiver<Vec<u8>>, - resize_r: async_std::channel::Receiver<(u16, u16)>, - event_w: async_std::channel::Sender<Event>, -) { - async_std::task::spawn(async move { - let pty = match pty::Pty::new( - entry.lock_arc().await.size(), - &entry, - input_r, - resize_r, - event_w.clone(), - ) { - Ok(pty) => pty, - Err(e) => { - let mut entry = entry.lock_arc().await; - entry.process( - format!("nbsh: failed to allocate pty: {}\r\n", e) - .as_bytes(), - ); - env.set_status(async_std::process::ExitStatus::from_raw( - 1 << 8, - )); - entry.finish(env, event_w).await; - return; - } - }; - - let status = - match spawn_commands(&cmdline, &pty, &mut env, event_w.clone()) - .await - { - Ok(status) => status, - Err(e) => { - let mut entry = entry.lock_arc().await; - entry.process( - format!( - "nbsh: failed to spawn {}: {}\r\n", - cmdline, e - ) - .as_bytes(), - ); - env.set_status(async_std::process::ExitStatus::from_raw( - 1 << 8, - )); - entry.finish(env, event_w).await; - return; - } - }; - env.set_status(status); - - entry.lock_arc().await.finish(env, event_w).await; - pty.close().await; - }); -} - -async fn spawn_commands( - cmdline: &str, - pty: &pty::Pty, - env: &mut Env, - event_w: async_std::channel::Sender<Event>, -) -> anyhow::Result<async_std::process::ExitStatus> { - let mut cmd = pty_process::Command::new(std::env::current_exe()?); - cmd.args(&["-c", cmdline, "--status-fd", "3"]); - env.apply(&mut cmd); - let (from_r, from_w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; - // Safety: dup2 is an async-signal-safe function - unsafe { - cmd.pre_exec(move || { - nix::unistd::dup2(from_w, 3)?; - Ok(()) - }); - } - let child = pty.spawn(cmd)?; - nix::unistd::close(from_w)?; - - let (read_w, read_r) = async_std::channel::unbounded(); - let new_read = move || { - let read_w = read_w.clone(); - async_std::task::spawn(async move { - let event = blocking::unblock(move || { - // Safety: from_r was just opened above and is only - // referenced in this closure, which takes ownership of it - // at the start and returns ownership of it at the end - let fh = unsafe { std::fs::File::from_raw_fd(from_r) }; - let event = bincode::deserialize_from(&fh); - let _ = fh.into_raw_fd(); - event - }) - .await; - if read_w.is_closed() { - // we should never drop read_r while there are still valid - // things to read - assert!(event.is_err()); - } else { - read_w.send(event).await.unwrap(); - } - }); - }; - - new_read(); - let mut read_done = false; - let mut exit_done = None; - loop { - enum Res { - Read(bincode::Result<crate::runner::Event>), - Exit(std::io::Result<std::process::ExitStatus>), - } - - let read_r = read_r.clone(); - let read = async move { Res::Read(read_r.recv().await.unwrap()) }; - let exit = async { - Res::Exit(if exit_done.is_none() { - child.status_no_drop().await - } else { - std::future::pending().await - }) - }; - match read.or(exit).await { - Res::Read(Ok(event)) => match event { - crate::runner::Event::RunPipeline(idx, span) => { - event_w - .send(Event::ChildRunPipeline(idx, span)) - .await - .unwrap(); - new_read(); - } - crate::runner::Event::Suspend(idx) => { - event_w.send(Event::ChildSuspend(idx)).await.unwrap(); - new_read(); - } - crate::runner::Event::Exit(new_env) => { - *env = new_env; - read_done = true; - } - }, - Res::Read(Err(e)) => { - if let bincode::ErrorKind::Io(io_e) = &*e { - if io_e.kind() == std::io::ErrorKind::UnexpectedEof { - read_done = true; - } else { - anyhow::bail!(e); - } - } else { - anyhow::bail!(e); - } - } - Res::Exit(Ok(status)) => { - exit_done = Some(status); - } - Res::Exit(Err(e)) => { - anyhow::bail!(e); - } - } - if let (true, Some(status)) = (read_done, exit_done) { - nix::unistd::close(from_r)?; - return Ok(status); - } - } -} diff --git a/src/shell/history/pty.rs b/src/shell/history/pty.rs index 0fe0942..cef4ca9 100644 --- a/src/shell/history/pty.rs +++ b/src/shell/history/pty.rs @@ -1,106 +1,196 @@ use crate::shell::prelude::*; +#[derive(Debug)] +enum Request { + Input(Vec<u8>), + Resize(u16, u16), +} + pub struct Pty { - pty: async_std::sync::Arc<pty_process::Pty>, - close_w: async_std::channel::Sender<()>, + vt: std::sync::Arc<std::sync::Mutex<Vt>>, + request_w: tokio::sync::mpsc::UnboundedSender<Request>, } impl Pty { pub fn new( size: (u16, u16), - entry: &crate::mutex::Mutex<super::Entry>, - input_r: async_std::channel::Receiver<Vec<u8>>, - resize_r: async_std::channel::Receiver<(u16, u16)>, - event_w: async_std::channel::Sender<Event>, - ) -> anyhow::Result<Self> { - let (close_w, close_r) = async_std::channel::unbounded(); + event_w: crate::shell::event::Writer, + ) -> Result<(Self, pty_process::Pts)> { + let (request_w, request_r) = tokio::sync::mpsc::unbounded_channel(); let pty = pty_process::Pty::new()?; pty.resize(pty_process::Size::new(size.0, size.1))?; - let pty = async_std::sync::Arc::new(pty); - - async_std::task::spawn(pty_task( - async_std::sync::Arc::clone(&pty), - crate::mutex::clone(entry), - input_r, - resize_r, - close_r, + let pts = pty.pts()?; + + let vt = std::sync::Arc::new(std::sync::Mutex::new(Vt::new(size))); + + tokio::spawn(Self::task( + pty, + std::sync::Arc::clone(&vt), + request_r, event_w, )); - Ok(Self { pty, close_w }) + Ok((Self { vt, request_w }, pts)) } - pub fn spawn( - &self, - mut cmd: pty_process::Command, - ) -> anyhow::Result<async_std::process::Child> { - Ok(cmd.spawn(&self.pty)?) + pub fn with_vt<T>(&self, f: impl FnOnce(&Vt) -> T) -> T { + let vt = self.vt.lock().unwrap(); + f(&*vt) } - pub async fn close(&self) { - self.close_w.send(()).await.unwrap(); + pub fn with_vt_mut<T>(&self, f: impl FnOnce(&mut Vt) -> T) -> T { + let mut vt = self.vt.lock().unwrap(); + f(&mut *vt) + } + + pub fn lock_vt(&self) -> std::sync::MutexGuard<Vt> { + self.vt.lock().unwrap() + } + + pub fn fullscreen(&self) -> bool { + self.with_vt(|vt| vt.screen().alternate_screen()) + } + + pub fn input(&self, bytes: Vec<u8>) { + #[allow(clippy::let_underscore_drop)] + let _ = self.request_w.send(Request::Input(bytes)); + } + + pub fn resize(&self, size: (u16, u16)) { + #[allow(clippy::let_underscore_drop)] + let _ = self.request_w.send(Request::Resize(size.0, size.1)); } -} -async fn pty_task( - pty: async_std::sync::Arc<pty_process::Pty>, - entry: crate::mutex::Mutex<super::Entry>, - input_r: async_std::channel::Receiver<Vec<u8>>, - resize_r: async_std::channel::Receiver<(u16, u16)>, - close_r: async_std::channel::Receiver<()>, - event_w: async_std::channel::Sender<Event>, -) { - loop { + async fn task( + pty: pty_process::Pty, + vt: std::sync::Arc<std::sync::Mutex<Vt>>, + request_r: tokio::sync::mpsc::UnboundedReceiver<Request>, + event_w: crate::shell::event::Writer, + ) { enum Res { - Read(Result<usize, std::io::Error>), - Write(Result<Vec<u8>, async_std::channel::RecvError>), - Resize(Result<(u16, u16), async_std::channel::RecvError>), - Close(Result<(), async_std::channel::RecvError>), + Read(Result<bytes::Bytes, std::io::Error>), + Request(Request), } - let mut buf = [0_u8; 4096]; - let read = async { Res::Read((&*pty).read(&mut buf).await) }; - let write = async { Res::Write(input_r.recv().await) }; - let resize = async { Res::Resize(resize_r.recv().await) }; - let close = async { Res::Close(close_r.recv().await) }; - match read.race(write).race(resize).or(close).await { - Res::Read(res) => match res { - Ok(bytes) => { - entry.lock_arc().await.process(&buf[..bytes]); - event_w.send(Event::PtyOutput).await.unwrap(); - } - Err(e) => { - if e.raw_os_error() != Some(libc::EIO) { + + let (pty_r, mut pty_w) = pty.into_split(); + let mut stream: futures_util::stream::SelectAll<_> = [ + tokio_util::io::ReaderStream::new(pty_r) + .map(Res::Read) + .boxed(), + tokio_stream::wrappers::UnboundedReceiverStream::new(request_r) + .map(Res::Request) + .boxed(), + ] + .into_iter() + .collect(); + while let Some(res) = stream.next().await { + match res { + Res::Read(res) => match res { + Ok(bytes) => { + vt.lock().unwrap().process(&bytes); + event_w.send(Event::PtyOutput); + } + Err(e) => { + // this means that there are no longer any open pts + // fds. we could alternately signal this through an + // explicit channel at ChildExit time, but this seems + // reliable enough. + if e.raw_os_error() == Some(libc::EIO) { + return; + } panic!("pty read failed: {:?}", e); } + }, + Res::Request(Request::Input(bytes)) => { + pty_w.write(&bytes).await.unwrap(); } - }, - Res::Write(res) => match res { - Ok(bytes) => { - (&*pty).write(&bytes).await.unwrap(); - } - Err(e) => { - panic!("failed to read from input channel: {}", e); - } - }, - Res::Resize(res) => match res { - Ok(size) => { - pty.resize(pty_process::Size::new(size.0, size.1)) - .unwrap(); - } - Err(e) => { - panic!("failed to read from resize channel: {}", e); + Res::Request(Request::Resize(row, col)) => { + pty_w.resize(pty_process::Size::new(row, col)).unwrap(); + vt.lock().unwrap().set_size((row, col)); } - }, - Res::Close(res) => match res { - Ok(()) => { - event_w.send(Event::PtyClose).await.unwrap(); - return; - } - Err(e) => { - panic!("failed to read from close channel: {}", e); - } - }, + } + } + } +} + +pub struct Vt { + vt: vt100::Parser, + bell_state: usize, + bell: bool, + real_bell_pending: bool, +} + +impl Vt { + pub fn new(size: (u16, u16)) -> Self { + Self { + vt: vt100::Parser::new(size.0, size.1, 0), + bell_state: 0, + bell: false, + real_bell_pending: false, + } + } + + pub fn process(&mut self, bytes: &[u8]) { + self.vt.process(bytes); + let screen = self.vt.screen(); + + let new_bell_state = screen.audible_bell_count(); + if new_bell_state != self.bell_state { + self.bell = true; + self.real_bell_pending = true; + self.bell_state = new_bell_state; + } + } + + pub fn screen(&self) -> &vt100::Screen { + self.vt.screen() + } + + pub fn set_size(&mut self, size: (u16, u16)) { + self.vt.set_size(size.0, size.1); + } + + pub fn is_bell(&self) -> bool { + self.bell + } + + pub fn bell(&mut self, focused: bool) -> bool { + let mut should = false; + if self.real_bell_pending { + if self.bell { + should = true; + } + self.real_bell_pending = false; + } + if focused { + self.bell = false; + } + should + } + + pub fn binary(&self) -> bool { + self.vt.screen().errors() > 5 + } + + pub fn output_lines(&self, focused: bool, running: bool) -> usize { + if self.binary() { + return 1; + } + + let screen = self.vt.screen(); + let mut last_row = 0; + for (idx, row) in screen.rows(0, screen.size().1).enumerate() { + if !row.is_empty() { + last_row = idx + 1; + } + } + if focused && running { + last_row = std::cmp::max( + last_row, + usize::from(screen.cursor_position().0) + 1, + ); } + last_row } } diff --git a/src/shell/inputs/clock.rs b/src/shell/inputs/clock.rs new file mode 100644 index 0000000..250466e --- /dev/null +++ b/src/shell/inputs/clock.rs @@ -0,0 +1,27 @@ +use crate::shell::prelude::*; + +pub struct Handler; + +impl Handler { + pub fn new(event_w: crate::shell::event::Writer) -> Self { + tokio::spawn(Self::task(event_w)); + Self + } + + async fn task(event_w: crate::shell::event::Writer) { + let now_clock = time::OffsetDateTime::now_utc(); + let now_instant = tokio::time::Instant::now(); + let mut interval = tokio::time::interval_at( + now_instant + + std::time::Duration::from_nanos( + 1_000_000_000_u64 + .saturating_sub(now_clock.nanosecond().into()), + ), + std::time::Duration::from_secs(1), + ); + loop { + interval.tick().await; + event_w.send(Event::ClockTimer); + } + } +} diff --git a/src/shell/git.rs b/src/shell/inputs/git.rs index 48e5eea..dbae1c4 100644 --- a/src/shell/git.rs +++ b/src/shell/inputs/git.rs @@ -1,3 +1,76 @@ +use crate::shell::prelude::*; + +use notify::Watcher as _; + +pub struct Handler { + git_w: tokio::sync::mpsc::UnboundedSender<std::path::PathBuf>, +} + +impl Handler { + pub fn new(event_w: crate::shell::event::Writer) -> Self { + let (git_w, git_r) = tokio::sync::mpsc::unbounded_channel(); + tokio::spawn(Self::task(git_r, event_w)); + Self { git_w } + } + + pub fn new_dir(&self, path: std::path::PathBuf) { + self.git_w.send(path).unwrap(); + } + + async fn task( + mut git_r: tokio::sync::mpsc::UnboundedReceiver<std::path::PathBuf>, + event_w: crate::shell::event::Writer, + ) { + // clippy can't tell that we assign to this later + #[allow(clippy::no_effect_underscore_binding)] + let mut _active_watcher = None; + while let Some(mut dir) = git_r.recv().await { + while let Ok(newer_dir) = git_r.try_recv() { + dir = newer_dir; + } + let repo = git2::Repository::discover(&dir).ok(); + if repo.is_some() { + let (sync_watch_w, sync_watch_r) = std::sync::mpsc::channel(); + let (watch_w, mut watch_r) = + tokio::sync::mpsc::unbounded_channel(); + let mut watcher = + notify::recommended_watcher(sync_watch_w).unwrap(); + watcher + .watch(&dir, notify::RecursiveMode::Recursive) + .unwrap(); + tokio::task::spawn_blocking(move || { + while let Ok(event) = sync_watch_r.recv() { + if watch_w.send(event).is_err() { + break; + } + } + }); + let event_w = event_w.clone(); + tokio::spawn(async move { + while watch_r.recv().await.is_some() { + let repo = git2::Repository::discover(&dir).ok(); + let info = tokio::task::spawn_blocking(|| { + repo.map(|repo| Info::new(&repo)) + }) + .await + .unwrap(); + event_w.send(Event::GitInfo(info)); + } + }); + _active_watcher = Some(watcher); + } else { + _active_watcher = None; + } + let info = tokio::task::spawn_blocking(|| { + repo.map(|repo| Info::new(&repo)) + }) + .await + .unwrap(); + event_w.send(Event::GitInfo(info)); + } + } +} + #[derive(Debug)] pub struct Info { modified_files: bool, diff --git a/src/shell/inputs/mod.rs b/src/shell/inputs/mod.rs new file mode 100644 index 0000000..48590a2 --- /dev/null +++ b/src/shell/inputs/mod.rs @@ -0,0 +1,32 @@ +use crate::shell::prelude::*; + +mod clock; +mod git; +pub use git::Info as GitInfo; +mod signals; +mod stdin; + +pub struct Handler { + _clock: clock::Handler, + git: git::Handler, + _signals: signals::Handler, + _stdin: stdin::Handler, +} + +impl Handler { + pub fn new( + input: textmode::blocking::Input, + event_w: crate::shell::event::Writer, + ) -> Result<Self> { + Ok(Self { + _clock: clock::Handler::new(event_w.clone()), + git: git::Handler::new(event_w.clone()), + _signals: signals::Handler::new(event_w.clone())?, + _stdin: stdin::Handler::new(input, event_w), + }) + } + + pub fn new_dir(&self, path: std::path::PathBuf) { + self.git.new_dir(path); + } +} diff --git a/src/shell/inputs/signals.rs b/src/shell/inputs/signals.rs new file mode 100644 index 0000000..4b91273 --- /dev/null +++ b/src/shell/inputs/signals.rs @@ -0,0 +1,30 @@ +use crate::shell::prelude::*; + +pub struct Handler; + +impl Handler { + pub fn new(event_w: crate::shell::event::Writer) -> Result<Self> { + let signals = tokio::signal::unix::signal( + tokio::signal::unix::SignalKind::window_change(), + )?; + tokio::spawn(Self::task(signals, event_w)); + Ok(Self) + } + + async fn task( + mut signals: tokio::signal::unix::Signal, + event_w: crate::shell::event::Writer, + ) { + event_w.send(resize_event()); + while signals.recv().await.is_some() { + event_w.send(resize_event()); + } + } +} + +fn resize_event() -> Event { + Event::Resize(terminal_size::terminal_size().map_or( + (24, 80), + |(terminal_size::Width(w), terminal_size::Height(h))| (h, w), + )) +} diff --git a/src/shell/inputs/stdin.rs b/src/shell/inputs/stdin.rs new file mode 100644 index 0000000..b966307 --- /dev/null +++ b/src/shell/inputs/stdin.rs @@ -0,0 +1,17 @@ +use crate::shell::prelude::*; + +pub struct Handler; + +impl Handler { + pub fn new( + mut input: textmode::blocking::Input, + event_w: crate::shell::event::Writer, + ) -> Self { + std::thread::spawn(move || { + while let Some(key) = input.read_key().unwrap() { + event_w.send(Event::Key(key)); + } + }); + Self + } +} diff --git a/src/shell/mod.rs b/src/shell/mod.rs index f7080a4..fa7147b 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -1,16 +1,16 @@ use crate::shell::prelude::*; -use notify::Watcher as _; use textmode::Textmode as _; mod event; -mod git; mod history; +mod inputs; +mod old_history; mod prelude; mod readline; -pub async fn main() -> anyhow::Result<i32> { - let mut input = textmode::Input::new().await?; +pub async fn main() -> Result<i32> { + let mut input = textmode::blocking::Input::new()?; let mut output = textmode::Output::new().await?; // avoid the guards getting stuck in a task that doesn't run to @@ -18,163 +18,40 @@ pub async fn main() -> anyhow::Result<i32> { let _input_guard = input.take_raw_guard(); let _output_guard = output.take_screen_guard(); - let (event_w, event_r) = async_std::channel::unbounded(); + let (event_w, event_r) = event::channel(); - { - // nix::sys::signal::Signal is repr(i32) - #[allow(clippy::as_conversions)] - let signals = signal_hook_async_std::Signals::new(&[ - nix::sys::signal::Signal::SIGWINCH as i32, - ])?; - let event_w = event_w.clone(); - async_std::task::spawn(async move { - // nix::sys::signal::Signal is repr(i32) - #[allow(clippy::as_conversions)] - let mut signals = async_std::stream::once( - nix::sys::signal::Signal::SIGWINCH as i32, - ) - .chain(signals); - while signals.next().await.is_some() { - event_w - .send(Event::Resize( - terminal_size::terminal_size().map_or( - (24, 80), - |( - terminal_size::Width(w), - terminal_size::Height(h), - )| { (h, w) }, - ), - )) - .await - .unwrap(); - } - }); - } - - { - let event_w = event_w.clone(); - async_std::task::spawn(async move { - while let Some(key) = input.read_key().await.unwrap() { - event_w.send(Event::Key(key)).await.unwrap(); - } - }); - } - - // redraw the clock every second - { - let event_w = event_w.clone(); - async_std::task::spawn(async move { - let first_sleep = 1_000_000_000_u64.saturating_sub( - time::OffsetDateTime::now_utc().nanosecond().into(), - ); - async_std::task::sleep(std::time::Duration::from_nanos( - first_sleep, - )) - .await; - let mut interval = async_std::stream::interval( - std::time::Duration::from_secs(1), - ); - event_w.send(Event::ClockTimer).await.unwrap(); - while interval.next().await.is_some() { - event_w.send(Event::ClockTimer).await.unwrap(); - } - }); - } - - let (git_w, git_r): (async_std::channel::Sender<std::path::PathBuf>, _) = - async_std::channel::unbounded(); - { - let event_w = event_w.clone(); - let mut _active_watcher = None; - async_std::task::spawn(async move { - while let Ok(mut dir) = git_r.recv().await { - while let Ok(newer_dir) = git_r.try_recv() { - dir = newer_dir; - } - let repo = git2::Repository::discover(&dir).ok(); - if repo.is_some() { - let (sync_watch_w, sync_watch_r) = - std::sync::mpsc::channel(); - let (watch_w, watch_r) = async_std::channel::unbounded(); - let mut watcher = notify::RecommendedWatcher::new( - sync_watch_w, - std::time::Duration::from_millis(100), - ) - .unwrap(); - watcher - .watch(&dir, notify::RecursiveMode::Recursive) - .unwrap(); - async_std::task::spawn(blocking::unblock(move || { - while let Ok(event) = sync_watch_r.recv() { - let watch_w = watch_w.clone(); - let send_failed = - async_std::task::block_on(async move { - watch_w.send(event).await.is_err() - }); - if send_failed { - break; - } - } - })); - let event_w = event_w.clone(); - async_std::task::spawn(async move { - while watch_r.recv().await.is_ok() { - let repo = git2::Repository::discover(&dir).ok(); - let info = blocking::unblock(|| { - repo.map(|repo| git::Info::new(&repo)) - }) - .await; - if event_w - .send(Event::GitInfo(info)) - .await - .is_err() - { - break; - } - } - }); - _active_watcher = Some(watcher); - } else { - _active_watcher = None; - } - let info = blocking::unblock(|| { - repo.map(|repo| git::Info::new(&repo)) - }) - .await; - event_w.send(Event::GitInfo(info)).await.unwrap(); - } - }); - } + let inputs = inputs::Handler::new(input, event_w.clone()).unwrap(); let mut shell = Shell::new(crate::info::get_offset())?; let mut prev_dir = shell.env.pwd().to_path_buf(); - git_w.send(prev_dir.clone()).await.unwrap(); - let event_reader = event::Reader::new(event_r); - while let Some(event) = event_reader.recv().await { - let dir = shell.env().pwd(); - if dir != prev_dir { - prev_dir = dir.to_path_buf(); - git_w.send(dir.to_path_buf()).await.unwrap(); - } - match shell.handle_event(event, &event_w).await { + inputs.new_dir(prev_dir.clone()); + while let Some(event) = event_r.recv().await { + match shell.handle_event(event, &event_w) { Some(Action::Refresh) => { - shell.render(&mut output).await?; + shell.render(&mut output)?; output.refresh().await?; } Some(Action::HardRefresh) => { - shell.render(&mut output).await?; + shell.render(&mut output)?; output.hard_refresh().await?; } Some(Action::Resize(rows, cols)) => { output.set_size(rows, cols); - shell.render(&mut output).await?; + shell.render(&mut output)?; output.hard_refresh().await?; } Some(Action::Quit) => break, None => {} } + let dir = shell.env().pwd(); + if dir != prev_dir { + prev_dir = dir.to_path_buf(); + inputs.new_dir(dir.to_path_buf()); + } } + shell.history.save().await; + Ok(0) } @@ -201,8 +78,9 @@ pub enum Action { pub struct Shell { readline: readline::Readline, history: history::History, + old_history: old_history::History, env: Env, - git: Option<git::Info>, + git: Option<inputs::GitInfo>, focus: Focus, scene: Scene, escape: bool, @@ -211,13 +89,14 @@ pub struct Shell { } impl Shell { - pub fn new(offset: time::UtcOffset) -> anyhow::Result<Self> { + pub fn new(offset: time::UtcOffset) -> Result<Self> { let mut env = Env::new()?; env.set_var("SHELL", std::env::current_exe()?); env.set_var("TERM", "screen"); Ok(Self { readline: readline::Readline::new(), history: history::History::new(), + old_history: old_history::History::new(), env, git: None, focus: Focus::Readline, @@ -228,87 +107,76 @@ impl Shell { }) } - pub async fn render( - &self, - out: &mut impl textmode::Textmode, - ) -> anyhow::Result<()> { + pub fn render(&self, out: &mut impl textmode::Textmode) -> Result<()> { out.clear(); out.write(&vt100::Parser::default().screen().input_mode_formatted()); match self.scene { Scene::Readline => match self.focus { Focus::Readline => { - self.history - .render( + self.history.render( + out, + self.readline.lines(), + None, + false, + self.offset, + ); + self.readline.render( + out, + &self.env, + self.git.as_ref(), + true, + self.offset, + )?; + } + Focus::History(idx) => { + if self.hide_readline { + self.history.render( + out, + 0, + Some(idx), + false, + self.offset, + ); + } else { + self.history.render( out, self.readline.lines(), - None, + Some(idx), false, self.offset, - ) - .await?; - self.readline - .render( + ); + let pos = out.screen().cursor_position(); + self.readline.render( out, &self.env, self.git.as_ref(), - true, + false, self.offset, - ) - .await?; - } - Focus::History(idx) => { - if self.hide_readline { - self.history - .render(out, 0, Some(idx), false, self.offset) - .await?; - } else { - self.history - .render( - out, - self.readline.lines(), - Some(idx), - false, - self.offset, - ) - .await?; - let pos = out.screen().cursor_position(); - self.readline - .render( - out, - &self.env, - self.git.as_ref(), - false, - self.offset, - ) - .await?; + )?; out.move_to(pos.0, pos.1); } } Focus::Scrolling(idx) => { - self.history - .render( - out, - self.readline.lines(), - idx, - true, - self.offset, - ) - .await?; - self.readline - .render( - out, - &self.env, - self.git.as_ref(), - idx.is_none(), - self.offset, - ) - .await?; + self.history.render( + out, + self.readline.lines(), + idx, + true, + self.offset, + ); + self.readline.render( + out, + &self.env, + self.git.as_ref(), + idx.is_none(), + self.offset, + )?; out.hide_cursor(true); } }, Scene::Fullscreen => { if let Focus::History(idx) = self.focus { - self.history.render_fullscreen(out, idx).await; + self.history.entry(idx).render_fullscreen(out); } else { unreachable!(); } @@ -317,79 +185,72 @@ impl Shell { Ok(()) } - pub async fn handle_event( + pub fn handle_event( &mut self, event: Event, - event_w: &async_std::channel::Sender<Event>, + event_w: &crate::shell::event::Writer, ) -> Option<Action> { match event { Event::Key(key) => { return if self.escape { self.escape = false; - self.handle_key_escape(key, event_w.clone()).await + self.handle_key_escape(&key, event_w.clone()) } else if key == textmode::Key::Ctrl(b'e') { self.escape = true; None } else { match self.focus { Focus::Readline => { - self.handle_key_readline(key, event_w.clone()) - .await + self.handle_key_readline(&key, event_w.clone()) } Focus::History(idx) => { - self.handle_key_history(key, idx).await; + self.handle_key_history(key, idx); None } Focus::Scrolling(_) => { - self.handle_key_escape(key, event_w.clone()).await + self.handle_key_escape(&key, event_w.clone()) } } }; } Event::Resize(new_size) => { - self.readline.resize(new_size).await; - self.history.resize(new_size).await; + self.readline.resize(new_size); + self.history.resize(new_size); return Some(Action::Resize(new_size.0, new_size.1)); } Event::PtyOutput => { // the number of visible lines may have changed, so make sure // the focus is still visible - self.history - .make_focus_visible( - self.readline.lines(), - self.focus_idx(), - matches!(self.focus, Focus::Scrolling(_)), - ) - .await; - self.scene = self.default_scene(self.focus, None).await; - } - Event::PtyClose => { - if let Some(idx) = self.focus_idx() { - let entry = self.history.entry(idx).await; - if !entry.running() { + self.history.make_focus_visible( + self.readline.lines(), + self.focus_idx(), + matches!(self.focus, Focus::Scrolling(_)), + ); + self.scene = self.default_scene(self.focus); + } + Event::ChildExit(idx, exit_info, env) => { + self.history.entry_mut(idx).exited(exit_info); + if self.focus_idx() == Some(idx) { + if let Some(env) = env { if self.hide_readline { let idx = self.env.idx(); - self.env = entry.env().clone(); + self.env = env; self.env.set_idx(idx); } - self.set_focus( - if self.hide_readline { - Focus::Readline - } else { - Focus::Scrolling(Some(idx)) - }, - Some(entry), - ) - .await; } + self.set_focus(if self.hide_readline { + Focus::Readline + } else { + Focus::Scrolling(Some(idx)) + }); } } Event::ChildRunPipeline(idx, span) => { - self.history.entry(idx).await.set_span(span); + self.history.entry_mut(idx).set_span(span); } Event::ChildSuspend(idx) => { if self.focus_idx() == Some(idx) { - self.set_focus(Focus::Readline, None).await; + self.set_focus(Focus::Readline); } } Event::GitInfo(info) => { @@ -400,18 +261,17 @@ impl Shell { Some(Action::Refresh) } - async fn handle_key_escape( + fn handle_key_escape( &mut self, - key: textmode::Key, - event_w: async_std::channel::Sender<Event>, + key: &textmode::Key, + event_w: crate::shell::event::Writer, ) -> Option<Action> { match key { textmode::Key::Ctrl(b'd') => { return Some(Action::Quit); } textmode::Key::Ctrl(b'e') => { - self.set_focus(Focus::Scrolling(self.focus_idx()), None) - .await; + self.set_focus(Focus::Scrolling(self.focus_idx())); } textmode::Key::Ctrl(b'l') => { return Some(Action::HardRefresh); @@ -419,48 +279,37 @@ impl Shell { textmode::Key::Ctrl(b'm') => { if let Some(idx) = self.focus_idx() { self.readline.clear_input(); - let entry = self.history.entry(idx).await; - let input = entry.cmd(); - let idx = self - .history - .run(input, &self.env, event_w.clone()) - .await - .unwrap(); - self.set_focus(Focus::History(idx), Some(entry)).await; + self.history.run( + self.history.entry(idx).cmd().to_string(), + self.env.clone(), + event_w, + ); + let idx = self.history.entry_count() - 1; + self.set_focus(Focus::History(idx)); self.hide_readline = true; self.env.set_idx(idx + 1); } else { - self.set_focus(Focus::Readline, None).await; + self.set_focus(Focus::Readline); } } textmode::Key::Char(' ') => { - let idx = self.focus_idx(); - let (focus, entry) = if let Some(idx) = idx { - let entry = self.history.entry(idx).await; - (entry.running(), Some(entry)) + if let Some(idx) = self.focus_idx() { + if self.history.entry(idx).running() { + self.set_focus(Focus::History(idx)); + } } else { - (true, None) - }; - if focus { - self.set_focus( - idx.map_or(Focus::Readline, |idx| { - Focus::History(idx) - }), - entry, - ) - .await; + self.set_focus(Focus::Readline); } } textmode::Key::Char('e') => { if let Focus::History(idx) = self.focus { - self.handle_key_history(textmode::Key::Ctrl(b'e'), idx) - .await; + self.handle_key_history(textmode::Key::Ctrl(b'e'), idx); } } textmode::Key::Char('f') => { if let Some(idx) = self.focus_idx() { - let mut entry = self.history.entry(idx).await; let mut focus = Focus::History(idx); + let entry = self.history.entry_mut(idx); if let Focus::Scrolling(_) = self.focus { entry.set_fullscreen(true); } else { @@ -469,38 +318,30 @@ impl Shell { focus = Focus::Scrolling(Some(idx)); } } - self.set_focus(focus, Some(entry)).await; + self.set_focus(focus); } } textmode::Key::Char('i') => { if let Some(idx) = self.focus_idx() { - let entry = self.history.entry(idx).await; - self.readline.set_input(entry.cmd()); - self.set_focus(Focus::Readline, Some(entry)).await; + self.readline + .set_input(self.history.entry(idx).cmd().to_string()); + self.set_focus(Focus::Readline); } } textmode::Key::Char('j') | textmode::Key::Down => { - self.set_focus( - Focus::Scrolling(self.scroll_down(self.focus_idx())), - None, - ) - .await; + self.set_focus(Focus::Scrolling(self.scroll_down())); } textmode::Key::Char('k') | textmode::Key::Up => { - self.set_focus( - Focus::Scrolling(self.scroll_up(self.focus_idx())), - None, - ) - .await; + self.set_focus(Focus::Scrolling(self.scroll_up())); } textmode::Key::Char('n') => { - self.set_focus(self.next_running().await, None).await; + self.set_focus(self.next_running()); } textmode::Key::Char('p') => { - self.set_focus(self.prev_running().await, None).await; + self.set_focus(self.prev_running()); } textmode::Key::Char('r') => { - self.set_focus(Focus::Readline, None).await; + self.set_focus(Focus::Readline); } _ => { return None; @@ -509,10 +350,10 @@ impl Shell { Some(Action::Refresh) } - async fn handle_key_readline( + fn handle_key_readline( &mut self, - key: textmode::Key, - event_w: async_std::channel::Sender<Event>, + key: &textmode::Key, + event_w: crate::shell::event::Writer, ) -> Option<Action> { match key { textmode::Key::Char(c) => { @@ -528,12 +369,13 @@ impl Shell { textmode::Key::Ctrl(b'm') => { let input = self.readline.input(); if !input.is_empty() { - let idx = self - .history - .run(input, &self.env, event_w.clone()) - .await - .unwrap(); - self.set_focus(Focus::History(idx), None).await; + self.history.run( + input.to_string(), + self.env.clone(), + event_w, + ); + let idx = self.history.entry_count() - 1; + self.set_focus(Focus::History(idx)); self.hide_readline = true; self.env.set_idx(idx + 1); self.readline.clear_input(); @@ -546,11 +388,7 @@ impl Shell { textmode::Key::Up => { let entry_count = self.history.entry_count(); if entry_count > 0 { - self.set_focus( - Focus::Scrolling(Some(entry_count - 1)), - None, - ) - .await; + self.set_focus(Focus::Scrolling(Some(entry_count - 1))); } } _ => return None, @@ -558,24 +396,15 @@ impl Shell { Some(Action::Refresh) } - async fn handle_key_history(&mut self, key: textmode::Key, idx: usize) { - self.history.send_input(idx, key.into_bytes()).await; + fn handle_key_history(&mut self, key: textmode::Key, idx: usize) { + self.history.entry(idx).input(key.into_bytes()); } - async fn default_scene( - &self, - focus: Focus, - entry: Option<crate::mutex::Guard<history::Entry>>, - ) -> Scene { + fn default_scene(&self, focus: Focus) -> Scene { match focus { Focus::Readline | Focus::Scrolling(_) => Scene::Readline, Focus::History(idx) => { - let fullscreen = if let Some(entry) = entry { - entry.should_fullscreen() - } else { - self.history.entry(idx).await.should_fullscreen() - }; - if fullscreen { + if self.history.entry(idx).should_fullscreen() { Scene::Fullscreen } else { Scene::Readline @@ -584,25 +413,15 @@ impl Shell { } } - async fn set_focus( - &mut self, - new_focus: Focus, - entry: Option<crate::mutex::Guard<history::Entry>>, - ) { + fn set_focus(&mut self, new_focus: Focus) { self.focus = new_focus; self.hide_readline = false; - self.scene = self.default_scene(new_focus, entry).await; - // passing entry into default_scene above consumes it, which means - // that the mutex lock will be dropped before we call into - // make_focus_visible, which is important because otherwise we might - // get a deadlock depending on what is visible - self.history - .make_focus_visible( - self.readline.lines(), - self.focus_idx(), - matches!(self.focus, Focus::Scrolling(_)), - ) - .await; + self.scene = self.default_scene(new_focus); + self.history.make_focus_visible( + self.readline.lines(), + self.focus_idx(), + matches!(self.focus, Focus::Scrolling(_)), + ); } fn env(&self) -> &Env { @@ -617,8 +436,8 @@ impl Shell { } } - fn scroll_up(&self, idx: Option<usize>) -> Option<usize> { - idx.map_or_else( + fn scroll_up(&self) -> Option<usize> { + self.focus_idx().map_or_else( || { let count = self.history.entry_count(); if count == 0 { @@ -631,8 +450,8 @@ impl Shell { ) } - fn scroll_down(&self, idx: Option<usize>) -> Option<usize> { - idx.and_then(|idx| { + fn scroll_down(&self) -> Option<usize> { + self.focus_idx().and_then(|idx| { if idx >= self.history.entry_count() - 1 { None } else { @@ -641,25 +460,25 @@ impl Shell { }) } - async fn next_running(&self) -> Focus { + fn next_running(&self) -> Focus { let count = self.history.entry_count(); let cur = self.focus_idx().unwrap_or(count); for idx in ((cur + 1)..count).chain(0..cur) { - if self.history.entry(idx).await.running() { + if self.history.entry(idx).running() { return Focus::History(idx); } } - self.focus_idx().map_or(Focus::Readline, Focus::History) + self.focus } - async fn prev_running(&self) -> Focus { + fn prev_running(&self) -> Focus { let count = self.history.entry_count(); let cur = self.focus_idx().unwrap_or(count); for idx in ((cur + 1)..count).chain(0..cur).rev() { - if self.history.entry(idx).await.running() { + if self.history.entry(idx).running() { return Focus::History(idx); } } - self.focus_idx().map_or(Focus::Readline, Focus::History) + self.focus } } diff --git a/src/shell/old_history.rs b/src/shell/old_history.rs new file mode 100644 index 0000000..49fd1c2 --- /dev/null +++ b/src/shell/old_history.rs @@ -0,0 +1,185 @@ +use crate::shell::prelude::*; + +use tokio::io::AsyncBufReadExt as _; + +use pest::Parser as _; + +#[derive(pest_derive::Parser)] +#[grammar = "history.pest"] +struct HistoryLine; + +pub struct History { + entries: std::sync::Arc<std::sync::Mutex<Vec<Entry>>>, +} + +impl History { + pub fn new() -> Self { + let entries = std::sync::Arc::new(std::sync::Mutex::new(vec![])); + tokio::spawn(Self::task(std::sync::Arc::clone(&entries))); + Self { entries } + } + + pub fn entry_count(&self) -> usize { + self.entries.lock().unwrap().len() + } + + async fn task(entries: std::sync::Arc<std::sync::Mutex<Vec<Entry>>>) { + // TODO: we should actually read this in reverse order, because we + // want to populate the most recent entries first + let mut stream = tokio_stream::wrappers::LinesStream::new( + tokio::io::BufReader::new( + tokio::fs::File::open(crate::dirs::history_file()) + .await + .unwrap(), + ) + .lines(), + ); + while let Some(line) = stream.next().await { + let line = if let Ok(line) = line { + line + } else { + continue; + }; + let entry = if let Ok(entry) = line.parse() { + entry + } else { + continue; + }; + entries.lock().unwrap().push(entry); + } + } +} + +pub struct Entry { + cmdline: String, + start_time: Option<time::OffsetDateTime>, + duration: Option<std::time::Duration>, +} + +impl Entry { + pub fn render( + &self, + out: &mut impl textmode::Textmode, + offset: time::UtcOffset, + ) { + let size = out.screen().size(); + let mut time = "".to_string(); + if let Some(duration) = self.duration { + time.push_str(&crate::format::duration(duration)); + } + if let Some(start_time) = self.start_time { + time.push_str(&crate::format::time(start_time.to_offset(offset))); + } + + out.write_str(" $ "); + let start = usize::from(out.screen().cursor_position().1); + let end = usize::from(size.1) - time.len() - 2; + let max_len = end - start; + let cmd = if self.cmdline.len() > max_len { + &self.cmdline[..(max_len - 4)] + } else { + &self.cmdline + }; + out.write_str(cmd); + if self.cmdline.len() > max_len { + out.write_str(" "); + out.set_fgcolor(textmode::color::BLUE); + out.write_str("..."); + } + out.reset_attributes(); + + out.set_bgcolor(textmode::Color::Rgb(0x20, 0x20, 0x20)); + let cur_pos = out.screen().cursor_position(); + out.write_str(&" ".repeat( + usize::from(size.1) - time.len() - 1 - usize::from(cur_pos.1), + )); + out.write_str(&time); + out.write_str(" "); + out.reset_attributes(); + } + + pub fn cmd(&self) -> &str { + &self.cmdline + } +} + +impl std::str::FromStr for Entry { + type Err = anyhow::Error; + + fn from_str(line: &str) -> std::result::Result<Self, Self::Err> { + let mut parsed = + HistoryLine::parse(Rule::line, line).map_err(|e| anyhow!(e))?; + let line = parsed.next().unwrap(); + assert!(matches!(line.as_rule(), Rule::line)); + + let mut start_time = None; + let mut duration = None; + let mut cmdline = None; + for part in line.into_inner() { + match part.as_rule() { + Rule::time => { + start_time = + Some(time::OffsetDateTime::from_unix_timestamp( + part.as_str().parse()?, + )?); + } + Rule::duration => { + if part.as_str() == "0" { + continue; + } + let mut dur_parts = part.as_str().split('.'); + let secs: u64 = dur_parts.next().unwrap().parse()?; + let nsec_str = dur_parts.next().unwrap_or("0"); + let nsec_str = &nsec_str[..9.min(nsec_str.len())]; + let nsecs: u64 = nsec_str.parse()?; + duration = Some(std::time::Duration::from_nanos( + secs * 1_000_000_000 + + nsecs + * (10u64.pow( + (9 - nsec_str.len()).try_into().unwrap(), + )), + )); + } + Rule::command => { + cmdline = Some(part.as_str().to_string()); + } + Rule::line => unreachable!(), + Rule::EOI => break, + } + } + + Ok(Self { + cmdline: cmdline.unwrap(), + start_time, + duration, + }) + } +} + +#[test] +fn test_parse() { + let entry: Entry = + ": 1646779848:1234.56;vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!( + entry.duration, + Some(std::time::Duration::from_nanos(1_234_560_000_000)) + ); + assert_eq!( + entry.start_time, + Some(time::macros::datetime!(2022-03-08 22:50:48).assume_utc()) + ); + + let entry: Entry = ": 1646779848:1;vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!(entry.duration, Some(std::time::Duration::from_secs(1))); + assert_eq!( + entry.start_time, + Some(time::macros::datetime!(2022-03-08 22:50:48).assume_utc()) + ); + + let entry: Entry = "vim ~/.zsh_history".parse().unwrap(); + assert_eq!(entry.cmdline, "vim ~/.zsh_history"); + assert_eq!(entry.duration, None); + assert_eq!(entry.start_time, None); +} diff --git a/src/shell/readline.rs b/src/shell/readline.rs index f0fb950..654d264 100644 --- a/src/shell/readline.rs +++ b/src/shell/readline.rs @@ -19,14 +19,14 @@ impl Readline { } } - pub async fn render( + pub fn render( &self, out: &mut impl textmode::Textmode, env: &Env, - git: Option<&super::git::Info>, + git: Option<&super::inputs::GitInfo>, focus: bool, offset: time::UtcOffset, - ) -> anyhow::Result<()> { + ) -> Result<()> { let pwd = env.pwd(); let user = crate::info::user()?; let hostname = crate::info::hostname()?; @@ -83,7 +83,7 @@ impl Readline { Ok(()) } - pub async fn resize(&mut self, size: (u16, u16)) { + pub fn resize(&mut self, size: (u16, u16)) { self.size = size; } @@ -102,9 +102,9 @@ impl Readline { self.inc_pos(s.chars().count()); } - pub fn set_input(&mut self, s: &str) { - self.input_line = s.to_string(); + pub fn set_input(&mut self, s: String) { self.set_pos(s.chars().count()); + self.input_line = s; } pub fn backspace(&mut self) { |