Implemented proper variable scoping
Extracted business logic out of signal handler functions Consolidated state variables into a single struct Implemented var types
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@ shell.nix
|
|||||||
*~
|
*~
|
||||||
TODO.md
|
TODO.md
|
||||||
rust-toolchain.toml
|
rust-toolchain.toml
|
||||||
|
/ref
|
||||||
|
|
||||||
# cachix tmp file
|
# cachix tmp file
|
||||||
store-path-pre-build
|
store-path-pre-build
|
||||||
|
|||||||
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -4,18 +4,18 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"anstyle-parse",
|
"anstyle-parse",
|
||||||
@@ -28,50 +28,50 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle"
|
name = "anstyle"
|
||||||
version = "1.0.10"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-parse"
|
name = "anstyle-parse"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"utf8parse",
|
"utf8parse",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-query"
|
name = "anstyle-query"
|
||||||
version = "1.1.2"
|
version = "1.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstyle-wincon"
|
name = "anstyle-wincon"
|
||||||
version = "3.0.7"
|
version = "3.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"once_cell",
|
"once_cell_polyfill",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.8.0"
|
version = "2.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg_aliases"
|
name = "cfg_aliases"
|
||||||
@@ -81,9 +81,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.38"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -91,9 +91,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.38"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -103,9 +103,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.32"
|
version = "4.5.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -115,15 +115,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.7.4"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
@@ -134,7 +134,7 @@ dependencies = [
|
|||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"windows-sys",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -149,6 +149,22 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fern"
|
name = "fern"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -165,10 +181,22 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glob"
|
name = "getrandom"
|
||||||
version = "0.3.2"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
@@ -178,40 +206,39 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.42.2"
|
version = "1.46.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
|
checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"linked-hash-map",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project",
|
|
||||||
"similar",
|
"similar",
|
||||||
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.169"
|
version = "0.2.180"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linux-raw-sys"
|
||||||
version = "0.5.6"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
@@ -227,29 +254,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.1"
|
version = "1.21.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "once_cell_polyfill"
|
||||||
version = "1.1.10"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
dependencies = [
|
|
||||||
"pin-project-internal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-internal"
|
|
||||||
version = "1.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
@@ -263,27 +276,33 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.93"
|
version = "1.0.106"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.38"
|
version = "1.0.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "r-efi"
|
||||||
version = "1.11.1"
|
version = "5.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -293,9 +312,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.9"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -304,9 +323,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.5"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
@@ -322,9 +354,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.98"
|
version = "2.0.114"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -332,10 +364,23 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "tempfile"
|
||||||
version = "1.0.17"
|
version = "3.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
@@ -345,9 +390,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.0"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
@@ -355,6 +400,21 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.2+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.59.0"
|
version = "0.59.0"
|
||||||
@@ -364,6 +424,15 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@@ -428,6 +497,12 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.51.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yansi"
|
name = "yansi"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ description = "A linux shell written in rust"
|
|||||||
publish = false
|
publish = false
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
|
|
||||||
env::set_current_dir(new_dir).unwrap();
|
env::set_current_dir(new_dir).unwrap();
|
||||||
let new_dir = env::current_dir().unwrap();
|
let new_dir = env::current_dir().unwrap();
|
||||||
env::set_var("PWD", new_dir);
|
unsafe { env::set_var("PWD", new_dir) };
|
||||||
|
|
||||||
state::set_status(0);
|
state::set_status(0);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
libsh::error::ShResult,
|
libsh::error::ShResult,
|
||||||
parse::{NdRule, Node},
|
parse::{NdRule, Node},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{borrow_fd, IoStack},
|
procio::{IoStack, borrow_fd},
|
||||||
state::{self, write_vars},
|
state::{self, VarFlags, write_vars},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
@@ -34,7 +34,7 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
|||||||
} else {
|
} else {
|
||||||
for (arg, _) in argv {
|
for (arg, _) in argv {
|
||||||
if let Some((var, val)) = arg.split_once('=') {
|
if let Some((var, val)) = arg.split_once('=') {
|
||||||
write_vars(|v| v.set_var(var, val, true)); // Export an assignment like
|
write_vars(|v| v.set_var(var, val, VarFlags::EXPORT)); // Export an assignment like
|
||||||
// 'foo=bar'
|
// 'foo=bar'
|
||||||
} else {
|
} else {
|
||||||
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
write_vars(|v| v.export_var(&arg)); // Export an existing variable, if
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub fn shift(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
for _ in 0..count {
|
for _ in 0..count {
|
||||||
write_vars(|v| v.fpop_arg());
|
write_vars(|v| v.cur_scope_mut().fpop_arg());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
336
src/expand.rs
336
src/expand.rs
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
use std::mem::take;
|
||||||
use std::str::{Chars, FromStr};
|
use std::str::{Chars, FromStr};
|
||||||
|
|
||||||
use glob::Pattern;
|
use glob::Pattern;
|
||||||
@@ -11,7 +12,7 @@ use crate::parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Tk, TkFl
|
|||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Redir, RedirType};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
use crate::procio::{IoBuf, IoFrame, IoMode, IoStack};
|
||||||
use crate::state::{read_jobs, read_vars, write_jobs, write_meta, write_vars, LogTab};
|
use crate::state::{LogTab, VarFlags, read_jobs, read_vars, write_jobs, write_meta, write_vars};
|
||||||
|
|
||||||
const PARAMETERS: [char; 7] = ['@', '*', '#', '$', '?', '!', '0'];
|
const PARAMETERS: [char; 7] = ['@', '*', '#', '$', '?', '!', '0'];
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ impl Tk {
|
|||||||
pub fn expand(self) -> ShResult<Self> {
|
pub fn expand(self) -> ShResult<Self> {
|
||||||
let flags = self.flags;
|
let flags = self.flags;
|
||||||
let span = self.span.clone();
|
let span = self.span.clone();
|
||||||
let exp = Expander::new(self).expand()?;
|
let exp = Expander::new(self)?.expand()?;
|
||||||
let class = TkRule::Expanded { exp };
|
let class = TkRule::Expanded { exp };
|
||||||
Ok(Self { class, span, flags })
|
Ok(Self { class, span, flags })
|
||||||
}
|
}
|
||||||
@@ -58,9 +59,11 @@ pub struct Expander {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Expander {
|
impl Expander {
|
||||||
pub fn new(raw: Tk) -> Self {
|
pub fn new(raw: Tk) -> ShResult<Self> {
|
||||||
let unescaped = unescape_str(raw.span.as_str());
|
let mut raw = raw.span.as_str().to_string();
|
||||||
Self { raw: unescaped }
|
raw = expand_braces_full(&raw)?.join(" ");
|
||||||
|
let unescaped = unescape_str(&raw);
|
||||||
|
Ok(Self { raw: unescaped })
|
||||||
}
|
}
|
||||||
pub fn expand(&mut self) -> ShResult<Vec<String>> {
|
pub fn expand(&mut self) -> ShResult<Vec<String>> {
|
||||||
let mut chars = self.raw.chars().peekable();
|
let mut chars = self.raw.chars().peekable();
|
||||||
@@ -100,6 +103,323 @@ impl Expander {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a string contains valid brace expansion patterns.
|
||||||
|
/// Returns true if there's a valid {a,b} or {1..5} pattern at the outermost level.
|
||||||
|
fn has_braces(s: &str) -> bool {
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
let mut depth = 0;
|
||||||
|
let mut found_open = false;
|
||||||
|
let mut has_comma = false;
|
||||||
|
let mut has_range = false;
|
||||||
|
let mut cur_quote: Option<char> = None;
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => { chars.next(); } // skip escaped char
|
||||||
|
'\'' if cur_quote.is_none() => cur_quote = Some('\''),
|
||||||
|
'\'' if cur_quote == Some('\'') => cur_quote = None,
|
||||||
|
'"' if cur_quote.is_none() => cur_quote = Some('"'),
|
||||||
|
'"' if cur_quote == Some('"') => cur_quote = None,
|
||||||
|
'{' if cur_quote.is_none() => {
|
||||||
|
if depth == 0 {
|
||||||
|
found_open = true;
|
||||||
|
has_comma = false;
|
||||||
|
has_range = false;
|
||||||
|
}
|
||||||
|
depth += 1;
|
||||||
|
}
|
||||||
|
'}' if cur_quote.is_none() && depth > 0 => {
|
||||||
|
depth -= 1;
|
||||||
|
if depth == 0 && found_open && (has_comma || has_range) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
',' if cur_quote.is_none() && depth == 1 => {
|
||||||
|
has_comma = true;
|
||||||
|
}
|
||||||
|
'.' if cur_quote.is_none() && depth == 1 => {
|
||||||
|
if chars.peek() == Some(&'.') {
|
||||||
|
chars.next();
|
||||||
|
has_range = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand braces in a string, zsh-style: one level per call, loop until done.
|
||||||
|
/// Returns a Vec of expanded strings.
|
||||||
|
fn expand_braces_full(input: &str) -> ShResult<Vec<String>> {
|
||||||
|
let mut results = vec![input.to_string()];
|
||||||
|
|
||||||
|
// Keep expanding until no results contain braces
|
||||||
|
loop {
|
||||||
|
let mut any_expanded = false;
|
||||||
|
let mut new_results = Vec::new();
|
||||||
|
|
||||||
|
for word in results {
|
||||||
|
if has_braces(&word) {
|
||||||
|
any_expanded = true;
|
||||||
|
let expanded = expand_one_brace(&word)?;
|
||||||
|
new_results.extend(expanded);
|
||||||
|
} else {
|
||||||
|
new_results.push(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results = new_results;
|
||||||
|
if !any_expanded {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand the first (outermost) brace expression in a word.
|
||||||
|
/// "pre{a,b}post" -> ["preapost", "prebpost"]
|
||||||
|
/// "pre{1..3}post" -> ["pre1post", "pre2post", "pre3post"]
|
||||||
|
fn expand_one_brace(word: &str) -> ShResult<Vec<String>> {
|
||||||
|
let (prefix, inner, suffix) = match get_brace_parts(word) {
|
||||||
|
Some(parts) => parts,
|
||||||
|
None => return Ok(vec![word.to_string()]), // No valid braces
|
||||||
|
};
|
||||||
|
|
||||||
|
// Split the inner content on top-level commas, or expand as range
|
||||||
|
let parts = split_brace_inner(&inner);
|
||||||
|
|
||||||
|
// If we got back a single part with no expansion, treat as literal
|
||||||
|
if parts.len() == 1 && parts[0] == inner {
|
||||||
|
// Check if it's a range
|
||||||
|
if let Some(range_parts) = try_expand_range(&inner) {
|
||||||
|
return Ok(range_parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| format!("{}{}{}", prefix, p, suffix))
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
// Not a valid brace expression, return as-is with literal braces
|
||||||
|
return Ok(vec![format!("{}{{{}}}{}", prefix, inner, suffix)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(parts
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| format!("{}{}{}", prefix, p, suffix))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract prefix, inner, and suffix from a brace expression.
|
||||||
|
/// "pre{a,b}post" -> Some(("pre", "a,b", "post"))
|
||||||
|
fn get_brace_parts(word: &str) -> Option<(String, String, String)> {
|
||||||
|
let mut chars = word.chars().enumerate().peekable();
|
||||||
|
let mut prefix = String::new();
|
||||||
|
let mut cur_quote: Option<char> = None;
|
||||||
|
let mut brace_start = None;
|
||||||
|
|
||||||
|
// Find the opening brace
|
||||||
|
while let Some((i, ch)) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
prefix.push(ch);
|
||||||
|
if let Some((_, next)) = chars.next() {
|
||||||
|
prefix.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\'' if cur_quote.is_none() => { cur_quote = Some('\'');
|
||||||
|
prefix.push(ch); }
|
||||||
|
'\'' if cur_quote == Some('\'') => { cur_quote = None;
|
||||||
|
prefix.push(ch); }
|
||||||
|
'"' if cur_quote.is_none() => { cur_quote = Some('"'); prefix.push(ch); }
|
||||||
|
'"' if cur_quote == Some('"') => { cur_quote = None; prefix.push(ch); }
|
||||||
|
'{' if cur_quote.is_none() => {
|
||||||
|
brace_start = Some(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => prefix.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let brace_start = brace_start?;
|
||||||
|
|
||||||
|
// Find matching closing brace
|
||||||
|
let mut depth = 1;
|
||||||
|
let mut inner = String::new();
|
||||||
|
cur_quote = None;
|
||||||
|
|
||||||
|
while let Some((_, ch)) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
inner.push(ch);
|
||||||
|
if let Some((_, next)) = chars.next() {
|
||||||
|
inner.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\'' if cur_quote.is_none() => { cur_quote = Some('\''); inner.push(ch); }
|
||||||
|
'\'' if cur_quote == Some('\'') => { cur_quote = None; inner.push(ch); }
|
||||||
|
'"' if cur_quote.is_none() => { cur_quote = Some('"'); inner.push(ch); }
|
||||||
|
'"' if cur_quote == Some('"') => { cur_quote = None; inner.push(ch); }
|
||||||
|
'{' if cur_quote.is_none() => {
|
||||||
|
depth += 1;
|
||||||
|
inner.push(ch);
|
||||||
|
}
|
||||||
|
'}' if cur_quote.is_none() => {
|
||||||
|
depth -= 1;
|
||||||
|
if depth == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
inner.push(ch);
|
||||||
|
}
|
||||||
|
_ => inner.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth != 0 {
|
||||||
|
return None; // Unbalanced braces
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect suffix
|
||||||
|
let suffix: String = chars.map(|(_, c)| c).collect();
|
||||||
|
|
||||||
|
Some((prefix, inner, suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split brace inner content on top-level commas.
|
||||||
|
/// "a,b,c" -> ["a", "b", "c"]
|
||||||
|
/// "a,{b,c},d" -> ["a", "{b,c}", "d"]
|
||||||
|
fn split_brace_inner(inner: &str) -> Vec<String> {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut chars = inner.chars().peekable();
|
||||||
|
let mut depth = 0;
|
||||||
|
let mut cur_quote: Option<char> = None;
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
current.push(ch);
|
||||||
|
if let Some(next) = chars.next() {
|
||||||
|
current.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\'' if cur_quote.is_none() => { cur_quote = Some('\''); current.push(ch); }
|
||||||
|
'\'' if cur_quote == Some('\'') => { cur_quote = None; current.push(ch); }
|
||||||
|
'"' if cur_quote.is_none() => { cur_quote = Some('"'); current.push(ch); }
|
||||||
|
'"' if cur_quote == Some('"') => { cur_quote = None; current.push(ch); }
|
||||||
|
'{' if cur_quote.is_none() => {
|
||||||
|
depth += 1;
|
||||||
|
current.push(ch);
|
||||||
|
}
|
||||||
|
'}' if cur_quote.is_none() => {
|
||||||
|
depth -= 1;
|
||||||
|
current.push(ch);
|
||||||
|
}
|
||||||
|
',' if cur_quote.is_none() && depth == 0 => {
|
||||||
|
parts.push(std::mem::take(&mut current));
|
||||||
|
}
|
||||||
|
_ => current.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(current);
|
||||||
|
parts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to expand a range like "1..5" or "a..z" or "1..10..2"
|
||||||
|
fn try_expand_range(inner: &str) -> Option<Vec<String>> {
|
||||||
|
// Look for ".." pattern
|
||||||
|
let parts: Vec<&str> = inner.split("..").collect();
|
||||||
|
|
||||||
|
match parts.len() {
|
||||||
|
2 => {
|
||||||
|
let start = parts[0];
|
||||||
|
let end = parts[1];
|
||||||
|
expand_range(start, end, 1)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let start = parts[0];
|
||||||
|
let end = parts[1];
|
||||||
|
let step: i32 = parts[2].parse().ok()?;
|
||||||
|
if step == 0 { return None; }
|
||||||
|
expand_range(start, end, step.unsigned_abs() as usize)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_range(start: &str, end: &str, step: usize) ->
|
||||||
|
Option<Vec<String>> {
|
||||||
|
// Try character range first
|
||||||
|
if is_alpha_range_bound(start) && is_alpha_range_bound(end) {
|
||||||
|
let start_char = start.chars().next()? as u8;
|
||||||
|
let end_char = end.chars().next()? as u8;
|
||||||
|
let reverse = end_char < start_char;
|
||||||
|
|
||||||
|
let (lo, hi) = if reverse {
|
||||||
|
(end_char, start_char)
|
||||||
|
} else {
|
||||||
|
(start_char, end_char)
|
||||||
|
};
|
||||||
|
|
||||||
|
let chars: Vec<String> = (lo..=hi)
|
||||||
|
.step_by(step)
|
||||||
|
.map(|c| (c as char).to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Some(if reverse {
|
||||||
|
chars.into_iter().rev().collect()
|
||||||
|
} else {
|
||||||
|
chars
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try numeric range
|
||||||
|
if is_numeric_range_bound(start) && is_numeric_range_bound(end) {
|
||||||
|
let start_num: i32 = start.parse().ok()?;
|
||||||
|
let end_num: i32 = end.parse().ok()?;
|
||||||
|
let reverse = end_num < start_num;
|
||||||
|
|
||||||
|
// Handle zero-padding
|
||||||
|
let pad_width = start.len().max(end.len());
|
||||||
|
let needs_padding = start.starts_with('0') ||
|
||||||
|
end.starts_with('0');
|
||||||
|
|
||||||
|
let (lo, hi) = if reverse {
|
||||||
|
(end_num, start_num)
|
||||||
|
} else {
|
||||||
|
(start_num, end_num)
|
||||||
|
};
|
||||||
|
|
||||||
|
let nums: Vec<String> = (lo..=hi)
|
||||||
|
.step_by(step)
|
||||||
|
.map(|n| {
|
||||||
|
if needs_padding {
|
||||||
|
format!("{:0>width$}", n, width = pad_width)
|
||||||
|
} else {
|
||||||
|
n.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return Some(if reverse {
|
||||||
|
nums.into_iter().rev().collect()
|
||||||
|
} else {
|
||||||
|
nums
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn is_alpha_range_bound(word: &str) -> bool {
|
||||||
|
word.len() == 1 && word.chars().all(|c| c.is_ascii_alphabetic())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_numeric_range_bound(word: &str) -> bool {
|
||||||
|
!word.is_empty() && word.chars().all(|c| c.is_ascii_digit())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn expand_raw(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
|
pub fn expand_raw(chars: &mut Peekable<Chars<'_>>) -> ShResult<String> {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
|
|
||||||
@@ -897,7 +1217,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
ParamExp::SetDefaultUnsetOrNull(default) => {
|
ParamExp::SetDefaultUnsetOrNull(default) => {
|
||||||
if !vars.var_exists(&var_name) || vars.get_var(&var_name).is_empty() {
|
if !vars.var_exists(&var_name) || vars.get_var(&var_name).is_empty() {
|
||||||
write_vars(|v| v.set_var(&var_name, &default, false));
|
write_vars(|v| v.set_var(&var_name, &default, VarFlags::NONE));
|
||||||
Ok(default)
|
Ok(default)
|
||||||
} else {
|
} else {
|
||||||
Ok(vars.get_var(&var_name))
|
Ok(vars.get_var(&var_name))
|
||||||
@@ -905,7 +1225,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
ParamExp::SetDefaultUnset(default) => {
|
ParamExp::SetDefaultUnset(default) => {
|
||||||
if !vars.var_exists(&var_name) {
|
if !vars.var_exists(&var_name) {
|
||||||
write_vars(|v| v.set_var(&var_name, &default, false));
|
write_vars(|v| v.set_var(&var_name, &default, VarFlags::NONE));
|
||||||
Ok(default)
|
Ok(default)
|
||||||
} else {
|
} else {
|
||||||
Ok(vars.get_var(&var_name))
|
Ok(vars.get_var(&var_name))
|
||||||
@@ -1061,7 +1381,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
|||||||
}
|
}
|
||||||
ParamExp::VarNamesWithPrefix(prefix) => {
|
ParamExp::VarNamesWithPrefix(prefix) => {
|
||||||
let mut match_vars = vec![];
|
let mut match_vars = vec![];
|
||||||
for var in vars.vars().keys() {
|
for var in vars.flatten_vars().keys() {
|
||||||
if var.starts_with(&prefix) {
|
if var.starts_with(&prefix) {
|
||||||
match_vars.push(var.clone())
|
match_vars.push(var.clone())
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/fern.rs
34
src/fern.rs
@@ -18,10 +18,11 @@ pub mod state;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
use crate::libsh::error::ShErrKind;
|
||||||
use crate::libsh::sys::{save_termios, set_termios};
|
use crate::libsh::sys::{save_termios, set_termios};
|
||||||
use crate::parse::execute::exec_input;
|
use crate::parse::execute::exec_input;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::signal::sig_setup;
|
use crate::signal::{check_signals, sig_setup, signals_pending};
|
||||||
use crate::state::source_rc;
|
use crate::state::source_rc;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use shopt::FernEditMode;
|
use shopt::FernEditMode;
|
||||||
@@ -78,9 +79,9 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
write_vars(|v| v.bpush_arg(path.to_string_lossy().to_string()));
|
write_vars(|v| v.cur_scope_mut().bpush_arg(path.to_string_lossy().to_string()));
|
||||||
for arg in args {
|
for arg in args {
|
||||||
write_vars(|v| v.bpush_arg(arg))
|
write_vars(|v| v.cur_scope_mut().bpush_arg(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = exec_input(input, None) {
|
if let Err(e) = exec_input(input, None) {
|
||||||
@@ -100,18 +101,40 @@ fn fern_interactive() {
|
|||||||
|
|
||||||
let mut readline_err_count: u32 = 0;
|
let mut readline_err_count: u32 = 0;
|
||||||
|
|
||||||
loop {
|
// Initialize a new string, we will use this to store
|
||||||
|
// partial line inputs when read() calls are interrupted by EINTR
|
||||||
|
let mut partial_input = String::new();
|
||||||
|
|
||||||
|
'outer: loop {
|
||||||
// Main loop
|
// Main loop
|
||||||
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let input = match prompt::readline(edit_mode) {
|
let input = match prompt::readline(edit_mode, Some(&partial_input)) {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
readline_err_count = 0;
|
readline_err_count = 0;
|
||||||
|
partial_input.clear();
|
||||||
line
|
line
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
if let ShErrKind::ReadlineIntr(partial) = e.kind() {
|
||||||
|
// Did we get signaled? Check signal flags
|
||||||
|
// If nothing to worry about, retry the readline
|
||||||
|
while signals_pending() {
|
||||||
|
if let Err(e) = check_signals() {
|
||||||
|
if let ShErrKind::ClearReadline = e.kind() {
|
||||||
|
partial_input.clear();
|
||||||
|
if !signals_pending() {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partial_input = partial.to_string();
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
readline_err_count += 1;
|
readline_err_count += 1;
|
||||||
if readline_err_count == 20 {
|
if readline_err_count == 20 {
|
||||||
@@ -121,6 +144,7 @@ fn fern_interactive() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = exec_input(input, None) {
|
if let Err(e) = exec_input(input, None) {
|
||||||
|
|||||||
32
src/jobs.rs
32
src/jobs.rs
@@ -2,10 +2,7 @@ use crate::{
|
|||||||
libsh::{
|
libsh::{
|
||||||
error::ShResult,
|
error::ShResult,
|
||||||
term::{Style, Styled},
|
term::{Style, Styled},
|
||||||
},
|
}, prelude::*, procio::{IoMode, borrow_fd}, signal::{disable_reaping, enable_reaping}, state::{self, set_status, write_jobs}
|
||||||
prelude::*,
|
|
||||||
procio::{borrow_fd, IoMode},
|
|
||||||
state::{self, set_status, write_jobs},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||||
@@ -643,29 +640,6 @@ pub fn take_term() -> ShResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disable_reaping() -> ShResult<()> {
|
|
||||||
flog!(TRACE, "Disabling reaping");
|
|
||||||
unsafe {
|
|
||||||
signal(
|
|
||||||
Signal::SIGCHLD,
|
|
||||||
SigHandler::Handler(crate::signal::ignore_sigchld),
|
|
||||||
)
|
|
||||||
}?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_reaping() -> ShResult<()> {
|
|
||||||
flog!(TRACE, "Enabling reaping");
|
|
||||||
unsafe {
|
|
||||||
signal(
|
|
||||||
Signal::SIGCHLD,
|
|
||||||
SigHandler::Handler(crate::signal::handle_sigchld),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Waits on the current foreground job and updates the shell's last status code
|
/// Waits on the current foreground job and updates the shell's last status code
|
||||||
pub fn wait_fg(job: Job) -> ShResult<()> {
|
pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||||
if job.children().is_empty() {
|
if job.children().is_empty() {
|
||||||
@@ -674,7 +648,7 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
flog!(TRACE, "Waiting on foreground job");
|
flog!(TRACE, "Waiting on foreground job");
|
||||||
let mut code = 0;
|
let mut code = 0;
|
||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
disable_reaping()?;
|
disable_reaping();
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
for status in statuses {
|
for status in statuses {
|
||||||
match status {
|
match status {
|
||||||
@@ -697,7 +671,7 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
|||||||
take_term()?;
|
take_term()?;
|
||||||
set_status(code);
|
set_status(code);
|
||||||
flog!(TRACE, "exit code: {}", code);
|
flog!(TRACE, "exit code: {}", code);
|
||||||
enable_reaping()?;
|
enable_reaping();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ impl From<std::env::VarError> for ShErr {
|
|||||||
|
|
||||||
impl From<Errno> for ShErr {
|
impl From<Errno> for ShErr {
|
||||||
fn from(value: Errno) -> Self {
|
fn from(value: Errno) -> Self {
|
||||||
ShErr::simple(ShErrKind::Errno, value.to_string())
|
ShErr::simple(ShErrKind::Errno(value), value.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,14 +402,18 @@ pub enum ShErrKind {
|
|||||||
HistoryReadErr,
|
HistoryReadErr,
|
||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno,
|
Errno(Errno),
|
||||||
FileNotFound(String),
|
FileNotFound(String),
|
||||||
CmdNotFound(String),
|
CmdNotFound(String),
|
||||||
|
ReadlineIntr(String),
|
||||||
|
ReadlineErr,
|
||||||
|
|
||||||
|
// Not really errors, more like internal signals
|
||||||
CleanExit(i32),
|
CleanExit(i32),
|
||||||
FuncReturn(i32),
|
FuncReturn(i32),
|
||||||
LoopContinue(i32),
|
LoopContinue(i32),
|
||||||
LoopBreak(i32),
|
LoopBreak(i32),
|
||||||
ReadlineErr,
|
ClearReadline,
|
||||||
Null,
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,14 +428,16 @@ impl Display for ShErrKind {
|
|||||||
Self::ExecFail => "Execution Failed",
|
Self::ExecFail => "Execution Failed",
|
||||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
Self::BadPermission => "Bad Permissions",
|
Self::BadPermission => "Bad Permissions",
|
||||||
Self::Errno => "ERRNO",
|
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
||||||
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
||||||
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||||
Self::CleanExit(_) => "",
|
Self::CleanExit(_) => "",
|
||||||
Self::FuncReturn(_) => "",
|
Self::FuncReturn(_) => "",
|
||||||
Self::LoopContinue(_) => "",
|
Self::LoopContinue(_) => "",
|
||||||
Self::LoopBreak(_) => "",
|
Self::LoopBreak(_) => "",
|
||||||
Self::ReadlineErr => "Line Read Error",
|
Self::ReadlineIntr(_) => "",
|
||||||
|
Self::ReadlineErr => "Readline Error",
|
||||||
|
Self::ClearReadline => "",
|
||||||
Self::Null => "",
|
Self::Null => "",
|
||||||
};
|
};
|
||||||
write!(f, "{output}")
|
write!(f, "{output}")
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::{
|
|||||||
echo::echo,
|
echo::echo,
|
||||||
export::export,
|
export::export,
|
||||||
flowctl::flowctl,
|
flowctl::flowctl,
|
||||||
jobctl::{continue_job, jobs, JobBehavior},
|
jobctl::{JobBehavior, continue_job, jobs},
|
||||||
pwd::pwd,
|
pwd::pwd,
|
||||||
shift::shift,
|
shift::shift,
|
||||||
shopt::shopt,
|
shopt::shopt,
|
||||||
@@ -16,7 +16,7 @@ use crate::{
|
|||||||
zoltraak::zoltraak,
|
zoltraak::zoltraak,
|
||||||
},
|
},
|
||||||
expand::expand_aliases,
|
expand::expand_aliases,
|
||||||
jobs::{dispatch_job, ChildProc, JobBldr, JobStack},
|
jobs::{ChildProc, JobBldr, JobStack, dispatch_job},
|
||||||
libsh::{
|
libsh::{
|
||||||
error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
error::{ShErr, ShErrKind, ShResult, ShResultExt},
|
||||||
utils::RedirVecUtils,
|
utils::RedirVecUtils,
|
||||||
@@ -24,8 +24,7 @@ use crate::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
procio::{IoFrame, IoMode, IoStack},
|
procio::{IoFrame, IoMode, IoStack},
|
||||||
state::{
|
state::{
|
||||||
self, get_snapshots, read_logic, restore_snapshot, write_logic, write_meta, write_vars, ShFunc,
|
self, FERN, ShFunc, VarFlags, read_logic, write_logic, write_meta, write_vars
|
||||||
VarTab, LOGIC_TABLE,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,6 +34,49 @@ use super::{
|
|||||||
ParsedSrc, Redir, RedirType,
|
ParsedSrc, Redir, RedirType,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct ScopeGuard;
|
||||||
|
|
||||||
|
|
||||||
|
impl ScopeGuard {
|
||||||
|
pub fn exclusive_scope(args: Option<Vec<(String,Span)>>) -> Self {
|
||||||
|
let argv = args.map(|a| a.into_iter().map(|(s, _)| s).collect::<Vec<_>>());
|
||||||
|
write_vars(|v| v.descend(argv));
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
pub fn shared_scope() -> Self {
|
||||||
|
// used in environments that inherit from the parent, like subshells
|
||||||
|
write_vars(|v| v.descend(None));
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ScopeGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
write_vars(|v| v.ascend());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to throw away variables that exist in temporary contexts
|
||||||
|
/// such as 'VAR=value <command> <args>'
|
||||||
|
/// or for-loop variables
|
||||||
|
struct VarCtxGuard {
|
||||||
|
vars: HashSet<String>
|
||||||
|
}
|
||||||
|
impl VarCtxGuard {
|
||||||
|
fn new(vars: HashSet<String>) -> Self {
|
||||||
|
Self { vars }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for VarCtxGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
write_vars(|v| {
|
||||||
|
for var in &self.vars {
|
||||||
|
v.unset_var(var);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum AssignBehavior {
|
pub enum AssignBehavior {
|
||||||
Export,
|
Export,
|
||||||
Set,
|
Set,
|
||||||
@@ -76,9 +118,13 @@ impl ExecArgs {
|
|||||||
|
|
||||||
pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
|
pub fn exec_input(input: String, io_stack: Option<IoStack>) -> ShResult<()> {
|
||||||
write_meta(|m| m.start_timer());
|
write_meta(|m| m.start_timer());
|
||||||
let log_tab = LOGIC_TABLE.read().unwrap();
|
let log_tab = {
|
||||||
|
let fern = FERN.read().unwrap();
|
||||||
|
// TODO: Is there a better way to do this?
|
||||||
|
// The goal is mainly to not be holding a lock while executing input
|
||||||
|
fern.read_logic().clone()
|
||||||
|
};
|
||||||
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
let input = expand_aliases(input, HashSet::new(), &log_tab);
|
||||||
mem::drop(log_tab); // Release lock ASAP
|
|
||||||
let mut parser = ParsedSrc::new(Arc::new(input));
|
let mut parser = ParsedSrc::new(Arc::new(input));
|
||||||
if let Err(errors) = parser.parse_src() {
|
if let Err(errors) = parser.parse_src() {
|
||||||
for error in errors {
|
for error in errors {
|
||||||
@@ -137,10 +183,10 @@ impl Dispatcher {
|
|||||||
let Some(cmd) = node.get_command() else {
|
let Some(cmd) = node.get_command() else {
|
||||||
return self.exec_cmd(node); // Argv is empty, probably an assignment
|
return self.exec_cmd(node); // Argv is empty, probably an assignment
|
||||||
};
|
};
|
||||||
if cmd.flags.contains(TkFlags::BUILTIN) {
|
if is_func(node.get_command().cloned()) {
|
||||||
self.exec_builtin(node)
|
|
||||||
} else if is_func(node.get_command().cloned()) {
|
|
||||||
self.exec_func(node)
|
self.exec_func(node)
|
||||||
|
} else if cmd.flags.contains(TkFlags::BUILTIN) {
|
||||||
|
self.exec_builtin(node)
|
||||||
} else if is_subsh(node.get_command().cloned()) {
|
} else if is_subsh(node.get_command().cloned()) {
|
||||||
self.exec_subsh(node)
|
self.exec_subsh(node)
|
||||||
} else {
|
} else {
|
||||||
@@ -216,20 +262,17 @@ impl Dispatcher {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_assignments(assignments, AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
||||||
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
self.io_stack.append_to_frame(subsh.redirs);
|
self.io_stack.append_to_frame(subsh.redirs);
|
||||||
let mut argv = prepare_argv(argv)?;
|
let mut argv = prepare_argv(argv)?;
|
||||||
|
|
||||||
let subsh = argv.remove(0);
|
let subsh = argv.remove(0);
|
||||||
let subsh_body = subsh.0.to_string();
|
let subsh_body = subsh.0.to_string();
|
||||||
let snapshot = get_snapshots();
|
let _guard = ScopeGuard::shared_scope();
|
||||||
|
|
||||||
if let Err(e) = exec_input(subsh_body, None) {
|
exec_input(subsh_body, None)?;
|
||||||
restore_snapshot(snapshot);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
restore_snapshot(snapshot);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
||||||
@@ -242,36 +285,26 @@ impl Dispatcher {
|
|||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_assignments(assignments, AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(assignments, AssignBehavior::Export)?;
|
||||||
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
|
|
||||||
self.io_stack.append_to_frame(func.redirs);
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
|
|
||||||
let func_name = argv.remove(0).span.as_str().to_string();
|
let func_name = argv.remove(0).span.as_str().to_string();
|
||||||
let argv = prepare_argv(argv)?;
|
let argv = prepare_argv(argv)?;
|
||||||
if let Some(func) = read_logic(|l| l.get_func(&func_name)) {
|
if let Some(func) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let snapshot = get_snapshots();
|
let _guard = ScopeGuard::exclusive_scope(Some(argv));
|
||||||
// Set up the inner scope
|
|
||||||
write_vars(|v| {
|
|
||||||
**v = VarTab::new();
|
|
||||||
v.clear_args();
|
|
||||||
for (arg, _) in argv {
|
|
||||||
v.bpush_arg(arg.to_string());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
||||||
restore_snapshot(snapshot);
|
|
||||||
match e.kind() {
|
match e.kind() {
|
||||||
ShErrKind::FuncReturn(code) => {
|
ShErrKind::FuncReturn(code) => {
|
||||||
state::set_status(*code);
|
state::set_status(*code);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => return { Err(e) },
|
_ => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return to the outer scope
|
|
||||||
restore_snapshot(snapshot);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ShErr::full(
|
Err(ShErr::full(
|
||||||
@@ -384,9 +417,13 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
fn exec_for(&mut self, for_stmt: Node) -> ShResult<()> {
|
||||||
|
|
||||||
let NdRule::ForNode { vars, arr, body } = for_stmt.class else {
|
let NdRule::ForNode { vars, arr, body } = for_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
|
let mut for_guard = VarCtxGuard::new(
|
||||||
|
vars.iter().map(|v| v.to_string()).collect()
|
||||||
|
);
|
||||||
|
|
||||||
let io_frame = self.io_stack.pop_frame();
|
let io_frame = self.io_stack.pop_frame();
|
||||||
let (_, mut body_frame) = io_frame.split_frame();
|
let (_, mut body_frame) = io_frame.split_frame();
|
||||||
@@ -400,7 +437,8 @@ impl Dispatcher {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (var, val) in chunk_iter {
|
for (var, val) in chunk_iter {
|
||||||
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), false));
|
write_vars(|v| v.set_var(&var.to_string(), &val.to_string(), VarFlags::NONE));
|
||||||
|
for_guard.vars.insert(var.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for node in body.clone() {
|
for node in body.clone() {
|
||||||
@@ -491,13 +529,15 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
fn exec_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
fn exec_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
let NdRule::Command {
|
let NdRule::Command {
|
||||||
ref mut assignments,
|
assignments,
|
||||||
ref mut argv,
|
argv,
|
||||||
} = &mut cmd.class
|
} = &mut cmd.class
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
let env_vars = self.set_assignments(mem::take(assignments), AssignBehavior::Export)?;
|
||||||
|
let _var_guard = VarCtxGuard::new(env_vars.into_iter().collect());
|
||||||
|
|
||||||
let cmd_raw = argv.first().unwrap();
|
let cmd_raw = argv.first().unwrap();
|
||||||
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
let curr_job_mut = self.job_stack.curr_job_mut().unwrap();
|
||||||
let io_stack_mut = &mut self.io_stack;
|
let io_stack_mut = &mut self.io_stack;
|
||||||
@@ -543,10 +583,6 @@ impl Dispatcher {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
for var in env_vars_to_unset {
|
|
||||||
env::set_var(&var, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
state::set_status(1);
|
state::set_status(1);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
@@ -584,7 +620,7 @@ impl Dispatcher {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
for var in env_vars_to_unset {
|
for var in env_vars_to_unset {
|
||||||
std::env::set_var(&var, "");
|
unsafe { std::env::set_var(&var, "") };
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -600,7 +636,7 @@ impl Dispatcher {
|
|||||||
let var = var.span.as_str();
|
let var = var.span.as_str();
|
||||||
let val = val.expand()?.get_words().join(" ");
|
let val = val.expand()?.get_words().join(" ");
|
||||||
match kind {
|
match kind {
|
||||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, true)),
|
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::EXPORT)),
|
||||||
AssignKind::PlusEq => todo!(),
|
AssignKind::PlusEq => todo!(),
|
||||||
AssignKind::MinusEq => todo!(),
|
AssignKind::MinusEq => todo!(),
|
||||||
AssignKind::MultEq => todo!(),
|
AssignKind::MultEq => todo!(),
|
||||||
@@ -617,7 +653,7 @@ impl Dispatcher {
|
|||||||
let var = var.span.as_str();
|
let var = var.span.as_str();
|
||||||
let val = val.expand()?.get_words().join(" ");
|
let val = val.expand()?.get_words().join(" ");
|
||||||
match kind {
|
match kind {
|
||||||
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, true)),
|
AssignKind::Eq => write_vars(|v| v.set_var(var, &val, VarFlags::NONE)),
|
||||||
AssignKind::PlusEq => todo!(),
|
AssignKind::PlusEq => todo!(),
|
||||||
AssignKind::MinusEq => todo!(),
|
AssignKind::MinusEq => todo!(),
|
||||||
AssignKind::MultEq => todo!(),
|
AssignKind::MultEq => todo!(),
|
||||||
@@ -688,7 +724,7 @@ pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
|||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let err = ShErr::full(ShErrKind::Errno, format!("{e}"), span);
|
let err = ShErr::full(ShErrKind::Errno(e), format!("{e}"), span);
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,17 @@ fn get_prompt() -> ShResult<String> {
|
|||||||
expand_prompt(&prompt)
|
expand_prompt(&prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readline(edit_mode: FernEditMode) -> ShResult<String> {
|
pub fn readline(edit_mode: FernEditMode, initial: Option<&str>) -> ShResult<String> {
|
||||||
let prompt = get_prompt()?;
|
let prompt = get_prompt()?;
|
||||||
let mut reader: Box<dyn Readline> = match edit_mode {
|
let mut reader: Box<dyn Readline> = match edit_mode {
|
||||||
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))?),
|
FernEditMode::Vi => {
|
||||||
FernEditMode::Emacs => todo!(),
|
let mut fern_vi = FernVi::new(Some(prompt))?;
|
||||||
|
if let Some(input) = initial {
|
||||||
|
fern_vi = fern_vi.with_initial(&input)
|
||||||
|
}
|
||||||
|
Box::new(fern_vi) as Box<dyn Readline>
|
||||||
|
}
|
||||||
|
FernEditMode::Emacs => todo!(), // idk if I'm ever gonna do this one actually, I don't use emacs
|
||||||
};
|
};
|
||||||
reader.readline()
|
reader.readline()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -622,17 +622,25 @@ impl LineBuf {
|
|||||||
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
pub fn is_sentence_punctuation(&mut self, pos: usize) -> bool {
|
pub fn is_sentence_punctuation(&self, pos: usize) -> bool {
|
||||||
if let Some(gr) = self.grapheme_at(pos) {
|
self.next_sentence_start_from_punctuation(pos).is_some()
|
||||||
if PUNCTUATION.contains(&gr) && self.grapheme_after(pos).is_some() {
|
}
|
||||||
|
|
||||||
|
/// If position is at sentence-ending punctuation, returns the position of the next sentence start.
|
||||||
|
/// Handles closing delimiters (`)`, `]`, `"`, `'`) after punctuation.
|
||||||
|
#[allow(clippy::collapsible_if)]
|
||||||
|
pub fn next_sentence_start_from_punctuation(&self, pos: usize) -> Option<usize> {
|
||||||
|
if let Some(gr) = self.read_grapheme_at(pos) {
|
||||||
|
if PUNCTUATION.contains(&gr) && self.read_grapheme_after(pos).is_some() {
|
||||||
let mut fwd_indices = (pos + 1..self.cursor.max).peekable();
|
let mut fwd_indices = (pos + 1..self.cursor.max).peekable();
|
||||||
|
// Skip any closing delimiters after the punctuation
|
||||||
if self
|
if self
|
||||||
.grapheme_after(pos)
|
.read_grapheme_after(pos)
|
||||||
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
||||||
{
|
{
|
||||||
while let Some(idx) = fwd_indices.peek() {
|
while let Some(idx) = fwd_indices.peek() {
|
||||||
if self
|
if self
|
||||||
.grapheme_after(*idx)
|
.read_grapheme_at(*idx)
|
||||||
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
||||||
{
|
{
|
||||||
fwd_indices.next();
|
fwd_indices.next();
|
||||||
@@ -641,16 +649,32 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Now we should be at whitespace - skip it to find sentence start
|
||||||
if let Some(idx) = fwd_indices.next() {
|
if let Some(idx) = fwd_indices.next() {
|
||||||
if let Some(gr) = self.grapheme_at(idx) {
|
if let Some(gr) = self.read_grapheme_at(idx) {
|
||||||
if is_whitespace(gr) {
|
if is_whitespace(gr) {
|
||||||
return true;
|
if gr == "\n" {
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
// Skip remaining whitespace to find actual sentence start
|
||||||
|
while let Some(idx) = fwd_indices.next() {
|
||||||
|
if let Some(gr) = self.read_grapheme_at(idx) {
|
||||||
|
if is_whitespace(gr) {
|
||||||
|
if gr == "\n" {
|
||||||
|
return Some(idx);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return Some(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
pub fn is_sentence_start(&mut self, pos: usize) -> bool {
|
pub fn is_sentence_start(&mut self, pos: usize) -> bool {
|
||||||
if self.grapheme_before(pos).is_some_and(is_whitespace) {
|
if self.grapheme_before(pos).is_some_and(is_whitespace) {
|
||||||
@@ -875,11 +899,7 @@ impl LineBuf {
|
|||||||
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
self.end_of_word_forward_or_start_of_word_backward_from(
|
self.start_of_word_backward(self.cursor.get(), word)
|
||||||
self.cursor.get(),
|
|
||||||
word,
|
|
||||||
Direction::Backward,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, true);
|
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, true);
|
||||||
Some((start, end))
|
Some((start, end))
|
||||||
@@ -888,11 +908,7 @@ impl LineBuf {
|
|||||||
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
||||||
self.cursor.get()
|
self.cursor.get()
|
||||||
} else {
|
} else {
|
||||||
self.end_of_word_forward_or_start_of_word_backward_from(
|
self.start_of_word_backward(self.cursor.get(), word)
|
||||||
self.cursor.get(),
|
|
||||||
word,
|
|
||||||
Direction::Backward,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, false);
|
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, false);
|
||||||
Some((start, end))
|
Some((start, end))
|
||||||
@@ -907,39 +923,26 @@ impl LineBuf {
|
|||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
let mut start = None;
|
let mut start = None;
|
||||||
let mut end = None;
|
let mut end = None;
|
||||||
let mut fwd_indices = start_pos..self.cursor.max;
|
let mut fwd_indices = (start_pos..self.cursor.max).peekable();
|
||||||
while let Some(idx) = fwd_indices.next() {
|
while let Some(idx) = fwd_indices.next() {
|
||||||
let Some(gr) = self.grapheme_at(idx) else {
|
if self.grapheme_at(idx).is_none() {
|
||||||
end = Some(self.cursor.max);
|
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
if PUNCTUATION.contains(&gr) && self.is_sentence_punctuation(idx) {
|
|
||||||
|
if let Some(next_sentence_start) = self.next_sentence_start_from_punctuation(idx) {
|
||||||
match bound {
|
match bound {
|
||||||
Bound::Inside => {
|
Bound::Inside => {
|
||||||
end = Some(idx);
|
end = Some(idx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Bound::Around => {
|
Bound::Around => {
|
||||||
let mut end_pos = idx;
|
end = Some(next_sentence_start);
|
||||||
while let Some(idx) = fwd_indices.next() {
|
|
||||||
if !self.grapheme_at(idx).is_some_and(is_whitespace) {
|
|
||||||
end_pos += 1;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
end_pos += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end = Some(end_pos);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut end = end.unwrap_or(self.cursor.max);
|
let mut end = end.unwrap_or(self.cursor.max);
|
||||||
flog!(DEBUG, end);
|
|
||||||
flog!(DEBUG, self.grapheme_at(end));
|
|
||||||
flog!(DEBUG, self.grapheme_before(end));
|
|
||||||
flog!(DEBUG, self.grapheme_after(end));
|
|
||||||
|
|
||||||
let mut bkwd_indices = (0..end).rev();
|
let mut bkwd_indices = (0..end).rev();
|
||||||
while let Some(idx) = bkwd_indices.next() {
|
while let Some(idx) = bkwd_indices.next() {
|
||||||
@@ -949,10 +952,6 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let start = start.unwrap_or(0);
|
let start = start.unwrap_or(0);
|
||||||
flog!(DEBUG, start);
|
|
||||||
flog!(DEBUG, self.grapheme_at(start));
|
|
||||||
flog!(DEBUG, self.grapheme_before(start));
|
|
||||||
flog!(DEBUG, self.grapheme_after(start));
|
|
||||||
|
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
if let Some((_, new_end)) = self.text_obj_sentence(end, count - 1, bound) {
|
if let Some((_, new_end)) = self.text_obj_sentence(end, count - 1, bound) {
|
||||||
@@ -1321,7 +1320,6 @@ impl LineBuf {
|
|||||||
dir: Direction,
|
dir: Direction,
|
||||||
include_last_char: bool,
|
include_last_char: bool,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
// Not sorry for these method names btw
|
|
||||||
let mut pos = ClampedUsize::new(self.cursor.get(), self.cursor.max, false);
|
let mut pos = ClampedUsize::new(self.cursor.get(), self.cursor.max, false);
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
// We alter 'include_last_char' to only be true on the last iteration
|
// We alter 'include_last_char' to only be true on the last iteration
|
||||||
@@ -1331,16 +1329,12 @@ impl LineBuf {
|
|||||||
pos.set(match to {
|
pos.set(match to {
|
||||||
To::Start => {
|
To::Start => {
|
||||||
match dir {
|
match dir {
|
||||||
Direction::Forward => self.start_of_word_forward_or_end_of_word_backward_from(
|
Direction::Forward => {
|
||||||
pos.get(),
|
self.start_of_word_forward(pos.get(), word, include_last_char_and_is_last_word)
|
||||||
word,
|
}
|
||||||
dir,
|
|
||||||
include_last_char_and_is_last_word,
|
|
||||||
),
|
|
||||||
Direction::Backward => 'backward: {
|
Direction::Backward => 'backward: {
|
||||||
// We also need to handle insert mode's Ctrl+W behaviors here
|
// We also need to handle insert mode's Ctrl+W behaviors here
|
||||||
let target =
|
let target = self.start_of_word_backward(pos.get(), word);
|
||||||
self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir);
|
|
||||||
|
|
||||||
// Check to see if we are in insert mode
|
// Check to see if we are in insert mode
|
||||||
let Some(start_pos) = self.insert_mode_start_pos else {
|
let Some(start_pos) = self.insert_mode_start_pos else {
|
||||||
@@ -1361,38 +1355,18 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
To::End => match dir {
|
To::End => match dir {
|
||||||
Direction::Forward => {
|
Direction::Forward => self.end_of_word_forward(pos.get(), word),
|
||||||
self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir)
|
Direction::Backward => self.end_of_word_backward(pos.get(), word, false),
|
||||||
}
|
|
||||||
Direction::Backward => {
|
|
||||||
self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir, false)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
pos.get()
|
pos.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the start of a word forward, or the end of a word backward
|
/// Find the start of the next word forward
|
||||||
///
|
pub fn start_of_word_forward(&mut self, mut pos: usize, word: Word, include_last_char: bool) -> usize {
|
||||||
/// Finding the start of a word in the forward direction, and finding the end
|
let default = self.grapheme_indices().len();
|
||||||
/// of a word in the backward direction are logically the same operation, if
|
let mut indices_iter = (pos..self.cursor.max).peekable();
|
||||||
/// you use a reversed iterator for the backward motion.
|
|
||||||
///
|
|
||||||
/// Tied with 'end_of_word_forward_or_start_of_word_backward_from()' for the
|
|
||||||
/// longest method name I have ever written
|
|
||||||
pub fn start_of_word_forward_or_end_of_word_backward_from(
|
|
||||||
&mut self,
|
|
||||||
mut pos: usize,
|
|
||||||
word: Word,
|
|
||||||
dir: Direction,
|
|
||||||
include_last_char: bool,
|
|
||||||
) -> usize {
|
|
||||||
let default = match dir {
|
|
||||||
Direction::Backward => 0,
|
|
||||||
Direction::Forward => self.grapheme_indices().len(),
|
|
||||||
};
|
|
||||||
let mut indices_iter = self.directional_indices_iter_from(pos, dir).peekable(); // And make it peekable
|
|
||||||
|
|
||||||
match word {
|
match word {
|
||||||
Word::Big => {
|
Word::Big => {
|
||||||
@@ -1404,7 +1378,6 @@ impl LineBuf {
|
|||||||
let Some(idx) = indices_iter.next() else {
|
let Some(idx) = indices_iter.next() else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
// We have a 'cw' call, do not include the trailing whitespace
|
|
||||||
if include_last_char {
|
if include_last_char {
|
||||||
return idx;
|
return idx;
|
||||||
} else {
|
} else {
|
||||||
@@ -1412,15 +1385,14 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check current grapheme
|
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
let on_whitespace = is_whitespace(&cur_char);
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
// Find the next whitespace
|
|
||||||
if !on_whitespace {
|
if !on_whitespace {
|
||||||
let Some(ws_pos) = indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
let Some(ws_pos) =
|
||||||
|
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
||||||
else {
|
else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
@@ -1429,11 +1401,9 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the next visible grapheme position
|
indices_iter
|
||||||
let non_ws_pos = indices_iter
|
|
||||||
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
.unwrap_or(default);
|
.unwrap_or(default)
|
||||||
non_ws_pos
|
|
||||||
}
|
}
|
||||||
Word::Normal => {
|
Word::Normal => {
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
@@ -1462,7 +1432,6 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
let on_whitespace = is_whitespace(&cur_char);
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
// Advance until hitting whitespace or a different character class
|
|
||||||
if !on_whitespace {
|
if !on_whitespace {
|
||||||
let other_class_pos = indices_iter.find(|i| {
|
let other_class_pos = indices_iter.find(|i| {
|
||||||
self
|
self
|
||||||
@@ -1472,7 +1441,6 @@ impl LineBuf {
|
|||||||
let Some(other_class_pos) = other_class_pos else {
|
let Some(other_class_pos) = other_class_pos else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
// If we hit a different character class, we return here
|
|
||||||
if self
|
if self
|
||||||
.grapheme_at(other_class_pos)
|
.grapheme_at(other_class_pos)
|
||||||
.is_some_and(|c| !is_whitespace(c))
|
.is_some_and(|c| !is_whitespace(c))
|
||||||
@@ -1482,79 +1450,54 @@ impl LineBuf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are now certainly on a whitespace character. Advance until a
|
indices_iter
|
||||||
// non-whitespace character.
|
|
||||||
let non_ws_pos = indices_iter
|
|
||||||
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
.unwrap_or(default);
|
.unwrap_or(default)
|
||||||
non_ws_pos
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Finds the end of a word forward, or the start of a word backward
|
|
||||||
///
|
|
||||||
/// Finding the end of a word in the forward direction, and finding the start
|
|
||||||
/// of a word in the backward direction are logically the same operation, if
|
|
||||||
/// you use a reversed iterator for the backward motion.
|
|
||||||
pub fn end_of_word_forward_or_start_of_word_backward_from(
|
|
||||||
&mut self,
|
|
||||||
mut pos: usize,
|
|
||||||
word: Word,
|
|
||||||
dir: Direction,
|
|
||||||
) -> usize {
|
|
||||||
let default = match dir {
|
|
||||||
Direction::Backward => 0,
|
|
||||||
Direction::Forward => self.grapheme_indices().len(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut indices_iter = self.directional_indices_iter_from(pos, dir).peekable();
|
/// Find the end of the previous word backward
|
||||||
|
pub fn end_of_word_backward(&mut self, mut pos: usize, word: Word, include_last_char: bool) -> usize {
|
||||||
|
let default = self.grapheme_indices().len();
|
||||||
|
let mut indices_iter = (0..pos).rev().peekable();
|
||||||
|
|
||||||
match word {
|
match word {
|
||||||
Word::Big => {
|
Word::Big => {
|
||||||
let Some(next_idx) = indices_iter.peek() else {
|
let Some(next) = indices_iter.peek() else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
let on_boundary = self.grapheme_at(*next_idx).is_none_or(is_whitespace);
|
let on_boundary = self.grapheme_at(*next).is_none_or(is_whitespace);
|
||||||
if on_boundary {
|
if on_boundary {
|
||||||
let Some(idx) = indices_iter.next() else {
|
let Some(idx) = indices_iter.next() else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
|
if include_last_char {
|
||||||
|
return idx;
|
||||||
|
} else {
|
||||||
pos = idx;
|
pos = idx;
|
||||||
}
|
}
|
||||||
// Check current grapheme
|
}
|
||||||
|
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
let on_whitespace = is_whitespace(&cur_char);
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
// Advance iterator to next visible grapheme
|
if !on_whitespace {
|
||||||
if on_whitespace {
|
let Some(ws_pos) =
|
||||||
let Some(_non_ws_pos) =
|
|
||||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
|
||||||
else {
|
|
||||||
return default;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The position of the next whitespace will tell us where the end (or start) of
|
|
||||||
// the word is
|
|
||||||
let Some(next_ws_pos) =
|
|
||||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
||||||
else {
|
else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
pos = next_ws_pos;
|
if include_last_char {
|
||||||
|
return ws_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if pos == self.grapheme_indices().len() {
|
indices_iter
|
||||||
// We reached the end of the buffer
|
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
pos
|
.unwrap_or(default)
|
||||||
} else {
|
|
||||||
// We hit some whitespace, so we will go back one
|
|
||||||
match dir {
|
|
||||||
Direction::Forward => pos.saturating_sub(1),
|
|
||||||
Direction::Backward => pos + 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Word::Normal => {
|
Word::Normal => {
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
@@ -1568,32 +1511,131 @@ impl LineBuf {
|
|||||||
.grapheme_at(*next_idx)
|
.grapheme_at(*next_idx)
|
||||||
.is_none_or(|c| is_other_class_or_is_ws(c, &cur_char));
|
.is_none_or(|c| is_other_class_or_is_ws(c, &cur_char));
|
||||||
if on_boundary {
|
if on_boundary {
|
||||||
let next_idx = indices_iter.next().unwrap();
|
if include_last_char {
|
||||||
pos = next_idx
|
return *next_idx;
|
||||||
|
} else {
|
||||||
|
pos = *next_idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(next_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
if is_other_class_not_ws(&cur_char, &next_char) {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
|
if !on_whitespace {
|
||||||
|
let other_class_pos = indices_iter.find(|i| {
|
||||||
|
self
|
||||||
|
.grapheme_at(*i)
|
||||||
|
.is_some_and(|c| is_other_class_or_is_ws(c, &next_char))
|
||||||
|
});
|
||||||
|
let Some(other_class_pos) = other_class_pos else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
if self
|
||||||
|
.grapheme_at(other_class_pos)
|
||||||
|
.is_some_and(|c| !is_whitespace(c))
|
||||||
|
|| include_last_char
|
||||||
|
{
|
||||||
|
return other_class_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indices_iter
|
||||||
|
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
|
.unwrap_or(default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the end of the current/next word forward
|
||||||
|
pub fn end_of_word_forward(&mut self, mut pos: usize, word: Word) -> usize {
|
||||||
|
let default = self.cursor.max;
|
||||||
|
if pos >= default {
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
let mut fwd_indices = (pos + 1..default).peekable();
|
||||||
|
|
||||||
|
match word {
|
||||||
|
Word::Big => {
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let Some(next_idx) = fwd_indices.peek() else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_boundary =
|
||||||
|
!is_whitespace(&cur_char) && self.grapheme_at(*next_idx).is_none_or(is_whitespace);
|
||||||
|
if on_boundary {
|
||||||
|
let Some(idx) = fwd_indices.next() else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check current grapheme
|
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
let on_whitespace = is_whitespace(&cur_char);
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
// Proceed to next visible grapheme
|
|
||||||
if on_whitespace {
|
if on_whitespace {
|
||||||
let Some(non_ws_pos) =
|
let Some(_non_ws_pos) =
|
||||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
fwd_indices.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
else {
|
else {
|
||||||
return default;
|
return default;
|
||||||
};
|
};
|
||||||
pos = non_ws_pos
|
}
|
||||||
|
|
||||||
|
let Some(next_ws_pos) =
|
||||||
|
fwd_indices.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
||||||
|
else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = next_ws_pos;
|
||||||
|
|
||||||
|
if pos == self.grapheme_indices().len() {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
pos.saturating_sub(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Word::Normal => {
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let Some(next_idx) = fwd_indices.peek() else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_boundary = !is_whitespace(&cur_char)
|
||||||
|
&& self
|
||||||
|
.grapheme_at(*next_idx)
|
||||||
|
.is_none_or(|c| is_other_class_or_is_ws(c, &cur_char));
|
||||||
|
if on_boundary {
|
||||||
|
let next_idx = fwd_indices.next().unwrap();
|
||||||
|
pos = next_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
|
if on_whitespace {
|
||||||
|
let Some(non_ws_pos) =
|
||||||
|
fwd_indices.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
|
else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = non_ws_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
return self.grapheme_indices().len();
|
return self.grapheme_indices().len();
|
||||||
};
|
};
|
||||||
// The position of the next differing character class will tell us where the end
|
let Some(next_ws_pos) = fwd_indices.find(|i| {
|
||||||
// (or start) of the word is
|
|
||||||
let Some(next_ws_pos) = indices_iter.find(|i| {
|
|
||||||
self
|
self
|
||||||
.grapheme_at(*i)
|
.grapheme_at(*i)
|
||||||
.is_some_and(|c| is_other_class_or_is_ws(c, &cur_char))
|
.is_some_and(|c| is_other_class_or_is_ws(c, &cur_char))
|
||||||
@@ -1603,18 +1645,113 @@ impl LineBuf {
|
|||||||
pos = next_ws_pos;
|
pos = next_ws_pos;
|
||||||
|
|
||||||
if pos == self.grapheme_indices().len() {
|
if pos == self.grapheme_indices().len() {
|
||||||
// We reached the end of the buffer
|
|
||||||
pos
|
pos
|
||||||
} else {
|
} else {
|
||||||
// We hit some other character class, so we go back one
|
pos.saturating_sub(1)
|
||||||
match dir {
|
|
||||||
Direction::Forward => pos.saturating_sub(1),
|
|
||||||
Direction::Backward => pos + 1,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the start of the current/previous word backward
|
||||||
|
pub fn start_of_word_backward(&mut self, mut pos: usize, word: Word) -> usize {
|
||||||
|
let default = 0;
|
||||||
|
let mut indices_iter = (0..pos).rev().peekable();
|
||||||
|
|
||||||
|
match word {
|
||||||
|
Word::Big => {
|
||||||
|
let on_boundary = 'bound_check: {
|
||||||
|
let Some(next_idx) = indices_iter.peek() else {
|
||||||
|
break 'bound_check false;
|
||||||
|
};
|
||||||
|
self.grapheme_at(*next_idx).is_none_or(is_whitespace)
|
||||||
|
};
|
||||||
|
if on_boundary {
|
||||||
|
let Some(idx) = indices_iter.next() else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
|
if on_whitespace {
|
||||||
|
let Some(_non_ws_pos) =
|
||||||
|
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
|
else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(next_ws_pos) =
|
||||||
|
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
||||||
|
else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = next_ws_pos;
|
||||||
|
|
||||||
|
if pos == self.grapheme_indices().len() {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
pos + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Word::Normal => {
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_boundary = 'bound_check: {
|
||||||
|
let Some(next_idx) = indices_iter.peek() else {
|
||||||
|
break 'bound_check false;
|
||||||
|
};
|
||||||
|
!is_whitespace(&cur_char)
|
||||||
|
&& self
|
||||||
|
.grapheme_at(*next_idx)
|
||||||
|
.is_some_and(|c| is_other_class_or_is_ws(c, &cur_char))
|
||||||
|
};
|
||||||
|
if on_boundary {
|
||||||
|
let next_idx = indices_iter.next().unwrap();
|
||||||
|
pos = next_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
let on_whitespace = is_whitespace(&cur_char);
|
||||||
|
|
||||||
|
if on_whitespace {
|
||||||
|
let Some(non_ws_pos) =
|
||||||
|
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||||
|
else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = non_ws_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||||
|
return self.grapheme_indices().len();
|
||||||
|
};
|
||||||
|
let Some(next_ws_pos) = indices_iter.find(|i| {
|
||||||
|
self
|
||||||
|
.grapheme_at(*i)
|
||||||
|
.is_some_and(|c| is_other_class_or_is_ws(c, &cur_char))
|
||||||
|
}) else {
|
||||||
|
return default;
|
||||||
|
};
|
||||||
|
pos = next_ws_pos;
|
||||||
|
|
||||||
|
if pos == 0 {
|
||||||
|
pos
|
||||||
|
} else {
|
||||||
|
pos + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn grapheme_index_for_display_col(&self, line: &str, target_col: usize) -> usize {
|
fn grapheme_index_for_display_col(&self, line: &str, target_col: usize) -> usize {
|
||||||
let mut col = 0;
|
let mut col = 0;
|
||||||
for (grapheme_index, g) in line.graphemes(true).enumerate() {
|
for (grapheme_index, g) in line.graphemes(true).enumerate() {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl Readline for FernVi {
|
|||||||
loop {
|
loop {
|
||||||
raw_mode_guard.disable_for(|| self.print_line())?;
|
raw_mode_guard.disable_for(|| self.print_line())?;
|
||||||
|
|
||||||
let Some(key) = self.reader.read_key() else {
|
let Some(key) = self.reader.read_key()? else {
|
||||||
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
raw_mode_guard.disable_for(|| self.writer.flush_write("\n"))?;
|
||||||
std::mem::drop(raw_mode_guard);
|
std::mem::drop(raw_mode_guard);
|
||||||
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"));
|
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"));
|
||||||
@@ -116,11 +116,17 @@ impl FernVi {
|
|||||||
old_layout: None,
|
old_layout: None,
|
||||||
repeat_action: None,
|
repeat_action: None,
|
||||||
repeat_motion: None,
|
repeat_motion: None,
|
||||||
editor: LineBuf::new().with_initial(LOREM_IPSUM, 0),
|
editor: LineBuf::new(),
|
||||||
history: History::new()?,
|
history: History::new()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_initial(mut self, initial: &str) -> Self {
|
||||||
|
self.editor = LineBuf::new().with_initial(initial, 0);
|
||||||
|
self.history.update_pending_cmd(self.editor.as_str());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_layout(&mut self) -> Layout {
|
pub fn get_layout(&mut self) -> Layout {
|
||||||
let line = self.editor.to_string();
|
let line = self.editor.to_string();
|
||||||
flog!(DEBUG, line);
|
flog!(DEBUG, line);
|
||||||
@@ -268,8 +274,10 @@ impl FernVi {
|
|||||||
self.repeat_action = mode.as_replay();
|
self.repeat_action = mode.as_replay();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.editor.exec_cmd(cmd)?;
|
// Set cursor clamp BEFORE executing the command so that motions
|
||||||
|
// (like EndOfLine for 'A') can reach positions valid in the new mode
|
||||||
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
self.editor.set_cursor_clamp(self.mode.clamp_cursor());
|
||||||
|
self.editor.exec_cmd(cmd)?;
|
||||||
|
|
||||||
if selecting {
|
if selecting {
|
||||||
self
|
self
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ pub trait WidthCalculator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait KeyReader {
|
pub trait KeyReader {
|
||||||
fn read_key(&mut self) -> Option<KeyEvent>;
|
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LineWriter {
|
pub trait LineWriter {
|
||||||
@@ -232,12 +232,10 @@ impl TermBuffer {
|
|||||||
impl Read for TermBuffer {
|
impl Read for TermBuffer {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
assert!(isatty(self.tty).is_ok_and(|r| r));
|
assert!(isatty(self.tty).is_ok_and(|r| r));
|
||||||
loop {
|
|
||||||
match nix::unistd::read(self.tty, buf) {
|
match nix::unistd::read(self.tty, buf) {
|
||||||
Ok(n) => return Ok(n),
|
Ok(n) => Ok(n),
|
||||||
Err(Errno::EINTR) => {}
|
Err(Errno::EINTR) => Err(Errno::EINTR.into()),
|
||||||
Err(e) => return Err(std::io::Error::from_raw_os_error(e as i32)),
|
Err(e) => Err(std::io::Error::from_raw_os_error(e as i32)),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -420,24 +418,24 @@ impl TermReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl KeyReader for TermReader {
|
impl KeyReader for TermReader {
|
||||||
fn read_key(&mut self) -> Option<KeyEvent> {
|
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr> {
|
||||||
use core::str;
|
use core::str;
|
||||||
|
|
||||||
let mut collected = Vec::with_capacity(4);
|
let mut collected = Vec::with_capacity(4);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let byte = self.next_byte().ok()?;
|
let byte = self.next_byte()?;
|
||||||
flog!(DEBUG, "read byte: {:?}", byte as char);
|
flog!(DEBUG, "read byte: {:?}", byte as char);
|
||||||
collected.push(byte);
|
collected.push(byte);
|
||||||
|
|
||||||
// If it's an escape seq, delegate to ESC sequence handler
|
// If it's an escape seq, delegate to ESC sequence handler
|
||||||
if collected[0] == 0x1b && collected.len() == 1 && self.poll(PollTimeout::ZERO).ok()? {
|
if collected[0] == 0x1b && collected.len() == 1 && self.poll(PollTimeout::ZERO)? {
|
||||||
return self.parse_esc_seq().ok();
|
return self.parse_esc_seq().map(Some);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parse as valid UTF-8
|
// Try parse as valid UTF-8
|
||||||
if let Ok(s) = str::from_utf8(&collected) {
|
if let Ok(s) = str::from_utf8(&collected) {
|
||||||
return Some(KeyEvent::new(s, ModKeys::empty()));
|
return Ok(Some(KeyEvent::new(s, ModKeys::empty())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTF-8 max 4 bytes — if it’s invalid at this point, bail
|
// UTF-8 max 4 bytes — if it’s invalid at this point, bail
|
||||||
@@ -446,7 +444,7 @@ impl KeyReader for TermReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
173
src/signal.rs
173
src/signal.rs
@@ -1,79 +1,178 @@
|
|||||||
|
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||||
|
|
||||||
|
use nix::sys::signal::{SaFlags, SigAction, sigaction};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
jobs::{take_term, JobCmdFlags, JobID},
|
jobs::{JobCmdFlags, JobID, take_term},
|
||||||
libsh::{error::ShResult, sys::sh_quit},
|
libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
state::{read_jobs, write_jobs},
|
state::{read_jobs, write_jobs},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static GOT_SIGINT: AtomicBool = AtomicBool::new(false);
|
||||||
|
static GOT_SIGHUP: AtomicBool = AtomicBool::new(false);
|
||||||
|
static GOT_SIGTSTP: AtomicBool = AtomicBool::new(false);
|
||||||
|
static GOT_SIGCHLD: AtomicBool = AtomicBool::new(false);
|
||||||
|
static REAPING_ENABLED: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
|
static SHOULD_QUIT: AtomicBool = AtomicBool::new(false);
|
||||||
|
static QUIT_CODE: AtomicI32 = AtomicI32::new(0);
|
||||||
|
|
||||||
|
pub fn signals_pending() -> bool {
|
||||||
|
GOT_SIGINT.load(Ordering::SeqCst)
|
||||||
|
|| GOT_SIGHUP.load(Ordering::SeqCst)
|
||||||
|
|| GOT_SIGTSTP.load(Ordering::SeqCst)
|
||||||
|
|| (REAPING_ENABLED.load(Ordering::SeqCst)
|
||||||
|
&& GOT_SIGCHLD.load(Ordering::SeqCst))
|
||||||
|
|| SHOULD_QUIT.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_signals() -> ShResult<()> {
|
||||||
|
if GOT_SIGINT.swap(false, Ordering::SeqCst) {
|
||||||
|
interrupt()?;
|
||||||
|
return Err(ShErr::simple(ShErrKind::ClearReadline, ""));
|
||||||
|
}
|
||||||
|
if GOT_SIGHUP.swap(false, Ordering::SeqCst) {
|
||||||
|
hang_up(0);
|
||||||
|
}
|
||||||
|
if GOT_SIGTSTP.swap(false, Ordering::SeqCst) {
|
||||||
|
terminal_stop()?;
|
||||||
|
}
|
||||||
|
if REAPING_ENABLED.load(Ordering::SeqCst) && GOT_SIGCHLD.swap(false, Ordering::SeqCst) {
|
||||||
|
wait_child()?;
|
||||||
|
}
|
||||||
|
if SHOULD_QUIT.load(Ordering::SeqCst) {
|
||||||
|
let code = QUIT_CODE.load(Ordering::SeqCst);
|
||||||
|
return Err(ShErr::simple(ShErrKind::CleanExit(code), "exit"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_reaping() {
|
||||||
|
REAPING_ENABLED.store(false, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
pub fn enable_reaping() {
|
||||||
|
REAPING_ENABLED.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sig_setup() {
|
pub fn sig_setup() {
|
||||||
|
let flags = SaFlags::empty();
|
||||||
|
|
||||||
|
let actions = [
|
||||||
|
SigAction::new(
|
||||||
|
SigHandler::Handler(handle_sigchld),
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new(
|
||||||
|
SigHandler::Handler(handle_sigquit),
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new(
|
||||||
|
SigHandler::Handler(handle_sigtstp),
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new(
|
||||||
|
SigHandler::Handler(handle_sighup),
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new(
|
||||||
|
SigHandler::Handler(handle_sigint),
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new( // SIGTTIN
|
||||||
|
SigHandler::SigIgn,
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
SigAction::new( // SIGTTOU
|
||||||
|
SigHandler::SigIgn,
|
||||||
|
flags,
|
||||||
|
SigSet::empty(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
sigaction(Signal::SIGCHLD, &actions[0]).unwrap();
|
||||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
sigaction(Signal::SIGQUIT, &actions[1]).unwrap();
|
||||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
sigaction(Signal::SIGTSTP, &actions[2]).unwrap();
|
||||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
sigaction(Signal::SIGHUP, &actions[3]).unwrap();
|
||||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
sigaction(Signal::SIGINT, &actions[4]).unwrap();
|
||||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
sigaction(Signal::SIGTTIN, &actions[5]).unwrap();
|
||||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
sigaction(Signal::SIGTTOU, &actions[6]).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||||
|
GOT_SIGHUP.store(true, Ordering::SeqCst);
|
||||||
|
SHOULD_QUIT.store(true, Ordering::SeqCst);
|
||||||
|
QUIT_CODE.store(128 + libc::SIGHUP, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hang_up(_: libc::c_int) {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
for job in j.jobs_mut().iter_mut().flatten() {
|
for job in j.jobs_mut().iter_mut().flatten() {
|
||||||
job.killpg(Signal::SIGTERM).ok();
|
job.killpg(Signal::SIGTERM).ok();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||||
|
GOT_SIGTSTP.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn terminal_stop() -> ShResult<()> {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.get_fg_mut() {
|
if let Some(job) = j.get_fg_mut() {
|
||||||
job.killpg(Signal::SIGTSTP).ok();
|
job.killpg(Signal::SIGTSTP)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
// TODO: It seems like there is supposed to be a take_term() call here
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||||
|
GOT_SIGINT.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt() -> ShResult<()> {
|
||||||
write_jobs(|j| {
|
write_jobs(|j| {
|
||||||
if let Some(job) = j.get_fg_mut() {
|
if let Some(job) = j.get_fg_mut() {
|
||||||
job.killpg(Signal::SIGINT).ok();
|
job.killpg(Signal::SIGINT)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "C" fn ignore_sigchld(_: libc::c_int) {
|
|
||||||
/*
|
|
||||||
Do nothing
|
|
||||||
|
|
||||||
This function exists because using SIGIGN to ignore SIGCHLD
|
|
||||||
will cause the kernel to automatically reap the child process, which is not what we want.
|
|
||||||
This handler will leave the signaling process as a zombie, allowing us
|
|
||||||
to handle it somewhere else.
|
|
||||||
|
|
||||||
This handler is used when we want to handle SIGCHLD explicitly,
|
|
||||||
like in the case of handling foreground jobs
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn handle_sigquit(_: libc::c_int) {
|
extern "C" fn handle_sigquit(_: libc::c_int) {
|
||||||
sh_quit(0)
|
SHOULD_QUIT.store(true, Ordering::SeqCst);
|
||||||
|
QUIT_CODE.store(128 + libc::SIGQUIT, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn handle_sigchld(_: libc::c_int) {
|
extern "C" fn handle_sigchld(_: libc::c_int) {
|
||||||
|
GOT_SIGCHLD.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_child() -> ShResult<()> {
|
||||||
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED;
|
||||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||||
if let Err(e) = match status {
|
match status {
|
||||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
WtStat::Exited(pid, _) => child_exited(pid, status)?,
|
||||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal)?,
|
||||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
WtStat::Stopped(pid, signal) => child_stopped(pid, signal)?,
|
||||||
WtStat::Continued(pid) => child_continued(pid),
|
WtStat::Continued(pid) => child_continued(pid)?,
|
||||||
WtStat::StillAlive => break,
|
WtStat::StillAlive => break,
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
} {
|
|
||||||
eprintln!("{}", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||||
|
|||||||
581
src/state.rs
581
src/state.rs
@@ -1,8 +1,5 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque}, fmt::Display, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref}, str::FromStr, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration
|
||||||
ops::Deref,
|
|
||||||
sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
|
||||||
time::Duration,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use nix::unistd::{gethostname, getppid, User};
|
use nix::unistd::{gethostname, getppid, User};
|
||||||
@@ -19,15 +16,248 @@ use crate::{
|
|||||||
shopt::ShOpts,
|
shopt::ShOpts,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
pub struct Fern {
|
||||||
|
pub jobs: JobTab,
|
||||||
|
pub var_scopes: ScopeStack,
|
||||||
|
pub meta: MetaTab,
|
||||||
|
pub logic: LogTab,
|
||||||
|
pub shopts: ShOpts,
|
||||||
|
}
|
||||||
|
|
||||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
impl Fern {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
jobs: JobTab::new(),
|
||||||
|
var_scopes: ScopeStack::new(),
|
||||||
|
meta: MetaTab::new(),
|
||||||
|
logic: LogTab::new(),
|
||||||
|
shopts: ShOpts::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn write_jobs(&mut self) -> &mut JobTab {
|
||||||
|
&mut self.jobs
|
||||||
|
}
|
||||||
|
pub fn write_vars(&mut self) -> &mut ScopeStack {
|
||||||
|
&mut self.var_scopes
|
||||||
|
}
|
||||||
|
pub fn write_meta(&mut self) -> &mut MetaTab {
|
||||||
|
&mut self.meta
|
||||||
|
}
|
||||||
|
pub fn write_logic(&mut self) -> &mut LogTab {
|
||||||
|
&mut self.logic
|
||||||
|
}
|
||||||
|
pub fn write_shopts(&mut self) -> &mut ShOpts {
|
||||||
|
&mut self.shopts
|
||||||
|
}
|
||||||
|
pub fn read_jobs(&self) -> &JobTab {
|
||||||
|
&self.jobs
|
||||||
|
}
|
||||||
|
pub fn read_vars(&self) -> &ScopeStack {
|
||||||
|
&self.var_scopes
|
||||||
|
}
|
||||||
|
pub fn read_meta(&self) -> &MetaTab {
|
||||||
|
&self.meta
|
||||||
|
}
|
||||||
|
pub fn read_logic(&self) -> &LogTab {
|
||||||
|
&self.logic
|
||||||
|
}
|
||||||
|
pub fn read_shopts(&self) -> &ShOpts {
|
||||||
|
&self.shopts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub static META_TABLE: LazyLock<RwLock<MetaTab>> = LazyLock::new(|| RwLock::new(MetaTab::new()));
|
impl Default for Fern {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
|
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
|
||||||
|
pub enum ShellParam {
|
||||||
|
// Global
|
||||||
|
Status,
|
||||||
|
ShPid,
|
||||||
|
LastJob,
|
||||||
|
ShellName,
|
||||||
|
|
||||||
pub static SHOPTS: LazyLock<RwLock<ShOpts>> = LazyLock::new(|| RwLock::new(ShOpts::default()));
|
// Local
|
||||||
|
Pos(usize),
|
||||||
|
AllArgs,
|
||||||
|
AllArgsStr,
|
||||||
|
ArgCount
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ShellParam {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Status => write!(f, "?"),
|
||||||
|
Self::ShPid => write!(f, "$"),
|
||||||
|
Self::LastJob => write!(f, "!"),
|
||||||
|
Self::ShellName => write!(f, "0"),
|
||||||
|
Self::Pos(n) => write!(f, "{}", n),
|
||||||
|
Self::AllArgs => write!(f, "@"),
|
||||||
|
Self::AllArgsStr => write!(f, "*"),
|
||||||
|
Self::ArgCount => write!(f, "#"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ShellParam {
|
||||||
|
type Err = ShErr;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"?" => Ok(Self::Status),
|
||||||
|
"$" => Ok(Self::ShPid),
|
||||||
|
"!" => Ok(Self::LastJob),
|
||||||
|
"0" => Ok(Self::ShellName),
|
||||||
|
"@" => Ok(Self::AllArgs),
|
||||||
|
"*" => Ok(Self::AllArgsStr),
|
||||||
|
"#" => Ok(Self::ArgCount),
|
||||||
|
n if n.parse::<usize>().is_ok() => {
|
||||||
|
let idx = n.parse::<usize>().unwrap();
|
||||||
|
Ok(Self::Pos(idx))
|
||||||
|
}
|
||||||
|
_ => Err(ShErr::simple(
|
||||||
|
ShErrKind::InternalErr,
|
||||||
|
format!("Invalid shell parameter: {}", s),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct ScopeStack {
|
||||||
|
// ALWAYS keep one scope.
|
||||||
|
// The bottom scope is the global variable space.
|
||||||
|
// Scopes that come after that are pushed in functions,
|
||||||
|
// and only contain variables that are defined using `local`.
|
||||||
|
scopes: Vec<VarTab>,
|
||||||
|
depth: u32,
|
||||||
|
|
||||||
|
// Global parameters such as $?, $!, $$, etc
|
||||||
|
global_params: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScopeStack {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut new = Self::default();
|
||||||
|
new.scopes.push(VarTab::new());
|
||||||
|
new
|
||||||
|
}
|
||||||
|
pub fn descend(&mut self, argv: Option<Vec<String>>) {
|
||||||
|
let mut new_vars = VarTab::new();
|
||||||
|
if let Some(argv) = argv {
|
||||||
|
for arg in argv {
|
||||||
|
new_vars.bpush_arg(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.scopes.push(new_vars);
|
||||||
|
self.depth += 1;
|
||||||
|
}
|
||||||
|
pub fn ascend(&mut self) {
|
||||||
|
if self.depth >= 1 {
|
||||||
|
self.scopes.pop();
|
||||||
|
self.depth -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn cur_scope(&self) -> &VarTab {
|
||||||
|
self.scopes.last().unwrap()
|
||||||
|
}
|
||||||
|
pub fn cur_scope_mut(&mut self) -> &mut VarTab {
|
||||||
|
self.scopes.last_mut().unwrap()
|
||||||
|
}
|
||||||
|
pub fn unset_var(&mut self, var_name: &str) {
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
scope.unset_var(var_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn export_var(&mut self, var_name: &str) {
|
||||||
|
for scope in self.scopes.iter_mut().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
scope.export_var(var_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub fn flatten_vars(&self) -> HashMap<String, Var> {
|
||||||
|
let mut flat_vars = HashMap::new();
|
||||||
|
for scope in self.scopes.iter() {
|
||||||
|
for (var_name, var) in scope.vars() {
|
||||||
|
flat_vars.insert(var_name.clone(), var.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flat_vars
|
||||||
|
}
|
||||||
|
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||||
|
if flags.contains(VarFlags::LOCAL) {
|
||||||
|
self.set_var_local(var_name, val, flags);
|
||||||
|
} else {
|
||||||
|
self.set_var_global(var_name, val, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn set_var_global(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||||
|
if let Some(scope) = self.scopes.first_mut() {
|
||||||
|
scope.set_var(var_name, val, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn set_var_local(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||||
|
if let Some(scope) = self.scopes.last_mut() {
|
||||||
|
scope.set_var(var_name, val, flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_var(&self, var_name: &str) -> String {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
if scope.var_exists(var_name) {
|
||||||
|
return scope.get_var(var_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to env var
|
||||||
|
std::env::var(var_name).unwrap_or_default()
|
||||||
|
}
|
||||||
|
pub fn get_param(&self, param: ShellParam) -> String {
|
||||||
|
for scope in self.scopes.iter().rev() {
|
||||||
|
let val = scope.get_param(param);
|
||||||
|
if !val.is_empty() {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to empty string
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
|
/// Set a shell parameter
|
||||||
|
/// Therefore, these are global state and we use the global scope
|
||||||
|
pub fn set_param(&mut self, param: ShellParam, val: &str) {
|
||||||
|
match param {
|
||||||
|
ShellParam::ShPid |
|
||||||
|
ShellParam::Status |
|
||||||
|
ShellParam::LastJob |
|
||||||
|
ShellParam::ShellName => {
|
||||||
|
self.global_params.insert(param.to_string(), val.to_string());
|
||||||
|
}
|
||||||
|
ShellParam::Pos(_) |
|
||||||
|
ShellParam::AllArgs |
|
||||||
|
ShellParam::AllArgsStr |
|
||||||
|
ShellParam::ArgCount => {
|
||||||
|
if let Some(scope) = self.scopes.first_mut() {
|
||||||
|
scope.set_param(param, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static FERN: LazyLock<RwLock<Fern>> = LazyLock::new(|| RwLock::new(Fern::new()));
|
||||||
|
|
||||||
/// A shell function
|
/// A shell function
|
||||||
///
|
///
|
||||||
@@ -108,35 +338,143 @@ impl LogTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct VarFlags(u8);
|
||||||
|
|
||||||
|
impl VarFlags {
|
||||||
|
pub const NONE : Self = Self(0);
|
||||||
|
pub const EXPORT : Self = Self(1 << 0);
|
||||||
|
pub const LOCAL : Self = Self(1 << 1);
|
||||||
|
pub const READONLY : Self = Self(1 << 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitOr for VarFlags {
|
||||||
|
type Output = Self;
|
||||||
|
fn bitor(self, rhs: Self) -> Self::Output {
|
||||||
|
Self(self.0 | rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitOrAssign for VarFlags {
|
||||||
|
fn bitor_assign(&mut self, rhs: Self) {
|
||||||
|
self.0 |= rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitAnd for VarFlags {
|
||||||
|
type Output = Self;
|
||||||
|
fn bitand(self, rhs: Self) -> Self::Output {
|
||||||
|
Self(self.0 & rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitAndAssign for VarFlags {
|
||||||
|
fn bitand_assign(&mut self, rhs: Self) {
|
||||||
|
self.0 &= rhs.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarFlags {
|
||||||
|
pub fn contains(&self, flag: Self) -> bool {
|
||||||
|
(self.0 & flag.0) == flag.0
|
||||||
|
}
|
||||||
|
pub fn intersects(&self, flag: Self) -> bool {
|
||||||
|
(self.0 & flag.0) != 0
|
||||||
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, flag: Self) {
|
||||||
|
self.0 |= flag.0;
|
||||||
|
}
|
||||||
|
pub fn remove(&mut self, flag: Self) {
|
||||||
|
self.0 &= !flag.0;
|
||||||
|
}
|
||||||
|
pub fn toggle(&mut self, flag: Self) {
|
||||||
|
self.0 ^= flag.0;
|
||||||
|
}
|
||||||
|
pub fn set(&mut self, flag: Self, value: bool) {
|
||||||
|
if value {
|
||||||
|
self.insert(flag);
|
||||||
|
} else {
|
||||||
|
self.remove(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum VarKind {
|
||||||
|
Str(String),
|
||||||
|
Int(i32),
|
||||||
|
Arr(Vec<String>),
|
||||||
|
AssocArr(Vec<(String, String)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for VarKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
VarKind::Str(s) => write!(f, "{s}"),
|
||||||
|
VarKind::Int(i) => write!(f, "{i}"),
|
||||||
|
VarKind::Arr(items) => {
|
||||||
|
let mut item_iter = items.iter().peekable();
|
||||||
|
while let Some(item) = item_iter.next() {
|
||||||
|
write!(f, "{item}")?;
|
||||||
|
if item_iter.peek().is_some() {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
VarKind::AssocArr(items) => {
|
||||||
|
let mut item_iter = items.iter().peekable();
|
||||||
|
while let Some(item) = item_iter.next() {
|
||||||
|
let (k,v) = item;
|
||||||
|
write!(f, "{k}={v}")?;
|
||||||
|
if item_iter.peek().is_some() {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Var {
|
pub struct Var {
|
||||||
export: bool,
|
flags: VarFlags,
|
||||||
value: String,
|
kind: VarKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Var {
|
impl Var {
|
||||||
pub fn new(value: String) -> Self {
|
pub fn new(kind: VarKind, flags: VarFlags) -> Self {
|
||||||
Self {
|
Self {
|
||||||
export: false,
|
flags,
|
||||||
value,
|
kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn kind(&self) -> &VarKind {
|
||||||
|
&self.kind
|
||||||
|
}
|
||||||
|
pub fn kind_mut(&mut self) -> &mut VarKind {
|
||||||
|
&mut self.kind
|
||||||
|
}
|
||||||
pub fn mark_for_export(&mut self) {
|
pub fn mark_for_export(&mut self) {
|
||||||
self.export = true;
|
self.flags.set(VarFlags::EXPORT, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Var {
|
impl Display for Var {
|
||||||
type Target = String;
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fn deref(&self) -> &Self::Target {
|
self.kind.fmt(f)
|
||||||
&self.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String, Var>,
|
vars: HashMap<String, Var>,
|
||||||
params: HashMap<String, String>,
|
params: HashMap<ShellParam, String>,
|
||||||
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift`
|
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift`
|
||||||
* straightforward */
|
* straightforward */
|
||||||
}
|
}
|
||||||
@@ -154,20 +492,19 @@ impl VarTab {
|
|||||||
var_tab.init_sh_argv();
|
var_tab.init_sh_argv();
|
||||||
var_tab
|
var_tab
|
||||||
}
|
}
|
||||||
fn init_params() -> HashMap<String, String> {
|
fn init_params() -> HashMap<ShellParam, String> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("?".into(), "0".into()); // Last command exit status
|
params.insert(ShellParam::ArgCount, "0".into()); // Number of positional parameters
|
||||||
params.insert("#".into(), "0".into()); // Number of positional parameters
|
|
||||||
params.insert(
|
params.insert(
|
||||||
"0".into(),
|
ShellParam::Pos(0),
|
||||||
std::env::current_exe()
|
std::env::current_exe()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
); // Name of the shell
|
); // Name of the shell
|
||||||
params.insert("$".into(), Pid::this().to_string()); // PID of the shell
|
params.insert(ShellParam::ShPid, Pid::this().to_string()); // PID of the shell
|
||||||
params.insert("!".into(), "".into()); // PID of the last background job (if any)
|
params.insert(ShellParam::LastJob, "".into()); // PID of the last background job (if any)
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
fn init_env() {
|
fn init_env() {
|
||||||
@@ -202,6 +539,7 @@ impl VarTab {
|
|||||||
.map(|hname| hname.to_string_lossy().to_string())
|
.map(|hname| hname.to_string_lossy().to_string())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
env::set_var("IFS", " \t\n");
|
env::set_var("IFS", " \t\n");
|
||||||
env::set_var("HOST", hostname.clone());
|
env::set_var("HOST", hostname.clone());
|
||||||
env::set_var("UID", uid.to_string());
|
env::set_var("UID", uid.to_string());
|
||||||
@@ -218,6 +556,7 @@ impl VarTab {
|
|||||||
env::set_var("FERN_HIST", format!("{}/.fernhist", home));
|
env::set_var("FERN_HIST", format!("{}/.fernhist", home));
|
||||||
env::set_var("FERN_RC", format!("{}/.fernrc", home));
|
env::set_var("FERN_RC", format!("{}/.fernrc", home));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
for arg in env::args() {
|
for arg in env::args() {
|
||||||
self.bpush_arg(arg);
|
self.bpush_arg(arg);
|
||||||
@@ -226,10 +565,10 @@ impl VarTab {
|
|||||||
pub fn update_exports(&mut self) {
|
pub fn update_exports(&mut self) {
|
||||||
for var_name in self.vars.keys() {
|
for var_name in self.vars.keys() {
|
||||||
let var = self.vars.get(var_name).unwrap();
|
let var = self.vars.get(var_name).unwrap();
|
||||||
if var.export {
|
if var.flags.contains(VarFlags::EXPORT) {
|
||||||
env::set_var(var_name, &var.value);
|
unsafe { env::set_var(var_name, var.to_string()) };
|
||||||
} else {
|
} else {
|
||||||
env::set_var(var_name, "");
|
unsafe { env::set_var(var_name, "") };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -247,8 +586,8 @@ impl VarTab {
|
|||||||
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
fn update_arg_params(&mut self) {
|
fn update_arg_params(&mut self) {
|
||||||
self.set_param("@", &self.sh_argv.clone().to_vec()[1..].join(" "));
|
self.set_param(ShellParam::AllArgs, &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||||
self.set_param("#", &(self.sh_argv.len() - 1).to_string());
|
self.set_param(ShellParam::ArgCount, &(self.sh_argv.len() - 1).to_string());
|
||||||
}
|
}
|
||||||
/// Push an arg to the front of the arg deque
|
/// Push an arg to the front of the arg deque
|
||||||
pub fn fpush_arg(&mut self, arg: String) {
|
pub fn fpush_arg(&mut self, arg: String) {
|
||||||
@@ -278,21 +617,21 @@ impl VarTab {
|
|||||||
pub fn vars_mut(&mut self) -> &mut HashMap<String, Var> {
|
pub fn vars_mut(&mut self) -> &mut HashMap<String, Var> {
|
||||||
&mut self.vars
|
&mut self.vars
|
||||||
}
|
}
|
||||||
pub fn params(&self) -> &HashMap<String, String> {
|
pub fn params(&self) -> &HashMap<ShellParam, String> {
|
||||||
&self.params
|
&self.params
|
||||||
}
|
}
|
||||||
pub fn params_mut(&mut self) -> &mut HashMap<String, String> {
|
pub fn params_mut(&mut self) -> &mut HashMap<ShellParam, String> {
|
||||||
&mut self.params
|
&mut self.params
|
||||||
}
|
}
|
||||||
pub fn export_var(&mut self, var_name: &str) {
|
pub fn export_var(&mut self, var_name: &str) {
|
||||||
if let Some(var) = self.vars.get_mut(var_name) {
|
if let Some(var) = self.vars.get_mut(var_name) {
|
||||||
var.mark_for_export();
|
var.mark_for_export();
|
||||||
env::set_var(var_name, &var.value);
|
unsafe { env::set_var(var_name, var.to_string()) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var: &str) -> String {
|
pub fn get_var(&self, var: &str) -> String {
|
||||||
if var.chars().count() == 1 || var.parse::<usize>().is_ok() {
|
if let Ok(param) = var.parse::<ShellParam>() {
|
||||||
let param = self.get_param(var);
|
let param = self.get_param(param);
|
||||||
if !param.is_empty() {
|
if !param.is_empty() {
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
@@ -303,52 +642,59 @@ impl VarTab {
|
|||||||
std::env::var(var).unwrap_or_default()
|
std::env::var(var).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_var(&mut self, var_name: &str, val: &str, export: bool) {
|
pub fn unset_var(&mut self, var_name: &str) {
|
||||||
|
self.vars.remove(var_name);
|
||||||
|
unsafe { env::remove_var(var_name) };
|
||||||
|
}
|
||||||
|
pub fn set_var(&mut self, var_name: &str, val: &str, flags: VarFlags) {
|
||||||
if let Some(var) = self.vars.get_mut(var_name) {
|
if let Some(var) = self.vars.get_mut(var_name) {
|
||||||
var.value = val.to_string();
|
var.kind = VarKind::Str(val.to_string());
|
||||||
if var.export {
|
if var.flags.contains(VarFlags::EXPORT) || flags.contains(VarFlags::EXPORT) {
|
||||||
env::set_var(var_name, val);
|
if flags.contains(VarFlags::EXPORT) && !var.flags.contains(VarFlags::EXPORT) {
|
||||||
|
var.mark_for_export();
|
||||||
|
}
|
||||||
|
unsafe { env::set_var(var_name, val) };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut var = Var::new(val.to_string());
|
let mut var = Var::new(VarKind::Str(val.to_string()), VarFlags::NONE);
|
||||||
if export {
|
if flags.contains(VarFlags::EXPORT) {
|
||||||
var.mark_for_export();
|
var.mark_for_export();
|
||||||
env::set_var(var_name, &*var);
|
unsafe { env::set_var(var_name, var.to_string()) };
|
||||||
}
|
}
|
||||||
self.vars.insert(var_name.to_string(), var);
|
self.vars.insert(var_name.to_string(), var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
if var_name.parse::<usize>().is_ok() {
|
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||||
return self.params.contains_key(var_name);
|
return self.params.contains_key(¶m);
|
||||||
}
|
}
|
||||||
self.vars.contains_key(var_name) || (var_name.len() == 1 && self.params.contains_key(var_name))
|
self.vars.contains_key(var_name)
|
||||||
}
|
}
|
||||||
pub fn set_param(&mut self, param: &str, val: &str) {
|
pub fn set_param(&mut self, param: ShellParam, val: &str) {
|
||||||
self.params.insert(param.to_string(), val.to_string());
|
self.params.insert(param, val.to_string());
|
||||||
}
|
}
|
||||||
pub fn get_param(&self, param: &str) -> String {
|
pub fn get_param(&self, param: ShellParam) -> String {
|
||||||
if param.parse::<usize>().is_ok() {
|
match param {
|
||||||
let argv_idx = param.to_string().parse::<usize>().unwrap();
|
ShellParam::Pos(n) => {
|
||||||
let arg = self
|
|
||||||
.sh_argv
|
|
||||||
.get(argv_idx)
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
arg
|
|
||||||
} else if param == "?" {
|
|
||||||
self
|
self
|
||||||
.params
|
.sh_argv()
|
||||||
.get(param)
|
.get(n)
|
||||||
.map(|s| s.to_string())
|
|
||||||
.unwrap_or("0".into())
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
.params
|
|
||||||
.get(param)
|
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
ShellParam::Status => {
|
||||||
|
self
|
||||||
|
.params
|
||||||
|
.get(&ShellParam::Status)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or("0".into())
|
||||||
|
}
|
||||||
|
_ => self
|
||||||
|
.params
|
||||||
|
.get(¶m)
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,60 +720,77 @@ impl MetaTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the job table
|
/// Read from the job table
|
||||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T {
|
||||||
let lock = JOB_TABLE.read().unwrap();
|
let fern = FERN.read().unwrap();
|
||||||
f(lock)
|
let jobs = fern.read_jobs();
|
||||||
|
f(jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the job table
|
/// Write to the job table
|
||||||
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T {
|
||||||
let lock = &mut JOB_TABLE.write().unwrap();
|
let mut fern = FERN.write().unwrap();
|
||||||
f(lock)
|
let jobs = &mut fern.jobs;
|
||||||
|
f(jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the variable table
|
/// Read from the var scope stack
|
||||||
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T {
|
||||||
let lock = VAR_TABLE.read().unwrap();
|
let fern = FERN.read().unwrap();
|
||||||
f(lock)
|
let vars = fern.read_vars();
|
||||||
|
f(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the variable table
|
/// Write to the variable table
|
||||||
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T {
|
||||||
let lock = &mut VAR_TABLE.write().unwrap();
|
let mut fern = FERN.write().unwrap();
|
||||||
f(lock)
|
let vars = fern.write_vars();
|
||||||
|
f(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
|
||||||
let lock = META_TABLE.read().unwrap();
|
let fern = FERN.read().unwrap();
|
||||||
f(lock)
|
let meta = fern.read_meta();
|
||||||
|
f(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the variable table
|
/// Write to the variable table
|
||||||
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T {
|
||||||
let lock = &mut META_TABLE.write().unwrap();
|
let mut fern = FERN.write().unwrap();
|
||||||
f(lock)
|
let meta = fern.write_meta();
|
||||||
|
f(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the logic table
|
/// Read from the logic table
|
||||||
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T {
|
||||||
let lock = LOGIC_TABLE.read().unwrap();
|
let fern = FERN.read().unwrap();
|
||||||
f(lock)
|
let logic = fern.read_logic();
|
||||||
|
f(logic)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the logic table
|
/// Write to the logic table
|
||||||
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T {
|
||||||
let lock = &mut LOGIC_TABLE.write().unwrap();
|
let mut fern = FERN.write().unwrap();
|
||||||
f(lock)
|
let logic = &mut fern.logic;
|
||||||
|
f(logic)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_shopts<T, F: FnOnce(RwLockReadGuard<ShOpts>) -> T>(f: F) -> T {
|
pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T {
|
||||||
let lock = SHOPTS.read().unwrap();
|
let fern = FERN.read().unwrap();
|
||||||
f(lock)
|
let shopts = fern.read_shopts();
|
||||||
|
f(shopts)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_shopts<T, F: FnOnce(&mut RwLockWriteGuard<ShOpts>) -> T>(f: F) -> T {
|
pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T {
|
||||||
let lock = &mut SHOPTS.write().unwrap();
|
let mut fern = FERN.write().unwrap();
|
||||||
f(lock)
|
let shopts = &mut fern.shopts;
|
||||||
|
f(shopts)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn descend_scope(argv: Option<Vec<String>>) {
|
||||||
|
write_vars(|v| v.descend(argv));
|
||||||
|
}
|
||||||
|
pub fn ascend_scope() {
|
||||||
|
write_vars(|v| v.ascend());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function is used internally and ideally never sees user input
|
/// This function is used internally and ideally never sees user input
|
||||||
@@ -438,31 +801,11 @@ pub fn get_shopt(path: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
read_vars(|v| v.get_param("?")).parse::<i32>().unwrap()
|
read_vars(|v| v.get_param(ShellParam::Status)).parse::<i32>().unwrap()
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn set_status(code: i32) {
|
pub fn set_status(code: i32) {
|
||||||
write_vars(|v| v.set_param("?", &code.to_string()))
|
write_vars(|v| v.set_param(ShellParam::Status, &code.to_string()))
|
||||||
}
|
|
||||||
|
|
||||||
/// Save the current state of the logic and variable table, and the working
|
|
||||||
/// directory path
|
|
||||||
pub fn get_snapshots() -> (LogTab, VarTab, String) {
|
|
||||||
(
|
|
||||||
read_logic(|l| l.clone()),
|
|
||||||
read_vars(|v| v.clone()),
|
|
||||||
env::var("PWD").unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore_snapshot(snapshot: (LogTab, VarTab, String)) {
|
|
||||||
write_logic(|l| **l = snapshot.0);
|
|
||||||
write_vars(|v| {
|
|
||||||
**v = snapshot.1;
|
|
||||||
v.update_exports();
|
|
||||||
});
|
|
||||||
env::set_current_dir(&snapshot.2).unwrap();
|
|
||||||
env::set_var("PWD", &snapshot.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_rc() -> ShResult<()> {
|
pub fn source_rc() -> ShResult<()> {
|
||||||
|
|||||||
Reference in New Issue
Block a user