pretty printing

This commit is contained in:
Jana Dönszelmann 2026-04-04 21:37:51 +02:00
parent 52a80cfb0e
commit c867ad379e
No known key found for this signature in database
30 changed files with 1533 additions and 387 deletions

606
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "adler2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.1.4" version = "1.1.4"
@ -53,7 +59,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -64,7 +70,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell_polyfill", "once_cell_polyfill",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -73,6 +79,27 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arboard"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
dependencies = [
"clipboard-win",
"image",
"log",
"objc2",
"objc2-app-kit",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-foundation",
"parking_lot",
"percent-encoding",
"windows-sys 0.60.2",
"wl-clipboard-rs",
"x11rb",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.5.2" version = "0.5.2"
@ -163,6 +190,12 @@ version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "castaway" name = "castaway"
version = "0.2.4" version = "0.2.4"
@ -234,6 +267,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "clipboard-win"
version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
dependencies = [
"error-code",
]
[[package]] [[package]]
name = "color-ansi" name = "color-ansi"
version = "0.1.0" version = "0.1.0"
@ -268,7 +310,7 @@ checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -289,6 +331,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.29.0" version = "0.29.0"
@ -316,6 +367,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.7"
@ -417,6 +474,16 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags 2.11.0",
"objc2",
]
[[package]] [[package]]
name = "document-features" name = "document-features"
version = "0.2.12" version = "0.2.12"
@ -426,6 +493,12 @@ dependencies = [
"litrs", "litrs",
] ]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]] [[package]]
name = "dumpster" name = "dumpster"
version = "2.1.0" version = "2.1.0"
@ -473,9 +546,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]]
name = "error-code"
version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
[[package]] [[package]]
name = "euclid" name = "euclid"
version = "0.22.13" version = "0.22.13"
@ -501,6 +580,35 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "fax"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
dependencies = [
"fax_derive",
]
[[package]]
name = "fax_derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]] [[package]]
name = "filedescriptor" name = "filedescriptor"
version = "0.8.3" version = "0.8.3"
@ -530,6 +638,22 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "flate2"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -573,6 +697,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "gethostname"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix",
"windows-link",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.3.4"
@ -598,6 +732,17 @@ dependencies = [
"wasip3", "wasip3",
] ]
[[package]]
name = "half"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
"zerocopy",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@ -642,6 +787,20 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"moxcms",
"num-traits",
"png",
"tiff",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.13.0" version = "2.13.0"
@ -721,7 +880,7 @@ dependencies = [
"portable-atomic", "portable-atomic",
"portable-atomic-util", "portable-atomic-util",
"serde_core", "serde_core",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -833,14 +992,23 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]] [[package]]
name = "logparse" name = "logparse"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"insta", "insta",
"logparse-pretty-print",
"proptest", "proptest",
"proptest-derive", "proptest-derive",
"winnow", "winnow",
] ]
[[package]]
name = "logparse-pretty-print"
version = "0.1.0"
dependencies = [
"color-ansi",
"unicode-segmentation",
]
[[package]] [[package]]
name = "loom" name = "loom"
version = "0.7.2" version = "0.7.2"
@ -909,6 +1077,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.1.1" version = "1.1.1"
@ -918,7 +1096,17 @@ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi",
"windows-sys", "windows-sys 0.61.2",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
] ]
[[package]] [[package]]
@ -956,13 +1144,22 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "nom"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.3" version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [ dependencies = [
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1000,6 +1197,79 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-graphics",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags 2.11.0",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags 2.11.0",
"objc2",
"objc2-core-foundation",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
@ -1021,6 +1291,16 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "os_pipe"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@ -1044,6 +1324,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.6" version = "2.8.6"
@ -1087,6 +1373,17 @@ dependencies = [
"sha2", "sha2",
] ]
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset 0.5.7",
"hashbrown 0.15.5",
"indexmap",
]
[[package]] [[package]]
name = "phf" name = "phf"
version = "0.11.3" version = "0.11.3"
@ -1145,6 +1442,25 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "png"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags 2.11.0",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.1" version = "1.13.1"
@ -1186,20 +1502,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "pretty-print"
version = "0.1.8"
dependencies = [
"color-ansi",
"unicode-segmentation",
]
[[package]] [[package]]
name = "pretty-test" name = "pretty-test"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"logparse-pretty-print",
"pretty", "pretty",
"pretty-print",
] ]
[[package]] [[package]]
@ -1251,12 +1559,33 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "pxfm"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "1.2.3" version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.44" version = "1.0.44"
@ -1462,6 +1791,7 @@ checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
name = "rustc-logviz" name = "rustc-logviz"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arboard",
"clap", "clap",
"crossterm", "crossterm",
"dumpster", "dumpster",
@ -1469,7 +1799,6 @@ dependencies = [
"jiff", "jiff",
"logparse", "logparse",
"nix 0.31.1", "nix 0.31.1",
"pretty-print",
"ratatui", "ratatui",
"ratatui-themes", "ratatui-themes",
"regex", "regex",
@ -1498,7 +1827,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1514,7 +1843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [ dependencies = [
"fnv", "fnv",
"quick-error", "quick-error 1.2.3",
"tempfile", "tempfile",
"wait-timeout", "wait-timeout",
] ]
@ -1643,6 +1972,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.7.0" version = "2.7.0"
@ -1726,7 +2061,7 @@ dependencies = [
"getrandom 0.4.1", "getrandom 0.4.1",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1736,7 +2071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [ dependencies = [
"fnv", "fnv",
"nom", "nom 7.1.3",
"phf", "phf",
"phf_codegen", "phf_codegen",
] ]
@ -1762,7 +2097,7 @@ dependencies = [
"fancy-regex", "fancy-regex",
"filedescriptor", "filedescriptor",
"finl_unicode", "finl_unicode",
"fixedbitset", "fixedbitset 0.4.2",
"hex", "hex",
"lazy_static", "lazy_static",
"libc", "libc",
@ -1841,6 +2176,20 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "tiff"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
"fax",
"flate2",
"half",
"quick-error 2.0.1",
"weezl",
"zune-jpeg",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.47" version = "0.3.47"
@ -1911,6 +2260,17 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "tree_magic_mini"
version = "3.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
dependencies = [
"memchr",
"nom 8.0.0",
"petgraph",
]
[[package]] [[package]]
name = "tui-widget-list" name = "tui-widget-list"
version = "0.15.0" version = "0.15.0"
@ -2131,6 +2491,82 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "wayland-backend"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
dependencies = [
"cc",
"downcast-rs",
"rustix",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-client"
version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
dependencies = [
"bitflags 2.11.0",
"rustix",
"wayland-backend",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols"
version = "0.32.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
dependencies = [
"bitflags 2.11.0",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [
"bitflags 2.11.0",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.31.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
dependencies = [
"proc-macro2",
"quick-xml",
"quote",
]
[[package]]
name = "wayland-sys"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
dependencies = [
"pkg-config",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "wezterm-bidi" name = "wezterm-bidi"
version = "0.2.3" version = "0.2.3"
@ -2240,6 +2676,15 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.61.2" version = "0.61.2"
@ -2249,6 +2694,71 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"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.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "1.0.1" version = "1.0.1"
@ -2346,6 +2856,41 @@ dependencies = [
"wasmparser", "wasmparser",
] ]
[[package]]
name = "wl-clipboard-rs"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
dependencies = [
"libc",
"log",
"os_pipe",
"rustix",
"thiserror 2.0.18",
"tree_magic_mini",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-protocols-wlr",
]
[[package]]
name = "x11rb"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
dependencies = [
"gethostname",
"rustix",
"x11rb-protocol",
]
[[package]]
name = "x11rb-protocol"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.48" version = "0.8.48"
@ -2371,3 +2916,18 @@ name = "zmij"
version = "1.0.21" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zune-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]

View file

@ -25,5 +25,5 @@ nix = {version = "0.31", features = ["process", "signal"]}
regex = "1" regex = "1"
crossterm = "*" crossterm = "*"
dumpster = "2.1" dumpster = "2.1"
logparse = {path = "./logparse/", version="0.2.0"} logparse = {path = "./logparse/", version="0.3.0"}
pretty-print = {path = "./pretty-print/projects/pretty-print"} arboard = {version="3", features=["wayland-data-control"]}

View file

@ -1,6 +1,6 @@
[package] [package]
name = "logparse" name = "logparse"
version = "0.2.0" version = "0.3.0"
edition = "2024" edition = "2024"
description = "parse arbitrary messages containing rust-like debug output to syntax highlight them" description = "parse arbitrary messages containing rust-like debug output to syntax highlight them"
authors = ["Jana Dönszelmann <cratesio@donsz.nl>"] authors = ["Jana Dönszelmann <cratesio@donsz.nl>"]
@ -12,6 +12,9 @@ readme = "README.md"
[dependencies] [dependencies]
winnow = {version="1", features=["parser"]} winnow = {version="1", features=["parser"]}
logparse-pretty-print = {path = "../pretty-print/projects/pretty-print", version="0.1.0"}
[dev-dependencies]
proptest = "1" proptest = "1"
proptest-derive = "0.8" proptest-derive = "0.8"
insta = "1" insta = "1"

View file

@ -1,3 +1,4 @@
#[cfg(test)]
use proptest_derive::Arbitrary; use proptest_derive::Arbitrary;
use std::borrow::Cow; use std::borrow::Cow;
@ -11,7 +12,8 @@ pub enum Separator {
} }
/// See [`Token::String`]. /// See [`Token::String`].
#[derive(Copy, Clone, Debug, Arbitrary, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(test, derive(Arbitrary))]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum QuoteType { pub enum QuoteType {
Single, Single,
@ -20,7 +22,8 @@ pub enum QuoteType {
} }
/// See [`Token::Delimited`]. /// See [`Token::Delimited`].
#[derive(Clone, Debug, Arbitrary, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(test, derive(Arbitrary))]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum Delimiter { pub enum Delimiter {
Paren, Paren,
@ -50,7 +53,8 @@ pub struct AnyString<'a> {
pub struct Space<'a>(pub Cow<'a, str>); pub struct Space<'a>(pub Cow<'a, str>);
/// See [`Token::Path`]. /// See [`Token::Path`].
#[derive(Copy, Clone, Debug, PartialEq, Arbitrary)] #[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(test, derive(Arbitrary))]
#[allow(missing_docs)] #[allow(missing_docs)]
pub enum PathSep { pub enum PathSep {
/// Happens at the start of paths, for the no leading / case /// Happens at the start of paths, for the no leading / case

View file

@ -1,5 +1,5 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(warnings)] // #![deny(warnings)]
#![doc=include_str!("../README.md")] #![doc=include_str!("../README.md")]
/// The structure of parsed log lines /// The structure of parsed log lines

View file

@ -23,7 +23,7 @@ proptest! {
#[test] #[test]
fn proptest_into_spans(original in Segments::arb(Token::arb())) { fn proptest_into_spans(original in Segments::arb(Token::arb())) {
let stringified = original.to_string(); let stringified = original.to_string();
let spans = into_spans(original, Config {collapse_space: false}); let spans = into_spans(original, Config {collapse_space: false, ..Default::default()});
let spans_concatenated = spans.into_iter().map(|i| i.text).collect::<String>(); let spans_concatenated = spans.into_iter().map(|i| i.text).collect::<String>();
assert_eq!(stringified, spans_concatenated); assert_eq!(stringified, spans_concatenated);
} }

View file

@ -47,12 +47,11 @@ pub struct Span<'a> {
/// Configuration options for [`into_spans`] /// Configuration options for [`into_spans`]
#[derive(Default)] #[derive(Default)]
#[non_exhaustive]
pub struct Config { pub struct Config {
/// Turn sequences of more than 1 space into exactly 1 space. /// Turn sequences of more than 1 space into exactly 1 space.
pub collapse_space: bool, pub collapse_space: bool,
/// Pretty print: wrap at braces etc /// Pretty print: wrap at braces etc to the given width
pub pretty_print: bool, pub pretty_print: Option<usize>,
} }
pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {} pub trait IntoSpans<'a>: private::IntoSpansImpl<'a> {}
@ -63,18 +62,27 @@ pub fn into_spans<'a>(ast: impl IntoSpans<'a>, config: Config) -> Vec<Span<'a>>
config, config,
res: Vec::new(), res: Vec::new(),
depth: 0, depth: 0,
ignore_next_space: false,
}; };
ast.into_spans(&mut cx); if let Some(width) = cx.config.pretty_print {
let res = ast.into_pretty_spans(&mut cx);
let _ = res.render_vec(width, &mut cx.res);
} else {
ast.into_spans(&mut cx)
}
cx.res cx.res
} }
mod private { mod private {
use logparse_pretty_print::{PrettyBuilder, PrettyTree, Text};
use super::*; use super::*;
pub struct Context<'a> { pub struct Context<'a> {
pub config: Config, pub config: Config,
pub res: Vec<Span<'a>>, pub res: Vec<Span<'a>>,
pub depth: usize, pub depth: usize,
pub ignore_next_space: bool,
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
@ -86,8 +94,22 @@ mod private {
} }
} }
impl<'a> Text<'a> for Span<'a> {
fn from_static_str(s: &'static str) -> Self {
Span {
text: Cow::Borrowed(s),
kind: SpanKind::Text,
}
}
fn as_str(&self) -> Cow<'_, str> {
self.text.clone()
}
}
pub trait IntoSpansImpl<'a> { pub trait IntoSpansImpl<'a> {
fn into_spans(self, cx: &mut Context<'a>); fn into_spans(self, cx: &mut Context<'a>);
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>>;
} }
impl<'a, T> IntoSpans<'a> for T where T: IntoSpansImpl<'a> {} impl<'a, T> IntoSpans<'a> for T where T: IntoSpansImpl<'a> {}
@ -100,14 +122,48 @@ mod private {
Separator::DoubleColon => cx.push("::", SpanKind::Separator), Separator::DoubleColon => cx.push("::", SpanKind::Separator),
} }
} }
fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
match self {
Separator::Eq => PrettyTree::text(Span {
text: Cow::Borrowed("="),
kind: SpanKind::Separator,
}),
Separator::Colon => PrettyTree::text(Span {
text: Cow::Borrowed(":"),
kind: SpanKind::Separator,
}),
Separator::DoubleColon => PrettyTree::text(Span {
text: Cow::Borrowed("::"),
kind: SpanKind::Separator,
}),
}
}
} }
impl<'a> IntoSpansImpl<'a> for QuoteType { impl<'a> IntoSpansImpl<'a> for QuoteType {
fn into_spans(self, cx: &mut Context<'a>) { fn into_spans(self, cx: &mut Context<'a>) {
match self { match self {
QuoteType::Single => cx.push("'", SpanKind::Separator), QuoteType::Single => cx.push("'", SpanKind::Surroundings),
QuoteType::Double => cx.push("\"", SpanKind::Separator), QuoteType::Double => cx.push("\"", SpanKind::Surroundings),
QuoteType::Backtick => cx.push("`", SpanKind::Separator), QuoteType::Backtick => cx.push("`", SpanKind::Surroundings),
}
}
fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
match self {
QuoteType::Single => PrettyTree::text(Span {
text: Cow::Borrowed("'"),
kind: SpanKind::Surroundings,
}),
QuoteType::Double => PrettyTree::text(Span {
text: Cow::Borrowed("\""),
kind: SpanKind::Surroundings,
}),
QuoteType::Backtick => PrettyTree::text(Span {
text: Cow::Borrowed("`"),
kind: SpanKind::Surroundings,
}),
} }
} }
} }
@ -135,12 +191,55 @@ mod private {
} }
cx.push(suffix, SpanKind::Surroundings); cx.push(suffix, SpanKind::Surroundings);
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
let Self {
prefix,
ty,
contents,
num_hashtags,
suffix,
} = self;
let hashtags = "#".repeat(num_hashtags);
PrettyTree::text(Span {
text: prefix,
kind: SpanKind::Surroundings,
})
.append(PrettyTree::text(Span {
text: Cow::Owned(hashtags.clone()),
kind: SpanKind::Surroundings,
}))
.group()
.append(ty.into_pretty_spans(cx))
.group()
.append(PrettyTree::text(Span {
text: contents,
kind: SpanKind::String,
}))
.group()
.append(ty.into_pretty_spans(cx))
.group()
.append(PrettyTree::text(Span {
text: suffix,
kind: SpanKind::Surroundings,
}))
.group()
}
} }
impl<'a> IntoSpansImpl<'a> for Path<'a> { impl<'a> IntoSpansImpl<'a> for Path<'a> {
fn into_spans(self, cx: &mut Context<'a>) { fn into_spans(self, cx: &mut Context<'a>) {
cx.push(self.to_string(), SpanKind::Path) cx.push(self.to_string(), SpanKind::Path)
} }
fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
PrettyTree::text(Span {
text: Cow::Owned(self.to_string()),
kind: SpanKind::Path,
})
}
} }
impl<'a> IntoSpansImpl<'a> for Number<'a> { impl<'a> IntoSpansImpl<'a> for Number<'a> {
@ -152,6 +251,29 @@ mod private {
cx.push(suffix, SpanKind::Surroundings); cx.push(suffix, SpanKind::Surroundings);
} }
} }
fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
let main = PrettyTree::text(Span {
text: self.number,
kind: SpanKind::Number,
});
let suffix = if let Some(suffix) = self.suffix_without_underscore {
PrettyTree::text(Span {
text: Cow::Borrowed("_"),
kind: SpanKind::Surroundings,
})
.append(PrettyTree::text(Span {
text: suffix,
kind: SpanKind::Surroundings,
}))
.group()
} else {
PrettyTree::Nil
};
main.append(suffix).group()
}
} }
impl<'a> IntoSpansImpl<'a> for Atom<'a> { impl<'a> IntoSpansImpl<'a> for Atom<'a> {
@ -160,6 +282,15 @@ mod private {
Atom::Text(text) => cx.push(text, SpanKind::Text), Atom::Text(text) => cx.push(text, SpanKind::Text),
} }
} }
fn into_pretty_spans(self, _cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
match self {
Atom::Text(text) => PrettyTree::text(Span {
text,
kind: SpanKind::Text,
}),
}
}
} }
impl<'a> IntoSpansImpl<'a> for Space<'a> { impl<'a> IntoSpansImpl<'a> for Space<'a> {
@ -171,6 +302,23 @@ mod private {
n => cx.push(self.0, SpanKind::Space(n)), n => cx.push(self.0, SpanKind::Space(n)),
} }
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
if cx.ignore_next_space {
cx.ignore_next_space = false;
return PrettyTree::Nil;
}
match self.0.len() {
0 => PrettyTree::Nil,
1 => PrettyTree::line_or_space(),
n if cx.config.collapse_space => PrettyTree::line_or_space(),
n => PrettyTree::text(Span {
text: self.0,
kind: SpanKind::Space(n),
}),
}
}
} }
impl<'a> IntoSpansImpl<'a> for Token<'a> { impl<'a> IntoSpansImpl<'a> for Token<'a> {
@ -205,6 +353,50 @@ mod private {
} }
} }
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
match self {
Token::True => PrettyTree::text(Span {
text: Cow::Borrowed("true"),
kind: SpanKind::Literal,
}),
Token::False => PrettyTree::text(Span {
text: Cow::Borrowed("true"),
kind: SpanKind::Literal,
}),
Token::None => PrettyTree::text(Span {
text: Cow::Borrowed("None"),
kind: SpanKind::Literal,
}),
Token::Lifetime(lifetime) => PrettyTree::Text(Span {
text: Cow::Borrowed("'"),
kind: SpanKind::Surroundings,
})
.append(PrettyTree::text(Span {
text: lifetime,
kind: SpanKind::Lifetime,
}))
.group(),
Token::Path(path) => path.into_pretty_spans(cx),
Token::String(string) => string.into_pretty_spans(cx),
Token::Number(number) => number.into_pretty_spans(cx),
Token::Separated {
before,
space_before,
separator,
after,
} => before
.into_pretty_spans(cx)
.append(space_before.into_pretty_spans(cx))
.group()
.append(separator.into_pretty_spans(cx))
.group()
.append(after.into_pretty_spans(cx))
.group(),
Token::Delimited(delimited) => delimited.into_pretty_spans(cx),
Token::Atom(atom) => atom.into_pretty_spans(cx),
}
}
} }
impl<'a> IntoSpansImpl<'a> for Segment<'a> { impl<'a> IntoSpansImpl<'a> for Segment<'a> {
@ -216,6 +408,18 @@ mod private {
leading_space.into_spans(cx); leading_space.into_spans(cx);
token.into_spans(cx); token.into_spans(cx);
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
let Self {
leading_space,
token,
} = self;
leading_space
.into_pretty_spans(cx)
.append(token.into_pretty_spans(cx))
.group()
}
} }
impl<'a> IntoSpansImpl<'a> for Segments<'a> { impl<'a> IntoSpansImpl<'a> for Segments<'a> {
@ -229,6 +433,36 @@ mod private {
} }
trailing_space.into_spans(cx); trailing_space.into_spans(cx);
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
let ignore_start = cx.ignore_next_space;
let Self {
segments,
trailing_space,
} = self;
let mut res = PrettyTree::Nil;
for segment in segments {
res = res.append(segment.into_pretty_spans(cx)).group();
}
// if we ignore the first space, also ignore the last one.
if ignore_start {
cx.ignore_next_space = true;
}
res.append(trailing_space.into_pretty_spans(cx)).group()
}
}
fn prefix_kind(delimiter: Delimiter, space: &Space<'_>) -> SpanKind {
match delimiter {
Delimiter::Brace => SpanKind::Constructor,
Delimiter::Paren if space.0.is_empty() => SpanKind::Constructor,
Delimiter::Paren => SpanKind::Text,
Delimiter::Bracket => SpanKind::Text,
Delimiter::Angle if space.0.is_empty() => SpanKind::Constructor,
Delimiter::Angle => SpanKind::Text,
}
} }
impl<'a> IntoSpansImpl<'a> for Delimited<'a> { impl<'a> IntoSpansImpl<'a> for Delimited<'a> {
@ -241,17 +475,7 @@ mod private {
match prefix { match prefix {
Some((Atom::Text(text), space)) => { Some((Atom::Text(text), space)) => {
cx.push( cx.push(text, prefix_kind(delimiter, &space));
text,
match delimiter {
Delimiter::Brace => SpanKind::Constructor,
Delimiter::Paren if space.0.is_empty() => SpanKind::Constructor,
Delimiter::Paren => SpanKind::Text,
Delimiter::Bracket => SpanKind::Text,
Delimiter::Angle if space.0.is_empty() => SpanKind::Constructor,
Delimiter::Angle => SpanKind::Text,
},
);
space.into_spans(cx); space.into_spans(cx);
} }
None => {} None => {}
@ -275,12 +499,105 @@ mod private {
Delimiter::Angle => cx.push(">", SpanKind::Delimiter(cx.depth)), Delimiter::Angle => cx.push(">", SpanKind::Delimiter(cx.depth)),
} }
} }
fn into_pretty_spans(self, cx: &mut Context<'a>) -> PrettyTree<'a, Span<'a>> {
let Self {
prefix,
delimiter,
contents,
} = self;
let group = contents
.segments
.iter()
.filter(|i| !matches!(i.token, Token::Atom(..)))
.count()
<= 1;
let prefix = match prefix {
Some((Atom::Text(text), space)) => PrettyTree::text(Span {
text,
kind: prefix_kind(delimiter, &space),
})
.append(space.into_pretty_spans(cx))
.group(),
None => PrettyTree::Nil,
};
let open = match delimiter {
Delimiter::Paren => Span {
text: Cow::Borrowed("("),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Bracket => Span {
text: Cow::Borrowed("["),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Brace => Span {
text: Cow::Borrowed("{"),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Angle => Span {
text: Cow::Borrowed("<"),
kind: SpanKind::Delimiter(cx.depth),
},
};
let close = match delimiter {
Delimiter::Paren => Span {
text: Cow::Borrowed(")"),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Bracket => Span {
text: Cow::Borrowed("]"),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Brace => Span {
text: Cow::Borrowed("}"),
kind: SpanKind::Delimiter(cx.depth),
},
Delimiter::Angle => Span {
text: Cow::Borrowed(">"),
kind: SpanKind::Delimiter(cx.depth),
},
};
cx.depth += 1;
if !group {
cx.ignore_next_space = true;
}
let contents = contents.into_pretty_spans(cx);
cx.depth -= 1;
if group {
prefix
.append(PrettyTree::text(open))
.group()
.append(contents)
.group()
.append(PrettyTree::text(close))
} else {
prefix
.append(PrettyTree::text(open))
.group()
.append(PrettyTree::Hardline)
.group()
.append(contents)
.group()
.nest(4)
.group()
.append(PrettyTree::Hardline)
.group()
.append(PrettyTree::text(close))
.group()
}
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use insta::assert_debug_snapshot; use insta::{assert_debug_snapshot, assert_snapshot};
use super::SpanKind; use super::SpanKind;
use crate::{Config, into_spans, parse_input}; use crate::{Config, into_spans, parse_input};
@ -291,6 +608,7 @@ mod tests {
res, res,
Config { Config {
collapse_space: true, collapse_space: true,
..Default::default()
}, },
) )
.into_iter() .into_iter()
@ -298,6 +616,73 @@ mod tests {
.collect() .collect()
} }
fn pretty_print(input: &str, n: usize) -> String {
let res = parse_input(input).unwrap();
into_spans(
res,
Config {
pretty_print: Some(n),
..Default::default()
},
)
.into_iter()
.map(|i| i.text.into_owned())
.collect::<String>()
}
#[test]
fn pretty_print_ex1() {
assert_snapshot!(pretty_print(
r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 60
), @"
def_id=DefId(
0:3 ~ unsized_coercion[10fa]::Trait
)
");
assert_snapshot!(pretty_print(
r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 30
), @"
def_id=DefId(
0:3 ~
unsized_coercion[10fa]::Trait
)
");
assert_snapshot!(pretty_print(
r#"def_id=DefId(0:3 ~ unsized_coercion[10fa]::Trait)"#, 10
), @"
def_id=DefId(
0:3 ~
unsized_coercion[10fa]::Trait
)
");
}
#[test]
fn pretty_print_ex2() {
assert_snapshot!(pretty_print(
r#"stability: inspecting def_id=DefId(3:662 ~ alloc[ef11]::boxed::Box) span=tests/ui/impl-trait/unsized_coercion.rs:16:16: 16:30 (#0) of stability=Some(Stability { level: Stable { since: Version(RustcVersion { major: 1, minor: 0, patch: 0 }), allowed_through_unstable_modules: None }, feature: "rust1" })"#, 60
), @r#"
stability: inspecting
def_id=DefId(
3:662 ~ alloc[ef11]::boxed::Box
) span=tests/ui/impl-trait/unsized_coercion.rs:16:16: 16:30
(#0) of
stability=Some(Stability {
level:
Stable {
since:
Version(RustcVersion {
major: 1, minor: 0, patch: 0
}), allowed_through_unstable_modules: None
}, feature: "rust1"
})
"#);
}
#[test] #[test]
fn spans_ex1() { fn spans_ex1() {
assert_debug_snapshot!(spans( assert_debug_snapshot!(spans(
@ -418,7 +803,7 @@ mod tests {
), ),
( (
"\"", "\"",
Separator, Surroundings,
), ),
( (
"MetaSized", "MetaSized",
@ -426,7 +811,7 @@ mod tests {
), ),
( (
"\"", "\"",
Separator, Surroundings,
), ),
( (
"", "",

View file

@ -1,11 +1,11 @@
[package] [package]
name = "pretty-print" name = "logparse-pretty-print"
version = "0.1.8" version = "0.1.0"
authors = ["Aster <192607617@qq.com>"] authors = ["Aster <192607617@qq.com>", "Jana Dönszelmann <cratesio@donsz.nl>"]
description = "pretty print tree" description = "pretty print tree"
repository = "https://github.com/oovm/pretty-print" repository = "https://git.donsz.nl/jana/logviewer"
documentation = "https://docs.rs/pretty-print" documentation = "https://docs.rs/logparse-pretty-print"
readme = "Readme.md" readme = "../../Readme.md"
license = "MPL-2.0" license = "MPL-2.0"
edition = "2021" edition = "2021"
@ -14,12 +14,9 @@ unicode-segmentation = "1.10.1"
[dependencies.color-ansi] [dependencies.color-ansi]
version = "0.1.0" version = "0.1.0"
#default-features = false
#path = 'C:\Users\Dell\CLionProjects\color-rs\projects\color-ansi'
[dev-dependencies] [dev-dependencies]
[features] [features]
default = ["std"] default = ["std"]
std = [] std = []

View file

@ -1,3 +1,7 @@
use std::fmt::Debug;
use crate::Text;
use super::*; use super::*;
/// A soft block is a block that is not required to be on a new line. /// A soft block is a block that is not required to be on a new line.
@ -10,8 +14,8 @@ use super::*;
/// b, /// b,
/// } /// }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct HardBlock { pub struct HardBlock<'a, T> {
/// The indentation of the soft block /// The indentation of the soft block
pub indent: usize, pub indent: usize,
/// The left hand side of the soft block /// The left hand side of the soft block
@ -19,10 +23,21 @@ pub struct HardBlock {
/// The right hand side of the soft block /// The right hand side of the soft block
pub rhs: &'static str, pub rhs: &'static str,
/// The joint node of the soft block /// The joint node of the soft block
pub joint: PrettyTree, pub joint: PrettyTree<'a, T>,
} }
impl HardBlock { impl<'a, T: Text<'a> + Debug> Debug for HardBlock<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HardBlock")
.field("indent", &self.indent)
.field("lhs", &self.lhs)
.field("rhs", &self.rhs)
.field("joint", &self.joint)
.finish()
}
}
impl<'a, T: Text<'a>> HardBlock<'a, T> {
/// Build a new soft block /// Build a new soft block
pub fn new(lhs: &'static str, rhs: &'static str) -> Self { pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space() } Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space() }
@ -40,14 +55,14 @@ impl HardBlock {
Self::new("{", "}") Self::new("{", "}")
} }
/// Set the joint node of the soft block /// Set the joint node of the soft block
pub fn with_joint(self, joint: PrettyTree) -> Self { pub fn with_joint(self, joint: PrettyTree<'a, T>) -> Self {
Self { joint, ..self } Self { joint, ..self }
} }
} }
impl HardBlock { impl<'a, T: Clone + Text<'a>> HardBlock<'a, T> {
/// Join a slice of pretty printables with the soft block /// Join a slice of pretty printables with the soft block
pub fn join_slice<T: PrettyPrint>(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree { pub fn join_slice<P: PrettyPrint<'a, T>>(&self, slice: &[P], theme: &PrettyProvider) -> PrettyTree<'a, T> {
let mut outer = PrettySequence::new(5); let mut outer = PrettySequence::new(5);
outer += self.lhs; outer += self.lhs;
outer += PrettyTree::Hardline; outer += PrettyTree::Hardline;

View file

@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::PrettyBuilder; use crate::{PrettyBuilder, Text};
/// `K & R` style brackets /// `K & R` style brackets
/// ///
@ -28,15 +28,15 @@ impl KAndRBracket {
Self { head_space: true, bracket_l: "{", bracket_r: "}" } Self { head_space: true, bracket_l: "{", bracket_r: "}" }
} }
/// Build a bracketed block /// Build a bracketed block
pub fn build<'a, I>( pub fn build<'b, 'a, I, T: Text<'a>>(
&self, &self,
items: &[I], items: &[I],
allocator: &'a PrettyProvider, allocator: &'b PrettyProvider,
inline_join: PrettyTree, inline_join: PrettyTree<'a, T>,
block_join: PrettyTree, block_join: PrettyTree<'a, T>,
) -> PrettyTree ) -> PrettyTree<'a, T>
where where
I: PrettyPrint, I: PrettyPrint<'a, T>,
{ {
let mut output = PrettySequence::new(5); let mut output = PrettySequence::new(5);
if self.head_space { if self.head_space {

View file

@ -1,60 +1,70 @@
use std::fmt::Debug;
use crate::Text;
use super::*; use super::*;
/// The document sequence type. /// The document sequence type.
#[derive(Clone, Debug, Default)] #[derive(Clone, Default)]
pub struct PrettySequence { pub struct PrettySequence<'a, T> {
items: Vec<PrettyTree>, items: Vec<PrettyTree<'a, T>>,
} }
impl PrettySequence { impl<'a, T: Text<'a> + Debug> Debug for PrettySequence<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrettySequence").field("items", &self.items).finish()
}
}
impl<'a, T> PrettySequence<'a, T> {
/// Create a new sequence with the given capacity. /// Create a new sequence with the given capacity.
pub fn new(capacity: usize) -> Self { pub fn new(capacity: usize) -> Self {
Self { items: Vec::with_capacity(capacity) } Self { items: Vec::with_capacity(capacity) }
} }
/// Create a new sequence with the given capacity. /// Create a new sequence with the given capacity.
pub fn push<T>(&mut self, item: T) pub fn push<U>(&mut self, item: U)
where where
T: Into<PrettyTree>, U: Into<PrettyTree<'a, T>>,
{ {
self.items.push(item.into()); self.items.push(item.into());
} }
/// Create a new sequence with the given capacity. /// Create a new sequence with the given capacity.
pub fn extend<I, T>(&mut self, items: I) pub fn extend<I, S>(&mut self, items: I)
where where
I: IntoIterator<Item = T>, I: IntoIterator<Item = S>,
T: Into<PrettyTree>, S: Into<PrettyTree<'a, T>>,
{ {
self.items.extend(items.into_iter().map(|x| x.into())); self.items.extend(items.into_iter().map(|x| x.into()));
} }
} }
impl PrettyBuilder for PrettySequence { impl<'a, T: Text<'a>> PrettyBuilder<'a, T> for PrettySequence<'a, T> {
fn flat_alt<E>(self, flat: E) -> PrettyTree fn flat_alt<E>(self, flat: E) -> PrettyTree<'a, T>
where where
E: Into<PrettyTree>, E: Into<PrettyTree<'a, T>>,
{ {
PrettyTree::from(self).flat_alt(flat) PrettyTree::from(self).flat_alt(flat)
} }
fn indent(self, indent: usize) -> PrettyTree { fn indent(self, indent: usize) -> PrettyTree<'a, T> {
PrettyTree::from(self).indent(indent) PrettyTree::from(self).indent(indent)
} }
fn nest(self, offset: isize) -> PrettyTree { fn nest(self, offset: isize) -> PrettyTree<'a, T> {
PrettyTree::from(self).nest(offset) PrettyTree::from(self).nest(offset)
} }
} }
impl<T> AddAssign<T> for PrettySequence impl<'a, U, T> AddAssign<U> for PrettySequence<'a, T>
where where
T: Into<PrettyTree>, U: Into<PrettyTree<'a, T>>,
{ {
fn add_assign(&mut self, rhs: T) { fn add_assign(&mut self, rhs: U) {
self.push(rhs); self.push(rhs);
} }
} }
impl From<PrettySequence> for PrettyTree { impl<'a, T: Text<'a>> From<PrettySequence<'a, T>> for PrettyTree<'a, T> {
fn from(value: PrettySequence) -> Self { fn from(value: PrettySequence<'a, T>) -> Self {
Self::concat(value.items) Self::concat(value.items)
} }
} }

View file

@ -1,3 +1,7 @@
use std::fmt::Debug;
use crate::Text;
use super::*; use super::*;
/// A soft block is a block that is not required to be on a new line. /// A soft block is a block that is not required to be on a new line.
@ -10,8 +14,8 @@ use super::*;
/// b, /// b,
/// } /// }
/// ``` /// ```
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct SoftBlock { pub struct SoftBlock<'a, T> {
/// The indentation of the soft block /// The indentation of the soft block
pub indent: usize, pub indent: usize,
/// The left hand side of the soft block /// The left hand side of the soft block
@ -19,12 +23,24 @@ pub struct SoftBlock {
/// The right hand side of the soft block /// The right hand side of the soft block
pub rhs: &'static str, pub rhs: &'static str,
/// The joint node of the soft block /// The joint node of the soft block
pub joint: PrettyTree, pub joint: PrettyTree<'a, T>,
/// The tail node of the soft block /// The tail node of the soft block
pub tail: PrettyTree, pub tail: PrettyTree<'a, T>,
} }
impl SoftBlock { impl<'a, T: Text<'a> + Debug> Debug for SoftBlock<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SoftBlock")
.field("indent", &self.indent)
.field("lhs", &self.lhs)
.field("rhs", &self.rhs)
.field("joint", &self.joint)
.field("tail", &self.tail)
.finish()
}
}
impl<'a, T: Text<'a>> SoftBlock<'a, T> {
/// Build a new soft block /// Build a new soft block
pub fn new(lhs: &'static str, rhs: &'static str) -> Self { pub fn new(lhs: &'static str, rhs: &'static str) -> Self {
Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space(), tail: PrettyTree::Nil } Self { lhs, rhs, indent: 4, joint: PrettyTree::line_or_space(), tail: PrettyTree::Nil }
@ -46,14 +62,14 @@ impl SoftBlock {
Self::new("{", "}") Self::new("{", "}")
} }
/// Set the joint node of the soft block /// Set the joint node of the soft block
pub fn with_joint(self, joint: PrettyTree) -> Self { pub fn with_joint(self, joint: PrettyTree<'a, T>) -> Self {
Self { joint, ..self } Self { joint, ..self }
} }
} }
impl SoftBlock { impl<'a, T: Text<'a> + Clone> SoftBlock<'a, T> {
/// Join a slice of pretty printables with the soft block /// Join a slice of pretty printables with the soft block
pub fn join_slice<T: PrettyPrint>(&self, slice: &[T], theme: &PrettyProvider) -> PrettyTree { pub fn join_slice<U: PrettyPrint<'a, T>>(&self, slice: &[U], theme: &PrettyProvider) -> PrettyTree<'a, T> {
let mut outer = PrettySequence::new(5); let mut outer = PrettySequence::new(5);
outer += self.lhs; outer += self.lhs;
outer += PrettyTree::line_or_space(); outer += PrettyTree::line_or_space();

View file

@ -11,21 +11,23 @@ extern crate core;
pub mod helpers; pub mod helpers;
mod providers; mod providers;
mod render; mod render;
mod text;
mod traits; mod traits;
mod tree; mod tree;
pub use self::render::{ pub use self::render::{
write_fmt::{BufferWrite, FmtWrite},
PrettyFormatter, Render, RenderAnnotated, PrettyFormatter, Render, RenderAnnotated,
write_fmt::{BufferWrite, FmtWrite},
}; };
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub use crate::render::write_io::{IoWrite, TerminalWriter}; pub use crate::render::write_io::{IoWrite, TerminalWriter, VecWrite};
pub use crate::{ pub use crate::{
providers::PrettyProvider, providers::PrettyProvider,
traits::{printer::PrettyPrint, PrettyBuilder}, traits::{PrettyBuilder, printer::PrettyPrint},
tree::PrettyTree, tree::PrettyTree,
}; };
pub use color_ansi::*; pub use color_ansi::*;
pub use text::Text;
/// Concatenates a number of documents (or values that can be converted into a document via the /// Concatenates a number of documents (or values that can be converted into a document via the
/// `Pretty` trait, like `&str`) /// `Pretty` trait, like `&str`)

View file

@ -1,5 +1,5 @@
use crate::{PrettyPrint, PrettyTree}; use crate::{PrettyPrint, PrettyTree, Text};
use alloc::{borrow::Cow, rc::Rc}; use alloc::rc::Rc;
use color_ansi::AnsiStyle; use color_ansi::AnsiStyle;
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Formatter};
@ -57,112 +57,110 @@ impl PrettyProvider {
self.width = width; self.width = width;
} }
/// Gets the width of the document. /// Gets the width of the document.
pub fn text<S>(&self, text: S) -> PrettyTree pub fn text<'a, S, T>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text) PrettyTree::text(text)
} }
/// Gets the width of the document. /// Gets the width of the document.
pub fn custom<S>(&self, text: S, style: Rc<AnsiStyle>) -> PrettyTree pub fn custom<'a, S, T: Text<'a>>(&self, text: S, style: Rc<AnsiStyle>) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(style) PrettyTree::text(text).annotate(style)
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn keyword<S>(&self, text: S) -> PrettyTree pub fn keyword<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.keyword.clone()) PrettyTree::text(text).annotate(self.keyword.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn identifier<S>(&self, text: S) -> PrettyTree pub fn identifier<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.operator.clone()) PrettyTree::text(text).annotate(self.operator.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn generic<S>(&self, text: S) -> PrettyTree pub fn generic<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.macros.clone()) PrettyTree::text(text).annotate(self.macros.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn variable<S>(&self, text: S, mutable: bool) -> PrettyTree pub fn variable<'a, S, T: Text<'a>>(&self, text: S, mutable: bool) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
if mutable { if mutable {
PrettyTree::text(text).annotate(self.local_mut.clone()) PrettyTree::text(text).annotate(self.local_mut.clone())
} } else {
else {
PrettyTree::text(text).annotate(self.local.clone()) PrettyTree::text(text).annotate(self.local.clone())
} }
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn argument<S>(&self, text: S, mutable: bool) -> PrettyTree pub fn argument<'a, S, T: Text<'a>>(&self, text: S, mutable: bool) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
if mutable { if mutable {
PrettyTree::text(text).annotate(self.argument_mut.clone()) PrettyTree::text(text).annotate(self.argument_mut.clone())
} } else {
else {
PrettyTree::text(text).annotate(self.argument.clone()) PrettyTree::text(text).annotate(self.argument.clone())
} }
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn operator<S>(&self, text: S) -> PrettyTree pub fn operator<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.operator.clone()) PrettyTree::text(text).annotate(self.operator.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn string<S>(&self, text: S) -> PrettyTree pub fn string<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.string.clone()) PrettyTree::text(text).annotate(self.string.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn annotation<S>(&self, text: S) -> PrettyTree pub fn annotation<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.macros.clone()) PrettyTree::text(text).annotate(self.macros.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn number<S>(&self, text: S) -> PrettyTree pub fn number<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.number.clone()) PrettyTree::text(text).annotate(self.number.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn structure<S>(&self, text: S) -> PrettyTree pub fn structure<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.structure.clone()) PrettyTree::text(text).annotate(self.structure.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn variant<S>(&self, text: S) -> PrettyTree pub fn variant<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.variant.clone()) PrettyTree::text(text).annotate(self.variant.clone())
} }
/// Allocate a document containing the given text. /// Allocate a document containing the given text.
pub fn interface<S>(&self, text: S) -> PrettyTree pub fn interface<'a, S, T: Text<'a>>(&self, text: S) -> PrettyTree<'a, T>
where where
S: Into<Cow<'static, str>>, S: Into<T>,
{ {
PrettyTree::text(text).annotate(self.interface.clone()) PrettyTree::text(text).annotate(self.interface.clone())
} }
@ -178,11 +176,11 @@ impl PrettyProvider {
/// let theme = PrettyProvider::new(80); /// let theme = PrettyProvider::new(80);
/// theme.join(vec!["a", "b", "c"], ", "); /// theme.join(vec!["a", "b", "c"], ", ");
/// ``` /// ```
pub fn join<I, T1, T2>(&self, iter: I, joint: T2) -> PrettyTree pub fn join<'a, I, T1, T2, T: Text<'a>>(&self, iter: I, joint: T2) -> PrettyTree<'a, T>
where where
I: IntoIterator<Item = T1>, I: IntoIterator<Item = T1>,
T1: PrettyPrint, T1: PrettyPrint<'a, T>,
T2: PrettyPrint, T2: PrettyPrint<'a, T>,
{ {
PrettyTree::join(iter.into_iter().map(|x| x.pretty(self)), joint.pretty(self)) PrettyTree::join(iter.into_iter().map(|x| x.pretty(self)), joint.pretty(self))
} }
@ -195,10 +193,10 @@ impl PrettyProvider {
/// let theme = PrettyProvider::new(80); /// let theme = PrettyProvider::new(80);
/// theme.join(&["a", "b", "c"], ", "); /// theme.join(&["a", "b", "c"], ", ");
/// ``` /// ```
pub fn join_slice<I, T>(&self, iter: &[I], joint: T) -> PrettyTree pub fn join_slice<'a, I, U, T: Text<'a>>(&self, iter: &[I], joint: U) -> PrettyTree<'a, T>
where where
I: PrettyPrint, I: PrettyPrint<'a, T>,
T: PrettyPrint, U: PrettyPrint<'a, T>,
{ {
PrettyTree::join(iter.iter().map(|s| s.pretty(self)), joint.pretty(self)) PrettyTree::join(iter.iter().map(|s| s.pretty(self)), joint.pretty(self))
} }
@ -211,10 +209,10 @@ impl PrettyProvider {
/// let theme = PrettyProvider::new(80); /// let theme = PrettyProvider::new(80);
/// theme.concat(vec!["1", "2", "3"]); /// theme.concat(vec!["1", "2", "3"]);
/// ``` /// ```
pub fn concat<I, T>(&self, iter: I) -> PrettyTree pub fn concat<'a, I, U, T: Text<'a>>(&self, iter: I) -> PrettyTree<'a, T>
where where
I: IntoIterator<Item = T>, I: IntoIterator<Item = U>,
T: PrettyPrint, U: PrettyPrint<'a, T>,
{ {
PrettyTree::concat(iter.into_iter().map(|x| x.pretty(self))) PrettyTree::concat(iter.into_iter().map(|x| x.pretty(self)))
} }
@ -227,9 +225,9 @@ impl PrettyProvider {
/// let theme = PrettyProvider::new(80); /// let theme = PrettyProvider::new(80);
/// theme.concat_slice(&["1", "2", "3"]); /// theme.concat_slice(&["1", "2", "3"]);
/// ``` /// ```
pub fn concat_slice<T>(&self, iter: &[T]) -> PrettyTree pub fn concat_slice<'a, U, T: Text<'a>>(&self, iter: &[U]) -> PrettyTree<'a, T>
where where
T: PrettyPrint, U: PrettyPrint<'a, T>,
{ {
PrettyTree::concat(iter.iter().map(|s| s.pretty(self))) PrettyTree::concat(iter.iter().map(|s| s.pretty(self)))
} }

View file

@ -1,7 +1,10 @@
use crate::{BufferWrite, PrettyTree}; use crate::{BufferWrite, PrettyTree, Text};
use alloc::{rc::Rc, vec, vec::Vec}; use alloc::{rc::Rc, vec, vec::Vec};
use color_ansi::AnsiStyle; use color_ansi::AnsiStyle;
use core::fmt::{Debug, Display, Formatter}; use core::{
fmt::{Debug, Display, Formatter},
slice,
};
#[cfg(feature = "std")] #[cfg(feature = "std")]
pub mod write_io; pub mod write_io;
@ -9,40 +12,36 @@ pub mod write_io;
pub mod write_fmt; pub mod write_fmt;
/// Trait representing the operations necessary to render a document /// Trait representing the operations necessary to render a document
pub trait Render { pub trait Render<'a, T> {
/// The type of the output /// The type of the output
type Error; type Error;
/// Write a string to the output /// Write to the output
fn write_str(&mut self, s: &str) -> Result<usize, Self::Error>; fn write_all(&mut self, s: &[T]) -> Result<(), Self::Error>;
/// Write a character to the output /// Emit an error
fn write_str_all(&mut self, mut s: &str) -> Result<(), Self::Error> {
while !s.is_empty() {
let count = self.write_str(s)?;
s = &s[count..];
}
Ok(())
}
/// Write a character to the output
fn fail_doc(&self) -> Self::Error; fn fail_doc(&self) -> Self::Error;
} }
/// The given text, which must not contain line breaks. /// The given text, which must not contain line breaks.
#[derive(Debug)] pub struct PrettyFormatter<'b, 'a, T> {
pub struct PrettyFormatter<'a> { tree: &'b PrettyTree<'a, T>,
tree: &'a PrettyTree,
width: usize, width: usize,
} }
impl<'a> Display for PrettyFormatter<'a> { impl<'a, 'b, T: Text<'a> + Debug> Debug for PrettyFormatter<'b, 'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrettyFormatter").field("tree", &self.tree).field("width", &self.width).finish()
}
}
impl<'a, 'b, T: Text<'a>> Display for PrettyFormatter<'b, 'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.tree.render_fmt(self.width, f) self.tree.render_fmt(self.width, f)
} }
} }
impl PrettyTree { impl<'a, T> PrettyTree<'a, T> {
/// Returns a value which implements `std::fmt::Display` /// Returns a value which implements `std::fmt::Display`
/// ///
/// ``` /// ```
@ -52,13 +51,13 @@ impl PrettyTree {
/// assert_eq!(format!("{}", doc.pretty(80)), "hello world"); /// assert_eq!(format!("{}", doc.pretty(80)), "hello world");
/// ``` /// ```
#[inline] #[inline]
pub fn pretty(&self, width: usize) -> PrettyFormatter<'_> { pub fn pretty(&self, width: usize) -> PrettyFormatter<'_, 'a, T> {
PrettyFormatter { tree: self, width } PrettyFormatter { tree: self, width }
} }
} }
/// Trait representing the operations necessary to write an annotated document. /// Trait representing the operations necessary to write an annotated document.
pub trait RenderAnnotated: Render { pub trait RenderAnnotated<'a, T>: Render<'a, T> {
/// Push an annotation onto the stack /// Push an annotation onto the stack
fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error>; fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error>;
/// Pop an annotation from the stack /// Pop an annotation from the stack
@ -78,13 +77,20 @@ macro_rules! make_spaces {
pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,); pub(crate) const SPACES: &str = make_spaces!(,,,,,,,,,,);
fn append_docs2(ldoc: Rc<PrettyTree>, rdoc: Rc<PrettyTree>, mut consumer: impl FnMut(Rc<PrettyTree>)) -> Rc<PrettyTree> { fn append_docs2<'a, T>(
ldoc: Rc<PrettyTree<'a, T>>,
rdoc: Rc<PrettyTree<'a, T>>,
mut consumer: impl FnMut(Rc<PrettyTree<'a, T>>),
) -> Rc<PrettyTree<'a, T>> {
let d = append_docs(rdoc, &mut consumer); let d = append_docs(rdoc, &mut consumer);
consumer(d); consumer(d);
append_docs(ldoc, &mut consumer) append_docs(ldoc, &mut consumer)
} }
fn append_docs(mut doc: Rc<PrettyTree>, consumer: &mut impl FnMut(Rc<PrettyTree>)) -> Rc<PrettyTree> { fn append_docs<'a, T>(
mut doc: Rc<PrettyTree<'a, T>>,
consumer: &mut impl FnMut(Rc<PrettyTree<'a, T>>),
) -> Rc<PrettyTree<'a, T>> {
loop { loop {
// Since appended documents often appear in sequence on the left side we // Since appended documents often appear in sequence on the left side we
// gain a slight performance increase by batching these pushes (avoiding // gain a slight performance increase by batching these pushes (avoiding
@ -100,9 +106,9 @@ fn append_docs(mut doc: Rc<PrettyTree>, consumer: &mut impl FnMut(Rc<PrettyTree>
} }
} }
pub fn best<W>(doc: Rc<PrettyTree>, width: usize, out: &mut W) -> Result<(), W::Error> pub fn best<'a, W, T: Text<'a>>(doc: Rc<PrettyTree<'a, T>>, width: usize, out: &mut W) -> Result<(), W::Error>
where where
W: RenderAnnotated, W: RenderAnnotated<'a, T>,
W: ?Sized, W: ?Sized,
{ {
Best { Best {
@ -123,43 +129,44 @@ enum Mode {
Flat, Flat,
} }
struct RenderCommand { struct RenderCommand<'a, T> {
indent: usize, indent: usize,
mode: Mode, mode: Mode,
node: Rc<PrettyTree>, node: Rc<PrettyTree<'a, T>>,
} }
fn write_newline<W>(ind: usize, out: &mut W) -> Result<(), W::Error> fn write_newline<'a, W, T: Text<'a>>(ind: usize, out: &mut W) -> Result<(), W::Error>
where where
W: ?Sized + Render, W: ?Sized + Render<'a, T>,
{ {
out.write_str_all("\n")?; out.write_all(&[T::newline()])?;
write_spaces(ind, out) write_spaces(ind, out)
} }
fn write_spaces<W>(spaces: usize, out: &mut W) -> Result<(), W::Error> fn write_spaces<'a, W, T: Text<'a>>(spaces: usize, out: &mut W) -> Result<(), W::Error>
where where
W: ?Sized + Render, W: ?Sized + Render<'a, T>,
{ {
let mut inserted = 0; let mut inserted = 0;
while inserted < spaces { while inserted < spaces {
let insert = core::cmp::min(SPACES.len(), spaces - inserted); let insert = core::cmp::min(SPACES.len(), spaces - inserted);
inserted += out.write_str(&SPACES[..insert])?; out.write_all(&[T::from_static_spaces(&SPACES[..insert])])?;
inserted += insert;
} }
Ok(()) Ok(())
} }
struct Best { struct Best<'a, T> {
pos: usize, pos: usize,
back_cmds: Vec<RenderCommand>, back_cmds: Vec<RenderCommand<'a, T>>,
front_cmds: Vec<Rc<PrettyTree>>, front_cmds: Vec<Rc<PrettyTree<'a, T>>>,
annotation_levels: Vec<usize>, annotation_levels: Vec<usize>,
width: usize, width: usize,
} }
impl Best { impl<'a, T: Text<'a>> Best<'a, T> {
fn fitting(&mut self, next: Rc<PrettyTree>, mut pos: usize, ind: usize) -> bool { fn fitting(&mut self, next: Rc<PrettyTree<'a, T>>, mut pos: usize, ind: usize) -> bool {
let mut bidx = self.back_cmds.len(); let mut bidx = self.back_cmds.len();
self.front_cmds.clear(); // clear from previous calls from best self.front_cmds.clear(); // clear from previous calls from best
self.front_cmds.push(next); self.front_cmds.push(next);
@ -196,14 +203,8 @@ impl Best {
return false; return false;
} }
} }
PrettyTree::StaticText(str) => { PrettyTree::Text(ref s) => {
pos += str.len(); pos += s.len();
if pos > self.width {
return false;
}
}
PrettyTree::Text(ref str) => {
pos += str.len();
if pos > self.width { if pos > self.width {
return false; return false;
} }
@ -240,7 +241,7 @@ impl Best {
fn best<W>(&mut self, top: usize, out: &mut W) -> Result<bool, W::Error> fn best<W>(&mut self, top: usize, out: &mut W) -> Result<bool, W::Error>
where where
W: RenderAnnotated, W: RenderAnnotated<'a, T>,
W: ?Sized, W: ?Sized,
{ {
let mut fits = true; let mut fits = true;
@ -302,24 +303,14 @@ impl Best {
} }
PrettyTree::RenderLength { length: len, body: doc } => match doc.as_ref() { PrettyTree::RenderLength { length: len, body: doc } => match doc.as_ref() {
PrettyTree::Text(s) => { PrettyTree::Text(s) => {
out.write_str_all(s)?; out.write_all(slice::from_ref(s))?;
self.pos += len;
fits &= self.pos <= self.width;
}
PrettyTree::StaticText(s) => {
out.write_str_all(s)?;
self.pos += len; self.pos += len;
fits &= self.pos <= self.width; fits &= self.pos <= self.width;
} }
_ => unreachable!(), _ => unreachable!(),
}, },
PrettyTree::Text(ref s) => { PrettyTree::Text(ref s) => {
out.write_str_all(s)?; out.write_all(slice::from_ref(s))?;
self.pos += s.len();
fits &= self.pos <= self.width;
}
PrettyTree::StaticText(s) => {
out.write_str_all(s)?;
self.pos += s.len(); self.pos += s.len();
fits &= self.pos <= self.width; fits &= self.pos <= self.width;
} }

View file

@ -1,7 +1,7 @@
use crate::{render::Annotation, Render, RenderAnnotated}; use crate::{Render, RenderAnnotated, Text, render::Annotation};
use alloc::rc::Rc; use alloc::rc::Rc;
use color_ansi::AnsiStyle; use color_ansi::AnsiStyle;
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Error, Formatter};
/// Writes to something implementing `std::fmt::Write` /// Writes to something implementing `std::fmt::Write`
pub struct FmtWrite<W> { pub struct FmtWrite<W> {
@ -9,27 +9,27 @@ pub struct FmtWrite<W> {
} }
/// Represents a terminal writer. /// Represents a terminal writer.
#[derive(Debug)] #[derive(Debug)]
pub struct BufferWrite { pub struct BufferWrite<T> {
buffer: String, buffer: Vec<T>,
annotations: Vec<(usize, Annotation<AnsiStyle>)>, annotations: Vec<(usize, Annotation<AnsiStyle>)>,
} }
impl BufferWrite { impl<'a, T: Text<'a>> BufferWrite<T> {
/// Creates a new terminal writer. /// Creates a new terminal writer.
pub fn new(capacity: usize) -> Self { pub fn new(capacity: usize) -> Self {
BufferWrite { buffer: String::with_capacity(capacity), annotations: Vec::new() } BufferWrite { buffer: Vec::with_capacity(capacity), annotations: Vec::new() }
} }
/// Creates a new terminal writer. /// Creates a new terminal writer.
pub fn render<W>(&mut self, render: &mut W) -> Result<(), W::Error> pub fn render<W>(&mut self, render: &mut W) -> Result<(), W::Error>
where where
W: RenderAnnotated, W: RenderAnnotated<'a, T>,
W: ?Sized, W: ?Sized,
{ {
let mut start = 0; let mut start = 0;
for (end, annotation) in &self.annotations { for (end, annotation) in &self.annotations {
let s = &self.buffer[start..*end]; let s = &self.buffer[start..*end];
if !s.is_empty() { if !s.is_empty() {
render.write_str_all(s)?; render.write_all(s)?;
} }
start = *end; start = *end;
match annotation { match annotation {
@ -39,31 +39,26 @@ impl BufferWrite {
} }
let s = &self.buffer[start..]; let s = &self.buffer[start..];
if !s.is_empty() { if !s.is_empty() {
render.write_str_all(s)?; render.write_all(s)?;
} }
Ok(()) Ok(())
} }
} }
impl Render for BufferWrite { impl<'a, T: Clone> Render<'a, T> for BufferWrite<T> {
type Error = core::fmt::Error; type Error = Error;
fn write_str(&mut self, s: &str) -> Result<usize, Self::Error> { fn write_all(&mut self, s: &[T]) -> Result<(), Self::Error> {
self.buffer.push_str(s); self.buffer.extend(s.iter().cloned());
Ok(s.len())
}
fn write_str_all(&mut self, s: &str) -> Result<(), Self::Error> {
self.buffer.push_str(s);
Ok(()) Ok(())
} }
fn fail_doc(&self) -> Self::Error { fn fail_doc(&self) -> Self::Error {
core::fmt::Error::default() Error
} }
} }
impl<W> RenderAnnotated for FmtWrite<W> impl<'a, W, T: Text<'a>> RenderAnnotated<'a, T> for FmtWrite<W>
where where
W: core::fmt::Write, W: core::fmt::Write,
{ {
@ -76,7 +71,7 @@ where
} }
} }
impl RenderAnnotated for BufferWrite { impl<'a, T: Clone> RenderAnnotated<'a, T> for BufferWrite<T> {
fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error> { fn push_annotation(&mut self, annotation: Rc<AnsiStyle>) -> Result<(), Self::Error> {
self.annotations.push((self.buffer.len(), Annotation::Push(annotation))); self.annotations.push((self.buffer.len(), Annotation::Push(annotation)));
Ok(()) Ok(())
@ -101,21 +96,20 @@ impl<W> FmtWrite<W> {
} }
} }
impl<W> Render for FmtWrite<W> impl<'a, W, T: Text<'a>> Render<'a, T> for FmtWrite<W>
where where
W: core::fmt::Write, W: core::fmt::Write,
{ {
type Error = core::fmt::Error; type Error = Error;
fn write_str(&mut self, s: &str) -> Result<usize, core::fmt::Error> { fn write_all(&mut self, s: &[T]) -> core::fmt::Result {
self.write_str_all(s).map(|_| s.len()) for i in s {
} self.upstream.write_str(i.as_str().as_ref())?
}
fn write_str_all(&mut self, s: &str) -> core::fmt::Result { Ok(())
self.upstream.write_str(s)
} }
fn fail_doc(&self) -> Self::Error { fn fail_doc(&self) -> Self::Error {
core::fmt::Error Error
} }
} }

View file

@ -1,8 +1,13 @@
#[cfg(feature = "std")]
use crate::Text;
use crate::{Render, RenderAnnotated}; use crate::{Render, RenderAnnotated};
use alloc::rc::Rc; use alloc::rc::Rc;
use color_ansi::{AnsiAbility, AnsiStyle, AnsiWriter}; use color_ansi::{AnsiAbility, AnsiStyle, AnsiWriter};
use core::fmt::{Debug, Formatter}; use core::fmt::{Debug, Formatter};
use std::io::{Error, ErrorKind, Write};
#[cfg(feature = "std")]
use std::io::{Error, Write};
/// Represents a terminal writer. /// Represents a terminal writer.
pub struct TerminalWriter<W> { pub struct TerminalWriter<W> {
color_stack: Vec<Rc<AnsiStyle>>, color_stack: Vec<Rc<AnsiStyle>>,
@ -28,29 +33,29 @@ impl<W> IoWrite<W> {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<W> Render for IoWrite<W> impl<'a, W, T: Text<'a>> Render<'a, T> for IoWrite<W>
where where
W: std::io::Write, W: std::io::Write,
{ {
type Error = std::io::Error; type Error = std::io::Error;
fn write_str(&mut self, s: &str) -> std::io::Result<usize> { fn write_all(&mut self, s: &[T]) -> std::io::Result<()> {
self.upstream.write(s.as_bytes()) for i in s {
} self.upstream.write_all(i.as_str().as_bytes())?;
}
fn write_str_all(&mut self, s: &str) -> std::io::Result<()> { Ok(())
self.upstream.write_all(s.as_bytes())
} }
fn fail_doc(&self) -> Self::Error { fn fail_doc(&self) -> Self::Error {
std::io::Error::new(std::io::ErrorKind::Other, "Document failed to render") std::io::Error::other("Document failed to render")
} }
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
impl<W> RenderAnnotated for IoWrite<W> impl<'a, W, T: Text<'a>> RenderAnnotated<'a, T> for IoWrite<W>
where where
W: std::io::Write, W: std::io::Write,
{ {
fn push_annotation(&mut self, _: Rc<AnsiStyle>) -> Result<(), Self::Error> { fn push_annotation(&mut self, _: Rc<AnsiStyle>) -> Result<(), Self::Error> {
Ok(()) Ok(())
@ -79,26 +84,26 @@ impl<W: Write> TerminalWriter<W> {
} }
} }
impl<W> Render for TerminalWriter<W> impl<'a, W, T: Text<'a>> Render<'a, T> for TerminalWriter<W>
where where
W: Write, W: Write,
{ {
type Error = Error; type Error = Error;
fn write_str(&mut self, s: &str) -> std::io::Result<usize> { fn write_all(&mut self, s: &[T]) -> std::io::Result<()> {
self.upstream.write(s.as_bytes()) for i in s {
} self.upstream.write_all(i.as_str().as_bytes())?;
}
fn write_str_all(&mut self, s: &str) -> std::io::Result<()> { Ok(())
self.upstream.write_all(s.as_bytes())
} }
fn fail_doc(&self) -> Self::Error { fn fail_doc(&self) -> Self::Error {
Error::new(ErrorKind::Other, "Document failed to render") Error::other("Document failed to render")
} }
} }
impl<W: Write> RenderAnnotated for TerminalWriter<W> { impl<'a, W: Write, T: Text<'a>> RenderAnnotated<'a, T> for TerminalWriter<W> {
fn push_annotation(&mut self, color: Rc<AnsiStyle>) -> Result<(), Self::Error> { fn push_annotation(&mut self, color: Rc<AnsiStyle>) -> Result<(), Self::Error> {
self.color_stack.push(color.clone()); self.color_stack.push(color.clone());
self.upstream.set_style(&color) self.upstream.set_style(&color)
@ -112,3 +117,49 @@ impl<W: Write> RenderAnnotated for TerminalWriter<W> {
} }
} }
} }
/// Writes to something implementing `std::io::Write`
pub struct VecWrite<'v, T> {
upstream: &'v mut Vec<T>,
}
impl<'v, W> Debug for VecWrite<'v, W> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.debug_struct("VecWrite").finish()
}
}
impl<'v, T> VecWrite<'v, T> {
/// Creates a new terminal writer.
pub fn new(upstream: &'v mut Vec<T>) -> VecWrite<'v, T> {
VecWrite { upstream }
}
}
#[cfg(feature = "std")]
impl<'a, T: Text<'a>> Render<'a, T> for VecWrite<'_, T> {
type Error = &'static str;
fn write_all(&mut self, s: &[T]) -> Result<(), &'static str> {
for i in s {
self.upstream.push(i.clone());
}
Ok(())
}
fn fail_doc(&self) -> Self::Error {
"Document failed to render"
}
}
#[cfg(feature = "std")]
impl<'a, T: Text<'a>> RenderAnnotated<'a, T> for VecWrite<'_, T> {
fn push_annotation(&mut self, _: Rc<AnsiStyle>) -> Result<(), Self::Error> {
Ok(())
}
fn pop_annotation(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}

View file

@ -0,0 +1,58 @@
use std::borrow::Cow;
/// Pretty printing can work with arbitrary text-like objects,
/// as long as they implement this trait.
#[allow(clippy::len_without_is_empty)]
pub trait Text<'a>: Sized + Clone + 'a {
/// Turn a &'static str into Self, without any further knowledge about it.
///
/// Used for a bunch of functions to give a default.
/// Often not the most optimal or even desired implementation.
fn from_static_str(s: &'static str) -> Self;
/// We foten insert spaces as a static str.
///
/// By default this is implemented with [`from_static_str`].
fn from_static_spaces(s: &'static str) -> Self {
Self::from_static_str(s)
}
/// Turn custom text into a `Cow<'a, str>`.
fn as_str(&self) -> Cow<'_, str>;
/// Get the length of the custom text.
fn len(&self) -> usize {
str::len(&self.as_str())
}
/// A space character
///
/// By default this is implemented with [`from_static_str`].
fn space() -> Self {
Self::from_static_str(" ")
}
/// A newline character
///
/// By default this is implemented with [`from_static_str`].
fn newline() -> Self {
Self::from_static_str("\n")
}
/// A comma and space character
///
/// By default this is implemented with [`from_static_str`].
fn comma_space() -> Self {
Self::from_static_str(", ")
}
}
impl<'a> Text<'a> for Cow<'a, str> {
fn from_static_str(s: &'static str) -> Self {
Cow::Borrowed(s)
}
fn as_str(&self) -> Cow<'a, str> {
self.clone()
}
}

View file

@ -1,10 +1,10 @@
use crate::{providers::PrettyProvider, PrettyTree}; use crate::{PrettyTree, providers::PrettyProvider};
use alloc::string::String; use alloc::string::String;
pub mod printer; pub mod printer;
/// The `PrettyPrint` trait is implemented by types that can be pretty-printed. /// The `PrettyPrint` trait is implemented by types that can be pretty-printed.
pub trait PrettyBuilder { pub trait PrettyBuilder<'a, T> {
/// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
/// ///
/// ``` /// ```
@ -23,12 +23,12 @@ pub trait PrettyBuilder {
/// assert_eq!(doc.1.pretty(100).to_string(), "let x in x"); /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
/// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx"); /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
/// ``` /// ```
fn flat_alt<E>(self, inline: E) -> PrettyTree fn flat_alt<E>(self, inline: E) -> PrettyTree<'a, T>
where where
E: Into<PrettyTree>; E: Into<PrettyTree<'a, T>>;
/// Acts as `self` when laid out on a single line and acts as `that` when laid out on multiple lines. /// Acts as `self` when laid out on a single line and acts as `that` when laid out on multiple lines.
fn indent(self, indent: usize) -> PrettyTree; fn indent(self, indent: usize) -> PrettyTree<'a, T>;
/// Increase the indentation level of this document. /// Increase the indentation level of this document.
fn nest(self, offset: isize) -> PrettyTree; fn nest(self, offset: isize) -> PrettyTree<'a, T>;
} }

View file

@ -1,13 +1,15 @@
use crate::Text;
use super::*; use super::*;
/// Marker trait for types that can be pretty printed. /// Marker trait for types that can be pretty printed.
pub trait PrettyPrint { pub trait PrettyPrint<'a, T: Text<'a>> {
/// Build a pretty tree for this type. /// Build a pretty tree for this type.
fn pretty(&self, theme: &PrettyProvider) -> PrettyTree; fn pretty(&self, theme: &PrettyProvider) -> PrettyTree<'a, T>;
/// Get a pretty string for this type. /// Get a pretty string for this type.
fn pretty_string(&self, theme: &PrettyProvider) -> String { fn pretty_string(&self, theme: &PrettyProvider) -> String {
let mut buffer = String::new(); let mut buffer = String::new();
if let Err(e) = self.pretty(&theme).render_fmt(theme.get_width(), &mut buffer) { if let Err(e) = self.pretty(theme).render_fmt(theme.get_width(), &mut buffer) {
panic!("Error: {}", e); panic!("Error: {}", e);
} }
buffer buffer
@ -15,7 +17,7 @@ pub trait PrettyPrint {
/// Print a pretty string for this type. /// Print a pretty string for this type.
fn pretty_colorful(&self, theme: &PrettyProvider) -> String { fn pretty_colorful(&self, theme: &PrettyProvider) -> String {
let mut buffer = vec![]; let mut buffer = vec![];
if let Err(e) = self.pretty(&theme).render_colored(theme.get_width(), &mut buffer) { if let Err(e) = self.pretty(theme).render_colored(theme.get_width(), &mut buffer) {
panic!("Error: {}", e); panic!("Error: {}", e);
} }
match String::from_utf8(buffer) { match String::from_utf8(buffer) {
@ -25,13 +27,13 @@ pub trait PrettyPrint {
} }
} }
impl PrettyPrint for PrettyTree { impl<'a, T: Text<'a>> PrettyPrint<'a, T> for PrettyTree<'a, T> {
fn pretty(&self, _: &PrettyProvider) -> PrettyTree { fn pretty(&self, _: &PrettyProvider) -> PrettyTree<'a, T> {
self.clone() self.clone()
} }
} }
impl PrettyPrint for &'static str { impl<'a, T: Text<'a>> PrettyPrint<'a, T> for &'static str {
fn pretty(&self, _: &PrettyProvider) -> PrettyTree { fn pretty(&self, _: &PrettyProvider) -> PrettyTree<'a, T> {
PrettyTree::StaticText(*self) PrettyTree::Text(T::from_static_str(self))
} }
} }

View file

@ -1,18 +1,11 @@
use super::*; use super::*;
impl Default for PrettyTree { impl<'a, T: Clone> Clone for PrettyTree<'a, T> {
fn default() -> Self {
Self::Nil
}
}
impl Clone for PrettyTree {
fn clone(&self) -> Self { fn clone(&self) -> Self {
match self { match self {
Self::Nil => Self::Nil, Self::Nil => Self::Nil,
Self::Hardline => Self::Hardline, Self::Hardline => Self::Hardline,
Self::Text(s) => Self::Text(s.clone()), Self::Text(s) => Self::Text(s.clone()),
Self::StaticText(s) => Self::StaticText(*s),
Self::Annotated { style: color, body: doc } => Self::Annotated { style: color.clone(), body: doc.clone() }, Self::Annotated { style: color, body: doc } => Self::Annotated { style: color.clone(), body: doc.clone() },
Self::Append { lhs, rhs } => Self::Append { lhs: lhs.clone(), rhs: rhs.clone() }, Self::Append { lhs, rhs } => Self::Append { lhs: lhs.clone(), rhs: rhs.clone() },
Self::Group { items } => Self::Group { items: items.clone() }, Self::Group { items } => Self::Group { items: items.clone() },
@ -27,15 +20,18 @@ impl Clone for PrettyTree {
} }
} }
impl Debug for PrettyTree { impl<'a, T> Debug for PrettyTree<'a, T>
where
T: Text<'a> + Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let is_line = |doc: &PrettyTree| match doc { let is_line = |doc: &PrettyTree<'a, T>| match doc {
PrettyTree::MaybeInline { block: flat, inline: alt } => { PrettyTree::MaybeInline { block: flat, inline: alt } => {
matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::StaticText(" "))) matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Text(t)) if t.as_str() == " ")
} }
_ => false, _ => false,
}; };
let is_line_ = |doc: &PrettyTree| match doc { let is_line_ = |doc: &PrettyTree<'a, T>| match doc {
PrettyTree::MaybeInline { block: flat, inline: alt } => { PrettyTree::MaybeInline { block: flat, inline: alt } => {
matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Nil)) matches!((&**flat, &**alt), (PrettyTree::Hardline, PrettyTree::Nil))
} }
@ -66,7 +62,6 @@ impl Debug for PrettyTree {
PrettyTree::Hardline => f.debug_tuple("Hardline").finish(), PrettyTree::Hardline => f.debug_tuple("Hardline").finish(),
PrettyTree::RenderLength { body: doc, .. } => doc.fmt(f), PrettyTree::RenderLength { body: doc, .. } => doc.fmt(f),
PrettyTree::Text(s) => Debug::fmt(s, f), PrettyTree::Text(s) => Debug::fmt(s, f),
PrettyTree::StaticText(s) => Debug::fmt(s, f),
PrettyTree::Annotated { style: color, body: doc } => f.debug_tuple("Annotated").field(color).field(doc).finish(), PrettyTree::Annotated { style: color, body: doc } => f.debug_tuple("Annotated").field(color).field(doc).finish(),
PrettyTree::Union { lhs: left, rhs: right } => f.debug_tuple("Union").field(left).field(right).finish(), PrettyTree::Union { lhs: left, rhs: right } => f.debug_tuple("Union").field(left).field(right).finish(),
PrettyTree::Column { .. } => f.debug_tuple("Column(..)").finish(), PrettyTree::Column { .. } => f.debug_tuple("Column(..)").finish(),
@ -76,7 +71,7 @@ impl Debug for PrettyTree {
} }
} }
fn append_docs(mut doc: &PrettyTree, consumer: &mut impl FnMut(&PrettyTree)) { fn append_docs<'a, T>(mut doc: &PrettyTree<'a, T>, consumer: &mut impl FnMut(&PrettyTree<'a, T>)) {
loop { loop {
match doc { match doc {
PrettyTree::Append { lhs, rhs } => { PrettyTree::Append { lhs, rhs } => {

View file

@ -1,29 +1,29 @@
use super::*; use super::*;
impl<T> Add<T> for PrettyTree impl<'a, U, T: Text<'a>> Add<U> for PrettyTree<'a, T>
where where
T: Into<Self>, U: Into<Self>,
{ {
type Output = Self; type Output = Self;
fn add(self, rhs: T) -> Self::Output { fn add(self, rhs: U) -> Self::Output {
self.append(rhs.into()) self.append(rhs.into())
} }
} }
impl<T> AddAssign<T> for PrettyTree impl<'a, U, T: Text<'a>> AddAssign<U> for PrettyTree<'a, T>
where where
T: Into<Self>, U: Into<Self>,
{ {
fn add_assign(&mut self, rhs: T) { fn add_assign(&mut self, rhs: U) {
*self = self.clone().append(rhs.into()); *self = self.clone().append(rhs.into());
} }
} }
impl<T> From<Option<T>> for PrettyTree impl<'a, U, T> From<Option<U>> for PrettyTree<'a, T>
where where
Self: From<T>, Self: From<U>,
{ {
fn from(x: Option<T>) -> Self { fn from(x: Option<U>) -> Self {
match x { match x {
Some(x) => x.into(), Some(x) => x.into(),
None => Self::Nil, None => Self::Nil,
@ -31,20 +31,20 @@ where
} }
} }
impl From<()> for PrettyTree { impl<'a, T> From<()> for PrettyTree<'a, T> {
fn from(_: ()) -> Self { fn from(_: ()) -> Self {
Self::Nil Self::Nil
} }
} }
impl From<&'static str> for PrettyTree { impl<'a, T: Text<'a>> From<&'static str> for PrettyTree<'a, T> {
fn from(s: &'static str) -> Self { fn from(s: &'static str) -> Self {
Self::StaticText(s) Self::Text(T::from_static_str(s))
} }
} }
impl From<String> for PrettyTree { impl<'a, T: Text<'a>> From<T> for PrettyTree<'a, T> {
fn from(s: String) -> Self { fn from(s: T) -> Self {
Self::Text(Rc::from(s)) Self::Text(s)
} }
} }

View file

@ -1,12 +1,11 @@
use crate::{helpers::PrettySequence, render, FmtWrite, PrettyBuilder, RenderAnnotated}; use crate::{FmtWrite, PrettyBuilder, RenderAnnotated, Text, helpers::PrettySequence, render};
use alloc::{borrow::Cow, rc::Rc, string::String}; use alloc::rc::Rc;
use color_ansi::AnsiStyle; use color_ansi::AnsiStyle;
use core::{ use core::{
fmt::{Debug, Formatter}, fmt::{Debug, Formatter},
ops::{Add, AddAssign}, ops::{Add, AddAssign},
}; };
use std::io::Write; use std::{borrow::Cow, io::Write};
use unicode_segmentation::UnicodeSegmentation;
mod display; mod display;
mod into; mod into;
@ -16,15 +15,15 @@ mod into;
/// ///
/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how /// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
/// it is used /// it is used
pub enum PrettyTree { #[derive(Default)]
pub enum PrettyTree<'a, T = Cow<'a, str>> {
/// Nothing to show /// Nothing to show
#[default]
Nil, Nil,
/// A hard line break /// A hard line break
Hardline, Hardline,
/// A dynamic text document, all newlines are hard line breaks /// A dynamic text document, all newlines are hard line breaks
Text(Rc<str>), Text(T),
/// A static text document, all newlines are hard line breaks
StaticText(&'static str),
/// A document with ansi styles /// A document with ansi styles
Annotated { Annotated {
/// The style to use for the text /// The style to use for the text
@ -75,51 +74,45 @@ pub enum PrettyTree {
/// Concatenates two documents with a line in between /// Concatenates two documents with a line in between
Column { Column {
/// The first document /// The first document
invoke: Rc<dyn Fn(usize) -> Self>, invoke: Rc<dyn Fn(usize) -> Self + 'a>,
}, },
/// Concatenates two documents with a line in between /// Concatenates two documents with a line in between
Nesting { Nesting {
/// The first document /// The first document
invoke: Rc<dyn Fn(usize) -> Self>, invoke: Rc<dyn Fn(usize) -> Self + 'a>,
}, },
/// Concatenates two documents with a line in between /// Concatenates two documents with a line in between
Fail, Fail,
} }
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
impl PrettyTree { impl<'a, T: Text<'a>> PrettyTree<'a, T> {
/// A hard line break
pub const Space: Self = PrettyTree::StaticText(" ");
/// A line acts like a `\n` but behaves like `space` if it is grouped on a single line. /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line.
#[inline] #[inline]
pub fn line_or_space() -> Self { pub fn line_or_space() -> Self {
Self::Hardline.flat_alt(Self::Space).into() Self::Hardline.flat_alt(T::space())
} }
/// A line acts like `\n` but behaves like `nil` if it is grouped on a single line. /// A line acts like `\n` but behaves like `nil` if it is grouped on a single line.
#[inline] #[inline]
pub fn line_or_comma() -> Self { pub fn line_or_comma() -> Self {
Self::Hardline.flat_alt(PrettyTree::StaticText(", ")).into() Self::Hardline.flat_alt(PrettyTree::Text(T::comma_space()))
} }
/// Acts like `line` but behaves like `nil` if grouped on a single line /// Acts like `line` but behaves like `nil` if grouped on a single line
#[inline] #[inline]
pub fn line_or_nil() -> Self { pub fn line_or_nil() -> Self {
Self::Hardline.flat_alt(Self::Nil).into() Self::Hardline.flat_alt(Self::Nil)
} }
} }
impl PrettyTree { impl<'a, T> PrettyTree<'a, T> {
/// The given text, which must not contain line breaks. /// The given text, which must not contain line breaks.
#[inline] #[inline]
pub fn text<U: Into<Cow<'static, str>>>(data: U) -> Self { pub fn text(data: impl Into<T>) -> Self {
match data.into() { Self::Text(data.into())
Cow::Borrowed(s) => PrettyTree::StaticText(s),
Cow::Owned(s) => PrettyTree::Text(Rc::from(s)),
}
.with_utf8_len()
} }
} }
impl PrettyTree { impl<'a, T: Text<'a>> PrettyTree<'a, T> {
/// Writes a rendered document to a `std::io::Write` object. /// Writes a rendered document to a `std::io::Write` object.
#[inline] #[inline]
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -130,6 +123,13 @@ impl PrettyTree {
self.render_raw(width, &mut crate::IoWrite::new(out)) self.render_raw(width, &mut crate::IoWrite::new(out))
} }
/// Writes a rendered document to a `std::io::Write` object.
#[inline]
#[cfg(feature = "std")]
pub fn render_vec(&self, width: usize, out: &mut Vec<T>) -> Result<(), &'static str> {
self.render_raw(width, &mut crate::VecWrite::new(out))
}
/// Writes a rendered document to a `std::fmt::Write` object. /// Writes a rendered document to a `std::fmt::Write` object.
#[inline] #[inline]
pub fn render_fmt<W>(&self, width: usize, out: &mut W) -> core::fmt::Result pub fn render_fmt<W>(&self, width: usize, out: &mut W) -> core::fmt::Result
@ -143,14 +143,14 @@ impl PrettyTree {
#[inline] #[inline]
pub fn render_raw<W>(&self, width: usize, out: &mut W) -> Result<(), W::Error> pub fn render_raw<W>(&self, width: usize, out: &mut W) -> Result<(), W::Error>
where where
W: RenderAnnotated, W: RenderAnnotated<'a, T>,
W: ?Sized, W: ?Sized,
{ {
render::best(Rc::new(self.clone()), width, out) render::best(Rc::new(self.clone()), width, out)
} }
} }
impl PrettyTree { impl<'a, T: Text<'a>> PrettyTree<'a, T> {
/// The given text, which must not contain line breaks. /// The given text, which must not contain line breaks.
#[inline] #[inline]
#[cfg(feature = "std")] #[cfg(feature = "std")]
@ -159,7 +159,7 @@ impl PrettyTree {
} }
} }
impl PrettyBuilder for PrettyTree { impl<'a, T: Text<'a>> PrettyBuilder<'a, T> for PrettyTree<'a, T> {
/// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line. /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
/// ///
/// ``` /// ```
@ -181,7 +181,7 @@ impl PrettyBuilder for PrettyTree {
#[inline] #[inline]
fn flat_alt<E>(self, flat: E) -> Self fn flat_alt<E>(self, flat: E) -> Self
where where
E: Into<PrettyTree>, E: Into<PrettyTree<'a, T>>,
{ {
Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) } Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) }
} }
@ -217,7 +217,7 @@ impl PrettyBuilder for PrettyTree {
while remaining != 0 { while remaining != 0 {
let i = SPACES.len().min(remaining); let i = SPACES.len().min(remaining);
remaining -= i; remaining -= i;
doc = doc.append(PrettyTree::text(&SPACES[..i])) doc = doc.append(PrettyTree::text(T::from_static_spaces(&SPACES[..i])))
} }
doc doc
}; };
@ -237,28 +237,12 @@ impl PrettyBuilder for PrettyTree {
} }
} }
impl PrettyTree { impl<'a, T: Text<'a>> PrettyTree<'a, T> {
fn with_utf8_len(self) -> Self {
let s = match &self {
Self::Text(s) => s.as_ref(),
Self::StaticText(s) => s,
// Doc::SmallText(s) => s,
_ => return self,
};
if s.is_ascii() {
self
}
else {
let grapheme_len = s.graphemes(true).count();
Self::RenderLength { length: grapheme_len, body: Rc::new(self) }
}
}
/// Append the given document after this document. /// Append the given document after this document.
#[inline] #[inline]
pub fn append<E>(self, follow: E) -> Self pub fn append<E>(self, follow: E) -> Self
where where
E: Into<PrettyTree>, E: Into<PrettyTree<'a, T>>,
{ {
let rhs = follow.into(); let rhs = follow.into();
match (&self, &rhs) { match (&self, &rhs) {
@ -275,11 +259,11 @@ impl PrettyTree {
/// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr
/// like `RefDoc` or `RcDoc` /// like `RefDoc` or `RcDoc`
#[inline] #[inline]
pub fn join<I, T1, T2>(terms: I, joint: T2) -> PrettyTree pub fn join<I, T1, T2>(terms: I, joint: T2) -> PrettyTree<'a, T>
where where
I: IntoIterator<Item = T1>, I: IntoIterator<Item = T1>,
T1: Into<PrettyTree>, T1: Into<PrettyTree<'a, T>>,
T2: Into<PrettyTree>, T2: Into<PrettyTree<'a, T>>,
{ {
let joint = joint.into(); let joint = joint.into();
let mut iter = terms.into_iter().map(|s| s.into()); let mut iter = terms.into_iter().map(|s| s.into());
@ -295,7 +279,7 @@ impl PrettyTree {
pub fn concat<I>(docs: I) -> Self pub fn concat<I>(docs: I) -> Self
where where
I: IntoIterator, I: IntoIterator,
I::Item: Into<PrettyTree>, I::Item: Into<PrettyTree<'a, T>>,
{ {
let mut head = Self::Nil; let mut head = Self::Nil;
for item in docs.into_iter() { for item in docs.into_iter() {
@ -313,7 +297,7 @@ impl PrettyTree {
#[inline] #[inline]
pub fn group(self) -> Self { pub fn group(self) -> Self {
match self { match self {
Self::Group { .. } | Self::Text(_) | Self::StaticText(_) | Self::Nil => self, Self::Group { .. } | Self::Text(_) | Self::Nil => self,
_ => Self::Group { items: Rc::new(self) }, _ => Self::Group { items: Rc::new(self) },
} }
} }
@ -321,13 +305,13 @@ impl PrettyTree {
/// Mark this document as a comment. /// Mark this document as a comment.
#[inline] #[inline]
pub fn annotate(self, style: Rc<AnsiStyle>) -> Self { pub fn annotate(self, style: Rc<AnsiStyle>) -> Self {
Self::Annotated { style: style, body: Rc::new(self) } Self::Annotated { style, body: Rc::new(self) }
} }
/// Mark this document as a hard line break. /// Mark this document as a hard line break.
#[inline] #[inline]
pub fn union<E>(self, other: E) -> Self pub fn union<E>(self, other: E) -> Self
where where
E: Into<PrettyTree>, E: Into<PrettyTree<'a, T>>,
{ {
Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) } Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) }
} }
@ -402,6 +386,7 @@ impl PrettyTree {
pub fn width<F>(self, f: F) -> Self pub fn width<F>(self, f: F) -> Self
where where
F: Fn(isize) -> Self + Clone + 'static, F: Fn(isize) -> Self + Clone + 'static,
T: Clone,
{ {
Self::Column { Self::Column {
invoke: Rc::new(move |start| { invoke: Rc::new(move |start| {

View file

@ -1,5 +1,6 @@
use pretty_print::*; #![allow(unused)]
use SExp::*; use SExp::*;
use pretty_print::*;
enum SExp { enum SExp {
Atom(u32), Atom(u32),
List(Vec<SExp>), List(Vec<SExp>),
@ -7,11 +8,11 @@ enum SExp {
impl SExp { impl SExp {
/// Return a pretty printed format of self. /// Return a pretty printed format of self.
pub fn to_doc(&self) -> PrettyTree { pub fn to_doc<'a>(&self) -> PrettyTree<'a> {
match self { match self {
Atom(x) => PrettyTree::text(x.to_string()), Atom(x) => PrettyTree::text(x.to_string()),
List(xs) => PrettyTree::text("(") List(xs) => PrettyTree::text("(")
.append(PrettyTree::join(xs.into_iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group()) .append(PrettyTree::join(xs.iter().map(|x| x.to_doc()), PrettyTree::line_or_space()).nest(1).group())
.append(PrettyTree::text(")")), .append(PrettyTree::text(")")),
} }
} }

View file

@ -6,7 +6,7 @@ authors = ["Aster <192607617@qq.com>"]
description = "..." description = "..."
repository = "https://github.com/oovm/sub_projects" repository = "https://github.com/oovm/sub_projects"
documentation = "https://docs.rs/sub_projects" documentation = "https://docs.rs/sub_projects"
readme = "Readme.md" readme = "../../Readme.md"
license = "MPL-2.0" license = "MPL-2.0"
edition = "2021" edition = "2021"
exclude = ["package.json", "tests/**"] exclude = ["package.json", "tests/**"]
@ -14,11 +14,12 @@ exclude = ["package.json", "tests/**"]
[dependencies] [dependencies]
pretty = "0.12.1" pretty = "0.12.1"
[dependencies.pretty-print] [dependencies.logparse-pretty-print]
version = "*" version = "*"
default-features = false default-features = false
features = ["std"] features = ["std"]
path = "../pretty-print" path = "../pretty-print"
name = "pretty-print"
[dev-dependencies] [dev-dependencies]

View file

@ -1,6 +1,8 @@
#![allow(dead_code, unused)] #![allow(dead_code, unused)]
use pretty_print::{AnsiColor, AnsiStyle, PrettyBuilder, PrettyTree}; use pretty_print::{AnsiColor, AnsiStyle, PrettyBuilder, PrettyTree as RawPrettyTree};
use std::{io::stdout, rc::Rc}; use std::{borrow::Cow, io::stdout, rc::Rc};
type PrettyTree<'a> = RawPrettyTree<'a, Cow<'a, str>>;
#[test] #[test]
fn ready() { fn ready() {

View file

@ -1,3 +1,5 @@
#[cfg(target_os = "linux")]
use arboard::SetExtLinux;
use crossterm::{ use crossterm::{
event::{ event::{
DisableMouseCapture, EnableMouseCapture, KeyEventKind, KeyEventState, MouseButton, DisableMouseCapture, EnableMouseCapture, KeyEventKind, KeyEventState, MouseButton,
@ -8,6 +10,8 @@ use crossterm::{
use itertools::Itertools; use itertools::Itertools;
use logparse::{self as lp, Config, into_spans, parse_input}; use logparse::{self as lp, Config, into_spans, parse_input};
use ratatui_themes::{Color, Theme, ThemeName}; use ratatui_themes::{Color, Theme, ThemeName};
#[cfg(target_os = "linux")]
use std::time::Instant;
use std::{ use std::{
borrow::Cow, borrow::Cow,
fs::{self, DirEntry}, fs::{self, DirEntry},
@ -40,7 +44,7 @@ use ratatui::{
prelude::CrosstermBackend, prelude::CrosstermBackend,
style::Style, style::Style,
symbols::merge::MergeStrategy, symbols::merge::MergeStrategy,
text::{Line, Span}, text::{Line, Span, Text},
widgets::{ widgets::{
Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap, Block, Clear, List, ListItem, ListState, Padding, Paragraph, StatefulWidget, Widget, Wrap,
}, },
@ -121,6 +125,11 @@ pub fn run(logs_dir: PathBuf, compiler_root: Option<PathBuf>, theme: ThemeName)
} }
} }
enum ZoomTarget {
Field { span: SpanDescriptor, name: String },
Log,
}
enum Tab { enum Tab {
FileChooser { FileChooser {
files: Vec<DirEntry>, files: Vec<DirEntry>,
@ -132,8 +141,7 @@ enum Tab {
Empty, Empty,
Help, Help,
Zoom { Zoom {
span: SpanDescriptor, target: ZoomTarget,
name: String,
value: String, value: String,
}, },
} }
@ -146,10 +154,20 @@ impl Tab {
(Tab::LogViewer(_), Some(path)) => format!("logs of {}", path.display()), (Tab::LogViewer(_), Some(path)) => format!("logs of {}", path.display()),
(Tab::LogViewer(_), None) => "logs".to_string(), (Tab::LogViewer(_), None) => "logs".to_string(),
(Tab::Help, _) => "help".to_string(), (Tab::Help, _) => "help".to_string(),
(Tab::Zoom { span, name, .. }, _) => match span { (Tab::Zoom { target, .. }, _) => match target {
SpanDescriptor::Main => format!("{name}"), ZoomTarget::Log => "value of log".to_string(),
SpanDescriptor::Span(s) => format!("{name} in {s}"), ZoomTarget::Field {
SpanDescriptor::Numbered(n) => format!("{name} in span #{n}"), span: SpanDescriptor::Main,
name,
} => format!("{name}"),
ZoomTarget::Field {
span: SpanDescriptor::Span(s),
name,
} => format!("{name} in {s}"),
ZoomTarget::Field {
span: SpanDescriptor::Numbered(n),
name,
} => format!("{name} in span #{n}"),
}, },
} }
} }
@ -256,9 +274,9 @@ impl App {
fn handle_current_tab_keycode(&mut self, key: KeyEvent) { fn handle_current_tab_keycode(&mut self, key: KeyEvent) {
match self.tabs.last_mut().unwrap() { match self.tabs.last_mut().unwrap() {
Tab::Help => {} Tab::Help => self.pop_tab(),
Tab::Empty => {} Tab::Empty => {}
Tab::Zoom { .. } => {} Tab::Zoom { .. } => self.pop_tab(),
Tab::FileChooser { Tab::FileChooser {
files, files,
state, state,
@ -344,7 +362,36 @@ impl App {
KeyCode::Char('r') => lv.redo(), KeyCode::Char('r') => lv.redo(),
KeyCode::Char('z') => { KeyCode::Char('z') => {
if let Some((span, name, value)) = lv.get_selected_field() { if let Some((span, name, value)) = lv.get_selected_field() {
self.push_tab(Tab::Zoom { span, name, value }); if let InputState::Target(InputTarget::Fields(..)) = lv.input_state {
self.push_tab(Tab::Zoom {
target: ZoomTarget::Field { span, name },
value,
});
} else if let Some((l, _)) = lv.selected() {
let message = l.line_text("".to_string(), &lv.filters).message;
self.push_tab(Tab::Zoom {
target: ZoomTarget::Log,
value: message,
});
}
}
}
KeyCode::Char('y') => {
if let Some((_, _, value)) = lv.get_selected_field() {
match arboard::Clipboard::new() {
Ok(mut cb) => {
#[allow(unused_mut)]
let mut set = cb.set();
#[cfg(target_os = "linux")]
let set = set.wait_until(
Instant::now()
.checked_add(Duration::from_millis(200))
.unwrap(),
);
set.text(value).unwrap();
}
Err(e) => panic!("{e}"),
}
} }
} }
@ -605,7 +652,7 @@ impl Widget for &mut App {
.areas(area); .areas(area);
let [_, popup_area, _] = Layout::horizontal([ let [_, popup_area, _] = Layout::horizontal([
Constraint::Fill(1), Constraint::Fill(1),
Constraint::Min(40), Constraint::Min(80),
Constraint::Fill(1), Constraint::Fill(1),
]) ])
.areas(popup_area); .areas(popup_area);
@ -750,25 +797,44 @@ impl Widget for &mut App {
inner inner
}; };
if let Ok(parsed) = parse_input(&value) { if let Ok(parsed) = parse_input(value) {
let spans = into_spans( let spans = into_spans(
parsed, parsed,
Config { Config {
collapse_space: true, collapse_space: true,
pretty_print: Some(popup_area.width as usize),
}, },
); );
let spans = spans let spans = spans
.into_iter() .into_iter()
.map(|lp::Span { text, kind }| { .map(|lp::Span { text, kind }| {
let span = Span::from(text.into_owned()); let lines = text.split("\n").map(ToString::to_string).collect_vec();
let styles = &styles;
lines.into_iter().map(move |text| {
let span = Span::from(text.to_string());
let style = style_span(kind, styles.default, &styles); let style = style_span(kind, styles.default, styles);
span.style(style) span.style(style)
})
}) })
.collect_vec(); .collect_vec();
Paragraph::new(Line::from(spans)).render(popup_area, buf); let mut lines = Vec::new();
let mut line = Vec::new();
for mut spans in spans {
if let Some(i) = spans.next() {
line.push(i);
}
for i in spans {
lines.push(Line::from(line));
line = vec![i];
}
}
lines.push(Line::from(line));
Text::from(lines).render(popup_area, buf);
} else { } else {
Paragraph::new(value.clone()).render(popup_area, buf); Paragraph::new(value.clone()).render(popup_area, buf);
} }

View file

@ -15,7 +15,7 @@ use tui_widget_list::{ListBuilder, ListState, ListView};
use crate::tui::{ use crate::tui::{
block_around, block_around,
log_viewer::LogViewer, log_viewer::LogViewer,
model::{SpanDescriptor, LogEntry, LogFields}, model::{LogEntry, LogFields, SpanDescriptor},
widgets::{line_text::style_span, styled::Styled}, widgets::{line_text::style_span, styled::Styled},
}; };
@ -91,7 +91,10 @@ impl<'a> FieldTree<'a> {
self.lv.selected().map(|(s, _)| s) self.lv.selected().map(|(s, _)| s)
} }
fn current_span_fields(&mut self, spans: &[(SpanDescriptor, &LogFields)]) -> Vec<(String, String)> { fn current_span_fields(
&mut self,
spans: &[(SpanDescriptor, &LogFields)],
) -> Vec<(String, String)> {
let mut selected = self.state().current_span.selected.unwrap_or(0); let mut selected = self.state().current_span.selected.unwrap_or(0);
if selected > spans.len() { if selected > spans.len() {
selected = spans.len().saturating_sub(1); selected = spans.len().saturating_sub(1);
@ -120,7 +123,12 @@ impl<'a> FieldTree<'a> {
} }
impl Styled<'_, &mut FieldTree<'_>> { impl Styled<'_, &mut FieldTree<'_>> {
fn areas(&self, area: Rect, buf: &mut Buffer, name: Option<&SpanDescriptor>) -> (Rect, Rect, Rect) { fn areas(
&self,
area: Rect,
buf: &mut Buffer,
name: Option<&SpanDescriptor>,
) -> (Rect, Rect, Rect) {
let [spans_area, fields_area] = Layout::horizontal([ let [spans_area, fields_area] = Layout::horizontal([
Constraint::Length(if self.spans_focussed() || !self.focussed { Constraint::Length(if self.spans_focussed() || !self.focussed {
25 25
@ -220,6 +228,7 @@ impl Widget for Styled<'_, &mut FieldTree<'_>> {
i, i,
Config { Config {
collapse_space: true, collapse_space: true,
..Default::default()
}, },
) )
}) })

View file

@ -9,7 +9,7 @@ use crate::tui::{
filters::Filters, filters::Filters,
input::{FieldMatcher, InputState, InputTarget}, input::{FieldMatcher, InputState, InputTarget},
}, },
model::{SpanDescriptor, LogEntry}, model::{LogEntry, SpanDescriptor},
widgets::{ widgets::{
last_error::LastError, last_error::LastError,
line_text::Highlighted, line_text::Highlighted,

View file

@ -189,6 +189,7 @@ impl Into<Line<'static>> for Styled<'_, LineText> {
parsed, parsed,
Config { Config {
collapse_space: true, collapse_space: true,
..Default::default()
}, },
); );