ratatui-0.30.0/.cargo_vcs_info.json0000644000000001450000000000100125650ustar { "git": { "sha1": "0a2a7c0363a4806b0cf05c1915bf7cdd438f756c" }, "path_in_vcs": "ratatui" }ratatui-0.30.0/Cargo.lock0000644000002105400000000000100105420ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "alloca" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" dependencies = [ "cc", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anyhow" version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "atomic" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ "serde_core", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "by_address" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "color-eyre" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" dependencies = [ "backtrace", "color-spantrace", "eyre", "indenter", "once_cell", "owo-colors", "tracing-error", ] [[package]] name = "color-spantrace" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ "once_cell", "owo-colors", "tracing-core", "tracing-error", ] [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "serde", "static_assertions", ] [[package]] name = "convert_case" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" dependencies = [ "unicode-segmentation", ] [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "criterion" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", "itertools 0.13.0", "num-traits", "oorandom", "page_size", "plotters", "rayon", "regex", "serde", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools 0.13.0", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.10.0", "crossterm_winapi", "mio", "parking_lot", "rustix 0.38.44", "serde", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.10.0", "crossterm_winapi", "derive_more", "document-features", "futures-core", "mio", "parking_lot", "rustix 1.0.8", "serde", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "csscolorparser" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ "lab", "phf", ] [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn 2.0.106", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn 2.0.106", ] [[package]] name = "deltae" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "deranged" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc" dependencies = [ "powerfmt", ] [[package]] name = "derive_more" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "convert_case", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "euclid" version = "0.22.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" dependencies = [ "num-traits", ] [[package]] name = "eyre" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", ] [[package]] name = "fakeit" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff50593e569e8da220e12d8c171c89794fb2dab023f6391edbe5d16f3cdb311" dependencies = [ "rand 0.9.2", "simplerand", "uuid 0.8.2", ] [[package]] name = "fancy-regex" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ "bit-set", "regex", ] [[package]] name = "fast-srgb8" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "filedescriptor" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ "libc", "thiserror 1.0.69", "winapi", ] [[package]] name = "finl_unicode" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94c970b525906eb37d3940083aa65b95e481fc1857d467d13374e1d925cfc163" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indenter" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.5", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "instability" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" dependencies = [ "darling", "indoc", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kasuari" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" dependencies = [ "hashbrown 0.16.1", "portable-atomic", "portable-atomic-util", "thiserror 2.0.17", ] [[package]] name = "lab" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "line-clipping" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ "bitflags 2.10.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.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lru" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "mac_address" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ "nix", "winapi", ] [[package]] name = "matchers" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ "regex-automata", ] [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmem" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nu-ansi-term" version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "numtoa" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "owo-colors" version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "page_size" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" dependencies = [ "libc", "winapi", ] [[package]] name = "palette" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" dependencies = [ "approx", "fast-srgb8", "palette_derive", "phf", ] [[package]] name = "palette_derive" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" dependencies = [ "by_address", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "pest" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.17", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "pest_meta" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ "pest", "sha2", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "plotters" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", "plotters-svg", "wasm-bindgen", "web-sys", ] [[package]] name = "plotters-backend" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "pretty_assertions" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "ratatui" version = "0.30.0" dependencies = [ "color-eyre", "criterion", "crossterm 0.29.0", "document-features", "fakeit", "futures", "indoc", "instability", "palette", "pretty_assertions", "rand 0.9.2", "rand_chacha", "ratatui-core", "ratatui-crossterm", "ratatui-macros", "ratatui-termion", "ratatui-termwiz", "ratatui-widgets", "rstest", "serde", "serde_json", "time", "tokio", "tracing", "tracing-appender", "tracing-subscriber", ] [[package]] name = "ratatui-core" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.10.0", "compact_str", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", "lru", "palette", "serde", "strum", "thiserror 2.0.17", "unicode-segmentation", "unicode-truncate", "unicode-width", ] [[package]] name = "ratatui-crossterm" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" dependencies = [ "cfg-if", "crossterm 0.28.1", "crossterm 0.29.0", "instability", "ratatui-core", ] [[package]] name = "ratatui-macros" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] name = "ratatui-termion" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cade85a8591fbc911e147951422f0d6fd40f4948b271b6216c7dc01838996f8" dependencies = [ "instability", "ratatui-core", "termion", ] [[package]] name = "ratatui-termwiz" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" dependencies = [ "ratatui-core", "termwiz", ] [[package]] name = "ratatui-widgets" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ "bitflags 2.10.0", "hashbrown 0.16.1", "indoc", "instability", "itertools 0.14.0", "line-clipping", "ratatui-core", "serde", "strum", "time", "unicode-segmentation", "unicode-width", ] [[package]] name = "rayon" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.10.0", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rstest" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", "rstest_macros", ] [[package]] name = "rstest_macros" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", "proc-macro-crate", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", "syn 2.0.106", "unicode-ident", ] [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.60.2", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "serde_json" version = "1.0.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] [[package]] name = "simplerand" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e111daec97c913e764c730b11de670f36adfc9ef87c791ee8999ecc3ded8f644" dependencies = [ "lazy_static", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "terminfo" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom", "phf", "phf_codegen", ] [[package]] name = "termion" version = "4.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f44138a9ae08f0f502f24104d82517ef4da7330c35acd638f1f29d3cd5475ecb" dependencies = [ "libc", "numtoa", "serde", ] [[package]] name = "termios" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" dependencies = [ "libc", ] [[package]] name = "termwiz" version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", "bitflags 2.10.0", "fancy-regex", "filedescriptor", "finl_unicode", "fixedbitset", "hex", "lazy_static", "libc", "log", "memmem", "nix", "num-derive", "num-traits", "ordered-float", "pest", "pest_derive", "phf", "serde", "sha2", "signal-hook", "siphasher", "terminfo", "termios", "thiserror 1.0.69", "ucd-trie", "unicode-segmentation", "vtparse", "wezterm-bidi", "wezterm-blob-leases", "wezterm-color-types", "wezterm-dynamic", "wezterm-input-types", "winapi", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl 2.0.17", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thiserror-impl" version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "thread_local" version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", ] [[package]] name = "time" version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "tokio" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-appender" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", "thiserror 2.0.17", "time", "tracing-subscriber", ] [[package]] name = "tracing-attributes" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] [[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-error" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex-automata", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fbf03860ff438702f3910ca5f28f8dac63c1c11e7efb5012b8b175493606330" dependencies = [ "itertools 0.13.0", "unicode-segmentation", "unicode-width", ] [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "uuid" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "atomic", "getrandom 0.3.3", "js-sys", "serde", "wasm-bindgen", ] [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vtparse" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" dependencies = [ "utf8parse", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wezterm-bidi" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" dependencies = [ "log", "wezterm-dynamic", ] [[package]] name = "wezterm-blob-leases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.3", "mac_address", "serde", "sha2", "thiserror 1.0.69", "uuid 1.18.0", ] [[package]] name = "wezterm-color-types" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" dependencies = [ "csscolorparser", "deltae", "lazy_static", "serde", "wezterm-dynamic", ] [[package]] name = "wezterm-dynamic" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" dependencies = [ "log", "ordered-float", "strsim", "thiserror 1.0.69", "wezterm-dynamic-derive", ] [[package]] name = "wezterm-dynamic-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "wezterm-input-types" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" dependencies = [ "bitflags 1.3.2", "euclid", "lazy_static", "serde", "wezterm-dynamic", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-link" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[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", ] [[package]] name = "windows-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.3", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.10.0", ] [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "zerocopy" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", "syn 2.0.106", ] ratatui-0.30.0/Cargo.toml0000644000000151330000000000100105660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.86.0" name = "ratatui" version = "0.30.0" authors = [ "Florian Dehau ", "The Ratatui Developers", ] build = false exclude = [ "assets/*", ".github", "Makefile.toml", "CONTRIBUTING.md", "*.log", "tags", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A library that's all about cooking up terminal user interfaces" homepage = "https://ratatui.rs" documentation = "https://docs.rs/ratatui/latest/ratatui/" readme = "README.md" keywords = [ "tui", "terminal", "dashboard", ] categories = ["command-line-interface"] license = "MIT" repository = "https://github.com/ratatui/ratatui" resolver = "2" [package.metadata.docs.rs] all-features = true cargo-args = [ "-Zunstable-options", "-Zrustdoc-scrape-examples", ] rustdoc-args = [ "--cfg", "docsrs", ] [features] all-widgets = ["widget-calendar"] crossterm = [ "std", "dep:ratatui-crossterm", ] crossterm_0_28 = [ "crossterm", "ratatui-crossterm/crossterm_0_28", ] crossterm_0_29 = [ "crossterm", "ratatui-crossterm/crossterm_0_29", ] default = [ "crossterm", "underline-color", "all-widgets", "macros", "layout-cache", ] layout-cache = [ "std", "ratatui-core/layout-cache", ] macros = ["dep:ratatui-macros"] palette = [ "std", "ratatui-core/palette", "dep:palette", ] portable-atomic = ["ratatui-core/portable-atomic"] scrolling-regions = [ "ratatui-core/scrolling-regions", "ratatui-crossterm?/scrolling-regions", "ratatui-termion?/scrolling-regions", "ratatui-termwiz?/scrolling-regions", ] serde = [ "std", "dep:serde", "ratatui-core/serde", "ratatui-widgets/serde", "ratatui-crossterm?/serde", "ratatui-termion?/serde", "ratatui-termwiz?/serde", ] std = [ "ratatui-core/std", "ratatui-widgets/std", ] termion = [ "std", "dep:ratatui-termion", ] termwiz = [ "std", "dep:ratatui-termwiz", ] underline-color = [ "ratatui-core/underline-color", "ratatui-crossterm?/underline-color", "ratatui-termwiz?/underline-color", ] unstable = [ "unstable-rendered-line-info", "unstable-widget-ref", "unstable-backend-writer", ] unstable-backend-writer = [ "ratatui-crossterm?/unstable-backend-writer", "ratatui-termion?/unstable-backend-writer", ] unstable-rendered-line-info = ["ratatui-widgets/unstable-rendered-line-info"] unstable-widget-ref = [] widget-calendar = ["ratatui-widgets/calendar"] [lib] name = "ratatui" path = "src/lib.rs" bench = false [[test]] name = "backend_termion" path = "tests/backend_termion.rs" [[test]] name = "state_serde" path = "tests/state_serde.rs" required-features = ["serde"] [[test]] name = "stateful_widget_ref_dyn" path = "tests/stateful_widget_ref_dyn.rs" [[test]] name = "stylize" path = "tests/stylize.rs" [[test]] name = "terminal" path = "tests/terminal.rs" [[test]] name = "widgets_barchart" path = "tests/widgets_barchart.rs" [[test]] name = "widgets_block" path = "tests/widgets_block.rs" [[test]] name = "widgets_calendar" path = "tests/widgets_calendar.rs" [[test]] name = "widgets_canvas" path = "tests/widgets_canvas.rs" [[test]] name = "widgets_chart" path = "tests/widgets_chart.rs" [[test]] name = "widgets_gauge" path = "tests/widgets_gauge.rs" [[test]] name = "widgets_list" path = "tests/widgets_list.rs" [[test]] name = "widgets_paragraph" path = "tests/widgets_paragraph.rs" [[test]] name = "widgets_table" path = "tests/widgets_table.rs" [[test]] name = "widgets_tabs" path = "tests/widgets_tabs.rs" [[bench]] name = "main" path = "benches/main.rs" harness = false [dependencies.document-features] version = "0.2" optional = true [dependencies.instability] version = "0.3" [dependencies.palette] version = "0.7" optional = true [dependencies.ratatui-core] version = "0.1.0" [dependencies.ratatui-crossterm] version = "0.1.0" optional = true [dependencies.ratatui-macros] version = "0.7.0" optional = true [dependencies.ratatui-termwiz] version = "0.1.0" optional = true [dependencies.ratatui-widgets] version = "0.3.0" [dependencies.serde] version = "1" features = ["derive"] optional = true [dev-dependencies.color-eyre] version = "0.6" [dev-dependencies.criterion] version = "0.8" features = ["html_reports"] [dev-dependencies.crossterm] version = "0.29" features = ["event-stream"] [dev-dependencies.fakeit] version = "1" [dev-dependencies.futures] version = "0.3" [dev-dependencies.indoc] version = "2" [dev-dependencies.pretty_assertions] version = "1" [dev-dependencies.rand] version = "0.9" [dev-dependencies.rand_chacha] version = "0.9" [dev-dependencies.rstest] version = "0.26" [dev-dependencies.serde_json] version = "1" [dev-dependencies.time] version = "0.3" features = ["local-offset"] default-features = false [dev-dependencies.tokio] version = "1" features = [ "rt", "macros", "time", "rt-multi-thread", ] [dev-dependencies.tracing] version = "0.1" [dev-dependencies.tracing-appender] version = "0.2" [dev-dependencies.tracing-subscriber] version = "0.3" features = ["env-filter"] [target."cfg(not(windows))".dependencies.ratatui-termion] version = "0.1.0" optional = true [lints.clippy] as_underscore = "warn" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" deref_by_slicing = "warn" else_if_without_else = "warn" empty_line_after_doc_comments = "warn" equatable_if_let = "warn" fn_to_numeric_cast_any = "warn" format_push_string = "warn" implicit_clone = "warn" map_err_ignore = "warn" missing_const_for_fn = "warn" missing_errors_doc = "allow" missing_panics_doc = "allow" mixed_read_write_in_expression = "warn" mod_module_files = "warn" module_inception = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" needless_pass_by_ref_mut = "warn" needless_raw_strings = "warn" or_fun_call = "warn" redundant_type_annotations = "warn" rest_pat_in_fully_bound_structs = "warn" string_lit_chars_any = "warn" string_slice = "warn" unnecessary_self_imports = "warn" use_self = "warn" [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] unsafe_code = "forbid" ratatui-0.30.0/Cargo.toml.orig000064400000000000000000000142321046102023000142460ustar 00000000000000[package] name = "ratatui" description = "A library that's all about cooking up terminal user interfaces" version = "0.30.0" authors.workspace = true documentation.workspace = true repository.workspace = true homepage.workspace = true keywords.workspace = true categories.workspace = true readme.workspace = true license.workspace = true exclude.workspace = true edition.workspace = true rust-version.workspace = true [package.metadata.docs.rs] all-features = true # see https://doc.rust-lang.org/nightly/rustdoc/scraped-examples.html cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] rustdoc-args = ["--cfg", "docsrs"] [features] #! The crate provides a set of optional features that can be enabled in your `Cargo.toml` file. #! ## By default, we enable the crossterm backend as this is a reasonable choice for most applications ## as it is supported on Linux/Mac/Windows systems. We also enable the `underline-color` feature ## which allows you to set the underline color of text, the `macros` feature which provides ## some useful macros and `layout-cache` which speeds up layout cache calculations ## in `std`-enabled environments. default = ["crossterm", "underline-color", "all-widgets", "macros", "layout-cache"] #! Generally an application will only use one backend, so you should only enable one of the following features: ## enables the [`CrosstermBackend`](backend::CrosstermBackend) backend and adds a dependency on [`crossterm`]. crossterm = ["std", "dep:ratatui-crossterm"] ## selects the crossterm 0.28.x backend implementation crossterm_0_28 = ["crossterm", "ratatui-crossterm/crossterm_0_28"] ## selects the crossterm 0.29.x backend implementation (default) crossterm_0_29 = ["crossterm", "ratatui-crossterm/crossterm_0_29"] ## enables the [`TermionBackend`](backend::TermionBackend) backend and adds a dependency on [`termion`]. termion = ["std", "dep:ratatui-termion"] ## enables the [`TermwizBackend`](backend::TermwizBackend) backend and adds a dependency on [`termwiz`]. termwiz = ["std", "dep:ratatui-termwiz"] ## enables std std = ["ratatui-core/std", "ratatui-widgets/std"] #! The following optional features are available for all backends: ## enables serialization and deserialization of style and color types using the [`serde`] crate. ## This is useful if you want to save themes to a file. serde = [ "std", "dep:serde", "ratatui-core/serde", "ratatui-widgets/serde", "ratatui-crossterm?/serde", "ratatui-termion?/serde", "ratatui-termwiz?/serde", ] ## enables layout cache layout-cache = ["std", "ratatui-core/layout-cache"] ## enables conversions from colors in the [`palette`] crate to [`Color`](crate::style::Color). palette = ["std", "ratatui-core/palette", "dep:palette"] ## enables portable-atomic integration for targets that don't support atomic types. portable-atomic = ["ratatui-core/portable-atomic"] ## Use terminal scrolling regions to make some operations less prone to ## flickering. (i.e. Terminal::insert_before). scrolling-regions = [ "ratatui-core/scrolling-regions", "ratatui-crossterm?/scrolling-regions", "ratatui-termion?/scrolling-regions", "ratatui-termwiz?/scrolling-regions", ] ## enables the [`macros`](macros) module which provides some useful macros for creating spans, ## lines, text, and layouts macros = ["dep:ratatui-macros"] ## enables all widgets. all-widgets = ["widget-calendar"] #! Widgets that add dependencies are gated behind feature flags to prevent unused transitive #! dependencies. The available features are: ## enables the [`calendar`](widgets::calendar) widget module. widget-calendar = ["ratatui-widgets/calendar"] #! The following optional features are only available for some backends: ## Enables the backend code that sets the underline color. ## Underline color is only supported by the Crossterm and Termwiz backends, and is not supported on ## Windows 7. underline-color = [ "ratatui-core/underline-color", "ratatui-crossterm?/underline-color", "ratatui-termwiz?/underline-color", ] #! The following features are unstable and may change in the future: ## Enable all unstable features. unstable = ["unstable-rendered-line-info", "unstable-widget-ref", "unstable-backend-writer"] ## Enables the [`Paragraph::line_count`](widgets::Paragraph::line_count) ## [`Paragraph::line_width`](widgets::Paragraph::line_width) methods ## which are experimental and may change in the future. ## See [Issue 293](https://github.com/ratatui/ratatui/issues/293) for more details. unstable-rendered-line-info = ["ratatui-widgets/unstable-rendered-line-info"] ## enables the [`WidgetRef`] and [`StatefulWidgetRef`] traits which are experimental and may change ## in the future. ## ## [`WidgetRef`]: widgets::WidgetRef ## [`StatefulWidgetRef`]: widgets::StatefulWidgetRef unstable-widget-ref = [] ## Enables getting access to backends' writers. unstable-backend-writer = [ "ratatui-crossterm?/unstable-backend-writer", "ratatui-termion?/unstable-backend-writer", ] [dependencies] document-features = { workspace = true, optional = true } instability.workspace = true palette = { workspace = true, optional = true } ratatui-core = { workspace = true } ratatui-crossterm = { workspace = true, optional = true } ratatui-macros = { workspace = true, optional = true } ratatui-termwiz = { workspace = true, optional = true } ratatui-widgets = { workspace = true } serde = { workspace = true, optional = true } [target.'cfg(not(windows))'.dependencies] ratatui-termion = { workspace = true, optional = true } [dev-dependencies] color-eyre.workspace = true criterion.workspace = true crossterm = { workspace = true, features = ["event-stream"] } fakeit.workspace = true futures.workspace = true indoc.workspace = true pretty_assertions.workspace = true rand.workspace = true rand_chacha.workspace = true rstest.workspace = true serde_json.workspace = true time = { workspace = true, features = ["local-offset"] } tokio = { workspace = true, features = ["rt", "macros", "time", "rt-multi-thread"] } tracing.workspace = true tracing-appender.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true [lib] bench = false [[bench]] name = "main" harness = false [[test]] name = "state_serde" required-features = ["serde"] ratatui-0.30.0/README.md000064400000000000000000000176551046102023000126520ustar 00000000000000
Table of Contents - [Quickstart](#quickstart) - [Documentation](#documentation) - [Templates](#templates) - [Built with Ratatui](#built-with-ratatui) - [Alternatives](#alternatives) - [Contributing](#contributing) - [Acknowledgements](#acknowledgements) - [License](#license)
![Release header](https://github.com/ratatui/ratatui/blob/b23480adfa9430697071c906c7ba4d4f9bd37a73/assets/release-header.png?raw=true)
[![Crate Badge]][Crate] [![Repo Badge]][Repo] [![Docs Badge]][Docs] [![License Badge]][License] \ [![CI Badge]][CI] [![Deps Badge]][Deps] [![Codecov Badge]][Codecov] [![Sponsors Badge]][Sponsors] \ [Ratatui Website] · [Docs] · [Widget Examples] · [App Examples] · [Changelog] \ [Breaking Changes] · [Contributing] · [Report a bug] · [Request a Feature]
[Ratatui][Ratatui Website] (_ˌræ.təˈtu.i_) is a Rust crate for cooking up terminal user interfaces (TUIs). It provides a simple and flexible way to create text-based user interfaces in the terminal, which can be used for command-line applications, dashboards, and other interactive console programs. ## Quickstart Ratatui has [templates] available to help you get started quickly. You can use the [`cargo-generate`] command to create a new project with Ratatui: ```shell cargo install --locked cargo-generate cargo generate ratatui/templates ``` Selecting the Hello World template produces the following application: ```rust use color_eyre::Result; use crossterm::event::{self, Event}; use ratatui::{DefaultTerminal, Frame}; fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); let result = run(terminal); ratatui::restore(); result } fn run(mut terminal: DefaultTerminal) -> Result<()> { loop { terminal.draw(render)?; if matches!(event::read()?, Event::Key(_)) { break Ok(()); } } } fn render(frame: &mut Frame) { frame.render_widget("hello world", frame.area()); } ``` ## Documentation - [Docs] - the full API documentation for the library on docs.rs. - [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials. - [Ratatui Forum] - a place to ask questions and discuss the library. - [Widget Examples] - a collection of examples that demonstrate how to use the library. - [App Examples] - a collection of more complex examples that demonstrate how to build apps. - [ARCHITECTURE.md] - explains the crate organization and modular workspace structure. - [Changelog] - generated by [git-cliff] utilizing [Conventional Commits]. - [Breaking Changes] - a list of breaking changes in the library. You can also watch the [EuroRust 2024 talk] to learn about common concepts in Ratatui and what's possible to build with it. ## Templates If you're looking to get started quickly, you can use one of the available templates from the [templates] repository using [`cargo-generate`]: ```shell cargo generate ratatui/templates ``` ## Built with Ratatui [![Awesome](https://awesome.re/badge-flat2.svg)][awesome-ratatui] Check out the [showcase] section of the website, or the [awesome-ratatui] repository for a curated list of awesome apps and libraries built with Ratatui! ## Alternatives - [Cursive](https://crates.io/crates/cursive) - a ncurses-based TUI library. - [iocraft](https://crates.io/crates/iocraft) - a declarative TUI library. ## Contributing [![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix] [![Forum Badge]][Ratatui Forum] Feel free to join our [Discord server](https://discord.gg/pMCEU9hNEj) for discussions and questions! There is also a [Matrix](https://matrix.org/) bridge available at [#ratatui:matrix.org](https://matrix.to/#/#ratatui:matrix.org). We have also recently launched the [Ratatui Forum]. We rely on GitHub for [bugs][Report a bug] and [feature requests][Request a Feature]. Please make sure you read the [contributing](./CONTRIBUTING.md) guidelines before [creating a pull request][Create a Pull Request]. We accept AI generated code, but please read the [AI Contributions] guidelines to ensure compliance. If you'd like to show your support, you can add the Ratatui badge to your project's README: ```md [![Built With Ratatui](https://img.shields.io/badge/Built_With_Ratatui-000?logo=ratatui&logoColor=fff)](https://ratatui.rs/) ``` [![Built With Ratatui](https://img.shields.io/badge/Built_With_Ratatui-000?logo=ratatui&logoColor=fff)](https://ratatui.rs/) ## Acknowledgements Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its development. None of this could be possible without [Florian Dehau] who originally created [tui-rs] which inspired many Rust TUIs. Special thanks to [Pavel Fomchenkov] for his work in designing an awesome logo for the Ratatui project and organization. ## License This project is licensed under the [MIT License][License]. [Repo]: https://github.com/ratatui/ratatui [Ratatui Website]: https://ratatui.rs/ [Ratatui Forum]: https://forum.ratatui.rs [Docs]: https://docs.rs/ratatui [Widget Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui-widgets/examples [App Examples]: https://github.com/ratatui/ratatui/tree/main/examples [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md [Changelog]: https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md [git-cliff]: https://git-cliff.org [Conventional Commits]: https://www.conventionalcommits.org [Breaking Changes]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md [EuroRust 2024 talk]: https://www.youtube.com/watch?v=hWG51Mc1DlM [Report a bug]: https://github.com/ratatui/ratatui/issues/new?labels=bug&projects=&template=bug_report.md [Request a Feature]: https://github.com/ratatui/ratatui/issues/new?labels=enhancement&projects=&template=feature_request.md [Create a Pull Request]: https://github.com/ratatui/ratatui/compare [Contributing]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md [AI Contributions]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md#ai-generated-content [Crate]: https://crates.io/crates/ratatui [tui-rs]: https://crates.io/crates/tui [Sponsors]: https://github.com/sponsors/ratatui [Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&color=E05D44 [Repo Badge]: https://img.shields.io/badge/repo-ratatui/ratatui-1370D3?style=flat-square&logo=github [License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3 [CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui/ratatui/ci.yml?style=flat-square&logo=github [CI]: https://github.com/ratatui/ratatui/actions/workflows/ci.yml [Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3 [Codecov]: https://app.codecov.io/gh/ratatui/ratatui [Deps Badge]: https://deps.rs/repo/github/ratatui/ratatui/status.svg?path=ratatui&style=flat-square [Deps]: https://deps.rs/repo/github/ratatui/ratatui?path=ratatui [Discord Badge]: https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3 [Discord Server]: https://discord.gg/pMCEU9hNEj [Docs Badge]: https://img.shields.io/badge/docs-ratatui-1370D3?style=flat-square&logo=rust [Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3 [Matrix]: https://matrix.to/#/#ratatui:matrix.org [Forum Badge]: https://img.shields.io/discourse/likes?server=https%3A%2F%2Fforum.ratatui.rs&style=flat-square&logo=discourse&label=forum&color=C43AC3 [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui?logo=github&style=flat-square&color=1370D3 [templates]: https://github.com/ratatui/templates/ [showcase]: https://ratatui.rs/showcase/ [awesome-ratatui]: https://github.com/ratatui/awesome-ratatui [Pavel Fomchenkov]: https://github.com/nawok [Florian Dehau]: https://github.com/fdehau [`cargo-generate`]: https://crates.io/crates/cargo-generate [License]: ./LICENSE ratatui-0.30.0/benches/main/barchart.rs000064400000000000000000000042561046102023000160530ustar 00000000000000use criterion::{Bencher, BenchmarkId, Criterion, criterion_group}; use rand::Rng; use ratatui::buffer::Buffer; use ratatui::layout::{Direction, Rect}; use ratatui::widgets::{Bar, BarChart, BarGroup, Widget}; /// Benchmark for rendering a barchart. fn barchart(c: &mut Criterion) { let mut group = c.benchmark_group("barchart"); let mut rng = rand::rng(); for data_count in [64, 256, 2048] { let data: Vec = (0..data_count) .map(|i| { Bar::default() .label(format!("B{i}")) .value(rng.random_range(0..data_count)) }) .collect(); let bargroup = BarGroup::default().bars(&data); // Render a basic barchart group.bench_with_input( BenchmarkId::new("render", data_count), &BarChart::default().data(bargroup.clone()), render, ); // Render an horizontal barchart group.bench_with_input( BenchmarkId::new("render_horizontal", data_count), &BarChart::default() .direction(Direction::Horizontal) .data(bargroup.clone()), render, ); // Render a barchart with multiple groups group.bench_with_input( BenchmarkId::new("render_grouped", data_count), &BarChart::default() // We call `data` multiple time to add multiple groups. // This is not a duplicated call. .data(bargroup.clone()) .data(bargroup.clone()) .data(bargroup.clone()), render, ); } group.finish(); } /// Render the widget in a classical size buffer. fn render(bencher: &mut Bencher, barchart: &BarChart) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || barchart.clone(), |bench_barchart| { bench_barchart.render(buffer.area, &mut buffer); }, criterion::BatchSize::LargeInput, ); } criterion_group!(benches, barchart); ratatui-0.30.0/benches/main/block.rs000064400000000000000000000032661046102023000153570ustar 00000000000000use criterion::{BatchSize, Bencher, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::{Alignment, Rect}; use ratatui::text::Line; use ratatui::widgets::{Block, Padding, Widget}; /// Benchmark for rendering a block. fn block(c: &mut Criterion) { let mut group = c.benchmark_group("block"); for (width, height) in [ (100, 50), // vertically split screen (200, 50), // 1080p fullscreen with medium font (256, 256), // Max sized area ] { let buffer_size = Rect::new(0, 0, width, height); // Render an empty block group.bench_with_input( format!("render_empty/{width}x{height}"), &Block::new(), |b, block| render(b, block, buffer_size), ); // Render with all features group.bench_with_input( format!("render_all_feature/{width}x{height}"), &Block::bordered() .padding(Padding::new(5, 5, 2, 2)) .title("test title") .title_bottom(Line::from("bottom left title").alignment(Alignment::Right)), |b, block| render(b, block, buffer_size), ); } group.finish(); } /// Render the block into a buffer of the given `size`. fn render(bencher: &mut Bencher, block: &Block, size: Rect) { let mut buffer = Buffer::empty(size); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || block.to_owned(), |bench_block| { bench_block.render(buffer.area, &mut buffer); }, BatchSize::SmallInput, ); } criterion_group!(benches, block); ratatui-0.30.0/benches/main/buffer.rs000064400000000000000000000035201046102023000155270ustar 00000000000000use std::hint::black_box; use criterion::{BenchmarkId, Criterion}; use ratatui::buffer::{Buffer, Cell}; use ratatui::layout::Rect; use ratatui::text::Line; criterion::criterion_group!(benches, empty, filled, with_lines); const fn rect(size: u16) -> Rect { Rect::new(0, 0, size, size) } fn empty(c: &mut Criterion) { let mut group = c.benchmark_group("buffer/empty"); for size in [16, 64, 255] { let area = rect(size); group.bench_with_input(BenchmarkId::from_parameter(size), &area, |b, &area| { b.iter(|| { let _buffer = Buffer::empty(black_box(area)); }); }); } group.finish(); } /// This likely should have the same performance as `empty`, but it's here for completeness /// and to catch any potential performance regressions. fn filled(c: &mut Criterion) { let mut group = c.benchmark_group("buffer/filled"); for size in [16, 64, 255] { let area = rect(size); let cell = Cell::new("AAAA"); // simulate a multi-byte character group.bench_with_input( BenchmarkId::from_parameter(size), &(area, cell), |b, (area, cell)| { b.iter(|| { let _buffer = Buffer::filled(black_box(*area), cell.clone()); }); }, ); } group.finish(); } fn with_lines(c: &mut Criterion) { let mut group = c.benchmark_group("buffer/with_lines"); for size in [16, 64, 255] { let word_count = 50; let lines = fakeit::words::sentence(word_count); let lines = lines.lines().map(Line::from); group.bench_with_input(BenchmarkId::from_parameter(size), &lines, |b, lines| { b.iter(|| { let _buffer = Buffer::with_lines(black_box(lines.clone())); }); }); } group.finish(); } ratatui-0.30.0/benches/main/constraints.rs000064400000000000000000000037341046102023000166340ustar 00000000000000use std::hint::black_box; use std::rc::Rc; use criterion::{Criterion, criterion_group}; use ratatui::layout::Constraint::{Fill, Length, Max, Min, Percentage, Ratio}; use ratatui::layout::{Layout, Rect}; const SPLIT_BY: u16 = 10; fn layout_split(criterion: &mut Criterion) { for size in [16, 64, 256] { let mut group = criterion.benchmark_group(format!("constraints {size}x{size}")); let area = Rect::new(0, 0, size, size); group.bench_function("Fill", |bencher| { bencher.iter(|| layout_fill(black_box(area))); }); group.bench_function("Length", |bencher| { bencher.iter(|| layout_length(black_box(area))); }); group.bench_function("Max", |bencher| { bencher.iter(|| layout_max(black_box(area))); }); group.bench_function("Min", |bencher| { bencher.iter(|| layout_min(black_box(area))); }); group.bench_function("Percentage", |bencher| { bencher.iter(|| layout_percentage(black_box(area))); }); group.bench_function("Ratio", |bencher| { bencher.iter(|| layout_ratio(black_box(area))); }); group.finish(); } } fn layout_fill(area: Rect) -> Rc<[Rect]> { Layout::vertical([Fill(1); SPLIT_BY as usize]).split(area) } fn layout_length(area: Rect) -> Rc<[Rect]> { Layout::vertical([Length(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area) } fn layout_max(area: Rect) -> Rc<[Rect]> { Layout::vertical([Max(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area) } fn layout_min(area: Rect) -> Rc<[Rect]> { Layout::vertical([Min(area.width / SPLIT_BY); SPLIT_BY as usize]).split(area) } fn layout_percentage(area: Rect) -> Rc<[Rect]> { Layout::vertical([Percentage(100 / SPLIT_BY); SPLIT_BY as usize]).split(area) } fn layout_ratio(area: Rect) -> Rc<[Rect]> { Layout::vertical([Ratio(1, SPLIT_BY.into()); SPLIT_BY as usize]).split(area) } criterion_group!(benches, layout_split); ratatui-0.30.0/benches/main/gauge.rs000064400000000000000000000035721046102023000153550ustar 00000000000000use criterion::{BatchSize, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Style; use ratatui::widgets::{Block, Gauge, Widget}; /// Benchmark for rendering a gauge. fn gauge(c: &mut Criterion) { let mut group = c.benchmark_group("gauge"); let (width, height) = (200, 50); // 1080p fullscreen with medium font let buffer_size = Rect::new(0, 0, width, height); // Render an empty gauge group.bench_with_input( format!("render_empty/{width}x{height}"), &Gauge::default(), |b, gauge| { let mut buffer = Buffer::empty(buffer_size); // We use `iter_batched` to clone the value in the setup function because // `Widget::render` consumes the widget. b.iter_batched( || gauge.to_owned(), |bench_gauge| { bench_gauge.render(buffer.area, &mut buffer); }, BatchSize::SmallInput, ); }, ); // Render with all features group.bench_with_input( format!("render_all_feature/{width}x{height}"), &Gauge::default() .block(Block::bordered().title("Progress")) .gauge_style(Style::new().white().on_black().italic()) .percent(20) .label("20%") .use_unicode(true), |b, gauge| { let mut buffer = Buffer::empty(buffer_size); // We use `iter_batched` to clone the value in the setup function because // `Widget::render` consumes the widget. b.iter_batched( || gauge.to_owned(), |bench_gauge| { bench_gauge.render(buffer.area, &mut buffer); }, BatchSize::SmallInput, ); }, ); group.finish(); } criterion_group!(benches, gauge); ratatui-0.30.0/benches/main/line.rs000064400000000000000000000020611046102023000152040ustar 00000000000000use std::hint::black_box; use criterion::{Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::{Alignment, Rect}; use ratatui::style::Stylize; use ratatui::text::Line; use ratatui::widgets::Widget; fn line_render(criterion: &mut Criterion) { for alignment in [Alignment::Left, Alignment::Center, Alignment::Right] { let mut group = criterion.benchmark_group(format!("line_render/{alignment}")); group.sample_size(1000); let line = &Line::from(vec![ "This".red(), " ".green(), "is".italic(), " ".blue(), "SPARTA!!".bold(), ]) .alignment(alignment); for width in [0, 3, 4, 6, 7, 10, 42] { let area = Rect::new(0, 0, width, 1); group.bench_function(width.to_string(), |bencher| { let mut buffer = Buffer::empty(area); bencher.iter(|| black_box(line).render(area, &mut buffer)); }); } group.finish(); } } criterion_group!(benches, line_render); ratatui-0.30.0/benches/main/list.rs000064400000000000000000000045241046102023000152360ustar 00000000000000use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::{List, ListItem, ListState, StatefulWidget, Widget}; /// Benchmark for rendering a list. /// It only benchmarks the render with a different amount of items. fn list(c: &mut Criterion) { let mut group = c.benchmark_group("list"); for line_count in [64, 2048, 16384] { let lines: Vec = (0..line_count) .map(|_| ListItem::new(fakeit::words::sentence(10))) .collect(); // Render default list group.bench_with_input( BenchmarkId::new("render", line_count), &List::new(lines.clone()), render, ); // Render with an offset to the middle of the list and a selected item group.bench_with_input( BenchmarkId::new("render_scroll_half", line_count), &List::new(lines.clone()).highlight_symbol(">>"), |b, list| { render_stateful( b, list, ListState::default() .with_offset(line_count / 2) .with_selected(Some(line_count / 2)), ); }, ); } group.finish(); } /// Render the list into a common size buffer. fn render(bencher: &mut Bencher, list: &List) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || list.to_owned(), |bench_list| { Widget::render(bench_list, buffer.area, &mut buffer); }, BatchSize::LargeInput, ); } /// render the list into a common size buffer with a state fn render_stateful(bencher: &mut Bencher, list: &List, mut state: ListState) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || list.to_owned(), |bench_list| { StatefulWidget::render(bench_list, buffer.area, &mut buffer, &mut state); }, BatchSize::LargeInput, ); } criterion_group!(benches, list); ratatui-0.30.0/benches/main/paragraph.rs000064400000000000000000000076471046102023000162410ustar 00000000000000use std::hint::black_box; use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::{Paragraph, Widget, Wrap}; /// because the scroll offset is a u16, the maximum number of lines that can be scrolled is 65535. /// This is a limitation of the current implementation and may be fixed by changing the type of the /// scroll offset to a u32. const NO_WRAP_WIDTH: u16 = 200; const WRAP_WIDTH: u16 = 100; const PARAGRAPH_DEFAULT_HEIGHT: u16 = 50; /// Benchmark for rendering a paragraph with a given number of lines. The design of this benchmark /// allows comparison of the performance of rendering a paragraph with different numbers of lines. /// as well as comparing with the various settings on the scroll and wrap features. fn paragraph(c: &mut Criterion) { let mut group = c.benchmark_group("paragraph"); for line_count in [64, 2048, u16::MAX] { let lines = random_lines(line_count); let lines = lines.as_str(); let y_scroll = line_count - PARAGRAPH_DEFAULT_HEIGHT; // benchmark that measures the overhead of creating a paragraph separately from rendering group.bench_with_input(BenchmarkId::new("new", line_count), lines, |b, lines| { b.iter(|| Paragraph::new(black_box(lines))); }); // render the paragraph with no scroll group.bench_with_input( BenchmarkId::new("render", line_count), &Paragraph::new(lines), |bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH), ); // scroll the paragraph by half the number of lines and render group.bench_with_input( BenchmarkId::new("render_scroll_half", line_count), &Paragraph::new(lines).scroll((y_scroll / 2, 0)), |bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH), ); // scroll the paragraph by the full number of lines and render group.bench_with_input( BenchmarkId::new("render_scroll_full", line_count), &Paragraph::new(lines).scroll((y_scroll, 0)), |bencher, paragraph| render(bencher, paragraph, NO_WRAP_WIDTH), ); // render the paragraph wrapped to 100 characters group.bench_with_input( BenchmarkId::new("render_wrap", line_count), &Paragraph::new(lines).wrap(Wrap { trim: false }), |bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH), ); // scroll the paragraph by the full number of lines and render wrapped to 100 characters group.bench_with_input( BenchmarkId::new("render_wrap_scroll_full", line_count), &Paragraph::new(lines) .wrap(Wrap { trim: false }) .scroll((y_scroll, 0)), |bencher, paragraph| render(bencher, paragraph, WRAP_WIDTH), ); } group.finish(); } /// Render the paragraph into a buffer with the given width. fn render(bencher: &mut Bencher, paragraph: &Paragraph, width: u16) { let mut buffer = Buffer::empty(Rect::new(0, 0, width, PARAGRAPH_DEFAULT_HEIGHT)); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || paragraph.to_owned(), |bench_paragraph| { bench_paragraph.render(buffer.area, &mut buffer); }, BatchSize::LargeInput, ); } /// Create a string with the given number of lines filled with nonsense words /// /// English language has about 5.1 average characters per word so including the space between words /// this should emit around 200 characters per paragraph on average. fn random_lines(count: u16) -> String { let count = i64::from(count); let sentence_count = 3; let word_count = 11; fakeit::words::paragraph(count, sentence_count, word_count, "\n".into()) } criterion_group!(benches, paragraph); ratatui-0.30.0/benches/main/rect.rs000064400000000000000000000061031046102023000152130ustar 00000000000000use std::hint::black_box; use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group}; use ratatui::layout::Rect; fn rect_iters_benchmark(c: &mut Criterion) { let rect_sizes = vec![[16, 16], [64, 64], [255, 255]]; let mut group = c.benchmark_group("rect"); for rect in rect_sizes.into_iter().map(|[width, height]| Rect { width, height, ..Default::default() }) { group.bench_with_input( BenchmarkId::new("rect_rows_iter", rect.height), &rect, |b, rect| rect_rows_iter(b, *rect), ); group.bench_with_input( BenchmarkId::new("rect_rows_collect", rect.height), &rect, |b, rect| rect_rows_collect(b, *rect), ); group.bench_with_input( BenchmarkId::new("rect_columns_iter", rect.width), &rect, |b, rect| rect_columns_iter(b, *rect), ); group.bench_with_input( BenchmarkId::new("rect_columns_collect", rect.width), &rect, |b, rect| rect_columns_collect(b, *rect), ); group.bench_with_input( BenchmarkId::new( "rect_positions_iter", format!("{}x{}", rect.width, rect.height), ), &rect, |b, rect| rect_positions_iter(b, *rect), ); group.bench_with_input( BenchmarkId::new( "rect_positions_collect", format!("{}x{}", rect.width, rect.height), ), &rect, |b, rect| rect_positions_collect(b, *rect), ); } group.finish(); } fn rect_rows_iter(c: &mut Bencher, rect: Rect) { c.iter_batched( || black_box(rect), |rect| { for row in black_box(rect.rows()) { black_box(row); } }, BatchSize::LargeInput, ); } fn rect_rows_collect(c: &mut Bencher, rect: Rect) { c.iter_batched( || black_box(rect), |rect| black_box(rect.rows()).collect::>(), BatchSize::LargeInput, ); } fn rect_columns_iter(c: &mut Bencher, rect: Rect) { c.iter_batched( || black_box(rect), |rect| { for col in black_box(rect.columns()) { black_box(col); } }, BatchSize::LargeInput, ); } fn rect_columns_collect(c: &mut Bencher, rect: Rect) { c.iter_batched( || black_box(rect), |rect| black_box(rect.columns()).collect::>(), BatchSize::LargeInput, ); } fn rect_positions_iter(c: &mut Bencher, rect: Rect) { c.iter_batched( || black_box(rect), |rect| { for pos in black_box(rect.positions()) { black_box(pos); } }, BatchSize::LargeInput, ); } fn rect_positions_collect(b: &mut Bencher, rect: Rect) { b.iter_batched( || black_box(rect), |rect| black_box(rect.positions()).collect::>(), BatchSize::LargeInput, ); } criterion_group!(benches, rect_iters_benchmark); ratatui-0.30.0/benches/main/sparkline.rs000064400000000000000000000024031046102023000162450ustar 00000000000000use criterion::{Bencher, BenchmarkId, Criterion, criterion_group}; use rand::Rng; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::widgets::{Sparkline, Widget}; /// Benchmark for rendering a sparkline. fn sparkline(c: &mut Criterion) { let mut group = c.benchmark_group("sparkline"); let mut rng = rand::rng(); for data_count in [64, 256, 2048] { let data: Vec = (0..data_count) .map(|_| rng.random_range(0..data_count)) .collect(); // Render a basic sparkline group.bench_with_input( BenchmarkId::new("render", data_count), &Sparkline::default().data(&data), render, ); } group.finish(); } /// Render the block into a buffer of the given `size`. fn render(bencher: &mut Bencher, sparkline: &Sparkline) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || sparkline.clone(), |bench_sparkline| { bench_sparkline.render(buffer.area, &mut buffer); }, criterion::BatchSize::LargeInput, ); } criterion_group!(benches, sparkline); ratatui-0.30.0/benches/main/table.rs000064400000000000000000000044211046102023000153460ustar 00000000000000use criterion::{BatchSize, Bencher, BenchmarkId, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Rect}; use ratatui::widgets::{Row, StatefulWidget, Table, TableState, Widget}; /// Benchmark for rendering a table. /// It only benchmarks the render with a different number of rows, and columns. fn table(c: &mut Criterion) { let mut group = c.benchmark_group("table"); for row_count in [64, 2048, 16384] { for col_count in [2, 4, 8] { let bench_sizes = format!("{row_count}x{col_count}"); let rows: Vec = (0..row_count) .map(|_| Row::new((0..col_count).map(|_| fakeit::words::quote()))) .collect(); // Render default table group.bench_with_input( BenchmarkId::new("render", &bench_sizes), &Table::new(rows.clone(), [] as [Constraint; 0]), render, ); // Render with an offset to the middle of the table and a selected row group.bench_with_input( BenchmarkId::new("render_scroll_half", &bench_sizes), &Table::new(rows, [] as [Constraint; 0]).highlight_symbol(">>"), |b, table| { render_stateful( b, table, TableState::default() .with_offset(row_count / 2) .with_selected(Some(row_count / 2)), ); }, ); } } group.finish(); } fn render(bencher: &mut Bencher, table: &Table) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); bencher.iter_batched( || table.to_owned(), |bench_table| { Widget::render(bench_table, buffer.area, &mut buffer); }, BatchSize::LargeInput, ); } fn render_stateful(bencher: &mut Bencher, table: &Table, mut state: TableState) { let mut buffer = Buffer::empty(Rect::new(0, 0, 200, 50)); bencher.iter_batched( || table.to_owned(), |bench_table| { StatefulWidget::render(bench_table, buffer.area, &mut buffer, &mut state); }, BatchSize::LargeInput, ); } criterion_group!(benches, table); ratatui-0.30.0/benches/main/text.rs000064400000000000000000000054061046102023000152470ustar 00000000000000use criterion::{BatchSize, Bencher, Criterion, criterion_group}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Stylize; use ratatui::text::{Line, Text}; use ratatui::widgets::Widget; /// Benchmark for rendering a text. fn text(c: &mut Criterion) { let mut group = c.benchmark_group("text"); for (width, height) in [ (200, 50), // 1080p fullscreen area with medium font. (1, u16::MAX), // Heavily vertically skewed area. (u16::MAX, 1), // Heavily horizontally skewed area. (4096, 4096), // Max sized area for benchmarking (~sqrt(u16::MAX) * 16, ~768 MB buffer). ] { let buffer_size = Rect::new(0, 0, width, height); // Generates sample text content scaled to the given rendering area. // The number of repeated text blocks is roughly proportional to the area size. // - Small areas produce a few lines (at least 5 x 1 lines). // - Large areas produce many lines (up to ~5 x 1000 lines). let make_text = |height: u16| { let repeat = (height as usize / 5).clamp(1, 1000); Text::from( (0..repeat) .flat_map(|_| { vec![ Line::from("The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs."), Line::from("🦀 Rustaceans unite! 東京・İstanbul・Sydney・San Francisco・Warsaw 🌏 RustConf連携中!").bold(), Line::from("naïve cafés ☕ serve résumé-ready developers 👩‍💻🧑🏾‍💻 testing text rendering engines.").green(), Line::from("ゼロ幅スペース\u{200B}、結合絵文字👨‍👩‍👧‍👦、全角文字ABC、半角abcが混在。").blue(), Line::from("Emoji test: 🙂😇🤖👩🏻‍🎨🧑‍🚀 — wrapped in a buffer for layout & clipping check.").italic(), ] }) .collect::>(), ) }; group.bench_with_input( format!("render/{width}x{height}"), &make_text(height), |b, text| render(b, text, buffer_size), ); } group.finish(); } /// Render the text into a buffer of the given `size`. fn render(bencher: &mut Bencher, text: &Text, size: Rect) { let mut buffer = Buffer::empty(size); // We use `iter_batched` to clone the value in the setup function. // See https://github.com/ratatui/ratatui/pull/377. bencher.iter_batched( || text.to_owned(), |bench_text| { bench_text.render(buffer.area, &mut buffer); }, BatchSize::SmallInput, ); } criterion_group!(benches, text); ratatui-0.30.0/benches/main.rs000064400000000000000000000010621046102023000142550ustar 00000000000000pub mod main { pub mod barchart; pub mod block; pub mod buffer; pub mod constraints; pub mod gauge; pub mod line; pub mod list; pub mod paragraph; pub mod rect; pub mod sparkline; pub mod table; pub mod text; } pub use main::*; criterion::criterion_main!( barchart::benches, block::benches, buffer::benches, line::benches, list::benches, paragraph::benches, rect::benches, sparkline::benches, table::benches, text::benches, constraints::benches, gauge::benches, ); ratatui-0.30.0/src/init.rs000064400000000000000000000463021046102023000134620ustar 00000000000000//! Terminal initialization and restoration functions. //! //! This module provides a set of convenience functions for initializing and restoring terminal //! state when creating Ratatui applications. These functions handle the common setup and teardown //! tasks required for terminal user interfaces. //! //! All functions in this module use the [`CrosstermBackend`] by default, which provides excellent //! cross-platform compatibility and is the recommended backend for most applications. The //! [`DefaultTerminal`] type alias encapsulates this choice, providing a ready-to-use terminal //! configuration that works well across different operating systems. For more information about //! backend choices and alternatives, see the [`backend`](`crate::backend`) module. //! //! Once you have initialized a terminal using the functions in this module, you can use it to //! [draw the UI](`crate#drawing-the-ui`) and [handle events](`crate#handling-events`). For more //! information about building widgets for your application, see the [`widgets`](`crate::widgets`) //! module. //! //! **Note**: All functions and types in this module are re-exported at the crate root for //! convenience, so you can call `ratatui::run()`, `ratatui::init()`, etc. instead of //! `ratatui::init::run()`, `ratatui::init::init()`, etc. //! //! # Available Types and Functions //! //! ## Types //! //! - [`DefaultTerminal`] - A type alias for `Terminal>`, providing a //! reasonable default terminal configuration for most applications. All initialization functions //! return this type. //! //! ## Functions //! //! The module provides several related functions that handle different initialization scenarios: //! //! - [`run`] - Initializes a terminal, runs a closure, and automatically restores the terminal //! state. This is the simplest way to run a Ratatui application and handles all setup and cleanup //! automatically. //! - [`init`] - Creates a terminal with reasonable defaults including alternate screen and raw //! mode. Panics on failure. //! - [`try_init`] - Same as [`init`] but returns a `Result` instead of panicking. //! - [`init_with_options`] - Creates a terminal with custom [`TerminalOptions`], enabling raw mode //! but not alternate screen. Panics on failure. //! - [`try_init_with_options`] - Same as [`init_with_options`] but returns a `Result` instead of //! panicking. //! - [`restore`] - Restores the terminal to its original state. Prints errors to stderr but does //! not panic. //! - [`try_restore`] - Same as [`restore`] but returns a `Result` instead of printing errors. //! //! # Usage Guide //! //! For the simplest setup with automatic cleanup, use [`run`]: //! //! ```rust,no_run //! fn main() -> std::io::Result<()> { //! ratatui::run(|terminal| { //! loop { //! terminal.draw(|frame| frame.render_widget("Hello, world!", frame.area()))?; //! if crossterm::event::read()?.is_key_press() { //! break Ok(()); //! } //! } //! }) //! } //! ``` //! //! For standard full-screen applications with manual control over initialization and cleanup: //! //! ```rust,no_run //! // Using init() - panics on failure //! let mut terminal = ratatui::init(); //! // ... app logic ... //! ratatui::restore(); //! //! // Using try_init() - returns Result for custom error handling //! let mut terminal = ratatui::try_init()?; //! // ... app logic ... //! ratatui::try_restore()?; //! # Ok::<(), std::io::Error>(()) //! ``` //! //! For applications that need custom terminal behavior (inline rendering, custom viewport sizes, //! or applications that don't want alternate screen buffer): //! //! ```rust,no_run //! use ratatui::{TerminalOptions, Viewport}; //! //! let options = TerminalOptions { //! viewport: Viewport::Inline(10), //! }; //! //! // Using init_with_options() - panics on failure //! let mut terminal = ratatui::init_with_options(options); //! // ... app logic ... //! ratatui::restore(); //! //! // Using try_init_with_options() - returns Result for custom error handling //! let options = TerminalOptions { //! viewport: Viewport::Inline(10), //! }; //! let mut terminal = ratatui::try_init_with_options(options)?; //! // ... app logic ... //! ratatui::try_restore()?; //! # Ok::<(), std::io::Error>(()) //! ``` //! //! For cleanup, use [`restore`] in most cases where you want to attempt restoration but don't need //! to handle errors (they are printed to stderr). Use [`try_restore`] when you need to handle //! restoration errors, perhaps to retry or provide user feedback. //! //! Once you have a terminal set up, continue with the main loop to [draw the //! UI](`crate#drawing-the-ui`) and [handle events](`crate#handling-events`). See the [main crate //! documentation](`crate`) for comprehensive examples of complete applications. //! //! # Key Differences //! //! | Function | Alternate Screen | Raw Mode | Error Handling | Use Case | //! |----------|------------------|----------|----------------|----------| //! | [`run`] | ✓ | ✓ | Auto-cleanup | Simple apps | //! | [`init`] | ✓ | ✓ | Panic | Standard full-screen apps | //! | [`try_init`] | ✓ | ✓ | Result | Standard apps with error handling | //! | [`init_with_options`] | ✗ | ✓ | Panic | Custom viewport apps | //! | [`try_init_with_options`] | ✗ | ✓ | Result | Custom viewport with error handling | //! //! # Panic Hook //! //! All initialization functions install a panic hook that automatically restores the terminal //! state before panicking. This ensures that even if your application panics, the terminal will //! be left in a usable state. //! //! **Important**: Call the initialization functions *after* installing any other panic hooks to //! ensure the terminal is restored before other hooks run. use std::io::{self, Stdout, stdout}; use ratatui_core::terminal::{Terminal, TerminalOptions}; use ratatui_crossterm::CrosstermBackend; use ratatui_crossterm::crossterm::execute; use ratatui_crossterm::crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, }; /// A type alias for the default terminal type. /// /// This is a [`Terminal`] using the [`CrosstermBackend`] which writes to [`Stdout`]. This is a /// reasonable default for most applications. To use a different backend or output stream, instead /// use [`Terminal`] and a [backend][`crate::backend`] of your choice directly. pub type DefaultTerminal = Terminal>; /// Run a closure with a terminal initialized with reasonable defaults for most applications. /// /// This function creates a new [`DefaultTerminal`] with [`init`] and then runs the given closure /// with a mutable reference to the terminal. After the closure completes, the terminal is restored /// to its original state with [`restore`]. /// /// This function is a convenience wrapper around [`init`] and [`restore`], and is useful for simple /// applications that need a terminal with reasonable defaults for the entire lifetime of the /// application. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Examples /// /// A simple example where the app logic is contained in the closure: /// /// ```rust,no_run /// use crossterm::event; /// /// fn main() -> Result<(), Box> { /// ratatui::run(|terminal| { /// loop { /// terminal.draw(|frame| frame.render_widget("Hello, world!", frame.area()))?; /// if event::read()?.is_key_press() { /// break Ok(()); /// } /// } /// }) /// } /// ``` /// /// A more complex example where the app logic is contained in a separate function: /// /// ```rust,no_run /// use crossterm::event; /// /// type Result = std::result::Result>; /// /// fn main() -> Result<()> { /// ratatui::run(app) /// } /// /// fn app(terminal: &mut ratatui::DefaultTerminal) -> Result<()> { /// const GREETING: &str = "Hello, world!"; /// loop { /// terminal.draw(|frame| frame.render_widget(format!("{GREETING}"), frame.area()))?; /// if matches!(event::read()?, event::Event::Key(_)) { /// break Ok(()); /// } /// } /// } /// ``` /// /// Once the app logic becomes more complex, it may be beneficial to move the app logic into a /// separate struct. This allows the app logic to be split into multiple methods with each having /// access to the state of the app. This can make the app logic easier to understand and maintain. /// /// ```rust,no_run /// use crossterm::event; /// /// type Result = std::result::Result>; /// /// fn main() -> Result<()> { /// let mut app = App::new(); /// ratatui::run(|terminal| app.run(terminal)) /// } /// /// struct App { /// should_quit: bool, /// name: String, /// } /// /// impl App { /// fn new() -> Self { /// Self { /// should_quit: false, /// name: "world".to_string(), /// } /// } /// /// fn run(&mut self, terminal: &mut ratatui::DefaultTerminal) -> Result<()> { /// while !self.should_quit { /// terminal.draw(|frame| frame.render_widget("Hello, world!", frame.area()))?; /// self.handle_events()?; /// } /// Ok(()) /// } /// /// fn render(&mut self, frame: &mut ratatui::Frame) -> Result<()> { /// let greeting = format!("Hello, {}!", self.name); /// frame.render_widget(greeting, frame.area()); /// Ok(()) /// } /// /// fn handle_events(&mut self) -> Result<()> { /// if event::read()?.is_key_press() { /// self.should_quit = true; /// } /// Ok(()) /// } /// } /// ``` pub fn run(f: F) -> R where F: FnOnce(&mut DefaultTerminal) -> R, { let mut terminal = init(); let result = f(&mut terminal); restore(); result } /// Initialize a terminal with reasonable defaults for most applications. /// /// This will create a new [`DefaultTerminal`] and initialize it with the following defaults: /// /// - Backend: [`CrosstermBackend`] writing to [`Stdout`] /// - Raw mode is enabled /// - Alternate screen buffer enabled /// - A panic hook is installed that restores the terminal before panicking. Ensure that this method /// is called after any other panic hooks that may be installed to ensure that the terminal is /// restored before those hooks are called. /// /// For more control over the terminal initialization, use [`Terminal::new`] or /// [`Terminal::with_options`]. /// /// Ensure that this method is called *after* your app installs any other panic hooks to ensure the /// terminal is restored before the other hooks are called. /// /// Generally, use this function instead of [`try_init`] to ensure that the terminal is restored /// correctly if any of the initialization steps fail. If you need to handle the error yourself, use /// [`try_init`] instead. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Panics /// /// This function will panic if any of the following steps fail: /// /// - Enabling raw mode /// - Entering the alternate screen buffer /// - Creating the terminal fails due to being unable to calculate the terminal size /// /// # Examples /// /// ```rust,no_run /// let terminal = ratatui::init(); /// ``` pub fn init() -> DefaultTerminal { try_init().expect("failed to initialize terminal") } /// Try to initialize a terminal using reasonable defaults for most applications. /// /// This function will attempt to create a [`DefaultTerminal`] and initialize it with the following /// defaults: /// /// - Raw mode is enabled /// - Alternate screen buffer enabled /// - A panic hook is installed that restores the terminal before panicking. /// - A [`Terminal`] is created using [`CrosstermBackend`] writing to [`Stdout`] /// /// If any of these steps fail, the error is returned. /// /// Ensure that this method is called *after* your app installs any other panic hooks to ensure the /// terminal is restored before the other hooks are called. /// /// Generally, you should use [`init`] instead of this function, as the panic hook installed by this /// function will ensure that any failures during initialization will restore the terminal before /// panicking. This function is provided for cases where you need to handle the error yourself. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Examples /// /// ```no_run /// let terminal = ratatui::try_init()?; /// # Ok::<(), std::io::Error>(()) /// ``` pub fn try_init() -> io::Result { set_panic_hook(); enable_raw_mode()?; execute!(stdout(), EnterAlternateScreen)?; let backend = CrosstermBackend::new(stdout()); Terminal::new(backend) } /// Initialize a terminal with the given options and reasonable defaults. /// /// This function allows the caller to specify a custom [`Viewport`] via the [`TerminalOptions`]. It /// will create a new [`DefaultTerminal`] and initialize it with the given options and the following /// defaults: /// /// [`Viewport`]: crate::Viewport /// /// - Raw mode is enabled /// - A panic hook is installed that restores the terminal before panicking. /// /// Unlike [`init`], this function does not enter the alternate screen buffer as this may not be /// desired in all cases. If you need the alternate screen buffer, you should enable it manually /// after calling this function. /// /// For more control over the terminal initialization, use [`Terminal::with_options`]. /// /// Ensure that this method is called *after* your app installs any other panic hooks to ensure the /// terminal is restored before the other hooks are called. /// /// Generally, use this function instead of [`try_init_with_options`] to ensure that the terminal is /// restored correctly if any of the initialization steps fail. If you need to handle the error /// yourself, use [`try_init_with_options`] instead. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Panics /// /// This function will panic if any of the following steps fail: /// /// - Enabling raw mode /// - Creating the terminal fails due to being unable to calculate the terminal size /// /// # Examples /// /// ```rust,no_run /// use ratatui::{TerminalOptions, Viewport}; /// /// let options = TerminalOptions { /// viewport: Viewport::Inline(5), /// }; /// let terminal = ratatui::init_with_options(options); /// ``` pub fn init_with_options(options: TerminalOptions) -> DefaultTerminal { try_init_with_options(options).expect("failed to initialize terminal") } /// Try to initialize a terminal with the given options and reasonable defaults. /// /// This function allows the caller to specify a custom [`Viewport`] via the [`TerminalOptions`]. It /// will attempt to create a [`DefaultTerminal`] and initialize it with the given options and the /// following defaults: /// /// [`Viewport`]: crate::Viewport /// /// - Raw mode is enabled /// - A panic hook is installed that restores the terminal before panicking. /// /// Unlike [`try_init`], this function does not enter the alternate screen buffer as this may not be /// desired in all cases. If you need the alternate screen buffer, you should enable it manually /// after calling this function. /// /// If any of these steps fail, the error is returned. /// /// Ensure that this method is called *after* your app installs any other panic hooks to ensure the /// terminal is restored before the other hooks are called. /// /// Generally, you should use [`init_with_options`] instead of this function, as the panic hook /// installed by this function will ensure that any failures during initialization will restore the /// terminal before panicking. This function is provided for cases where you need to handle the /// error yourself. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Examples /// /// ```no_run /// use ratatui::{TerminalOptions, Viewport}; /// /// let options = TerminalOptions { /// viewport: Viewport::Inline(5), /// }; /// let terminal = ratatui::try_init_with_options(options)?; /// # Ok::<(), std::io::Error>(()) /// ``` pub fn try_init_with_options(options: TerminalOptions) -> io::Result { set_panic_hook(); enable_raw_mode()?; let backend = CrosstermBackend::new(stdout()); Terminal::with_options(backend, options) } /// Restores the terminal to its original state. /// /// This function should be called before the program exits to ensure that the terminal is /// restored to its original state. /// /// This function will attempt to restore the terminal to its original state by performing the /// following steps: /// /// 1. Raw mode is disabled. /// 2. The alternate screen buffer is left. /// /// If either of these steps fail, the error is printed to stderr and ignored. /// /// Use this function over [`try_restore`] when you don't need to handle the error yourself, as /// ignoring the error is generally the correct behavior when cleaning up before exiting. If you /// need to handle the error yourself, use [`try_restore`] instead. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Examples /// /// ```rust,no_run /// ratatui::restore(); /// ``` pub fn restore() { if let Err(err) = try_restore() { // There's not much we can do if restoring the terminal fails, so we just print the error std::eprintln!("Failed to restore terminal: {err}"); } } /// Restore the terminal to its original state. /// /// This function will attempt to restore the terminal to its original state by performing the /// following steps: /// /// 1. Raw mode is disabled. /// 2. The alternate screen buffer is left. /// /// If either of these steps fail, the error is returned. /// /// Use [`restore`] instead of this function when you don't need to handle the error yourself, as /// ignoring the error is generally the correct behavior when cleaning up before exiting. If you /// need to handle the error yourself, use this function instead. /// /// See the [module-level documentation](mod@crate::init) for a comparison of all initialization /// functions and guidance on when to use each one. /// /// # Examples /// /// ```no_run /// ratatui::try_restore()?; /// # Ok::<(), std::io::Error>(()) /// ``` pub fn try_restore() -> io::Result<()> { // disabling raw mode first is important as it has more side effects than leaving the alternate // screen buffer disable_raw_mode()?; execute!(stdout(), LeaveAlternateScreen)?; Ok(()) } /// Sets a panic hook that restores the terminal before panicking. /// /// Replaces the panic hook with a one that will restore the terminal state before calling the /// original panic hook. This ensures that the terminal is left in a good state when a panic occurs. fn set_panic_hook() { let hook = std::panic::take_hook(); std::panic::set_hook(alloc::boxed::Box::new(move |info| { restore(); hook(info); })); } ratatui-0.30.0/src/lib.rs000064400000000000000000000524321046102023000132660ustar 00000000000000#![no_std] // show the feature flags in the generated documentation #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/logo.png", html_favicon_url = "https://raw.githubusercontent.com/ratatui/ratatui/main/assets/favicon.ico" )] #![warn(missing_docs)] //! ![Release header](https://github.com/ratatui/ratatui/blob/b23480adfa9430697071c906c7ba4d4f9bd37a73/assets/release-header.png?raw=true) //! //!
//! //! [![Crate Badge]][Crate] [![Docs Badge]][API Docs] [![CI Badge]][CI Workflow] [![Deps.rs //! Badge]][Deps.rs]
[![Codecov Badge]][Codecov] [![License Badge]](./LICENSE) [![Sponsors //! Badge]][GitHub Sponsors]
[![Discord Badge]][Discord Server] [![Matrix Badge]][Matrix] //! [![Forum Badge]][Forum]
//! //! [Ratatui Website] · [API Docs] · [Examples] · [Changelog] · [Breaking Changes]
//! [Contributing] · [Report a bug] · [Request a Feature] · [Create a Pull Request] //! //!
//! //! [Ratatui][Ratatui Website] is a crate for cooking up terminal user interfaces in Rust. It is a //! lightweight library that provides a set of [widgets](`widgets`) and utilities to build complex //! Rust TUIs. Ratatui was forked from the [tui-rs] crate in 2023 in order to continue its //! development. //! //! ## Quickstart //! //! Add `ratatui` and `crossterm` as dependencies to your cargo.toml: //! //! ```shell //! cargo add ratatui crossterm //! ``` //! //! Then you can create a simple "Hello World" application: //! //! ```rust,no_run //! use crossterm::event; //! //! fn main() -> std::io::Result<()> { //! ratatui::run(|mut terminal| { //! loop { //! terminal.draw(|frame| frame.render_widget("Hello World!", frame.area()))?; //! if event::read()?.is_key_press() { //! break Ok(()); //! } //! } //! }) //! } //! ``` //! //! The full code for this example which contains a little more detail is in the [Examples] //! directory. For more guidance on different ways to structure your application see the //! [Application Patterns] and [Hello Ratatui tutorial] sections in the [Ratatui Website] and the //! various [Examples]. There are also several starter [Templates] available to help you get //! started quickly with common patterns. //! //! # Other documentation //! //! - [Ratatui Website] - explains the library's concepts and provides step-by-step tutorials //! - [Tutorials] - step-by-step guides including [Hello Ratatui tutorial] and [Counter App] //! - [Recipes] - practical how-to guides for common tasks and patterns //! - [FAQ] - frequently asked questions and answers //! - [Templates] - pre-built project templates using [Cargo Generate] //! - [Showcase] - a gallery of applications and widgets built with Ratatui //! - [Ratatui Forum][Forum] - a place to ask questions and discuss the library //! - [API Docs] - the full API documentation for the library on docs.rs. //! - [Examples] - a collection of examples that demonstrate how to use the library. //! - [Contributing] - Please read this if you are interested in contributing to the project. //! - [Changelog] - generated by [git-cliff] utilizing [Conventional Commits]. //! - [Breaking Changes] - a list of breaking changes in the library. //! //! You can also watch the [FOSDEM 2024 talk] about Ratatui which gives a brief introduction to //! terminal user interfaces and showcases the features of Ratatui, along with a hello world demo. //! //! ## Getting Help //! //! If you need help or have questions, check out our [FAQ] for common questions and solutions. //! You can also join our community on [Discord][Discord Server], [Matrix], or post on our //! [Forum] for assistance and discussions. //! //! # Crate Organization //! //! Starting with Ratatui 0.30.0, the project was reorganized into a modular workspace to improve //! compilation times, API stability, and dependency management. Most applications should continue //! using this main `ratatui` crate, which re-exports everything for convenience: //! //! - **[`ratatui`](crate)**: Main crate with complete functionality (recommended for apps) //! - **[`ratatui-core`]**: Core traits and types for widget libraries //! - **[`ratatui-widgets`]**: Built-in widget implementations //! - **Backend crates**: [`ratatui-crossterm`], [`ratatui-termion`], [`ratatui-termwiz`] //! - **[`ratatui-macros`]**: Macros for simplifying the boilerplate //! //! **For application developers**: No changes needed - continue using `ratatui` as before. //! //! **For widget library authors**: Consider depending on [`ratatui-core`] instead of the full //! `ratatui` crate for better API stability and reduced dependencies. //! //! See [ARCHITECTURE.md] for detailed information about the crate organization and design //! decisions. //! //! # Writing Applications //! //! Ratatui is based on the principle of immediate rendering with intermediate buffers. This means //! that for each frame, your app must render all [`widgets`] that are supposed to be part of the //! UI. This is in contrast to the retained mode style of rendering where widgets are updated and //! then automatically redrawn on the next frame. See the [Rendering] section of the [Ratatui //! Website] for more info. //! //! Ratatui uses [Crossterm] by default as it works on most platforms. See the [Installation] //! section of the [Ratatui Website] for more details on how to use other backends ([Termion] / //! [Termwiz]). //! //! Every application built with `ratatui` needs to implement the following steps: //! //! - Initialize the terminal (see the [`init` module] for convenient initialization functions) //! - A main loop that: //! - Draws the UI //! - Handles input events //! - Restore the terminal state //! //! ## Initialize and restore the terminal //! //! The simplest way to initialize and run a terminal application is to use the [`run()`] function, //! which handles terminal initialization, restoration, and panic hooks automatically: //! //! ```rust,no_run //! fn main() -> std::io::Result<()> { //! ratatui::run(|mut terminal| { //! loop { //! terminal.draw(render)?; //! if should_quit()? { //! break Ok(()); //! } //! } //! }) //! } //! //! fn render(frame: &mut ratatui::Frame) { //! // ... //! } //! //! fn should_quit() -> std::io::Result { //! // ... //! # Ok(false) //! } //! ``` //! //! For more control over initialization and restoration, you can use [`init()`] and [`restore()`]: //! //! ```rust,no_run //! fn main() -> std::io::Result<()> { //! let mut terminal = ratatui::init(); //! let result = run_app(&mut terminal); //! ratatui::restore(); //! result //! } //! //! fn run_app(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> { //! loop { //! terminal.draw(render)?; //! if should_quit()? { //! break Ok(()); //! } //! } //! } //! # fn render(_frame: &mut ratatui::Frame) {} //! # fn should_quit() -> std::io::Result { Ok(false) } //! ``` //! //! Note that when using [`init()`] and [`restore()`], it's important to use a separate function //! for the main loop to ensure that [`restore()`] is always called, even if the `?` operator //! causes early return from an error. //! //! For more detailed information about initialization options and when to use each function, see //! the [`init` module] documentation. //! //! ### Manual Terminal and Backend Construction //! //! Before the convenience functions were introduced in version 0.28.1 ([`init()`]/[`restore()`]) //! and 0.30.0 ([`run()`]), applications constructed [`Terminal`] and [`Backend`] instances //! manually. This approach is still supported for applications that need fine-grained control over //! initialization. See the [`Terminal`] and [`backend`] module documentation for details. //! //! See the [`backend` module] and the [Backends] section of the [Ratatui Website] for more info on //! the alternate screen and raw mode. Learn more about different backend options in the [Backend //! Comparison] guide. //! //! ## Drawing the UI //! //! Drawing the UI is done by calling the [`Terminal::draw`] method on the terminal instance. This //! method takes a closure that is called with a [`Frame`] instance. The [`Frame`] provides the size //! of the area to draw to and allows the app to render any [`Widget`] using the provided //! [`render_widget`] method. After this closure returns, a diff is performed and only the changes //! are drawn to the terminal. See the [Widgets] section of the [Ratatui Website] and the [Widget //! Recipes] for more info on creating effective UIs. //! //! The closure passed to the [`Terminal::draw`] method should handle the rendering of a full frame. //! For guidance on setting up the terminal before drawing, see the [`init` module] documentation. //! //! ```rust,no_run //! use ratatui::Frame; //! use ratatui::widgets::Paragraph; //! //! fn run(terminal: &mut ratatui::DefaultTerminal) -> std::io::Result<()> { //! loop { //! terminal.draw(|frame| render(frame))?; //! if handle_events()? { //! break Ok(()); //! } //! } //! } //! //! fn render(frame: &mut Frame) { //! let text = Paragraph::new("Hello World!"); //! frame.render_widget(text, frame.area()); //! } //! # fn handle_events() -> std::io::Result { Ok(false) } //! ``` //! //! ## Handling events //! //! Ratatui does not include any input handling. Instead event handling can be implemented by //! calling backend library methods directly. See the [Handling Events] section of the [Ratatui //! Website] for conceptual information. For example, if you are using [Crossterm], you can use the //! [`crossterm::event`] module to handle events. //! //! ```rust,no_run //! use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; //! //! fn handle_events() -> std::io::Result { //! match event::read()? { //! Event::Key(key) if key.kind == KeyEventKind::Press => match key.code { //! KeyCode::Char('q') => return Ok(true), //! // handle other key events //! _ => {} //! }, //! // handle other events //! _ => {} //! } //! Ok(false) //! } //! ``` //! //! ## Layout //! //! The library comes with a basic yet useful layout management object called [`Layout`] which //! allows you to split the available space into multiple areas and then render widgets in each //! area. This lets you describe a responsive terminal UI by nesting layouts. See the [Layout] //! section of the [Ratatui Website] for more info, and check out the [Layout Recipes] for //! practical examples. //! //! ```rust,no_run //! use ratatui::Frame; //! use ratatui::layout::{Constraint, Layout}; //! use ratatui::widgets::Block; //! //! fn draw(frame: &mut Frame) { //! use Constraint::{Fill, Length, Min}; //! //! let vertical = Layout::vertical([Length(1), Min(0), Length(1)]); //! let [title_area, main_area, status_area] = vertical.areas(frame.area()); //! let horizontal = Layout::horizontal([Fill(1); 2]); //! let [left_area, right_area] = horizontal.areas(main_area); //! //! frame.render_widget(Block::bordered().title("Title Bar"), title_area); //! frame.render_widget(Block::bordered().title("Status Bar"), status_area); //! frame.render_widget(Block::bordered().title("Left"), left_area); //! frame.render_widget(Block::bordered().title("Right"), right_area); //! } //! ``` //! //! Running this example produces the following output: //! //! ```text //! Title Bar─────────────────────────────────── //! ┌Left────────────────┐┌Right───────────────┐ //! │ ││ │ //! └────────────────────┘└────────────────────┘ //! Status Bar────────────────────────────────── //! ``` //! //! ## Text and styling //! //! The [`Text`], [`Line`] and [`Span`] types are the building blocks of the library and are used in //! many places. [`Text`] is a list of [`Line`]s and a [`Line`] is a list of [`Span`]s. A [`Span`] //! is a string with a specific style. //! //! The [`style` module] provides types that represent the various styling options. The most //! important one is [`Style`] which represents the foreground and background colors and the text //! attributes of a [`Span`]. The [`style` module] also provides a [`Stylize`] trait that allows //! short-hand syntax to apply a style to widgets and text. See the [Styling Text] section of the //! [Ratatui Website] for more info, and explore the [Styling Recipes] for creative examples. //! //! ```rust,no_run //! use ratatui::Frame; //! use ratatui::layout::{Constraint, Layout}; //! use ratatui::style::{Color, Modifier, Style, Stylize}; //! use ratatui::text::{Line, Span}; //! use ratatui::widgets::{Block, Paragraph}; //! //! fn draw(frame: &mut Frame) { //! let areas = Layout::vertical([Constraint::Length(1); 4]).split(frame.area()); //! //! let line = Line::from(vec![ //! Span::raw("Hello "), //! Span::styled( //! "World", //! Style::new() //! .fg(Color::Green) //! .bg(Color::White) //! .add_modifier(Modifier::BOLD), //! ), //! "!".red().on_light_yellow().italic(), //! ]); //! frame.render_widget(line, areas[0]); //! //! // using the short-hand syntax and implicit conversions //! let paragraph = Paragraph::new("Hello World!".red().on_white().bold()); //! frame.render_widget(paragraph, areas[1]); //! //! // style the whole widget instead of just the text //! let paragraph = Paragraph::new("Hello World!").style(Style::new().red().on_white()); //! frame.render_widget(paragraph, areas[2]); //! //! // use the simpler short-hand syntax //! let paragraph = Paragraph::new("Hello World!").blue().on_yellow(); //! frame.render_widget(paragraph, areas[3]); //! } //! ``` #![cfg_attr(feature = "document-features", doc = "\n## Features")] #![cfg_attr(feature = "document-features", doc = document_features::document_features!())] //! //! [Ratatui Website]: https://ratatui.rs/ //! [Installation]: https://ratatui.rs/installation/ //! [Tutorials]: https://ratatui.rs/tutorials/ //! [Hello Ratatui tutorial]: https://ratatui.rs/tutorials/hello-ratatui/ //! [Counter App]: https://ratatui.rs/tutorials/counter-app/ //! [Recipes]: https://ratatui.rs/recipes/ //! [FAQ]: https://ratatui.rs/faq/ //! [Templates]: https://ratatui.rs/templates/ //! [Cargo Generate]: https://cargo-generate.github.io/cargo-generate/ //! [Showcase]: https://ratatui.rs/showcase/ //! [Rendering]: https://ratatui.rs/concepts/rendering/ //! [Application Patterns]: https://ratatui.rs/concepts/application-patterns/ //! [Hello World tutorial]: https://ratatui.rs/tutorials/hello-world/ //! [Backends]: https://ratatui.rs/concepts/backends/ //! [Backend Comparison]: https://ratatui.rs/concepts/backends/comparison/ //! [Widgets]: https://ratatui.rs/recipes/widgets/ //! [Widget Recipes]: https://ratatui.rs/recipes/widgets/ //! [Handling Events]: https://ratatui.rs/concepts/event-handling/ //! [Layout]: https://ratatui.rs/recipes/layout/ //! [Layout Recipes]: https://ratatui.rs/recipes/layout/ //! [Styling Text]: https://ratatui.rs/recipes/render/style-text/ //! [Styling Recipes]: https://ratatui.rs/recipes/render/ //! [templates]: https://github.com/ratatui/templates/ //! [Examples]: https://github.com/ratatui/ratatui/tree/main/ratatui/examples/README.md //! [Report a bug]: https://github.com/ratatui/ratatui/issues/new?labels=bug&projects=&template=bug_report.md //! [Request a Feature]: https://github.com/ratatui/ratatui/issues/new?labels=enhancement&projects=&template=feature_request.md //! [Create a Pull Request]: https://github.com/ratatui/ratatui/compare //! [git-cliff]: https://git-cliff.org //! [Conventional Commits]: https://www.conventionalcommits.org //! [API Docs]: https://docs.rs/ratatui //! [Changelog]: https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md //! [Contributing]: https://github.com/ratatui/ratatui/blob/main/CONTRIBUTING.md //! [Breaking Changes]: https://github.com/ratatui/ratatui/blob/main/BREAKING-CHANGES.md //! [FOSDEM 2024 talk]: https://www.youtube.com/watch?v=NU0q6NOLJ20 //! [`render_widget`]: Frame::render_widget //! [`Widget`]: widgets::Widget //! [`Layout`]: layout::Layout //! [`Text`]: text::Text //! [`Line`]: text::Line //! [`Span`]: text::Span //! [`Style`]: style::Style //! [`style` module]: style //! [`Stylize`]: style::Stylize //! [`Backend`]: backend::Backend //! [`Terminal`]: Terminal //! [`backend` module]: backend //! [`init` module]: mod@init //! [`crossterm::event`]: https://docs.rs/crossterm/latest/crossterm/event/index.html //! [Crate]: https://crates.io/crates/ratatui //! [Crossterm]: https://crates.io/crates/crossterm //! [Termion]: https://crates.io/crates/termion //! [Termwiz]: https://crates.io/crates/termwiz //! [tui-rs]: https://crates.io/crates/tui //! [`ratatui-core`]: https://crates.io/crates/ratatui-core //! [`ratatui-widgets`]: https://crates.io/crates/ratatui-widgets //! [`ratatui-crossterm`]: https://crates.io/crates/ratatui-crossterm //! [`ratatui-termion`]: https://crates.io/crates/ratatui-termion //! [`ratatui-termwiz`]: https://crates.io/crates/ratatui-termwiz //! [`ratatui-macros`]: https://crates.io/crates/ratatui-macros //! [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md //! [GitHub Sponsors]: https://github.com/sponsors/ratatui //! [Crate Badge]: https://img.shields.io/crates/v/ratatui?logo=rust&style=flat-square&logoColor=E05D44&color=E05D44 //! [License Badge]: https://img.shields.io/crates/l/ratatui?style=flat-square&color=1370D3 //! [CI Badge]: https://img.shields.io/github/actions/workflow/status/ratatui/ratatui/ci.yml?style=flat-square&logo=github //! [CI Workflow]: https://github.com/ratatui/ratatui/actions/workflows/ci.yml //! [Codecov Badge]: https://img.shields.io/codecov/c/github/ratatui/ratatui?logo=codecov&style=flat-square&token=BAQ8SOKEST&color=C43AC3&logoColor=C43AC3 //! [Codecov]: https://app.codecov.io/gh/ratatui/ratatui //! [Deps.rs Badge]: https://deps.rs/repo/github/ratatui/ratatui/status.svg?style=flat-square //! [Deps.rs]: https://deps.rs/repo/github/ratatui/ratatui //! [Discord Badge]: https://img.shields.io/discord/1070692720437383208?label=discord&logo=discord&style=flat-square&color=1370D3&logoColor=1370D3 //! [Discord Server]: https://discord.gg/pMCEU9hNEj //! [Docs Badge]: https://img.shields.io/docsrs/ratatui?logo=rust&style=flat-square&logoColor=E05D44 //! [Matrix Badge]: https://img.shields.io/matrix/ratatui-general%3Amatrix.org?style=flat-square&logo=matrix&label=Matrix&color=C43AC3 //! [Matrix]: https://matrix.to/#/#ratatui:matrix.org //! [Forum Badge]: https://img.shields.io/discourse/likes?server=https%3A%2F%2Fforum.ratatui.rs&style=flat-square&logo=discourse&label=forum&color=C43AC3 //! [Forum]: https://forum.ratatui.rs //! [Sponsors Badge]: https://img.shields.io/github/sponsors/ratatui?logo=github&style=flat-square&color=1370D3 //! [`ratatui-core`]: https://crates.io/crates/ratatui-core //! [`ratatui-widgets`]: https://crates.io/crates/ratatui-widgets //! [`ratatui-crossterm`]: https://crates.io/crates/ratatui-crossterm //! [`ratatui-termion`]: https://crates.io/crates/ratatui-termion //! [`ratatui-termwiz`]: https://crates.io/crates/ratatui-termwiz //! [`ratatui-macros`]: https://crates.io/crates/ratatui-macros //! [ARCHITECTURE.md]: https://github.com/ratatui/ratatui/blob/main/ARCHITECTURE.md #![warn(clippy::std_instead_of_core)] #![warn(clippy::std_instead_of_alloc)] #![warn(clippy::alloc_instead_of_core)] extern crate alloc; #[cfg(feature = "std")] extern crate std; /// re-export the `palette` crate so that users don't have to add it as a dependency #[cfg(feature = "palette")] pub use palette; pub use ratatui_core::terminal::{CompletedFrame, Frame, Terminal, TerminalOptions, Viewport}; pub use ratatui_core::{buffer, layout}; /// re-export the `crossterm` crate so that users don't have to add it as a dependency #[cfg(feature = "crossterm")] pub use ratatui_crossterm::crossterm; #[cfg(feature = "macros")] pub use ratatui_macros as macros; /// re-export the `termion` crate so that users don't have to add it as a dependency #[cfg(all(not(windows), feature = "termion"))] pub use ratatui_termion::termion; /// re-export the `termwiz` crate so that users don't have to add it as a dependency #[cfg(feature = "termwiz")] pub use ratatui_termwiz::termwiz; #[cfg(feature = "crossterm")] #[doc(inline)] pub use crate::init::{ DefaultTerminal, init, init_with_options, restore, run, try_init, try_init_with_options, try_restore, }; /// Re-exports for the backend implementations. pub mod backend { pub use ratatui_core::backend::{Backend, ClearType, TestBackend, WindowSize}; #[cfg(feature = "crossterm")] pub use ratatui_crossterm::{CrosstermBackend, FromCrossterm, IntoCrossterm}; #[cfg(all(not(windows), feature = "termion"))] pub use ratatui_termion::{FromTermion, IntoTermion, TermionBackend}; #[cfg(feature = "termwiz")] pub use ratatui_termwiz::{FromTermwiz, IntoTermwiz, TermwizBackend}; } pub mod prelude; pub use ratatui_core::{style, symbols, text}; pub mod widgets; pub use ratatui_widgets::border; #[cfg(feature = "crossterm")] pub mod init; ratatui-0.30.0/src/prelude.rs000064400000000000000000000035101046102023000141510ustar 00000000000000//! A prelude for conveniently writing applications using this library. //! //! The prelude module is no longer used universally in Ratatui, as it can make it harder to //! distinguish between library and non-library types, especially when viewing source code //! outside of an IDE (such as on GitHub or in a git diff). For more details and user feedback, //! see [Issue #1150]. However, the prelude is still available for backward compatibility and for //! those who prefer to use it. //! //! [Issue #1150]: https://github.com/ratatui/ratatui/issues/1150 //! //! # Examples //! //! ```rust,no_run //! use ratatui::prelude::*; //! ``` //! //! Aside from the main types that are used in the library, this prelude also re-exports several //! modules to make it easy to qualify types that would otherwise collide. E.g.: //! //! ```rust //! use ratatui::prelude::*; //! use ratatui::widgets::*; //! //! #[derive(Debug, Default, PartialEq, Eq)] //! struct Line; //! //! assert_eq!(Line::default(), Line); //! assert_eq!(text::Line::default(), ratatui::text::Line::from(vec![])); //! ``` pub use ratatui_core::backend::{self, Backend}; #[cfg(feature = "crossterm")] pub use ratatui_crossterm::{CrosstermBackend, FromCrossterm, IntoCrossterm}; #[cfg(all(not(windows), feature = "termion"))] pub use crate::backend::{FromTermion, IntoTermion, TermionBackend}; #[cfg(feature = "termwiz")] pub use crate::backend::{FromTermwiz, IntoTermwiz, TermwizBackend}; pub use crate::buffer::{self, Buffer}; pub use crate::layout::{ self, Alignment, Constraint, Direction, HorizontalAlignment, Layout, Margin, Position, Rect, Size, VerticalAlignment, }; pub use crate::style::{self, Color, Modifier, Style, Stylize}; pub use crate::text::{self, Line, Masked, Span, Text}; pub use crate::widgets::{BlockExt, StatefulWidget, Widget}; pub use crate::{Frame, Terminal, symbols}; ratatui-0.30.0/src/widgets/stateful_widget_ref.rs000064400000000000000000000133361046102023000202140ustar 00000000000000use ratatui_core::widgets::StatefulWidget; use crate::buffer::Buffer; use crate::layout::Rect; /// A `StatefulWidgetRef` is a trait that allows rendering a stateful widget by reference. /// /// This is the stateful equivalent of `WidgetRef`. It is useful when you need to store a reference /// to a stateful widget and render it later. It also allows you to render boxed stateful widgets. /// /// This trait was introduced in Ratatui 0.26.0. It is currently marked as unstable as we are still /// evaluating the API and may make changes in the future. See /// for more information. /// /// A blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget` /// is provided. Most of the time you will want to implement `StatefulWidget` against a reference to /// the widget instead of implementing `StatefulWidgetRef` directly. /// /// See the documentation for [`WidgetRef`] for more information on boxed widgets. See the /// documentation for [`StatefulWidget`] for more information on stateful widgets. /// /// For comprehensive information about widget implementation patterns, rendering, and usage, /// see the [`widgets`] module documentation. /// /// [`widgets`]: crate::widgets /// /// # Examples /// /// ```rust /// # #[cfg(feature = "unstable-widget-ref")] { /// use ratatui::widgets::StatefulWidgetRef; /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// use ratatui_core::style::Stylize; /// use ratatui_core::text::Line; /// use ratatui_core::widgets::{StatefulWidget, Widget}; /// /// struct PersonalGreeting; /// /// impl StatefulWidgetRef for PersonalGreeting { /// type State = String; /// fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { /// Line::raw(format!("Hello {}", state)).render(area, buf); /// } /// } /// /// impl StatefulWidget for PersonalGreeting { /// type State = String; /// fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { /// (&self).render_ref(area, buf, state); /// } /// } /// /// fn render(area: Rect, buf: &mut Buffer) { /// let widget = PersonalGreeting; /// let mut state = "world".to_string(); /// widget.render(area, buf, &mut state); /// } /// # } /// ``` #[instability::unstable(feature = "widget-ref")] pub trait StatefulWidgetRef { /// State associated with the stateful widget. /// /// If you don't need this then you probably want to implement [`WidgetRef`] instead. /// /// [`WidgetRef`]: super::WidgetRef type State: ?Sized; /// Draws the current state of the widget in the given buffer. That is the only method required /// to implement a custom stateful widget. fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State); } /// Blanket implementation of `StatefulWidgetRef` for `&W` where `W` implements `StatefulWidget`. /// /// This allows you to render a stateful widget by reference. impl StatefulWidgetRef for &W where for<'a> &'a W: StatefulWidget, { type State = State; fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { self.render(area, buf, state); } } #[cfg(test)] mod tests { use alloc::borrow::ToOwned; use alloc::boxed::Box; use alloc::format; use alloc::string::{String, ToString}; use rstest::{fixture, rstest}; use super::*; use crate::buffer::Buffer; use crate::layout::Rect; use crate::text::Line; use crate::widgets::Widget; #[fixture] fn buf() -> Buffer { Buffer::empty(Rect::new(0, 0, 20, 1)) } #[fixture] fn state() -> String { "world".to_string() } struct PersonalGreeting; impl StatefulWidget for &PersonalGreeting { type State = String; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { Line::from(format!("Hello {state}")).render(area, buf); } } #[rstest] fn render_ref(mut buf: Buffer, mut state: String) { let widget = &PersonalGreeting; widget.render_ref(buf.area, &mut buf, &mut state); assert_eq!(buf, Buffer::with_lines(["Hello world "])); } #[rstest] fn box_render_ref(mut buf: Buffer, mut state: String) { let widget = Box::new(&PersonalGreeting); widget.render_ref(buf.area, &mut buf, &mut state); assert_eq!(buf, Buffer::with_lines(["Hello world "])); } #[rstest] fn render_stateful_widget_ref_with_unsized_state(mut buf: Buffer) { struct Bytes; impl StatefulWidgetRef for Bytes { type State = [u8]; fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let slice = core::str::from_utf8(state).unwrap(); Line::from(format!("Bytes: {slice}")).render(area, buf); } } let widget = Bytes; let state = b"hello"; widget.render_ref(buf.area, &mut buf, &mut state.clone()); assert_eq!(buf, Buffer::with_lines(["Bytes: hello "])); } #[rstest] fn render_stateful_widget_with_unsized_state(mut buf: Buffer) { struct Bytes; impl StatefulWidget for &Bytes { type State = [u8]; fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let slice = core::str::from_utf8(state).unwrap(); Line::from(format!("Bytes: {slice}")).render(area, buf); } } let widget = &Bytes; let mut state = b"hello".to_owned(); let state = state.as_mut_slice(); widget.render_ref(buf.area, &mut buf, state); assert_eq!(buf, Buffer::with_lines(["Bytes: hello "])); } } ratatui-0.30.0/src/widgets/widget_ref.rs000064400000000000000000000210731046102023000163020ustar 00000000000000use alloc::string::String; use super::Widget; use crate::buffer::Buffer; use crate::layout::Rect; use crate::style::Style; /// A `WidgetRef` is a trait that allows rendering a widget by reference. /// /// This trait is useful when you want to store a reference to a widget and render it later. It also /// allows you to render boxed widgets. /// /// Boxed widgets allow you to store widgets with a type that is not known at compile time. This is /// useful when you want to store a collection of widgets with different types. You can then iterate /// over the collection and render each widget. /// /// This trait was introduced in Ratatui 0.26.0 and is implemented for all the internal widgets. It /// is currently marked as unstable as we are still evaluating the API and may make changes in the /// future. See for more information. /// /// A blanket implementation of `Widget` for `&W` where `W` implements `WidgetRef` is provided. /// /// A blanket implementation of `WidgetRef` for `Option` where `W` implements `WidgetRef` is /// provided. This is a convenience approach to make it easier to attach child widgets to parent /// widgets. It allows you to render an optional widget by reference. /// /// For comprehensive information about widget implementation patterns, rendering, and usage, /// see the [`widgets`] module documentation. /// /// [`widgets`]: crate::widgets /// /// # Examples /// /// ```rust /// # #[cfg(feature = "unstable-widget-ref")] { /// use ratatui::widgets::WidgetRef; /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// use ratatui_core::text::Line; /// use ratatui_core::widgets::Widget; /// /// struct Greeting; /// /// struct Farewell; /// /// impl WidgetRef for Greeting { /// fn render_ref(&self, area: Rect, buf: &mut Buffer) { /// Line::raw("Hello").render(area, buf); /// } /// } /// /// /// Only needed for backwards compatibility /// impl Widget for Greeting { /// fn render(self, area: Rect, buf: &mut Buffer) { /// self.render_ref(area, buf); /// } /// } /// /// impl WidgetRef for Farewell { /// fn render_ref(&self, area: Rect, buf: &mut Buffer) { /// Line::raw("Goodbye").right_aligned().render(area, buf); /// } /// } /// /// /// Only needed for backwards compatibility /// impl Widget for Farewell { /// fn render(self, area: Rect, buf: &mut Buffer) { /// self.render_ref(area, buf); /// } /// } /// /// # fn render(area: Rect, buf: &mut Buffer) { /// let greeting = Greeting; /// let farewell = Farewell; /// /// // these calls do not consume the widgets, so they can be used again later /// greeting.render_ref(area, buf); /// farewell.render_ref(area, buf); /// /// // a collection of widgets with different types /// let widgets: Vec> = vec![Box::new(greeting), Box::new(farewell)]; /// for widget in widgets { /// widget.render_ref(area, buf); /// } /// # } /// # } /// ``` #[instability::unstable(feature = "widget-ref")] pub trait WidgetRef { /// Draws the current state of the widget in the given buffer. That is the only method required /// to implement a custom widget. fn render_ref(&self, area: Rect, buf: &mut Buffer); } /// This allows you to render a widget by reference. impl WidgetRef for &W where for<'a> &'a W: Widget, { fn render_ref(&self, area: Rect, buf: &mut Buffer) { self.render(area, buf); } } /// Provides the ability to render a string slice by reference. /// /// This trait implementation ensures that a string slice, which is an immutable view over a /// `String`, can be drawn on demand without requiring ownership of the string itself. It utilizes /// the default text style when rendering onto the provided [`Buffer`] at the position defined by /// [`Rect`]. impl WidgetRef for &str { fn render_ref(&self, area: Rect, buf: &mut Buffer) { buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new()); } } /// Provides the ability to render a `String` by reference. /// /// This trait allows for a `String` to be rendered onto the [`Buffer`], similarly using the default /// style settings. It ensures that an owned `String` can be rendered efficiently by reference, /// without the need to give up ownership of the underlying text. impl WidgetRef for String { fn render_ref(&self, area: Rect, buf: &mut Buffer) { buf.set_stringn(area.x, area.y, self, area.width as usize, Style::new()); } } /// A blanket implementation of `WidgetExt` for `Option` where `W` implements `WidgetRef`. /// /// This is a convenience implementation that makes it easy to attach child widgets to parent /// widgets. It allows you to render an optional widget by reference. /// /// The internal widgets use this pattern to render the optional `Block` widgets that are included /// on most widgets. /// Blanket implementation of `WidgetExt` for `Option` where `W` implements `WidgetRef`. /// /// # Examples /// /// ```rust /// # #[cfg(feature = "unstable-widget-ref")] { /// use ratatui::widgets::WidgetRef; /// use ratatui_core::buffer::Buffer; /// use ratatui_core::layout::Rect; /// use ratatui_core::text::Line; /// use ratatui_core::widgets::Widget; /// /// struct Parent { /// child: Option, /// } /// /// struct Child; /// /// impl WidgetRef for Child { /// fn render_ref(&self, area: Rect, buf: &mut Buffer) { /// Line::raw("Hello from child").render(area, buf); /// } /// } /// /// impl WidgetRef for Parent { /// fn render_ref(&self, area: Rect, buf: &mut Buffer) { /// self.child.render_ref(area, buf); /// } /// } /// # } /// ``` impl WidgetRef for Option { fn render_ref(&self, area: Rect, buf: &mut Buffer) { if let Some(widget) = self { widget.render_ref(area, buf); } } } #[cfg(test)] mod tests { use alloc::boxed::Box; use alloc::vec; use alloc::vec::Vec; use rstest::{fixture, rstest}; use super::*; use crate::buffer::Buffer; use crate::layout::Rect; use crate::text::Line; #[fixture] fn buf() -> Buffer { Buffer::empty(Rect::new(0, 0, 20, 1)) } struct Greeting; struct Farewell; impl Widget for &Greeting { fn render(self, area: Rect, buf: &mut Buffer) { Line::from("Hello").render(area, buf); } } impl Widget for &Farewell { fn render(self, area: Rect, buf: &mut Buffer) { Line::from("Goodbye").right_aligned().render(area, buf); } } /// Ensure that the blanket implementation of `WidgetRef` for `&W` where `W` implements /// `Widget` works as expected. #[rstest] fn render_ref(mut buf: Buffer) { let widget = &Greeting; widget.render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["Hello "])); } #[rstest] fn render_ref_box(mut buf: Buffer) { let widget: Box = Box::new(&Greeting); widget.render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["Hello "])); } #[rstest] fn render_ref_box_vec(mut buf: Buffer) { let widgets: Vec> = vec![Box::new(&Greeting), Box::new(&Farewell)]; for widget in widgets { widget.render_ref(buf.area, &mut buf); } assert_eq!(buf, Buffer::with_lines(["Hello Goodbye"])); } #[rstest] fn render_ref_some(mut buf: Buffer) { let widget = Some(&Greeting); widget.render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["Hello "])); } #[rstest] fn render_ref_none(mut buf: Buffer) { let widget: Option<&Greeting> = None; widget.render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines([" "])); } #[rstest] fn render_ref_str(mut buf: Buffer) { "hello world".render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["hello world "])); } #[rstest] fn render_ref_option_str(mut buf: Buffer) { Some("hello world").render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["hello world "])); } #[rstest] fn render_ref_string(mut buf: Buffer) { String::from("hello world").render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["hello world "])); } #[rstest] fn render_ref_option_string(mut buf: Buffer) { Some(String::from("hello world")).render_ref(buf.area, &mut buf); assert_eq!(buf, Buffer::with_lines(["hello world "])); } } ratatui-0.30.0/src/widgets.rs000064400000000000000000001047651046102023000141750ustar 00000000000000#![warn(missing_docs)] //! Widgets are the building blocks of user interfaces in Ratatui. //! //! They are used to create and manage the layout and style of the terminal interface. Widgets can //! be combined and nested to create complex UIs, and can be easily customized to suit the needs of //! your application. //! //! Ratatui provides a wide variety of built-in widgets that can be used to quickly create UIs. //! Additionally, [`String`], [`&str`], [`Span`], [`Line`], and [`Text`] can be used as widgets //! (though often [`Paragraph`] is used instead of these directly as it allows wrapping and //! surrounding the text with a block). //! //! # Crate Organization //! //! Starting with Ratatui 0.30.0, the project was split into multiple crates for better modularity: //! //! - **[`ratatui-core`]**: Contains the core widget traits ([`Widget`], [`StatefulWidget`]) and //! text-related types ([`String`], [`&str`], [`Span`], [`Line`], [`Text`]) //! - **[`ratatui-widgets`]**: Contains all the built-in widget implementations ([`Block`], //! [`Paragraph`], [`List`], etc.) //! - **[`ratatui`](crate)**: The main crate that re-exports everything for convenience. The //! unstable [`WidgetRef`] and [`StatefulWidgetRef`] traits are defined in the main `ratatui` //! crate as they are experimental. //! //! This split serves different user needs: //! //! - **App Authors**: Most application developers should use the main [`ratatui`](crate) crate, //! which provides everything needed to build terminal applications with widgets, backends, and //! layout systems //! - **Widget Library Authors**: When creating third-party widget libraries, consider depending //! only on [`ratatui-core`] to avoid pulling in unnecessary built-in widgets and reduce //! compilation time for your users //! - **Minimalist Projects**: Use [`ratatui-core`] directly if you only need the fundamental traits //! and text types without any built-in widgets //! //! The modular structure allows widget library authors to create lightweight dependencies while //! still being compatible with the broader Ratatui ecosystem. //! //! [`ratatui-core`]: https://crates.io/crates/ratatui-core //! [`ratatui-widgets`]: https://crates.io/crates/ratatui-widgets //! //! # Built-in Widgets //! //! Ratatui provides a comprehensive set of built-in widgets: //! //! - [`Block`]: a basic widget that draws a block with optional borders, titles and styles. //! - [`BarChart`]: displays multiple datasets as bars with optional grouping. //! - [`calendar::Monthly`]: displays a single month. //! - [`Canvas`]: draws arbitrary shapes using drawing characters. //! - [`Chart`]: displays multiple datasets as a lines or scatter graph. //! - [`Clear`]: clears the area it occupies. Useful to render over previously drawn widgets. //! - [`Gauge`]: displays progress percentage using block characters. //! - [`LineGauge`]: display progress as a line. //! - [`List`]: displays a list of items and allows selection. //! - [`Paragraph`]: displays a paragraph of optionally styled and wrapped text. //! - [`Scrollbar`]: displays a scrollbar. //! - [`Sparkline`]: display a single data set as a sparkline. //! - [`Table`]: displays multiple rows and columns in a grid and allows selection. //! - [`Tabs`]: displays a tab bar and allows selection. //! - [`RatatuiLogo`]: displays the Ratatui logo. //! - [`RatatuiMascot`]: displays the Ratatui mascot. //! //! Additionally, primitive text types implement [`Widget`]: //! - [`String`]: renders the owned string content //! - [`&str`]: renders the string slice content //! - [`Line`]: renders a single line of styled text spans //! - [`Span`]: renders a styled text segment //! - [`Text`]: renders multiple lines of styled text //! //! For more information on these widgets, you can view the widget showcase and examples. //! //! # Third-Party Widgets //! //! Beyond the built-in widgets, there's a rich ecosystem of third-party widgets available that //! extend Ratatui's functionality. These community-contributed widgets provide specialized UI //! components for various use cases. //! //! To discover third-party widgets: //! //! - **Search crates.io**: Look for crates with "tui" or "ratatui" in their names or descriptions //! - **Awesome Ratatui**: Check the [Awesome Ratatui](https://github.com/ratatui-org/awesome-ratatui) //! repository for a curated list of widgets, libraries, and applications //! - **Widget Showcase**: Browse the [third-party widgets showcase](https://ratatui.rs/showcase/third-party-widgets/) //! on the Ratatui website to see widgets in action //! //! These third-party widgets cover a wide range of functionality including specialized input //! components, data visualization widgets, layout helpers, and domain-specific UI elements. //! //! [`Canvas`]: crate::widgets::canvas::Canvas //! [`Frame`]: crate::Frame //! [`Terminal::draw`]: crate::Terminal::draw //! [`Line`]: crate::text::Line //! [`Span`]: crate::text::Span //! [`Text`]: crate::text::Text //! [`String`]: alloc::string::String //! [`&str`]: str //! //! # Widget Traits //! //! In Ratatui, widgets are implemented as Rust traits, which allow for easy implementation and //! extension. The main traits for widgets are: //! //! - [`Widget`]: Basic trait for stateless widgets that are consumed when rendered //! - [`StatefulWidget`]: Trait for widgets that maintain state between renders //! - [`WidgetRef`]: Trait for rendering widgets by reference (unstable) //! - [`StatefulWidgetRef`]: Trait for rendering stateful widgets by reference (unstable) //! //! ## `Widget` //! //! The [`Widget`] trait is the most basic trait for widgets in Ratatui. It provides the basic //! functionality for rendering a widget onto a buffer. Widgets implementing this trait are consumed //! when rendered. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect}; //! pub trait Widget { //! fn render(self, area: Rect, buf: &mut Buffer); //! } //! ``` //! //! Prior to Ratatui 0.26.0, widgets were generally created for each frame as they were consumed //! during rendering. This meant that they were not meant to be stored but used as *commands* to //! draw common figures in the UI. Starting with 0.26.0, implementing widgets on references became //! the preferred pattern for reusability. //! //! ## `StatefulWidget` //! //! The [`StatefulWidget`] trait is similar to the [`Widget`] trait, but also includes state that //! can be managed and updated during rendering. This is useful for widgets that need to remember //! things between draw calls, such as scroll position or selection state. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect}; //! pub trait StatefulWidget { //! type State; //! fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State); //! } //! ``` //! //! For example, the built-in [`List`] widget can highlight the currently selected item. This //! requires maintaining an offset to ensure the selected item is visible within the viewport. //! Without state, the widget could only provide basic scrolling behavior, but with access to the //! previous offset, it can implement natural scrolling where the offset is preserved until the //! selected item moves out of view. //! //! ## `WidgetRef` and `StatefulWidgetRef` //! //! The [`WidgetRef`] and [`StatefulWidgetRef`] traits were introduced in Ratatui 0.26.0 to enable //! rendering widgets by reference instead of consuming them. These traits address several important //! use cases that the original `Widget` and `StatefulWidget` traits couldn't handle elegantly. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect}; //! # #[cfg(feature = "unstable-widget-ref")] //! pub trait WidgetRef { //! fn render_ref(&self, area: Rect, buf: &mut Buffer); //! } //! //! # #[cfg(feature = "unstable-widget-ref")] //! pub trait StatefulWidgetRef { //! type State; //! fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State); //! } //! ``` //! //! The reference-based traits solve several key problems: //! //! - **Reusability**: Widgets can be rendered multiple times without being consumed //! - **Collections**: Store heterogeneous widgets in collections like `Vec>` //! - **Borrowing**: Render widgets when you only have a reference, not ownership //! - **Efficiency**: Avoid unnecessary cloning or reconstruction for repeated renders //! //! These traits are currently **experimental** and gated behind the `unstable-widget-ref` feature //! flag. This means: //! //! - The API may change in future releases //! - Method names, signatures, or behavior might be adjusted based on community feedback //! - You must explicitly enable the feature flag to use them: `features = ["unstable-widget-ref"]` //! - They are not covered by semantic versioning guarantees until stabilized //! //! The traits are being evaluated for potential breaking changes and improvements. See the //! [tracking issue](https://github.com/ratatui/ratatui/issues/1287) for ongoing discussions and //! design considerations. //! //! # Rendering Widgets //! //! Widgets are typically rendered using the [`Frame`] type, which provides methods for rendering //! both consuming and reference-based widgets. These methods are usually called from the closure //! passed to [`Terminal::draw`]. //! //! ## Rendering Consuming Widgets //! //! Most widgets in Ratatui are rendered using `Frame::render_widget()`, which consumes the widget //! when rendering. This is the standard approach for stateless widgets that don't need to persist //! data between frames. //! //! ```rust //! # use ratatui::{backend::TestBackend, Terminal}; //! # use ratatui::widgets::Paragraph; //! # let backend = TestBackend::new(10, 3); //! # let mut terminal = Terminal::new(backend).unwrap(); //! terminal.draw(|frame| { //! let widget = Paragraph::new("Hello, world!"); //! frame.render_widget(widget, frame.area()); //! }); //! ``` //! //! ## Rendering Widget References //! //! When you implement widgets on references (`Widget for &MyWidget`), you can render them directly //! using the same `Frame::render_widget()` method. This approach enables widget reuse without //! reconstruction and is the recommended pattern for new widgets. //! //! ```rust //! # use ratatui::{backend::TestBackend, Terminal}; //! # use ratatui::widgets::{Block, Paragraph}; //! # let backend = TestBackend::new(10, 3); //! # let mut terminal = Terminal::new(backend).unwrap(); //! // Create the widget outside the draw closure //! let paragraph = Paragraph::new("Hello, world!").block(Block::bordered()); //! //! terminal.draw(|frame| { //! // Widget can be rendered by reference without being consumed //! frame.render_widget(¶graph, frame.area()); //! }); //! //! // The widget can be used again in subsequent frames //! terminal.draw(|frame| { //! frame.render_widget(¶graph, frame.area()); //! }); //! ``` //! //! ## Rendering Stateful Widgets //! //! Widgets that need to maintain state between frames use `Frame::render_stateful_widget()`. This //! method takes both the widget and a mutable reference to its state, allowing the widget to read //! and modify state during rendering (such as updating scroll positions or handling selections). //! //! ```rust //! # use ratatui::{backend::TestBackend, Terminal}; //! # use ratatui::widgets::{List, ListItem, ListState}; //! # let backend = TestBackend::new(10, 3); //! # let mut terminal = Terminal::new(backend).unwrap(); //! let mut list_state = ListState::default(); //! terminal.draw(|frame| { //! let items = vec![ListItem::new("Item 1"), ListItem::new("Item 2")]; //! let list = List::new(items); //! frame.render_stateful_widget(list, frame.area(), &mut list_state); //! }); //! ``` //! //! ## Single Root Widget Pattern //! //! A common compositional pattern in Ratatui applications is to have a single root widget (often an //! `App` struct) that represents your entire application state. This widget is passed to //! `Frame::render_widget()`, and within its render method, it calls render on child widgets //! directly. This pattern provides a clean separation between your application logic and rendering //! code, and allows for easy composition of complex UIs from simpler components. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, widgets::Widget}; //! # use ratatui::widgets::{Block, Paragraph}; //! #[derive(Default)] //! struct App { //! should_quit: bool, //! } //! //! impl Widget for &App { //! fn render(self, area: Rect, buf: &mut Buffer) { //! // Render header //! let header = Paragraph::new("My App").block(Block::bordered()); //! header.render(Rect::new(area.x, area.y, area.width, 3), buf); //! //! // Render main content //! let content = Paragraph::new("Main content area"); //! content.render( //! Rect::new(area.x, area.y + 3, area.width, area.height - 3), //! buf, //! ); //! } //! } //! ``` //! //! # Authoring Custom Widgets //! //! When implementing custom widgets in Ratatui, you'll make fundamental decisions about how your //! widget manages state and how it's used by applications. Understanding these choices will help //! you create widgets that fit well into your application's architecture. Widget implementation //! involves several key architectural decisions that work together to determine how your widget //! behaves - these decisions are independent but complementary, allowing you to mix and match //! approaches based on your specific needs. //! //! **State Management**: The first choice is where state lives. Some widgets need to track //! information between renders - things like scroll positions, selections, or counters. You can //! either build this state into the widget itself (widget-owned state) or keep it separate and pass //! it in during rendering (external state). //! //! **Ownership Model**: The second choice is how the widget is consumed. Widgets can either be //! consumed when rendered (taking ownership) or work by reference (borrowing). Reference-based //! widgets can be stored and reused across multiple frames, while consuming widgets are created //! fresh each time. //! //! **`StatefulWidget` vs Mutable References**: When your widget needs state, you have two main //! approaches. The [`StatefulWidget`] trait represents the established pattern - it separates the //! widget from its state, allowing the application to own and manage the state independently. This //! is what you'll see in most existing Ratatui code and built-in widgets like [`List`] and //! [`Table`]. The mutable reference approach (`Widget for &mut MyWidget`) is newer and less common, //! but useful when the state is intrinsic to the widget's identity. With mutable references, the //! widget owns its state directly. //! //! The key question for state management is: "If I recreate this widget, should the state reset?" //! If yes (like a counter that should start at zero), use mutable references with widget-owned //! state. If no (like a list selection that should persist), use [`StatefulWidget`] with external //! state that the application manages. //! //! **Evolution and Current Recommendations**: Ratatui's patterns have evolved significantly. Before //! version 0.26.0, widgets were typically consuming (`Widget for MyWidget`) and created fresh each //! frame. Starting with 0.26.0, reference-based widgets (`Widget for &MyWidget`) became possible, //! allowing widgets to be stored and reused. You'll encounter both patterns in existing code, but //! reference-based implementations are now recommended for new widgets because they enable //! reusability and automatic [`WidgetRef`] support through blanket implementations. //! //! For new widgets, implement [`Widget`] or [`StatefulWidget`] on references to your widget types //! (`&MyWidget` or `&mut MyWidget`). This provides reusability and automatic [`WidgetRef`] support. //! You can optionally implement the consuming version for backward compatibility. //! //! ## State Management Patterns //! //! For a comprehensive exploration of different approaches to handling both mutable and immutable //! state in widgets, see the [state examples] in the Ratatui repository. These examples demonstrate //! various patterns including: //! //! **Immutable State Patterns** (recommended for most use cases): //! - Function-based immutable state (`fn render(frame: &mut Frame, area: Rect, state: &State)`) //! - Shared reference widgets (`impl Widget for &MyWidget`) //! - Consuming widgets (`impl Widget for MyWidget`) //! //! **Mutable State Patterns** (for widgets that modify state during rendering): //! - Function-based mutable state (`fn render(frame: &mut Frame, area: Rect, state: &mut State)`) //! - Mutable widget references (`impl Widget for &mut MyWidget`) //! - `StatefulWidget` pattern (`impl StatefulWidget for MyWidget`) //! - Custom component traits (`trait MyComponent { fn render(&mut self, frame: &mut Frame, area: //! Rect) }`) //! - Interior mutability with `RefCell` (`struct MyWidget { state: Rc> }`) //! - Lifetime-based mutable references (`struct MyWidget<'a> { state: &'a mut State }`) //! - Nested widget hierarchies (compositions with owned or external state) //! //! Each pattern has different trade-offs in terms of complexity, performance, and architectural //! fit, making them suitable for different use cases and application designs. For most //! applications, start with immutable patterns as they are simpler to reason about and less prone //! to borrowing issues. //! //! [state examples]: https://github.com/ratatui/ratatui/tree/main/examples/concepts/state //! //! ## Shared References (`&Widget`) //! //! The recommended pattern for most new widgets implements [`Widget`] on a shared reference, //! allowing the widget to be rendered multiple times without being consumed. This approach is ideal //! for immutable widgets that don't need to modify their internal state during rendering, and it's //! the most common pattern you should use for new widgets. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget}; //! struct MyWidget { //! content: String, //! } //! //! impl Widget for &MyWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! Line::raw(&self.content).render(area, buf); //! } //! } //! ``` //! //! This automatically provides [`WidgetRef`] support through blanket implementations and enables //! widgets to be stored and reused across frames without reconstruction. For most use cases where //! the widget doesn't need to change its internal state during rendering, this is the best choice. //! //! ## Mutable References (`&mut Widget`) //! //! For widgets that need to modify their internal state during rendering, implement [`Widget`] on a //! mutable reference. This is a newer pattern that's less common but useful when the state is //! intrinsic to the widget's identity and behavior. Use this pattern when the widget should own and //! manage its state directly, rather than having external state passed in. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::Widget}; //! struct CounterWidget { //! count: u32, // This state belongs to the widget //! label: String, //! } //! //! impl Widget for &mut CounterWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! self.count += 1; // State changes as part of rendering behavior //! let text = format!("{label}: {count}", label = self.label, count = self.count); //! Line::raw(text).render(area, buf); //! } //! } //! ``` //! //! This pattern works well when the widget owns its state and the state is part of the widget's //! identity. It's ideal for counters, animations, cursors, progress indicators, or other //! widget-specific behavior where the state should reset when you create a new widget instance. //! //! ## Consuming Widget Implementation //! //! The consuming widget pattern was the original approach in Ratatui and remains very common in //! existing codebases. You'll encounter this pattern frequently when reading examples and community //! code. Widgets implementing this pattern take ownership when rendered, which means they're //! consumed on each use. While not the recommended approach for new widgets, it's still useful to //! understand this pattern for compatibility and when working with existing code. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, style::Modifier, text::{Line, Span}, widgets::Widget}; //! struct GreetingWidget { //! name: String, //! } //! //! impl Widget for GreetingWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! let hello = Span::raw("Hello, "); //! let name = Span::styled(self.name, Modifier::BOLD); //! let line = Line::from(vec![hello, name]); //! line.render(area, buf); //! } //! } //! ``` //! //! This approach is simpler and works well for widgets created fresh each frame, but it means the //! widget cannot be reused. Before reference-based widgets were introduced in version 0.26.0, this //! was the standard pattern, and it's still valid for simple use cases or when following existing //! code patterns. //! //! The easiest way to implement this pattern when you have a reference-based widget is to implement //! the consuming version on the owned type, which can then call the reference-based implementation: //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, widgets::Widget}; //! # struct GreetingWidget; //! # impl Widget for &GreetingWidget { //! # fn render(self, area: Rect, buf: &mut Buffer) {} //! # } //! impl Widget for GreetingWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! // Call the reference-based implementation //! (&self).render(area, buf); //! } //! } //! `````` //! //! ## `StatefulWidget` Implementation //! //! When your widget needs to work with external state - data that exists independently of the //! widget and should persist between widget instances - implement [`StatefulWidget`]. This is the //! established pattern used by built-in widgets like [`List`] and [`Table`], where the widget //! configuration is separate from application state like selections or scroll positions. //! //! Like [`Widget`], you can implement [`StatefulWidget`] on references to allow reuse, though it's //! more common to see this trait implemented on owned types which are consumed during rendering. //! //! ```rust //! # use ratatui_core::{buffer::Buffer, layout::Rect, text::Line, widgets::{StatefulWidget, Widget}}; //! struct ListView { //! items: Vec, //! } //! //! #[derive(Default)] //! struct ListState { //! selected: Option, // This is application state //! scroll_offset: usize, //! } //! //! impl StatefulWidget for ListView { //! type State = ListState; //! //! fn render(self, area: Rect, buf: &mut Buffer, state: &mut ListState) { //! // Render based on external state, possibly modify for scrolling //! let display_text = state //! .selected //! .and_then(|i| self.items.get(i)) //! .map_or("None selected", |s| s.as_str()); //! Line::raw(display_text).render(area, buf); //! } //! } //! ``` //! //! This pattern is ideal for selections, scroll positions, form data, or any state that should //! persist between renders or be shared across your application. The state exists independently of //! the widget, so recreating the widget doesn't reset the state. //! //! ### Automatic `WidgetRef` Support //! //! When you implement `Widget for &MyWidget`, you automatically get [`WidgetRef`] support without //! any additional code. Ratatui provides blanket implementations that automatically implement these //! traits for any type that implements [`Widget`] or [`StatefulWidget`] on a reference. This means //! that implementing `Widget for &MyWidget` gives you both the standard widget functionality and //! the unstable [`WidgetRef`] capabilities for free. //! //! ## Manual `WidgetRef` Implementation (Advanced) //! //! Manual implementation of [`WidgetRef`] or [`StatefulWidgetRef`] is only necessary when you need //! to store widgets as trait objects (`Box`) or when you want a different API than //! the reference-based [`Widget`] implementation provides. In most cases, the automatic //! implementation via blanket implementations is sufficient. //! //! These traits enable several benefits: //! - Widgets can be stored and rendered multiple times without reconstruction //! - Collections of widgets with different types can be stored using `Box` //! - Avoids the consumption model while maintaining backward compatibility //! //! Manual implementation is only needed when you want to use trait objects or need a different API //! than the reference-based [`Widget`] implementation: //! //! ```rust //! # #[cfg(feature = "unstable-widget-ref")] { //! # use ratatui_core::{buffer::Buffer, layout::Rect, style::Modifier, text::{Line, Span}}; //! # use ratatui::widgets::{Widget, WidgetRef}; //! struct GreetingWidget { //! name: String, //! } //! //! // Manual WidgetRef implementation (usually not needed) //! impl WidgetRef for GreetingWidget { //! fn render_ref(&self, area: Rect, buf: &mut Buffer) { //! let hello = Span::raw("Hello, "); //! let name = Span::styled(&self.name, Modifier::BOLD); //! let line = Line::from(vec![hello, name]); //! line.render(area, buf); //! } //! } //! //! // For backward compatibility //! impl Widget for GreetingWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! self.render_ref(area, buf); //! } //! } //! # } //! ``` //! //! This pattern allows the widget to be stored and rendered multiple times: //! //! ```rust //! # #[cfg(feature = "unstable-widget-ref")] { //! # use ratatui_core::{buffer::Buffer, layout::Rect}; //! # use ratatui::widgets::WidgetRef; //! # struct GreetingWidget { name: String } //! # impl WidgetRef for GreetingWidget { //! # fn render_ref(&self, area: Rect, buf: &mut Buffer) {} //! # } //! struct App { //! greeting: GreetingWidget, //! } //! //! // The widget can be rendered multiple times without reconstruction //! fn render_app(app: &App, area: Rect, buf: &mut Buffer) { //! app.greeting.render_ref(area, buf); //! } //! # } //! ``` //! //! ### Using Trait Objects for Dynamic Collections //! //! The main benefit of manual [`WidgetRef`] implementation is the ability to create collections of //! different widget types using trait objects. This is useful when you need to store widgets with //! types that are not known at compile time: //! //! ```rust //! # #[cfg(feature = "unstable-widget-ref")] { //! # use ratatui_core::{buffer::Buffer, layout::Rect}; //! # use ratatui::widgets::WidgetRef; //! # struct Greeting; //! # struct Farewell; //! # impl WidgetRef for Greeting { fn render_ref(&self, area: Rect, buf: &mut Buffer) {} } //! # impl WidgetRef for Farewell { fn render_ref(&self, area: Rect, buf: &mut Buffer) {} } //! # let area = Rect::new(0, 0, 10, 3); //! # let mut buf = &mut Buffer::empty(area); //! let widgets: Vec> = vec![Box::new(Greeting), Box::new(Farewell)]; //! //! for widget in &widgets { //! widget.render_ref(area, buf); //! } //! # } //! ``` //! //! However, if you implement `Widget for &MyWidget`, you can achieve similar functionality by //! storing references or using the automatic [`WidgetRef`] implementation without needing to //! manually implement the trait. //! //! ## Authoring Custom Widget Libraries //! //! When creating a library of custom widgets for distribution, there are specific considerations //! that will make your library more compatible and accessible to a wider range of users. Following //! these guidelines will help ensure your widget library works well in various environments and //! can be easily integrated into different types of applications. //! //! ### Depend on `ratatui_core` //! //! For widget libraries, depend on [`ratatui-core`] instead of the full `ratatui` crate. This //! provides all the essential types and traits needed for widget development while avoiding //! unnecessary dependencies on backends and other components that widget libraries don't need. //! //! This approach offers several key advantages for both library authors and users: //! //! - **Lighter dependencies**: Users don't pull in backend code they don't need, keeping their //! dependency tree smaller and more focused //! - **Better compile times**: Fewer dependencies mean faster builds for both development and //! end-user projects //! - **Future-proofing**: Your library remains compatible as Ratatui evolves its architecture, //! since core widget functionality is stable across versions //! //! ### Make Your Crate `no_std` Compatible //! //! For maximum compatibility, especially in embedded environments, consider making your widget //! library `no_std` compatible. This is often easier than you might expect and broadens the range //! of projects that can use your widgets. //! //! For more detail on advantages of this, maintenance tips and feature flags, see the //! [no-std concept guide]. //! //! To implement `no_std` compatibility, add the `#![no_std]` attribute at the top of your `lib.rs`. //! When working in a `no_std` environment, you'll need to make a few adjustments: //! //! - Use `core::` instead of `std::` for basic functionality //! - Add `extern crate alloc;` to access allocation types //! - Use `alloc::` for heap-allocated types like `String`, `Vec`, and `Box` //! //! Here's a complete example of a `no_std` compatible widget: //! //! ```ignore //! #![no_std] //! //! extern crate alloc; //! //! use alloc::string::String; //! //! use ratatui_core::buffer::Buffer; //! use ratatui_core::layout::Rect; //! use ratatui_core::text::Line; //! use ratatui_core::widgets::Widget; //! //! struct MyWidget { //! content: String, //! } //! //! impl Widget for &MyWidget { //! fn render(self, area: Rect, buf: &mut Buffer) { //! Line::raw(&self.content).render(area, buf); //! } //! } //! ``` //! //! The benefits of `no_std` compatibility include: //! //! - **Broader compatibility**: Your widgets work seamlessly in embedded environments and other //! `no_std` contexts where standard library functionality isn't available //! - **Easy to adopt**: Even if you haven't worked with `no_std` development before, the changes //! are typically minimal for widget libraries. Most widget logic involves basic data manipulation //! and rendering operations that work well within `no_std` constraints, making this compatibility //! straightforward to implement //! //! [`ratatui-core`]: https://crates.io/crates/ratatui-core //! [no-std concept guide]: https://ratatui.rs/concepts/no-std/ pub use ratatui_core::widgets::{StatefulWidget, Widget}; pub use ratatui_widgets::barchart::{Bar, BarChart, BarGroup}; pub use ratatui_widgets::block::{Block, BlockExt, Padding, TitlePosition}; pub use ratatui_widgets::borders::{BorderType, Borders}; #[cfg(feature = "widget-calendar")] pub use ratatui_widgets::calendar; pub use ratatui_widgets::canvas; pub use ratatui_widgets::chart::{Axis, Chart, Dataset, GraphType, LegendPosition}; pub use ratatui_widgets::clear::Clear; pub use ratatui_widgets::gauge::{Gauge, LineGauge}; pub use ratatui_widgets::list::{List, ListDirection, ListItem, ListState}; pub use ratatui_widgets::logo::{RatatuiLogo, Size as RatatuiLogoSize}; pub use ratatui_widgets::mascot::{MascotEyeColor, RatatuiMascot}; pub use ratatui_widgets::paragraph::{Paragraph, Wrap}; pub use ratatui_widgets::scrollbar::{ ScrollDirection, Scrollbar, ScrollbarOrientation, ScrollbarState, }; pub use ratatui_widgets::sparkline::{RenderDirection, Sparkline, SparklineBar}; pub use ratatui_widgets::table::{Cell, HighlightSpacing, Row, Table, TableState}; pub use ratatui_widgets::tabs::Tabs; #[instability::unstable(feature = "widget-ref")] pub use {stateful_widget_ref::StatefulWidgetRef, widget_ref::WidgetRef}; mod stateful_widget_ref; mod widget_ref; use ratatui_core::layout::Rect; /// Extension trait for [`Frame`] that provides methods to render [`WidgetRef`] and /// [`StatefulWidgetRef`] to the current buffer. #[instability::unstable(feature = "widget-ref")] pub trait FrameExt { /// Render a [`WidgetRef`] to the current buffer using [`WidgetRef::render_ref`]. /// /// Usually the area argument is the size of the current frame or a sub-area of the current /// frame (which can be obtained using [`Layout`] to split the total area). /// /// # Example /// /// ```rust /// # #[cfg(feature = "unstable-widget-ref")] { /// # use ratatui::{backend::TestBackend, Terminal}; /// # let backend = TestBackend::new(5, 5); /// # let mut terminal = Terminal::new(backend).unwrap(); /// # let mut frame = terminal.get_frame(); /// use ratatui::layout::Rect; /// use ratatui::widgets::{Block, FrameExt}; /// /// let block = Block::new(); /// let area = Rect::new(0, 0, 5, 5); /// frame.render_widget_ref(&block, area); /// # } /// ``` /// /// [`Layout`]: crate::layout::Layout fn render_widget_ref(&mut self, widget: W, area: Rect); /// Render a [`StatefulWidgetRef`] to the current buffer using /// [`StatefulWidgetRef::render_ref`]. /// /// Usually the area argument is the size of the current frame or a sub-area of the current /// frame (which can be obtained using [`Layout`] to split the total area). /// /// The last argument should be an instance of the [`StatefulWidgetRef::State`] associated to /// the given [`StatefulWidgetRef`]. /// /// # Example /// /// ```rust /// # #[cfg(feature = "unstable-widget-ref")] { /// # use ratatui::{backend::TestBackend, Terminal}; /// # let backend = TestBackend::new(5, 5); /// # let mut terminal = Terminal::new(backend).unwrap(); /// # let mut frame = terminal.get_frame(); /// use ratatui::layout::Rect; /// use ratatui::widgets::{FrameExt, List, ListItem, ListState}; /// /// let mut state = ListState::default().with_selected(Some(1)); /// let list = List::new(vec![ListItem::new("Item 1"), ListItem::new("Item 2")]); /// let area = Rect::new(0, 0, 5, 5); /// frame.render_stateful_widget_ref(&list, area, &mut state); /// # } /// ``` /// [`Layout`]: crate::layout::Layout fn render_stateful_widget_ref(&mut self, widget: W, area: Rect, state: &mut W::State) where W: StatefulWidgetRef; } #[cfg(feature = "unstable-widget-ref")] impl FrameExt for ratatui_core::terminal::Frame<'_> { fn render_widget_ref(&mut self, widget: W, area: Rect) { widget.render_ref(area, self.buffer_mut()); } fn render_stateful_widget_ref(&mut self, widget: W, area: Rect, state: &mut W::State) where W: StatefulWidgetRef, { widget.render_ref(area, self.buffer_mut(), state); } } ratatui-0.30.0/tests/backend_termion.rs000064400000000000000000000041021046102023000162060ustar 00000000000000#[cfg(all(not(windows), feature = "termion"))] #[test] fn backend_termion_should_only_write_diffs() -> Result<(), Box> { use std::fmt::Write; use std::io::Cursor; let mut bytes = Vec::new(); let mut stdout = Cursor::new(&mut bytes); { use ratatui::backend::TermionBackend; use ratatui::layout::Rect; use ratatui::widgets::Paragraph; use ratatui::{Terminal, TerminalOptions, Viewport}; let backend = TermionBackend::new(&mut stdout); let area = Rect::new(0, 0, 3, 1); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Fixed(area), }, )?; terminal.draw(|f| { f.render_widget(Paragraph::new("a"), area); })?; terminal.draw(|f| { f.render_widget(Paragraph::new("ab"), area); })?; terminal.draw(|f| { f.render_widget(Paragraph::new("abc"), area); })?; } let expected = { use ratatui::termion::{color, cursor, style}; let mut s = String::new(); // First draw write!(s, "{}", cursor::Goto(1, 1))?; s.push('a'); write!(s, "{}", color::Fg(color::Reset))?; write!(s, "{}", color::Bg(color::Reset))?; write!(s, "{}", style::Reset)?; write!(s, "{}", cursor::Hide)?; // Second draw write!(s, "{}", cursor::Goto(2, 1))?; s.push('b'); write!(s, "{}", color::Fg(color::Reset))?; write!(s, "{}", color::Bg(color::Reset))?; write!(s, "{}", style::Reset)?; write!(s, "{}", cursor::Hide)?; // Third draw write!(s, "{}", cursor::Goto(3, 1))?; s.push('c'); write!(s, "{}", color::Fg(color::Reset))?; write!(s, "{}", color::Bg(color::Reset))?; write!(s, "{}", style::Reset)?; write!(s, "{}", cursor::Hide)?; // Terminal drop write!(s, "{}", cursor::Show)?; s }; assert_eq!(std::str::from_utf8(&bytes)?, expected); Ok(()) } ratatui-0.30.0/tests/state_serde.rs000064400000000000000000000152451046102023000153760ustar 00000000000000//! State like [`ListState`], [`TableState`] and [`ScrollbarState`] can be serialized and //! deserialized through serde. This allows saving your entire state to disk when the user exits the //! the app, and restore it again upon re-opening the app. //! This way, they get right back to where they were, without having to re-seek to their previous //! position, if that's applicable for the app at hand. //! //! **Note**: For this pattern to work easily, you need to have some toplevel struct which stores //! _only_ state and not any draw commands. //! //! **Note**: For many applications, it might be beneficial to instead keep your own state and //! instead construct the state for widgets on the fly instead, if that allows you to express you //! the semantic meaning of your state better or only fetch part of a dataset. // not too happy about the redundancy in these tests, // but if that helps readability then it's ok i guess /shrug use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::text::Line; use ratatui::widgets::{ Block, Borders, List, ListState, Row, Scrollbar, ScrollbarOrientation, ScrollbarState, Table, TableState, }; #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] struct AppState { list: ListState, table: TableState, scrollbar: ScrollbarState, } impl Default for AppState { fn default() -> Self { Self { list: ListState::default(), table: TableState::default(), scrollbar: ScrollbarState::new(10), } } } impl AppState { const fn select(&mut self, index: usize) { self.list.select(Some(index)); self.table.select_cell(Some((index, index))); self.scrollbar = self.scrollbar.position(index); } } /// Renders the list to a `TestBackend` and asserts that the result matches the expected buffer. #[track_caller] fn assert_buffer<'line, Lines>(state: &mut AppState, expected: Lines) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(21, 5); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let items = [ "awa", "banana", "Cats!!", "d20", "Echo", "Foxtrot", "Golf", "Hotel", "IwI", "Juliett", ]; let layout = Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Length(10), Constraint::Length(10), Constraint::Length(1), ]) .split(f.area()); let list = List::new(items) .highlight_symbol(">>") .block(Block::new().borders(Borders::RIGHT)); f.render_stateful_widget(list, layout[0], &mut state.list); let table = Table::new( items.into_iter().map(|i| Row::new(vec![i])), [Constraint::Length(10); 1], ) .highlight_symbol(">>"); f.render_stateful_widget(table, layout[1], &mut state.table); let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight); f.render_stateful_widget(scrollbar, layout[2], &mut state.scrollbar); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } const DEFAULT_STATE_BUFFER: [&str; 5] = [ "awa │awa ▲", "banana │banana █", "Cats!! │Cats!! ║", "d20 │d20 ║", "Echo │Echo ▼", ]; const DEFAULT_STATE_REPR: &str = r#"{ "list": { "offset": 0, "selected": null }, "table": { "offset": 0, "selected": null, "selected_column": null }, "scrollbar": { "content_length": 10, "position": 0, "viewport_content_length": 0 } }"#; #[test] fn default_state_serialize() { let mut state = AppState::default(); assert_buffer(&mut state, DEFAULT_STATE_BUFFER); let state = serde_json::to_string_pretty(&state).unwrap(); assert_eq!(state, DEFAULT_STATE_REPR); } #[test] fn default_state_deserialize() { let mut state: AppState = serde_json::from_str(DEFAULT_STATE_REPR).unwrap(); assert_buffer(&mut state, DEFAULT_STATE_BUFFER); } const SELECTED_STATE_BUFFER: [&str; 5] = [ " awa │ awa ▲", ">>banana │>>banana █", " Cats!! │ Cats!! ║", " d20 │ d20 ║", " Echo │ Echo ▼", ]; const SELECTED_STATE_REPR: &str = r#"{ "list": { "offset": 0, "selected": 1 }, "table": { "offset": 0, "selected": 1, "selected_column": 0 }, "scrollbar": { "content_length": 10, "position": 1, "viewport_content_length": 0 } }"#; #[test] fn selected_state_serialize() { let mut state = AppState::default(); state.select(1); assert_buffer(&mut state, SELECTED_STATE_BUFFER); let state = serde_json::to_string_pretty(&state).unwrap(); assert_eq!(state, SELECTED_STATE_REPR); } #[test] fn selected_state_deserialize() { let mut state: AppState = serde_json::from_str(SELECTED_STATE_REPR).unwrap(); assert_buffer(&mut state, SELECTED_STATE_BUFFER); } const SCROLLED_STATE_BUFFER: [&str; 5] = [ " Echo │ Echo ▲", " Foxtrot│ Foxtrot ║", " Golf │ Golf ║", " Hotel │ Hotel █", ">>IwI │>>IwI ▼", ]; const SCROLLED_STATE_REPR: &str = r#"{ "list": { "offset": 4, "selected": 8 }, "table": { "offset": 4, "selected": 8, "selected_column": 0 }, "scrollbar": { "content_length": 10, "position": 8, "viewport_content_length": 0 } }"#; #[test] fn scrolled_state_serialize() { let mut state = AppState::default(); state.select(8); assert_buffer(&mut state, SCROLLED_STATE_BUFFER); let state = serde_json::to_string_pretty(&state).unwrap(); assert_eq!(state, SCROLLED_STATE_REPR); } #[test] fn scrolled_state_deserialize() { let mut state: AppState = serde_json::from_str(SCROLLED_STATE_REPR).unwrap(); assert_buffer(&mut state, SCROLLED_STATE_BUFFER); } // For backwards compatibility these fields should be enough to deserialize the state. const OLD_TABLE_DESERIALIZE: &str = r#"{ "offset": 0, "selected": 1 }"#; const NEW_TABLE_DESERIALIZE: &str = r#"{ "offset": 0, "selected": 1, "selected_column": null }"#; // This test is to check for backwards compatibility with the old states. #[test] fn table_state_backwards_compatibility() { let old_state: TableState = serde_json::from_str(OLD_TABLE_DESERIALIZE).unwrap(); let new_state: TableState = serde_json::from_str(NEW_TABLE_DESERIALIZE).unwrap(); assert_eq!(old_state, new_state); } ratatui-0.30.0/tests/stateful_widget_ref_dyn.rs000064400000000000000000000044701046102023000177720ustar 00000000000000#![cfg(feature = "unstable-widget-ref")] use std::any::{Any, type_name}; use std::cell::RefCell; use pretty_assertions::assert_eq; use ratatui::widgets::StatefulWidgetRef; use ratatui_core::buffer::Buffer; use ratatui_core::layout::Rect; use ratatui_core::text::Line; use ratatui_core::widgets::Widget; trait AnyWindow: StatefulWidgetRef { fn title(&self) -> &str { type_name::() } } struct Window1; struct Window1State { pub value: u32, } impl AnyWindow for Window1 {} impl StatefulWidgetRef for Window1 { type State = dyn Any; fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let state = state.downcast_mut::().expect("window1 state"); Line::from(format!("{}, u32: {}", self.title(), state.value)).render(area, buf); } } struct Window2; struct Window2State { pub value: String, } impl AnyWindow for Window2 {} impl StatefulWidgetRef for Window2 { type State = dyn Any; fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { let state = state.downcast_mut::().expect("window2 state"); Line::from(format!("{}, String: {}", self.title(), state.value)).render(area, buf); } } type BoxedWindow = Box; type BoxedState = Box>; #[test] fn render_dyn_widgets() { let windows: Vec<(BoxedWindow, BoxedState)> = vec![ ( Box::new(Window1), Box::new(RefCell::new(Window1State { value: 32 })), ), ( Box::new(Window2), Box::new(RefCell::new(Window2State { value: "Some".to_string(), })), ), ( Box::new(Window1), Box::new(RefCell::new(Window1State { value: 42 })), ), ]; let mut buf = Buffer::empty(Rect::new(0, 0, 50, 3)); let mut area = Rect::new(0, 0, 50, 1); for (w, s) in &windows { let mut s = s.borrow_mut(); w.render_ref(area, &mut buf, &mut *s); area.y += 1; } assert_eq!( buf, Buffer::with_lines([ "stateful_widget_ref_dyn::Window1, u32: 32 ", "stateful_widget_ref_dyn::Window2, String: Some ", "stateful_widget_ref_dyn::Window1, u32: 42 ", ]) ); } ratatui-0.30.0/tests/stylize.rs000064400000000000000000000054641046102023000146010ustar 00000000000000use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::{Color, Style, Stylize}; use ratatui::widgets::{BarChart, Block, Paragraph}; #[test] fn barchart_can_be_stylized() { let barchart = BarChart::default() .on_white() .bar_style(Style::new().red()) .bar_width(2) .value_style(Style::new().green()) .label_style(Style::new().blue()) .data(&[("A", 1), ("B", 2), ("C", 3)]) .max(3); let area = Rect::new(0, 0, 9, 5); let mut terminal = Terminal::new(TestBackend::new(9, 6)).unwrap(); terminal .draw(|f| { f.render_widget(barchart, area); }) .unwrap(); let mut expected = Buffer::with_lines([ " ██ ", " ▅▅ ██ ", "▂▂ ██ ██ ", "1█ 2█ 3█ ", "A B C ", " ", ]); for y in area.y..area.height { // background for x in area.x..area.width { expected[(x, y)].set_bg(Color::White); } // bars for x in [0, 1, 3, 4, 6, 7] { expected[(x, y)].set_fg(Color::Red); } } // values for x in 0..3 { expected[(x * 3, 3)].set_fg(Color::Green); } // labels for x in 0..3 { expected[(x * 3, 4)].set_fg(Color::Blue); expected[(x * 3 + 1, 4)].set_fg(Color::Reset); } terminal.backend().assert_buffer(&expected); } #[test] fn block_can_be_stylized() -> Result<(), core::convert::Infallible> { let block = Block::bordered() .title("Title".light_blue()) .on_cyan() .cyan(); let area = Rect::new(0, 0, 8, 3); let mut terminal = Terminal::new(TestBackend::new(11, 4))?; terminal.draw(|f| { f.render_widget(block, area); })?; #[rustfmt::skip] let mut expected = Buffer::with_lines([ "┌Title─┐ ", "│ │ ", "└──────┘ ", " ", ]); for x in area.x..area.width { for y in area.y..area.height { expected[(x, y)].set_fg(Color::Cyan).set_bg(Color::Cyan); } } for x in 1..=5 { expected[(x, 0)].set_fg(Color::LightBlue); } terminal.backend().assert_buffer(&expected); Ok(()) } #[test] fn paragraph_can_be_stylized() -> Result<(), core::convert::Infallible> { let paragraph = Paragraph::new("Text".cyan()); let area = Rect::new(0, 0, 10, 1); let mut terminal = Terminal::new(TestBackend::new(10, 1))?; terminal.draw(|f| { f.render_widget(paragraph, area); })?; let mut expected = Buffer::with_lines(["Text "]); for x in 0..4 { expected[(x, 0)].set_fg(Color::Cyan); } terminal.backend().assert_buffer(&expected); Ok(()) } ratatui-0.30.0/tests/terminal.rs000064400000000000000000000356741046102023000147170ustar 00000000000000use std::error::Error; use ratatui::backend::TestBackend; use ratatui::layout::Rect; use ratatui::widgets::{Block, Paragraph, Widget}; use ratatui::{Terminal, TerminalOptions, Viewport}; #[test] fn swap_buffer_clears_prev_buffer() { let backend = TestBackend::new(100, 50); let mut terminal = Terminal::new(backend).unwrap(); terminal .current_buffer_mut() .set_string(0, 0, "Hello", ratatui::style::Style::reset()); assert_eq!(terminal.current_buffer_mut().content()[0].symbol(), "H"); terminal.swap_buffers(); assert_eq!(terminal.current_buffer_mut().content()[0].symbol(), " "); } #[test] fn terminal_draw_returns_the_completed_frame() -> Result<(), Box> { let backend = TestBackend::new(10, 10); let mut terminal = Terminal::new(backend)?; let frame = terminal.draw(|f| { let paragraph = Paragraph::new("Test"); f.render_widget(paragraph, f.area()); })?; assert_eq!(frame.buffer[(0, 0)].symbol(), "T"); assert_eq!(frame.area, Rect::new(0, 0, 10, 10)); terminal.backend_mut().resize(8, 8); let frame = terminal.draw(|f| { let paragraph = Paragraph::new("test"); f.render_widget(paragraph, f.area()); })?; assert_eq!(frame.buffer[(0, 0)].symbol(), "t"); assert_eq!(frame.area, Rect::new(0, 0, 8, 8)); Ok(()) } #[test] fn terminal_draw_increments_frame_count() -> Result<(), Box> { let backend = TestBackend::new(10, 10); let mut terminal = Terminal::new(backend)?; let frame = terminal.draw(|f| { assert_eq!(f.count(), 0); let paragraph = Paragraph::new("Test"); f.render_widget(paragraph, f.area()); })?; assert_eq!(frame.count, 0); let frame = terminal.draw(|f| { assert_eq!(f.count(), 1); let paragraph = Paragraph::new("test"); f.render_widget(paragraph, f.area()); })?; assert_eq!(frame.count, 1); let frame = terminal.draw(|f| { assert_eq!(f.count(), 2); let paragraph = Paragraph::new("test"); f.render_widget(paragraph, f.area()); })?; assert_eq!(frame.count, 2); Ok(()) } #[test] fn terminal_insert_before_moves_viewport() -> Result<(), Box> { // When we have a terminal with 5 lines, and a single line viewport, if we insert a // number of lines less than the `terminal height - viewport height` it should move // viewport down to accommodate the new lines. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; // insert_before cannot guarantee the contents of the viewport remain unharmed // by potential scrolling as such it is necessary to call draw afterwards to // redraw the contents of the viewport over the newly designated area. terminal.insert_before(2, |buf| { Paragraph::new(vec![ "------ Line 1 ------".into(), "------ Line 2 ------".into(), ]) .render(buf.area, buf); })?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.backend().assert_buffer_lines([ "------ Line 1 ------", "------ Line 2 ------", "[---- Viewport ----]", " ", " ", ]); terminal.backend().assert_scrollback_empty(); Ok(()) } #[test] #[cfg(feature = "scrolling-regions")] fn terminal_insert_before_moves_viewport_does_not_clobber() -> Result<(), Box> { // This is like terminal_insert_before_moves_viewport, except it draws first before calling // insert_before, and doesn't draw again afterwards. When using scrolling regions, we // shouldn't clobber the viewport. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.insert_before(2, |buf| { Paragraph::new(vec![ "------ Line 1 ------".into(), "------ Line 2 ------".into(), ]) .render(buf.area, buf); })?; terminal.backend().assert_scrollback_empty(); terminal.backend().assert_buffer_lines([ "------ Line 1 ------", "------ Line 2 ------", "[---- Viewport ----]", " ", " ", ]); Ok(()) } #[test] fn terminal_insert_before_scrolls_on_large_input() -> Result<(), Box> { // When we have a terminal with 5 lines, and a single line viewport, if we insert many // lines before the viewport (greater than `terminal height - viewport height`) it should // move the viewport down to the bottom of the terminal and scroll all lines above the viewport // until all have been added to the buffer. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; terminal.insert_before(5, |buf| { Paragraph::new(vec![ "------ Line 1 ------".into(), "------ Line 2 ------".into(), "------ Line 3 ------".into(), "------ Line 4 ------".into(), "------ Line 5 ------".into(), ]) .render(buf.area, buf); })?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.backend().assert_buffer_lines([ "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "[---- Viewport ----]", ]); terminal .backend() .assert_scrollback_lines(["------ Line 1 ------"]); Ok(()) } #[test] #[cfg(feature = "scrolling-regions")] fn terminal_insert_before_scrolls_on_large_input_does_not_clobber() -> Result<(), Box> { // This is like terminal_insert_scrolls_on_large_input, except it draws first before calling // insert_before, and doesn't draw again afterwards. When using scrolling regions, we // shouldn't clobber the viewport. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.insert_before(5, |buf| { Paragraph::new(vec![ "------ Line 1 ------".into(), "------ Line 2 ------".into(), "------ Line 3 ------".into(), "------ Line 4 ------".into(), "------ Line 5 ------".into(), ]) .render(buf.area, buf); })?; terminal .backend() .assert_scrollback_lines(["------ Line 1 ------"]); terminal.backend().assert_buffer_lines([ "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "[---- Viewport ----]", ]); Ok(()) } #[test] fn terminal_insert_before_scrolls_on_many_inserts() -> Result<(), Box> { // This test ensures similar behaviour to `terminal_insert_before_scrolls_on_large_input` // but covers a bug previously present whereby multiple small insertions // (less than `terminal height - viewport height`) would have disparate behaviour to one large // insertion. This was caused by an undocumented cap on the height to be inserted, which has now // been removed. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 2 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 3 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 4 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 5 ------".into()]).render(buf.area, buf); })?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.backend().assert_buffer_lines([ "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "[---- Viewport ----]", ]); terminal .backend() .assert_scrollback_lines(["------ Line 1 ------"]); Ok(()) } #[test] #[cfg(feature = "scrolling-regions")] fn terminal_insert_before_scrolls_on_many_inserts_does_not_clobber() -> Result<(), Box> { // This is like terminal_insert_before_scrolls_on_many_inserts, except it draws first before // calling insert_before, and doesn't draw again afterwards. When using scrolling regions, we // shouldn't clobber the viewport. let backend = TestBackend::new(20, 5); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(1), }, )?; terminal.draw(|f| { let paragraph = Paragraph::new("[---- Viewport ----]"); f.render_widget(paragraph, f.area()); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 2 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 3 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 4 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 5 ------".into()]).render(buf.area, buf); })?; terminal .backend() .assert_scrollback_lines(["------ Line 1 ------"]); terminal.backend().assert_buffer_lines([ "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "[---- Viewport ----]", ]); Ok(()) } #[test] fn terminal_insert_before_large_viewport() -> Result<(), Box> { // This test covers a bug previously present whereby doing an insert_before when the // viewport covered the entire screen would cause a panic. let backend = TestBackend::new(20, 3); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(3), }, )?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(3, |buf| { Paragraph::new(vec![ "------ Line 2 ------".into(), "------ Line 3 ------".into(), "------ Line 4 ------".into(), ]) .render(buf.area, buf); })?; terminal.insert_before(7, |buf| { Paragraph::new(vec![ "------ Line 5 ------".into(), "------ Line 6 ------".into(), "------ Line 7 ------".into(), "------ Line 8 ------".into(), "------ Line 9 ------".into(), "----- Line 10 ------".into(), "----- Line 11 ------".into(), ]) .render(buf.area, buf); })?; terminal.draw(|f| { let paragraph = Paragraph::new("Viewport") .centered() .block(Block::bordered()); f.render_widget(paragraph, f.area()); })?; terminal.backend().assert_buffer_lines([ "┌──────────────────┐", "│ Viewport │", "└──────────────────┘", ]); terminal.backend().assert_scrollback_lines([ "------ Line 1 ------", "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "------ Line 6 ------", "------ Line 7 ------", "------ Line 8 ------", "------ Line 9 ------", "----- Line 10 ------", "----- Line 11 ------", ]); Ok(()) } #[test] #[cfg(feature = "scrolling-regions")] fn terminal_insert_before_large_viewport_does_not_clobber() -> Result<(), Box> { // This is like terminal_insert_before_large_viewport, except it draws first before calling // insert_before, and doesn't draw again afterwards. When using scrolling regions, we shouldn't // clobber the viewport. let backend = TestBackend::new(20, 3); let mut terminal = Terminal::with_options( backend, TerminalOptions { viewport: Viewport::Inline(3), }, )?; terminal.draw(|f| { let paragraph = Paragraph::new("Viewport") .centered() .block(Block::bordered()); f.render_widget(paragraph, f.area()); })?; terminal.insert_before(1, |buf| { Paragraph::new(vec!["------ Line 1 ------".into()]).render(buf.area, buf); })?; terminal.insert_before(3, |buf| { Paragraph::new(vec![ "------ Line 2 ------".into(), "------ Line 3 ------".into(), "------ Line 4 ------".into(), ]) .render(buf.area, buf); })?; terminal.insert_before(7, |buf| { Paragraph::new(vec![ "------ Line 5 ------".into(), "------ Line 6 ------".into(), "------ Line 7 ------".into(), "------ Line 8 ------".into(), "------ Line 9 ------".into(), "----- Line 10 ------".into(), "----- Line 11 ------".into(), ]) .render(buf.area, buf); })?; terminal.backend().assert_buffer_lines([ "┌──────────────────┐", "│ Viewport │", "└──────────────────┘", ]); terminal.backend().assert_scrollback_lines([ "------ Line 1 ------", "------ Line 2 ------", "------ Line 3 ------", "------ Line 4 ------", "------ Line 5 ------", "------ Line 6 ------", "------ Line 7 ------", "------ Line 8 ------", "------ Line 9 ------", "----- Line 10 ------", "----- Line 11 ------", ]); Ok(()) } ratatui-0.30.0/tests/widgets_barchart.rs000064400000000000000000000077271046102023000164160ustar 00000000000000use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::style::{Color, Style}; use ratatui::widgets::{Bar, BarChart, BarGroup, Block}; // check that bars fill up correctly up to max value #[test] fn widgets_barchart_not_full_below_max_value() { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let barchart = BarChart::default() .block(Block::bordered()) .data(&[("empty", 0), ("half", 50), ("almost", 99), ("full", 100)]) .max(100) .bar_width(7) .bar_gap(0); f.render_widget(barchart, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines([ "┌────────────────────────────┐", "│ ▇▇▇▇▇▇▇███████│", "│ ██████████████│", "│ ██████████████│", "│ ▄▄▄▄▄▄▄██████████████│", "│ █████████████████████│", "│ █████████████████████│", "│ ██50█████99█████100██│", "│ empty half almost full │", "└────────────────────────────┘", ]); } #[test] fn widgets_barchart_group() { const TERMINAL_HEIGHT: u16 = 11; let backend = TestBackend::new(35, TERMINAL_HEIGHT); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let barchart = BarChart::default() .block(Block::bordered()) .data( BarGroup::default().label("Mar").bars(&[ Bar::default() .value(10) .label("C1") .style(Style::default().fg(Color::Red)) .value_style(Style::default().fg(Color::Blue)), Bar::default() .value(20) .style(Style::default().fg(Color::Green)) .text_value("20M"), ]), ) .data(&vec![("C1", 50), ("C2", 40)]) .data(&[("C1", 60), ("C2", 90)]) .data(&[("xx", 10), ("xx", 10)]) .group_gap(2) .bar_width(4) .bar_gap(1); f.render_widget(barchart, f.area()); }) .unwrap(); let mut expected = Buffer::with_lines([ "┌─────────────────────────────────┐", "│ ████│", "│ ████│", "│ ▅▅▅▅ ████│", "│ ▇▇▇▇ ████ ████│", "│ ████ ████ ████ ████│", "│ ▄▄▄▄ ████ ████ ████ ████│", "│▆10▆ 20M█ █50█ █40█ █60█ █90█│", "│ C1 C1 C2 C1 C2 │", "│Mar │", "└─────────────────────────────────┘", ]); for y in 1..(TERMINAL_HEIGHT - 3) { for x in 1..5 { expected[(x, y)].set_fg(Color::Red); expected[(x + 5, y)].set_fg(Color::Green); } } expected[(2, 7)].set_fg(Color::Blue); expected[(3, 7)].set_fg(Color::Blue); terminal.backend().assert_buffer(&expected); } ratatui-0.30.0/tests/widgets_block.rs000064400000000000000000000407261046102023000157160ustar 00000000000000use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::{Alignment, Rect}; use ratatui::style::{Color, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders}; use rstest::rstest; #[test] fn widgets_block_renders() { let backend = TestBackend::new(10, 10); let mut terminal = Terminal::new(backend).unwrap(); let block = Block::bordered().title(Span::styled("Title", Style::default().fg(Color::LightBlue))); terminal .draw(|frame| frame.render_widget(block, Rect::new(0, 0, 8, 8))) .unwrap(); let mut expected = Buffer::with_lines([ "┌Title─┐ ", "│ │ ", "│ │ ", "│ │ ", "│ │ ", "│ │ ", "│ │ ", "└──────┘ ", " ", " ", ]); for x in 1..=5 { expected[(x, 0)].set_fg(Color::LightBlue); } terminal.backend().assert_buffer(&expected); } #[test] fn widgets_block_titles_overlap() { #[track_caller] fn test_case<'line, Lines>(block: Block, area: Rect, expected: Lines) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(area.width, area.height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|frame| frame.render_widget(block, area)) .unwrap(); terminal.backend().assert_buffer_lines(expected); } // Center overrides left titles test_case( Block::new() .title(Line::from("aaaaa").left_aligned()) .title(Line::from("bbb").centered()) .title(Line::from("ccc").right_aligned()), Rect::new(0, 0, 10, 1), ["aaabbb ccc"], ); // Right alignment overrides the center alignment which overrides the left alignment test_case( Block::new() .title(Line::from("aaaaa").left_aligned()) .title(Line::from("bbbbb").centered()) .title(Line::from("ccccc").right_aligned()), Rect::new(0, 0, 11, 1), ["aaabbbccccc"], ); // Center alignment overwrites multiple left alignment, right alignment overwrites center // alignment test_case( Block::new() .title(Line::from("aaaaa").left_aligned()) .title(Line::from("aaaaa").left_aligned()) .title(Line::from("bbbbb").centered()) .title(Line::from("ccccc").right_aligned()), Rect::new(0, 0, 11, 1), ["aaabbbccccc"], ); // Right alignment overrides the center alignment test_case( Block::new() .title(Line::from("bbbbb").centered()) .title(Line::from("ccccccccccc").right_aligned()), Rect::new(0, 0, 11, 1), ["ccccccccccc"], ); } #[test] fn widgets_block_renders_on_small_areas() { #[track_caller] fn test_case(block: Block, area: Rect, expected: &Buffer) { let backend = TestBackend::new(area.width, area.height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|frame| frame.render_widget(block, area)) .unwrap(); terminal.backend().assert_buffer(expected); } let one_cell_test_cases = [ (Borders::NONE, "T"), (Borders::LEFT, "│"), (Borders::TOP, "T"), (Borders::RIGHT, "│"), (Borders::BOTTOM, "T"), (Borders::ALL, "┌"), ]; for (borders, symbol) in one_cell_test_cases { test_case( Block::new().borders(borders).title("Test"), Rect::new(0, 0, 0, 0), &Buffer::empty(Rect::new(0, 0, 0, 0)), ); test_case( Block::new().borders(borders).title("Test"), Rect::new(0, 0, 1, 0), &Buffer::empty(Rect::new(0, 0, 1, 0)), ); test_case( Block::new().borders(borders).title("Test"), Rect::new(0, 0, 0, 1), &Buffer::empty(Rect::new(0, 0, 0, 1)), ); test_case( Block::new().borders(borders).title("Test"), Rect::new(0, 0, 1, 1), &Buffer::with_lines([symbol]), ); } test_case( Block::new().borders(Borders::LEFT).title("Test"), Rect::new(0, 0, 4, 1), &Buffer::with_lines(["│Tes"]), ); test_case( Block::new().borders(Borders::RIGHT).title("Test"), Rect::new(0, 0, 4, 1), &Buffer::with_lines(["Tes│"]), ); test_case( Block::new().borders(Borders::RIGHT).title("Test"), Rect::new(0, 0, 4, 1), &Buffer::with_lines(["Tes│"]), ); test_case( Block::new() .borders(Borders::LEFT | Borders::RIGHT) .title("Test"), Rect::new(0, 0, 4, 1), &Buffer::with_lines(["│Te│"]), ); test_case( Block::new().borders(Borders::TOP).title("Test"), Rect::new(0, 0, 4, 1), &Buffer::with_lines(["Test"]), ); test_case( Block::new().borders(Borders::TOP).title("Test"), Rect::new(0, 0, 5, 1), &Buffer::with_lines(["Test─"]), ); test_case( Block::new() .borders(Borders::LEFT | Borders::TOP) .title("Test"), Rect::new(0, 0, 5, 1), &Buffer::with_lines(["┌Test"]), ); test_case( Block::new() .borders(Borders::LEFT | Borders::TOP) .title("Test"), Rect::new(0, 0, 6, 1), &Buffer::with_lines(["┌Test─"]), ); } #[rstest] #[case::left_with_all_borders(Alignment::Left, Borders::ALL, [ " ┌Title──────┐ ", " │ │ ", " └───────────┘ ", ])] #[case::left_without_top_border(Alignment::Left, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │Title │ ", " │ │ ", " └───────────┘ ", ])] #[case::left_without_left_border(Alignment::Left, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " Title───────┐ ", " │ ", " ────────────┘ ", ])] #[case::left_without_right_border(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌Title─────── ", " │ ", " └──────────── ", ])] #[case::left_without_borders(Alignment::Left, Borders::NONE, [ " Title ", " ", " ", ])] #[case::center_with_all_borders(Alignment::Center, Borders::ALL, [ " ┌───Title───┐ ", " │ │ ", " └───────────┘ ", ])] #[case::center_without_top_border(Alignment::Center, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │ Title │ ", " │ │ ", " └───────────┘ ", ])] #[case::center_without_left_border(Alignment::Center, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ───Title────┐ ", " │ ", " ────────────┘ ", ])] #[case::center_without_right_border(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌───Title──── ", " │ ", " └──────────── ", ])] #[case::center_without_borders(Alignment::Center, Borders::NONE, [ " Title ", " ", " ", ])] #[case::right_with_all_borders(Alignment::Right, Borders::ALL, [ " ┌──────Title┐ ", " │ │ ", " └───────────┘ ", ])] #[case::right_without_top_border(Alignment::Right, Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │ Title│ ", " │ │ ", " └───────────┘ ", ])] #[case::right_without_left_border(Alignment::Right, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ───────Title┐ ", " │ ", " ────────────┘ ", ])] #[case::right_without_right_border(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌───────Title ", " │ ", " └──────────── ", ])] #[case::right_without_borders(Alignment::Right, Borders::NONE, [ " Title ", " ", " ", ])] fn widgets_block_title_alignment_top<'line, Lines>( #[case] alignment: Alignment, #[case] borders: Borders, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(15, 3); let mut terminal = Terminal::new(backend).unwrap(); let block1 = Block::new() .borders(borders) .title(Line::from("Title").alignment(alignment)); let block2 = Block::new() .borders(borders) .title_alignment(alignment) .title("Title"); let area = Rect::new(1, 0, 13, 3); let expected = Buffer::with_lines(expected); for block in [block1, block2] { terminal .draw(|frame| frame.render_widget(block, area)) .unwrap(); terminal.backend().assert_buffer(&expected); } } #[rstest] #[case::left(Alignment::Left, Borders::ALL, [ " ┌───────────┐ ", " │ │ ", " └Title──────┘ ", ])] #[case::left(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::RIGHT, [ " ┌───────────┐ ", " │ │ ", " │Title │ ", ])] #[case::left(Alignment::Left, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ────────────┐ ", " │ ", " Title───────┘ ", ])] #[case::left(Alignment::Left, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌──────────── ", " │ ", " └Title─────── ", ])] #[case::left(Alignment::Left, Borders::NONE, [ " ", " ", " Title ", ])] #[case::left(Alignment::Center, Borders::ALL, [ " ┌───────────┐ ", " │ │ ", " └───Title───┘ ", ])] #[case::left(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::RIGHT, [ " ┌───────────┐ ", " │ │ ", " │ Title │ ", ])] #[case::left(Alignment::Center, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ────────────┐ ", " │ ", " ───Title────┘ ", ])] #[case::left(Alignment::Center, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌──────────── ", " │ ", " └───Title──── ", ])] #[case::left(Alignment::Center, Borders::NONE, [ " ", " ", " Title ", ])] #[case::left(Alignment::Right, Borders::ALL, [ " ┌───────────┐ ", " │ │ ", " └──────Title┘ ", ])] #[case::left(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::RIGHT, [ " ┌───────────┐ ", " │ │ ", " │ Title│ ", ])] #[case::left(Alignment::Right, Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ────────────┐ ", " │ ", " ───────Title┘ ", ])] #[case::left(Alignment::Right, Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌──────────── ", " │ ", " └───────Title ", ])] #[case::left(Alignment::Right, Borders::NONE, [ " ", " ", " Title ", ])] fn widgets_block_title_alignment_bottom<'line, Lines>( #[case] alignment: Alignment, #[case] borders: Borders, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(15, 3); let mut terminal = Terminal::new(backend).unwrap(); let title = Line::from("Title").alignment(alignment); let block = Block::default().title_bottom(title).borders(borders); let area = Rect::new(1, 0, 13, 3); terminal .draw(|frame| frame.render_widget(block, area)) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::left_with_all_borders(Line::from("foo"), Line::from("bar"), Borders::ALL, [ " ┌foo─bar────┐ ", " │ │ ", " └───────────┘ ", ])] #[case::left_without_top_border(Line::from("foo"), Line::from("bar"), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │foo bar │ ", " │ │ ", " └───────────┘ ", ])] #[case::left_without_left_border(Line::from("foo"), Line::from("bar"), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " foo─bar─────┐ ", " │ ", " ────────────┘ ", ])] #[case::left_without_right_border(Line::from("foo"), Line::from("bar"), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌foo─bar───── ", " │ ", " └──────────── ", ])] #[case::left_without_borders(Line::from("foo"), Line::from("bar"), Borders::NONE, [ " foo bar ", " ", " ", ])] #[case::center_with_borders(Line::from("foo").centered(), Line::from("bar").centered(), Borders::ALL, [ " ┌──foo─bar──┐ ", " │ │ ", " └───────────┘ ", ])] #[case::center_without_top_border(Line::from("foo").centered(), Line::from("bar").centered(), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │ foo bar │ ", " │ │ ", " └───────────┘ ", ])] #[case::center_without_left_border(Line::from("foo").centered(), Line::from("bar").centered(), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ──foo─bar───┐ ", " │ ", " ────────────┘ ", ])] #[case::center_without_right_border(Line::from("foo").centered(), Line::from("bar").centered(), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌──foo─bar─── ", " │ ", " └──────────── ", ])] #[case::center_without_borders(Line::from("foo").centered(), Line::from("bar").centered(), Borders::NONE, [ " foo bar ", " ", " ", ])] #[case::right_with_all_borders(Line::from("foo").right_aligned(), Line::from("bar").right_aligned(), Borders::ALL, [ " ┌────foo─bar┐ ", " │ │ ", " └───────────┘ ", ])] #[case::right_without_top_border(Line::from("foo").right_aligned(), Line::from("bar").right_aligned(), Borders::LEFT | Borders::BOTTOM | Borders::RIGHT, [ " │ foo bar│ ", " │ │ ", " └───────────┘ ", ])] #[case::right_without_left_border(Line::from("foo").right_aligned(), Line::from("bar").right_aligned(), Borders::TOP | Borders::RIGHT | Borders::BOTTOM, [ " ─────foo─bar┐ ", " │ ", " ────────────┘ ", ])] #[case::right_without_right_border(Line::from("foo").right_aligned(), Line::from("bar").right_aligned(), Borders::LEFT | Borders::TOP | Borders::BOTTOM, [ " ┌─────foo─bar ", " │ ", " └──────────── ", ])] #[case::right_without_borders(Line::from("foo").right_aligned(), Line::from("bar").right_aligned(), Borders::NONE, [ " foo bar ", " ", " ", ])] fn widgets_block_multiple_titles<'line, Lines>( #[case] title_a: Line, #[case] title_b: Line, #[case] borders: Borders, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(15, 3); let mut terminal = Terminal::new(backend).unwrap(); let block = Block::default() .title(title_a) .title(title_b) .borders(borders); let area = Rect::new(1, 0, 13, 3); terminal .draw(|f| { f.render_widget(block, area); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } ratatui-0.30.0/tests/widgets_calendar.rs000064400000000000000000000060671046102023000163750ustar 00000000000000#![cfg(feature = "widget-calendar")] use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::style::Style; use ratatui::widgets::Widget; use ratatui::widgets::calendar::{CalendarEventStore, Monthly}; use time::{Date, Month}; #[track_caller] fn test_render(widget: W, width: u16, height: u16, expected: &Buffer) { let backend = TestBackend::new(width, height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| f.render_widget(widget, f.area())) .unwrap(); terminal.backend().assert_buffer(expected); } #[test] fn days_layout() { let c = Monthly::new( Date::from_calendar_date(2023, Month::January, 1).unwrap(), CalendarEventStore::default(), ); let expected = Buffer::with_lines([ " 1 2 3 4 5 6 7", " 8 9 10 11 12 13 14", " 15 16 17 18 19 20 21", " 22 23 24 25 26 27 28", " 29 30 31", ]); test_render(c, 21, 5, &expected); } #[test] fn days_layout_show_surrounding() { let c = Monthly::new( Date::from_calendar_date(2023, Month::December, 1).unwrap(), CalendarEventStore::default(), ) .show_surrounding(Style::default()); let expected = Buffer::with_lines([ " 26 27 28 29 30 1 2", " 3 4 5 6 7 8 9", " 10 11 12 13 14 15 16", " 17 18 19 20 21 22 23", " 24 25 26 27 28 29 30", " 31 1 2 3 4 5 6", ]); test_render(c, 21, 6, &expected); } #[test] fn show_month_header() { let c = Monthly::new( Date::from_calendar_date(2023, Month::January, 1).unwrap(), CalendarEventStore::default(), ) .show_month_header(Style::default()); let expected = Buffer::with_lines([ " January 2023 ", " 1 2 3 4 5 6 7", " 8 9 10 11 12 13 14", " 15 16 17 18 19 20 21", " 22 23 24 25 26 27 28", " 29 30 31", ]); test_render(c, 21, 6, &expected); } #[test] fn show_weekdays_header() { let c = Monthly::new( Date::from_calendar_date(2023, Month::January, 1).unwrap(), CalendarEventStore::default(), ) .show_weekdays_header(Style::default()); let expected = Buffer::with_lines([ " Su Mo Tu We Th Fr Sa", " 1 2 3 4 5 6 7", " 8 9 10 11 12 13 14", " 15 16 17 18 19 20 21", " 22 23 24 25 26 27 28", " 29 30 31", ]); test_render(c, 21, 6, &expected); } #[test] fn show_combo() { let c = Monthly::new( Date::from_calendar_date(2023, Month::January, 1).unwrap(), CalendarEventStore::default(), ) .show_weekdays_header(Style::default()) .show_month_header(Style::default()) .show_surrounding(Style::default()); let expected = Buffer::with_lines([ " January 2023 ", " Su Mo Tu We Th Fr Sa", " 1 2 3 4 5 6 7", " 8 9 10 11 12 13 14", " 15 16 17 18 19 20 21", " 22 23 24 25 26 27 28", " 29 30 31 1 2 3 4", ]); test_render(c, 21, 7, &expected); } ratatui-0.30.0/tests/widgets_canvas.rs000064400000000000000000000023031046102023000160640ustar 00000000000000use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::style::{Color, Style}; use ratatui::text::Span; use ratatui::widgets::canvas::Canvas; #[test] fn widgets_canvas_draw_labels() { let backend = TestBackend::new(5, 5); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let label = String::from("test"); let canvas = Canvas::default() .background_color(Color::Yellow) .x_bounds([0.0, 5.0]) .y_bounds([0.0, 5.0]) .paint(|ctx| { ctx.print( 0.0, 0.0, Span::styled(label.clone(), Style::default().fg(Color::Blue)), ); }); f.render_widget(canvas, f.area()); }) .unwrap(); let mut expected = Buffer::with_lines(["", "", "", "", "test "]); for row in 0..5 { for col in 0..5 { expected[(col, row)].set_bg(Color::Yellow); } } for col in 0..4 { expected[(col, 4)].set_fg(Color::Blue); } terminal.backend().assert_buffer(&expected); } ratatui-0.30.0/tests/widgets_chart.rs000064400000000000000000000262141046102023000157210ustar 00000000000000use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::{Alignment, Rect}; use ratatui::style::{Color, Style}; use ratatui::text::{self, Span}; use ratatui::widgets::GraphType::Line; use ratatui::widgets::{Axis, Block, Chart, Dataset}; use ratatui::{Terminal, symbols}; use rstest::rstest; fn create_labels<'a>(labels: &'a [&'a str]) -> Vec> { labels.iter().map(|l| Span::from(*l)).collect() } #[track_caller] fn axis_test_case<'line, Lines>( width: u16, height: u16, x_axis: Axis, y_axis: Axis, expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(width, height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let chart = Chart::new(vec![]).x_axis(x_axis).y_axis(y_axis); f.render_widget(chart, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case(0, 0)] #[case(0, 1)] #[case(1, 0)] #[case(1, 1)] #[case(2, 2)] fn widgets_chart_can_render_on_small_areas(#[case] width: u16, #[case] height: u16) { let backend = TestBackend::new(width, height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let datasets = vec![ Dataset::default() .marker(symbols::Marker::Braille) .style(Style::default().fg(Color::Magenta)) .data(&[(0.0, 0.0)]), ]; let chart = Chart::new(datasets) .block(Block::bordered().title("Plot")) .x_axis( Axis::default() .bounds([0.0, 0.0]) .labels(create_labels(&["0.0", "1.0"])), ) .y_axis( Axis::default() .bounds([0.0, 0.0]) .labels(create_labels(&["0.0", "1.0"])), ); f.render_widget(chart, f.area()); }) .unwrap(); } #[rstest] #[case( Some(("AAAA", "B")), None, Alignment::Left, vec![ " ", " ", " ", " ───────", "AAA B", ], )] #[case( Some(("A", "BBBB")), None, Alignment::Left, vec![ " ", " ", " ", " ─────────", "A BBBB", ], )] #[case( Some(("AAAAAAAAAAA", "B")), None, Alignment::Left, vec![ " ", " ", " ", " ───────", "AAA B", ], )] #[case( Some(("A", "B")), Some(("CCCCCCC", "D")), Alignment::Left, vec![ "D │ ", " │ ", "CCC│ ", " └──────", " A B", ], )] #[case( Some(("AAAAAAAAAA", "B")), Some(("C", "D")), Alignment::Center, vec![ "D │ ", " │ ", "C │ ", " └──────", "AAAAAAA B", ], )] #[case( Some(("AAAAAAA", "B")), Some(("C", "D")), Alignment::Right, vec![ "D│ ", " │ ", "C│ ", " └────────", " AAAAA B", ], )] #[case( Some(("AAAAAAA", "BBBBBBB")), Some(("C", "D")), Alignment::Right, vec![ "D│ ", " │ ", "C│ ", " └────────", " AAAAABBBB", ], )] fn widgets_chart_handles_long_labels<'line, Lines>( #[case] x_labels: Option<(&str, &str)>, #[case] y_labels: Option<(&str, &str)>, #[case] x_alignment: Alignment, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let mut x_axis = Axis::default().bounds([0.0, 1.0]); if let Some((left_label, right_label)) = x_labels { x_axis = x_axis .labels([left_label, right_label]) .labels_alignment(x_alignment); } let mut y_axis = Axis::default().bounds([0.0, 1.0]); if let Some((left_label, right_label)) = y_labels { y_axis = y_axis.labels([left_label, right_label]); } axis_test_case(10, 5, x_axis, y_axis, expected); } #[rstest] #[case::left( Alignment::Left, vec![ " ", " ", " ", " ───────", "AAA B C", ], )] #[case::center( Alignment::Center, vec![ " ", " ", " ", " ────────", "AAAA B C", ], )] #[case::right( Alignment::Right, vec![ " ", " ", " ", "──────────", "AAA B C", ], )] fn widgets_chart_handles_x_axis_labels_alignments<'line, Lines>( #[case] y_alignment: Alignment, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let x_axis = Axis::default() .labels(["AAAA", "B", "C"]) .labels_alignment(y_alignment); let y_axis = Axis::default(); axis_test_case(10, 5, x_axis, y_axis, expected); } #[rstest] #[case::left(Alignment::Left, [ "D │ ", " │ ", "C │ ", " └───────────────", "AAAAA B", ])] #[case::center(Alignment::Center, [ " D │ ", " │ ", " C │ ", " └───────────────", "AAAAA B", ])] #[case::right(Alignment::Right, [ " D│ ", " │ ", " C│ ", " └───────────────", "AAAAA B", ])] fn widgets_chart_handles_y_axis_labels_alignments<'line, Lines>( #[case] y_alignment: Alignment, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let x_axis = Axis::default().labels(create_labels(&["AAAAA", "B"])); let y_axis = Axis::default() .labels(create_labels(&["C", "D"])) .labels_alignment(y_alignment); axis_test_case(20, 5, x_axis, y_axis, expected); } #[test] fn widgets_chart_can_have_axis_with_zero_length_bounds() { let backend = TestBackend::new(100, 100); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let datasets = vec![ Dataset::default() .marker(symbols::Marker::Braille) .style(Style::default().fg(Color::Magenta)) .data(&[(0.0, 0.0)]), ]; let chart = Chart::new(datasets) .block(Block::bordered().title("Plot")) .x_axis( Axis::default() .bounds([0.0, 0.0]) .labels(create_labels(&["0.0", "1.0"])), ) .y_axis( Axis::default() .bounds([0.0, 0.0]) .labels(create_labels(&["0.0", "1.0"])), ); f.render_widget( chart, Rect { x: 0, y: 0, width: 100, height: 100, }, ); }) .unwrap(); } #[test] fn widgets_chart_handles_overflows() { let backend = TestBackend::new(80, 30); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let datasets = vec![ Dataset::default() .marker(symbols::Marker::Braille) .style(Style::default().fg(Color::Magenta)) .data(&[ (1_588_298_471.0, 1.0), (1_588_298_473.0, 0.0), (1_588_298_496.0, 1.0), ]), ]; let chart = Chart::new(datasets) .block(Block::bordered().title("Plot")) .x_axis( Axis::default() .bounds([1_588_298_471.0, 1_588_992_600.0]) .labels(create_labels(&["1588298471.0", "1588992600.0"])), ) .y_axis( Axis::default() .bounds([0.0, 1.0]) .labels(create_labels(&["0.0", "1.0"])), ); f.render_widget( chart, Rect { x: 0, y: 0, width: 80, height: 30, }, ); }) .unwrap(); } #[test] fn widgets_chart_can_have_empty_datasets() { let backend = TestBackend::new(100, 100); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let datasets = vec![Dataset::default().data(&[]).graph_type(Line)]; let chart = Chart::new(datasets) .block(Block::bordered().title("Empty Dataset With Line")) .x_axis( Axis::default() .bounds([0.0, 0.0]) .labels(create_labels(&["0.0", "1.0"])), ) .y_axis( Axis::default() .bounds([0.0, 1.0]) .labels(create_labels(&["0.0", "1.0"])), ); f.render_widget( chart, Rect { x: 0, y: 0, width: 100, height: 100, }, ); }) .unwrap(); } #[test] fn widgets_chart_top_line_styling_is_correct() { let backend = TestBackend::new(9, 5); let mut terminal = Terminal::new(backend).unwrap(); let title_style = Style::default().fg(Color::Red).bg(Color::LightRed); let data_style = Style::default().fg(Color::Blue); terminal .draw(|f| { let data: [(f64, f64); 2] = [(0.0, 1.0), (1.0, 1.0)]; let widget = Chart::new(vec![ Dataset::default() .data(&data) .graph_type(ratatui::widgets::GraphType::Line) .style(data_style), ]) .y_axis( Axis::default() .title(Span::styled("abc", title_style)) .bounds([0.0, 1.0]) .labels(create_labels(&["a", "b"])), ) .x_axis(Axis::default().bounds([0.0, 1.0])); f.render_widget(widget, f.area()); }) .unwrap(); let mut expected = Buffer::with_lines([ "b│abc••••", " │ ", " │ ", " │ ", "a│ ", ]); expected.set_style(Rect::new(2, 0, 3, 1), title_style); expected.set_style(Rect::new(5, 0, 4, 1), data_style); terminal.backend().assert_buffer(&expected); } ratatui-0.30.0/tests/widgets_gauge.rs000064400000000000000000000230721046102023000157070ustar 00000000000000use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::Span; use ratatui::widgets::{Block, Gauge, LineGauge}; use ratatui::{Terminal, symbols}; #[test] fn widgets_gauge_renders() { let backend = TestBackend::new(40, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(f.area()); let gauge = Gauge::default() .block(Block::bordered().title("Percentage")) .gauge_style(Style::default().bg(Color::Blue).fg(Color::Red)) .use_unicode(true) .percent(43); f.render_widget(gauge, chunks[0]); let gauge = Gauge::default() .block(Block::bordered().title("Ratio")) .gauge_style(Style::default().bg(Color::Blue).fg(Color::Red)) .use_unicode(true) .ratio(0.511_313_934_313_1); f.render_widget(gauge, chunks[1]); }) .unwrap(); let mut expected = Buffer::with_lines([ " ", " ", " ┌Percentage────────────────────────┐ ", " │██████████████▋43% │ ", " └──────────────────────────────────┘ ", " ┌Ratio─────────────────────────────┐ ", " │███████████████51% │ ", " └──────────────────────────────────┘ ", " ", " ", ]); expected.set_style(Rect::new(3, 3, 34, 1), Style::new().red().on_blue()); expected.set_style(Rect::new(3, 6, 15, 1), Style::new().red().on_blue()); // Note that filled part of the gauge only covers the 5 and the 1, not the % symbol expected.set_style(Rect::new(18, 6, 2, 1), Style::new().blue().on_red()); expected.set_style(Rect::new(20, 6, 17, 1), Style::new().red().on_blue()); terminal.backend().assert_buffer(&expected); } #[test] fn widgets_gauge_renders_no_unicode() { let backend = TestBackend::new(40, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(2) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) .split(f.area()); let gauge = Gauge::default() .block(Block::bordered().title("Percentage")) .percent(43) .use_unicode(false); f.render_widget(gauge, chunks[0]); let gauge = Gauge::default() .block(Block::bordered().title("Ratio")) .ratio(0.211_313_934_313_1) .use_unicode(false); f.render_widget(gauge, chunks[1]); }) .unwrap(); terminal.backend().assert_buffer_lines([ " ", " ", " ┌Percentage────────────────────────┐ ", " │███████████████43% │ ", " └──────────────────────────────────┘ ", " ┌Ratio─────────────────────────────┐ ", " │███████ 21% │ ", " └──────────────────────────────────┘ ", " ", " ", ]); } #[test] fn widgets_gauge_applies_styles() { let backend = TestBackend::new(12, 5); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let gauge = Gauge::default() .block( Block::bordered().title(Span::styled("Test", Style::default().fg(Color::Red))), ) .gauge_style(Style::default().fg(Color::Blue).bg(Color::Red)) .percent(43) .label(Span::styled( "43%", Style::default() .fg(Color::Green) .add_modifier(Modifier::BOLD), )); f.render_widget(gauge, f.area()); }) .unwrap(); let mut expected = Buffer::with_lines([ "┌Test──────┐", "│████ │", "│███43% │", "│████ │", "└──────────┘", ]); // title expected.set_style(Rect::new(1, 0, 4, 1), Style::default().fg(Color::Red)); // gauge area expected.set_style( Rect::new(1, 1, 10, 3), Style::default().fg(Color::Blue).bg(Color::Red), ); // filled area expected.set_style( Rect::new(1, 1, 4, 3), Style::default().fg(Color::Blue).bg(Color::Red), ); // label (foreground and modifier from label style) expected.set_style( Rect::new(4, 2, 1, 1), Style::default() .fg(Color::Green) // "4" is in the filled area so background is gauge_style foreground .bg(Color::Blue) .add_modifier(Modifier::BOLD), ); expected.set_style( Rect::new(5, 2, 2, 1), Style::default() .fg(Color::Green) // "3%" is not in the filled area so background is gauge_style background .bg(Color::Red) .add_modifier(Modifier::BOLD), ); terminal.backend().assert_buffer(&expected); } #[test] fn widgets_gauge_supports_large_labels() { let backend = TestBackend::new(10, 1); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let gauge = Gauge::default() .percent(43) .label("43333333333333333333333333333%"); f.render_widget(gauge, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(["4333333333"]); } #[test] fn widgets_line_gauge_renders() { let backend = TestBackend::new(20, 6); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let gauge = LineGauge::default() .filled_style(Style::default().fg(Color::Green)) .unfilled_style(Style::default().fg(Color::White)) .ratio(0.43); f.render_widget( gauge, Rect { x: 0, y: 0, width: 20, height: 1, }, ); // custom (same) symbols for filled and unfilled parts let gauge = LineGauge::default() .block(Block::bordered().title("Gauge 2")) .filled_style(Style::default().fg(Color::Green)) .filled_symbol(symbols::line::THICK_HORIZONTAL) .unfilled_symbol(symbols::line::THICK_HORIZONTAL) .ratio(0.211_313_934_313_1); f.render_widget( gauge, Rect { x: 0, y: 1, width: 20, height: 3, }, ); // default symbol for filled part, but empty for unfilled part let gauge = LineGauge::default().unfilled_symbol(" ").ratio(0.50); f.render_widget( gauge, Rect { x: 0, y: 4, width: 20, height: 1, }, ); // different custom symbols for filled unfilled parts let gauge = LineGauge::default() .filled_symbol("█") // similar to `symbols::bar::FULL` .unfilled_symbol("░") // similar to `symbols::shade::LIGHT` .ratio(0.80); f.render_widget( gauge, Rect { x: 0, y: 5, width: 20, height: 1, }, ); }) .unwrap(); let mut expected = Buffer::with_lines([ " 43% ───────────────", "┌Gauge 2───────────┐", "│ 21% ━━━━━━━━━━━━━│", "└──────────────────┘", " 50% ─────── ", " 80% ████████████░░░", ]); for col in 5..11 { expected[(col, 0)].set_fg(Color::Green); } for col in 11..20 { expected[(col, 0)].set_fg(Color::White); } for col in 6..8 { expected[(col, 2)].set_fg(Color::Green); } terminal.backend().assert_buffer(&expected); } ratatui-0.30.0/tests/widgets_list.rs000064400000000000000000000270071046102023000155740ustar 00000000000000use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::{Color, Style}; use ratatui::text::Line; use ratatui::widgets::{Block, Borders, HighlightSpacing, List, ListItem, ListState}; use ratatui::{Terminal, symbols}; use rstest::rstest; #[test] fn list_should_shows_the_length() { let items = vec![ ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3"), ]; let list = List::new(items); assert_eq!(list.len(), 3); assert!(!list.is_empty()); let empty_list = List::default(); assert_eq!(empty_list.len(), 0); assert!(empty_list.is_empty()); } #[test] fn widgets_list_should_highlight_the_selected_item() { let backend = TestBackend::new(10, 3); let mut terminal = Terminal::new(backend).unwrap(); let mut state = ListState::default(); state.select(Some(1)); terminal .draw(|f| { let items = vec![ ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3"), ]; let list = List::new(items) .highlight_style(Style::default().bg(Color::Yellow)) .highlight_symbol(">> "); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); #[rustfmt::skip] let mut expected = Buffer::with_lines([ " Item 1 ", ">> Item 2 ", " Item 3 ", ]); for x in 0..10 { expected[(x, 1)].set_bg(Color::Yellow); } terminal.backend().assert_buffer(&expected); } #[test] fn widgets_list_should_highlight_the_selected_item_wide_symbol() { let backend = TestBackend::new(10, 3); let mut terminal = Terminal::new(backend).unwrap(); let mut state = ListState::default(); let wide_symbol = "▶ "; state.select(Some(1)); terminal .draw(|f| { let items = vec![ ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3"), ]; let list = List::new(items) .highlight_style(Style::default().bg(Color::Yellow)) .highlight_symbol(wide_symbol); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); #[rustfmt::skip] let mut expected = Buffer::with_lines([ " Item 1 ", "▶ Item 2 ", " Item 3 ", ]); for x in 0..10 { expected[(x, 1)].set_bg(Color::Yellow); } terminal.backend().assert_buffer(&expected); } #[test] fn widgets_list_should_truncate_items() { struct TruncateTestCase<'a> { selected: Option, items: Vec>, expected: Buffer, } let backend = TestBackend::new(10, 2); let mut terminal = Terminal::new(backend).unwrap(); let cases = [ // An item is selected TruncateTestCase { selected: Some(0), items: vec![ ListItem::new("A very long line"), ListItem::new("A very long line"), ], expected: Buffer::with_lines([ format!(">> A ve{} ", symbols::line::VERTICAL), format!(" A ve{} ", symbols::line::VERTICAL), ]), }, // No item is selected TruncateTestCase { selected: None, items: vec![ ListItem::new("A very long line"), ListItem::new("A very long line"), ], expected: Buffer::with_lines([ format!("A very {} ", symbols::line::VERTICAL), format!("A very {} ", symbols::line::VERTICAL), ]), }, ]; for case in cases { let mut state = ListState::default(); state.select(case.selected); terminal .draw(|f| { let list = List::new(case.items.clone()) .block(Block::new().borders(Borders::RIGHT)) .highlight_symbol(">> "); f.render_stateful_widget(list, Rect::new(0, 0, 8, 2), &mut state); }) .unwrap(); terminal.backend().assert_buffer(&case.expected); } } #[test] fn widgets_list_should_clamp_offset_if_items_are_removed() { let backend = TestBackend::new(10, 4); let mut terminal = Terminal::new(backend).unwrap(); let mut state = ListState::default(); // render with 6 items => offset will be at 2 state.select(Some(5)); terminal .draw(|f| { let items = vec![ ListItem::new("Item 0"), ListItem::new("Item 1"), ListItem::new("Item 2"), ListItem::new("Item 3"), ListItem::new("Item 4"), ListItem::new("Item 5"), ]; let list = List::new(items).highlight_symbol(">> "); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines([ " Item 2 ", " Item 3 ", " Item 4 ", ">> Item 5 ", ]); // render again with 1 items => check offset is clamped to 1 state.select(Some(1)); terminal .draw(|f| { let items = vec![ListItem::new("Item 3")]; let list = List::new(items).highlight_symbol(">> "); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines([ ">> Item 3 ", " ", " ", " ", ]); } #[test] fn widgets_list_should_display_multiline_items() { let backend = TestBackend::new(10, 6); let mut terminal = Terminal::new(backend).unwrap(); let mut state = ListState::default(); state.select(Some(1)); terminal .draw(|f| { let items = vec![ ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]), ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]), ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]), ]; let list = List::new(items) .highlight_style(Style::default().bg(Color::Yellow)) .highlight_symbol(">> "); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); let mut expected = Buffer::with_lines([ " Item 1 ", " Item 1a", ">> Item 2 ", " Item 2b", " Item 3 ", " Item 3c", ]); for x in 0..10 { expected[(x, 2)].set_bg(Color::Yellow); expected[(x, 3)].set_bg(Color::Yellow); } terminal.backend().assert_buffer(&expected); } #[test] fn widgets_list_should_repeat_highlight_symbol() { let backend = TestBackend::new(10, 6); let mut terminal = Terminal::new(backend).unwrap(); let mut state = ListState::default(); state.select(Some(1)); terminal .draw(|f| { let items = vec![ ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]), ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]), ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]), ]; let list = List::new(items) .highlight_style(Style::default().bg(Color::Yellow)) .highlight_symbol(">> ") .repeat_highlight_symbol(true); f.render_stateful_widget(list, f.area(), &mut state); }) .unwrap(); let mut expected = Buffer::with_lines([ " Item 1 ", " Item 1a", ">> Item 2 ", ">> Item 2b", " Item 3 ", " Item 3c", ]); for x in 0..10 { expected[(x, 2)].set_bg(Color::Yellow); expected[(x, 3)].set_bg(Color::Yellow); } terminal.backend().assert_buffer(&expected); } #[test] fn widget_list_should_not_ignore_empty_string_items() { let backend = TestBackend::new(6, 4); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let items = vec![ ListItem::new("Item 1"), ListItem::new(""), ListItem::new(""), ListItem::new("Item 4"), ]; let list = List::new(items) .style(Style::default()) .highlight_style(Style::default()); f.render_widget(list, f.area()); }) .unwrap(); terminal .backend() .assert_buffer_lines(["Item 1", "", "", "Item 4"]); } #[rstest] #[case::none_when_selected(None, HighlightSpacing::WhenSelected, [ "┌─────────────┐", "│Item 1 │", "│Item 1a │", "│Item 2 │", "│Item 2b │", "│Item 3 │", "│Item 3c │", "└─────────────┘", ])] #[case::none_always(None, HighlightSpacing::Always, [ "┌─────────────┐", "│ Item 1 │", "│ Item 1a │", "│ Item 2 │", "│ Item 2b │", "│ Item 3 │", "│ Item 3c │", "└─────────────┘", ])] #[case::none_never(None, HighlightSpacing::Never, [ "┌─────────────┐", "│Item 1 │", "│Item 1a │", "│Item 2 │", "│Item 2b │", "│Item 3 │", "│Item 3c │", "└─────────────┘", ])] #[case::first_when_selected(Some(0), HighlightSpacing::WhenSelected, [ "┌─────────────┐", "│>> Item 1 │", "│ Item 1a │", "│ Item 2 │", "│ Item 2b │", "│ Item 3 │", "│ Item 3c │", "└─────────────┘", ])] #[case::first_always(Some(0), HighlightSpacing::Always, [ "┌─────────────┐", "│>> Item 1 │", "│ Item 1a │", "│ Item 2 │", "│ Item 2b │", "│ Item 3 │", "│ Item 3c │", "└─────────────┘", ])] #[case::first_never(Some(0), HighlightSpacing::Never, [ "┌─────────────┐", "│Item 1 │", "│Item 1a │", "│Item 2 │", "│Item 2b │", "│Item 3 │", "│Item 3c │", "└─────────────┘", ])] fn widgets_list_enable_always_highlight_spacing<'line, Lines>( #[case] selected: Option, #[case] space: HighlightSpacing, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let mut state = ListState::default().with_selected(selected); let backend = TestBackend::new(15, 8); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = List::new(vec![ ListItem::new(vec![Line::from("Item 1"), Line::from("Item 1a")]), ListItem::new(vec![Line::from("Item 2"), Line::from("Item 2b")]), ListItem::new(vec![Line::from("Item 3"), Line::from("Item 3c")]), ]) .block(Block::bordered()) .highlight_symbol(">> ") .highlight_spacing(space); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); terminal .backend() .assert_buffer(&Buffer::with_lines(expected)); } ratatui-0.30.0/tests/widgets_paragraph.rs000064400000000000000000000311261046102023000165630ustar 00000000000000use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::Alignment; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Block, Padding, Paragraph, Wrap}; /// Tests the [`Paragraph`] widget against the expected [`Buffer`] by rendering it onto an equal /// area and comparing the rendered and expected content. #[track_caller] fn test_case(paragraph: Paragraph, expected: &Buffer) { let backend = TestBackend::new(expected.area.width, expected.area.height); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| f.render_widget(paragraph, f.area())) .unwrap(); terminal.backend().assert_buffer(expected); } #[test] fn widgets_paragraph_renders_double_width_graphemes() { let s = "コンピュータ上で文字を扱う場合、典型的には文字による通信を行う場合にその両端点では、"; let text = vec![Line::from(s)]; let paragraph = Paragraph::new(text) .block(Block::bordered()) .wrap(Wrap { trim: true }); test_case( paragraph, &Buffer::with_lines([ "┌────────┐", "│コンピュ│", "│ータ上で│", "│文字を扱│", "│う場合、│", "│典型的に│", "│は文字に│", "│よる通信│", "│を行う場│", "└────────┘", ]), ); } #[test] fn widgets_paragraph_renders_mixed_width_graphemes() { let backend = TestBackend::new(10, 7); let mut terminal = Terminal::new(backend).unwrap(); let s = "aコンピュータ上で文字を扱う場合、"; terminal .draw(|f| { let text = vec![Line::from(s)]; let paragraph = Paragraph::new(text) .block(Block::bordered()) .wrap(Wrap { trim: true }); f.render_widget(paragraph, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines([ // The internal width is 8 so only 4 slots for double-width characters. "┌────────┐", "│aコンピ │", // Here we have 1 latin character so only 3 double-width ones can fit. "│ュータ上│", "│で文字を│", "│扱う場合│", "│、 │", "└────────┘", ]); } #[test] fn widgets_paragraph_can_wrap_with_a_trailing_nbsp() { let nbsp = "\u{00a0}"; let line = Line::from(vec![Span::raw("NBSP"), Span::raw(nbsp)]); let paragraph = Paragraph::new(line).block(Block::bordered()); test_case( paragraph, &Buffer::with_lines([ "┌──────────────────┐", "│NBSP\u{00a0} │", "└──────────────────┘", ]), ); } #[test] fn widgets_paragraph_can_scroll_horizontally() { let text = Text::from("段落现在可以水平滚动了!\nParagraph can scroll horizontally!\nLittle line"); let paragraph = Paragraph::new(text).block(Block::bordered()); test_case( paragraph.clone().alignment(Alignment::Left).scroll((0, 7)), &Buffer::with_lines([ "┌──────────────────┐", "│在可以水平滚动了!│", "│ph can scroll hori│", "│line │", "│ │", "│ │", "│ │", "│ │", "│ │", "└──────────────────┘", ]), ); // only support Alignment::Left test_case( paragraph.clone().alignment(Alignment::Right).scroll((0, 7)), &Buffer::with_lines([ "┌──────────────────┐", "│段落现在可以水平滚│", "│Paragraph can scro│", "│ Little line│", "│ │", "│ │", "│ │", "│ │", "│ │", "└──────────────────┘", ]), ); } const SAMPLE_STRING: &str = "The library is based on the principle of immediate rendering with \ intermediate buffers. This means that at each new frame you should build all widgets that are \ supposed to be part of the UI. While providing a great flexibility for rich and \ interactive UI, this may introduce overhead for highly dynamic content."; #[test] fn widgets_paragraph_can_wrap_its_content() { let text = vec![Line::from(SAMPLE_STRING)]; let paragraph = Paragraph::new(text) .block(Block::bordered()) .wrap(Wrap { trim: true }); test_case( paragraph.clone().alignment(Alignment::Left), &Buffer::with_lines([ "┌──────────────────┐", "│The library is │", "│based on the │", "│principle of │", "│immediate │", "│rendering with │", "│intermediate │", "│buffers. This │", "│means that at each│", "└──────────────────┘", ]), ); test_case( paragraph.clone().alignment(Alignment::Center), &Buffer::with_lines([ "┌──────────────────┐", "│ The library is │", "│ based on the │", "│ principle of │", "│ immediate │", "│ rendering with │", "│ intermediate │", "│ buffers. This │", "│means that at each│", "└──────────────────┘", ]), ); test_case( paragraph.clone().alignment(Alignment::Right), &Buffer::with_lines([ "┌──────────────────┐", "│ The library is│", "│ based on the│", "│ principle of│", "│ immediate│", "│ rendering with│", "│ intermediate│", "│ buffers. This│", "│means that at each│", "└──────────────────┘", ]), ); } #[test] fn widgets_paragraph_works_with_padding() { let block = Block::bordered().padding(Padding { left: 2, right: 2, top: 1, bottom: 1, }); let paragraph = Paragraph::new(vec![Line::from(SAMPLE_STRING)]) .block(block.clone()) .wrap(Wrap { trim: true }); test_case( paragraph.clone().alignment(Alignment::Left), &Buffer::with_lines([ "┌────────────────────┐", "│ │", "│ The library is │", "│ based on the │", "│ principle of │", "│ immediate │", "│ rendering with │", "│ intermediate │", "│ buffers. This │", "│ means that at │", "│ │", "└────────────────────┘", ]), ); test_case( paragraph.clone().alignment(Alignment::Right), &Buffer::with_lines([ "┌────────────────────┐", "│ │", "│ The library is │", "│ based on the │", "│ principle of │", "│ immediate │", "│ rendering with │", "│ intermediate │", "│ buffers. This │", "│ means that at │", "│ │", "└────────────────────┘", ]), ); let paragraph = Paragraph::new(vec![ Line::from("This is always centered.").alignment(Alignment::Center), Line::from(SAMPLE_STRING), ]) .block(block) .wrap(Wrap { trim: true }); test_case( paragraph.alignment(Alignment::Right), &Buffer::with_lines([ "┌────────────────────┐", "│ │", "│ This is always │", "│ centered. │", "│ The library is │", "│ based on the │", "│ principle of │", "│ immediate │", "│ rendering with │", "│ intermediate │", "│ buffers. This │", "│ means that at │", "│ │", "└────────────────────┘", ]), ); } #[test] fn widgets_paragraph_can_align_spans() { let right_s = "This string will override the paragraph alignment to be right aligned."; let default_s = "This string will be aligned based on the alignment of the paragraph."; let text = vec![ Line::from(right_s).alignment(Alignment::Right), Line::from(default_s), ]; let paragraph = Paragraph::new(text) .block(Block::bordered()) .wrap(Wrap { trim: true }); test_case( paragraph.clone().alignment(Alignment::Left), &Buffer::with_lines([ "┌──────────────────┐", "│ This string will│", "│ override the│", "│ paragraph│", "│ alignment to be│", "│ right aligned.│", "│This string will │", "│be aligned based │", "│on the alignment │", "└──────────────────┘", ]), ); test_case( paragraph.alignment(Alignment::Center), &Buffer::with_lines([ "┌──────────────────┐", "│ This string will│", "│ override the│", "│ paragraph│", "│ alignment to be│", "│ right aligned.│", "│ This string will │", "│ be aligned based │", "│ on the alignment │", "└──────────────────┘", ]), ); let left_lines = vec!["This string", "will override the paragraph alignment"] .into_iter() .map(|s| Line::from(s).alignment(Alignment::Left)) .collect::>(); let mut lines = vec![ "This", "must be pretty long", "in order to effectively show", "truncation.", ] .into_iter() .map(Line::from) .collect::>(); let mut text = left_lines.clone(); text.append(&mut lines); let paragraph = Paragraph::new(text).block(Block::bordered()); test_case( paragraph.clone().alignment(Alignment::Right), &Buffer::with_lines([ "┌──────────────────┐", "│This string │", "│will override the │", "│ This│", "│must be pretty lon│", "│in order to effect│", "│ truncation.│", "│ │", "│ │", "└──────────────────┘", ]), ); test_case( paragraph.alignment(Alignment::Left), &Buffer::with_lines([ "┌──────────────────┐", "│This string │", "│will override the │", "│This │", "│must be pretty lon│", "│in order to effect│", "│truncation. │", "│ │", "└──────────────────┘", ]), ); } ratatui-0.30.0/tests/widgets_table.rs000064400000000000000000001011741046102023000157060ustar 00000000000000#![allow(deprecated)] use ratatui::Terminal; use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::Constraint; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Cell, HighlightSpacing, Row, Table, TableState}; use rstest::rstest; #[rstest] #[case::no_space_between_columns(0, [ "┌────────────────────────────┐", "│Head1Head2Head3 │", "│ │", "│Row11Row12Row13 │", "│Row21Row22Row23 │", "│Row31Row32Row33 │", "│Row41Row42Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::one_space_between_columns(1, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::large_width_just_before_pushing_a_column_off(6, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::large_width_pushes_part_of_third_column_off(7, [ "┌────────────────────────────┐", "│Head1 Head Head3│", "│ │", "│Row11 Row1 Row13│", "│Row21 Row2 Row23│", "│Row31 Row3 Row33│", "│Row41 Row4 Row43│", "│ │", "│ │", "└────────────────────────────┘", ])] fn widgets_table_column_spacing_can_be_changed<'line, Lines>( #[case] column_spacing: u16, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), ], [ Constraint::Length(5), Constraint::Length(5), Constraint::Length(5), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .column_spacing(column_spacing); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::zero_width_shows_nothing( &[ Constraint::Length(0), Constraint::Length(0), Constraint::Length(0), ], [ "┌────────────────────────────┐", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::slim_columns_trim_data(&[ Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), ], [ "┌────────────────────────────┐", "│H H H │", "│ │", "│R R R │", "│R R R │", "│R R R │", "│R R R │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::large_width_just_before_pushing_a_column_off(&[ Constraint::Length(8), Constraint::Length(8), Constraint::Length(8), ], [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] fn widgets_table_columns_widths_can_use_fixed_length_constraints<'line, Lines>( #[case] widths: &[Constraint], #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), ], widths, ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::zero_width_shows_nothing(&[ Constraint::Percentage(0), Constraint::Percentage(0), Constraint::Percentage(0), ], [ "┌────────────────────────────┐", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::slim_columns_trim_data(&[ Constraint::Percentage(11), Constraint::Percentage(11), Constraint::Percentage(11), ], [ "┌────────────────────────────┐", "│HeaHeaHea │", "│ │", "│RowRowRow │", "│RowRowRow │", "│RowRowRow │", "│RowRowRow │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::large_width_just_before_pushing_a_column_off(&[ Constraint::Percentage(33), Constraint::Percentage(33), Constraint::Percentage(33), ], [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::sum_100_equal_widths(&[Constraint::Percentage(50), Constraint::Percentage(50)], [ "┌────────────────────────────┐", "│Head1 Head2 │", "│ │", "│Row11 Row12 │", "│Row21 Row22 │", "│Row31 Row32 │", "│Row41 Row42 │", "│ │", "│ │", "└────────────────────────────┘", ])] fn widgets_table_columns_widths_can_use_percentage_constraints<'line, Lines>( #[case] widths: &[Constraint], #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), ], widths, ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .column_spacing(0); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::zero_width_shows_nothing(&[ Constraint::Percentage(0), Constraint::Length(0), Constraint::Percentage(0), ], [ "┌────────────────────────────┐", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::slim_columns_trim_data(&[ Constraint::Percentage(11), Constraint::Length(20), Constraint::Percentage(11), ], [ "┌────────────────────────────┐", "│Hea Head2 Hea│", "│ │", "│Row Row12 Row│", "│Row Row22 Row│", "│Row Row32 Row│", "│Row Row42 Row│", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::large_width_just_before_pushing_a_column_off(&[ Constraint::Percentage(33), Constraint::Length(10), Constraint::Percentage(33), ], [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::more_than_100(&[ Constraint::Percentage(60), Constraint::Length(10), Constraint::Percentage(60), ], [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] fn widgets_table_columns_widths_can_use_mixed_constraints<'line, Lines>( #[case] widths: &[Constraint], #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), ], widths, ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::zero_shows_nothing(&[ Constraint::Ratio(0, 1), Constraint::Ratio(0, 1), Constraint::Ratio(0, 1), ], [ "┌────────────────────────────┐", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::slim_trims_data(&[ Constraint::Ratio(1, 9), Constraint::Ratio(1, 9), Constraint::Ratio(1, 9), ], [ "┌────────────────────────────┐", "│HeaHeaHea │", "│ │", "│RowRowRow │", "│RowRowRow │", "│RowRowRow │", "│RowRowRow │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::three(&[Constraint::Ratio(1, 3), Constraint::Ratio(1, 3), Constraint::Ratio(1, 3)], [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ])] #[case::two(&[Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], [ "┌────────────────────────────┐", "│Head1 Head2 │", "│ │", "│Row11 Row12 │", "│Row21 Row22 │", "│Row31 Row32 │", "│Row41 Row42 │", "│ │", "│ │", "└────────────────────────────┘", ])] fn widgets_table_columns_widths_can_use_ratio_constraints<'line, Lines>( #[case] widths: &[Constraint], #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let backend = TestBackend::new(30, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), ], widths, ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .column_spacing(0); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::none( None, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│ │", "│Row31 Row32 Row33 │", "└────────────────────────────┘", ], )] #[case::first( Some(0), [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│>> Row11 Row12 Row13 │", "│ Row21 Row22 Row23 │", "│ │", "│ Row31 Row32 Row33 │", "└────────────────────────────┘", ], )] #[case::second_no_partially_fourth( Some(1), [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│ Row11 Row12 Row13 │", "│>> Row21 Row22 Row23 │", "│ │", "│ Row31 Row32 Row33 │", "└────────────────────────────┘", ], )] #[case::fourth_no_partially_first( Some(3), [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│ Row31 Row32 Row33 │", "│>> Row41 Row42 Row43 │", "│ │", "│ │", "└────────────────────────────┘", ], )] fn widgets_table_can_have_rows_with_multi_lines<'line, Lines>( #[case] selected: Option, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let mut state = TableState::new().with_selected(selected); let backend = TestBackend::new(30, 8); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]).height(2), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]).height(2), ], [ Constraint::Length(5), Constraint::Length(5), Constraint::Length(5), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .highlight_symbol(">> ") .column_spacing(1); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[rstest] #[case::none_when_selected(None, HighlightSpacing::WhenSelected, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│ │", "│Row31 Row32 Row33 │", "└────────────────────────────┘", ])] #[case::none_always( None, HighlightSpacing::Always, [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│ Row11 Row12 Row13 │", "│ Row21 Row22 Row23 │", "│ │", "│ Row31 Row32 Row33 │", "└────────────────────────────┘", ])] #[case::none_never(None, HighlightSpacing::Never, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│ │", "│Row31 Row32 Row33 │", "└────────────────────────────┘", ])] #[case::first_when_selected(Some(0), HighlightSpacing::WhenSelected, [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│>> Row11 Row12 Row13 │", "│ Row21 Row22 Row23 │", "│ │", "│ Row31 Row32 Row33 │", "└────────────────────────────┘", ])] #[case::first_always(Some(0), HighlightSpacing::Always, [ "┌────────────────────────────┐", "│ Head1 Head2 Head3 │", "│ │", "│>> Row11 Row12 Row13 │", "│ Row21 Row22 Row23 │", "│ │", "│ Row31 Row32 Row33 │", "└────────────────────────────┘", ])] #[case::first_never(Some(0), HighlightSpacing::Never, [ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row11 Row12 Row13 │", "│Row21 Row22 Row23 │", "│ │", "│Row31 Row32 Row33 │", "└────────────────────────────┘", ])] fn widgets_table_enable_always_highlight_spacing<'line, Lines>( #[case] selected: Option, #[case] space: HighlightSpacing, #[case] expected: Lines, ) where Lines: IntoIterator, Lines::Item: Into>, { let mut state = TableState::new().with_selected(selected); let backend = TestBackend::new(30, 8); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]).height(2), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]).height(2), ], [ Constraint::Length(5), Constraint::Length(5), Constraint::Length(5), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .highlight_symbol(">> ") .highlight_spacing(space) .column_spacing(1); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines(expected); } #[test] fn widgets_table_can_have_elements_styled_individually() { let backend = TestBackend::new(30, 4); let mut terminal = Terminal::new(backend).unwrap(); let mut state = TableState::default(); state.select(Some(0)); state.select_column(Some(1)); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row11", "Row12", "Row13"]) .style(Style::default().fg(Color::Green)), Row::new(vec![ Cell::from("Row21"), Cell::from("Row22").style(Style::default().fg(Color::Yellow)), Cell::from(Line::from(vec![ Span::raw("Row"), Span::styled("23", Style::default().fg(Color::Blue)), ])) .style(Style::default().fg(Color::Red)), ]) .style(Style::default().fg(Color::LightGreen)), ], [ Constraint::Length(6), Constraint::Length(6), Constraint::Length(6), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::new().borders(Borders::LEFT | Borders::RIGHT)) .highlight_symbol(">> ") .row_highlight_style(Style::default().add_modifier(Modifier::BOLD)) .column_highlight_style(Style::default().add_modifier(Modifier::ITALIC)) .cell_highlight_style(Style::default().add_modifier(Modifier::DIM)) .column_spacing(1); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); let mut expected = Buffer::with_lines([ "│ Head1 Head2 Head3 │", "│ │", "│>> Row11 Row12 Row13 │", "│ Row21 Row22 Row23 │", ]); // First row = row color + highlight style for col in 1..=28 { expected[(col, 2)].set_style( Style::default() .fg(Color::Green) .add_modifier(Modifier::BOLD), ); } // Second column highlight style for row in 2..=3 { for col in 11..=16 { expected[(col, row)].set_style(Style::default().add_modifier(Modifier::ITALIC)); } } // First row, second column highlight style (cell highlight) for col in 11..=16 { expected[(col, 2)].set_style(Style::default().add_modifier(Modifier::DIM)); } // Second row: // 1. row color for col in 1..=28 { expected[(col, 3)].set_style(Style::default().fg(Color::LightGreen)); } // 2. cell color for col in 11..=16 { expected[(col, 3)].set_style(Style::default().fg(Color::Yellow)); } for col in 18..=23 { expected[(col, 3)].set_style(Style::default().fg(Color::Red)); } // 3. text color for col in 21..=22 { expected[(col, 3)].set_style(Style::default().fg(Color::Blue)); } terminal.backend().assert_buffer(&expected); } #[test] fn widgets_table_should_render_even_if_empty() { let backend = TestBackend::new(30, 4); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let table = Table::new( Vec::::new(), [ Constraint::Length(6), Constraint::Length(6), Constraint::Length(6), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"])) .block(Block::new().borders(Borders::LEFT | Borders::RIGHT)) .column_spacing(1); f.render_widget(table, f.area()); }) .unwrap(); terminal.backend().assert_buffer_lines([ "│Head1 Head2 Head3 │", "│ │", "│ │", "│ │", ]); } // based on https://github.com/fdehau/tui-rs/issues/470#issuecomment-852562848 #[test] fn widgets_table_columns_dont_panic() { let table_width = 98; let table = Table::new( vec![Row::new(vec!["r1", "r2", "r3", "r4"])], [ Constraint::Percentage(15), Constraint::Percentage(15), Constraint::Percentage(25), Constraint::Percentage(45), ], ) .header(Row::new(vec!["h1", "h2", "h3", "h4"])) .block(Block::bordered()) .highlight_symbol(">> ") .column_spacing(1); let mut state = TableState::default(); // select first, which would cause a panic before fix state.select(Some(0)); let backend = TestBackend::new(table_width, 8); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| f.render_stateful_widget(table, f.area(), &mut state)) .unwrap(); } #[test] fn widgets_table_should_clamp_offset_if_rows_are_removed() { let backend = TestBackend::new(30, 8); let mut terminal = Terminal::new(backend).unwrap(); let mut state = TableState::default(); // render with 6 items => offset will be at 2 state.select(Some(5)); terminal .draw(|f| { let table = Table::new( vec![ Row::new(vec!["Row01", "Row02", "Row03"]), Row::new(vec!["Row11", "Row12", "Row13"]), Row::new(vec!["Row21", "Row22", "Row23"]), Row::new(vec!["Row31", "Row32", "Row33"]), Row::new(vec!["Row41", "Row42", "Row43"]), Row::new(vec!["Row51", "Row52", "Row53"]), ], [ Constraint::Length(5), Constraint::Length(5), Constraint::Length(5), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .column_spacing(1); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines([ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row21 Row22 Row23 │", "│Row31 Row32 Row33 │", "│Row41 Row42 Row43 │", "│Row51 Row52 Row53 │", "└────────────────────────────┘", ]); // render with 1 item => offset will be at 1 state.select(Some(1)); terminal .draw(|f| { let table = Table::new( vec![Row::new(vec!["Row31", "Row32", "Row33"])], [ Constraint::Length(5), Constraint::Length(5), Constraint::Length(5), ], ) .header(Row::new(vec!["Head1", "Head2", "Head3"]).bottom_margin(1)) .block(Block::bordered()) .column_spacing(1); f.render_stateful_widget(table, f.area(), &mut state); }) .unwrap(); terminal.backend().assert_buffer_lines([ "┌────────────────────────────┐", "│Head1 Head2 Head3 │", "│ │", "│Row31 Row32 Row33 │", "│ │", "│ │", "│ │", "└────────────────────────────┘", ]); } ratatui-0.30.0/tests/widgets_tabs.rs000064400000000000000000000026401046102023000155460ustar 00000000000000use ratatui::backend::TestBackend; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use ratatui::style::Style; use ratatui::widgets::Tabs; use ratatui::{Terminal, symbols}; #[test] fn widgets_tabs_should_not_panic_on_narrow_areas() { let backend = TestBackend::new(1, 1); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let tabs = Tabs::new(["Tab1", "Tab2"]); f.render_widget( tabs, Rect { x: 0, y: 0, width: 1, height: 1, }, ); }) .unwrap(); terminal.backend().assert_buffer_lines([" "]); } #[test] fn widgets_tabs_should_truncate_the_last_item() { let backend = TestBackend::new(10, 1); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let tabs = Tabs::new(["Tab1", "Tab2"]); f.render_widget( tabs, Rect { x: 0, y: 0, width: 9, height: 1, }, ); }) .unwrap(); let mut expected = Buffer::with_lines([format!(" Tab1 {} T ", symbols::line::VERTICAL)]); expected.set_style(Rect::new(1, 0, 4, 1), Style::new().reversed()); terminal.backend().assert_buffer(&expected); }