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
|
||||
rust-toolchain.toml
|
||||
/ref
|
||||
|
||||
# cachix tmp file
|
||||
store-path-pre-build
|
||||
|
||||
241
Cargo.lock
generated
241
Cargo.lock
generated
@@ -4,18 +4,18 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
version = "0.6.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -28,50 +28,50 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.10"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.7"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell",
|
||||
"windows-sys",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.8.0"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
@@ -81,9 +81,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.38"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000"
|
||||
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -91,9 +91,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.38"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120"
|
||||
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -103,9 +103,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.32"
|
||||
version = "4.5.55"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -115,15 +115,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.4"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
|
||||
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
@@ -134,7 +134,7 @@ dependencies = [
|
||||
"encode_unicode",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -149,6 +149,22 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "fern"
|
||||
version = "0.1.0"
|
||||
@@ -165,10 +181,22 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.2"
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
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]]
|
||||
name = "heck"
|
||||
@@ -178,40 +206,39 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.42.2"
|
||||
version = "1.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084"
|
||||
checksum = "248b42847813a1550dafd15296fd9748c651d0c32194559dbc05d804d54b21e8"
|
||||
dependencies = [
|
||||
"console",
|
||||
"linked-hash-map",
|
||||
"once_cell",
|
||||
"pin-project",
|
||||
"similar",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
@@ -227,29 +254,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.1"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
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",
|
||||
]
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
@@ -263,27 +276,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
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 = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -293,9 +312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -304,9 +323,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
version = "0.8.8"
|
||||
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]]
|
||||
name = "similar"
|
||||
@@ -322,9 +354,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -332,10 +364,23 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
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]]
|
||||
name = "unicode-segmentation"
|
||||
@@ -345,9 +390,9 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.0"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
@@ -355,6 +400,21 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -364,6 +424,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -428,6 +497,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
|
||||
@@ -4,7 +4,7 @@ description = "A linux shell written in rust"
|
||||
publish = false
|
||||
version = "0.1.0"
|
||||
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn cd(node: Node, job: &mut JobBldr) -> ShResult<()> {
|
||||
|
||||
env::set_current_dir(new_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);
|
||||
Ok(())
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::{
|
||||
libsh::error::ShResult,
|
||||
parse::{NdRule, Node},
|
||||
prelude::*,
|
||||
procio::{borrow_fd, IoStack},
|
||||
state::{self, write_vars},
|
||||
procio::{IoStack, borrow_fd},
|
||||
state::{self, VarFlags, write_vars},
|
||||
};
|
||||
|
||||
use super::setup_builtin;
|
||||
@@ -34,7 +34,7 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult
|
||||
} else {
|
||||
for (arg, _) in argv {
|
||||
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'
|
||||
} else {
|
||||
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 {
|
||||
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::iter::Peekable;
|
||||
use std::mem::take;
|
||||
use std::str::{Chars, FromStr};
|
||||
|
||||
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::prelude::*;
|
||||
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'];
|
||||
|
||||
@@ -40,7 +41,7 @@ impl Tk {
|
||||
pub fn expand(self) -> ShResult<Self> {
|
||||
let flags = self.flags;
|
||||
let span = self.span.clone();
|
||||
let exp = Expander::new(self).expand()?;
|
||||
let exp = Expander::new(self)?.expand()?;
|
||||
let class = TkRule::Expanded { exp };
|
||||
Ok(Self { class, span, flags })
|
||||
}
|
||||
@@ -58,9 +59,11 @@ pub struct Expander {
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
pub fn new(raw: Tk) -> Self {
|
||||
let unescaped = unescape_str(raw.span.as_str());
|
||||
Self { raw: unescaped }
|
||||
pub fn new(raw: Tk) -> ShResult<Self> {
|
||||
let mut raw = raw.span.as_str().to_string();
|
||||
raw = expand_braces_full(&raw)?.join(" ");
|
||||
let unescaped = unescape_str(&raw);
|
||||
Ok(Self { raw: unescaped })
|
||||
}
|
||||
pub fn expand(&mut self) -> ShResult<Vec<String>> {
|
||||
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> {
|
||||
let mut result = String::new();
|
||||
|
||||
@@ -897,7 +1217,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
||||
}
|
||||
ParamExp::SetDefaultUnsetOrNull(default) => {
|
||||
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)
|
||||
} else {
|
||||
Ok(vars.get_var(&var_name))
|
||||
@@ -905,7 +1225,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
||||
}
|
||||
ParamExp::SetDefaultUnset(default) => {
|
||||
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)
|
||||
} else {
|
||||
Ok(vars.get_var(&var_name))
|
||||
@@ -1061,7 +1381,7 @@ pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
||||
}
|
||||
ParamExp::VarNamesWithPrefix(prefix) => {
|
||||
let mut match_vars = vec![];
|
||||
for var in vars.vars().keys() {
|
||||
for var in vars.flatten_vars().keys() {
|
||||
if var.starts_with(&prefix) {
|
||||
match_vars.push(var.clone())
|
||||
}
|
||||
|
||||
50
src/fern.rs
50
src/fern.rs
@@ -18,10 +18,11 @@ pub mod state;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
use crate::libsh::error::ShErrKind;
|
||||
use crate::libsh::sys::{save_termios, set_termios};
|
||||
use crate::parse::execute::exec_input;
|
||||
use crate::prelude::*;
|
||||
use crate::signal::sig_setup;
|
||||
use crate::signal::{check_signals, sig_setup, signals_pending};
|
||||
use crate::state::source_rc;
|
||||
use clap::Parser;
|
||||
use shopt::FernEditMode;
|
||||
@@ -78,9 +79,9 @@ fn run_script<P: AsRef<Path>>(path: P, args: Vec<String>) {
|
||||
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 {
|
||||
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) {
|
||||
@@ -100,26 +101,49 @@ fn fern_interactive() {
|
||||
|
||||
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
|
||||
let edit_mode = write_shopts(|opt| opt.query("prompt.edit_mode"))
|
||||
.unwrap()
|
||||
.map(|mode| mode.parse::<FernEditMode>().unwrap_or_default())
|
||||
.unwrap();
|
||||
let input = match prompt::readline(edit_mode) {
|
||||
let input = match prompt::readline(edit_mode, Some(&partial_input)) {
|
||||
Ok(line) => {
|
||||
readline_err_count = 0;
|
||||
partial_input.clear();
|
||||
line
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
readline_err_count += 1;
|
||||
if readline_err_count == 20 {
|
||||
eprintln!("reached maximum readline error count, exiting");
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
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}");
|
||||
readline_err_count += 1;
|
||||
if readline_err_count == 20 {
|
||||
eprintln!("reached maximum readline error count, exiting");
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
32
src/jobs.rs
32
src/jobs.rs
@@ -2,10 +2,7 @@ use crate::{
|
||||
libsh::{
|
||||
error::ShResult,
|
||||
term::{Style, Styled},
|
||||
},
|
||||
prelude::*,
|
||||
procio::{borrow_fd, IoMode},
|
||||
state::{self, set_status, write_jobs},
|
||||
}, prelude::*, procio::{IoMode, borrow_fd}, signal::{disable_reaping, enable_reaping}, state::{self, set_status, write_jobs}
|
||||
};
|
||||
|
||||
pub const SIG_EXIT_OFFSET: i32 = 128;
|
||||
@@ -643,29 +640,6 @@ pub fn take_term() -> ShResult<()> {
|
||||
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
|
||||
pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||
if job.children().is_empty() {
|
||||
@@ -674,7 +648,7 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||
flog!(TRACE, "Waiting on foreground job");
|
||||
let mut code = 0;
|
||||
attach_tty(job.pgid())?;
|
||||
disable_reaping()?;
|
||||
disable_reaping();
|
||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||
for status in statuses {
|
||||
match status {
|
||||
@@ -697,7 +671,7 @@ pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||
take_term()?;
|
||||
set_status(code);
|
||||
flog!(TRACE, "exit code: {}", code);
|
||||
enable_reaping()?;
|
||||
enable_reaping();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -388,7 +388,7 @@ impl From<std::env::VarError> for ShErr {
|
||||
|
||||
impl From<Errno> for ShErr {
|
||||
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,
|
||||
ResourceLimitExceeded,
|
||||
BadPermission,
|
||||
Errno,
|
||||
Errno(Errno),
|
||||
FileNotFound(String),
|
||||
CmdNotFound(String),
|
||||
ReadlineIntr(String),
|
||||
ReadlineErr,
|
||||
|
||||
// Not really errors, more like internal signals
|
||||
CleanExit(i32),
|
||||
FuncReturn(i32),
|
||||
LoopContinue(i32),
|
||||
LoopBreak(i32),
|
||||
ReadlineErr,
|
||||
ClearReadline,
|
||||
Null,
|
||||
}
|
||||
|
||||
@@ -424,14 +428,16 @@ impl Display for ShErrKind {
|
||||
Self::ExecFail => "Execution Failed",
|
||||
Self::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||
Self::BadPermission => "Bad Permissions",
|
||||
Self::Errno => "ERRNO",
|
||||
Self::Errno(e) => &format!("Errno: {}", e.desc()),
|
||||
Self::FileNotFound(file) => &format!("File not found: {file}"),
|
||||
Self::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||
Self::CleanExit(_) => "",
|
||||
Self::FuncReturn(_) => "",
|
||||
Self::LoopContinue(_) => "",
|
||||
Self::LoopBreak(_) => "",
|
||||
Self::ReadlineErr => "Line Read Error",
|
||||
Self::ReadlineIntr(_) => "",
|
||||
Self::ReadlineErr => "Readline Error",
|
||||
Self::ClearReadline => "",
|
||||
Self::Null => "",
|
||||
};
|
||||
write!(f, "{output}")
|
||||
|
||||
1304
src/parse/execute.rs
1304
src/parse/execute.rs
File diff suppressed because it is too large
Load Diff
@@ -26,11 +26,17 @@ fn get_prompt() -> ShResult<String> {
|
||||
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 mut reader: Box<dyn Readline> = match edit_mode {
|
||||
FernEditMode::Vi => Box::new(FernVi::new(Some(prompt))?),
|
||||
FernEditMode::Emacs => todo!(),
|
||||
FernEditMode::Vi => {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -622,17 +622,25 @@ impl LineBuf {
|
||||
.map(|slice| slice.graphemes(true).filter(|g| *g == "\n").count())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
pub fn is_sentence_punctuation(&mut self, pos: usize) -> bool {
|
||||
if let Some(gr) = self.grapheme_at(pos) {
|
||||
if PUNCTUATION.contains(&gr) && self.grapheme_after(pos).is_some() {
|
||||
pub fn is_sentence_punctuation(&self, pos: usize) -> bool {
|
||||
self.next_sentence_start_from_punctuation(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();
|
||||
// Skip any closing delimiters after the punctuation
|
||||
if self
|
||||
.grapheme_after(pos)
|
||||
.read_grapheme_after(pos)
|
||||
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
||||
{
|
||||
while let Some(idx) = fwd_indices.peek() {
|
||||
if self
|
||||
.grapheme_after(*idx)
|
||||
.read_grapheme_at(*idx)
|
||||
.is_some_and(|gr| [")", "]", "\"", "'"].contains(&gr))
|
||||
{
|
||||
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(gr) = self.grapheme_at(idx) {
|
||||
if let Some(gr) = self.read_grapheme_at(idx) {
|
||||
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 {
|
||||
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) {
|
||||
self.cursor.get()
|
||||
} else {
|
||||
self.end_of_word_forward_or_start_of_word_backward_from(
|
||||
self.cursor.get(),
|
||||
word,
|
||||
Direction::Backward,
|
||||
)
|
||||
self.start_of_word_backward(self.cursor.get(), word)
|
||||
};
|
||||
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, true);
|
||||
Some((start, end))
|
||||
@@ -888,11 +908,7 @@ impl LineBuf {
|
||||
let start = if self.is_word_bound(self.cursor.get(), word, Direction::Backward) {
|
||||
self.cursor.get()
|
||||
} else {
|
||||
self.end_of_word_forward_or_start_of_word_backward_from(
|
||||
self.cursor.get(),
|
||||
word,
|
||||
Direction::Backward,
|
||||
)
|
||||
self.start_of_word_backward(self.cursor.get(), word)
|
||||
};
|
||||
let end = self.dispatch_word_motion(count, To::Start, word, Direction::Forward, false);
|
||||
Some((start, end))
|
||||
@@ -907,39 +923,26 @@ impl LineBuf {
|
||||
) -> Option<(usize, usize)> {
|
||||
let mut start = 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() {
|
||||
let Some(gr) = self.grapheme_at(idx) else {
|
||||
end = Some(self.cursor.max);
|
||||
if self.grapheme_at(idx).is_none() {
|
||||
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 {
|
||||
Bound::Inside => {
|
||||
end = Some(idx);
|
||||
break;
|
||||
}
|
||||
Bound::Around => {
|
||||
let mut end_pos = idx;
|
||||
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);
|
||||
end = Some(next_sentence_start);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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();
|
||||
while let Some(idx) = bkwd_indices.next() {
|
||||
@@ -949,10 +952,6 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
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 let Some((_, new_end)) = self.text_obj_sentence(end, count - 1, bound) {
|
||||
@@ -1321,7 +1320,6 @@ impl LineBuf {
|
||||
dir: Direction,
|
||||
include_last_char: bool,
|
||||
) -> usize {
|
||||
// Not sorry for these method names btw
|
||||
let mut pos = ClampedUsize::new(self.cursor.get(), self.cursor.max, false);
|
||||
for i in 0..count {
|
||||
// We alter 'include_last_char' to only be true on the last iteration
|
||||
@@ -1331,16 +1329,12 @@ impl LineBuf {
|
||||
pos.set(match to {
|
||||
To::Start => {
|
||||
match dir {
|
||||
Direction::Forward => self.start_of_word_forward_or_end_of_word_backward_from(
|
||||
pos.get(),
|
||||
word,
|
||||
dir,
|
||||
include_last_char_and_is_last_word,
|
||||
),
|
||||
Direction::Forward => {
|
||||
self.start_of_word_forward(pos.get(), word, include_last_char_and_is_last_word)
|
||||
}
|
||||
Direction::Backward => 'backward: {
|
||||
// We also need to handle insert mode's Ctrl+W behaviors here
|
||||
let target =
|
||||
self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir);
|
||||
let target = self.start_of_word_backward(pos.get(), word);
|
||||
|
||||
// Check to see if we are in insert mode
|
||||
let Some(start_pos) = self.insert_mode_start_pos else {
|
||||
@@ -1361,38 +1355,18 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
To::End => match dir {
|
||||
Direction::Forward => {
|
||||
self.end_of_word_forward_or_start_of_word_backward_from(pos.get(), word, dir)
|
||||
}
|
||||
Direction::Backward => {
|
||||
self.start_of_word_forward_or_end_of_word_backward_from(pos.get(), word, dir, false)
|
||||
}
|
||||
Direction::Forward => self.end_of_word_forward(pos.get(), word),
|
||||
Direction::Backward => self.end_of_word_backward(pos.get(), word, false),
|
||||
},
|
||||
});
|
||||
}
|
||||
pos.get()
|
||||
}
|
||||
|
||||
/// Finds the start of a word forward, or the end of a word backward
|
||||
///
|
||||
/// Finding the start of a word in the forward direction, and finding the end
|
||||
/// of a word in the backward direction are logically the same operation, if
|
||||
/// 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
|
||||
/// 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 {
|
||||
let default = self.grapheme_indices().len();
|
||||
let mut indices_iter = (pos..self.cursor.max).peekable();
|
||||
|
||||
match word {
|
||||
Word::Big => {
|
||||
@@ -1404,7 +1378,6 @@ impl LineBuf {
|
||||
let Some(idx) = indices_iter.next() else {
|
||||
return default;
|
||||
};
|
||||
// We have a 'cw' call, do not include the trailing whitespace
|
||||
if include_last_char {
|
||||
return idx;
|
||||
} else {
|
||||
@@ -1412,15 +1385,14 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
|
||||
// Check current grapheme
|
||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||
return default;
|
||||
};
|
||||
let on_whitespace = is_whitespace(&cur_char);
|
||||
|
||||
// Find the next 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 {
|
||||
return default;
|
||||
};
|
||||
@@ -1429,11 +1401,9 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
|
||||
// Return the next visible grapheme position
|
||||
let non_ws_pos = indices_iter
|
||||
indices_iter
|
||||
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||
.unwrap_or(default);
|
||||
non_ws_pos
|
||||
.unwrap_or(default)
|
||||
}
|
||||
Word::Normal => {
|
||||
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);
|
||||
|
||||
// Advance until hitting whitespace or a different character class
|
||||
if !on_whitespace {
|
||||
let other_class_pos = indices_iter.find(|i| {
|
||||
self
|
||||
@@ -1472,7 +1441,6 @@ impl LineBuf {
|
||||
let Some(other_class_pos) = other_class_pos else {
|
||||
return default;
|
||||
};
|
||||
// If we hit a different character class, we return here
|
||||
if self
|
||||
.grapheme_at(other_class_pos)
|
||||
.is_some_and(|c| !is_whitespace(c))
|
||||
@@ -1482,79 +1450,54 @@ impl LineBuf {
|
||||
}
|
||||
}
|
||||
|
||||
// We are now certainly on a whitespace character. Advance until a
|
||||
// non-whitespace character.
|
||||
let non_ws_pos = indices_iter
|
||||
indices_iter
|
||||
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||
.unwrap_or(default);
|
||||
non_ws_pos
|
||||
.unwrap_or(default)
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 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 {
|
||||
Word::Big => {
|
||||
let Some(next_idx) = indices_iter.peek() else {
|
||||
let Some(next) = indices_iter.peek() else {
|
||||
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 {
|
||||
let Some(idx) = indices_iter.next() else {
|
||||
return default;
|
||||
};
|
||||
pos = idx;
|
||||
if include_last_char {
|
||||
return idx;
|
||||
} else {
|
||||
pos = idx;
|
||||
}
|
||||
}
|
||||
// Check current grapheme
|
||||
|
||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||
return default;
|
||||
};
|
||||
let on_whitespace = is_whitespace(&cur_char);
|
||||
|
||||
// Advance iterator to next visible grapheme
|
||||
if on_whitespace {
|
||||
let Some(_non_ws_pos) =
|
||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||
if !on_whitespace {
|
||||
let Some(ws_pos) =
|
||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(is_whitespace))
|
||||
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))
|
||||
else {
|
||||
return default;
|
||||
};
|
||||
pos = next_ws_pos;
|
||||
|
||||
if pos == self.grapheme_indices().len() {
|
||||
// We reached the end of the buffer
|
||||
pos
|
||||
} else {
|
||||
// We hit some whitespace, so we will go back one
|
||||
match dir {
|
||||
Direction::Forward => pos.saturating_sub(1),
|
||||
Direction::Backward => pos + 1,
|
||||
if include_last_char {
|
||||
return ws_pos;
|
||||
}
|
||||
}
|
||||
|
||||
indices_iter
|
||||
.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||
.unwrap_or(default)
|
||||
}
|
||||
Word::Normal => {
|
||||
let Some(cur_char) = self.grapheme_at(pos).map(|c| c.to_string()) else {
|
||||
@@ -1568,32 +1511,131 @@ impl LineBuf {
|
||||
.grapheme_at(*next_idx)
|
||||
.is_none_or(|c| is_other_class_or_is_ws(c, &cur_char));
|
||||
if on_boundary {
|
||||
let next_idx = indices_iter.next().unwrap();
|
||||
pos = next_idx
|
||||
if include_last_char {
|
||||
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 {
|
||||
return default;
|
||||
};
|
||||
let on_whitespace = is_whitespace(&cur_char);
|
||||
|
||||
// Proceed to next visible grapheme
|
||||
if on_whitespace {
|
||||
let Some(non_ws_pos) =
|
||||
indices_iter.find(|i| self.grapheme_at(*i).is_some_and(|c| !is_whitespace(c)))
|
||||
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(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 {
|
||||
return self.grapheme_indices().len();
|
||||
};
|
||||
// The position of the next differing character class will tell us where the end
|
||||
// (or start) of the word is
|
||||
let Some(next_ws_pos) = indices_iter.find(|i| {
|
||||
let Some(next_ws_pos) = fwd_indices.find(|i| {
|
||||
self
|
||||
.grapheme_at(*i)
|
||||
.is_some_and(|c| is_other_class_or_is_ws(c, &cur_char))
|
||||
@@ -1603,18 +1645,113 @@ impl LineBuf {
|
||||
pos = next_ws_pos;
|
||||
|
||||
if pos == self.grapheme_indices().len() {
|
||||
// We reached the end of the buffer
|
||||
pos
|
||||
} else {
|
||||
// We hit some other character class, so we go back one
|
||||
match dir {
|
||||
Direction::Forward => pos.saturating_sub(1),
|
||||
Direction::Backward => pos + 1,
|
||||
}
|
||||
pos.saturating_sub(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 {
|
||||
let mut col = 0;
|
||||
for (grapheme_index, g) in line.graphemes(true).enumerate() {
|
||||
|
||||
@@ -48,7 +48,7 @@ impl Readline for FernVi {
|
||||
loop {
|
||||
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"))?;
|
||||
std::mem::drop(raw_mode_guard);
|
||||
return Err(ShErr::simple(ShErrKind::ReadlineErr, "EOF"));
|
||||
@@ -116,11 +116,17 @@ impl FernVi {
|
||||
old_layout: None,
|
||||
repeat_action: None,
|
||||
repeat_motion: None,
|
||||
editor: LineBuf::new().with_initial(LOREM_IPSUM, 0),
|
||||
editor: LineBuf::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 {
|
||||
let line = self.editor.to_string();
|
||||
flog!(DEBUG, line);
|
||||
@@ -268,8 +274,10 @@ impl FernVi {
|
||||
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.exec_cmd(cmd)?;
|
||||
|
||||
if selecting {
|
||||
self
|
||||
|
||||
@@ -167,7 +167,7 @@ pub trait WidthCalculator {
|
||||
}
|
||||
|
||||
pub trait KeyReader {
|
||||
fn read_key(&mut self) -> Option<KeyEvent>;
|
||||
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr>;
|
||||
}
|
||||
|
||||
pub trait LineWriter {
|
||||
@@ -232,13 +232,11 @@ impl TermBuffer {
|
||||
impl Read for TermBuffer {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
assert!(isatty(self.tty).is_ok_and(|r| r));
|
||||
loop {
|
||||
match nix::unistd::read(self.tty, buf) {
|
||||
Ok(n) => return Ok(n),
|
||||
Err(Errno::EINTR) => {}
|
||||
Err(e) => return Err(std::io::Error::from_raw_os_error(e as i32)),
|
||||
}
|
||||
}
|
||||
match nix::unistd::read(self.tty, buf) {
|
||||
Ok(n) => Ok(n),
|
||||
Err(Errno::EINTR) => Err(Errno::EINTR.into()),
|
||||
Err(e) => Err(std::io::Error::from_raw_os_error(e as i32)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,24 +418,24 @@ impl TermReader {
|
||||
}
|
||||
|
||||
impl KeyReader for TermReader {
|
||||
fn read_key(&mut self) -> Option<KeyEvent> {
|
||||
fn read_key(&mut self) -> Result<Option<KeyEvent>, ShErr> {
|
||||
use core::str;
|
||||
|
||||
let mut collected = Vec::with_capacity(4);
|
||||
|
||||
loop {
|
||||
let byte = self.next_byte().ok()?;
|
||||
let byte = self.next_byte()?;
|
||||
flog!(DEBUG, "read byte: {:?}", byte as char);
|
||||
collected.push(byte);
|
||||
|
||||
// If it's an escape seq, delegate to ESC sequence handler
|
||||
if collected[0] == 0x1b && collected.len() == 1 && self.poll(PollTimeout::ZERO).ok()? {
|
||||
return self.parse_esc_seq().ok();
|
||||
if collected[0] == 0x1b && collected.len() == 1 && self.poll(PollTimeout::ZERO)? {
|
||||
return self.parse_esc_seq().map(Some);
|
||||
}
|
||||
|
||||
// Try parse as valid UTF-8
|
||||
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
|
||||
@@ -446,7 +444,7 @@ impl KeyReader for TermReader {
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
177
src/signal.rs
177
src/signal.rs
@@ -1,79 +1,178 @@
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
|
||||
use nix::sys::signal::{SaFlags, SigAction, sigaction};
|
||||
|
||||
use crate::{
|
||||
jobs::{take_term, JobCmdFlags, JobID},
|
||||
libsh::{error::ShResult, sys::sh_quit},
|
||||
jobs::{JobCmdFlags, JobID, take_term},
|
||||
libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit},
|
||||
prelude::*,
|
||||
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() {
|
||||
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 {
|
||||
signal(Signal::SIGCHLD, SigHandler::Handler(handle_sigchld)).unwrap();
|
||||
signal(Signal::SIGQUIT, SigHandler::Handler(handle_sigquit)).unwrap();
|
||||
signal(Signal::SIGTSTP, SigHandler::Handler(handle_sigtstp)).unwrap();
|
||||
signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup)).unwrap();
|
||||
signal(Signal::SIGINT, SigHandler::Handler(handle_sigint)).unwrap();
|
||||
signal(Signal::SIGTTIN, SigHandler::SigIgn).unwrap();
|
||||
signal(Signal::SIGTTOU, SigHandler::SigIgn).unwrap();
|
||||
sigaction(Signal::SIGCHLD, &actions[0]).unwrap();
|
||||
sigaction(Signal::SIGQUIT, &actions[1]).unwrap();
|
||||
sigaction(Signal::SIGTSTP, &actions[2]).unwrap();
|
||||
sigaction(Signal::SIGHUP, &actions[3]).unwrap();
|
||||
sigaction(Signal::SIGINT, &actions[4]).unwrap();
|
||||
sigaction(Signal::SIGTTIN, &actions[5]).unwrap();
|
||||
sigaction(Signal::SIGTTOU, &actions[6]).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
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| {
|
||||
for job in j.jobs_mut().iter_mut().flatten() {
|
||||
job.killpg(Signal::SIGTERM).ok();
|
||||
}
|
||||
});
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigtstp(_: libc::c_int) {
|
||||
GOT_SIGTSTP.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn terminal_stop() -> ShResult<()> {
|
||||
write_jobs(|j| {
|
||||
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) {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGINT).ok();
|
||||
}
|
||||
});
|
||||
GOT_SIGINT.store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
pub fn interrupt() -> ShResult<()> {
|
||||
write_jobs(|j| {
|
||||
if let Some(job) = j.get_fg_mut() {
|
||||
job.killpg(Signal::SIGINT)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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;
|
||||
while let Ok(status) = waitpid(None, Some(flags)) {
|
||||
if let Err(e) = match status {
|
||||
WtStat::Exited(pid, _) => child_exited(pid, status),
|
||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal),
|
||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal),
|
||||
WtStat::Continued(pid) => child_continued(pid),
|
||||
match status {
|
||||
WtStat::Exited(pid, _) => child_exited(pid, status)?,
|
||||
WtStat::Signaled(pid, signal, _) => child_signaled(pid, signal)?,
|
||||
WtStat::Stopped(pid, signal) => child_stopped(pid, signal)?,
|
||||
WtStat::Continued(pid) => child_continued(pid)?,
|
||||
WtStat::StillAlive => break,
|
||||
_ => unimplemented!(),
|
||||
} {
|
||||
eprintln!("{}", e)
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn child_signaled(pid: Pid, sig: Signal) -> ShResult<()> {
|
||||
|
||||
623
src/state.rs
623
src/state.rs
@@ -1,8 +1,5 @@
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
ops::Deref,
|
||||
sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
|
||||
time::Duration,
|
||||
collections::{HashMap, VecDeque}, fmt::Display, ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, Deref}, str::FromStr, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration
|
||||
};
|
||||
|
||||
use nix::unistd::{gethostname, getppid, User};
|
||||
@@ -19,15 +16,248 @@ use crate::{
|
||||
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
|
||||
///
|
||||
@@ -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)]
|
||||
pub struct Var {
|
||||
export: bool,
|
||||
value: String,
|
||||
flags: VarFlags,
|
||||
kind: VarKind,
|
||||
}
|
||||
|
||||
impl Var {
|
||||
pub fn new(value: String) -> Self {
|
||||
pub fn new(kind: VarKind, flags: VarFlags) -> Self {
|
||||
Self {
|
||||
export: false,
|
||||
value,
|
||||
flags,
|
||||
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) {
|
||||
self.export = true;
|
||||
self.flags.set(VarFlags::EXPORT, true);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Var {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
impl Display for Var {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.kind.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct VarTab {
|
||||
vars: HashMap<String, Var>,
|
||||
params: HashMap<String, String>,
|
||||
params: HashMap<ShellParam, String>,
|
||||
sh_argv: VecDeque<String>, /* Using a VecDeque makes the implementation of `shift`
|
||||
* straightforward */
|
||||
}
|
||||
@@ -154,20 +492,19 @@ impl VarTab {
|
||||
var_tab.init_sh_argv();
|
||||
var_tab
|
||||
}
|
||||
fn init_params() -> HashMap<String, String> {
|
||||
fn init_params() -> HashMap<ShellParam, String> {
|
||||
let mut params = HashMap::new();
|
||||
params.insert("?".into(), "0".into()); // Last command exit status
|
||||
params.insert("#".into(), "0".into()); // Number of positional parameters
|
||||
params.insert(ShellParam::ArgCount, "0".into()); // Number of positional parameters
|
||||
params.insert(
|
||||
"0".into(),
|
||||
ShellParam::Pos(0),
|
||||
std::env::current_exe()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
); // Name of the shell
|
||||
params.insert("$".into(), Pid::this().to_string()); // PID of the shell
|
||||
params.insert("!".into(), "".into()); // PID of the last background job (if any)
|
||||
params.insert(ShellParam::ShPid, Pid::this().to_string()); // PID of the shell
|
||||
params.insert(ShellParam::LastJob, "".into()); // PID of the last background job (if any)
|
||||
params
|
||||
}
|
||||
fn init_env() {
|
||||
@@ -202,21 +539,23 @@ impl VarTab {
|
||||
.map(|hname| hname.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
env::set_var("IFS", " \t\n");
|
||||
env::set_var("HOST", hostname.clone());
|
||||
env::set_var("UID", uid.to_string());
|
||||
env::set_var("PPID", getppid().to_string());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
env::set_var("TERM", term);
|
||||
env::set_var("LANG", "en_US.UTF-8");
|
||||
env::set_var("USER", username.clone());
|
||||
env::set_var("LOGNAME", username);
|
||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("HOME", home.clone());
|
||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||
env::set_var("FERN_HIST", format!("{}/.fernhist", home));
|
||||
env::set_var("FERN_RC", format!("{}/.fernrc", home));
|
||||
unsafe {
|
||||
env::set_var("IFS", " \t\n");
|
||||
env::set_var("HOST", hostname.clone());
|
||||
env::set_var("UID", uid.to_string());
|
||||
env::set_var("PPID", getppid().to_string());
|
||||
env::set_var("TMPDIR", "/tmp");
|
||||
env::set_var("TERM", term);
|
||||
env::set_var("LANG", "en_US.UTF-8");
|
||||
env::set_var("USER", username.clone());
|
||||
env::set_var("LOGNAME", username);
|
||||
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||
env::set_var("HOME", home.clone());
|
||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||
env::set_var("FERN_HIST", format!("{}/.fernhist", home));
|
||||
env::set_var("FERN_RC", format!("{}/.fernrc", home));
|
||||
}
|
||||
}
|
||||
pub fn init_sh_argv(&mut self) {
|
||||
for arg in env::args() {
|
||||
@@ -226,10 +565,10 @@ impl VarTab {
|
||||
pub fn update_exports(&mut self) {
|
||||
for var_name in self.vars.keys() {
|
||||
let var = self.vars.get(var_name).unwrap();
|
||||
if var.export {
|
||||
env::set_var(var_name, &var.value);
|
||||
if var.flags.contains(VarFlags::EXPORT) {
|
||||
unsafe { env::set_var(var_name, var.to_string()) };
|
||||
} 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());
|
||||
}
|
||||
fn update_arg_params(&mut self) {
|
||||
self.set_param("@", &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||
self.set_param("#", &(self.sh_argv.len() - 1).to_string());
|
||||
self.set_param(ShellParam::AllArgs, &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||
self.set_param(ShellParam::ArgCount, &(self.sh_argv.len() - 1).to_string());
|
||||
}
|
||||
/// Push an arg to the front of the arg deque
|
||||
pub fn fpush_arg(&mut self, arg: String) {
|
||||
@@ -278,77 +617,84 @@ impl VarTab {
|
||||
pub fn vars_mut(&mut self) -> &mut HashMap<String, Var> {
|
||||
&mut self.vars
|
||||
}
|
||||
pub fn params(&self) -> &HashMap<String, String> {
|
||||
pub fn params(&self) -> &HashMap<ShellParam, String> {
|
||||
&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
|
||||
}
|
||||
pub fn export_var(&mut self, var_name: &str) {
|
||||
if let Some(var) = self.vars.get_mut(var_name) {
|
||||
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 {
|
||||
if var.chars().count() == 1 || var.parse::<usize>().is_ok() {
|
||||
let param = self.get_param(var);
|
||||
if let Ok(param) = var.parse::<ShellParam>() {
|
||||
let param = self.get_param(param);
|
||||
if !param.is_empty() {
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(var) = self.vars.get(var).map(|s| s.to_string()) {
|
||||
var
|
||||
} else {
|
||||
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) {
|
||||
var.value = val.to_string();
|
||||
if var.export {
|
||||
env::set_var(var_name, val);
|
||||
var.kind = VarKind::Str(val.to_string());
|
||||
if var.flags.contains(VarFlags::EXPORT) || flags.contains(VarFlags::EXPORT) {
|
||||
if flags.contains(VarFlags::EXPORT) && !var.flags.contains(VarFlags::EXPORT) {
|
||||
var.mark_for_export();
|
||||
}
|
||||
unsafe { env::set_var(var_name, val) };
|
||||
}
|
||||
} else {
|
||||
let mut var = Var::new(val.to_string());
|
||||
if export {
|
||||
let mut var = Var::new(VarKind::Str(val.to_string()), VarFlags::NONE);
|
||||
if flags.contains(VarFlags::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);
|
||||
}
|
||||
}
|
||||
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||
if var_name.parse::<usize>().is_ok() {
|
||||
return self.params.contains_key(var_name);
|
||||
if let Ok(param) = var_name.parse::<ShellParam>() {
|
||||
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) {
|
||||
self.params.insert(param.to_string(), val.to_string());
|
||||
pub fn set_param(&mut self, param: ShellParam, val: &str) {
|
||||
self.params.insert(param, val.to_string());
|
||||
}
|
||||
pub fn get_param(&self, param: &str) -> String {
|
||||
if param.parse::<usize>().is_ok() {
|
||||
let argv_idx = param.to_string().parse::<usize>().unwrap();
|
||||
let arg = self
|
||||
.sh_argv
|
||||
.get(argv_idx)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
arg
|
||||
} else if param == "?" {
|
||||
self
|
||||
.params
|
||||
.get(param)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or("0".into())
|
||||
} else {
|
||||
self
|
||||
.params
|
||||
.get(param)
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
pub fn get_param(&self, param: ShellParam) -> String {
|
||||
match param {
|
||||
ShellParam::Pos(n) => {
|
||||
self
|
||||
.sh_argv()
|
||||
.get(n)
|
||||
.map(|s| s.to_string())
|
||||
.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
|
||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
||||
let lock = JOB_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
pub fn read_jobs<T, F: FnOnce(&JobTab) -> T>(f: F) -> T {
|
||||
let fern = FERN.read().unwrap();
|
||||
let jobs = fern.read_jobs();
|
||||
f(jobs)
|
||||
}
|
||||
|
||||
/// Write to the job table
|
||||
pub fn write_jobs<T, F: FnOnce(&mut RwLockWriteGuard<JobTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut JOB_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
pub fn write_jobs<T, F: FnOnce(&mut JobTab) -> T>(f: F) -> T {
|
||||
let mut fern = FERN.write().unwrap();
|
||||
let jobs = &mut fern.jobs;
|
||||
f(jobs)
|
||||
}
|
||||
|
||||
/// Read from the variable table
|
||||
pub fn read_vars<T, F: FnOnce(RwLockReadGuard<VarTab>) -> T>(f: F) -> T {
|
||||
let lock = VAR_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
/// Read from the var scope stack
|
||||
pub fn read_vars<T, F: FnOnce(&ScopeStack) -> T>(f: F) -> T {
|
||||
let fern = FERN.read().unwrap();
|
||||
let vars = fern.read_vars();
|
||||
f(vars)
|
||||
}
|
||||
|
||||
/// Write to the variable table
|
||||
pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut VAR_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
pub fn write_vars<T, F: FnOnce(&mut ScopeStack) -> T>(f: F) -> T {
|
||||
let mut fern = FERN.write().unwrap();
|
||||
let vars = fern.write_vars();
|
||||
f(vars)
|
||||
}
|
||||
|
||||
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
||||
let lock = META_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
pub fn read_meta<T, F: FnOnce(&MetaTab) -> T>(f: F) -> T {
|
||||
let fern = FERN.read().unwrap();
|
||||
let meta = fern.read_meta();
|
||||
f(meta)
|
||||
}
|
||||
|
||||
/// Write to the variable table
|
||||
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut META_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
pub fn write_meta<T, F: FnOnce(&mut MetaTab) -> T>(f: F) -> T {
|
||||
let mut fern = FERN.write().unwrap();
|
||||
let meta = fern.write_meta();
|
||||
f(meta)
|
||||
}
|
||||
|
||||
/// Read from the logic table
|
||||
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
||||
let lock = LOGIC_TABLE.read().unwrap();
|
||||
f(lock)
|
||||
pub fn read_logic<T, F: FnOnce(&LogTab) -> T>(f: F) -> T {
|
||||
let fern = FERN.read().unwrap();
|
||||
let logic = fern.read_logic();
|
||||
f(logic)
|
||||
}
|
||||
|
||||
/// Write to the logic table
|
||||
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
||||
let lock = &mut LOGIC_TABLE.write().unwrap();
|
||||
f(lock)
|
||||
pub fn write_logic<T, F: FnOnce(&mut LogTab) -> T>(f: F) -> T {
|
||||
let mut fern = FERN.write().unwrap();
|
||||
let logic = &mut fern.logic;
|
||||
f(logic)
|
||||
}
|
||||
|
||||
pub fn read_shopts<T, F: FnOnce(RwLockReadGuard<ShOpts>) -> T>(f: F) -> T {
|
||||
let lock = SHOPTS.read().unwrap();
|
||||
f(lock)
|
||||
pub fn read_shopts<T, F: FnOnce(&ShOpts) -> T>(f: F) -> T {
|
||||
let fern = FERN.read().unwrap();
|
||||
let shopts = fern.read_shopts();
|
||||
f(shopts)
|
||||
}
|
||||
|
||||
pub fn write_shopts<T, F: FnOnce(&mut RwLockWriteGuard<ShOpts>) -> T>(f: F) -> T {
|
||||
let lock = &mut SHOPTS.write().unwrap();
|
||||
f(lock)
|
||||
pub fn write_shopts<T, F: FnOnce(&mut ShOpts) -> T>(f: F) -> T {
|
||||
let mut fern = FERN.write().unwrap();
|
||||
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
|
||||
@@ -438,31 +801,11 @@ pub fn get_shopt(path: &str) -> String {
|
||||
}
|
||||
|
||||
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]
|
||||
pub fn set_status(code: i32) {
|
||||
write_vars(|v| v.set_param("?", &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);
|
||||
write_vars(|v| v.set_param(ShellParam::Status, &code.to_string()))
|
||||
}
|
||||
|
||||
pub fn source_rc() -> ShResult<()> {
|
||||
|
||||
Reference in New Issue
Block a user