Merge pull request #37 from teohhanhui/riir

Fix all the line iterators
This commit is contained in:
Teoh Han Hui 2024-07-25 03:55:46 +08:00 committed by GitHub
commit fe7ed923ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 159 additions and 156 deletions

View file

@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::cmp; use std::cmp;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::fs::{self, File}; use std::fs::{self, File};
use std::io::{self, IsTerminal as _, Read as _}; use std::io::{self, IsTerminal as _, Read as _, Write as _};
use std::iter::zip; use std::iter::zip;
use std::path::PathBuf; use std::path::PathBuf;
@ -61,7 +61,7 @@ fn main() -> Result<()> {
if options.test_print { if options.test_print {
let (asc, _) = get_distro_ascii(distro, backend).context("failed to get distro ascii")?; let (asc, _) = get_distro_ascii(distro, backend).context("failed to get distro ascii")?;
println!("{asc}"); writeln!(io::stdout(), "{asc}").context("failed to write ascii to stdout")?;
return Ok(()); return Ok(());
} }
@ -102,10 +102,12 @@ fn main() -> Result<()> {
if show_pride_month && !config.pride_month_disable { if show_pride_month && !config.pride_month_disable {
pride_month::start_animation(color_mode).context("failed to draw pride month animation")?; pride_month::start_animation(color_mode).context("failed to draw pride month animation")?;
println!(); writeln!(
println!("Happy pride month!"); io::stdout(),
println!("(You can always view the animation again with `hyfetch --june`)"); "\nHappy pride month!\n(You can always view the animation again with `hyfetch \
println!(); --june`)\n"
)
.context("failed to write message to stdout")?;
if !june_path.is_file() { if !june_path.is_file() {
fs::create_dir_all(&cache_path) fs::create_dir_all(&cache_path)
@ -252,8 +254,7 @@ pub fn create_config(
) )
.expect("logo should not contain invalid color codes") .expect("logo should not contain invalid color codes")
); );
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
let mut option_counter: u8 = 1; let mut option_counter: u8 = 1;
@ -267,9 +268,9 @@ pub fn create_config(
*option_counter += 1; *option_counter += 1;
} }
fn print_title_prompt(option_counter: u8, prompt: &str, color_mode: AnsiMode) { fn print_title_prompt(option_counter: u8, prompt: &str, color_mode: AnsiMode) -> Result<()> {
printc(format!("&a{option_counter}. {prompt}"), color_mode) printc(format!("&a{option_counter}. {prompt}"), color_mode)
.expect("prompt should not contain invalid color codes"); .context("failed to print prompt")
} }
////////////////////////////// //////////////////////////////
@ -287,7 +288,7 @@ pub fn create_config(
), ),
color_mode, color_mode,
) )
.expect("message should not contain invalid color codes"); .context("failed to print message")?;
input(Some("Press enter to continue...")).context("failed to read input")?; input(Some("Press enter to continue...")).context("failed to read input")?;
} }
} }
@ -302,8 +303,7 @@ pub fn create_config(
return Ok((AnsiMode::Rgb, "Detected color mode")); return Ok((AnsiMode::Rgb, "Detected color mode"));
} }
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
let (Width(term_w), _) = terminal_size().context("failed to get terminal size")?; let (Width(term_w), _) = terminal_size().context("failed to get terminal size")?;
@ -352,8 +352,7 @@ pub fn create_config(
s s
}, },
); );
printc(line, AnsiMode::Ansi256) printc(line, AnsiMode::Ansi256).context("failed to print 8-bit color test line")?;
.expect("message should not contain invalid color codes");
} }
{ {
let label = format!( let label = format!(
@ -374,17 +373,21 @@ pub fn create_config(
s s
}, },
); );
printc(line, AnsiMode::Rgb).expect("message should not contain invalid color codes"); printc(line, AnsiMode::Rgb).context("failed to print RGB color test line")?;
} }
println!(); writeln!(io::stdout()).context("failed to write to stdout")?;
print_title_prompt( print_title_prompt(
option_counter, option_counter,
"Which &bcolor system &ado you want to use?", "Which &bcolor system &ado you want to use?",
color_mode, color_mode,
); )
println!(r#"(If you can't see colors under "RGB Color Testing", please choose 8bit)"#); .context("failed to print title prompt")?;
println!(); writeln!(
io::stdout(),
"(If you can't see colors under \"RGB Color Testing\", please choose 8bit)\n"
)
.context("failed to write message to stdout")?;
let choice = literal_input( let choice = literal_input(
"Your choice?", "Your choice?",
@ -392,7 +395,8 @@ pub fn create_config(
AnsiMode::Rgb.as_ref(), AnsiMode::Rgb.as_ref(),
true, true,
color_mode, color_mode,
)?; )
.context("failed to ask for choice input")?;
Ok(( Ok((
choice.parse().expect("selected color mode should be valid"), choice.parse().expect("selected color mode should be valid"),
"Selected color mode", "Selected color mode",
@ -414,21 +418,22 @@ pub fn create_config(
return Ok((det_bg.theme(), "Detected background color")); return Ok((det_bg.theme(), "Detected background color"));
} }
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
print_title_prompt( print_title_prompt(
option_counter, option_counter,
"Is your terminal in &blight mode&~ or &4dark mode&~?", "Is your terminal in &blight mode&~ or &4dark mode&~?",
color_mode, color_mode,
); )
.context("failed to print title prompt")?;
let choice = literal_input( let choice = literal_input(
"", "",
TerminalTheme::VARIANTS, TerminalTheme::VARIANTS,
TerminalTheme::Dark.as_ref(), TerminalTheme::Dark.as_ref(),
true, true,
color_mode, color_mode,
)?; )
.context("failed to ask for choice input")?;
Ok(( Ok((
choice.parse().expect("selected theme should be valid"), choice.parse().expect("selected theme should be valid"),
"Selected background color", "Selected background color",
@ -500,29 +505,33 @@ pub fn create_config(
pages.push(page); pages.push(page);
} }
let print_flag_page = |page, page_num| { let print_flag_page = |page, page_num| -> Result<()> {
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes"); print_title_prompt(option_counter, "Let's choose a flag!", color_mode)
print_title_prompt(option_counter, "Let's choose a flag!", color_mode); .context("failed to print title prompt")?;
println!("Available flag presets:"); writeln!(
println!("Page: {page_num} of {num_pages}", page_num = page_num + 1); io::stdout(),
println!(); "Available flag presets:\nPage: {page_num} of {num_pages}\n",
page_num = page_num + 1
)
.context("failed to write header to stdout")?;
for &row in page { for &row in page {
print_flag_row(row, color_mode); print_flag_row(row, color_mode).context("failed to print flag row")?;
} }
println!(); writeln!(io::stdout()).context("failed to write to stdout")?;
Ok(())
}; };
fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) { fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) -> Result<()> {
for i in 0..4 { for i in 0..4 {
let mut line = Vec::new(); let mut line = Vec::new();
for flag in row { for flag in row {
line.push(&*flag[i]); line.push(&*flag[i]);
} }
printc(line.join(" "), color_mode) printc(line.join(" "), color_mode).context("failed to print line")?;
.expect("flag line should not contain invalid color codes");
} }
println!(); writeln!(io::stdout()).context("failed to write to stdout")?;
Ok(())
} }
let default_lightness = Config::default_lightness(theme); let default_lightness = Config::default_lightness(theme);
@ -541,7 +550,7 @@ pub fn create_config(
let mut page: u8 = 0; let mut page: u8 = 0;
loop { loop {
print_flag_page(&pages[usize::from(page)], page); print_flag_page(&pages[usize::from(page)], page).context("failed to print flag page")?;
let mut opts: Vec<&str> = <Preset as VariantNames>::VARIANTS.into(); let mut opts: Vec<&str> = <Preset as VariantNames>::VARIANTS.into();
if page < num_pages - 1 { if page < num_pages - 1 {
@ -550,7 +559,11 @@ pub fn create_config(
if page > 0 { if page > 0 {
opts.push("prev"); opts.push("prev");
} }
println!("Enter 'next' to go to the next page and 'prev' to go to the previous page."); writeln!(
io::stdout(),
"Enter 'next' to go to the next page and 'prev' to go to the previous page."
)
.context("failed to write message to stdout")?;
let selection = literal_input( let selection = literal_input(
format!( format!(
"Which {preset} do you want to use? ", "Which {preset} do you want to use? ",
@ -561,6 +574,7 @@ pub fn create_config(
false, false,
color_mode, color_mode,
) )
.context("failed to ask for choice input")
.context("failed to select preset")?; .context("failed to select preset")?;
if selection == "next" { if selection == "next" {
page += 1; page += 1;
@ -593,33 +607,34 @@ pub fn create_config(
let test_ascii = &TEST_ASCII[1..TEST_ASCII.len() - 1]; let test_ascii = &TEST_ASCII[1..TEST_ASCII.len() - 1];
let test_ascii_width = test_ascii let test_ascii_width = test_ascii
.split('\n') .lines()
.map(|line| line.graphemes(true).count()) .map(|line| line.graphemes(true).count())
.max() .max()
.expect("line iterator should not be empty"); .expect("line iterator should not be empty");
let test_ascii_width = let test_ascii_width =
u8::try_from(test_ascii_width).expect("`test_ascii_width` should fit in `u8`"); u8::try_from(test_ascii_width).expect("`test_ascii_width` should fit in `u8`");
let test_ascii_height = test_ascii.split('\n').count(); let test_ascii_height = test_ascii.lines().count();
let test_ascii_height = let test_ascii_height =
u8::try_from(test_ascii_height).expect("`test_ascii_height` should fit in `u8`"); u8::try_from(test_ascii_height).expect("`test_ascii_height` should fit in `u8`");
let select_lightness = || -> Result<Lightness> { let select_lightness = || -> Result<Lightness> {
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
print_title_prompt( print_title_prompt(
option_counter, option_counter,
"Let's adjust the color brightness!", "Let's adjust the color brightness!",
color_mode, color_mode,
); )
println!( .context("failed to print title prompt")?;
"The colors might be a little bit too {bright_dark} for {light_dark} mode.", writeln!(
io::stdout(),
"The colors might be a little bit too {bright_dark} for {light_dark} mode.\n",
bright_dark = match theme { bright_dark = match theme {
TerminalTheme::Light => "bright", TerminalTheme::Light => "bright",
TerminalTheme::Dark => "dark", TerminalTheme::Dark => "dark",
}, },
light_dark = theme.as_ref() light_dark = theme.as_ref()
); )
println!(); .context("failed to write message to stdout")?;
let color_align = ColorAlignment::Horizontal { fore_back: None }; let color_align = ColorAlignment::Horizontal { fore_back: None };
@ -658,7 +673,7 @@ pub fn create_config(
theme, theme,
) )
.expect("recoloring test ascii should not fail"); .expect("recoloring test ascii should not fail");
asc.split('\n').map(ToOwned::to_owned).collect() asc.lines().map(ToOwned::to_owned).collect()
}) })
.collect(); .collect();
for i in 0..usize::from(test_ascii_height) { for i in 0..usize::from(test_ascii_height) {
@ -666,19 +681,19 @@ pub fn create_config(
for lines in &row { for lines in &row {
line.push(&*lines[i]); line.push(&*lines[i]);
} }
printc(line.join(" "), color_mode) printc(line.join(" "), color_mode).context("failed to print test ascii line")?;
.expect("test ascii line should not contain invalid color codes");
} }
} }
loop { loop {
println!(); writeln!(
println!( io::stdout(),
"Which brightness level looks the best? (Default: {default:.0}% for {light_dark} \ "\nWhich brightness level looks the best? (Default: {default:.0}% for \
mode)", {light_dark} mode)",
default = f32::from(default_lightness) * 100.0, default = f32::from(default_lightness) * 100.0,
light_dark = theme.as_ref() light_dark = theme.as_ref()
); )
.context("failed to write prompt to stdout")?;
let lightness = input(Some("> ")) let lightness = input(Some("> "))
.context("failed to read input")? .context("failed to read input")?
.trim() .trim()
@ -695,7 +710,7 @@ pub fn create_config(
as 45%, .45, or 45", as 45%, .45, or 45",
color_mode, color_mode,
) )
.expect("message should not contain invalid color codes"); .context("failed to print message")?;
}, },
} }
} }
@ -758,8 +773,7 @@ pub fn create_config(
// Loop for random rolling // Loop for random rolling
let mut rng = fastrand::Rng::new(); let mut rng = fastrand::Rng::new();
loop { loop {
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
// Random color schemes // Random color schemes
let mut preset_indices: Vec<PresetIndexedColor> = let mut preset_indices: Vec<PresetIndexedColor> =
@ -814,22 +828,25 @@ pub fn create_config(
colors, colors,
}) })
})); }));
let asciis = arrangements.iter().map(|(k, ca)| { let asciis: Vec<Vec<String>> = arrangements
let mut v: Vec<String> = ca .iter()
.recolor_ascii(&asc, &color_profile, color_mode, theme) .map(|(k, ca)| {
.expect("recoloring ascii should not fail") let mut v: Vec<String> = ca
.split('\n') .recolor_ascii(&asc, &color_profile, color_mode, theme)
.map(ToOwned::to_owned) .context("failed to recolor ascii")?
.collect(); .lines()
v.push(format!( .map(ToOwned::to_owned)
"{k:^asc_width$}", .collect();
asc_width = usize::from(asc_width) v.push(format!(
)); "{k:^asc_width$}",
v asc_width = usize::from(asc_width)
}); ));
Ok(v)
})
.collect::<Result<_>>()?;
for row in &asciis.chunks(usize::from(ascii_per_row)) { for row in &asciis.into_iter().chunks(usize::from(ascii_per_row)) {
let row: Vec<Vec<String>> = row.into_iter().collect(); let row: Vec<Vec<String>> = row.collect();
// Print by row // Print by row
for i in 0..usize::from(asc_lines) + 1 { for i in 0..usize::from(asc_lines) + 1 {
@ -837,27 +854,27 @@ pub fn create_config(
for lines in &row { for lines in &row {
line.push(&*lines[i]); line.push(&*lines[i]);
} }
printc(line.join(" "), color_mode) printc(line.join(" "), color_mode).context("failed to print ascii line")?;
.expect("ascii line should not contain invalid color codes");
} }
writeln!(io::stdout()).context("failed to write to stdout")?;
println!();
} }
print_title_prompt( print_title_prompt(
option_counter, option_counter,
"Let's choose a color arrangement!", "Let's choose a color arrangement!",
color_mode, color_mode,
); )
println!( .context("failed to print title prompt")?;
writeln!(
io::stdout(),
"You can choose standard horizontal or vertical alignment, or use one of the random \ "You can choose standard horizontal or vertical alignment, or use one of the random \
color schemes." color schemes.\nYou can type \"roll\" to randomize again.\n"
); )
println!(r#"You can type "roll" to randomize again."#); .context("failed to write message to stdout")?;
println!();
let mut opts: Vec<Cow<str>> = ["horizontal", "vertical", "roll"].map(Into::into).into(); let mut opts: Vec<Cow<str>> = ["horizontal", "vertical", "roll"].map(Into::into).into();
opts.extend((0..random_count).map(|i| format!("random{i}").into())); opts.extend((0..random_count).map(|i| format!("random{i}").into()));
let choice = literal_input("Your choice?", &opts[..], "horizontal", true, color_mode) let choice = literal_input("Your choice?", &opts[..], "horizontal", true, color_mode)
.context("failed to ask for choice input")
.context("failed to select color alignment")?; .context("failed to select color alignment")?;
if choice == "roll" { if choice == "roll" {
@ -891,9 +908,9 @@ pub fn create_config(
// 6. Select *fetch backend // 6. Select *fetch backend
let select_backend = || -> Result<Backend> { let select_backend = || -> Result<Backend> {
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes"); print_title_prompt(option_counter, "Select a *fetch backend", color_mode)
print_title_prompt(option_counter, "Select a *fetch backend", color_mode); .context("failed to print title prompt")?;
// Check if fastfetch is installed // Check if fastfetch is installed
let fastfetch_path = fastfetch_path().context("failed to get fastfetch path")?; let fastfetch_path = fastfetch_path().context("failed to get fastfetch path")?;
@ -906,7 +923,7 @@ pub fn create_config(
"- &bneofetch&r: Written in bash, &nbest compatibility&r on Unix systems", "- &bneofetch&r: Written in bash, &nbest compatibility&r on Unix systems",
color_mode, color_mode,
) )
.expect("message should not contain invalid color codes"); .context("failed to print message")?;
printc( printc(
format!( format!(
"- &bfastfetch&r: Written in C, &nbest performance&r {installed_not_installed}", "- &bfastfetch&r: Written in C, &nbest performance&r {installed_not_installed}",
@ -916,19 +933,18 @@ pub fn create_config(
), ),
color_mode, color_mode,
) )
.expect("message should not contain invalid color codes"); .context("failed to print message")?;
#[cfg(feature = "macchina")] #[cfg(feature = "macchina")]
printc( printc(
format!( format!(
"- &bmacchina&r: Written in Rust, &nbest performance&r {installed_not_installed}", "- &bmacchina&r: Written in Rust, &nbest performance&r {installed_not_installed}\n",
installed_not_installed = macchina_path installed_not_installed = macchina_path
.map(|path| format!("&a(Installed at {path})", path = path.display())) .map(|path| format!("&a(Installed at {path})", path = path.display()))
.unwrap_or_else(|| "&c(Not installed)".to_owned()) .unwrap_or_else(|| "&c(Not installed)".to_owned())
), ),
color_mode, color_mode,
) )
.expect("message should not contain invalid color codes"); .context("failed to print message")?;
println!();
let choice = literal_input( let choice = literal_input(
"Your choice?", "Your choice?",
@ -936,7 +952,8 @@ pub fn create_config(
backend.as_ref(), backend.as_ref(),
true, true,
color_mode, color_mode,
)?; )
.context("failed to ask for choice input")?;
Ok(choice.parse().expect("selected backend should be valid")) Ok(choice.parse().expect("selected backend should be valid"))
}; };
@ -949,8 +966,7 @@ pub fn create_config(
); );
// Create config // Create config
clear_screen(Some(&title), color_mode, debug_mode) clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
.expect("title should not contain invalid color codes");
let config = Config { let config = Config {
preset, preset,
mode: color_mode, mode: color_mode,
@ -965,7 +981,8 @@ pub fn create_config(
debug!(?config, "created config"); debug!(?config, "created config");
// Save config // Save config
let save = literal_input("Save config?", &["y", "n"], "y", true, color_mode)?; let save = literal_input("Save config?", &["y", "n"], "y", true, color_mode)
.context("failed to ask for choice input")?;
if save == "y" { if save == "y" {
let file = File::options() let file = File::options()
.write(true) .write(true)

View file

@ -408,27 +408,25 @@ pub fn printc<S>(msg: S, mode: AnsiMode) -> Result<()>
where where
S: AsRef<str>, S: AsRef<str>,
{ {
let msg = msg.as_ref(); writeln!(
io::stdout(),
println!(
"{msg}", "{msg}",
msg = color(format!("{msg}&r"), mode).context("failed to color message")? msg = color(format!("{msg}&r", msg = msg.as_ref()), mode)
); .context("failed to color message")?
)
Ok(()) .context("failed to write message to stdout")
} }
/// Clears screen using ANSI escape codes. /// Clears screen using ANSI escape codes.
pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug_mode: bool) -> Result<()> { pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug_mode: bool) -> Result<()> {
if !debug_mode { if !debug_mode {
print!("\x1b[2J\x1b[H"); write!(io::stdout(), "\x1b[2J\x1b[H")
io::stdout().flush()?; .and_then(|_| io::stdout().flush())
.context("failed to write clear screen sequence to stdout")?;
} }
if let Some(title) = title { if let Some(title) = title {
println!(); printc(format!("\n{title}\n"), mode).context("failed to print title")?;
printc(title, mode).context("failed to color title")?;
println!();
} }
Ok(()) Ok(())

View file

@ -5,7 +5,7 @@ use std::fmt::Write as _;
use std::fs; use std::fs;
#[cfg(windows)] #[cfg(windows)]
use std::io; use std::io;
use std::io::Write as _; use std::io::{self, Write as _};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::sync::OnceLock; use std::sync::OnceLock;
@ -121,7 +121,7 @@ impl ColorAlignment {
.with_length(length) .with_length(length)
.context("failed to spread color profile to length")? .context("failed to spread color profile to length")?
}; };
asc.split('\n') asc.lines()
.enumerate() .enumerate()
.map(|(i, line)| { .map(|(i, line)| {
let line = line.replace( let line = line.replace(
@ -165,7 +165,7 @@ impl ColorAlignment {
let asc = { let asc = {
let ac = NEOFETCH_COLORS_AC let ac = NEOFETCH_COLORS_AC
.get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap()); .get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap());
asc.split('\n') asc.lines()
.map(|line| { .map(|line| {
let mut matches = ac.find_iter(line).peekable(); let mut matches = ac.find_iter(line).peekable();
let mut dst = String::new(); let mut dst = String::new();
@ -261,8 +261,6 @@ impl ColorAlignment {
ac.replace_all(asc.as_ref(), &REPLACEMENTS) ac.replace_all(asc.as_ref(), &REPLACEMENTS)
}; };
let lines: Vec<_> = asc.split('\n').collect();
// Add new colors // Add new colors
match self { match self {
Self::Horizontal { .. } => { Self::Horizontal { .. } => {
@ -272,8 +270,7 @@ impl ColorAlignment {
.with_length(length) .with_length(length)
.context("failed to spread color profile to length")? .context("failed to spread color profile to length")?
}; };
lines asc.lines()
.into_iter()
.enumerate() .enumerate()
.map(|(i, line)| { .map(|(i, line)| {
let fore = colors[i] let fore = colors[i]
@ -282,8 +279,8 @@ impl ColorAlignment {
}) })
.join("\n") .join("\n")
}, },
Self::Vertical { .. } => lines Self::Vertical { .. } => asc
.into_iter() .lines()
.map(|line| { .map(|line| {
let line = color_profile let line = color_profile
.color_text( .color_text(
@ -327,10 +324,7 @@ impl ColorAlignment {
}; };
// Reset colors at end of each line to prevent color bleeding // Reset colors at end of each line to prevent color bleeding
let asc = asc let asc = asc.lines().map(|line| format!("{line}{reset}")).join("\n");
.split('\n')
.map(|line| format!("{line}{reset}"))
.join("\n");
asc asc
}, },
@ -425,12 +419,16 @@ where
}; };
if let Some(selected) = find_selection(&selection, options) { if let Some(selected) = find_selection(&selection, options) {
println!(); writeln!(io::stdout()).context("failed to write to stdout")?;
return Ok(selected); return Ok(selected);
} else { } else {
let options_text = options.iter().map(AsRef::as_ref).join("|"); let options_text = options.iter().map(AsRef::as_ref).join("|");
println!("Invalid selection! {selection} is not one of {options_text}"); writeln!(
io::stdout(),
"Invalid selection! {selection} is not one of {options_text}"
)
.context("failed to write message to stdout")?;
} }
} }
@ -657,12 +655,12 @@ where
}; };
let width = asc let width = asc
.split('\n') .lines()
.map(|line| line.graphemes(true).count()) .map(|line| line.graphemes(true).count())
.max() .max()
.expect("line iterator should not be empty"); .expect("line iterator should not be empty");
let width = u8::try_from(width).expect("`width` should fit in `u8`"); let width = u8::try_from(width).expect("`width` should fit in `u8`");
let height = asc.split('\n').count(); let height = asc.lines().count();
let height = u8::try_from(height).expect("`height` should fit in `u8`"); let height = u8::try_from(height).expect("`height` should fit in `u8`");
(width, height) (width, height)
@ -677,7 +675,7 @@ where
let (w, _) = ascii_size(asc); let (w, _) = ascii_size(asc);
asc.split('\n') asc.lines()
.map(|line| { .map(|line| {
let (line_w, _) = ascii_size(line); let (line_w, _) = ascii_size(line);
let pad = " ".repeat(usize::from(w - line_w)); let pad = " ".repeat(usize::from(w - line_w));
@ -699,7 +697,7 @@ where
let mut last = None; let mut last = None;
Ok(asc Ok(asc
.split('\n') .lines()
.map(|line| { .map(|line| {
let mut new = String::new(); let mut new = String::new();
let mut matches = ac.find_iter(line).peekable(); let mut matches = ac.find_iter(line).peekable();

View file

@ -28,22 +28,22 @@ const NOTICE: &str = "Press enter to continue";
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn start_animation(color_mode: AnsiMode) -> Result<()> { pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
let key_pressed = Arc::new(AtomicBool::new(false)); let key_pressed = Arc::new(AtomicBool::new(false));
let mut input: String = String::new();
// TODO: use non-blocking I/O; no need for another thread // TODO: use non-blocking I/O; no need for another thread
let _handle = thread::spawn({ let _handle = thread::spawn({
let key_pressed = Arc::clone(&key_pressed); let key_pressed = Arc::clone(&key_pressed);
move || { move || {
loop { loop {
match io::stdin().read_line(&mut input) { match io::stdin().lines().next() {
Ok(0) => { Some(Ok(_)) => {
// Ignore EOF
},
Ok(_) => {
key_pressed.store(true, Ordering::Release); key_pressed.store(true, Ordering::Release);
break; break;
}, },
Err(err) => { Some(Err(err)) => {
eprintln!("failed to read line from standard input: {err}"); eprintln!("failed to read line from stdin: {err}");
},
None => {
// EOF
}, },
} }
} }
@ -51,12 +51,12 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
}); });
let text = &TEXT_ASCII[1..TEXT_ASCII.len() - 1]; let text = &TEXT_ASCII[1..TEXT_ASCII.len() - 1];
let text_lines: Vec<&str> = text.split('\n').collect(); let text_lines: Vec<&str> = text.lines().collect();
let text_height: usize = text_lines.len(); let text_height: usize = text_lines.len();
let text_width: usize = text_lines[0].len(); let text_width: usize = text_lines[0].len();
let speed = 2; let speed = 2;
let frame_delay: Duration = Duration::from_secs_f32(1.0 / 25.0); let frame_delay = Duration::from_secs_f32(1.0 / 25.0);
let mut frame: usize = 0; let mut frame: usize = 0;
@ -149,7 +149,7 @@ pub fn start_animation(color_mode: AnsiMode) -> Result<()> {
write!(io::stdout(), "{buf}") write!(io::stdout(), "{buf}")
.and_then(|_| io::stdout().flush()) .and_then(|_| io::stdout().flush())
.context("failed to write `buf` to stdout")?; .context("failed to write to stdout")?;
Ok(()) Ok(())
}; };

View file

@ -28,26 +28,16 @@ where
S: AsRef<str>, S: AsRef<str>,
{ {
if let Some(prompt) = prompt { if let Some(prompt) = prompt {
print!("{prompt}", prompt = prompt.as_ref()); write!(io::stdout(), "{prompt}", prompt = prompt.as_ref())
io::stdout().flush()?; .and_then(|_| io::stdout().flush())
.context("failed to write prompt to stdout")?;
} }
let mut buf = String::new();
io::stdin() io::stdin()
.read_line(&mut buf) .lines()
.context("failed to read line from standard input")?; .next()
let buf = { .unwrap_or_else(|| Ok(String::new()))
#[cfg(not(windows))] .context("failed to read line from stdin")
{
buf.strip_suffix('\n').unwrap_or(&buf)
}
#[cfg(windows)]
{
buf.strip_suffix("\r\n").unwrap_or(&buf)
}
};
Ok(buf.to_owned())
} }
/// Finds a command in `PATH`. /// Finds a command in `PATH`.