diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index 0080c8b7..adc6ba7b 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -1,7 +1,7 @@ use std::borrow::Cow; use std::cmp; use std::fs::{self, File}; -use std::io::{self, ErrorKind, IsTerminal, Read}; +use std::io::{self, ErrorKind, IsTerminal, Read, Write}; use std::path::Path; use anyhow::{Context, Result}; @@ -10,8 +10,8 @@ use hyfetch::color_util::{clear_screen, color, printc, ForegroundBackground, The use hyfetch::models::Config; #[cfg(windows)] use hyfetch::neofetch_util::ensure_git_bash; -use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, ColorAlignment}; -use hyfetch::presets::{AssignLightness, Preset}; +use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, literal_input, ColorAlignment}; +use hyfetch::presets::{AssignLightness, ColorProfile, Preset}; use hyfetch::types::{AnsiMode, LightDark}; use hyfetch::utils::get_cache_path; use palette::Srgb; @@ -45,14 +45,14 @@ fn main() -> Result<()> { } let config = if options.config { - create_config(distro, &options.config_file, options.debug) + create_config(distro, &options.config_file, options.overlay, options.debug) .context("failed to create config")? } else if let Some(config) = read_config(&options.config_file).context("failed to read config")? { config } else { - create_config(distro, &options.config_file, options.debug) + create_config(distro, &options.config_file, options.overlay, options.debug) .context("failed to create config")? }; @@ -130,6 +130,7 @@ fn main() -> Result<()> { if options.ask_exit { print!("Press any key to exit..."); + io::stdout().flush()?; let mut buf = String::new(); io::stdin() .read_line(&mut buf) @@ -172,7 +173,12 @@ fn read_config(path: &Path) -> Result> { /// /// The config is automatically stored to file. #[tracing::instrument(level = "debug")] -fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result { +fn create_config( + distro: Option<&String>, + path: &Path, + use_overlay: bool, + debug_mode: bool, +) -> Result { // Detect terminal environment (doesn't work for all terminal emulators, // especially on Windows) let det_bg = match termbg::rgb(std::time::Duration::from_millis(100)) { @@ -209,7 +215,7 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result, path: &Path, debug_: bool) -> Result Result<(AnsiMode, &str)> { if det_ansi == Some(AnsiMode::Rgb) { return Ok((AnsiMode::Rgb, "Detected color mode")); } - clear_screen(Some(&title), color_mode, debug_) + clear_screen(Some(&title), color_mode, debug_mode) .expect("title should not contain invalid color codes"); // TODO @@ -274,12 +281,13 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result Result<(LightDark, &str)> { if let Some(det_bg) = det_bg { return Ok((det_bg.theme(), "Detected background color")); } - clear_screen(Some(&title), color_mode, debug_) + clear_screen(Some(&title), color_mode, debug_mode) .expect("title should not contain invalid color codes"); todo!() @@ -295,6 +303,7 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result, path: &Path, debug_: bool) -> Result, path: &Path, debug_: bool) -> Result::VARIANTS); + if page < num_pages - 1 { + opts.push("next"); + } + if page > 0 { + opts.push("prev"); + } + println!("Enter 'next' to go to the next page and 'prev' to go to the previous page."); + let preset = literal_input( + format!("Which {preset_rainbow} do you want to use? "), + &opts[..], + Preset::Rainbow.into(), + false, + color_mode, + ) + .context("failed to select preset")?; + if preset == "next" { + page += 1; + } else if preset == "prev" { + page -= 1; + } else { + let preset: Preset = preset.parse().expect("selected preset should be valid"); + debug!(?preset, "selected preset"); + color_profile = preset.color_profile(); + { + let preset_name: &'static str = preset.into(); + let preset_colored_name = color_profile + .with_lightness_dl(Config::default_lightness(theme), theme, use_overlay) + .color_text( + preset_name, + color_mode, + ForegroundBackground::Foreground, + false, + ) + .expect("coloring text with selected preset should not fail"); + update_title( + &mut title, + &mut option_counter, + "Selected flag", + &preset_colored_name, + ); + } + break; + } } + ////////////////////////////// + // 4. Dim/lighten colors + + // TODO + todo!() } -fn init_tracing_subsriber(debug: bool) -> Result<()> { +fn init_tracing_subsriber(debug_mode: bool) -> Result<()> { use std::env; use std::str::FromStr; @@ -432,7 +501,7 @@ fn init_tracing_subsriber(debug: bool) -> Result<()> { Targets::new().with_default(Subscriber::DEFAULT_MAX_LEVEL) }, }; - let targets = if debug { + let targets = if debug_mode { targets.with_target(env!("CARGO_CRATE_NAME"), Level::DEBUG) } else { targets diff --git a/crates/hyfetch/src/color_util.rs b/crates/hyfetch/src/color_util.rs index 541cc0e8..17c2dee2 100644 --- a/crates/hyfetch/src/color_util.rs +++ b/crates/hyfetch/src/color_util.rs @@ -1,3 +1,4 @@ +use std::io::{self, Write}; use std::num::{ParseFloatError, ParseIntError}; use std::str::FromStr; use std::sync::OnceLock; @@ -337,6 +338,7 @@ where Ok(dst) } +/// Prints with color. pub fn printc(msg: S, mode: AnsiMode) -> Result<()> where S: AsRef, @@ -351,9 +353,11 @@ where Ok(()) } -pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug: bool) -> Result<()> { - if !debug { +/// Clears screen using ANSI escape codes. +pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug_mode: bool) -> Result<()> { + if !debug_mode { print!("\x1b[2J\x1b[H"); + io::stdout().flush()?; } if let Some(title) = title { diff --git a/crates/hyfetch/src/models.rs b/crates/hyfetch/src/models.rs index 10265421..63c67c6f 100644 --- a/crates/hyfetch/src/models.rs +++ b/crates/hyfetch/src/models.rs @@ -21,7 +21,7 @@ pub struct Config { } impl Config { - pub fn default_lightness(term: &LightDark) -> Lightness { + pub fn default_lightness(term: LightDark) -> Lightness { match term { LightDark::Dark => { Lightness::new(0.65).expect("default lightness should not be invalid") @@ -34,7 +34,7 @@ impl Config { pub fn lightness(&self) -> Lightness { self.lightness - .unwrap_or_else(|| Self::default_lightness(&self.light_dark)) + .unwrap_or_else(|| Self::default_lightness(self.light_dark)) } } diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index ac38951b..fba8f9b3 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::ffi::OsStr; -use std::io::Write; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::OnceLock; @@ -17,7 +17,8 @@ use tracing::debug; use unicode_segmentation::UnicodeSegmentation; use crate::color_util::{ - color, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, ToAnsiString, + color, printc, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, + ToAnsiString, }; use crate::distros::Distro; use crate::presets::ColorProfile; @@ -285,6 +286,83 @@ impl ColorAlignment { } } +/// Asks the user to provide an input among a list of options. +pub fn literal_input<'a, S>( + prompt: S, + options: &[&'a str], + default: &str, + show_options: bool, + color_mode: AnsiMode, +) -> Result<&'a str> +where + S: AsRef, +{ + let prompt = prompt.as_ref(); + + if show_options { + let options_text = options + .iter() + .map(|&o| { + if o == default { + format!("&l&n{o}&L&N") + } else { + o.to_owned() + } + }) + .collect::>() + .join("|"); + printc(format!("{prompt} ({options_text})"), color_mode) + .context("failed to print input prompt")?; + } else { + printc(format!("{prompt} (default: {default})"), color_mode) + .context("failed to print input prompt")?; + } + + let find_selection = |sel: &str| { + if sel.is_empty() { + return None; + } + + // Find exact match + if let Some(selected) = options.iter().find(|&&o| o.to_lowercase() == sel) { + return Some(selected); + } + + // Find starting abbreviation + if let Some(selected) = options.iter().find(|&&o| o.to_lowercase().starts_with(sel)) { + return Some(selected); + } + + None + }; + + loop { + let mut buf = String::new(); + print!("> "); + io::stdout().flush()?; + io::stdin() + .read_line(&mut buf) + .context("failed to read line from input")?; + let selection = { + let selection = buf.trim_end_matches(&['\r', '\n']); + if selection.is_empty() { + default.to_owned() + } else { + selection.to_lowercase() + } + }; + + if let Some(selected) = find_selection(&selection) { + println!(); + + return Ok(selected); + } else { + let options_text = options.join("|"); + println!("Invalid selection! {selection} is not one of {options_text}"); + } + } +} + /// Gets the absolute path of the neofetch command. pub fn neofetch_path() -> Result> { if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {