From 3b5c3b9a4e73ec182a1d1544b8461e9b45356d9a Mon Sep 17 00:00:00 2001 From: Dan Schafer Date: Thu, 24 Apr 2025 14:43:06 -0700 Subject: [PATCH 1/3] Replace termion with crossterm for Windows compatibility --- Cargo.lock | 254 ++++++++++++++++++++++++++++++++++++++++++++-------- Cargo.toml | 2 +- src/main.rs | 128 +++++++++++++++----------- 3 files changed, 297 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05e372e..69b6695 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,7 +65,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -76,7 +76,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -324,6 +324,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -394,12 +403,60 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.9.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "difflib" version = "0.4.0" @@ -412,6 +469,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.15.0" @@ -447,6 +513,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "exr" version = "1.73.0" @@ -619,7 +695,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -734,14 +810,25 @@ dependencies = [ ] [[package]] -name = "libredox" -version = "0.1.3" +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ - "bitflags 2.9.0", - "libc", - "redox_syscall", + "autocfg", + "scopeguard", ] [[package]] @@ -791,6 +878,18 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -869,12 +968,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "numtoa" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" - [[package]] name = "once_cell" version = "1.21.3" @@ -893,6 +986,29 @@ version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" @@ -1045,6 +1161,7 @@ dependencies = [ "assert_cmd", "clap", "criterion", + "crossterm", "doc-comment", "env_logger", "image", @@ -1053,7 +1170,6 @@ dependencies = [ "owo-colors", "predicates", "regex", - "termion", ] [[package]] @@ -1186,12 +1302,6 @@ dependencies = [ "bitflags 2.9.0", ] -[[package]] -name = "redox_termios" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - [[package]] name = "regex" version = "1.11.1" @@ -1227,6 +1337,19 @@ version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -1248,6 +1371,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -1295,6 +1424,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -1352,18 +1511,6 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" -[[package]] -name = "termion" -version = "4.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3669a69de26799d6321a5aa713f55f7e2cd37bd47be044b50f2acafc42c122bb" -dependencies = [ - "libc", - "libredox", - "numtoa", - "redox_termios", -] - [[package]] name = "termtree" version = "0.5.1" @@ -1451,6 +1598,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1582,13 +1735,44 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 93bc0a3..bd2838a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,13 +21,13 @@ maintenance.status = "as-is" [dependencies] anyhow = "1.0.95" clap = { version = "4.5.27", features = ["derive"] } +crossterm = "0.29.0" doc-comment = "0.3.3" env_logger = "0.11.6" image = "0.25.6" itertools = "0.14.0" log = "0.4.25" owo-colors = "4.1.0" -termion = "4.0.3" [dev-dependencies] assert_cmd = "2.0.17" diff --git a/src/main.rs b/src/main.rs index 0031134..5e33dd1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,11 +5,17 @@ use std::{ use anyhow::Result; use clap::{Args, Parser, Subcommand, ValueEnum}; +use crossterm::{ + cursor::{Hide, MoveUp, Show}, + execute, + style::Print, + terminal::Clear, +}; use log::debug; -use qsolve::datastructure::CoordSet; use qsolve::heuristic::{Heuristic, all_heuristics}; use qsolve::share::generate_share_content; use qsolve::solvestate::{Charset, SolveState, SolveStrategy}; +use qsolve::{datastructure::CoordSet, solveiter::SolveIterItem}; use qsolve::{file::QueensFile, solveiter::solve_iter}; #[derive(Parser)] @@ -172,6 +178,68 @@ fn print(path_args: &PathCli, display_args: &DisplayCli) -> Result<()> { Ok(()) } +/// Helper function to print a given [SolveIterItem] as part of the +/// animate command. +fn print_animated_iter_item( + solve_iter_item: &SolveIterItem, + charset: Charset, + delay: Duration, +) -> Result<()> { + let mut stdout = std::io::stdout(); + let size: u16 = (solve_iter_item.solve_state.board.size()) + .try_into() + .unwrap(); + + execute!( + stdout, + Print( + solve_iter_item + .solve_state + .ansi_string(CoordSet::default(), charset) + .unwrap() + ), + Print("\n"), + )?; + std::thread::sleep(delay); + execute!( + stdout, + MoveUp(size), + Print( + solve_iter_item + .solve_state + .ansi_string( + solve_iter_item + .next_heuristic + .map(|h| h.seen_coords(&solve_iter_item.solve_state)) + .unwrap_or_default(), + charset + ) + .unwrap() + ), + Print("\n"), + Clear(crossterm::terminal::ClearType::CurrentLine), + Print( + solve_iter_item + .next_heuristic + .map_or("Done!\n".to_string(), Heuristic::description) + ), + Print("\n"), + )?; + if solve_iter_item.next_heuristic.is_none() { + return Ok(()); + } + std::thread::sleep(delay); + execute!( + stdout, + MoveUp(1), + Clear(crossterm::terminal::ClearType::CurrentLine), + MoveUp(1), + Clear(crossterm::terminal::ClearType::CurrentLine), + MoveUp(size), + )?; + Ok(()) +} + /// Top-level entry point for the animate subcommand. fn animate( path_args: &PathCli, @@ -182,56 +250,14 @@ fn animate( let queens_file = queens_file_from_path(path_args)?; let solve_state = SolveState::from(&queens_file); let heuristics = all_heuristics(solve_state.board); - println!("{}", termion::cursor::Hide); - let _hc = termion::cursor::HideCursor::from(std::io::stdout()); - let end_state = solve_iter(solve_state, solve_args.strategy, &heuristics) - .inspect(|ss| { - println!( - "{}", - ss.solve_state - .ansi_string(CoordSet::default(), display_args.charset) - .unwrap() - ); - std::thread::sleep(*delay); - println!( - "\r{}\r", - termion::cursor::Up((ss.solve_state.board.size() + 1).try_into().unwrap()) - ); - println!( - "{}", - ss.solve_state - .ansi_string( - ss.next_heuristic - .map(|h| h.seen_coords(&ss.solve_state)) - .unwrap_or_default(), - display_args.charset - ) - .unwrap() - ); - println!( - "{}{}", - termion::clear::CurrentLine, - ss.next_heuristic - .map_or("Done!".to_string(), Heuristic::description) - ); - std::thread::sleep(*delay); - println!( - "{}{}{}{}{}", - termion::cursor::Up(1), - termion::clear::CurrentLine, - termion::cursor::Up(1), - termion::clear::CurrentLine, - termion::cursor::Up((ss.solve_state.board.size() + 1).try_into().unwrap()) - ); - }) - .last() - .unwrap(); - println!( - "{}", - end_state - .solve_state - .ansi_string(CoordSet::default(), display_args.charset)? - ); + + let mut stdout = std::io::stdout(); + execute!(stdout, Hide)?; + + for solve_iter_item in solve_iter(solve_state, solve_args.strategy, &heuristics) { + print_animated_iter_item(&solve_iter_item, display_args.charset, *delay)?; + } + execute!(stdout, Show)?; Ok(()) } From 973965bfac9ed66eaeab16283290486823a5cfcc Mon Sep 17 00:00:00 2001 From: Dan Schafer Date: Thu, 24 Apr 2025 14:53:19 -0700 Subject: [PATCH 2/3] Add ctrlc handling to show cursor --- Cargo.lock | 29 +++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 5 +++++ 3 files changed, 35 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 69b6695..13392a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,6 +245,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "ciborium" version = "0.2.2" @@ -436,6 +442,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "ctrlc" +version = "3.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + [[package]] name = "derive_more" version = "2.0.1" @@ -896,6 +912,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1162,6 +1190,7 @@ dependencies = [ "clap", "criterion", "crossterm", + "ctrlc", "doc-comment", "env_logger", "image", diff --git a/Cargo.toml b/Cargo.toml index bd2838a..56a5665 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ maintenance.status = "as-is" anyhow = "1.0.95" clap = { version = "4.5.27", features = ["derive"] } crossterm = "0.29.0" +ctrlc = "3.4.6" doc-comment = "0.3.3" env_logger = "0.11.6" image = "0.25.6" diff --git a/src/main.rs b/src/main.rs index 5e33dd1..9e17608 100644 --- a/src/main.rs +++ b/src/main.rs @@ -252,6 +252,11 @@ fn animate( let heuristics = all_heuristics(solve_state.board); let mut stdout = std::io::stdout(); + ctrlc::set_handler(move || { + // Discard the error; we're in ctrl-c anyway + let _ = execute!(std::io::stdout(), Show); + std::process::exit(130); + })?; execute!(stdout, Hide)?; for solve_iter_item in solve_iter(solve_state, solve_args.strategy, &heuristics) { From f227e98efa8aea46371108e1b83b306d0a1abaef Mon Sep 17 00:00:00 2001 From: Dan Schafer Date: Thu, 24 Apr 2025 14:55:03 -0700 Subject: [PATCH 3/3] Update version to 1.0.1 --- CHANGELOG.md | 4 ++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9280c2..e916002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## QSolve 1.0.1 + +- Switch from `termion` to `crossterm` to support Windows. + ## QSolve 1.0.0 - Initial release diff --git a/Cargo.lock b/Cargo.lock index 13392a5..5b6a9b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "qsolve" -version = "1.0.0" +version = "1.0.1" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 56a5665..d0a0ee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "qsolve" -version = "1.0.0" +version = "1.0.1" authors = ["Dan Schafer "] edition = "2024" description = "A command-line tool for solving Queens puzzles"