diff --git a/Cargo.lock b/Cargo.lock index e678e18..b7c7b25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -47,7 +47,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -58,14 +58,14 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "cfg-if" @@ -102,9 +102,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "convert_case" @@ -147,9 +147,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_more" @@ -170,7 +167,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", ] [[package]] @@ -191,7 +188,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -210,7 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -226,11 +223,11 @@ dependencies = [ [[package]] name = "is_executable" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4" +checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" dependencies = [ - "windows-sys 0.60.2", + "windows-sys", ] [[package]] @@ -241,21 +238,21 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "libc", ] @@ -283,9 +280,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "logria" @@ -303,27 +300,27 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "once_cell_polyfill" @@ -368,18 +365,18 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -406,9 +403,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -418,9 +415,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -429,9 +426,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rustc_version" @@ -452,7 +449,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -463,15 +460,15 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -479,29 +476,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -543,9 +540,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "strsim" @@ -555,9 +552,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -566,32 +574,31 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -601,15 +608,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -623,9 +630,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "utf8parse" @@ -667,15 +674,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -685,73 +683,8 @@ dependencies = [ "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]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 0352710..b38427c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,11 +15,11 @@ version = "0.0.0" clap = { version = "=4.5.60", features = ["cargo"] } crossterm = "=0.29.0" dirs = "=6.0.0" -is_executable = "=1.0.5" -regex = "=1.12.3" -serde = { version = "=1.0.228", features = ["derive"] } -serde_json = "=1.0.149" -time = { version = "=0.3.47", features = ["formatting", "parsing"] } +is_executable = "=1.0.6" +regex = "=1.13.1" +serde = { version = "=1.0.229", features = ["derive"] } +serde_json = "=1.0.151" +time = { version = "=0.3.55", features = ["formatting", "parsing"] } [profile.release] # Perform Link Time Optimization diff --git a/src/communication/handlers/command.rs b/src/communication/handlers/command.rs index 0e501fc..e617e4b 100644 --- a/src/communication/handlers/command.rs +++ b/src/communication/handlers/command.rs @@ -64,7 +64,10 @@ impl CommandHandler { } // Remove "r " from the string - let parts = command[2..].split(','); + let Some(rest) = command.get(2..) else { + return Err(LogriaError::InvalidCommand(format!("{command:?}"))); + }; + let parts = rest.split(','); let mut out_l: Vec = vec![]; // Not for_each because we may need to bail early @@ -85,7 +88,7 @@ impl CommandHandler { (_, _) => { return Err(LogriaError::InvalidCommand(format!( "range invalid: {:?}", - &range + range ))); } } diff --git a/src/communication/handlers/parser.rs b/src/communication/handlers/parser.rs index a854475..be8752a 100644 --- a/src/communication/handlers/parser.rs +++ b/src/communication/handlers/parser.rs @@ -224,51 +224,51 @@ impl ProcessorMethods for ParserHandler { /// Parse messages, loading the buffer of parsed messages in the main window fn process_matches(&mut self, window: &mut MainWindow) -> Result<()> { // Only process if the parser is set up properly - if let ParserState::Full = window.config.parser_state { - if self.parser.is_some() { - let mut wrote_progress = false; - // Start from where we left off to the most recent message - let start = window.config.last_index_processed; - let end = window.previous_messages().len(); - - let last = end.checked_sub(1).unwrap_or(end); - for index in start..end { - if window.config.aggregation_enabled { - match self.aggregate_handle( - &window.previous_messages()[index], - &window.config.num_to_aggregate, - index == last, - ) { - Ok(aggregated_messages) => { - if !aggregated_messages.is_empty() { - window.config.auxiliary_messages.clear(); - window.config.auxiliary_messages.extend(aggregated_messages); - } + if let ParserState::Full = window.config.parser_state + && self.parser.is_some() + { + let mut wrote_progress = false; + // Start from where we left off to the most recent message + let start = window.config.last_index_processed; + let end = window.previous_messages().len(); + + let last = end.checked_sub(1).unwrap_or(end); + for index in start..end { + if window.config.aggregation_enabled { + match self.aggregate_handle( + &window.previous_messages()[index], + &window.config.num_to_aggregate, + index == last, + ) { + Ok(aggregated_messages) => { + if !aggregated_messages.is_empty() { + window.config.auxiliary_messages.clear(); + window.config.auxiliary_messages.extend(aggregated_messages); } - Err(why) => { - // If the message failed parsing, it might just be a different format, so we ignore it - // If the parser is in an invalid state, alert the user - if let LogriaError::InvalidParserState(error) = why { - window.write_to_command_line(&error)?; - } + } + Err(why) => { + // If the message failed parsing, it might just be a different format, so we ignore it + // If the parser is in an invalid state, alert the user + if let LogriaError::InvalidParserState(error) = why { + window.write_to_command_line(&error)?; } } - } else if let Ok(Some(message)) = self.parse( - window.config.parser_index, - &window.previous_messages()[index], - ) { - window.config.auxiliary_messages.push(message); } + } else if let Ok(Some(message)) = self.parse( + window.config.parser_index, + &window.previous_messages()[index], + ) { + window.config.auxiliary_messages.push(message); + } - // Update the user interface with the current state - wrote_progress = update_progress(window, start, end, index)?; + // Update the user interface with the current state + wrote_progress = update_progress(window, start, end, index)?; - // Update the last spot so we know where to start next time - window.config.last_index_processed = index + 1; - } - if wrote_progress { - window.write_status()?; - } + // Update the last spot so we know where to start next time + window.config.last_index_processed = index + 1; + } + if wrote_progress { + window.write_status()?; } } Ok(()) diff --git a/src/communication/handlers/processor.rs b/src/communication/handlers/processor.rs index f638ac6..ac8ef37 100644 --- a/src/communication/handlers/processor.rs +++ b/src/communication/handlers/processor.rs @@ -21,7 +21,7 @@ pub fn update_progress( index: usize, ) -> Result { // Update the user interface with the current state - if end - start > THRESHOLD && (index % STEP == 0 || index == end - 1) { + if end - start > THRESHOLD && (index.is_multiple_of(STEP) || index == end - 1) { let word = if index == end - 1 { "Processed" } else { diff --git a/src/communication/handlers/user_input.rs b/src/communication/handlers/user_input.rs index b72ad13..d245c31 100644 --- a/src/communication/handlers/user_input.rs +++ b/src/communication/handlers/user_input.rs @@ -84,11 +84,16 @@ impl UserInputHandler { (self.last_write - 1) as usize } + /// Index of the char at the cursor, `None` when the cursor sits past the end of the content + fn index_at_cursor(&self) -> Option { + let index = self.position_as_index(); + (index < self.content.len()).then_some(index) + } + /// Remove char 1 to the left of the cursor fn backspace(&mut self, window: &mut MainWindow) -> Result<()> { - if self.last_write >= 1 && !self.content.is_empty() { - self.content - .remove(self.position_as_index().saturating_sub(1)); + if self.last_write > 1 && !self.content.is_empty() { + self.content.remove(self.position_as_index() - 1); self.move_left()?; self.write(window)?; } @@ -97,8 +102,8 @@ impl UserInputHandler { /// Remove char 1 to the right of the cursor fn delete(&mut self, window: &mut MainWindow) -> Result<()> { - if self.last_write < self.x() && !self.content.is_empty() { - self.content.remove(self.position_as_index()); + if let Some(index) = self.index_at_cursor() { + self.content.remove(index); self.write(window)?; } Ok(()) @@ -121,21 +126,23 @@ impl UserInputHandler { /// Get the next item in the history tape if it exists fn tape_forward(&mut self, window: &mut MainWindow) -> Result<()> { - let content = self.history.scroll_forward(); - self.tape_render(window, &content)?; + if let Some(content) = self.history.scroll_forward() { + self.tape_render(window, &content)?; + } Ok(()) } /// Get the previous item in the history tape if it exists fn tape_back(&mut self, window: &mut MainWindow) -> Result<()> { - let content = self.history.scroll_back(); - self.tape_render(window, &content)?; + if let Some(content) = self.history.scroll_back() { + self.tape_render(window, &content)?; + } Ok(()) } /// Render the new choice fn tape_render(&mut self, window: &mut MainWindow, content: &str) -> Result<()> { - self.last_write = content.len() as u16 + 1; + self.last_write = content.chars().count() as u16 + 1; window.write_to_command_line(content)?; self.content = content.chars().collect(); queue!( @@ -206,3 +213,22 @@ impl Handler for UserInputHandler { Ok(()) } } + +#[cfg(test)] +mod tests { + use crate::communication::handlers::{handler::Handler, user_input::UserInputHandler}; + + #[test] + fn cursor_index_bounded_by_content() { + let mut handler = UserInputHandler::new(); + + // "abc" with the cursor past the end: the state that crashed forward-delete + handler.content = vec!['a', 'b', 'c']; + handler.last_write = 4; + assert_eq!(handler.index_at_cursor(), None); + + // Cursor between 'a' and 'b': forward-delete removes 'b' + handler.last_write = 2; + assert_eq!(handler.index_at_cursor(), Some(1)); + } +} diff --git a/src/communication/input.rs b/src/communication/input.rs index b7b9fc7..577e213 100644 --- a/src/communication/input.rs +++ b/src/communication/input.rs @@ -246,9 +246,9 @@ pub fn build_streams_from_input( match determine_stream_type(command) { SessionType::Command => { // None indicates default poll rate - match CommandInput::build(command.to_owned(), command.to_owned()) { - Ok(stream) => streams.push(stream), - Err(why) => return Err(why), + { + let stream = CommandInput::build(command.to_owned(), command.to_owned())?; + streams.push(stream) } stream_types.insert(SessionType::Command); } @@ -256,9 +256,9 @@ pub fn build_streams_from_input( // None indicates default poll rate let path = Path::new(command); let name = path.file_name().unwrap().to_str().unwrap().to_string(); - match FileInput::build(name, command.to_owned()) { - Ok(stream) => streams.push(stream), - Err(why) => return Err(why), + { + let stream = FileInput::build(name, command.to_owned())?; + streams.push(stream) } stream_types.insert(SessionType::File); } @@ -292,9 +292,9 @@ pub fn build_streams_from_session(session: Session) -> Result, SessionType::Command => { let mut streams: Vec = vec![]; for command in session.commands { - match CommandInput::build(command.clone(), command.clone()) { - Ok(stream) => streams.push(stream), - Err(why) => return Err(why), + { + let stream = CommandInput::build(command.clone(), command.clone())?; + streams.push(stream) } } Ok(streams) @@ -302,9 +302,9 @@ pub fn build_streams_from_session(session: Session) -> Result, SessionType::File => { let mut streams: Vec = vec![]; for command in session.commands { - match FileInput::build(command.clone(), command.clone()) { - Ok(stream) => streams.push(stream), - Err(why) => return Err(why), + { + let stream = FileInput::build(command.clone(), command.clone())?; + streams.push(stream) } } Ok(streams) diff --git a/src/communication/reader.rs b/src/communication/reader.rs index 1353df0..9b2beea 100644 --- a/src/communication/reader.rs +++ b/src/communication/reader.rs @@ -272,7 +272,7 @@ impl MainWindow { /// Determine the start and end indexes we need to render in the window pub fn determine_render_position(&mut self) -> (usize, usize) { - let mut end: usize = 0; + let end: usize; let mut rows: usize = 0; let message_pointer_length = self.number_of_messages(); @@ -341,10 +341,9 @@ impl MainWindow { // If we have over-scrolled, go back if self.config.current_end > message_pointer_length { self.config.current_end = message_pointer_length; - } else { - // Since current_end can be zero, we have to use the number of messages - end = message_pointer_length; } + // Since current_end can be zero, we have to use the number of messages + end = message_pointer_length; } } ScrollState::Centered => { @@ -445,7 +444,8 @@ impl MainWindow { let clean_message = ANSI_COLOR_REGEX.replace_all(message.as_bytes(), "".as_bytes()); // Store some vectors of char bytes so we don't have to cast to a string every loop - let mut new_msg: Vec = vec![]; + // Pre-allocate with room for the message plus color escape codes + let mut new_msg: Vec = Vec::with_capacity(clean_message.len() + 64); let mut last_end = 0; // Replace matched patterns with highlighted matched patterns @@ -477,7 +477,10 @@ impl MainWindow { let clean_message = ANSI_COLOR_REGEX.replace_all(message.as_bytes(), "".as_bytes()); // Store some vectors of char bytes so we don't have to cast to a string every loop - let mut new_msg: Vec = vec![]; + // Pre-allocate with room for the message plus color escape codes + let mut new_msg: Vec = Vec::with_capacity( + clean_message.len() + colors::HIGHLIGHT_COLOR.len() + colors::RESET_COLOR.len(), + ); new_msg.extend_from_slice(colors::HIGHLIGHT_COLOR.as_bytes()); new_msg.extend_from_slice(&clean_message); new_msg.extend_from_slice(colors::RESET_COLOR.as_bytes()); @@ -548,6 +551,9 @@ impl MainWindow { // Cast to usize so we can reference this instead of casting every time we need let width = self.config.width as usize; + // Reusable padding buffer: grows as needed, sliced to exact size each iteration + let mut padding_buf = String::new(); + // Render each message from bottom to top for index in (start..end).rev() { // Get the next message from the message pointer @@ -568,7 +574,12 @@ impl MainWindow { // See method docs for note on why we need this padding let message_padding_size = (width * message_rows) - message_length; - let padding = " ".repeat(message_padding_size); + if padding_buf.len() < message_padding_size { + padding_buf.extend(std::iter::repeat_n( + ' ', + message_padding_size - padding_buf.len(), + )); + } let msg: Cow = if self.config.highlight_match && self.config.regex_pattern.is_some() { @@ -589,7 +600,7 @@ impl MainWindow { stdout, cursor::MoveTo(0, current_row), style::Print(&msg), - style::Print(padding) + style::Print(&padding_buf[..message_padding_size]) )?; } @@ -791,6 +802,11 @@ impl MainWindow { pub fn start(&mut self, commands: Option>) -> Result<()> { self.validate_environment(); + let previous_hook = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + MainWindow::restore_terminal(); + previous_hook(info); + })); // Build the app if let Some(c) = commands { // Build streams from the command used to launch Logria @@ -827,10 +843,15 @@ impl MainWindow { Ok(()) } + /// Restore terminal state + pub fn restore_terminal() { + let _ = execute!(stdout(), cursor::Show, Clear(ClearType::All)); + let _ = disable_raw_mode(); + } + /// Immediately exit the program pub fn quit(&mut self) -> Result<()> { - execute!(stdout(), cursor::Show, Clear(ClearType::All))?; - disable_raw_mode()?; + Self::restore_terminal(); for stream in &self.config.streams { stream.should_die.store(true, Ordering::Relaxed); } diff --git a/src/constants/cli.rs b/src/constants/cli.rs index 9b442dc..5197594 100644 --- a/src/constants/cli.rs +++ b/src/constants/cli.rs @@ -13,7 +13,7 @@ pub mod patterns { use regex::bytes::Regex; - pub const ANSI_COLOR_PATTERN: &str = r"(?-u)(\x9b|\x1b\[)[0-?]*[ -/]*[@-~]"; + pub const ANSI_COLOR_PATTERN: &str = r"(?-u)\x1b\[[0-?]*[ -/]*[@-~]"; pub static ANSI_COLOR_REGEX: LazyLock = LazyLock::new(|| Regex::new(ANSI_COLOR_PATTERN).unwrap()); diff --git a/src/main.rs b/src/main.rs index a2d3420..c903ced 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,10 @@ fn main() -> Result<()> { // Start app let mut app = MainWindow::new(history, smart_poll_rate); - app.start(exec)?; + if let Err(why) = app.start(exec) { + MainWindow::restore_terminal(); + return Err(why); + } } Ok(()) } diff --git a/src/util/aggregators/aggregator.rs b/src/util/aggregators/aggregator.rs index 4a3a40c..906a92f 100644 --- a/src/util/aggregators/aggregator.rs +++ b/src/util/aggregators/aggregator.rs @@ -44,7 +44,7 @@ pub fn format_float(n: f64) -> String { let len = chars.len(); for (i, c) in chars.iter().enumerate() { - if i != 0 && (len - i) % 3 == 0 { + if i != 0 && (len - i).is_multiple_of(3) { result.push(','); } result.push(*c); @@ -66,7 +66,7 @@ pub fn format_int(n: usize) -> String { let len = chars.len(); for (i, c) in chars.iter().enumerate() { - if i != 0 && (len - i) % 3 == 0 { + if i != 0 && (len - i).is_multiple_of(3) { result.push(','); } result.push(*c); diff --git a/src/util/history.rs b/src/util/history.rs index 5069c60..a2f2a2f 100644 --- a/src/util/history.rs +++ b/src/util/history.rs @@ -43,7 +43,7 @@ impl Tape { }; match tape.read_from_disk() { Ok(()) => {} - Err(why) => panic!("{:?}", &why.to_string()), + Err(why) => panic!("{:?}", why.to_string()), } tape } @@ -67,7 +67,7 @@ impl Tape { } } - self.current_index = self.history_tape.len().checked_sub(1).unwrap_or_default(); + self.current_index = self.history_tape.len().saturating_sub(1); Ok(()) } } @@ -84,7 +84,7 @@ impl Tape { // Reset tape to end self.should_scroll_back = false; - self.current_index = self.history_tape.len().checked_sub(1).unwrap_or_default(); + self.current_index = self.history_tape.len().saturating_sub(1); // Write to file match OpenOptions::new() @@ -111,10 +111,7 @@ impl Tape { fn scroll_back_n(&mut self, num_to_scroll: usize) { if !self.history_tape.is_empty() { if self.should_scroll_back { - self.current_index = self - .current_index - .checked_sub(num_to_scroll) - .unwrap_or_default(); + self.current_index = self.current_index.saturating_sub(num_to_scroll); } else { self.should_scroll_back = true; } @@ -123,32 +120,32 @@ impl Tape { /// Scroll the tape forward if possible fn scroll_forward_n(&mut self, num_to_scroll: usize) { - if self.current_index != self.history_tape.len().checked_sub(1).unwrap_or_default() + if self.current_index != self.history_tape.len().saturating_sub(1) && !self.history_tape.is_empty() { self.current_index = min( - self.history_tape.len().checked_sub(1).unwrap_or_default(), + self.history_tape.len().saturating_sub(1), self.current_index .checked_add(num_to_scroll) - .unwrap_or_else(|| self.history_tape.len().checked_sub(1).unwrap_or_default()), + .unwrap_or_else(|| self.history_tape.len().saturating_sub(1)), ); } } /// Common case where we scroll back a single item - pub fn scroll_back(&mut self) -> String { + pub fn scroll_back(&mut self) -> Option { self.scroll_back_n(1); self.get_current_item() } /// Common case where we scroll up a single item - pub fn scroll_forward(&mut self) -> String { + pub fn scroll_forward(&mut self) -> Option { self.scroll_forward_n(1); self.get_current_item() } - pub fn get_current_item(&self) -> String { - self.history_tape[self.current_index].clone() + pub fn get_current_item(&self) -> Option { + self.history_tape.get(self.current_index).cloned() } } @@ -165,7 +162,7 @@ mod tests { fn can_add_item() { let mut tape = Tape::new(); tape.add_item("test").unwrap(); - assert_eq!(String::from("test"), tape.get_current_item()); + assert_eq!(Some(String::from("test")), tape.get_current_item()); } #[test] @@ -174,7 +171,7 @@ mod tests { // Create some dummy data (0..10).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; tape.scroll_back_n(5); @@ -187,7 +184,7 @@ mod tests { // Create some dummy data (0..5).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; tape.scroll_back_n(tape.history_tape.len() * 2); @@ -200,10 +197,10 @@ mod tests { // Create some dummy data (0..5).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; - tape.scroll_back(); + let _ = tape.scroll_back(); assert_eq!(tape.current_index, tape.history_tape.len() - 1 - 1); } @@ -213,7 +210,7 @@ mod tests { // Create some dummy data (0..25).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; tape.scroll_back_n(10); @@ -227,7 +224,7 @@ mod tests { // Create some dummy data (0..5).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; tape.scroll_back_n(10); @@ -241,11 +238,23 @@ mod tests { // Create some dummy data (0..5).for_each(|_| tape.history_tape.push(String::new())); - tape.current_index = tape.history_tape.len().checked_sub(1).unwrap_or_default(); + tape.current_index = tape.history_tape.len().saturating_sub(1); tape.should_scroll_back = true; - tape.scroll_back(); - tape.scroll_forward(); + let _ = tape.scroll_back(); + let _ = tape.scroll_forward(); assert_eq!(tape.current_index, tape.history_tape.len() - 1); } + + #[test] + fn scroll_empty_tape_is_noop() { + // Fresh installs create an empty tape file; Up/Down must not panic + let mut tape = Tape { + history_tape: vec![], + current_index: 0, + should_scroll_back: false, + }; + assert_eq!(tape.scroll_back(), None); + assert_eq!(tape.scroll_forward(), None); + } } diff --git a/src/util/sanitizers.rs b/src/util/sanitizers.rs index 8fc45e9..191b299 100644 --- a/src/util/sanitizers.rs +++ b/src/util/sanitizers.rs @@ -76,6 +76,15 @@ mod tests { assert_eq!(l.get_real_length(content), 6); } + #[test] + fn test_length_multibyte_next_to_ansi() { + // 💛 ends in byte 0x9B; the ANSI pattern must not match it together with the + // following ASCII char, which produced invalid UTF-8 and panicked highlight rendering + let l = LengthFinder::new(); + assert_eq!(l.get_real_length("\u{1F49B}a"), 2); + assert_eq!(l.get_real_length("\x1b[31m\u{1F49B}a\x1b[0m"), 2); + } + #[test] fn test_row_length_clean() { let l = LengthFinder::new();