From b6e71c2b5c744bc6824c6d246feb278586603d48 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 24 Jun 2026 23:38:37 +0300 Subject: [PATCH 1/5] Upgrade crossterm to 0.29 and ratatui to 0.30 Bump MSRV to 1.88. Adapt to the new ratatui API (backend error bound on the draw/event-loop helpers, elided lifetime on with_items) and fix the clippy lints the new toolchain surfaced. --- Cargo.lock | 601 +++++++++++++++++++++++++++----------- Cargo.toml | 8 +- src/commands/courses.rs | 2 +- src/commands/exercises.rs | 4 +- src/interactive/prompt.rs | 2 + src/interactive/state.rs | 2 +- src/lib.rs | 4 + src/updater.rs | 1 - 8 files changed, 444 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24ac1f2..022f215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -28,18 +28,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -128,6 +116,15 @@ dependencies = [ "backtrace", ] +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.3.2" @@ -228,9 +225,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "blake3" @@ -271,6 +268,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + [[package]] name = "byteorder" version = "1.5.0" @@ -304,12 +307,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - [[package]] name = "castaway" version = "0.2.3" @@ -338,9 +335,9 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -413,7 +410,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -440,9 +437,9 @@ dependencies = [ [[package]] name = "compact_str" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" dependencies = [ "castaway", "cfg-if", @@ -461,7 +458,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.13", "windows-sys 0.52.0", ] @@ -471,6 +468,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.17.0" @@ -565,15 +571,17 @@ checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "crossterm_winapi", + "derive_more", + "document-features", "mio", "parking_lot", - "rustix", + "rustix 1.1.4", "signal-hook", "signal-hook-mio", "winapi", @@ -606,12 +614,9 @@ checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "derive_arbitrary" @@ -621,7 +626,29 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.87", ] [[package]] @@ -670,7 +697,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -679,6 +706,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dunce" version = "1.0.5" @@ -720,14 +756,20 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] +[[package]] +name = "fast-srgb8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" + [[package]] name = "fastrand" version = "2.1.1" @@ -741,7 +783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix", + "rustix 0.38.36", "windows-sys 0.52.0", ] @@ -778,7 +820,7 @@ dependencies = [ "log", "nu-ansi-term 0.50.1", "regex", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -796,6 +838,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -867,7 +915,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -923,6 +971,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + [[package]] name = "gimli" version = "0.31.0" @@ -978,9 +1037,27 @@ name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ - "ahash", "allocator-api2", + "equivalent", + "foldhash", ] [[package]] @@ -1225,7 +1302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1238,7 +1315,16 @@ dependencies = [ "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.1.13", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", ] [[package]] @@ -1257,7 +1343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" dependencies = [ "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1292,9 +1378,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -1366,10 +1452,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1388,6 +1475,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "kasuari" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" +dependencies = [ + "hashbrown 0.16.1", + "portable-atomic", + "thiserror 2.0.18", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1396,9 +1494,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -1410,23 +1508,50 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "libc", "redox_syscall", ] +[[package]] +name = "line-clipping" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.12" @@ -1451,11 +1576,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.4" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9" dependencies = [ - "hashbrown", + "hashbrown 0.17.1", ] [[package]] @@ -1566,7 +1691,7 @@ dependencies = [ "oauth2", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", "uuid", ] @@ -1576,7 +1701,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -1619,9 +1744,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1632,6 +1757,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -1646,7 +1780,7 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" dependencies = [ "base64 0.13.1", "chrono", - "getrandom", + "getrandom 0.2.15", "http 0.2.12", "rand", "reqwest 0.11.27", @@ -1654,7 +1788,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sha2", - "thiserror", + "thiserror 1.0.63", "url", ] @@ -1685,6 +1819,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "palette" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" +dependencies = [ + "approx", + "fast-srgb8", + "libm", + "palette_derive", +] + +[[package]] +name = "palette_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" +dependencies = [ + "by_address", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -1708,12 +1866,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pbkdf2" version = "0.12.2" @@ -1765,7 +1917,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -1788,9 +1940,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" -version = "1.7.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "powerfmt" @@ -1875,7 +2027,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "socket2", - "thiserror", + "thiserror 1.0.63", "tokio", "tracing", ] @@ -1892,7 +2044,7 @@ dependencies = [ "rustc-hash 2.0.0", "rustls 0.23.12", "slab", - "thiserror", + "thiserror 1.0.63", "tinyvec", "tracing", ] @@ -1919,6 +2071,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -1946,28 +2104,73 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] name = "ratatui" -version = "0.28.1" +version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d" dependencies = [ - "bitflags 2.6.0", - "cassowary", - "compact_str", - "crossterm", "instability", + "ratatui-core", + "ratatui-crossterm", + "ratatui-widgets", + "serde", +] + +[[package]] +name = "ratatui-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c" +dependencies = [ + "bitflags 2.13.0", + "compact_str", + "hashbrown 0.17.1", "itertools", + "kasuari", "lru", - "paste", + "palette", + "serde", "strum", - "strum_macros", + "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", - "unicode-width", + "unicode-width 0.2.2", +] + +[[package]] +name = "ratatui-crossterm" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0" +dependencies = [ + "cfg-if", + "crossterm", + "instability", + "ratatui-core", +] + +[[package]] +name = "ratatui-widgets" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1" +dependencies = [ + "bitflags 2.13.0", + "hashbrown 0.17.1", + "indoc", + "instability", + "itertools", + "line-clipping", + "ratatui-core", + "serde", + "strum", + "time", + "unicode-segmentation", + "unicode-width 0.2.2", ] [[package]] @@ -1976,7 +2179,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", ] [[package]] @@ -1985,9 +2188,9 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -2130,7 +2333,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", "spin", "untrusted", @@ -2176,19 +2379,41 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.21.12" @@ -2305,7 +2530,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2324,12 +2549,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -2341,19 +2573,28 @@ checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782" dependencies = [ "log", "serde", - "thiserror", + "thiserror 1.0.63", "xml-rs", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2364,7 +2605,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2563,24 +2804,23 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.26.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -2612,9 +2852,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -2677,7 +2917,7 @@ dependencies = [ "cfg-if", "fastrand", "once_cell", - "rustix", + "rustix 0.38.36", "windows-sys 0.59.0", ] @@ -2696,7 +2936,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix", + "rustix 0.38.36", "windows-sys 0.48.0", ] @@ -2712,7 +2952,16 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.63", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -2723,7 +2972,18 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -2738,30 +2998,31 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", - "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -2784,7 +3045,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tmc" -version = "1.1.2" +version = "1.1.3" dependencies = [ "anyhow", "assert_cmd", @@ -2839,7 +3100,7 @@ dependencies = [ "shellwords", "tar", "tempfile", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-plugins", "tmc-langs-util", @@ -2862,7 +3123,7 @@ dependencies = [ "log", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-util", "walkdir", @@ -2886,7 +3147,7 @@ dependencies = [ "subprocess", "tar", "tempfile", - "thiserror", + "thiserror 1.0.63", "tmc-langs-util", "walkdir", "zip", @@ -2907,7 +3168,7 @@ dependencies = [ "serde_json", "tar", "tempfile", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-util", "walkdir", @@ -2924,7 +3185,7 @@ dependencies = [ "serde", "serde-xml-rs", "serde_yaml", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-util", "zip", @@ -2948,7 +3209,7 @@ source = "git+https://github.com/rage/tmc-langs-rust?tag=0.36.3#9313ab8ff5969757 dependencies = [ "log", "tar", - "thiserror", + "thiserror 1.0.63", "tmc-langs-csharp", "tmc-langs-framework", "tmc-langs-java", @@ -2976,7 +3237,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-util", "walkdir", @@ -2991,7 +3252,7 @@ dependencies = [ "log", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", "tmc-langs-framework", "tmc-langs-util", "zip", @@ -3012,7 +3273,7 @@ dependencies = [ "serde_path_to_error", "serde_yaml", "tempfile", - "thiserror", + "thiserror 1.0.63", "toml", "type-map", "walkdir", @@ -3033,7 +3294,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", "tmc-langs-util", "uuid", ] @@ -3056,7 +3317,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror", + "thiserror 1.0.63", "tmc-langs-plugins", "tmc-langs-util", "url", @@ -3192,7 +3453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" dependencies = [ "crossbeam-channel", - "thiserror", + "thiserror 1.0.63", "time", "tracing-subscriber", ] @@ -3205,7 +3466,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3300,19 +3561,19 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-truncate" -version = "1.1.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.2", ] [[package]] @@ -3321,6 +3582,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3353,12 +3620,14 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ - "getrandom", - "serde", + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", ] [[package]] @@ -3409,27 +3678,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.77", "wasm-bindgen-shared", ] @@ -3447,9 +3703,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3457,22 +3713,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.77", - "wasm-bindgen-backend", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" @@ -3743,8 +4002,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", - "linux-raw-sys", - "rustix", + "linux-raw-sys 0.4.14", + "rustix 0.38.36", ] [[package]] @@ -3771,7 +4030,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3791,7 +4050,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.87", ] [[package]] @@ -3816,7 +4075,7 @@ dependencies = [ "pbkdf2", "rand", "sha1", - "thiserror", + "thiserror 1.0.63", "time", "zeroize", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 69554e4..1c3d3db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,19 +12,19 @@ authors = [ edition = "2021" description = "Client for downloading, testing and submitting exercises through the TestMyCode and MOOC.fi systems." license = "Apache-2.0" -rust-version = "1.70.0" +rust-version = "1.88.0" [dependencies] anyhow = { version = "1.0.56", features = ["backtrace"] } bytes = "1.4.0" clap = { version = "4.0.7", features = ["derive"] } clap_complete = "4.0.2" -crossterm = "0.28.1" +crossterm = "0.29.0" flexi_logger = "0.29.0" indicatif = "0.17.1" log = "0.4.17" -ratatui = { version = "0.28.1", default-features = false, features = [ - 'crossterm', +ratatui = { version = "0.30.2", default-features = false, features = [ + "crossterm_0_29", ] } reqwest = { version = "0.12.7", default-features = false, features = [ "blocking", diff --git a/src/commands/courses.rs b/src/commands/courses.rs index b50814d..206a7fa 100644 --- a/src/commands/courses.rs +++ b/src/commands/courses.rs @@ -98,7 +98,7 @@ mod tests { let output = String::from_utf8(output.into_inner()).unwrap(); let output = output.lines().collect::>(); - assert!(output[0].eq(""), "first line should be empty"); + assert!(output[0].is_empty(), "first line should be empty"); assert!( output[1].eq("mooc-tutustumiskurssi"), "Expected 'mooc-tutustumiskurssi', got {}", diff --git a/src/commands/exercises.rs b/src/commands/exercises.rs index abcc198..45c65a9 100644 --- a/src/commands/exercises.rs +++ b/src/commands/exercises.rs @@ -189,7 +189,7 @@ mod tests { print_exercises(&mut io, "course_name", &exercises).unwrap(); let output = String::from_utf8(output.into_inner()).unwrap(); let output = output.lines().collect::>(); - assert!(output[0].eq(""), "first line should be empty"); + assert!(output[0].is_empty(), "first line should be empty"); let course_string = "Course name: course_name"; assert!( output[1].eq(course_string), @@ -235,7 +235,7 @@ mod tests { let output = String::from_utf8(output.into_inner()).unwrap(); let output = output.lines().collect::>(); - assert!(output[0].eq(""), "first line should be empty"); + assert!(output[0].is_empty(), "first line should be empty"); let course_string = "Course name: course_name"; assert!( output[1].eq(course_string), diff --git a/src/interactive/prompt.rs b/src/interactive/prompt.rs index 3b759dc..213d5e6 100644 --- a/src/interactive/prompt.rs +++ b/src/interactive/prompt.rs @@ -62,6 +62,7 @@ fn draw_terminal( ) -> anyhow::Result<()> where B: Backend, + B::Error: std::error::Error + Send + Sync + 'static, { terminal.draw(|f| { let chunks = Layout::default() @@ -150,6 +151,7 @@ fn event_loop( ) -> anyhow::Result> where B: Backend, + B::Error: std::error::Error + Send + Sync + 'static, { loop { draw_terminal(terminal, app, prompt)?; diff --git a/src/interactive/state.rs b/src/interactive/state.rs index c3777be..25bc8f4 100644 --- a/src/interactive/state.rs +++ b/src/interactive/state.rs @@ -20,7 +20,7 @@ impl<'a, T: Clone> StatefulList<'a, T> { StatefulList::with_items(&[]) } - pub fn with_items(items: &[T]) -> StatefulList { + pub fn with_items(items: &[T]) -> StatefulList<'_, T> { StatefulList { state: ListState::default(), displayed: items.to_vec(), diff --git a/src/lib.rs b/src/lib.rs index 39617ee..1ded7f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,7 @@ +// tmc-langs' LangsError/TestMyCodeClientError are large and returned by value across the +// client API; boxing every Result isn't worth it for a CLI. +#![allow(clippy::result_large_err)] + mod cli; mod client; mod commands; diff --git a/src/updater.rs b/src/updater.rs index 9e3cdfc..25fd181 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -22,7 +22,6 @@ pub const DELAY_MILLIS_24H: u128 = 1440 * 60 * 1000; /// generates a new timestamp. If a new version is found, the function /// stashes the old executable and downloads a new one. /// Will run in privileged stage if needed on Windows! - pub fn check_for_update(config: &mut TmcCliConfig, force: bool) -> anyhow::Result<()> { if force || is_it_time_yet(config)? { generate_time_stamp(config)?; From 6e2fbf25bb3151ae269e0b9bccb55f1bd92e7161 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 24 Jun 2026 23:38:55 +0300 Subject: [PATCH 2/5] Fix interactive menu input handling Handle the new key-event model: act on key press and auto-repeat but ignore key releases, which the Windows console emits per tap and which caused double input. Drain events buffered when a menu opens, harden CTRL-C (match any modifier combination, restore the terminal before exiting), and add tests for the key handling. --- src/interactive/prompt.rs | 173 +++++++++++++++++++++++++++++++------- 1 file changed, 141 insertions(+), 32 deletions(-) diff --git a/src/interactive/prompt.rs b/src/interactive/prompt.rs index 213d5e6..fc3d50f 100644 --- a/src/interactive/prompt.rs +++ b/src/interactive/prompt.rs @@ -1,6 +1,6 @@ use super::state::AppState; use crossterm::{ - event::{poll, read, Event, KeyCode, KeyModifiers}, + event::{poll, read, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, ExecutableCommand, }; @@ -45,6 +45,18 @@ pub fn interactive_list(prompt: &str, items: &[&str]) -> anyhow::Result anyhow::Result>> { if poll(Duration::from_millis(POLL_RATE))? { if let Event::Key(x) = read()? { - // CTRL-C is the usual stop command - // which is disabled by default because of raw mode - if x.code == KeyCode::Char('c') && x.modifiers == KeyModifiers::CONTROL { - // respect the interrupt and exit immediately after disabling raw mode - disable_raw_mode().ok(); - std::process::exit(0); - } - let selection = match x.code { - KeyCode::Esc => Some(None), - KeyCode::Up | KeyCode::Left => { - app.items.previous(); - None - } - KeyCode::Down | KeyCode::Right => { - app.items.next(); - None - } - // if no selection, None - // else Some(Some(selection)) - KeyCode::Enter => app.get_selected().map(Some), - KeyCode::Char(c) => { - app.push_filter(c); - None - } - KeyCode::Backspace => { - app.pop_filter(); - None - } - _ => None, - }; - return Ok(selection); + return handle_key_event(app, x); } } Ok(None) } +/// handles a single key event, mutating `app` and returning the selection result. +/// +/// See `read_keys` for the meaning of the `Option>` return value. +fn handle_key_event(app: &mut AppState, x: KeyEvent) -> anyhow::Result>> { + // Accept presses, and auto-repeat for held nav/backspace keys. Ignore other events + // (e.g. Windows Release) to avoid spurious input. + let repeat_nav = x.kind == KeyEventKind::Repeat + && matches!( + x.code, + KeyCode::Up | KeyCode::Down | KeyCode::Left | KeyCode::Right | KeyCode::Backspace + ); + if !(x.is_press() || repeat_nav) { + return Ok(None); + } + + // CTRL-C is the usual stop command + // which is disabled by default because of raw mode + if x.code == KeyCode::Char('c') && x.modifiers.contains(KeyModifiers::CONTROL) { + // restore the terminal before exiting + let _ = stdout().execute(LeaveAlternateScreen); + disable_raw_mode().ok(); + std::process::exit(0); + } + let selection = match x.code { + KeyCode::Esc => Some(None), + KeyCode::Up | KeyCode::Left => { + app.items.previous(); + None + } + KeyCode::Down | KeyCode::Right => { + app.items.next(); + None + } + // if no selection, None + // else Some(Some(selection)) + KeyCode::Enter => app.get_selected().map(Some), + KeyCode::Char(c) => { + app.push_filter(c); + None + } + KeyCode::Backspace => { + app.pop_filter(); + None + } + _ => None, + }; + Ok(selection) +} + fn event_loop( terminal: &mut Terminal, app: &mut AppState<'_>, @@ -171,3 +202,81 @@ where } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn key(code: KeyCode, kind: KeyEventKind) -> KeyEvent { + KeyEvent::new_with_kind(code, KeyModifiers::NONE, kind) + } + + fn enter(kind: KeyEventKind) -> KeyEvent { + key(KeyCode::Enter, kind) + } + + #[test] + fn enter_release_is_ignored() { + let items = &["eka", "toka"]; + let mut app = AppState::new(items); + + // A stray Enter release must not count as a selection. + let result = handle_key_event(&mut app, enter(KeyEventKind::Release)).unwrap(); + assert_eq!(result, None); + } + + #[test] + fn enter_repeat_is_ignored() { + let items = &["eka", "toka"]; + let mut app = AppState::new(items); + + let result = handle_key_event(&mut app, enter(KeyEventKind::Repeat)).unwrap(); + assert_eq!(result, None); + } + + #[test] + fn enter_press_selects_current_item() { + let items = &["eka", "toka"]; + let mut app = AppState::new(items); + + // index 0 is pre-selected by `AppState::new` + let result = handle_key_event(&mut app, enter(KeyEventKind::Press)).unwrap(); + assert_eq!(result, Some(Some(String::from("eka")))); + } + + #[test] + fn arrow_release_does_not_move_selection() { + let items = &["eka", "toka", "kolmas"]; + let mut app = AppState::new(items); + + // A stray Down release must not advance the cursor (Windows emits Press+Release per tap). + handle_key_event(&mut app, key(KeyCode::Down, KeyEventKind::Release)).unwrap(); + assert_eq!(app.get_selected().unwrap(), "eka"); + + handle_key_event(&mut app, key(KeyCode::Up, KeyEventKind::Release)).unwrap(); + assert_eq!(app.get_selected().unwrap(), "eka"); + } + + #[test] + fn down_press_moves_exactly_one_row() { + let items = &["eka", "toka", "kolmas"]; + let mut app = AppState::new(items); + + // One tap = one Press = one move. + handle_key_event(&mut app, key(KeyCode::Down, KeyEventKind::Press)).unwrap(); + assert_eq!(app.get_selected().unwrap(), "toka"); + + handle_key_event(&mut app, key(KeyCode::Down, KeyEventKind::Press)).unwrap(); + assert_eq!(app.get_selected().unwrap(), "kolmas"); + } + + #[test] + fn down_repeat_moves_selection() { + let items = &["eka", "toka", "kolmas"]; + let mut app = AppState::new(items); + + // Holding Down auto-repeats: each Repeat advances the cursor like a Press. + handle_key_event(&mut app, key(KeyCode::Down, KeyEventKind::Repeat)).unwrap(); + assert_eq!(app.get_selected().unwrap(), "toka"); + } +} From b48b7e6aec4fe4dcd77d9ebaff19f28a4f58288e Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 24 Jun 2026 23:15:31 +0300 Subject: [PATCH 3/5] Fix inverted auto-update gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --no-update branch ran the updater and the default branch printed "No Auto-Updates" — exactly backwards. Auto-update now runs by default on Windows, is skipped in test mode, and --no-update both disables it and prints the notice. Make --force-update conflict with --no-update. --- src/cli.rs | 4 ++-- src/lib.rs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ba4ebfb..9d3a0fa 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -17,7 +17,7 @@ pub struct Cli { #[arg(short = 'd', long, hide = !cfg!(windows))] pub no_update: bool, /// Force auto-update to run. - #[arg(short = 'u', long, hide = !cfg!(windows))] + #[arg(short = 'u', long, hide = !cfg!(windows), conflicts_with = "no_update")] pub force_update: bool, /// Only for internal testing, disables server connection. @@ -95,7 +95,7 @@ pub enum Command { impl Command { pub fn requires_organization_set(&self) -> bool { - matches!(self, Command::Download { .. } | Command::Courses { .. }) + matches!(self, Command::Download { .. } | Command::Courses) } } diff --git a/src/lib.rs b/src/lib.rs index 1ded7f3..8740c88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,14 +43,12 @@ fn run_inner(io: &mut Io, cli: Cli) -> anyhow::Result<()> { #[cfg(target_os = "windows")] let mut config = config; + // Auto-update on Windows unless disabled (--no-update) or running in test mode. if cli.no_update { - let os = std::env::consts::OS; - if os == "windows" { - #[cfg(target_os = "windows")] - updater::check_for_update(&mut config, cli.force_update)?; - } - } else { println!("No Auto-Updates"); + } else if !cli.testmode { + #[cfg(target_os = "windows")] + updater::check_for_update(&mut config, cli.force_update)?; } commands::handle(cli, io, config) From edb6fc0c42bfaef60258e8898c8b8048f531a47d Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 24 Jun 2026 23:15:44 +0300 Subject: [PATCH 4/5] Build Linux releases in a pinned Rust container Build inside rust:1.88.0-bullseye (glibc 2.31) so binaries run on older systems, with the cargo registry and target cached across runs. Drop the i686 OpenSSL setup since reqwest now uses rustls. --- .github/workflows/linux.yml | 40 +++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 6cfb691..87987d1 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,7 +40,7 @@ jobs: deploy: needs: test if: ${{ github.event_name == 'release' }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: target: [i686-unknown-linux-gnu, x86_64-unknown-linux-gnu] @@ -55,18 +55,38 @@ jobs: - name: "Set up Cloud SDK" uses: google-github-actions/setup-gcloud@v2.1.1 - - name: Install dependencies - run: | - sudo dpkg --add-architecture i386 - sudo apt update - sudo apt install -y gcc-multilib libssl-dev:i386 # required to build for 32-bit arch + - name: Cache cargo registry and build artifacts + uses: actions/cache@v4 + with: + path: | + ~/.cargo-container/registry + ~/.cargo-container/git + target + key: ${{ runner.os }}-release-cargo-${{ matrix.target }}-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-release-cargo-${{ matrix.target }}- + # Build inside the official Rust image on Debian bullseye (glibc 2.31, the same floor + # as Ubuntu 20.04) so the binaries run on older systems and the toolchain ships in the + # image instead of being downloaded on every run. gcc-multilib provides the 32-bit + # toolchain for the i686 target; reqwest uses rustls-tls, so no OpenSSL is needed. The + # image tag pins the toolchain to the MSRV. Cargo caches are mounted in and ownership + # normalised afterwards so the cache and gsutil can read the root-owned tree. - name: Cargo build run: | - export I686_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR=/usr/lib/i386-linux-gnu # required to build for 32-bit arch - export I686_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR=/usr/include/i386-linux-gnu # required to build for 32-bit arch - rustup target add ${{ matrix.target }} - cargo build -p tmc --release --verbose --target ${{ matrix.target }} + mkdir -p "$HOME/.cargo-container/registry" "$HOME/.cargo-container/git" + docker run --rm \ + -v "$PWD":/w -w /w \ + -v "$HOME/.cargo-container/registry":/usr/local/cargo/registry \ + -v "$HOME/.cargo-container/git":/usr/local/cargo/git \ + rust:1.88.0-bullseye bash -euo pipefail -c ' + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y gcc-multilib + rustup target add ${{ matrix.target }} + cargo build -p tmc --release --verbose --target ${{ matrix.target }} + ' + sudo chown -R "$(id -u):$(id -g)" target "$HOME/.cargo-container" - name: Get the version id: get_version From 566be01eef2907ffc036d437c507a998f126fb1b Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Wed, 24 Jun 2026 23:16:02 +0300 Subject: [PATCH 5/5] Release 1.1.3 Bump the version and stop hard-coding installer filenames in the README. --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c3d3db..e71f89e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tmc" -version = "1.1.2" +version = "1.1.3" authors = [ "University of Helsinki ", "HoolaBoola ", diff --git a/README.md b/README.md index 3cc6e21..8b1176b 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ After restarting the terminal, this should work: ### Using the Windows installer -The Windows installer can be found in the [releases](https://github.com/rage/tmc-cli-rust/releases). In most cases you will want the 64 bit version, `tmc-cli-rust-x86_64-pc-windows-msvc-v1.1.2.msi`. When a 32 bit version is required, `tmc-cli-rust-i686-pc-windows-msvc-v1.1.2.msi` is also available. After installation, the application updates automatically. +The Windows installer can be found in the [releases](https://github.com/rage/tmc-cli-rust/releases). In most cases you will want the 64 bit version (`tmc-cli-rust-x86_64-pc-windows-msvc-*.msi`); a 32 bit version (`tmc-cli-rust-i686-pc-windows-msvc-*.msi`) is also available. After installation, the application updates automatically. ### Manual installation, additional steps