Prompt for preset selection
This commit is contained in:
parent
4861744b50
commit
114de9be12
4 changed files with 171 additions and 20 deletions
|
@ -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<Option<Config>> {
|
|||
///
|
||||
/// The config is automatically stored to file.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<Config> {
|
||||
fn create_config(
|
||||
distro: Option<&String>,
|
||||
path: &Path,
|
||||
use_overlay: bool,
|
||||
debug_mode: bool,
|
||||
) -> Result<Config> {
|
||||
// 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<C
|
|||
)
|
||||
.expect("logo should not contain invalid color codes");
|
||||
let mut title = format!("Welcome to {logo} Let's set up some colors first.");
|
||||
clear_screen(Some(&title), color_mode, debug_)
|
||||
clear_screen(Some(&title), color_mode, debug_mode)
|
||||
.expect("title should not contain invalid color codes");
|
||||
|
||||
let mut option_counter: u8 = 1;
|
||||
|
@ -239,12 +245,13 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<C
|
|||
|
||||
//////////////////////////////
|
||||
// 1. Select color system
|
||||
|
||||
let select_color_system = || -> 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<C
|
|||
|
||||
//////////////////////////////
|
||||
// 2. Select light/dark mode
|
||||
|
||||
let select_light_dark = || -> 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<C
|
|||
|
||||
//////////////////////////////
|
||||
// 3. Choose preset
|
||||
|
||||
// Create flag lines
|
||||
let mut flags = Vec::with_capacity(Preset::COUNT);
|
||||
let spacing = {
|
||||
|
@ -372,7 +381,7 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<C
|
|||
};
|
||||
|
||||
let print_flag_page = |page, page_num| {
|
||||
clear_screen(Some(&title), color_mode, debug_)
|
||||
clear_screen(Some(&title), color_mode, debug_mode)
|
||||
.expect("title should not contain invalid color codes");
|
||||
print_title_prompt(option_counter, "Let's choose a flag!", color_mode);
|
||||
printc("Available flag presets:", color_mode)
|
||||
|
@ -388,17 +397,77 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<C
|
|||
println!();
|
||||
};
|
||||
|
||||
let page: u8 = 0;
|
||||
let color_profile: ColorProfile;
|
||||
let preset_rainbow = Preset::Rainbow
|
||||
.color_profile()
|
||||
.with_lightness_dl(Config::default_lightness(theme), theme, use_overlay)
|
||||
.color_text(
|
||||
"preset",
|
||||
color_mode,
|
||||
ForegroundBackground::Foreground,
|
||||
false,
|
||||
)
|
||||
.expect("coloring text with rainbow preset should not fail");
|
||||
|
||||
let mut page: u8 = 0;
|
||||
loop {
|
||||
print_flag_page(&pages[page as usize], page);
|
||||
|
||||
todo!();
|
||||
let mut opts = Vec::from(<Preset as VariantNames>::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
|
||||
|
|
|
@ -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<S>(msg: S, mode: AnsiMode) -> Result<()>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<str>,
|
||||
{
|
||||
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::<Vec<_>>()
|
||||
.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<Option<PathBuf>> {
|
||||
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
||||
|
|
Loading…
Reference in a new issue