cursive-0.21.1/.cargo_vcs_info.json0000644000000001450000000000100125750ustar { "git": { "sha1": "64540d6a683e7d7b987e1f71ac5df2601060a96f" }, "path_in_vcs": "cursive" }cursive-0.21.1/Cargo.lock0000644000000747640000000000100105720ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "ansi-parser" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43e7fd8284f025d0bd143c2855618ecdf697db55bde39211e5c9faec7669173" dependencies = [ "heapless", "nom", ] [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bear-lib-terminal" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "315549f695a3fef8ad1e1ebad093a7c38d135fab85e10067841744901f907116" dependencies = [ "bear-lib-terminal-sys", ] [[package]] name = "bear-lib-terminal-sys" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a04c83b2c02f07128177fa98ea2d241f85d351d376dcee70694fcb6960a279f" dependencies = [ "libc", ] [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "castaway" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "compact_str" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "static_assertions", ] [[package]] name = "crossbeam-channel" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", "mio", "parking_lot", "rustix", "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 = "cursive" version = "0.21.1" dependencies = [ "ahash", "bear-lib-terminal", "cfg-if", "crossbeam-channel", "crossterm", "cursive_core", "lazy_static", "libc", "log", "maplit", "ncurses 6.0.1", "pancurses", "pretty-bytes", "rand", "serde_json", "serde_yaml", "signal-hook", "termion", "unicode-segmentation", "unicode-width", ] [[package]] name = "cursive-macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac7ac0eb0cede3dfdfebf4d5f22354e05a730b79c25fd03481fc69fcfba0a73e" dependencies = [ "find-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "cursive_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91258b5e45aca5ef1b3e647a309963cc6a41aa42567de150d3ad23f35c63c655" dependencies = [ "ahash", "ansi-parser", "compact_str", "crossbeam-channel", "cursive-macros", "enum-map", "enumset", "inventory", "lazy_static", "log", "num", "parking_lot", "pulldown-cmark", "serde_json", "serde_yaml", "time", "toml 0.8.19", "unicode-segmentation", "unicode-width", "xi-unicode", ] [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "syn", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "enum-map" version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "enumset" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", ] [[package]] name = "find-crate" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" dependencies = [ "toml 0.5.11", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inventory" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[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.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libredox" version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ "bitflags 2.6.0", "libc", "redox_syscall 0.4.1", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi 0.3.9", "libc", "log", "wasi", "windows-sys", ] [[package]] name = "ncurses" version = "5.101.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" dependencies = [ "cc", "libc", "pkg-config", ] [[package]] name = "ncurses" version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd71afa95710e841d173521e8483e764004eb332bdf47bd01d00f568688027f" dependencies = [ "cc", "libc", "pkg-config", ] [[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 = "num" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-integer", "num-traits", ] [[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.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "pancurses" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db" dependencies = [ "libc", "log", "ncurses 5.101.0", "pdcurses-sys", "winreg", ] [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall 0.5.3", "smallvec", "windows-targets", ] [[package]] name = "pdcurses-sys" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b" dependencies = [ "cc", "libc", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee4364d9f3b902ef14fab8a1ddffb783a1cb6b4bba3bfc1fa3922732c7de97f" dependencies = [ "zerocopy 0.6.6", ] [[package]] name = "pretty-bytes" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "009d6edd2c1dbf2e1c0cd48a2f7766e03498d49ada7109a01c6911815c685316" dependencies = [ "atty", "getopts", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pulldown-cmark" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8746739f11d39ce5ad5c2520a9b75285310dbfe78c541ccf832d38615765aec0" dependencies = [ "bitflags 2.6.0", "memchr", "unicase", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_termios" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ab380d7d9f22ef3f21ad3e6c1ebe8e4fc7a2000ccba2e4d71fc96f15b2cb609" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termion" version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ccce68e518d1173e80876edd54760b60b792750d0cab6444a79101c6ea03848" dependencies = [ "libc", "libredox", "numtoa", "redox_termios", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicase" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[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-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[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_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[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_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] [[package]] name = "winreg" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" dependencies = [ "winapi", ] [[package]] name = "xi-unicode" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a67300977d3dc3f8034dae89778f502b6ba20b269527b3223ba59c0cf393bb8a" [[package]] name = "zerocopy" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "854e949ac82d619ee9a14c66a1b674ac730422372ccb759ce0c39cabcf2bf8e6" dependencies = [ "byteorder", "zerocopy-derive 0.6.6", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy-derive" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] cursive-0.21.1/Cargo.toml0000644000000053000000000000100105710ustar # 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 = "2021" name = "cursive" version = "0.21.1" authors = ["Alexandre Bury "] build = false include = [ "src/**/*", "LICENSE", "README.md", ] autobins = false autoexamples = false autotests = false autobenches = false description = "A TUI (Text User Interface) library focused on ease-of-use." documentation = "https://docs.rs/cursive" readme = "README.md" keywords = [ "ncurses", "TUI", "UI", ] categories = [ "command-line-interface", "gui", ] license = "MIT" repository = "https://github.com/gyscos/cursive" [package.metadata.docs.rs] features = [ "doc-cfg", "ansi", "toml", "markdown", "builder", "termion-backend", "crossterm-backend", "pancurses-backend", ] [lib] name = "cursive" path = "src/lib.rs" [dependencies.ahash] version = "0.8" [dependencies.bear-lib-terminal] version = "2" optional = true [dependencies.cfg-if] version = "1" [dependencies.crossbeam-channel] version = "0.5" [dependencies.crossterm] version = "0.28.1" optional = true [dependencies.cursive_core] version = "0.4.0" [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.log] version = "0.4" [dependencies.maplit] version = "1.0" optional = true [dependencies.ncurses] version = "6.0.1" features = ["wide"] optional = true [dependencies.pancurses] version = "0.17" features = ["wide"] optional = true [dependencies.termion] version = "4" optional = true [dependencies.unicode-segmentation] version = "1" [dependencies.unicode-width] version = "0.1" [dev-dependencies.pretty-bytes] version = "0.2" [dev-dependencies.rand] version = "0.8" [dev-dependencies.serde_json] version = "1.0.85" [dev-dependencies.serde_yaml] version = "0.9.13" [features] ansi = ["cursive_core/ansi"] blt-backend = ["dep:bear-lib-terminal"] builder = ["cursive_core/builder"] crossterm-backend = ["dep:crossterm"] default = ["crossterm-backend"] doc-cfg = ["cursive_core/doc-cfg"] markdown = ["cursive_core/markdown"] ncurses-backend = [ "dep:ncurses", "dep:maplit", ] pancurses-backend = [ "dep:pancurses", "dep:maplit", ] termion-backend = ["dep:termion"] toml = ["cursive_core/toml"] [target."cfg(unix)".dependencies.signal-hook] version = "0.3" cursive-0.21.1/Cargo.toml.orig000064400000000000000000000047471046102023000142700ustar 00000000000000[package] authors = ["Alexandre Bury "] categories = ["command-line-interface", "gui"] description = "A TUI (Text User Interface) library focused on ease-of-use." documentation = "https://docs.rs/cursive" edition = "2021" keywords = ["ncurses", "TUI", "UI"] license = "MIT" name = "cursive" readme = "README.md" repository = "https://github.com/gyscos/cursive" version = "0.21.1" include = ["src/**/*", "LICENSE", "README.md"] [package.metadata.docs.rs] features = [ "doc-cfg", "ansi", "toml", "markdown", "builder", "termion-backend", "crossterm-backend", "pancurses-backend", ] [dependencies] cursive_core = { path = "../cursive-core", version= "0.4.0"} crossbeam-channel = "0.5" cfg-if = "1" unicode-segmentation = "1" unicode-width = "0.1" lazy_static = "1" libc = "0.2" maplit = { version = "1.0", optional = true } log = "0.4" ahash = "0.8" [dependencies.bear-lib-terminal] optional = true version = "2" [dependencies.ncurses] features = ["wide"] optional = true version = "6.0.1" [dependencies.pancurses] features = ["wide"] optional = true version = "0.17" [dependencies.termion] optional = true version = "4" [dependencies.crossterm] optional = true version = "0.28.1" [features] doc-cfg = ["cursive_core/doc-cfg"] # Enable doc_cfg, a nightly-only doc feature. builder = ["cursive_core/builder"] # Enable the builder module to build views from config blobs. blt-backend = ["dep:bear-lib-terminal"] # Enable the BearLibTerminal backend. Requires BLT to be installed separately. default = ["crossterm-backend"] # Defaults to crossterm, supported on windows, linux and macos. ncurses-backend = ["dep:ncurses", "dep:maplit"] # Enable the ncurses backend. pancurses-backend = ["dep:pancurses", "dep:maplit"] # Enable the pancurses backend. termion-backend = ["dep:termion"] # Enable the termion backend. crossterm-backend = ["dep:crossterm"] # Enable the crossterm backend. markdown = ["cursive_core/markdown"] # Allows parsing StyledString from markdown text. ansi = ["cursive_core/ansi"] # Allows parsing StyledString from ANSI-marked up text. toml = ["cursive_core/toml"] # Allows parsing themes from toml. [lib] name = "cursive" [target.'cfg(unix)'.dependencies] signal-hook = "0.3" [[example]] name = "theme" required-features = ["toml"] [[example]] name = "ansi" required-features = ["ansi"] [[example]] name = "builder" required-features = ["builder"] [dev-dependencies] rand = "0.8" pretty-bytes = "0.2" serde_json = "1.0.85" serde_yaml = "0.9.13" cursive-0.21.1/LICENSE000064400000000000000000000020421046102023000123700ustar 00000000000000Copyright (c) 2015 Alexandre Bury Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cursive-0.21.1/README.md000064400000000000000000000210261046102023000126450ustar 00000000000000# Cursive [![crates.io](https://img.shields.io/crates/v/cursive.svg)](https://crates.io/crates/cursive) [![Rust](https://github.com/gyscos/cursive/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/gyscos/cursive/actions/workflows/rust.yml) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) [![Gitter chat](https://badges.gitter.im/gyscos/cursive.png)](https://gitter.im/cursive-rs/cursive) Cursive is a TUI (Text User Interface) library for rust. It uses the [`crossterm`] backend by default, but [other backends are available](https://github.com/gyscos/cursive/wiki/Backends). It allows you to build rich user interfaces for terminal applications. [`crossterm`]: https://github.com/crossterm-rs/crossterm # [Documentation](http://docs.rs/cursive) It is designed to be safe and easy to use: ```toml [dependencies] cursive = "0.21" ``` Or to use the latest git version: ```toml [dependencies] cursive = { git = "https://github.com/gyscos/cursive" } ``` ```rust,no_run use cursive::views::{Dialog, TextView}; fn main() { // Creates the cursive root - required for every application. let mut siv = cursive::default(); // Creates a dialog with a single "Quit" button siv.add_layer(Dialog::around(TextView::new("Hello Dialog!")) .title("Cursive") .button("Quit", |s| s.quit())); // Starts the event loop. siv.run(); } ``` [![Cursive dialog example](https://raw.githubusercontent.com/gyscos/cursive/main/doc/cursive_example.png)](https://github.com/gyscos/cursive/tree/main/cursive/examples/dialog.rs) Check out the other [examples](https://github.com/gyscos/cursive/tree/main/cursive/examples) to get these results, and more:
lorem.rs example menubar.rs example select.rs example mines example theme_manual.rs example syntect example
_(Colors may depend on your terminal configuration.)_ ## Tutorials These tutorials may help you get started with cursive: * [Starting with cursive: (1/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_1.md) * [Starting with cursive: (2/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_2.md) * [Starting with cursive: (3/3)](https://github.com/gyscos/cursive/tree/main/doc/tutorial_3.md) ## Third-party views Here are a few crates implementing new views for you to use: * [cursive-aligned-view](https://github.com/deinstapel/cursive-aligned-view): A view wrapper for gyscos/cursive views which aligns child views. * [cursive-async-view](https://github.com/deinstapel/cursive-async-view): A loading-screen wrapper. * [cursive-flexi-logger-view](https://github.com/deinstapel/cursive-flexi-logger-view): An alternative debug view using `emabee/flexi_logger`. * [cursive-markup](https://sr.ht/~ireas/cursive-markup-rs): A view that renders HTML or other markup. * [cursive-multiplex](https://github.com/deinstapel/cursive-multiplex): A tmux like multiplexer. * [cursive-spinner-view](https://github.com/otov4its/cursive-spinner-view): A spinner view. * [cursive-tabs](https://github.com/deinstapel/cursive-tabs): Tabs. * [cursive_calendar_view](https://github.com/BonsaiDen/cursive_calendar_view): A basic calendar view implementation. * [cursive_hexview](https://github.com/hellow554/cursive_hexview): A simple hexview. * [cursive_table_view](https://github.com/BonsaiDen/cursive_table_view): A basic table view component. * [cursive_tree_view](https://github.com/BonsaiDen/cursive_tree_view): A tree view implementation. * [cursive-hjkl](https://github.com/gamma-delta/cursive-hjkl): Wraps any view to use Vim-like `hjkl` controls. ## Showcases Here are some cool applications using cursive: * [RustyChat](https://github.com/SambaDialloB/RustyChat): Chat client made using Rust and Cursive. * [checkline](https://github.com/sixarm/checkline-rust-crate): Checkbox line picker from stdin to stdout. * [clock-cli](https://github.com/TianyiShi2001/clock-cli-rs): A clock with stopwatch and countdown timer functionalities. * [fui](https://github.com/xliiv/fui): Add CLI & form interface to your program. * [game2048-rs](https://github.com/genieCS/game2048-rs): a tui game2048 using Rust and cursive. * [git-branchless](https://github.com/arxanas/git-branchless): Branchless workflow for Git. * [grin-tui](https://github.com/mimblewimble/grin): Minimal implementation of the MimbleWimble protocol. * [kakikun](https://github.com/file-acomplaint/kakikun): A paint and ASCII art application for the terminal. * [launchk](https://github.com/mach-kernel/launchk): Manage launchd agents and daemons on macOS. * [markline](https://github.com/sixarm/markline): Marker-based line picker from stdin to stdout. * [mythra](https://github.com/deven96/mythra): CLI to search for music. * [ncspot](https://github.com/hrkfdn/ncspot): Cross-platform ncurses Spotify client. * [rbmenu-tui](https://github.com/DevHyperCoder/rbmenu-tui): A TUI for bookmark management. * [retris](https://github.com/genieCS/retris): A simple implementation of the classic tetris game. * [ripasso](https://github.com/cortex/ripasso): A simple password manager written in Rust. * [rusty-man](https://sr.ht/~ireas/rusty-man): Browse rustdoc documentation. * [saci-rs](https://gitlab.com/ihercowitz/saci-rs): Simple API Client Interface. * [so](https://github.com/samtay/so): A terminal interface for Stack Overflow. * [sudoku-tui](https://github.com/TianyiShi2001/sudoku-tui): Play sudoku on the command line. * [tap](https://github.com/timdubbins/tap): An audio player for the terminal with fuzzy finder. * [ttyloop](https://github.com/gamma-delta/ttyloop): Clone of the mobile game Loop. * [wiki-tui](https://github.com/Builditluc/wiki-tui): A simple and easy to use Wikipedia Text User Interface ## Goals * **Ease of use.** Simple apps should be simple. Complex apps should be manageable. * **Linux TTY Compatibility.** Colors may suffer, and UTF-8 may be too much, but most features *must* work properly on a Linux TTY. * **Flexibility.** This library should be able to handle simple UI scripts, complex real-time applications, or even games. * In particular, it tries to have enough features to recreate these kind of tools: * [menuconfig](http://en.wikipedia.org/wiki/Menuconfig#/media/File:Linux_x86_3.10.0-rc2_Kernel_Configuration.png) * [nmtui](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/sec-Configure_a_Network_Team_Using_the_Text_User_Interface_nmtui.html) ## Compatibility First off, terminals are messy. A small set of features is standard, but beyond that, almost every terminal has its own implementation. ### Output * **Colors**: the basic 8-colors palette should be broadly supported. User-defined colors is not supported in the raw linux TTY, but should work in most terminals, although it's still kinda experimental. * **UTF-8**: Currently Cursive really expects a UTF-8 locale. It may eventually get patched to support window borders on other locales, but it's not a priority. There is initial support for [wide characters](https://en.wikipedia.org/wiki/CJK_characters). [RTL](https://en.wikipedia.org/wiki/Right-to-left) support [is planned](https://github.com/gyscos/cursive/issues/31), but still very early. ### Input * The `key_codes` example can be a useful tool to see how the library reacts to various key presses. * Keep in mind that if the terminal has shortcuts registered, they probably won't be transmitted to the app. * UTF-8 input should work fine in a unicode-enabled terminal emulator, but raw linux TTY may be more capricious. ## [Contributing](CONTRIBUTING.md) ## Alternatives See also [ratatui](https://github.com/ratatui-org/ratatui) - and a small [comparison page](https://github.com/gyscos/cursive/wiki/Cursive-vs-tui%E2%80%90rs). cursive-0.21.1/src/backends/blt.rs000064400000000000000000000426161046102023000150660ustar 00000000000000//! Backend using BearLibTerminal //! //! Requires the `blt-backend` feature. #![cfg(feature = "blt-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub use bear_lib_terminal; use std::cell::Cell; use bear_lib_terminal::geometry::Size; use bear_lib_terminal::terminal::{self, state, Event as BltEvent, KeyCode}; use bear_lib_terminal::Color as BltColor; use unicode_width::UnicodeWidthStr; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{BaseColor, Color, ColorPair, Effect}; use crate::Vec2; // Use AHash instead of the slower SipHash type HashSet = std::collections::HashSet; enum ColorRole { Foreground, Background, } /// Backend using BearLibTerminal pub struct Backend { buttons_pressed: HashSet, cursor: Cell, mouse_position: Vec2, } impl Backend { /// Creates a new BearLibTerminal-based backend. pub fn init() -> Box { // TODO: Add some error handling? terminal::open("Cursive", 80, 24); terminal::set(terminal::config::Window::empty().resizeable(true)); terminal::set(vec![ terminal::config::InputFilter::Group { group: terminal::config::InputFilterGroup::Keyboard, both: false, }, terminal::config::InputFilter::Group { group: terminal::config::InputFilterGroup::Mouse, both: true, }, ]); let c = Backend { buttons_pressed: HashSet::default(), mouse_position: Vec2::zero(), cursor: Cell::new(Vec2::zero()), }; Box::new(c) } fn parse_next(&mut self) -> Option { // TODO: we could add backend-specific controls here. // Ex: ctrl+mouse wheel cause window cellsize to change terminal::read_event().map(|ev| { match ev { BltEvent::Close => Event::Exit, BltEvent::Resize { .. } => Event::WindowResize, // TODO: mouse support BltEvent::MouseMove { x, y } => { self.mouse_position = Vec2::new(x as usize, y as usize); // TODO: find out if a button is pressed? match self.buttons_pressed.iter().next() { None => Event::Refresh, Some(btn) => Event::Mouse { event: MouseEvent::Hold(*btn), position: self.mouse_position, offset: Vec2::zero(), }, } } BltEvent::MouseScroll { delta } => Event::Mouse { event: if delta < 0 { MouseEvent::WheelUp } else { MouseEvent::WheelDown }, position: self.mouse_position, offset: Vec2::zero(), }, BltEvent::KeyPressed { key, ctrl, shift } => { self.blt_keycode_to_ev(key, shift, ctrl) } // TODO: there's no Key::Shift/Ctrl for w/e reason BltEvent::ShiftPressed => Event::Refresh, BltEvent::ControlPressed => Event::Refresh, // TODO: what should we do here? BltEvent::KeyReleased { key, .. } => { // It's probably a mouse key. blt_keycode_to_mouse_button(key) .map(|btn| { self.buttons_pressed.remove(&btn); Event::Mouse { event: MouseEvent::Release(btn), position: self.mouse_position, offset: Vec2::zero(), } }) .unwrap_or_else(|| Event::Unknown(vec![])) } BltEvent::ShiftReleased | BltEvent::ControlReleased => Event::Refresh, _ => Event::Unknown(vec![]), } }) } fn blt_keycode_to_ev(&mut self, kc: KeyCode, shift: bool, ctrl: bool) -> Event { match kc { KeyCode::F1 | KeyCode::F2 | KeyCode::F3 | KeyCode::F4 | KeyCode::F5 | KeyCode::F6 | KeyCode::F7 | KeyCode::F8 | KeyCode::F9 | KeyCode::F10 | KeyCode::F11 | KeyCode::F12 | KeyCode::NumEnter | KeyCode::Enter | KeyCode::Escape | KeyCode::Backspace | KeyCode::Tab | KeyCode::Pause | KeyCode::Insert | KeyCode::Home | KeyCode::PageUp | KeyCode::Delete | KeyCode::End | KeyCode::PageDown | KeyCode::Right | KeyCode::Left | KeyCode::Down | KeyCode::Up => match (shift, ctrl) { (true, true) => Event::CtrlShift(blt_keycode_to_key(kc)), (true, false) => Event::Shift(blt_keycode_to_key(kc)), (false, true) => Event::Ctrl(blt_keycode_to_key(kc)), (false, false) => Event::Key(blt_keycode_to_key(kc)), }, // TODO: mouse support KeyCode::MouseLeft | KeyCode::MouseRight | KeyCode::MouseMiddle | KeyCode::MouseFourth | KeyCode::MouseFifth => blt_keycode_to_mouse_button(kc) .map(|btn| { self.buttons_pressed.insert(btn); Event::Mouse { event: MouseEvent::Press(btn), position: self.mouse_position, offset: Vec2::zero(), } }) .unwrap_or_else(|| Event::Unknown(vec![])), KeyCode::A | KeyCode::B | KeyCode::C | KeyCode::D | KeyCode::E | KeyCode::F | KeyCode::G | KeyCode::H | KeyCode::I | KeyCode::J | KeyCode::K | KeyCode::L | KeyCode::M | KeyCode::N | KeyCode::O | KeyCode::P | KeyCode::Q | KeyCode::R | KeyCode::S | KeyCode::T | KeyCode::U | KeyCode::V | KeyCode::W | KeyCode::X | KeyCode::Y | KeyCode::Z | KeyCode::Row1 | KeyCode::Row2 | KeyCode::Row3 | KeyCode::Row4 | KeyCode::Row5 | KeyCode::Row6 | KeyCode::Row7 | KeyCode::Row8 | KeyCode::Row9 | KeyCode::Row0 | KeyCode::Grave | KeyCode::Minus | KeyCode::Equals | KeyCode::LeftBracket | KeyCode::RightBracket | KeyCode::Backslash | KeyCode::Semicolon | KeyCode::Apostrophe | KeyCode::Comma | KeyCode::Period | KeyCode::Slash | KeyCode::Space | KeyCode::NumDivide | KeyCode::NumMultiply | KeyCode::NumMinus | KeyCode::NumPlus | KeyCode::NumPeriod | KeyCode::Num1 | KeyCode::Num2 | KeyCode::Num3 | KeyCode::Num4 | KeyCode::Num5 | KeyCode::Num6 | KeyCode::Num7 | KeyCode::Num8 | KeyCode::Num9 | KeyCode::Num0 => { if ctrl { Event::CtrlChar(blt_keycode_to_char(kc, shift)) } else { Event::Char(blt_keycode_to_char(kc, shift)) } } } } } impl Drop for Backend { fn drop(&mut self) { terminal::close(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "bear-lib-terminal" } fn set_title(&mut self, title: String) { terminal::set(terminal::config::Window::empty().title(title)); } fn set_color(&self, color: ColorPair) -> ColorPair { let current = ColorPair { front: blt_colour_to_colour(state::foreground()), back: blt_colour_to_colour(state::background()), }; let fg = colour_to_blt_colour(color.front, ColorRole::Foreground); let bg = colour_to_blt_colour(color.back, ColorRole::Background); terminal::set_colors(fg, bg); current } fn set_effect(&self, effect: Effect) { match effect { // TODO: does BLT support bold/italic/strikethrough/underline? Effect::Dim | Effect::Bold | Effect::Italic | Effect::Underline | Effect::Strikethrough | Effect::Blink | Effect::Simple => {} // TODO: implement this correctly. // Add a `reverse` flag in the Backend, and check it when calling `set_colors`. Effect::Reverse => terminal::set_colors(state::background(), state::foreground()), } } fn unset_effect(&self, effect: Effect) { match effect { // TODO: does BLT support bold/italic/strikethrough/underline? Effect::Dim | Effect::Bold | Effect::Italic | Effect::Underline | Effect::Strikethrough | Effect::Blink | Effect::Simple => {} // The process of reversing is the same as unreversing Effect::Reverse => terminal::set_colors(state::background(), state::foreground()), } } fn has_colors(&self) -> bool { true } fn screen_size(&self) -> Vec2 { let Size { width, height } = terminal::state::size(); (width, height).into() } fn clear(&self, color: Color) { terminal::set_background(colour_to_blt_colour(color, ColorRole::Background)); terminal::clear(None); } fn refresh(&mut self) { terminal::refresh(); } fn move_to(&self, pos: Vec2) { self.cursor.set(pos); } fn print(&self, text: &str) { let pos = self.cursor.get(); terminal::print_xy(pos.x as i32, pos.y as i32, text); self.cursor.set(pos + (text.width(), 0)); } fn poll_event(&mut self) -> Option { self.parse_next() } } fn blt_colour_to_colour(c: BltColor) -> Color { Color::Rgb(c.red, c.green, c.blue) } fn colour_to_blt_colour(clr: Color, role: ColorRole) -> BltColor { let (r, g, b) = match clr { Color::TerminalDefault => { let clr = match role { ColorRole::Foreground => state::foreground(), ColorRole::Background => state::background(), }; return clr; } // Colours taken from // https://en.wikipedia.org/wiki/ANSI_escape_code#Colors Color::Dark(BaseColor::Black) => (0, 0, 0), Color::Dark(BaseColor::Red) => (170, 0, 0), Color::Dark(BaseColor::Green) => (0, 170, 0), Color::Dark(BaseColor::Yellow) => (170, 85, 0), Color::Dark(BaseColor::Blue) => (0, 0, 170), Color::Dark(BaseColor::Magenta) => (170, 0, 170), Color::Dark(BaseColor::Cyan) => (0, 170, 170), Color::Dark(BaseColor::White) => (170, 170, 170), Color::Light(BaseColor::Black) => (85, 85, 85), Color::Light(BaseColor::Red) => (255, 85, 85), Color::Light(BaseColor::Green) => (85, 255, 85), Color::Light(BaseColor::Yellow) => (255, 255, 85), Color::Light(BaseColor::Blue) => (85, 85, 255), Color::Light(BaseColor::Magenta) => (255, 85, 255), Color::Light(BaseColor::Cyan) => (85, 255, 255), Color::Light(BaseColor::White) => (255, 255, 255), Color::Rgb(r, g, b) => (r, g, b), Color::RgbLowRes(r, g, b) => ( (f32::from(r) / 5.0 * 255.0) as u8, (f32::from(g) / 5.0 * 255.0) as u8, (f32::from(b) / 5.0 * 255.0) as u8, ), }; BltColor::from_rgb(r, g, b) } fn blt_keycode_to_char(kc: KeyCode, shift: bool) -> char { match bear_lib_terminal::terminal::state::char() { '\u{0}' => blt_keycode_to_char_impl(kc, shift), c => c, } } #[allow(clippy::cognitive_complexity)] fn blt_keycode_to_char_impl(kc: KeyCode, shift: bool) -> char { match kc { KeyCode::A if shift => 'A', KeyCode::A => 'a', KeyCode::B if shift => 'B', KeyCode::B => 'b', KeyCode::C if shift => 'C', KeyCode::C => 'c', KeyCode::D if shift => 'D', KeyCode::D => 'd', KeyCode::E if shift => 'E', KeyCode::E => 'e', KeyCode::F if shift => 'F', KeyCode::F => 'f', KeyCode::G if shift => 'G', KeyCode::G => 'g', KeyCode::H if shift => 'H', KeyCode::H => 'h', KeyCode::I if shift => 'I', KeyCode::I => 'i', KeyCode::J if shift => 'J', KeyCode::J => 'j', KeyCode::K if shift => 'K', KeyCode::K => 'k', KeyCode::L if shift => 'L', KeyCode::L => 'l', KeyCode::M if shift => 'M', KeyCode::M => 'm', KeyCode::N if shift => 'N', KeyCode::N => 'n', KeyCode::O if shift => 'O', KeyCode::O => 'o', KeyCode::P if shift => 'P', KeyCode::P => 'p', KeyCode::Q if shift => 'Q', KeyCode::Q => 'q', KeyCode::R if shift => 'R', KeyCode::R => 'r', KeyCode::S if shift => 'S', KeyCode::S => 's', KeyCode::T if shift => 'T', KeyCode::T => 't', KeyCode::U if shift => 'U', KeyCode::U => 'u', KeyCode::V if shift => 'V', KeyCode::V => 'v', KeyCode::W if shift => 'W', KeyCode::W => 'w', KeyCode::X if shift => 'X', KeyCode::X => 'x', KeyCode::Y if shift => 'Y', KeyCode::Y => 'y', KeyCode::Z if shift => 'Z', KeyCode::Z => 'z', KeyCode::Row1 if shift => '!', KeyCode::Row1 => '1', KeyCode::Row2 if shift => '@', KeyCode::Row2 => '2', KeyCode::Row3 if shift => '#', KeyCode::Row3 => '3', KeyCode::Row4 if shift => '$', KeyCode::Row4 => '4', KeyCode::Row5 if shift => '%', KeyCode::Row5 => '5', KeyCode::Row6 if shift => '^', KeyCode::Row6 => '6', KeyCode::Row7 if shift => '&', KeyCode::Row7 => '7', KeyCode::Row8 if shift => '*', KeyCode::Row8 => '8', KeyCode::Row9 if shift => '(', KeyCode::Row9 => '9', KeyCode::Row0 if shift => ')', KeyCode::Row0 => '0', KeyCode::Grave if shift => '~', KeyCode::Grave => '`', KeyCode::Minus if shift => '_', KeyCode::Minus => '-', KeyCode::Equals if shift => '+', KeyCode::Equals => '=', KeyCode::LeftBracket if shift => '{', KeyCode::LeftBracket => '[', KeyCode::RightBracket if shift => '}', KeyCode::RightBracket => ']', KeyCode::Backslash if shift => '|', KeyCode::Backslash => '\\', KeyCode::Semicolon if shift => ':', KeyCode::Semicolon => ';', KeyCode::Apostrophe if shift => '"', KeyCode::Apostrophe => '\'', KeyCode::Comma if shift => '<', KeyCode::Comma => ',', KeyCode::Period if shift => '>', KeyCode::Period => '.', KeyCode::Slash if shift => '?', KeyCode::Slash => '/', KeyCode::Space => ' ', KeyCode::NumDivide => '/', KeyCode::NumMultiply => '*', KeyCode::NumMinus => '-', KeyCode::NumPlus => '+', KeyCode::NumPeriod => '.', KeyCode::Num1 => '1', KeyCode::Num2 => '2', KeyCode::Num3 => '3', KeyCode::Num4 => '4', KeyCode::Num5 => '5', KeyCode::Num6 => '6', KeyCode::Num7 => '7', KeyCode::Num8 => '8', KeyCode::Num9 => '9', KeyCode::Num0 => '0', _ => unreachable!("Found unknown input: {:?}", kc), } } fn blt_keycode_to_key(kc: KeyCode) -> Key { match kc { KeyCode::F1 => Key::F1, KeyCode::F2 => Key::F2, KeyCode::F3 => Key::F3, KeyCode::F4 => Key::F4, KeyCode::F5 => Key::F5, KeyCode::F6 => Key::F6, KeyCode::F7 => Key::F7, KeyCode::F8 => Key::F8, KeyCode::F9 => Key::F9, KeyCode::F10 => Key::F10, KeyCode::F11 => Key::F11, KeyCode::F12 => Key::F12, KeyCode::NumEnter | KeyCode::Enter => Key::Enter, KeyCode::Escape => Key::Esc, KeyCode::Backspace => Key::Backspace, KeyCode::Tab => Key::Tab, KeyCode::Pause => Key::PauseBreak, KeyCode::Insert => Key::Ins, KeyCode::Home => Key::Home, KeyCode::PageUp => Key::PageUp, KeyCode::Delete => Key::Del, KeyCode::End => Key::End, KeyCode::PageDown => Key::PageDown, KeyCode::Right => Key::Right, KeyCode::Left => Key::Left, KeyCode::Down => Key::Down, KeyCode::Up => Key::Up, _ => unreachable!(), } } fn blt_keycode_to_mouse_button(kc: KeyCode) -> Option { Some(match kc { KeyCode::MouseLeft => MouseButton::Left, KeyCode::MouseRight => MouseButton::Right, KeyCode::MouseMiddle => MouseButton::Middle, _ => return None, }) } cursive-0.21.1/src/backends/crossterm.rs000064400000000000000000000331331046102023000163200ustar 00000000000000//! Backend using the pure-rust crossplatform crossterm library. //! //! Requires the `crossterm-backend` feature. #![cfg(feature = "crossterm-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] use std::{ cell::{Cell, RefCell, RefMut}, io::{BufWriter, Write}, time::Duration, }; #[cfg(unix)] use std::fs::File; pub use crossterm; use crossterm::{ cursor, event::{ poll, read, DisableMouseCapture, EnableMouseCapture, Event as CEvent, KeyCode, KeyEvent as CKeyEvent, KeyEventKind, KeyModifiers, MouseButton as CMouseButton, MouseEvent as CMouseEvent, MouseEventKind, }, execute, queue, style::{Attribute, Color, Print, SetAttribute, SetBackgroundColor, SetForegroundColor}, terminal::{ self, disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, }, }; use crate::{ backend, event::{Event, Key, MouseButton, MouseEvent}, theme, Vec2, }; #[cfg(windows)] type Stdout = std::io::Stdout; #[cfg(unix)] type Stdout = std::fs::File; /// Backend using crossterm pub struct Backend { current_style: Cell, stdout: RefCell>, } fn translate_button(button: CMouseButton) -> MouseButton { match button { CMouseButton::Left => MouseButton::Left, CMouseButton::Right => MouseButton::Right, CMouseButton::Middle => MouseButton::Middle, } } fn translate_key(code: KeyCode) -> Option { Some(match code { KeyCode::Esc => Key::Esc, KeyCode::Backspace => Key::Backspace, KeyCode::Left => Key::Left, KeyCode::Right => Key::Right, KeyCode::Up => Key::Up, KeyCode::Down => Key::Down, KeyCode::Home => Key::Home, KeyCode::End => Key::End, KeyCode::PageUp => Key::PageUp, KeyCode::PageDown => Key::PageDown, KeyCode::Delete => Key::Del, KeyCode::Insert => Key::Ins, KeyCode::Enter => Key::Enter, KeyCode::Tab => Key::Tab, KeyCode::F(n) => Key::from_f(n), KeyCode::BackTab => Key::Tab, /* not supported */ // These should never occur. _ => return None, }) } fn translate_event(event: CKeyEvent) -> Option { const CTRL_ALT: KeyModifiers = KeyModifiers::from_bits_truncate(KeyModifiers::CONTROL.bits() | KeyModifiers::ALT.bits()); const CTRL_SHIFT: KeyModifiers = KeyModifiers::from_bits_truncate(KeyModifiers::CONTROL.bits() | KeyModifiers::SHIFT.bits()); const ALT_SHIFT: KeyModifiers = KeyModifiers::from_bits_truncate(KeyModifiers::ALT.bits() | KeyModifiers::SHIFT.bits()); if event.kind == KeyEventKind::Press { Some(match event { // Handle Char + modifier. CKeyEvent { modifiers: KeyModifiers::CONTROL, code: KeyCode::Char(c), .. } => Event::CtrlChar(c), CKeyEvent { modifiers: KeyModifiers::ALT, code: KeyCode::Char(c), .. } => Event::AltChar(c), CKeyEvent { modifiers: KeyModifiers::SHIFT, code: KeyCode::Char(c), .. } => Event::Char(c), CKeyEvent { code: KeyCode::Char(c), .. } => Event::Char(c), // From now on, assume the key is never a `Char`. // Explicitly handle 'backtab' since crossterm does not sent SHIFT alongside the back tab key. CKeyEvent { code: KeyCode::BackTab, .. } => Event::Shift(Key::Tab), // Handle key + multiple modifiers CKeyEvent { modifiers: CTRL_ALT, code, .. } => Event::CtrlAlt(translate_key(code)?), CKeyEvent { modifiers: CTRL_SHIFT, code, .. } => Event::CtrlShift(translate_key(code)?), CKeyEvent { modifiers: ALT_SHIFT, code, .. } => Event::AltShift(translate_key(code)?), // Handle key + single modifier CKeyEvent { modifiers: KeyModifiers::CONTROL, code, .. } => Event::Ctrl(translate_key(code)?), CKeyEvent { modifiers: KeyModifiers::ALT, code, .. } => Event::Alt(translate_key(code)?), CKeyEvent { modifiers: KeyModifiers::SHIFT, code, .. } => Event::Shift(translate_key(code)?), // All other keys. CKeyEvent { code, .. } => Event::Key(translate_key(code)?), }) } else { None } } fn translate_color(base_color: theme::Color) -> Color { match base_color { theme::Color::Dark(theme::BaseColor::Black) => Color::Black, theme::Color::Dark(theme::BaseColor::Red) => Color::DarkRed, theme::Color::Dark(theme::BaseColor::Green) => Color::DarkGreen, theme::Color::Dark(theme::BaseColor::Yellow) => Color::DarkYellow, theme::Color::Dark(theme::BaseColor::Blue) => Color::DarkBlue, theme::Color::Dark(theme::BaseColor::Magenta) => Color::DarkMagenta, theme::Color::Dark(theme::BaseColor::Cyan) => Color::DarkCyan, theme::Color::Dark(theme::BaseColor::White) => Color::Grey, theme::Color::Light(theme::BaseColor::Black) => Color::DarkGrey, theme::Color::Light(theme::BaseColor::Red) => Color::Red, theme::Color::Light(theme::BaseColor::Green) => Color::Green, theme::Color::Light(theme::BaseColor::Yellow) => Color::Yellow, theme::Color::Light(theme::BaseColor::Blue) => Color::Blue, theme::Color::Light(theme::BaseColor::Magenta) => Color::Magenta, theme::Color::Light(theme::BaseColor::Cyan) => Color::Cyan, theme::Color::Light(theme::BaseColor::White) => Color::White, theme::Color::Rgb(r, g, b) => Color::Rgb { r, g, b }, theme::Color::RgbLowRes(r, g, b) => { debug_assert!( r <= 5, "Red color fragment (r = {r}) is out of bound. Make sure r ≤ 5." ); debug_assert!( g <= 5, "Green color fragment (g = {g}) is out of bound. Make sure g ≤ 5." ); debug_assert!( b <= 5, "Blue color fragment (b = {b}) is out of bound. Make sure b ≤ 5." ); Color::AnsiValue(16 + 36 * r + 6 * g + b) } theme::Color::TerminalDefault => Color::Reset, } } impl Backend { /// Creates a new crossterm backend. pub fn init() -> Result, std::io::Error> where Self: Sized, { #[cfg(unix)] let stdout = std::fs::File::create("/dev/tty")?; #[cfg(windows)] let stdout = std::io::stdout(); Self::init_with_stdout(stdout) } fn init_with_stdout(mut stdout: Stdout) -> Result, std::io::Error> where Self: Sized, { enable_raw_mode()?; execute!( stdout, EnterAlternateScreen, EnableMouseCapture, cursor::Hide )?; Ok(Box::new(Backend { current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), stdout: RefCell::new(BufWriter::new(stdout)), })) } /// Create a new crossterm backend with provided output file. Unix only #[cfg(unix)] pub fn init_with_stdout_file(outfile: File) -> Result, std::io::Error> where Self: Sized, { Self::init_with_stdout(outfile) } fn apply_colors(&self, colors: theme::ColorPair) { self.with_stdout(|stdout| { queue!( stdout, SetForegroundColor(translate_color(colors.front)), SetBackgroundColor(translate_color(colors.back)) ) .unwrap() }); } fn stdout_mut(&self) -> RefMut> { self.stdout.borrow_mut() } fn with_stdout(&self, f: impl FnOnce(&mut BufWriter)) { f(&mut self.stdout_mut()); } fn set_attr(&self, attr: Attribute) { self.with_stdout(|stdout| queue!(stdout, SetAttribute(attr)).unwrap()); } fn map_key(&mut self, event: CEvent) -> Option { Some(match event { CEvent::Key(key_event) => translate_event(key_event)?, CEvent::Mouse(CMouseEvent { kind, column, row, modifiers: _, }) => { let position = (column, row).into(); let event = match kind { MouseEventKind::Down(button) => MouseEvent::Press(translate_button(button)), MouseEventKind::Up(button) => MouseEvent::Release(translate_button(button)), MouseEventKind::Drag(button) => MouseEvent::Hold(translate_button(button)), MouseEventKind::Moved => { return None; } MouseEventKind::ScrollDown => MouseEvent::WheelDown, MouseEventKind::ScrollUp => MouseEvent::WheelUp, MouseEventKind::ScrollLeft | MouseEventKind::ScrollRight => { // TODO: Currently unsupported. return None; } }; Event::Mouse { event, position, offset: Vec2::zero(), } } CEvent::Resize(_, _) => Event::WindowResize, CEvent::Paste(_) => { unreachable!("Did not enable bracketed paste.") } CEvent::FocusGained | CEvent::FocusLost => return None, }) } } impl Drop for Backend { fn drop(&mut self) { // We have to execute the show cursor command at the `stdout`. self.with_stdout(|stdout| { execute!( stdout, SetForegroundColor(Color::Reset), SetBackgroundColor(Color::Reset), LeaveAlternateScreen, DisableMouseCapture, cursor::Show, cursor::MoveTo(0, 0), terminal::Clear(terminal::ClearType::All) ) .expect("Can not disable mouse capture or show cursor.") }); disable_raw_mode().unwrap(); } } impl backend::Backend for Backend { fn is_persistent(&self) -> bool { true } fn poll_event(&mut self) -> Option { match poll(Duration::from_millis(1)) { Ok(true) => match read() { Ok(event) => match self.map_key(event) { Some(event) => Some(event), None => self.poll_event(), }, Err(e) => panic!("{e:?}"), }, _ => None, } } fn set_title(&mut self, title: String) { self.with_stdout(|stdout| execute!(stdout, terminal::SetTitle(title)).unwrap()); } fn refresh(&mut self) { self.with_stdout(|stdout| stdout.flush().unwrap()); } fn has_colors(&self) -> bool { // TODO: color support detection? true } fn screen_size(&self) -> Vec2 { let size = terminal::size().unwrap_or((1, 1)); Vec2::from(size) } fn move_to(&self, pos: Vec2) { self.with_stdout(|stdout| { queue!(stdout, cursor::MoveTo(pos.x as u16, pos.y as u16)).unwrap() }); } fn print(&self, text: &str) { self.with_stdout(|stdout| queue!(stdout, Print(text)).unwrap()); } fn clear(&self, color: theme::Color) { self.apply_colors(theme::ColorPair { front: color, back: color, }); self.with_stdout(|stdout| queue!(stdout, Clear(ClearType::All)).unwrap()); } fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair { let current_style = self.current_style.get(); if current_style != color { self.apply_colors(color); self.current_style.set(color); } current_style } fn set_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.set_attr(Attribute::Reverse), theme::Effect::Dim => self.set_attr(Attribute::Dim), theme::Effect::Bold => self.set_attr(Attribute::Bold), theme::Effect::Blink => self.set_attr(Attribute::SlowBlink), theme::Effect::Italic => self.set_attr(Attribute::Italic), theme::Effect::Strikethrough => self.set_attr(Attribute::CrossedOut), theme::Effect::Underline => self.set_attr(Attribute::Underlined), } } fn unset_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.set_attr(Attribute::NoReverse), theme::Effect::Dim | theme::Effect::Bold => self.set_attr(Attribute::NormalIntensity), theme::Effect::Blink => self.set_attr(Attribute::NoBlink), theme::Effect::Italic => self.set_attr(Attribute::NoItalic), theme::Effect::Strikethrough => self.set_attr(Attribute::NotCrossedOut), theme::Effect::Underline => self.set_attr(Attribute::NoUnderline), } } fn name(&self) -> &str { "crossterm" } } cursive-0.21.1/src/backends/curses/mod.rs000064400000000000000000000111011046102023000163510ustar 00000000000000//! Common module for the ncurses and pancurses backends. //! //! Requires either of `ncurses-backend` or `pancurses-backend`. #![cfg(any(feature = "ncurses-backend", feature = "pancurses-backend"))] use crate::event::{Event, Key}; use crate::theme::{BaseColor, Color, ColorPair}; use maplit::hashmap; pub mod n; pub mod pan; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Split a i32 into individual bytes, little endian (least significant byte first). fn split_i32(code: i32) -> Vec { (0..4).map(|i| ((code >> (8 * i)) & 0xFF) as u8).collect() } fn fill_key_codes(target: &mut HashMap, f: F) where F: Fn(i32) -> Option, { let key_names = hashmap! { "DC" => Key::Del, "DN" => Key::Down, "END" => Key::End, "HOM" => Key::Home, "IC" => Key::Ins, "LFT" => Key::Left, "NXT" => Key::PageDown, "PRV" => Key::PageUp, "RIT" => Key::Right, "UP" => Key::Up, }; for code in 512..1024 { let name = match f(code) { Some(name) => name, None => continue, }; if !name.starts_with('k') { continue; } let (key_name, modifier) = name[1..].split_at(name.len() - 2); let key = match key_names.get(key_name) { Some(&key) => key, None => continue, }; let event = match modifier { "3" => Event::Alt(key), "4" => Event::AltShift(key), "5" => Event::Ctrl(key), "6" => Event::CtrlShift(key), "7" => Event::CtrlAlt(key), _ => continue, }; target.insert(code, event); } } fn find_closest_pair(pair: ColorPair, max_colors: i16) -> (i16, i16) { ( find_closest(pair.front, max_colors), find_closest(pair.back, max_colors), ) } /// Finds the closest index in the 256-color palette. /// /// If `max_colors` is less than 256 (like 8 or 16), the color will be /// downgraded to the closest one available. fn find_closest(color: Color, max_colors: i16) -> i16 { let max_colors = std::cmp::max(max_colors, 8); match color { Color::TerminalDefault => -1, Color::Dark(BaseColor::Black) => 0, Color::Dark(BaseColor::Red) => 1, Color::Dark(BaseColor::Green) => 2, Color::Dark(BaseColor::Yellow) => 3, Color::Dark(BaseColor::Blue) => 4, Color::Dark(BaseColor::Magenta) => 5, Color::Dark(BaseColor::Cyan) => 6, Color::Dark(BaseColor::White) => 7, Color::Light(BaseColor::Black) => 8 % max_colors, Color::Light(BaseColor::Red) => 9 % max_colors, Color::Light(BaseColor::Green) => 10 % max_colors, Color::Light(BaseColor::Yellow) => 11 % max_colors, Color::Light(BaseColor::Blue) => 12 % max_colors, Color::Light(BaseColor::Magenta) => 13 % max_colors, Color::Light(BaseColor::Cyan) => 14 % max_colors, Color::Light(BaseColor::White) => 15 % max_colors, Color::Rgb(r, g, b) if max_colors >= 256 => { // If r = g = b, it may be a grayscale value! // Grayscale colors have a bit higher resolution than the rest of // the palette, so if we can use it we should! // // r=g=b < 8 should go to pure black instead. // r=g=b >= 247 should go to pure white. // TODO: project almost-gray colors as well? if r == g && g == b && (8..247).contains(&r) { // The grayscale palette says the colors 232+n are: // (r = g = b) = 8 + 10 * n // With 0 <= n <= 23. This gives: // (r - 8) / 10 = n let n = (r - 8) / 10; i16::from(232 + n) } else { // Generic RGB let r = 6 * u16::from(r) / 256; let g = 6 * u16::from(g) / 256; let b = 6 * u16::from(b) / 256; (16 + 36 * r + 6 * g + b) as i16 } } Color::Rgb(r, g, b) => { // Have to hack it down to 8 colors. let r = i16::from(r > 127); let g = i16::from(g > 127); let b = i16::from(b > 127); r + 2 * g + 4 * b } Color::RgbLowRes(r, g, b) if max_colors >= 256 => i16::from(16 + 36 * r + 6 * g + b), Color::RgbLowRes(r, g, b) => { let r = i16::from(r > 2); let g = i16::from(g > 2); let b = i16::from(b > 2); r + 2 * g + 4 * b } } } cursive-0.21.1/src/backends/curses/n.rs000064400000000000000000000454531046102023000160500ustar 00000000000000//! Ncurses-specific backend. #![cfg(feature = "ncurses-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub use ncurses; use log::{debug, warn}; use ncurses::mmask_t; use std::cell::{Cell, RefCell}; use std::ffi::CString; use std::fs::File; use std::io; use std::io::Write; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{Color, ColorPair, Effect}; use crate::utf8; use crate::Vec2; use super::split_i32; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Backend using ncurses. pub struct Backend { current_style: Cell, // Maps (front, back) ncurses colors to ncurses pairs pairs: RefCell>, // Pre-computed map of ncurses codes to parsed Event key_codes: HashMap, // Remember the last pressed button to correctly feed Released Event last_mouse_button: Option, // Sometimes a code from ncurses should be split in two Events. // // So remember the one we didn't return. input_buffer: Option, } fn find_closest_pair(pair: ColorPair) -> (i16, i16) { super::find_closest_pair(pair, ncurses::COLORS() as i16) } /// Writes some bytes directly to `/dev/tty` /// /// Since this is not going to be used often, we can afford to re-open the /// file every time. fn write_to_tty(bytes: &[u8]) -> io::Result<()> { let mut tty_output = File::create("/dev/tty").expect("cursive can only run with a tty"); tty_output.write_all(bytes)?; // tty_output will be flushed automatically at the end of the function. Ok(()) } impl Backend { /// Creates a new ncurses-based backend. /// /// Uses `/dev/tty` for input/output. pub fn init() -> io::Result> { Self::init_with_files("/dev/tty", "/dev/tty") } /// Creates a new ncurses-based backend. /// /// Uses stdin/stdout for input/output. pub fn init_stdio() -> io::Result> { Self::init_with_files("/dev/stdin", "/dev/stdout") } /// Creates a new ncurses-based backend using the given files for input/output. pub fn init_with_files( input_path: &str, output_path: &str, ) -> io::Result> { // Check the $TERM variable. if std::env::var("TERM") .map(|var| var.is_empty()) .unwrap_or(true) { return Err(io::Error::new( io::ErrorKind::Other, "$TERM is unset. Cannot initialize ncurses interface.", )); } // Change the locale. // For some reasons it's mandatory to get some UTF-8 support. let buf = CString::new("").unwrap(); unsafe { libc::setlocale(libc::LC_ALL, buf.as_ptr()) }; // The delay is the time ncurses wait after pressing ESC // to see if it's an escape sequence. // Default delay is way too long. 25 is imperceptible yet works fine. ::std::env::set_var("ESCDELAY", "25"); // Don't output to standard IO, directly feed into /dev/tty // This leaves stdin and stdout usable for other purposes. let input = { let mode = CString::new("r").unwrap(); let path = CString::new(input_path).unwrap(); unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) } }; let output = { let mode = CString::new("w").unwrap(); let path = CString::new(output_path).unwrap(); unsafe { libc::fopen(path.as_ptr(), mode.as_ptr()) } }; ncurses::newterm(None, output, input).map_err(|e| { io::Error::new(io::ErrorKind::Other, format!("could not call newterm: {e}")) })?; // Enable keypad (like arrows) ncurses::keypad(ncurses::stdscr(), true); // This disables mouse click detection, // and provides 0-delay access to mouse presses. ncurses::mouseinterval(0); // Listen to all mouse events. ncurses::mousemask( (ncurses::ALL_MOUSE_EVENTS | ncurses::REPORT_MOUSE_POSITION) as mmask_t, None, ); // Enable non-blocking input, so getch() immediately returns. ncurses::timeout(0); // Don't echo user input, we'll take care of that ncurses::noecho(); // This disables buffering and some input processing. ncurses::raw(); // This enables color support. ncurses::start_color(); // Pick up background and text color from the terminal theme. ncurses::use_default_colors(); // Don't print cursors. ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); // This asks the terminal to provide us with mouse drag events // (Mouse move when a button is pressed). // Replacing 1002 with 1003 would give us ANY mouse move. write_to_tty(b"\x1B[?1002h")?; let c = Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::default()), key_codes: initialize_keymap(), last_mouse_button: None, input_buffer: None, }; Ok(Box::new(c)) } /// Save a new color pair. fn insert_color(&self, pairs: &mut HashMap<(i16, i16), i16>, (front, back): (i16, i16)) -> i16 { let n = 1 + pairs.len() as i16; let target = if ncurses::COLOR_PAIRS() > i32::from(n) { // We still have plenty of space for everyone. n } else { // The world is too small for both of us. let target = n - 1; // Remove the mapping to n-1 pairs.retain(|_, &mut v| v != target); target }; pairs.insert((front, back), target); ncurses::init_pair(target, front, back); target } /// Checks the pair in the cache, or re-define a color if needed. fn get_or_create(&self, pair: ColorPair) -> i16 { let mut pairs = self.pairs.borrow_mut(); // Find if we have this color in stock let result = find_closest_pair(pair); let lookup = pairs.get(&result).copied(); lookup.unwrap_or_else(|| self.insert_color(&mut pairs, result)) } fn set_colors(&self, pair: ColorPair) { let i = self.get_or_create(pair); self.current_style.set(pair); let style = ncurses::COLOR_PAIR(i); ncurses::attron(style); } fn parse_next(&mut self) -> Option { if let Some(event) = self.input_buffer.take() { return Some(event); } let ch: i32 = ncurses::getch(); // Non-blocking input will return -1 as long as no input is available. if ch == -1 { return None; } // Is it a UTF-8 starting point? let event = if (32..=255).contains(&ch) && ch != 127 { utf8::read_char(ch as u8, || Some(ncurses::getch() as u8)) .map(Event::Char) .unwrap_or_else(|e| { warn!("Error reading input: {e}"); Event::Unknown(vec![ch as u8]) }) } else { self.parse_ncurses_char(ch) }; Some(event) } fn parse_ncurses_char(&mut self, ch: i32) -> Event { // eprintln!("Found {:?}", ncurses::keyname(ch)); if ch == ncurses::KEY_MOUSE { self.parse_mouse_event() } else { self.key_codes .get(&ch) .cloned() .unwrap_or_else(|| Event::Unknown(split_i32(ch))) } } fn parse_mouse_event(&mut self) -> Event { let mut mevent = ncurses::MEVENT { id: 0, x: 0, y: 0, z: 0, bstate: 0, }; if ncurses::getmouse(&mut mevent as *mut ncurses::MEVENT) == ncurses::OK { // Currently unused let _ctrl = (mevent.bstate & ncurses::BUTTON_CTRL as mmask_t) != 0; let _shift = (mevent.bstate & ncurses::BUTTON_SHIFT as mmask_t) != 0; let _alt = (mevent.bstate & ncurses::BUTTON_ALT as mmask_t) != 0; // Keep the base state, without the modifiers mevent.bstate &= !(ncurses::BUTTON_SHIFT | ncurses::BUTTON_ALT | ncurses::BUTTON_CTRL) as mmask_t; // This makes a full `Event` from a `MouseEvent`. let make_event = |event| Event::Mouse { offset: Vec2::zero(), position: Vec2::new(mevent.x as usize, mevent.y as usize), event, }; if mevent.bstate == ncurses::REPORT_MOUSE_POSITION as mmask_t { // The event is either a mouse drag event, // or a weird double-release event. :S self.last_mouse_button .map(MouseEvent::Hold) .or_else(|| { // In legacy mode, some buttons overlap, // so we need to disambiguate. (mevent.bstate == ncurses::BUTTON5_DOUBLE_CLICKED as mmask_t) .then_some(MouseEvent::WheelDown) }) .map(make_event) .unwrap_or_else(|| Event::Unknown(vec![])) } else { // Identify the button let mut bare_event = mevent.bstate & ((1 << 25) - 1); let mut event = None; // ncurses encodes multiple events in the same value. while bare_event != 0 { let single_event = 1 << bare_event.trailing_zeros(); bare_event ^= single_event; // Process single_event on_mouse_event(single_event as i32, |e| { // Keep one event for later, // send the rest through the channel. if event.is_none() { event = Some(e); } else { self.input_buffer = Some(make_event(e)); } }); } if let Some(event) = event { match event { MouseEvent::Press(btn) => { self.last_mouse_button = Some(btn); } MouseEvent::Release(_) => { self.last_mouse_button = None; } _ => (), } make_event(event) } else { debug!("No event parsed?..."); Event::Unknown(vec![]) } } } else { debug!("Ncurses event not recognized."); Event::Unknown(vec![]) } } } impl Drop for Backend { fn drop(&mut self) { write_to_tty(b"\x1B[?1002l").unwrap(); ncurses::endwin(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "ncurses" } fn is_persistent(&self) -> bool { true } fn set_title(&mut self, title: String) { write_to_tty(format!("\x1B]0;{title}\x07").as_bytes()).unwrap(); } fn screen_size(&self) -> Vec2 { let mut x: i32 = 0; let mut y: i32 = 0; ncurses::getmaxyx(ncurses::stdscr(), &mut y, &mut x); (x, y).into() } fn has_colors(&self) -> bool { ncurses::has_colors() } fn poll_event(&mut self) -> Option { self.parse_next() } fn set_color(&self, colors: ColorPair) -> ColorPair { // eprintln!("Color used: {:?}", colors); let current = self.current_style.get(); if current != colors { self.set_colors(colors); } current } fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Reverse => ncurses::A_REVERSE, Effect::Simple => ncurses::A_NORMAL, Effect::Dim => ncurses::A_DIM, Effect::Bold => ncurses::A_BOLD, Effect::Blink => ncurses::A_BLINK, Effect::Italic => ncurses::A_ITALIC, Effect::Strikethrough => ncurses::A_NORMAL, Effect::Underline => ncurses::A_UNDERLINE, }; ncurses::attron(style); } fn unset_effect(&self, effect: Effect) { let style = match effect { Effect::Reverse => ncurses::A_REVERSE, Effect::Simple => ncurses::A_NORMAL, Effect::Dim => ncurses::A_DIM, Effect::Bold => ncurses::A_BOLD, Effect::Blink => ncurses::A_BLINK, Effect::Italic => ncurses::A_ITALIC, Effect::Strikethrough => ncurses::A_NORMAL, Effect::Underline => ncurses::A_UNDERLINE, }; ncurses::attroff(style); } fn clear(&self, color: Color) { let id = self.get_or_create(ColorPair { front: color, back: color, }); ncurses::wbkgd(ncurses::stdscr(), ncurses::COLOR_PAIR(id)); ncurses::clear(); } fn refresh(&mut self) { ncurses::refresh(); } fn move_to(&self, pos: Vec2) { ncurses::mv(pos.y as i32, pos.x as i32); } fn print(&self, text: &str) { // &str is assured it doesn't contain any \0 aka nuls here due to PR 786 // thus we can ignore the return value and avoid warning: unused `Result` that must be used let _ = ncurses::addstr(text); } } /// Returns the Key enum corresponding to the given ncurses event. fn get_mouse_button(bare_event: i32) -> MouseButton { match bare_event { ncurses::BUTTON1_RELEASED | ncurses::BUTTON1_PRESSED | ncurses::BUTTON1_CLICKED | ncurses::BUTTON1_DOUBLE_CLICKED | ncurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left, ncurses::BUTTON2_RELEASED | ncurses::BUTTON2_PRESSED | ncurses::BUTTON2_CLICKED | ncurses::BUTTON2_DOUBLE_CLICKED | ncurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle, ncurses::BUTTON3_RELEASED | ncurses::BUTTON3_PRESSED | ncurses::BUTTON3_CLICKED | ncurses::BUTTON3_DOUBLE_CLICKED | ncurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right, ncurses::BUTTON4_RELEASED | ncurses::BUTTON4_PRESSED | ncurses::BUTTON4_CLICKED | ncurses::BUTTON4_DOUBLE_CLICKED | ncurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4, ncurses::BUTTON5_RELEASED | ncurses::BUTTON5_PRESSED | ncurses::BUTTON5_CLICKED | ncurses::BUTTON5_DOUBLE_CLICKED | ncurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5, _ => MouseButton::Other, } } /// Parse the given code into one or more event. /// /// If the given event code should expend into multiple events /// (for instance click expends into PRESS + RELEASE), /// the returned Vec will include those queued events. /// /// The main event is returned separately to avoid allocation in most cases. fn on_mouse_event(bare_event: i32, mut f: F) where F: FnMut(MouseEvent), { let button = get_mouse_button(bare_event); match bare_event { ncurses::BUTTON1_RELEASED | ncurses::BUTTON2_RELEASED | ncurses::BUTTON3_RELEASED => { f(MouseEvent::Release(button)) } ncurses::BUTTON1_PRESSED | ncurses::BUTTON2_PRESSED | ncurses::BUTTON3_PRESSED => { f(MouseEvent::Press(button)) } ncurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp), ncurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown), // BUTTON4_RELEASED? BUTTON5_RELEASED? // Do they ever happen? _ => debug!("Unknown event: {:032b}", bare_event), } } fn add_fn(start: i32, with_key: F, map: &mut HashMap) where F: Fn(Key) -> Event, { for i in 0..12 { map.insert(start + i, with_key(Key::from_f((i + 1) as u8))); } } fn initialize_keymap() -> HashMap { // First, define the static mappings. let mut map = HashMap::default(); // Value sent by ncurses when nothing happens map.insert(-1, Event::Refresh); // Values under 256 are chars and control values // Tab is '\t' map.insert(9, Event::Key(Key::Tab)); // Treat '\n' and the numpad Enter the same map.insert(10, Event::Key(Key::Enter)); map.insert(ncurses::KEY_ENTER, Event::Key(Key::Enter)); // This is the escape key when pressed by itself. // When used for control sequences, // it should have been caught earlier. map.insert(27, Event::Key(Key::Esc)); // `Backspace` sends 127, but Ctrl-H sends `Backspace` map.insert(127, Event::Key(Key::Backspace)); map.insert(ncurses::KEY_BACKSPACE, Event::Key(Key::Backspace)); map.insert(410, Event::WindowResize); map.insert(ncurses::KEY_B2, Event::Key(Key::NumpadCenter)); map.insert(ncurses::KEY_DC, Event::Key(Key::Del)); map.insert(ncurses::KEY_IC, Event::Key(Key::Ins)); map.insert(ncurses::KEY_BTAB, Event::Shift(Key::Tab)); map.insert(ncurses::KEY_SLEFT, Event::Shift(Key::Left)); map.insert(ncurses::KEY_SRIGHT, Event::Shift(Key::Right)); map.insert(ncurses::KEY_LEFT, Event::Key(Key::Left)); map.insert(ncurses::KEY_RIGHT, Event::Key(Key::Right)); map.insert(ncurses::KEY_UP, Event::Key(Key::Up)); map.insert(ncurses::KEY_DOWN, Event::Key(Key::Down)); map.insert(ncurses::KEY_SR, Event::Shift(Key::Up)); map.insert(ncurses::KEY_SF, Event::Shift(Key::Down)); map.insert(ncurses::KEY_PPAGE, Event::Key(Key::PageUp)); map.insert(ncurses::KEY_NPAGE, Event::Key(Key::PageDown)); map.insert(ncurses::KEY_HOME, Event::Key(Key::Home)); map.insert(ncurses::KEY_END, Event::Key(Key::End)); map.insert(ncurses::KEY_SHOME, Event::Shift(Key::Home)); map.insert(ncurses::KEY_SEND, Event::Shift(Key::End)); map.insert(ncurses::KEY_SDC, Event::Shift(Key::Del)); map.insert(ncurses::KEY_SNEXT, Event::Shift(Key::PageDown)); map.insert(ncurses::KEY_SPREVIOUS, Event::Shift(Key::PageUp)); // Then add some dynamic ones for c in 1..=26 { let event = match c { // Ctrl-i and Ctrl-j are special, they use the same codes as Tab // and Enter respectively. There's just no way to detect them. :( 9 => Event::Key(Key::Tab), 10 => Event::Key(Key::Enter), other => Event::CtrlChar((b'a' - 1 + other as u8) as char), }; map.insert(c, event); } // Ncurses provides a F1 variable, but no modifiers add_fn(ncurses::KEY_F(1), Event::Key, &mut map); add_fn(277, Event::Shift, &mut map); add_fn(289, Event::Ctrl, &mut map); add_fn(301, Event::CtrlShift, &mut map); add_fn(313, Event::Alt, &mut map); // Those codes actually vary between ncurses versions... super::fill_key_codes(&mut map, ncurses::keyname); map } cursive-0.21.1/src/backends/curses/pan.rs000064400000000000000000000535071046102023000163700ustar 00000000000000//! Pancuses-specific backend. #![cfg(feature = "pancurses-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub use pancurses; use log::{debug, warn}; use std::cell::{Cell, RefCell}; use std::io::{stdout, Write}; use crate::backend; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme::{Color, ColorPair, Effect}; use crate::Vec2; use super::split_i32; use pancurses::mmask_t; // Use AHash instead of the slower SipHash type HashMap = std::collections::HashMap; /// Backend using pancurses. pub struct Backend { // Used current_style: Cell, pairs: RefCell>, // pancurses needs a handle to the current window. window: pancurses::Window, key_codes: HashMap, last_mouse_button: Option, input_buffer: Option, } fn find_closest_pair(pair: ColorPair) -> (i16, i16) { super::find_closest_pair(pair, pancurses::COLORS() as i16) } impl Backend { /// Creates a new pancurses-based backend. pub fn init() -> std::io::Result> { // Check the $TERM variable (at least on unix). // Otherwise we'll just abort. // TODO: On windows, is there anything to check? if cfg!(unix) && std::env::var("TERM") .map(|var| var.is_empty()) .unwrap_or(true) { return Err(std::io::Error::new( std::io::ErrorKind::Other, "$TERM is unset. Cannot initialize pancurses interface.", )); } ::std::env::set_var("ESCDELAY", "25"); if cfg!(unix) { let buf = std::ffi::CString::new("").unwrap(); unsafe { libc::setlocale(libc::LC_ALL, buf.as_ptr()) }; } // TODO: use pancurses::newterm() let window = pancurses::initscr(); window.keypad(true); window.timeout(0); pancurses::noecho(); pancurses::raw(); pancurses::start_color(); pancurses::use_default_colors(); pancurses::curs_set(0); pancurses::mouseinterval(0); pancurses::mousemask( pancurses::ALL_MOUSE_EVENTS | pancurses::REPORT_MOUSE_POSITION, None, ); // This asks the terminal to provide us with mouse drag events // (Mouse move when a button is pressed). // Replacing 1002 with 1003 would give us ANY mouse move. #[cfg(not(windows))] print!("\x1B[?1002h"); stdout().flush()?; let c = Backend { current_style: Cell::new(ColorPair::from_256colors(0, 0)), pairs: RefCell::new(HashMap::default()), key_codes: initialize_keymap(), last_mouse_button: None, input_buffer: None, window, }; Ok(Box::new(c)) } /// Save a new color pair. fn insert_color(&self, pairs: &mut HashMap<(i16, i16), i32>, (front, back): (i16, i16)) -> i32 { let n = 1 + pairs.len() as i32; // TODO: when COLORS_PAIRS is available... let target = if 256 > n { // We still have plenty of space for everyone. n } else { // The world is too small for both of us. let target = n - 1; // Remove the mapping to n-1 pairs.retain(|_, &mut v| v != target); target }; pairs.insert((front, back), target); pancurses::init_pair(target as i16, front, back); target } /// Checks the pair in the cache, or re-define a color if needed. fn get_or_create(&self, pair: ColorPair) -> i32 { let mut pairs = self.pairs.borrow_mut(); let pair = find_closest_pair(pair); // Find if we have this color in stock if pairs.contains_key(&pair) { // We got it! pairs[&pair] } else { self.insert_color(&mut pairs, pair) } } fn set_colors(&self, pair: ColorPair) { let i = self.get_or_create(pair); self.current_style.set(pair); let style = pancurses::COLOR_PAIR(i as pancurses::chtype); self.window.attron(style); } fn parse_next(&mut self) -> Option { if let Some(event) = self.input_buffer.take() { return Some(event); } if let Some(ev) = self.window.getch() { Some(match ev { pancurses::Input::Character('\n') | pancurses::Input::Character('\r') => { Event::Key(Key::Enter) } // TODO: wait for a very short delay. If more keys are // pipelined, it may be an escape sequence. pancurses::Input::Character('\u{7f}') | pancurses::Input::Character('\u{8}') => { Event::Key(Key::Backspace) } pancurses::Input::Character('\u{9}') => Event::Key(Key::Tab), pancurses::Input::Character('\u{1b}') => Event::Key(Key::Esc), // Ctrl+C pancurses::Input::Character(c) if (c as u32) <= 26 => { Event::CtrlChar((b'a' - 1 + c as u8) as char) } pancurses::Input::Character(c) => Event::Char(c), // TODO: Some key combos are not recognized by pancurses, // but are sent as Unknown. We could still parse them here. pancurses::Input::Unknown(code) => self .key_codes // pancurses does some weird keycode mapping .get(&(code + 256 + 48)) .cloned() .unwrap_or_else(|| { warn!("Unknown: {}", code); Event::Unknown(split_i32(code)) }), // TODO: I honestly have no fucking idea what KeyCodeYes is pancurses::Input::KeyCodeYes => Event::Refresh, pancurses::Input::KeyBreak => Event::Key(Key::PauseBreak), pancurses::Input::KeyDown => Event::Key(Key::Down), pancurses::Input::KeyUp => Event::Key(Key::Up), pancurses::Input::KeyLeft => Event::Key(Key::Left), pancurses::Input::KeyRight => Event::Key(Key::Right), pancurses::Input::KeyHome => Event::Key(Key::Home), pancurses::Input::KeyBackspace => Event::Key(Key::Backspace), pancurses::Input::KeyF0 => Event::Key(Key::F0), pancurses::Input::KeyF1 => Event::Key(Key::F1), pancurses::Input::KeyF2 => Event::Key(Key::F2), pancurses::Input::KeyF3 => Event::Key(Key::F3), pancurses::Input::KeyF4 => Event::Key(Key::F4), pancurses::Input::KeyF5 => Event::Key(Key::F5), pancurses::Input::KeyF6 => Event::Key(Key::F6), pancurses::Input::KeyF7 => Event::Key(Key::F7), pancurses::Input::KeyF8 => Event::Key(Key::F8), pancurses::Input::KeyF9 => Event::Key(Key::F9), pancurses::Input::KeyF10 => Event::Key(Key::F10), pancurses::Input::KeyF11 => Event::Key(Key::F11), pancurses::Input::KeyF12 => Event::Key(Key::F12), pancurses::Input::KeyF13 => Event::Shift(Key::F1), pancurses::Input::KeyF14 => Event::Shift(Key::F2), pancurses::Input::KeyF15 => Event::Shift(Key::F3), pancurses::Input::KeyDL => Event::Refresh, pancurses::Input::KeyIL => Event::Refresh, pancurses::Input::KeyDC => Event::Key(Key::Del), pancurses::Input::KeyIC => Event::Key(Key::Ins), pancurses::Input::KeyEIC => Event::Refresh, pancurses::Input::KeyClear => Event::Refresh, pancurses::Input::KeyEOS => Event::Refresh, pancurses::Input::KeyEOL => Event::Refresh, pancurses::Input::KeySF => Event::Shift(Key::Down), pancurses::Input::KeySR => Event::Shift(Key::Up), pancurses::Input::KeyNPage => Event::Key(Key::PageDown), pancurses::Input::KeyPPage => Event::Key(Key::PageUp), pancurses::Input::KeySTab => Event::Shift(Key::Tab), pancurses::Input::KeyCTab => Event::Ctrl(Key::Tab), pancurses::Input::KeyCATab => Event::CtrlAlt(Key::Tab), pancurses::Input::KeyEnter => Event::Key(Key::Enter), pancurses::Input::KeySReset => Event::Refresh, pancurses::Input::KeyReset => Event::Refresh, pancurses::Input::KeyPrint => Event::Refresh, pancurses::Input::KeyLL => Event::Refresh, pancurses::Input::KeyAbort => Event::Refresh, pancurses::Input::KeySHelp => Event::Refresh, pancurses::Input::KeyLHelp => Event::Refresh, pancurses::Input::KeyBTab => Event::Shift(Key::Tab), pancurses::Input::KeyBeg => Event::Refresh, pancurses::Input::KeyCancel => Event::Refresh, pancurses::Input::KeyClose => Event::Refresh, pancurses::Input::KeyCommand => Event::Refresh, pancurses::Input::KeyCopy => Event::Refresh, pancurses::Input::KeyCreate => Event::Refresh, pancurses::Input::KeyEnd => Event::Key(Key::End), pancurses::Input::KeyExit => Event::Refresh, pancurses::Input::KeyFind => Event::Refresh, pancurses::Input::KeyHelp => Event::Refresh, pancurses::Input::KeyMark => Event::Refresh, pancurses::Input::KeyMessage => Event::Refresh, pancurses::Input::KeyMove => Event::Refresh, pancurses::Input::KeyNext => Event::Refresh, pancurses::Input::KeyOpen => Event::Refresh, pancurses::Input::KeyOptions => Event::Refresh, pancurses::Input::KeyPrevious => Event::Refresh, pancurses::Input::KeyRedo => Event::Refresh, pancurses::Input::KeyReference => Event::Refresh, pancurses::Input::KeyRefresh => Event::Refresh, pancurses::Input::KeyReplace => Event::Refresh, pancurses::Input::KeyRestart => Event::Refresh, pancurses::Input::KeyResume => Event::Refresh, pancurses::Input::KeySave => Event::Refresh, pancurses::Input::KeySBeg => Event::Refresh, pancurses::Input::KeySCancel => Event::Refresh, pancurses::Input::KeySCommand => Event::Refresh, pancurses::Input::KeySCopy => Event::Refresh, pancurses::Input::KeySCreate => Event::Refresh, pancurses::Input::KeySDC => Event::Shift(Key::Del), pancurses::Input::KeySDL => Event::Refresh, pancurses::Input::KeySelect => Event::Refresh, pancurses::Input::KeySEnd => Event::Shift(Key::End), pancurses::Input::KeySEOL => Event::Refresh, pancurses::Input::KeySExit => Event::Refresh, pancurses::Input::KeySFind => Event::Refresh, pancurses::Input::KeySHome => Event::Shift(Key::Home), pancurses::Input::KeySIC => Event::Shift(Key::Ins), pancurses::Input::KeySLeft => Event::Shift(Key::Left), pancurses::Input::KeySMessage => Event::Refresh, pancurses::Input::KeySMove => Event::Refresh, pancurses::Input::KeySNext => Event::Shift(Key::PageDown), pancurses::Input::KeySOptions => Event::Refresh, pancurses::Input::KeySPrevious => Event::Shift(Key::PageUp), pancurses::Input::KeySPrint => Event::Refresh, pancurses::Input::KeySRedo => Event::Refresh, pancurses::Input::KeySReplace => Event::Refresh, pancurses::Input::KeySRight => Event::Shift(Key::Right), pancurses::Input::KeySResume => Event::Refresh, pancurses::Input::KeySSave => Event::Refresh, pancurses::Input::KeySSuspend => Event::Refresh, pancurses::Input::KeySUndo => Event::Refresh, pancurses::Input::KeySuspend => Event::Refresh, pancurses::Input::KeyUndo => Event::Refresh, pancurses::Input::KeyResize => { // Let pancurses adjust their structures when the // window is resized. // Do it for Windows only, as 'resize_term' is not // implemented for Unix if cfg!(target_os = "windows") { pancurses::resize_term(0, 0); } Event::WindowResize } pancurses::Input::KeyEvent => Event::Refresh, // TODO: mouse support pancurses::Input::KeyMouse => self.parse_mouse_event(), pancurses::Input::KeyA1 => Event::Refresh, pancurses::Input::KeyA3 => Event::Refresh, pancurses::Input::KeyB2 => Event::Key(Key::NumpadCenter), pancurses::Input::KeyC1 => Event::Refresh, pancurses::Input::KeyC3 => Event::Refresh, }) } else { None } } fn parse_mouse_event(&mut self) -> Event { let mut mevent = match pancurses::getmouse() { Err(code) => return Event::Unknown(split_i32(code)), Ok(event) => event, }; let _shift = (mevent.bstate & pancurses::BUTTON_SHIFT as mmask_t) != 0; let _alt = (mevent.bstate & pancurses::BUTTON_ALT as mmask_t) != 0; let _ctrl = (mevent.bstate & pancurses::BUTTON_CTRL as mmask_t) != 0; mevent.bstate &= !(pancurses::BUTTON_SHIFT | pancurses::BUTTON_ALT | pancurses::BUTTON_CTRL) as mmask_t; let make_event = |event| Event::Mouse { offset: Vec2::zero(), position: Vec2::new(mevent.x as usize, mevent.y as usize), event, }; if mevent.bstate == pancurses::REPORT_MOUSE_POSITION as mmask_t { // The event is either a mouse drag event, // or a weird double-release event. :S self.last_mouse_button .map(MouseEvent::Hold) .or_else(|| { // In legacy mode, some buttons overlap, // so we need to disambiguate. (mevent.bstate == pancurses::BUTTON5_DOUBLE_CLICKED as mmask_t) .then_some(MouseEvent::WheelDown) }) .map(make_event) .unwrap_or_else(|| { debug!("We got a mouse drag, but no last mouse pressed?"); Event::Unknown(vec![]) }) } else { // Identify the button let mut bare_event = mevent.bstate & ((1 << 25) - 1); let mut event = None; while bare_event != 0 { let single_event = 1 << bare_event.trailing_zeros(); bare_event ^= single_event; // Process single_event on_mouse_event(single_event, |e| { if event.is_none() { event = Some(e); } else { self.input_buffer = Some(make_event(e)); } }); } if let Some(event) = event { if let Some(btn) = event.button() { self.last_mouse_button = Some(btn); } make_event(event) } else { debug!("No event parsed?..."); Event::Unknown(vec![]) } } } } impl Drop for Backend { fn drop(&mut self) { print!("\x1B[?1002l"); stdout().flush().expect("could not flush stdout"); pancurses::endwin(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "pancurses" } fn is_persistent(&self) -> bool { true } fn set_title(&mut self, title: String) { print!("\x1B]0;{title}\x07"); stdout().flush().expect("could not flush stdout"); } fn screen_size(&self) -> Vec2 { // Coordinates are reversed here let (y, x) = self.window.get_max_yx(); (x, y).into() } fn has_colors(&self) -> bool { pancurses::has_colors() } fn set_color(&self, colors: ColorPair) -> ColorPair { let current = self.current_style.get(); if current != colors { self.set_colors(colors); } current } fn set_effect(&self, effect: Effect) { let style = match effect { Effect::Simple => pancurses::Attribute::Normal, Effect::Reverse => pancurses::Attribute::Reverse, Effect::Dim => pancurses::Attribute::Dim, Effect::Bold => pancurses::Attribute::Bold, Effect::Blink => pancurses::Attribute::Blink, Effect::Italic => pancurses::Attribute::Italic, Effect::Strikethrough => pancurses::Attribute::Strikeout, Effect::Underline => pancurses::Attribute::Underline, }; self.window.attron(style); } fn unset_effect(&self, effect: Effect) { let style = match effect { Effect::Simple => pancurses::Attribute::Normal, Effect::Reverse => pancurses::Attribute::Reverse, Effect::Dim => pancurses::Attribute::Dim, Effect::Bold => pancurses::Attribute::Bold, Effect::Blink => pancurses::Attribute::Blink, Effect::Italic => pancurses::Attribute::Italic, Effect::Strikethrough => pancurses::Attribute::Strikeout, Effect::Underline => pancurses::Attribute::Underline, }; self.window.attroff(style); } fn clear(&self, color: Color) { let id = self.get_or_create(ColorPair { front: color, back: color, }); self.window.bkgd(pancurses::ColorPair(id as u8)); self.window.clear(); } fn refresh(&mut self) { self.window.refresh(); } fn move_to(&self, pos: Vec2) { self.window.mv(pos.y as i32, pos.x as i32); } fn print(&self, text: &str) { self.window.addstr(text); } fn poll_event(&mut self) -> Option { self.parse_next() } } /// Parse the given code into one or more event. /// /// If the given event code should expend into multiple events /// (for instance click expends into PRESS + RELEASE), /// the returned Vec will include those queued events. /// /// The main event is returned separately to avoid allocation in most cases. fn on_mouse_event(bare_event: mmask_t, mut f: F) where F: FnMut(MouseEvent), { let button = get_mouse_button(bare_event); match bare_event { pancurses::BUTTON4_PRESSED => f(MouseEvent::WheelUp), pancurses::BUTTON5_PRESSED => f(MouseEvent::WheelDown), pancurses::BUTTON1_RELEASED | pancurses::BUTTON2_RELEASED | pancurses::BUTTON3_RELEASED | pancurses::BUTTON4_RELEASED | pancurses::BUTTON5_RELEASED => f(MouseEvent::Release(button)), pancurses::BUTTON1_PRESSED | pancurses::BUTTON2_PRESSED | pancurses::BUTTON3_PRESSED => { f(MouseEvent::Press(button)) } pancurses::BUTTON1_CLICKED | pancurses::BUTTON2_CLICKED | pancurses::BUTTON3_CLICKED | pancurses::BUTTON4_CLICKED | pancurses::BUTTON5_CLICKED => { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } // Well, we disabled click detection pancurses::BUTTON1_DOUBLE_CLICKED | pancurses::BUTTON2_DOUBLE_CLICKED | pancurses::BUTTON3_DOUBLE_CLICKED | pancurses::BUTTON4_DOUBLE_CLICKED | pancurses::BUTTON5_DOUBLE_CLICKED => { for _ in 0..2 { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } } pancurses::BUTTON1_TRIPLE_CLICKED | pancurses::BUTTON2_TRIPLE_CLICKED | pancurses::BUTTON3_TRIPLE_CLICKED | pancurses::BUTTON4_TRIPLE_CLICKED | pancurses::BUTTON5_TRIPLE_CLICKED => { for _ in 0..3 { f(MouseEvent::Press(button)); f(MouseEvent::Release(button)); } } _ => debug!("Unknown event: {:032b}", bare_event), } } /// Returns the Key enum corresponding to the given pancurses event. fn get_mouse_button(bare_event: mmask_t) -> MouseButton { match bare_event { pancurses::BUTTON1_RELEASED | pancurses::BUTTON1_PRESSED | pancurses::BUTTON1_CLICKED | pancurses::BUTTON1_DOUBLE_CLICKED | pancurses::BUTTON1_TRIPLE_CLICKED => MouseButton::Left, pancurses::BUTTON2_RELEASED | pancurses::BUTTON2_PRESSED | pancurses::BUTTON2_CLICKED | pancurses::BUTTON2_DOUBLE_CLICKED | pancurses::BUTTON2_TRIPLE_CLICKED => MouseButton::Middle, pancurses::BUTTON3_RELEASED | pancurses::BUTTON3_PRESSED | pancurses::BUTTON3_CLICKED | pancurses::BUTTON3_DOUBLE_CLICKED | pancurses::BUTTON3_TRIPLE_CLICKED => MouseButton::Right, pancurses::BUTTON4_RELEASED | pancurses::BUTTON4_PRESSED | pancurses::BUTTON4_CLICKED | pancurses::BUTTON4_DOUBLE_CLICKED | pancurses::BUTTON4_TRIPLE_CLICKED => MouseButton::Button4, pancurses::BUTTON5_RELEASED | pancurses::BUTTON5_PRESSED | pancurses::BUTTON5_CLICKED | pancurses::BUTTON5_DOUBLE_CLICKED | pancurses::BUTTON5_TRIPLE_CLICKED => MouseButton::Button5, _ => MouseButton::Other, } } fn initialize_keymap() -> HashMap { let mut map = HashMap::default(); super::fill_key_codes(&mut map, pancurses::keyname); map } cursive-0.21.1/src/backends/mod.rs000064400000000000000000000032111046102023000150500ustar 00000000000000//! Define backends using common libraries. //! //! Cursive doesn't print anything by itself: it delegates this job to a //! backend library, which handles all actual input and output. //! //! This module defines the [`Backend`] trait, as well as a few implementations //! using some common libraries. Each of those included backends needs a //! corresponding feature to be enabled. //! //! [`Backend`]: ../backend/trait.Backend.html #[cfg(unix)] mod resize; pub mod blt; pub mod crossterm; pub mod curses; pub mod puppet; pub mod termion; #[allow(dead_code)] fn boxed(e: impl std::error::Error + 'static) -> Box { Box::new(e) } /// Tries to initialize the default backend. /// /// Will use the first backend enabled from the list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// * Dummy pub fn try_default() -> Result, Box> { cfg_if::cfg_if! { if #[cfg(feature = "blt-backend")] { Ok(blt::Backend::init()) } else if #[cfg(feature = "termion-backend")] { termion::Backend::init().map_err(boxed) } else if #[cfg(feature = "pancurses-backend")] { curses::pan::Backend::init().map_err(boxed) } else if #[cfg(feature = "ncurses-backend")] { curses::n::Backend::init().map_err(boxed) } else if #[cfg(feature = "crossterm-backend")] { crossterm::Backend::init().map_err(boxed) } else { log::warn!("No built-it backend, falling back to Dummy backend."); Ok(cursive_core::backend::Dummy::init()) } } } cursive-0.21.1/src/backends/puppet/mod.rs000064400000000000000000000115651046102023000164000ustar 00000000000000//! Puppet backend use crossbeam_channel::{self, Receiver, Sender, TryRecvError}; use self::observed::ObservedCell; use self::observed::ObservedScreen; use self::observed::ObservedStyle; use crate::backend; use crate::event::Event; use crate::theme; use crate::Vec2; use std::cell::{Cell, RefCell}; use std::sync::Arc; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; pub mod observed; pub mod observed_screen_view; mod static_values; use static_values::*; /// Puppet backend for testing. pub struct Backend { inner_sender: Sender>, inner_receiver: Receiver>, prev_frame: RefCell>, current_frame: RefCell, size: Cell, current_style: RefCell>, screen_channel: (Sender, Receiver), cursor: Cell, } impl Backend { /// Creates new Puppet backend of given or default size. pub fn init(size_op: Option) -> Box where Self: Sized, { let (inner_sender, inner_receiver) = crossbeam_channel::unbounded(); let size = size_op.unwrap_or(*DEFAULT_SIZE); let mut backend = Backend { inner_sender, inner_receiver, prev_frame: RefCell::new(None), current_frame: RefCell::new(ObservedScreen::new(size)), size: Cell::new(size), cursor: Cell::new(Vec2::zero()), current_style: RefCell::new(Arc::new(DEFAULT_OBSERVED_STYLE.clone())), screen_channel: crossbeam_channel::unbounded(), }; { use backend::Backend; backend.refresh(); } Box::new(backend) } /// Returns current ObservedStyle pub fn current_style(&self) -> Arc { self.current_style.borrow().clone() } /// Output stream of consecutive frames rendered by Puppet backend pub fn stream(&self) -> Receiver { self.screen_channel.1.clone() } /// Input stream to inject artificial input to Puppet backend. pub fn input(&self) -> Sender> { self.inner_sender.clone() } } impl backend::Backend for Backend { fn poll_event(&mut self) -> Option { match self.inner_receiver.try_recv() { Ok(event) => event, Err(TryRecvError::Empty) => None, Err(e) => panic!("{}", e), } } fn set_title(&mut self, _title: String) {} fn refresh(&mut self) { let size = self.size.get(); let current_frame = self.current_frame.replace(ObservedScreen::new(size)); self.prev_frame.replace(Some(current_frame.clone())); self.screen_channel.0.send(current_frame).unwrap(); } fn has_colors(&self) -> bool { true } fn screen_size(&self) -> Vec2 { self.size.get() } fn move_to(&self, pos: Vec2) { self.cursor.set(pos); } fn print(&self, text: &str) { let pos = self.cursor.get(); let style = self.current_style.borrow().clone(); let mut screen = self.current_frame.borrow_mut(); let mut offset: usize = 0; for (idx, grapheme) in text.graphemes(true).enumerate() { let cpos = pos + Vec2::new(idx + offset, 0); screen[cpos] = Some(ObservedCell::new( cpos, style.clone(), Some(grapheme.to_string()), )); for _ in 0..grapheme.width() - 1 { offset += 1; let spos = pos + Vec2::new(idx + offset, 0); screen[spos] = Some(ObservedCell::new(spos, style.clone(), None)); } } self.cursor.set(pos + (text.width(), 0)); } fn clear(&self, clear_color: theme::Color) { let mut cloned_style = (*self.current_style()).clone(); let mut screen = self.current_frame.borrow_mut(); cloned_style.colors.back = clear_color; screen.clear(&Arc::new(cloned_style)) } // This sets the Colours and returns the previous colours // to allow you to set them back when you're done. fn set_color(&self, new_colors: theme::ColorPair) -> theme::ColorPair { let mut copied_style = (*self.current_style()).clone(); let old_colors = copied_style.colors; copied_style.colors = new_colors; self.current_style.replace(Arc::new(copied_style)); old_colors } fn set_effect(&self, effect: theme::Effect) { let mut copied_style = (*self.current_style()).clone(); copied_style.effects.insert(effect); self.current_style.replace(Arc::new(copied_style)); } fn unset_effect(&self, effect: theme::Effect) { let mut copied_style = (*self.current_style()).clone(); copied_style.effects.remove(effect); self.current_style.replace(Arc::new(copied_style)); } } cursive-0.21.1/src/backends/puppet/observed.rs000064400000000000000000000420621046102023000174260ustar 00000000000000//! Structs representing output of puppet backend use crate::reexports::enumset::EnumSet; use crate::theme::ColorPair; use crate::theme::Effect; use crate::Vec2; use std::ops::Index; use std::ops::IndexMut; use std::sync::Arc; use std::{fmt, fmt::Display, fmt::Formatter}; use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; /// Style of observed cell #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedStyle { /// Colors: front and back pub colors: ColorPair, /// Effects enabled on observed cell pub effects: EnumSet, } /// Contents of observed cell #[derive(Debug, Clone, Eq, PartialEq)] pub enum GraphemePart { /// Represents begin of wide character Begin(String), /// Represents a cell that is filled with continuation of some character that begun in cell with lower x-index. Continuation, } impl GraphemePart { /// Returns true iff GraphemePart is Continuation pub fn is_continuation(&self) -> bool { matches!(*self, GraphemePart::Continuation) } /// Returns Some(String) if GraphemePart is Begin(String), else None. pub fn as_option(&self) -> Option<&String> { match *self { GraphemePart::Begin(ref string) => Some(string), GraphemePart::Continuation => None, } } /// Returns String if GraphemePart is Begin(String), panics otherwise. pub fn unwrap(&self) -> String { match *self { GraphemePart::Begin(ref s) => s.clone(), _ => panic!("unwrapping GraphemePart::Continuation"), } } } /// Represents a single cell of terminal. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedCell { /// Absolute position pub pos: Vec2, /// Style pub style: Arc, /// Part of grapheme - either it's beginning or continuation when character is multi-cell long. pub letter: GraphemePart, } impl ObservedCell { /// Constructor pub fn new(pos: Vec2, style: Arc, letter: Option) -> Self { let letter: GraphemePart = match letter { Some(s) => GraphemePart::Begin(s), None => GraphemePart::Continuation, }; ObservedCell { pos, style, letter } } } /// Puppet backend output /// /// Represents single frame. #[derive(Debug, Clone, Eq, PartialEq)] pub struct ObservedScreen { /// Size size: Vec2, /// Contents. Each cell can be set or empty. contents: Vec>, } impl Display for ObservedScreen { fn fmt(&self, f: &mut Formatter) -> fmt::Result { writeln!(f, "captured piece:")?; write!(f, "x")?; for x in 0..self.size().x { write!(f, "{}", x % 10)?; } writeln!(f, "x")?; for y in 0..self.size().y { write!(f, "{}", y % 10)?; for x in 0..self.size().x { let pos = Vec2::new(x, y); let cell_op: &Option = &self[pos]; if cell_op.is_some() { let cell = cell_op.as_ref().unwrap(); if cell.letter.is_continuation() { write!(f, "c")?; continue; } else { let letter = cell.letter.unwrap(); if letter == " " { write!(f, " ")?; } else { write!(f, "{letter}")?; } } } else { write!(f, ".")?; } } writeln!(f, "|")?; } write!(f, "x")?; for _x in 0..self.size().x { write!(f, "-")?; } writeln!(f, "x")?; Ok(()) } } impl ObservedScreen { /// Creates empty ObservedScreen pub fn new(size: Vec2) -> Self { let contents: Vec> = vec![None; size.x * size.y]; ObservedScreen { size, contents } } fn flatten_index(&self, index: Vec2) -> usize { assert!(index.x < self.size.x); assert!(index.y < self.size.y); index.y * self.size.x + index.x } fn unflatten_index(&self, index: usize) -> Vec2 { assert!(index < self.contents.len()); Vec2::new(index / self.size.x, index % self.size.x) } /// Sets all cells to empty cells with given style pub fn clear(&mut self, style: &Arc) { for idx in 0..self.contents.len() { self.contents[idx] = Some(ObservedCell::new( self.unflatten_index(idx), style.clone(), None, )) } } /// Size pub fn size(&self) -> Vec2 { self.size } /// Returns a rectangular subset of observed screen. pub fn piece(&self, min: Vec2, max: Vec2) -> ObservedPiece { ObservedPiece::new(self, min, max) } /// Prints the piece to stdout. pub fn print_stdout(&self) { println!("{self}") } /// Returns occurrences of given string pattern pub fn find_occurences(&self, pattern: &str) -> Vec { // TODO(njskalski): test for two-cell letters. // TODO(njskalski): fails with whitespaces like "\t". let mut hits: Vec = vec![]; for y in self.min().y..self.max().y { 'x: for x in self.min().x..self.max().x { // check candidate. if pattern.len() > self.size().x - x { continue; } let mut pattern_cursor: usize = 0; let mut pos_cursor: usize = 0; loop { let pattern_symbol = pattern .graphemes(true) .nth(pattern_cursor) .unwrap_or_else(|| { panic!("Found no char at cursor {} in {}", pattern_cursor, &pattern) }); let pos_it = Vec2::new(x + pos_cursor, y); let found_symbol: Option<&String> = if let Some(ref cell) = self[pos_it] { cell.letter.as_option() } else { None }; match found_symbol { Some(screen_symbol) => { if pattern_symbol == screen_symbol { pattern_cursor += 1; pos_cursor += screen_symbol.width(); } else { continue 'x; } } None => { if pattern_symbol == " " { pattern_cursor += 1; pos_cursor += 1; } else { continue 'x; } } }; if pattern_cursor == pattern.graphemes(true).count() { break; }; } if pattern_cursor == pattern.graphemes(true).count() { hits.push(ObservedLine::new(self, Vec2::new(x, y), pos_cursor)); } } } hits } } /// Represents rectangular piece of observed screen (Puppet backend output) pub trait ObservedPieceInterface { /// Minimums of coordinates fn min(&self) -> Vec2; /// Maximums of coordinates fn max(&self) -> Vec2; /// Reference of ObservablePiece this one is a subsection of or Self fn parent(&self) -> &ObservedScreen; /// Size of piece fn size(&self) -> Vec2 { self.max() - self.min() } /// Returns a string representation of consecutive lines of this piece. fn as_strings(&self) -> Vec { let mut v: Vec = vec![]; for y in self.min().y..self.max().y { let mut s = String::new(); for x in self.min().x..self.max().x { match &self.parent()[Vec2::new(x, y)] { None => s.push(' '), Some(cell) => { if let GraphemePart::Begin(ref lex) = cell.letter { s.push_str(lex); } } } } v.push(s); } v } /// Returns expanded sibling of this piece /// /// Asserts if request can be satisfied. fn expanded(&self, up_left: Vec2, down_right: Vec2) -> ObservedPiece { assert!(self.min().x >= up_left.x); assert!(self.min().y >= up_left.y); assert!(self.max().x + down_right.x <= self.parent().size.x); assert!(self.max().y + down_right.y <= self.parent().size.y); ObservedPiece::new(self.parent(), self.min() - up_left, self.max() + down_right) } } /// Represents a piece or whole of observed screen. pub struct ObservedPiece<'a> { min: Vec2, max: Vec2, parent: &'a ObservedScreen, } impl<'a> ObservedPiece<'a> { fn new(parent: &'a ObservedScreen, min: Vec2, max: Vec2) -> Self { ObservedPiece { min, max, parent } } } impl ObservedPieceInterface for ObservedScreen { fn min(&self) -> Vec2 { Vec2::new(0, 0) } fn max(&self) -> Vec2 { self.size } fn parent(&self) -> &ObservedScreen { self } } impl<'a> ObservedPieceInterface for ObservedPiece<'a> { fn min(&self) -> Vec2 { self.min } fn max(&self) -> Vec2 { self.max } fn parent(&self) -> &ObservedScreen { self.parent } } /// Represents a single line of observed screen. pub struct ObservedLine<'a> { line_start: Vec2, line_len: usize, parent: &'a ObservedScreen, } impl<'a> ObservedLine<'a> { fn new(parent: &'a ObservedScreen, line_start: Vec2, line_len: usize) -> Self { ObservedLine { line_start, line_len, parent, } } /// Returns the same line, but expanded. /// /// Asserts whether request can be satisfied #[must_use] pub fn expanded_line(&self, left: usize, right: usize) -> Self { assert!(left <= self.line_start.x); assert!(self.line_start.x + self.line_len + right <= self.parent.size.x); ObservedLine { line_start: Vec2::new(self.line_start.x - left, self.line_start.y), line_len: self.line_len + left + right, parent: self.parent, } } } impl<'a> ObservedPieceInterface for ObservedLine<'a> { fn min(&self) -> Vec2 { self.line_start } fn max(&self) -> Vec2 { self.line_start + (self.line_len, 1) } fn parent(&self) -> &ObservedScreen { self.parent } } impl<'a> Display for ObservedLine<'a> { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "{}", self.as_strings().remove(0)) } } impl Index for dyn ObservedPieceInterface { type Output = Option; fn index(&self, index: Vec2) -> &Self::Output { assert!(self.max() - self.min() > index); let parent_index = self.min() + index; &self.parent()[parent_index] } } impl Index for ObservedScreen { type Output = Option; fn index(&self, index: Vec2) -> &Self::Output { let idx = self.flatten_index(index); &self.contents[idx] } } impl IndexMut for ObservedScreen { fn index_mut(&mut self, index: Vec2) -> &mut Option { let idx = self.flatten_index(index); &mut self.contents[idx] } } #[cfg(test)] mod tests { use super::*; use crate::backends::puppet::DEFAULT_OBSERVED_STYLE; /// Expecting fake_screen to be square, # will be replaced with blank. fn get_observed_screen(fake_screen: &[&str]) -> ObservedScreen { let observed_style: Arc = Arc::new(DEFAULT_OBSERVED_STYLE.clone()); let height = fake_screen.len(); let width = fake_screen[0].width(); let size = Vec2::new(width, height); let mut os = ObservedScreen::new(size); for (y, row) in fake_screen.iter().enumerate() { let mut x: usize = 0; for letter in row.graphemes(true) { let idx = os.flatten_index(Vec2::new(x, y)); os.contents[idx] = if letter == "#" { None } else { Some(ObservedCell::new( Vec2::new(x, y), observed_style.clone(), Some(letter.to_owned()), )) }; x += letter.width(); } } os } #[test] fn test_test() { let fake_screen: Vec<&'static str> = vec!["..hello***", "!!##$$$$$*", ".hello^^^^"]; let os = get_observed_screen(&fake_screen); assert_eq!( os[Vec2::new(0, 0)].as_ref().unwrap().letter.as_option(), Some(&".".to_owned()) ); assert_eq!(os[Vec2::new(2, 1)], None); } #[test] fn find_occurrences_no_blanks() { let fake_screen: Vec<&'static str> = vec!["..hello***", "!!##$$$$$*", ".hello^^^^"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello"); assert_eq!(hits.len(), 2); assert_eq!(hits[0].size(), Vec2::new(5, 1)); assert_eq!(hits[1].size(), Vec2::new(5, 1)); assert_eq!(hits[0].to_string(), "hello"); assert_eq!(hits[1].to_string(), "hello"); assert_eq!(hits[0].min(), Vec2::new(2, 0)); assert_eq!(hits[0].max(), Vec2::new(7, 1)); assert_eq!(hits[1].min(), Vec2::new(1, 2)); assert_eq!(hits[1].max(), Vec2::new(6, 3)); } #[test] fn find_occurrences_some_blanks() { let fake_screen: Vec<&'static str> = vec!["__hello world_", "hello!world___", "___hello#world"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello world"); assert_eq!(hits.len(), 2); assert_eq!(hits[0].size(), Vec2::new(11, 1)); assert_eq!(hits[1].size(), Vec2::new(11, 1)); assert_eq!(hits[0].to_string(), "hello world"); assert_eq!(hits[1].to_string(), "hello world"); assert_eq!(hits[0].min(), Vec2::new(2, 0)); assert_eq!(hits[0].max(), Vec2::new(13, 1)); assert_eq!(hits[1].min(), Vec2::new(3, 2)); assert_eq!(hits[1].max(), Vec2::new(14, 3)); } #[test] fn test_expand_lines() { let fake_screen: Vec<&'static str> = vec!["abc hello#efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("hello"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(5, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(8, 1)); assert_eq!(expanded_left.to_string(), "bc hello"); let expanded_left = hit.expanded_line(4, 0); assert_eq!(expanded_left.size(), Vec2::new(9, 1)); assert_eq!(expanded_left.to_string(), "abc hello"); let expanded_right = hit.expanded_line(0, 2); assert_eq!(expanded_right.size(), Vec2::new(7, 1)); assert_eq!(expanded_right.to_string(), "hello e"); let expanded_right = hit.expanded_line(0, 4); assert_eq!(expanded_right.size(), Vec2::new(9, 1)); assert_eq!(expanded_right.to_string(), "hello efg"); } #[test] fn test_expand_lines_weird_symbol_1() { let fake_screen: Vec<&'static str> = vec!["abc ▸ #efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("root"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(4, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(7, 1)); assert_eq!(expanded_left.to_string(), "▸ efg"); } #[test] fn test_expand_lines_weird_symbol_2() { let fake_screen: Vec<&'static str> = vec!["abc ▸ #efg"]; let os = get_observed_screen(&fake_screen); let hits = os.find_occurences("▸"); assert_eq!(hits.len(), 1); let hit = hits.first().unwrap(); assert_eq!(hit.size(), Vec2::new(1, 1)); let expanded_left = hit.expanded_line(3, 0); assert_eq!(expanded_left.size(), Vec2::new(4, 1)); assert_eq!(expanded_left.to_string(), "bc ▸"); let expanded_right = hit.expanded_line(0, 9); assert_eq!(expanded_right.size(), Vec2::new(10, 1)); assert_eq!(expanded_right.to_string(), "▸ e"); } } cursive-0.21.1/src/backends/puppet/observed_screen_view.rs000064400000000000000000000031441046102023000220150ustar 00000000000000//! View visualizing a captured PuppetBackend outputs use crate::backends::puppet::observed::ObservedCell; use crate::backends::puppet::observed::ObservedScreen; use crate::theme::ColorStyle; use crate::theme::ColorType; use crate::view::View; use crate::Printer; use crate::Vec2; /// A view that visualize observed screen pub struct ObservedScreenView { screen: ObservedScreen, } impl ObservedScreenView { /// Constructor pub fn new(obs: ObservedScreen) -> Self { ObservedScreenView { screen: obs } } } impl View for ObservedScreenView { fn draw(&self, printer: &Printer) { for x in 0..self.screen.size().x { for y in 0..self.screen.size().y { let pos = Vec2::new(x, y); let cell_op: &Option = &self.screen[pos]; if cell_op.is_none() { continue; } let cell = cell_op.as_ref().unwrap(); if cell.letter.is_continuation() { continue; } printer.with_effects(cell.style.effects, |printer| { let color_style = ColorStyle { front: ColorType::Color(cell.style.colors.front), back: ColorType::Color(cell.style.colors.back), }; printer.with_color(color_style, |printer| { printer.print(pos, &cell.letter.unwrap()); }); }); } } } fn required_size(&mut self, _: Vec2) -> Vec2 { self.screen.size() } } cursive-0.21.1/src/backends/puppet/static_values.rs000064400000000000000000000012571046102023000204640ustar 00000000000000/// Some default values to Puppet backend. use lazy_static::lazy_static; use crate::reexports::enumset::EnumSet; use crate::theme::ColorPair; use crate::theme::{Color, Effect}; use crate::Vec2; use crate::XY; use crate::backends::puppet::observed::*; lazy_static! { /// Default size for the puppet terminal. pub static ref DEFAULT_SIZE: Vec2 = XY:: { x: 120, y: 80 }; /// Default style for the puppet terminal. pub static ref DEFAULT_OBSERVED_STYLE: ObservedStyle = ObservedStyle { colors: ColorPair { front: Color::TerminalDefault, back: Color::TerminalDefault, }, effects: EnumSet::::empty(), }; } cursive-0.21.1/src/backends/resize.rs000064400000000000000000000013641046102023000156010ustar 00000000000000use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use crossbeam_channel::Sender; use signal_hook::iterator::Signals; /// This starts a new thread to listen for SIGWINCH signals #[allow(unused)] pub fn start_resize_thread(resize_sender: Sender<()>, resize_running: Arc) { let mut signals = Signals::new([libc::SIGWINCH]).unwrap(); thread::spawn(move || { // This thread will listen to SIGWINCH events and report them. while resize_running.load(Ordering::Relaxed) { // We know it will only contain SIGWINCH signals, so no need to check. if signals.wait().count() > 0 && resize_sender.send(()).is_err() { return; } } }); } cursive-0.21.1/src/backends/termion.rs000064400000000000000000000332511046102023000157550ustar 00000000000000//! Backend using the pure-rust termion library. //! //! Requires the `termion-backend` feature. #![cfg(feature = "termion-backend")] #![cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub use termion; use crossbeam_channel::{self, Receiver}; use termion::color as tcolor; use termion::event::Event as TEvent; use termion::event::Key as TKey; use termion::event::MouseButton as TMouseButton; use termion::event::MouseEvent as TMouseEvent; use termion::input::{Events, MouseTerminal, TermRead}; use termion::raw::{IntoRawMode, RawTerminal}; use termion::screen::{AlternateScreen, IntoAlternateScreen}; use termion::style as tstyle; use crate::backend; use crate::backends; use crate::event::{Event, Key, MouseButton, MouseEvent}; use crate::theme; use crate::Vec2; use std::cell::{Cell, RefCell}; use std::fs::File; use std::io::Write; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; /// Backend using termion pub struct Backend { // Do we want to make this generic on the writer? terminal: RefCell>>>, current_style: Cell, // Inner state required to parse input last_button: Option, events: Events, // Raw input file descriptor, to fix the file on exit, since we can't // (currently) get it from events. #[cfg(unix)] input_fd: std::os::unix::io::RawFd, resize_receiver: Receiver<()>, running: Arc, } /// Set the given file to be read in non-blocking mode. That is, attempting a /// read on the given file may return 0 bytes. /// /// Copied from private function at https://docs.rs/nonblock/0.1.0/nonblock/. /// /// The MIT License (MIT) /// /// Copyright (c) 2016 Anthony Nowell /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to deal /// in the Software without restriction, including without limitation the rights /// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell /// copies of the Software, and to permit persons to whom the Software is /// furnished to do so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in all /// copies or substantial portions of the Software. /// /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR /// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, /// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE /// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER /// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, /// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE /// SOFTWARE. #[cfg(unix)] fn set_blocking(fd: std::os::unix::io::RawFd, blocking: bool) -> std::io::Result<()> { use libc::{fcntl, F_GETFL, F_SETFL, O_NONBLOCK}; let flags = unsafe { fcntl(fd, F_GETFL, 0) }; if flags < 0 { return Err(std::io::Error::last_os_error()); } let flags = if blocking { flags & !O_NONBLOCK } else { flags | O_NONBLOCK }; let res = unsafe { fcntl(fd, F_SETFL, flags) }; if res != 0 { return Err(std::io::Error::last_os_error()); } Ok(()) } impl Backend { /// Creates a new termion-based backend. /// /// Uses `/dev/tty` for input and output. pub fn init() -> std::io::Result> { Self::init_with_files(File::open("/dev/tty")?, File::create("/dev/tty")?) } /// Creates a new termion-based backend. /// /// Uses `stdin` and `stdout` for input/output. pub fn init_stdio() -> std::io::Result> { Self::init_with_files(File::open("/dev/stdin")?, File::create("/dev/stdout")?) } /// Creates a new termion-based backend using the given input and output files. pub fn init_with_files( input_file: File, output_file: File, ) -> std::io::Result> { #[cfg(unix)] use std::os::unix::io::AsRawFd; #[cfg(unix)] let input_fd = input_file.as_raw_fd(); #[cfg(unix)] set_blocking(input_fd, false)?; // Use a ~8MB buffer // Should be enough for a single screen most of the time. let terminal = RefCell::new( MouseTerminal::from(output_file.into_raw_mode()?).into_alternate_screen()?, ); write!(terminal.borrow_mut(), "{}", termion::cursor::Hide)?; let (resize_sender, resize_receiver) = crossbeam_channel::bounded(0); let running = Arc::new(AtomicBool::new(true)); #[cfg(unix)] backends::resize::start_resize_thread(resize_sender, Arc::clone(&running)); let c = Backend { terminal, current_style: Cell::new(theme::ColorPair::from_256colors(0, 0)), last_button: None, events: input_file.events(), #[cfg(unix)] input_fd, resize_receiver, running, }; Ok(Box::new(c)) } fn apply_colors(&self, colors: theme::ColorPair) { with_color(colors.front, |c| self.write(tcolor::Fg(c))); with_color(colors.back, |c| self.write(tcolor::Bg(c))); } fn map_key(&mut self, event: TEvent) -> Event { match event { TEvent::Unsupported(bytes) => Event::Unknown(bytes), TEvent::Key(TKey::Esc) => Event::Key(Key::Esc), TEvent::Key(TKey::Backspace) => Event::Key(Key::Backspace), TEvent::Key(TKey::Left) => Event::Key(Key::Left), TEvent::Key(TKey::Right) => Event::Key(Key::Right), TEvent::Key(TKey::Up) => Event::Key(Key::Up), TEvent::Key(TKey::Down) => Event::Key(Key::Down), TEvent::Key(TKey::Home) => Event::Key(Key::Home), TEvent::Key(TKey::End) => Event::Key(Key::End), TEvent::Key(TKey::PageUp) => Event::Key(Key::PageUp), TEvent::Key(TKey::PageDown) => Event::Key(Key::PageDown), TEvent::Key(TKey::Delete) => Event::Key(Key::Del), TEvent::Key(TKey::Insert) => Event::Key(Key::Ins), TEvent::Key(TKey::F(i)) if i < 12 => Event::Key(Key::from_f(i)), TEvent::Key(TKey::F(j)) => Event::Unknown(vec![j]), TEvent::Key(TKey::Char('\n')) => Event::Key(Key::Enter), TEvent::Key(TKey::Char('\t')) => Event::Key(Key::Tab), TEvent::Key(TKey::BackTab) => Event::Shift(Key::Tab), TEvent::Key(TKey::Char(c)) => Event::Char(c), TEvent::Key(TKey::Ctrl(c)) => Event::CtrlChar(c), TEvent::Key(TKey::Alt(c)) => Event::AltChar(c), TEvent::Mouse(TMouseEvent::Press(btn, x, y)) => { let position = (x - 1, y - 1).into(); let event = match btn { TMouseButton::Left => MouseEvent::Press(MouseButton::Left), TMouseButton::Middle => MouseEvent::Press(MouseButton::Middle), TMouseButton::Right => MouseEvent::Press(MouseButton::Right), TMouseButton::WheelUp => MouseEvent::WheelUp, TMouseButton::WheelDown => MouseEvent::WheelDown, // TODO: Support left/right wheel moves? // Or convert to left/right arrow keys? TMouseButton::WheelLeft | TMouseButton::WheelRight => return Event::Refresh, }; if let MouseEvent::Press(btn) = event { self.last_button = Some(btn); } Event::Mouse { event, position, offset: Vec2::zero(), } } TEvent::Mouse(TMouseEvent::Release(x, y)) if self.last_button.is_some() => { let event = MouseEvent::Release(self.last_button.unwrap()); let position = (x - 1, y - 1).into(); Event::Mouse { event, position, offset: Vec2::zero(), } } TEvent::Mouse(TMouseEvent::Hold(x, y)) if self.last_button.is_some() => { let event = MouseEvent::Hold(self.last_button.unwrap()); let position = (x - 1, y - 1).into(); Event::Mouse { event, position, offset: Vec2::zero(), } } _ => Event::Unknown(vec![]), } } fn write(&self, content: T) where T: std::fmt::Display, { write!(self.terminal.borrow_mut(), "{content}").unwrap(); } } impl Drop for Backend { fn drop(&mut self) { self.running.store(false, Ordering::Relaxed); #[cfg(unix)] set_blocking(self.input_fd, true).unwrap(); write!( self.terminal.get_mut(), "{}{}", termion::cursor::Show, termion::cursor::Goto(1, 1) ) .unwrap(); write!( self.terminal.get_mut(), "{}[49m{}[39m{}", 27 as char, 27 as char, termion::clear::All ) .unwrap(); } } impl backend::Backend for Backend { fn name(&self) -> &str { "termion" } fn is_persistent(&self) -> bool { true } fn set_title(&mut self, title: String) { write!(self.terminal.get_mut(), "\x1B]0;{title}\x07").unwrap(); } fn set_color(&self, color: theme::ColorPair) -> theme::ColorPair { let current_style = self.current_style.get(); if current_style != color { self.apply_colors(color); self.current_style.set(color); } current_style } fn set_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.write(tstyle::Invert), theme::Effect::Dim => self.write(tstyle::Faint), theme::Effect::Bold => self.write(tstyle::Bold), theme::Effect::Blink => self.write(tstyle::Blink), theme::Effect::Italic => self.write(tstyle::Italic), theme::Effect::Strikethrough => self.write(tstyle::CrossedOut), theme::Effect::Underline => self.write(tstyle::Underline), } } fn unset_effect(&self, effect: theme::Effect) { match effect { theme::Effect::Simple => (), theme::Effect::Reverse => self.write(tstyle::NoInvert), theme::Effect::Dim | theme::Effect::Bold => self.write(tstyle::NoFaint), theme::Effect::Blink => self.write(tstyle::NoBlink), theme::Effect::Italic => self.write(tstyle::NoItalic), theme::Effect::Strikethrough => self.write(tstyle::NoCrossedOut), theme::Effect::Underline => self.write(tstyle::NoUnderline), } } fn has_colors(&self) -> bool { // TODO: color support detection? true } fn screen_size(&self) -> Vec2 { // TODO: termion::terminal_size currently requires stdout. // When available, we should try to use self.terminal or something instead. let (x, y) = termion::terminal_size().unwrap_or((1, 1)); (x, y).into() } fn clear(&self, color: theme::Color) { self.apply_colors(theme::ColorPair { front: color, back: color, }); self.write(termion::clear::All); } fn refresh(&mut self) { self.terminal.get_mut().flush().unwrap(); } fn move_to(&self, pos: Vec2) { write!( self.terminal.borrow_mut(), "{}", termion::cursor::Goto(1 + pos.x as u16, 1 + pos.y as u16), ) .unwrap(); } fn print(&self, text: &str) { write!(self.terminal.borrow_mut(), "{text}",).unwrap(); } fn poll_event(&mut self) -> Option { if let Some(Ok(event)) = self.events.next() { Some(self.map_key(event)) } else if let Ok(()) = self.resize_receiver.try_recv() { Some(Event::WindowResize) } else { None } } } fn with_color(clr: theme::Color, f: F) -> R where F: FnOnce(&dyn tcolor::Color) -> R, { match clr { theme::Color::TerminalDefault => f(&tcolor::Reset), theme::Color::Dark(theme::BaseColor::Black) => f(&tcolor::Black), theme::Color::Dark(theme::BaseColor::Red) => f(&tcolor::Red), theme::Color::Dark(theme::BaseColor::Green) => f(&tcolor::Green), theme::Color::Dark(theme::BaseColor::Yellow) => f(&tcolor::Yellow), theme::Color::Dark(theme::BaseColor::Blue) => f(&tcolor::Blue), theme::Color::Dark(theme::BaseColor::Magenta) => f(&tcolor::Magenta), theme::Color::Dark(theme::BaseColor::Cyan) => f(&tcolor::Cyan), theme::Color::Dark(theme::BaseColor::White) => f(&tcolor::White), theme::Color::Light(theme::BaseColor::Black) => f(&tcolor::LightBlack), theme::Color::Light(theme::BaseColor::Red) => f(&tcolor::LightRed), theme::Color::Light(theme::BaseColor::Green) => f(&tcolor::LightGreen), theme::Color::Light(theme::BaseColor::Yellow) => f(&tcolor::LightYellow), theme::Color::Light(theme::BaseColor::Blue) => f(&tcolor::LightBlue), theme::Color::Light(theme::BaseColor::Magenta) => f(&tcolor::LightMagenta), theme::Color::Light(theme::BaseColor::Cyan) => f(&tcolor::LightCyan), theme::Color::Light(theme::BaseColor::White) => f(&tcolor::LightWhite), theme::Color::Rgb(r, g, b) => f(&tcolor::Rgb(r, g, b)), theme::Color::RgbLowRes(r, g, b) => f(&tcolor::AnsiValue::rgb(r, g, b)), } } cursive-0.21.1/src/cursive_ext.rs000064400000000000000000000101441046102023000150620ustar 00000000000000/// Extension trait for the `Cursive` root to simplify initialization. /// /// It brings backend-specific methods to initialize a `Cursive` root. /// /// # Examples /// /// ```rust,no_run /// use cursive::{Cursive, CursiveExt}; /// /// let mut siv = Cursive::new(); /// /// // Use `CursiveExt::run()` to pick one of the enabled backends, /// // depending on cargo features. /// siv.run(); /// /// // Or explicitly use a specific backend /// #[cfg(feature = "ncurses-backend")] /// siv.run_ncurses().unwrap(); /// #[cfg(feature = "panncurses-backend")] /// siv.run_pancurses().unwrap(); /// #[cfg(feature = "termion-backend")] /// siv.run_termion().unwrap(); /// #[cfg(feature = "crossterm-backend")] /// siv.run_crossterm().unwrap(); /// #[cfg(feature = "blt-backend")] /// siv.run_blt(); /// ``` pub trait CursiveExt { /// Tries to use one of the enabled backends. /// /// Will fallback to the dummy backend if no other backend feature is enabled. /// /// # Panics /// /// If the backend initialization fails. fn run(&mut self); /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] fn run_ncurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a pancurses backend. #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] fn run_pancurses(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a termion backend. #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] fn run_termion(&mut self) -> std::io::Result<()>; /// Creates a new Cursive root using a crossterm backend. #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] fn run_crossterm(&mut self) -> Result<(), std::io::Error>; /// Creates a new Cursive root using a bear-lib-terminal backend. #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self); } impl CursiveExt for cursive_core::Cursive { fn run(&mut self) { cfg_if::cfg_if! { if #[cfg(feature = "blt-backend")] { self.run_blt() } else if #[cfg(feature = "termion-backend")] { self.run_termion().unwrap() } else if #[cfg(feature = "pancurses-backend")] { self.run_pancurses().unwrap() } else if #[cfg(feature = "ncurses-backend")] { self.run_ncurses().unwrap() } else if #[cfg(feature = "crossterm-backend")] { self.run_crossterm().unwrap() } else { log::warn!("No built-it backend, falling back to Cursive::dummy()."); self.run_dummy() } } } #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "curses-backend")))] fn run_ncurses(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::curses::n::Backend::init) } #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] fn run_pancurses(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::curses::pan::Backend::init) } #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] fn run_termion(&mut self) -> std::io::Result<()> { self.try_run_with(crate::backends::termion::Backend::init) } #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] fn run_crossterm(&mut self) -> Result<(), std::io::Error> { self.try_run_with(crate::backends::crossterm::Backend::init) } #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] fn run_blt(&mut self) { self.run_with(crate::backends::blt::Backend::init) } } cursive-0.21.1/src/cursive_runnable.rs000064400000000000000000000144671046102023000161040ustar 00000000000000use crate::{backend, backends, Cursive, CursiveRunner}; type Initializer = dyn FnMut() -> Result, Box>; /// A runnable wrapper around `Cursive`, bundling the backend initializer. /// /// This struct embeds both `Cursive` and a backend-initializer /// (`FnMut() -> Result`), to provide a simple `.run()` method. /// /// This lets you pick the backend when creating the Cursive root, rather than /// when running it. /// /// It implements `DerefMut`, so you can use it just like a /// regular `Cursive` object. pub struct CursiveRunnable { siv: Cursive, backend_init: Box, } impl Default for CursiveRunnable { /// Creates a new Cursive wrapper using one of the available backends. /// /// Picks the first backend enabled from the list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// * Dummy fn default() -> Self { Self::with_initializer(Box::new(backends::try_default)) } } impl std::ops::Deref for CursiveRunnable { type Target = Cursive; fn deref(&self) -> &Cursive { &self.siv } } impl std::ops::DerefMut for CursiveRunnable { fn deref_mut(&mut self) -> &mut Cursive { &mut self.siv } } impl std::borrow::Borrow for CursiveRunnable { fn borrow(&self) -> &Cursive { self } } impl std::borrow::BorrowMut for CursiveRunnable { fn borrow_mut(&mut self) -> &mut Cursive { self } } /// Helper function to help type inference when `Box::new` would not work. fn boxed(e: impl std::error::Error + 'static) -> Box { Box::new(e) } impl CursiveRunnable { /// Creates a new Cursive wrapper using the given boxed backend initializer. fn with_initializer(backend_init: Box) -> Self { let siv = Cursive::new(); Self { siv, backend_init } } /// Creates a new Cursive wrapper, using the given backend. pub fn new(mut backend_init: F) -> Self where E: std::error::Error + 'static, F: FnMut() -> Result, E> + 'static, { Self::with_initializer(Box::new(move || backend_init().map_err(boxed))) } /// Runs the event loop with the registered backend initializer. /// /// # Panics /// /// If the backend initialization fails. pub fn run(&mut self) { self.try_run().unwrap(); } /// Runs the event loop with the registered backend initializer. pub fn try_run(&mut self) -> Result<(), Box> { self.siv.try_run_with(&mut self.backend_init) } /// Gets a runner with the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will borrow `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be ready for another run if /// needed. pub fn try_runner( &mut self, ) -> Result, Box> { Ok(self.siv.runner((self.backend_init)()?)) } /// Gets a runner with the registered backend. /// /// # Panics /// /// If the backend initialization fails. pub fn runner(&mut self) -> CursiveRunner<&mut Cursive> { self.try_runner().unwrap() } /// Returns a new runner on the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will embed `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be dropped as well. pub fn try_into_runner(mut self) -> Result, Box> { let backend = (self.backend_init)()?; Ok(CursiveRunner::new(self, backend)) } /// Returns a new runner on the registered backend. /// /// Used to manually control the event loop. In most cases, running /// `run()` will be easier. /// /// The runner will embed `self`; when dropped, it will clear out the /// terminal, and the cursive instance will be dropped as well. /// /// # Panics /// /// If the backend initialization fails. pub fn into_runner(self) -> CursiveRunner { self.try_into_runner().unwrap() } /// Creates a new Cursive wrapper using the dummy backend. /// /// Nothing will actually be output when calling `.run()`. pub fn dummy() -> Self { Self::new::(|| Ok(cursive_core::backend::Dummy::init())) } /// Creates a new Cursive wrapper using the ncurses backend. /// /// _Requires the `ncurses-backend` feature._ #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub fn ncurses() -> Self { Self::new(backends::curses::n::Backend::init) } /// Creates a new Cursive wrapper using the panncurses backend. /// /// _Requires the `panncurses-backend` feature._ #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub fn pancurses() -> Self { Self::new(backends::curses::pan::Backend::init) } /// Creates a new Cursive wrapper using the termion backend. /// /// _Requires the `termion-backend` feature._ #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub fn termion() -> Self { Self::new(backends::termion::Backend::init) } /// Creates a new Cursive wrapper using the crossterm backend. /// /// _Requires the `crossterm-backend` feature._ #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] pub fn crossterm() -> Self { Self::new(backends::crossterm::Backend::init) } /// Creates a new Cursive wrapper using the bear-lib-terminal backend. /// /// _Requires the `blt-backend` feature._ #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub fn blt() -> Self { Self::new::(|| Ok(backends::blt::Backend::init())) } } cursive-0.21.1/src/lib.rs000064400000000000000000000100121046102023000132620ustar 00000000000000//! # Cursive //! //! [Cursive] is a [TUI] library - it lets you easily build rich interfaces //! for use in a terminal. //! //! [Cursive]: https://github.com/gyscos/cursive //! [TUI]: https://en.wikipedia.org/wiki/Text-based_user_interface //! //! ## Getting started //! //! * Every application should start with a [`Cursive`] object. It is the main //! entry-point to the library. //! * A declarative phase then describes the structure of the UI by adding //! views and configuring their behaviours. //! * Finally, the event loop is started by calling [`Cursive::run`]. //! //! ## Examples //! //! ```rust,no_run //! use cursive::views::TextView; //! use cursive::{Cursive, CursiveExt}; //! //! let mut siv = Cursive::new(); //! //! siv.add_layer(TextView::new("Hello World!\nPress q to quit.")); //! //! siv.add_global_callback('q', |s| s.quit()); //! //! siv.run(); //! ``` //! //! ## Views //! //! Views are the main components of a cursive interface. //! The [`views`] module contains many views to use in your //! application; if you don't find what you need, you may also implement the //! [`View`] trait and build your own. //! //! ## Callbacks //! //! Cursive is callback-driven: it reacts to events generated by user input. //! //! During the declarative phase, callbacks are set to trigger on specific //! events. These functions usually take an `&mut Cursive` argument, allowing //! them to modify the view tree at will. //! //! ## Debugging //! //! The `Cursive` root initializes the terminal on creation, and does cleanups //! on drop. While it is alive, printing to the terminal will not work //! as expected, making debugging a bit harder. //! //! One solution is to redirect stderr to a file when running the application, //! and log to it instead of stdout. //! //! Or you can use gdb as usual. //! //! ## Themes //! //! Cursive supports configuring the feels and looks of your application with //! custom themes and colors. For details see documentation of the //! [`cursive::theme`] module. //! //! [`cursive::theme`]: ./theme/index.html #![deny(missing_docs)] #![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] pub use cursive_core::*; mod utf8; pub mod backends; mod cursive_ext; mod cursive_runnable; pub use cursive_ext::CursiveExt; pub use cursive_runnable::CursiveRunnable; /// Creates a new Cursive root using one of the enabled backends. /// /// Will use the first available backend from this list: /// * BearLibTerminal /// * Termion /// * Crossterm /// * Pancurses /// * Ncurses /// /// If none of these is enabled, it will default to a dummy backend. /// /// # Panics /// /// If the backend initialization fails. pub fn default() -> CursiveRunnable { CursiveRunnable::default() } /// Creates a new Cursive root using a ncurses backend. #[cfg(feature = "ncurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "ncurses-backend")))] pub fn ncurses() -> CursiveRunnable { CursiveRunnable::ncurses() } /// Creates a new Cursive root using a pancurses backend. #[cfg(feature = "pancurses-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "pancurses-backend")))] pub fn pancurses() -> CursiveRunnable { CursiveRunnable::pancurses() } /// Creates a new Cursive root using a termion backend. #[cfg(feature = "termion-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "termion-backend")))] pub fn termion() -> CursiveRunnable { CursiveRunnable::termion() } /// Creates a new Cursive root using a crossterm backend. #[cfg(feature = "crossterm-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "crossterm-backend")))] pub fn crossterm() -> CursiveRunnable { CursiveRunnable::crossterm() } /// Creates a new Cursive root using a bear-lib-terminal backend. #[cfg(feature = "blt-backend")] #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "blt-backend")))] pub fn blt() -> CursiveRunnable { CursiveRunnable::blt() } /// Creates a new Cursive root using a dummy backend. /// /// Nothing will be output. This is mostly here for tests. pub fn dummy() -> CursiveRunnable { CursiveRunnable::dummy() } cursive-0.21.1/src/utf8.rs000064400000000000000000000033021046102023000134060ustar 00000000000000use std::char::from_u32; /// Reads a potentially multi-bytes utf8 codepoint. /// /// Reads the given first byte, and uses the given /// function to get more if needed. /// /// Returns an error if the stream is invalid utf-8. #[allow(dead_code)] pub fn read_char(first: u8, next: F) -> Result where F: Fn() -> Option, { if first < 0x80 { return Ok(first as char); } // Number of leading 1s determines the number of bytes we'll have to read let n_bytes = match (!first).leading_zeros() { n @ 2..=6 => n as usize, 1 => return Err("First byte is continuation byte.".to_string()), 7..=8 => return Err("WTF is this byte??".to_string()), _ => unreachable!(), }; let mut res = 0_u32; // First, get the data - only the few last bits res |= u32::from(first & make_mask(7 - n_bytes)); // We already have one byte, now read the others. for _ in 1..n_bytes { let byte = next().ok_or_else(|| "Missing UTF-8 byte".to_string())?; if byte & 0xC0 != 0x80 { return Err(format!( "Found non-continuation byte after leading: `{byte}`", )); } // We have 6 fresh new bits to read, make room. res <<= 6; // 0x3F is 00111111, so we keep the last 6 bits res |= u32::from(byte & 0x3F); } // from_u32 could return an error if we gave it invalid utf-8. // But we're probably safe since we respected the rules when building it. Ok(from_u32(res).unwrap()) } // Returns a simple bitmask with n 1s to the right. #[allow(dead_code)] fn make_mask(n: usize) -> u8 { let mut r = 0_u8; for i in 0..n { r |= 1 << i; } r }