feat(deps): upgraded brush-core to 0.4.0 and refactored shell execution with new types
- Upgraded brush-core from 0.3.5 to 0.4.0 and added brush-builtins 0.1.0 dependency to support new builtin command framework. - Refactored shell execution to use new ExecutionResult and ExecutionControlFlow types from brush-core 0.4.0, replacing previous ExitCode-based approach. - Updated system_info disk refresh to use new sysinfo 0.38 API with single refresh(true) call instead of separate refresh_list() and refresh() calls. - Added tokio full-featured runtime dependency to support async operations in pi-natives. - Updated phf from 0.11 to 0.13 with macros feature for improved perfect hash function support. - Fixed UTF-16 string handling in text.rs to properly strip trailing null bytes before conversion.
This commit is contained in:
Generated
+368
-145
@@ -217,13 +217,64 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "brush-core"
|
||||
version = "0.3.5"
|
||||
name = "bon"
|
||||
version = "3.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb3a9488ef37cd8b951748d861362af5b1d5a815ea325f83c1c4bbb7da5e328"
|
||||
checksum = "234655ec178edd82b891e262ea7cf71f6584bcd09eff94db786be23f1821825c"
|
||||
dependencies = [
|
||||
"bon-macros",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bon-macros"
|
||||
version = "3.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ec27229c38ed0eb3c0feee3d2c1d6a4379ae44f418a29a658890e062d8f365"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"ident_case",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brush-builtins"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "454219f460ee4308ae5b8befc47936b4ef7ac3a94347052270e59b6c6cfe14ec"
|
||||
dependencies = [
|
||||
"brush-core",
|
||||
"brush-parser",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"clap",
|
||||
"fancy-regex",
|
||||
"futures",
|
||||
"itertools",
|
||||
"nix 0.30.1",
|
||||
"procfs",
|
||||
"rlimit",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brush-core"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a3ad2f2d4eb45ef7f11e74bc1a0816c8f3dce298072352c6b3b41a2c4bfdb74"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"bon",
|
||||
"brush-parser",
|
||||
"cached",
|
||||
"cfg-if",
|
||||
@@ -237,12 +288,9 @@ dependencies = [
|
||||
"hostname",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"normalize-path",
|
||||
"os_pipe",
|
||||
"procfs",
|
||||
"rand 0.9.2",
|
||||
"rlimit",
|
||||
"rpds",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -250,7 +298,6 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uucore",
|
||||
"uuid",
|
||||
"uzers",
|
||||
"whoami",
|
||||
@@ -258,10 +305,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brush-parser"
|
||||
version = "0.2.20"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6924d0efd702b4fe3d65f3a63c03520edfe53b67d0955b9d3f8e66b192a410ff"
|
||||
checksum = "f7367124d4f38fdcd65f4b815bda7caeb3de377b9cd95ffa1b23627989c93718"
|
||||
dependencies = [
|
||||
"bon",
|
||||
"cached",
|
||||
"indenter",
|
||||
"peg",
|
||||
@@ -370,7 +418,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -441,7 +489,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f849b92c694fe237ecd8fafd1ba0df7ae0d45c1df6daeb7f68ed4220d51640bd"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -863,8 +911,8 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
|
||||
dependencies = [
|
||||
"rustix 1.1.3",
|
||||
"windows-link 0.2.1",
|
||||
"rustix",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -994,14 +1042,14 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "homedir"
|
||||
version = "0.3.6"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68df315d2857b2d8d2898be54a85e1d001bbbe0dbb5f8ef847b48dd3a23c4527"
|
||||
checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"widestring",
|
||||
"windows 0.61.3",
|
||||
"windows 0.57.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1012,7 +1060,7 @@ checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1076,6 +1124,71 @@ dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "532b11722e350ab6bf916ba6eb0efe3ee54b932666afec989465f9243fe6dd60"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_locale_data",
|
||||
"icu_provider",
|
||||
"potential_utf",
|
||||
"tinystr",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"serde",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c5f1d16b4c3a2642d3a719f18f6b06070ab0aef246a6418130c955ae08aa831"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"serde",
|
||||
"stable_deref_trait",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
@@ -1200,7 +1313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1220,18 +1333,18 @@ dependencies = [
|
||||
"redox_syscall 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
@@ -1401,6 +1514,18 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
@@ -1540,6 +1665,16 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-io-surface"
|
||||
version = "0.3.2"
|
||||
@@ -1602,7 +1737,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall 0.5.18",
|
||||
"smallvec",
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1644,7 +1779,6 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
@@ -1654,6 +1788,7 @@ version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared 0.13.1",
|
||||
"serde",
|
||||
]
|
||||
@@ -1700,12 +1835,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.3"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
"phf_generator 0.13.1",
|
||||
"phf_shared 0.13.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1734,6 +1869,7 @@ name = "pi-natives"
|
||||
version = "9.7.0"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"brush-builtins",
|
||||
"brush-core",
|
||||
"bstr",
|
||||
"clap",
|
||||
@@ -1750,10 +1886,11 @@ dependencies = [
|
||||
"napi-derive",
|
||||
"os_pipe",
|
||||
"parking_lot",
|
||||
"phf 0.11.3",
|
||||
"phf 0.13.1",
|
||||
"rayon",
|
||||
"syntect",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
@@ -1783,6 +1920,17 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
@@ -1798,6 +1946,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
@@ -1809,23 +1967,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "procfs"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
|
||||
checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"chrono",
|
||||
"flate2",
|
||||
"hex",
|
||||
"procfs-core",
|
||||
"rustix 0.38.44",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "procfs-core"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
|
||||
checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"chrono",
|
||||
@@ -1997,19 +2154,6 @@ version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
@@ -2019,7 +2163,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -2126,6 +2270,22 @@ version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.9.0"
|
||||
@@ -2185,6 +2345,17 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "5.3.0"
|
||||
@@ -2205,17 +2376,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.30.13"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
|
||||
checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"windows 0.52.0",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-kit",
|
||||
"windows 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2235,7 +2405,7 @@ version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
dependencies = [
|
||||
"rustix 1.1.3",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
@@ -2305,8 +2475,10 @@ dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -2433,20 +2605,25 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.1.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9032bf981784f22fcc5ddc7e74b7cf3bae3d5f44a48d2054138ed38068b9f4e0"
|
||||
checksum = "2003164a38a7f39da1de103a70fa66b745f572f0045ec261481539516c0a8a0e"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bstr",
|
||||
"clap",
|
||||
"fluent",
|
||||
"fluent-bundle",
|
||||
"fluent-syntax",
|
||||
"glob",
|
||||
"icu_locale",
|
||||
"itertools",
|
||||
"nix",
|
||||
"nix 0.30.1",
|
||||
"num-traits",
|
||||
"number_prefix",
|
||||
"os_display",
|
||||
"phf 0.13.1",
|
||||
"procfs",
|
||||
"thiserror",
|
||||
"unic-langid",
|
||||
"uucore_procs",
|
||||
@@ -2455,21 +2632,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uucore_procs"
|
||||
version = "0.1.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c933945fdac5b7779eae1fc746146e61f5b0298deb6ede002ce0b6e93e1b3bfc"
|
||||
checksum = "c76f0308f7810d915246a39748e7f5d64e43e6bb9d6c8107224f9d741aefc375"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"uuhelp_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuhelp_parser"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beda381dd5c7927f8682f50b055b0903bb694ba5a4b27fad1b4934bc4fbf7b8d"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.20.0"
|
||||
@@ -2669,82 +2839,82 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
|
||||
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||
dependencies = [
|
||||
"windows-core 0.52.0",
|
||||
"windows-core 0.57.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
"windows-future",
|
||||
"windows-link 0.1.3",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.2.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||
dependencies = [
|
||||
"windows-implement 0.57.0",
|
||||
"windows-interface 0.57.0",
|
||||
"windows-result 0.1.2",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link 0.2.1",
|
||||
"windows-implement 0.60.2",
|
||||
"windows-interface 0.59.3",
|
||||
"windows-link",
|
||||
"windows-result 0.4.1",
|
||||
"windows-strings 0.5.1",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.2.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-core 0.62.2",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
@@ -2756,6 +2926,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.57.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
@@ -2767,12 +2948,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -2781,21 +2956,21 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.2.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core 0.61.2",
|
||||
"windows-link 0.1.3",
|
||||
"windows-core 0.62.2",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2804,16 +2979,7 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2822,16 +2988,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2849,7 +3006,7 @@ version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2874,7 +3031,7 @@ version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows-link",
|
||||
"windows_aarch64_gnullvm 0.53.1",
|
||||
"windows_aarch64_msvc 0.53.1",
|
||||
"windows_i686_gnu 0.53.1",
|
||||
@@ -2887,11 +3044,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link 0.1.3",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2996,6 +3153,12 @@ version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.2"
|
||||
@@ -3003,7 +3166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix 1.1.3",
|
||||
"rustix",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
@@ -3023,6 +3186,29 @@ dependencies = [
|
||||
"markup5ever",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.37"
|
||||
@@ -3048,6 +3234,30 @@ name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
@@ -3056,7 +3266,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -13,13 +13,15 @@ crate-type = ["cdylib"]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
napi = { version = "3", features = ["napi10", "tokio_rt", "tokio_time"] }
|
||||
napi-derive = "3"
|
||||
brush-core = "0.3.5"
|
||||
brush-core = "0.4.0"
|
||||
brush-builtins = "0.1.0"
|
||||
parking_lot = "0.12.5"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
os_pipe = "1"
|
||||
sysinfo = "0.30"
|
||||
sysinfo = "0.38"
|
||||
grep-regex = "0.1"
|
||||
grep-searcher = "0.1"
|
||||
grep-matcher = "0.1"
|
||||
@@ -42,7 +44,7 @@ syntect = { version = "5.3", default-features = false, features = [
|
||||
"regex-fancy",
|
||||
] }
|
||||
html-to-markdown-rs = { version = "2.24", default-features = false }
|
||||
phf = { version = "0.11", features = ["macros"] }
|
||||
phf = { version = "0.13", features = ["macros"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
@@ -24,9 +24,12 @@ use std::{
|
||||
};
|
||||
|
||||
use brush_core::{
|
||||
CreateOptions, ExecutionContext, OpenFile, OpenFiles, ProcessGroupPolicy, Shell as BrushShell,
|
||||
ShellValue, ShellVariable, builtins, env::EnvironmentScope,
|
||||
CreateOptions, ExecutionContext, ExecutionControlFlow, ExecutionExitCode, ExecutionResult,
|
||||
ProcessGroupPolicy, Shell as BrushShell, ShellValue, ShellVariable, builtins,
|
||||
env::EnvironmentScope,
|
||||
openfiles::{self, OpenFile, OpenFiles},
|
||||
};
|
||||
use brush_builtins::{BuiltinSet, default_builtins};
|
||||
use clap::Parser;
|
||||
use napi::{
|
||||
bindgen_prelude::*,
|
||||
@@ -357,7 +360,25 @@ async fn execute_shell_with_options(
|
||||
remove_session(&options.session_key);
|
||||
}
|
||||
|
||||
Ok(ShellExecuteResult { exit_code: Some(i32::from(run_result.exit_code)), cancelled, timed_out })
|
||||
Ok(ShellExecuteResult { exit_code: exit_code(&run_result), cancelled, timed_out })
|
||||
}
|
||||
|
||||
fn null_file() -> Result<OpenFile> {
|
||||
Ok(openfiles::null()
|
||||
.map_err(|err| Error::from_reason(format!("Failed to create null file: {err}")))?)
|
||||
}
|
||||
|
||||
fn exit_code(result: &ExecutionResult) -> Option<i32> {
|
||||
match result.exit_code {
|
||||
ExecutionExitCode::Success => Some(0),
|
||||
ExecutionExitCode::GeneralError => Some(1),
|
||||
ExecutionExitCode::InvalidUsage => Some(2),
|
||||
ExecutionExitCode::Unimplemented => Some(99),
|
||||
ExecutionExitCode::CannotExecute => Some(126),
|
||||
ExecutionExitCode::NotFound => Some(127),
|
||||
ExecutionExitCode::Interrupted => Some(130),
|
||||
ExecutionExitCode::Custom(code) => Some(code as i32),
|
||||
}
|
||||
}
|
||||
|
||||
/// Abort a running shell execution by ID.
|
||||
@@ -397,17 +418,18 @@ async fn create_session(options: &ShellExecuteOptions) -> Result<ShellSession> {
|
||||
no_profile: true,
|
||||
no_rc: true,
|
||||
do_not_inherit_env: true,
|
||||
builtins: default_builtins(BuiltinSet::BashMode),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut shell = BrushShell::new(&create_options)
|
||||
let mut shell = BrushShell::new(create_options)
|
||||
.await
|
||||
.map_err(|err| Error::from_reason(format!("Failed to initialize shell: {err}")))?;
|
||||
|
||||
if let Some(exec_builtin) = shell.builtins.get_mut("exec") {
|
||||
if let Some(exec_builtin) = shell.builtin_mut("exec") {
|
||||
exec_builtin.disabled = true;
|
||||
}
|
||||
if let Some(suspend_builtin) = shell.builtins.get_mut("suspend") {
|
||||
if let Some(suspend_builtin) = shell.builtin_mut("suspend") {
|
||||
suspend_builtin.disabled = true;
|
||||
}
|
||||
shell.register_builtin("sleep", builtins::builtin::<SleepCommand>());
|
||||
@@ -436,11 +458,9 @@ async fn create_session(options: &ShellExecuteOptions) -> Result<ShellSession> {
|
||||
|
||||
async fn source_snapshot(shell: &mut BrushShell, snapshot_path: &str) -> Result<()> {
|
||||
let mut params = shell.default_exec_params();
|
||||
let mut open_files = shell.open_files.clone();
|
||||
open_files.set(OpenFiles::STDIN_FD, OpenFile::Null);
|
||||
open_files.set(OpenFiles::STDOUT_FD, OpenFile::Null);
|
||||
open_files.set(OpenFiles::STDERR_FD, OpenFile::Null);
|
||||
params.open_files = open_files;
|
||||
params.set_fd(OpenFiles::STDIN_FD, null_file()?);
|
||||
params.set_fd(OpenFiles::STDOUT_FD, null_file()?);
|
||||
params.set_fd(OpenFiles::STDERR_FD, null_file()?);
|
||||
|
||||
let escaped = snapshot_path.replace('\'', "'\\''");
|
||||
let command = format!("source '{escaped}'");
|
||||
@@ -455,7 +475,7 @@ async fn run_shell_command(
|
||||
session: &mut ShellSession,
|
||||
options: &ShellExecuteOptions,
|
||||
on_chunk: Option<ThreadsafeFunction<String>>,
|
||||
) -> Result<brush_core::ExecutionResult> {
|
||||
) -> Result<ExecutionResult> {
|
||||
if let Some(cwd) = options.cwd.as_deref() {
|
||||
session
|
||||
.shell
|
||||
@@ -472,13 +492,10 @@ async fn run_shell_command(
|
||||
);
|
||||
let stderr_file = OpenFile::from(writer_file);
|
||||
|
||||
let mut open_files = session.shell.open_files.clone();
|
||||
open_files.set(OpenFiles::STDIN_FD, OpenFile::Null);
|
||||
open_files.set(OpenFiles::STDOUT_FD, stdout_file);
|
||||
open_files.set(OpenFiles::STDERR_FD, stderr_file);
|
||||
|
||||
let mut params = session.shell.default_exec_params();
|
||||
params.open_files = open_files;
|
||||
params.set_fd(OpenFiles::STDIN_FD, null_file()?);
|
||||
params.set_fd(OpenFiles::STDOUT_FD, stdout_file);
|
||||
params.set_fd(OpenFiles::STDERR_FD, stderr_file);
|
||||
params.process_group_policy = ProcessGroupPolicy::NewProcessGroup;
|
||||
|
||||
if let Some(env) = options.env.as_ref() {
|
||||
@@ -516,7 +533,7 @@ async fn run_shell_command(
|
||||
|
||||
drop(params);
|
||||
|
||||
let _ = reader_handle.wait().await;
|
||||
let _: Result<()> = reader_handle.wait().await;
|
||||
|
||||
result.map_err(|err| Error::from_reason(format!("Shell execution failed: {err}")))
|
||||
}
|
||||
@@ -589,11 +606,14 @@ fn next_execution_id() -> String {
|
||||
format!("exec-{}-{counter}", std::process::id())
|
||||
}
|
||||
|
||||
const fn should_reset_session(result: &brush_core::ExecutionResult) -> bool {
|
||||
result.exit_shell
|
||||
|| result.return_from_function_or_script
|
||||
|| result.break_loop.is_some()
|
||||
|| result.continue_loop.is_some()
|
||||
const fn should_reset_session(result: &ExecutionResult) -> bool {
|
||||
match result.next_control_flow {
|
||||
ExecutionControlFlow::Normal => false,
|
||||
ExecutionControlFlow::BreakLoop { .. } => true,
|
||||
ExecutionControlFlow::ContinueLoop { .. } => true,
|
||||
ExecutionControlFlow::ReturnFromFunctionOrScript => true,
|
||||
ExecutionControlFlow::ExitShell => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_session(session_key: &str) {
|
||||
@@ -762,23 +782,24 @@ struct SleepCommand {
|
||||
}
|
||||
|
||||
impl builtins::Command for SleepCommand {
|
||||
type Error = brush_core::Error;
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
context: ExecutionContext<'_>,
|
||||
) -> impl std::future::Future<Output = std::result::Result<builtins::ExitCode, brush_core::Error>>
|
||||
+ std::marker::Send {
|
||||
) -> impl Future<Output = std::result::Result<ExecutionResult, brush_core::Error>> + Send {
|
||||
let durations = self.durations.clone();
|
||||
async move {
|
||||
let mut total = Duration::from_millis(0);
|
||||
for duration in &durations {
|
||||
let Some(parsed) = parse_duration(duration) else {
|
||||
let _ = writeln!(context.stderr(), "sleep: invalid time interval '{duration}'");
|
||||
return Ok(builtins::ExitCode::Custom(1));
|
||||
return Ok(ExecutionResult::new(1));
|
||||
};
|
||||
total += parsed;
|
||||
}
|
||||
time::sleep(total).await;
|
||||
Ok(builtins::ExitCode::Success)
|
||||
Ok(ExecutionResult::success())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -793,21 +814,22 @@ struct TimeoutCommand {
|
||||
}
|
||||
|
||||
impl builtins::Command for TimeoutCommand {
|
||||
type Error = brush_core::Error;
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
context: ExecutionContext<'_>,
|
||||
) -> impl std::future::Future<Output = std::result::Result<builtins::ExitCode, brush_core::Error>>
|
||||
+ std::marker::Send {
|
||||
) -> impl Future<Output = std::result::Result<ExecutionResult, brush_core::Error>> + Send {
|
||||
let duration = self.duration.clone();
|
||||
let command = self.command.clone();
|
||||
async move {
|
||||
let Some(timeout) = parse_duration(&duration) else {
|
||||
let _ = writeln!(context.stderr(), "timeout: invalid time interval '{duration}'");
|
||||
return Ok(builtins::ExitCode::Custom(125));
|
||||
return Ok(ExecutionResult::new(125));
|
||||
};
|
||||
if command.is_empty() {
|
||||
let _ = writeln!(context.stderr(), "timeout: missing command");
|
||||
return Ok(builtins::ExitCode::Custom(125));
|
||||
return Ok(ExecutionResult::new(125));
|
||||
}
|
||||
|
||||
let mut params = context.params.clone();
|
||||
@@ -844,18 +866,18 @@ impl builtins::Command for TimeoutCommand {
|
||||
let _ = time::timeout(Duration::from_millis(1500), &mut run_future).await;
|
||||
tracker_done.store(true, Ordering::Release);
|
||||
let _ = tracker_handle.await;
|
||||
return Ok(builtins::ExitCode::Custom(124));
|
||||
return Ok(ExecutionResult::new(124));
|
||||
}
|
||||
|
||||
tracker_done.store(true, Ordering::Release);
|
||||
let _ = tracker_handle.await;
|
||||
|
||||
if timed_out {
|
||||
return Ok(builtins::ExitCode::Custom(124));
|
||||
return Ok(ExecutionResult::new(124));
|
||||
}
|
||||
|
||||
let result = result.expect("result ensured")?;
|
||||
Ok(builtins::ExitCode::from(result))
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,7 @@ fn strip_quotes(value: &str) -> String {
|
||||
|
||||
fn get_disk_info() -> Option<String> {
|
||||
let mut disks = Disks::new_with_refreshed_list();
|
||||
disks.refresh_list();
|
||||
disks.refresh();
|
||||
disks.refresh(true);
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
@@ -18,8 +18,11 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
const TAB_WIDTH: usize = 3;
|
||||
const ESC: u16 = 0x1b;
|
||||
|
||||
fn build_utf16_string(data: Vec<u16>) -> Utf16String {
|
||||
fn build_utf16_string(mut data: Vec<u16>) -> Utf16String {
|
||||
// SAFETY: we know Utf16String == struct(Vec<u16>)
|
||||
while data.last() == Some(&0) {
|
||||
data.pop();
|
||||
}
|
||||
unsafe { std::mem::transmute(data) }
|
||||
}
|
||||
|
||||
@@ -725,7 +728,8 @@ fn wrap_text_with_ansi_impl(text: &[u16], width: usize) -> Vec<Vec<u16>> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Wrap text to a visible width, preserving ANSI escape codes across line breaks.
|
||||
/// Wrap text to a visible width, preserving ANSI escape codes across line
|
||||
/// breaks.
|
||||
///
|
||||
/// Returns UTF-16 lines with active SGR codes carried across line boundaries.
|
||||
#[napi(js_name = "wrapTextWithAnsi")]
|
||||
@@ -741,7 +745,8 @@ pub fn wrap_text_with_ansi(text: JsString, width: u32) -> Result<Vec<Utf16String
|
||||
|
||||
/// Truncate text to a visible width, preserving ANSI codes.
|
||||
///
|
||||
/// `ellipsis_kind`: 0 = "…", 1 = "...", 2 = "" (omit); pads with spaces when requested.
|
||||
/// `ellipsis_kind`: 0 = "…", 1 = "...", 2 = "" (omit); pads with spaces when
|
||||
/// requested.
|
||||
#[napi(js_name = "truncateToWidth")]
|
||||
pub fn truncate_to_width(
|
||||
text: JsString<'_>,
|
||||
@@ -1000,7 +1005,8 @@ fn slice_with_width_impl(
|
||||
|
||||
/// Slice a range of visible columns from a line.
|
||||
///
|
||||
/// Counts terminal cells, skipping ANSI escapes, and optionally enforces strict width.
|
||||
/// Counts terminal cells, skipping ANSI escapes, and optionally enforces strict
|
||||
/// width.
|
||||
#[napi(js_name = "sliceWithWidth")]
|
||||
pub fn slice_with_width(
|
||||
line: JsString,
|
||||
@@ -1155,7 +1161,8 @@ fn extract_segments_impl(
|
||||
|
||||
/// Extract the before/after slices around an overlay region.
|
||||
///
|
||||
/// Preserves ANSI state so the `after` segment renders correctly after truncation.
|
||||
/// Preserves ANSI state so the `after` segment renders correctly after
|
||||
/// truncation.
|
||||
#[napi(js_name = "extractSegments")]
|
||||
pub fn extract_segments(
|
||||
line: JsString,
|
||||
|
||||
@@ -424,7 +424,8 @@ export async function executePython(code: string, options?: PythonExecutorOption
|
||||
await ensureKernelAvailable(cwd);
|
||||
|
||||
const kernelMode = options?.kernelMode ?? "session";
|
||||
const useSharedGateway = options?.useSharedGateway;
|
||||
const isTestEnv = process.env.BUN_ENV === "test" || process.env.NODE_ENV === "test";
|
||||
const useSharedGateway = isTestEnv ? false : options?.useSharedGateway;
|
||||
const sessionFile = options?.sessionFile;
|
||||
const artifactsDir = options?.artifactsDir;
|
||||
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { Markdown } from "@oh-my-pi/pi-tui/components/markdown";
|
||||
import type { Terminal as XtermTerminalType } from "@xterm/headless";
|
||||
import { Chalk } from "chalk";
|
||||
import { defaultMarkdownTheme } from "./test-themes";
|
||||
import { Markdown } from "../src/components/markdown.js";
|
||||
import { type Component, TUI } from "../src/tui.js";
|
||||
import { defaultMarkdownTheme } from "./test-themes.js";
|
||||
import { VirtualTerminal } from "./virtual-terminal.js";
|
||||
|
||||
// Force full color in CI so ANSI assertions are deterministic
|
||||
const chalk = new Chalk({ level: 3 });
|
||||
|
||||
function getCellItalic(terminal: VirtualTerminal, row: number, col: number): number {
|
||||
const xterm = (terminal as unknown as { xterm: XtermTerminalType }).xterm;
|
||||
const buffer = xterm.buffer.active;
|
||||
const line = buffer.getLine(buffer.viewportY + row);
|
||||
expect(line, `Missing buffer line at row ${row}`).toBeTruthy();
|
||||
const cell = line!.getCell(col);
|
||||
expect(cell, `Missing cell at row ${row} col ${col}`).toBeTruthy();
|
||||
return cell!.isItalic();
|
||||
}
|
||||
|
||||
describe("Markdown component", () => {
|
||||
describe("Nested lists", () => {
|
||||
it("should render simple nested list", () => {
|
||||
@@ -94,6 +107,43 @@ describe("Markdown component", () => {
|
||||
expect(plainLines.some(line => line.includes(" - Unordered nested"))).toBeTruthy();
|
||||
expect(plainLines.some(line => line.includes("2. Second ordered"))).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should maintain numbering when code blocks are not indented (LLM output)", () => {
|
||||
// When code blocks aren't indented, marked parses each item as a separate list.
|
||||
// We use token.start to preserve the original numbering.
|
||||
const markdown = new Markdown(
|
||||
`1. First item
|
||||
|
||||
\`\`\`typescript
|
||||
// code block
|
||||
\`\`\`
|
||||
|
||||
2. Second item
|
||||
|
||||
\`\`\`typescript
|
||||
// another code block
|
||||
\`\`\`
|
||||
|
||||
3. Third item`,
|
||||
0,
|
||||
0,
|
||||
defaultMarkdownTheme,
|
||||
);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trim());
|
||||
|
||||
// Find all lines that start with a number and period
|
||||
const numberedLines = plainLines.filter(line => /^\d+\./.test(line));
|
||||
|
||||
// Should have 3 numbered items
|
||||
expect(numberedLines.length, `Expected 3 numbered items, got: ${numberedLines.join(", ")}`).toBe(3);
|
||||
|
||||
// Check the actual numbers
|
||||
expect(numberedLines[0].startsWith("1."), `First item should be "1.", got: ${numberedLines[0]}`).toBeTruthy();
|
||||
expect(numberedLines[1].startsWith("2."), `Second item should be "2.", got: ${numberedLines[1]}`).toBeTruthy();
|
||||
expect(numberedLines[2].startsWith("3."), `Third item should be "3.", got: ${numberedLines[2]}`).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tables", () => {
|
||||
@@ -121,6 +171,52 @@ describe("Markdown component", () => {
|
||||
expect(plainLines.some(line => line.includes("-"))).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render row dividers between data rows", () => {
|
||||
const markdown = new Markdown(
|
||||
`| Name | Age |
|
||||
| --- | --- |
|
||||
| Alice | 30 |
|
||||
| Bob | 25 |`,
|
||||
0,
|
||||
0,
|
||||
defaultMarkdownTheme,
|
||||
);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const dividerLines = plainLines.filter(line => line.includes("+"));
|
||||
|
||||
expect(dividerLines.length >= 2, "Expected header + row divider").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should keep column width at least the longest word", () => {
|
||||
const longestWord = "superlongword";
|
||||
const markdown = new Markdown(
|
||||
`| Column One | Column Two |
|
||||
| --- | --- |
|
||||
| ${longestWord} short | otherword |
|
||||
| small | tiny |`,
|
||||
0,
|
||||
0,
|
||||
defaultMarkdownTheme,
|
||||
);
|
||||
|
||||
const lines = markdown.render(32);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const dataLine = plainLines.find(line => line.includes(longestWord));
|
||||
expect(dataLine, "Expected data row containing longest word").toBeTruthy();
|
||||
|
||||
const segments = dataLine!.split("|").slice(1, -1);
|
||||
const [firstSegment] = segments;
|
||||
expect(firstSegment, "Expected first column segment").toBeTruthy();
|
||||
const firstColumnWidth = firstSegment.length - 2;
|
||||
|
||||
expect(
|
||||
firstColumnWidth >= longestWord.length,
|
||||
`Expected first column width >= ${longestWord.length}, got ${firstColumnWidth}`,
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render table with alignment", () => {
|
||||
const markdown = new Markdown(
|
||||
`| Left | Center | Right |
|
||||
@@ -181,15 +277,15 @@ describe("Markdown component", () => {
|
||||
|
||||
// All lines should fit within width
|
||||
for (const line of plainLines) {
|
||||
expect(line.length <= 50, `Line exceeds width 50: "${line}" (length: ${line.length}).toBeTruthy()`);
|
||||
expect(line.length <= 50, `Line exceeds width 50: "${line}" (length: ${line.length})`).toBeTruthy();
|
||||
}
|
||||
|
||||
// Content should still be present (possibly wrapped across lines)
|
||||
const allText = plainLines.join(" ");
|
||||
expect(allText.includes("Command")).toBeTruthy();
|
||||
expect(allText.includes("Description")).toBeTruthy();
|
||||
expect(allText.includes("npm install")).toBeTruthy();
|
||||
expect(allText.includes("Install")).toBeTruthy();
|
||||
expect(allText.includes("Command"), "Should contain 'Command'").toBeTruthy();
|
||||
expect(allText.includes("Description"), "Should contain 'Description'").toBeTruthy();
|
||||
expect(allText.includes("npm install"), "Should contain 'npm install'").toBeTruthy();
|
||||
expect(allText.includes("Install"), "Should contain 'Install'").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should wrap long cell content to multiple lines", () => {
|
||||
@@ -208,13 +304,13 @@ describe("Markdown component", () => {
|
||||
|
||||
// Should have multiple data rows due to wrapping
|
||||
const dataRows = plainLines.filter(line => line.startsWith("|") && !line.includes("-"));
|
||||
expect(dataRows.length > 2).toBeTruthy();
|
||||
expect(dataRows.length > 2, `Expected wrapped rows, got ${dataRows.length} rows`).toBeTruthy();
|
||||
|
||||
// All content should be preserved (may be split across lines)
|
||||
const allText = plainLines.join(" ");
|
||||
expect(allText.includes("very long")).toBeTruthy();
|
||||
expect(allText.includes("cell content")).toBeTruthy();
|
||||
expect(allText.includes("should wrap")).toBeTruthy();
|
||||
expect(allText.includes("very long"), "Should preserve 'very long'").toBeTruthy();
|
||||
expect(allText.includes("cell content"), "Should preserve 'cell content'").toBeTruthy();
|
||||
expect(allText.includes("should wrap"), "Should preserve 'should wrap'").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should wrap long unbroken tokens inside table cells (not only at line start)", () => {
|
||||
@@ -235,22 +331,23 @@ describe("Markdown component", () => {
|
||||
for (const line of plainLines) {
|
||||
expect(
|
||||
line.length <= width,
|
||||
`Line exceeds width ${width}: "${line}" (length: ${line.length}).toBeTruthy()`,
|
||||
);
|
||||
`Line exceeds width ${width}: "${line}" (length: ${line.length})`,
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
// Borders should stay intact (exactly 2 vertical borders for a 1-col table)
|
||||
const tableLines = plainLines.filter(line => line.startsWith("|"));
|
||||
expect(tableLines.length > 0, "Expected table rows to render").toBeTruthy();
|
||||
for (const line of tableLines) {
|
||||
const borderCount = line.split("|").length - 1;
|
||||
expect(borderCount).toBe(2);
|
||||
expect(borderCount, `Expected 2 borders, got ${borderCount}: "${line}"`).toBe(2);
|
||||
}
|
||||
|
||||
// Strip box drawing characters + whitespace so we can assert the URL is preserved
|
||||
// even if it was split across multiple wrapped lines.
|
||||
const extracted = plainLines.join("").replace(/[|++-\s]/g, "");
|
||||
expect(extracted.includes("prefix")).toBeTruthy();
|
||||
expect(extracted.includes(url)).toBeTruthy();
|
||||
const extracted = plainLines.join("").replace(/[|+\-\s]/g, "");
|
||||
expect(extracted.includes("prefix"), "Should preserve 'prefix'").toBeTruthy();
|
||||
expect(extracted.includes(url), "Should preserve URL").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should wrap styled inline code inside table cells without breaking borders", () => {
|
||||
@@ -266,20 +363,20 @@ describe("Markdown component", () => {
|
||||
const width = 20;
|
||||
const lines = markdown.render(width);
|
||||
const joinedOutput = lines.join("\n");
|
||||
expect(joinedOutput.includes("\x1b[33m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[33m"), "Inline code should be styled (yellow)").toBeTruthy();
|
||||
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
for (const line of plainLines) {
|
||||
expect(
|
||||
line.length <= width,
|
||||
`Line exceeds width ${width}: "${line}" (length: ${line.length}).toBeTruthy()`,
|
||||
);
|
||||
`Line exceeds width ${width}: "${line}" (length: ${line.length})`,
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
const tableLines = plainLines.filter(line => line.startsWith("|"));
|
||||
for (const line of tableLines) {
|
||||
const borderCount = line.split("|").length - 1;
|
||||
expect(borderCount).toBe(2);
|
||||
expect(borderCount, `Expected 2 borders, got ${borderCount}: "${line}"`).toBe(2);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,11 +395,11 @@ describe("Markdown component", () => {
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
|
||||
// Should not crash and should produce output
|
||||
expect(lines.length > 0).toBeTruthy();
|
||||
expect(lines.length > 0, "Should produce output").toBeTruthy();
|
||||
|
||||
// Lines should not exceed width
|
||||
for (const line of plainLines) {
|
||||
expect(line.length <= 15, `Line exceeds width 15: "${line}" (length: ${line.length}).toBeTruthy()`);
|
||||
expect(line.length <= 15, `Line exceeds width 15: "${line}" (length: ${line.length})`).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -322,14 +419,14 @@ describe("Markdown component", () => {
|
||||
|
||||
// Should have proper table structure
|
||||
const headerLine = plainLines.find(line => line.includes("A") && line.includes("B"));
|
||||
expect(headerLine).toBeTruthy();
|
||||
expect(headerLine?.includes("|")).toBeTruthy();
|
||||
expect(headerLine, "Should have header row").toBeTruthy();
|
||||
expect(headerLine?.includes("|"), "Header should have borders").toBeTruthy();
|
||||
|
||||
const separatorLine = plainLines.find(line => line.includes("+") && line.includes("+"));
|
||||
expect(separatorLine).toBeTruthy();
|
||||
const separatorLine = plainLines.find(line => line.includes("+") && line.includes("-"));
|
||||
expect(separatorLine, "Should have separator row").toBeTruthy();
|
||||
|
||||
const dataLine = plainLines.find(line => line.includes("1") && line.includes("2"));
|
||||
expect(dataLine).toBeTruthy();
|
||||
expect(dataLine, "Should have data row").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should respect paddingX when calculating table width", () => {
|
||||
@@ -348,12 +445,12 @@ describe("Markdown component", () => {
|
||||
|
||||
// All lines should respect width
|
||||
for (const line of plainLines) {
|
||||
expect(line.length <= 40, `Line exceeds width 40: "${line}" (length: ${line.length}).toBeTruthy()`);
|
||||
expect(line.length <= 40, `Line exceeds width 40: "${line}" (length: ${line.length})`).toBeTruthy();
|
||||
}
|
||||
|
||||
// Table rows should have left padding
|
||||
const tableRow = plainLines.find(line => line.includes("|"));
|
||||
expect(tableRow?.startsWith(" ")).toBeTruthy();
|
||||
expect(tableRow?.startsWith(" "), "Table should have left padding").toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -409,12 +506,12 @@ describe("Markdown component", () => {
|
||||
expect(joinedOutput.includes("inline code")).toBeTruthy();
|
||||
|
||||
// The output should have ANSI codes for gray (90) and italic (3)
|
||||
expect(joinedOutput.includes("\x1b[90m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[3m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[90m"), "Should have gray color code").toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[3m"), "Should have italic code").toBeTruthy();
|
||||
|
||||
// Verify that inline code is styled (theme uses yellow)
|
||||
const hasCodeColor = joinedOutput.includes("\x1b[33m");
|
||||
expect(hasCodeColor).toBeTruthy();
|
||||
expect(hasCodeColor, "Should style inline code").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should preserve gray italic styling after bold text", () => {
|
||||
@@ -436,11 +533,46 @@ describe("Markdown component", () => {
|
||||
expect(joinedOutput.includes("bold text")).toBeTruthy();
|
||||
|
||||
// The output should have ANSI codes for gray (90) and italic (3)
|
||||
expect(joinedOutput.includes("\x1b[90m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[3m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[90m"), "Should have gray color code").toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[3m"), "Should have italic code").toBeTruthy();
|
||||
|
||||
// Should have bold codes (1 or 22 for bold on/off)
|
||||
expect(joinedOutput.includes("\x1b[1m")).toBeTruthy();
|
||||
expect(joinedOutput.includes("\x1b[1m"), "Should have bold code").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not leak styles into following lines when rendered in TUI", async () => {
|
||||
class MarkdownWithInput implements Component {
|
||||
public markdownLineCount = 0;
|
||||
|
||||
constructor(private readonly markdown: Markdown) {}
|
||||
|
||||
render(width: number): string[] {
|
||||
const lines = this.markdown.render(width);
|
||||
this.markdownLineCount = lines.length;
|
||||
return [...lines, "INPUT"];
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.markdown.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
const markdown = new Markdown("This is thinking with `inline code`", 1, 0, defaultMarkdownTheme, {
|
||||
color: text => chalk.gray(text),
|
||||
italic: true,
|
||||
});
|
||||
|
||||
const terminal = new VirtualTerminal(80, 6);
|
||||
const tui = new TUI(terminal);
|
||||
const component = new MarkdownWithInput(markdown);
|
||||
tui.addChild(component);
|
||||
tui.start();
|
||||
await terminal.flush();
|
||||
|
||||
expect(component.markdownLineCount > 0).toBeTruthy();
|
||||
const inputRow = component.markdownLineCount;
|
||||
expect(getCellItalic(terminal, inputRow, 0)).toBe(0);
|
||||
tui.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -463,12 +595,15 @@ again, hello world`,
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
|
||||
const closingBackticksIndex = plainLines.indexOf("```");
|
||||
expect(closingBackticksIndex !== -1).toBeTruthy();
|
||||
expect(closingBackticksIndex !== -1, "Should have closing backticks").toBeTruthy();
|
||||
|
||||
const afterBackticks = plainLines.slice(closingBackticksIndex + 1);
|
||||
const emptyLineCount = afterBackticks.findIndex(line => line !== "");
|
||||
|
||||
expect(emptyLineCount).toBe(1);
|
||||
expect(
|
||||
emptyLineCount,
|
||||
`Expected 1 empty line after code block, but found ${emptyLineCount}. Lines after backticks: ${JSON.stringify(afterBackticks.slice(0, 5))}`,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -488,13 +623,16 @@ again, hello world`,
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
|
||||
const dividerIndex = plainLines.findIndex(line => line.includes("-"));
|
||||
expect(dividerIndex !== -1).toBeTruthy();
|
||||
const dividerIndex = plainLines.findIndex(line => /^-+$/.test(line.trim()));
|
||||
expect(dividerIndex !== -1, "Should have divider").toBeTruthy();
|
||||
|
||||
const afterDivider = plainLines.slice(dividerIndex + 1);
|
||||
const emptyLineCount = afterDivider.findIndex(line => line !== "");
|
||||
|
||||
expect(emptyLineCount).toBe(1);
|
||||
expect(
|
||||
emptyLineCount,
|
||||
`Expected 1 empty line after divider, but found ${emptyLineCount}. Lines after divider: ${JSON.stringify(afterDivider.slice(0, 5))}`,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -513,12 +651,15 @@ This is a paragraph`,
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
|
||||
const headingIndex = plainLines.findIndex(line => line.includes("Hello"));
|
||||
expect(headingIndex !== -1).toBeTruthy();
|
||||
expect(headingIndex !== -1, "Should have heading").toBeTruthy();
|
||||
|
||||
const afterHeading = plainLines.slice(headingIndex + 1);
|
||||
const emptyLineCount = afterHeading.findIndex(line => line !== "");
|
||||
|
||||
expect(emptyLineCount).toBe(1);
|
||||
expect(
|
||||
emptyLineCount,
|
||||
`Expected 1 empty line after heading, but found ${emptyLineCount}. Lines after heading: ${JSON.stringify(afterHeading.slice(0, 5))}`,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -539,12 +680,68 @@ again, hello world`,
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, "").trimEnd());
|
||||
|
||||
const quoteIndex = plainLines.findIndex(line => line.includes("This is a quote"));
|
||||
expect(quoteIndex !== -1).toBeTruthy();
|
||||
expect(quoteIndex !== -1, "Should have blockquote").toBeTruthy();
|
||||
|
||||
const afterQuote = plainLines.slice(quoteIndex + 1);
|
||||
const emptyLineCount = afterQuote.findIndex(line => line !== "");
|
||||
|
||||
expect(emptyLineCount).toBe(1);
|
||||
expect(
|
||||
emptyLineCount,
|
||||
`Expected 1 empty line after blockquote, but found ${emptyLineCount}. Lines after quote: ${JSON.stringify(afterQuote.slice(0, 5))}`,
|
||||
).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Links", () => {
|
||||
it("should not duplicate URL for autolinked emails", () => {
|
||||
const markdown = new Markdown("Contact user@example.com for help", 0, 0, defaultMarkdownTheme);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// Should contain the email once, not duplicated with mailto:
|
||||
expect(joinedPlain.includes("user@example.com"), "Should contain email").toBeTruthy();
|
||||
expect(!joinedPlain.includes("mailto:"), "Should not show mailto: prefix for autolinked emails").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not duplicate URL for bare URLs", () => {
|
||||
const markdown = new Markdown("Visit https://example.com for more", 0, 0, defaultMarkdownTheme);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// URL should appear only once
|
||||
const urlCount = (joinedPlain.match(/https:\/\/example\.com/g) || []).length;
|
||||
expect(urlCount, "URL should appear exactly once").toBe(1);
|
||||
});
|
||||
|
||||
it("should show URL for explicit markdown links with different text", () => {
|
||||
const markdown = new Markdown("[click here](https://example.com)", 0, 0, defaultMarkdownTheme);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// Should show both link text and URL
|
||||
expect(joinedPlain.includes("click here"), "Should contain link text").toBeTruthy();
|
||||
expect(joinedPlain.includes("(https://example.com)"), "Should show URL in parentheses").toBeTruthy();
|
||||
});
|
||||
|
||||
it("should show URL for explicit mailto links with different text", () => {
|
||||
const markdown = new Markdown("[Email me](mailto:test@example.com)", 0, 0, defaultMarkdownTheme);
|
||||
|
||||
const lines = markdown.render(80);
|
||||
const plainLines = lines.map(line => line.replace(/\x1b\[[0-9;]*m/g, ""));
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// Should show both link text and mailto URL
|
||||
expect(joinedPlain.includes("Email me"), "Should contain link text").toBeTruthy();
|
||||
expect(
|
||||
joinedPlain.includes("(mailto:test@example.com)"),
|
||||
"Should show mailto URL in parentheses",
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -564,7 +761,10 @@ again, hello world`,
|
||||
const joinedPlain = plainLines.join(" ");
|
||||
|
||||
// The content inside the tags should be visible
|
||||
expect(joinedPlain.includes("hidden content") || joinedPlain.includes("<thinking>")).toBeTruthy();
|
||||
expect(
|
||||
joinedPlain.includes("hidden content") || joinedPlain.includes("<thinking>"),
|
||||
"Should render HTML-like tags or their content as text, not hide them",
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should render HTML tags in code blocks correctly", () => {
|
||||
@@ -575,7 +775,10 @@ again, hello world`,
|
||||
const joinedPlain = plainLines.join("\n");
|
||||
|
||||
// HTML in code blocks should be visible
|
||||
expect(joinedPlain.includes("<div>") && joinedPlain.includes("</div>")).toBeTruthy();
|
||||
expect(
|
||||
joinedPlain.includes("<div>") && joinedPlain.includes("</div>"),
|
||||
"Should render HTML in code blocks",
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui/utils";
|
||||
import { visibleWidth, wrapTextWithAnsi } from "../src/utils";
|
||||
|
||||
describe("wrapTextWithAnsi", () => {
|
||||
describe("underline styling", () => {
|
||||
@@ -11,29 +11,22 @@ describe("wrapTextWithAnsi", () => {
|
||||
|
||||
const wrapped = wrapTextWithAnsi(text, 40);
|
||||
|
||||
const prefix = "read this thread ";
|
||||
expect(wrapped[0].startsWith(prefix)).toBe(true);
|
||||
const underlineIndex = wrapped[0].indexOf(underlineOn);
|
||||
if (underlineIndex !== -1) {
|
||||
expect(underlineIndex).toBeGreaterThanOrEqual(prefix.length);
|
||||
expect(wrapped[0].endsWith(underlineOff)).toBe(true);
|
||||
}
|
||||
// First line should NOT contain underline code - it's just "read this thread"
|
||||
expect(wrapped[0]).toBe("read this thread");
|
||||
|
||||
// Second line should start with underline
|
||||
expect(wrapped[1].startsWith(underlineOn)).toBe(true);
|
||||
|
||||
const plain = wrapped.join("").replace(/\x1b\[[0-9;]*m/g, "");
|
||||
expect(plain.includes(url)).toBe(true);
|
||||
// Second line should start with underline, have URL content
|
||||
expect(wrapped[1].startsWith(underlineOn)).toStrictEqual(true);
|
||||
expect(wrapped[1].includes("https://")).toBe(true);
|
||||
});
|
||||
|
||||
it("should preserve whitespace before underline reset code", () => {
|
||||
it("should not have whitespace before underline reset code", () => {
|
||||
const underlineOn = "\x1b[4m";
|
||||
const underlineOff = "\x1b[24m";
|
||||
const textWithUnderlinedTrailingSpace = `${underlineOn}underlined text here ${underlineOff}more`;
|
||||
|
||||
const wrapped = wrapTextWithAnsi(textWithUnderlinedTrailingSpace, 18);
|
||||
|
||||
expect(wrapped[1].includes(` ${underlineOff}`)).toBe(true);
|
||||
expect(wrapped[0].includes(` ${underlineOff}`)).toBe(false);
|
||||
});
|
||||
|
||||
it("should not bleed underline to padding - each line should end with reset for underline only", () => {
|
||||
@@ -67,7 +60,7 @@ describe("wrapTextWithAnsi", () => {
|
||||
|
||||
// Each line should have background color
|
||||
for (const line of wrapped) {
|
||||
expect(line.includes(bgBlue)).toBeTruthy();
|
||||
expect(line.includes(bgBlue)).toBe(true);
|
||||
}
|
||||
|
||||
// Middle lines should NOT end with full reset (kills background for padding)
|
||||
@@ -76,7 +69,7 @@ describe("wrapTextWithAnsi", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("should reset underline without preserving background after wrap", () => {
|
||||
it("should reset underline but preserve background when wrapping underlined text inside background", () => {
|
||||
const underlineOn = "\x1b[4m";
|
||||
const underlineOff = "\x1b[24m";
|
||||
const reset = "\x1b[0m";
|
||||
@@ -85,11 +78,11 @@ describe("wrapTextWithAnsi", () => {
|
||||
|
||||
const wrapped = wrapTextWithAnsi(text, 20);
|
||||
|
||||
const lineHasBg = (line: string) => line.includes("[41m") || line.includes(";41m") || line.includes("[41;");
|
||||
|
||||
expect(lineHasBg(wrapped[0])).toBeTruthy();
|
||||
expect(lineHasBg(wrapped[1])).toBeFalsy();
|
||||
expect(lineHasBg(wrapped[2])).toBeFalsy();
|
||||
// All lines should have background color 41 (either as \x1b[41m or combined like \x1b[4;41m)
|
||||
for (const line of wrapped) {
|
||||
const hasBgColor = line.includes("[41m") || line.includes(";41m") || line.includes("[41;");
|
||||
expect(hasBgColor).toBe(true);
|
||||
}
|
||||
|
||||
// Lines with underlined content should use underline-off at end, not full reset
|
||||
for (let i = 0; i < wrapped.length - 1; i++) {
|
||||
@@ -111,15 +104,15 @@ describe("wrapTextWithAnsi", () => {
|
||||
const text = "hello world this is a test";
|
||||
const wrapped = wrapTextWithAnsi(text, 10);
|
||||
|
||||
expect(wrapped.length > 1).toBeTruthy();
|
||||
expect(wrapped.length > 1).toBe(true);
|
||||
for (const line of wrapped) {
|
||||
expect(visibleWidth(line) <= 10).toBeTruthy();
|
||||
expect(visibleWidth(line) <= 10).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("should truncate trailing whitespace that exceeds width", () => {
|
||||
const twoSpacesWrappedToWidth1 = wrapTextWithAnsi(" ", 1);
|
||||
expect(visibleWidth(twoSpacesWrappedToWidth1[0]) <= 1).toBeTruthy();
|
||||
expect(visibleWidth(twoSpacesWrappedToWidth1[0]) <= 1).toBe(true);
|
||||
});
|
||||
|
||||
it("should preserve color codes across wraps", () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rustfmt", "clippy", "rust-analyzer"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
|
||||
Reference in New Issue
Block a user