diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index 220b0b10..774fb1e5 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -162,11 +162,8 @@ fn main() -> Result<()> { }; let color_align = if fore_back.is_some() { match config.color_align { - ca @ ColorAlignment::Horizontal { .. } | ca @ ColorAlignment::Vertical { .. } => { - ca.with_fore_back(fore_back).context( - "failed to create color alignment with foreground-background configuration", - )? - }, + ColorAlignment::Horizontal { .. } => ColorAlignment::Horizontal { fore_back }, + ColorAlignment::Vertical { .. } => ColorAlignment::Vertical { fore_back }, ca @ ColorAlignment::Custom { .. } => ca, } } else { @@ -545,12 +542,12 @@ fn create_config( fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) { for i in 0..4 { - let mut line = String::new(); + let mut line = Vec::new(); for flag in row { - line.push_str(&flag[i]); - line.push_str(" "); + line.push(&*flag[i]); } - printc(line, color_mode).expect("flag line should not contain invalid color codes"); + printc(line.join(" "), color_mode) + .expect("flag line should not contain invalid color codes"); } println!(); } @@ -698,12 +695,11 @@ fn create_config( }) .collect(); for i in 0..usize::from(test_ascii_height) { - let mut line = String::new(); + let mut line = Vec::new(); for lines in &row { - line.push_str(&lines[i]); - line.push_str(" "); + line.push(&*lines[i]); } - printc(line, color_mode) + printc(line.join(" "), color_mode) .expect("test ascii line should not contain invalid color codes"); } } @@ -783,7 +779,7 @@ fn create_config( let ascii_per_row: u8 = ascii_per_row .try_into() .expect("`ascii_per_row` should fit in `u8`"); - let ascii_rows = cmp::max(1, (term_h - 8) / u16::from(asc_lines)); + let ascii_rows = cmp::max(1, (term_h - 8) / (u16::from(asc_lines) + 1)); let ascii_rows: u8 = ascii_rows .try_into() .expect("`ascii_rows` should fit in `u8`"); @@ -793,18 +789,8 @@ fn create_config( // Displays horizontal and vertical arrangements in the first iteration, but // hide them in later iterations let hv_arrangements = [ - ( - "Horizontal", - ColorAlignment::Horizontal { fore_back: None } - .with_fore_back(fore_back) - .expect("horizontal color alignment should not be invalid"), - ), - ( - "Vertical", - ColorAlignment::Vertical { fore_back: None } - .with_fore_back(fore_back) - .expect("vertical color alignment should not be invalid"), - ), + ("Horizontal", ColorAlignment::Horizontal { fore_back }), + ("Vertical", ColorAlignment::Vertical { fore_back }), ]; let mut arrangements: IndexMap, ColorAlignment> = hv_arrangements.map(|(k, ca)| (k.into(), ca)).into(); @@ -892,12 +878,11 @@ fn create_config( // Print by row for i in 0..usize::from(asc_lines) + 1 { - let mut line = String::new(); + let mut line = Vec::new(); for lines in &row { - line.push_str(&lines[i]); - line.push_str(" "); + line.push(&*lines[i]); } - printc(line, color_mode) + printc(line.join(" "), color_mode) .expect("ascii line should not contain invalid color codes"); } diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index 357cf956..7ae55b3f 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -1,8 +1,9 @@ use std::borrow::Cow; use std::ffi::OsStr; +use std::fmt::Write as _; #[cfg(windows)] use std::io; -use std::io::Write; +use std::io::Write as _; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::OnceLock; @@ -57,33 +58,6 @@ pub enum ColorAlignment { } impl ColorAlignment { - /// Creates a new color alignment, with the specified foreground-background - /// configuration. - pub fn with_fore_back(&self, fore_back: Option) -> Result { - match self { - Self::Horizontal { .. } => Ok(Self::Horizontal { fore_back }), - Self::Vertical { .. } => { - if fore_back.is_some() { - debug!( - "foreground-background configuration not implemented for vertical color \ - alignment; ignoring" - ); - } - Ok(Self::Vertical { fore_back: None }) - }, - Self::Custom { colors } => { - if fore_back.is_some() { - return Err(anyhow!( - "foreground-background configuration not supported for custom colors" - )); - } - Ok(Self::Custom { - colors: colors.clone(), - }) - }, - } - } - /// Uses the color alignment to recolor an ascii art. #[tracing::instrument(level = "debug", skip(asc))] pub fn recolor_ascii( @@ -101,9 +75,6 @@ impl ColorAlignment { let asc = match self { &Self::Horizontal { fore_back: Some((fore, back)), - } - | &Self::Vertical { - fore_back: Some((fore, back)), } => { let asc = fill_starting(asc) .context("failed to fill in starting neofetch color codes")?; @@ -121,43 +92,28 @@ impl ColorAlignment { .expect("foreground color should not be invalid"), ); - let lines: Vec<_> = asc.split('\n').collect(); - // Add new colors - let asc = match self { - Self::Horizontal { .. } => { - let ColorProfile { colors } = { - let length = lines.len(); - let length: u8 = - length.try_into().expect("`length` should fit in `u8`"); - color_profile - .with_length(length) - .context("failed to spread color profile to length")? - }; - lines - .into_iter() - .enumerate() - .map(|(i, line)| { - let line = line.replace( - &format!("${{c{back}}}", back = u8::from(back)), - &colors[i].to_ansi_string(color_mode, { - // note: this is "background" in the ascii art, but - // foreground text in terminal - ForegroundBackground::Foreground - }), - ); - format!("{line}{reset}") - }) - .join("\n") - }, - Self::Vertical { .. } => { - unimplemented!( - "vertical color alignment with fore and back colors not implemented" - ); - }, - _ => { - unreachable!(); - }, + let asc = { + let ColorProfile { colors } = { + let (_, length) = ascii_size(&asc); + color_profile + .with_length(length) + .context("failed to spread color profile to length")? + }; + asc.split('\n') + .enumerate() + .map(|(i, line)| { + let line = line.replace( + &format!("${{c{back}}}", back = u8::from(back)), + &colors[i].to_ansi_string(color_mode, { + // note: this is "background" in the ascii art, but + // foreground text in terminal + ForegroundBackground::Foreground + }), + ); + format!("{line}{reset}") + }) + .join("\n") }; // Remove existing colors @@ -171,6 +127,105 @@ impl ColorAlignment { asc }, + &Self::Vertical { + fore_back: Some((fore, back)), + } => { + let asc = fill_starting(asc) + .context("failed to fill in starting neofetch color codes")?; + + let color_profile = { + let (length, _) = ascii_size(&asc); + color_profile + .with_length(length) + .context("failed to spread color profile to length")? + }; + + // Apply colors + let asc = { + let ac = NEOFETCH_COLORS_AC + .get_or_init(|| AhoCorasick::new(NEOFETCH_COLOR_PATTERNS).unwrap()); + asc.split('\n') + .map(|line| { + let mut matches = ac.find_iter(line).peekable(); + let mut dst = String::new(); + let mut offset = 0; + loop { + let current = matches.next(); + let next = matches.peek(); + let (neofetch_color_idx, span, done) = match (current, next) { + (Some(m), Some(m_next)) => { + let neofetch_color_idx: NeofetchAsciiIndexedColor = line + [m.start() + 3..m.end() - 1] + .parse() + .expect("neofetch color index should be valid"); + offset += m.len(); + let mut span = m.span(); + span.start = m.end(); + span.end = m_next.start(); + (neofetch_color_idx, span, false) + }, + (Some(m), None) => { + // Last color code + let neofetch_color_idx: NeofetchAsciiIndexedColor = line + [m.start() + 3..m.end() - 1] + .parse() + .expect("neofetch color index should be valid"); + offset += m.len(); + let mut span = m.span(); + span.start = m.end(); + span.end = line.len(); + (neofetch_color_idx, span, true) + }, + (None, _) => { + // No color code in the entire line + unreachable!( + "`fill_starting` ensured each line of ascii art \ + starts with neofetch color code" + ); + }, + }; + let txt = &line[span]; + + if neofetch_color_idx == fore { + let fore = color( + match theme { + TerminalTheme::Light => "&0", + TerminalTheme::Dark => "&f", + }, + color_mode, + ) + .expect("foreground color should not be invalid"); + write!(dst, "{fore}{txt}{reset}").unwrap(); + } else if neofetch_color_idx == back { + dst.push_str( + &ColorProfile::new(Vec::from( + &color_profile.colors + [span.start - offset..span.end - offset], + )) + .color_text( + txt, + color_mode, + ForegroundBackground::Foreground, + false, + ) + .context("failed to color text using color profile")?, + ); + } else { + dst.push_str(txt); + } + + if done { + break; + } + } + Ok(dst) + }) + .collect::>>()? + .join("\n") + }; + + asc + }, Self::Horizontal { fore_back: None } | Self::Vertical { fore_back: None } => { // Remove existing colors let asc = { @@ -187,9 +242,7 @@ impl ColorAlignment { match self { Self::Horizontal { .. } => { let ColorProfile { colors } = { - let length = lines.len(); - let length: u8 = - length.try_into().expect("`length` should fit in `u8`"); + let (_, length) = ascii_size(&asc); color_profile .with_length(length) .context("failed to spread color profile to length")? @@ -215,7 +268,7 @@ impl ColorAlignment { false, ) .context("failed to color text using color profile")?; - Ok(format!("{line}{reset}")) + Ok(line) }) .collect::>>()? .join("\n"), @@ -248,6 +301,12 @@ impl ColorAlignment { ac.replace_all(&asc, &replacements) }; + // Reset colors at end of each line to prevent color bleeding + let asc = asc + .split('\n') + .map(|line| format!("{line}{reset}")) + .join("\n"); + asc }, };