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::borrow::Cow;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::fs::{self, File};
|
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 std::path::Path;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
@ -10,8 +10,8 @@ use hyfetch::color_util::{clear_screen, color, printc, ForegroundBackground, The
|
||||||
use hyfetch::models::Config;
|
use hyfetch::models::Config;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use hyfetch::neofetch_util::ensure_git_bash;
|
use hyfetch::neofetch_util::ensure_git_bash;
|
||||||
use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, ColorAlignment};
|
use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, literal_input, ColorAlignment};
|
||||||
use hyfetch::presets::{AssignLightness, Preset};
|
use hyfetch::presets::{AssignLightness, ColorProfile, Preset};
|
||||||
use hyfetch::types::{AnsiMode, LightDark};
|
use hyfetch::types::{AnsiMode, LightDark};
|
||||||
use hyfetch::utils::get_cache_path;
|
use hyfetch::utils::get_cache_path;
|
||||||
use palette::Srgb;
|
use palette::Srgb;
|
||||||
|
@ -45,14 +45,14 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = if options.config {
|
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")?
|
.context("failed to create config")?
|
||||||
} else if let Some(config) =
|
} else if let Some(config) =
|
||||||
read_config(&options.config_file).context("failed to read config")?
|
read_config(&options.config_file).context("failed to read config")?
|
||||||
{
|
{
|
||||||
config
|
config
|
||||||
} else {
|
} else {
|
||||||
create_config(distro, &options.config_file, options.debug)
|
create_config(distro, &options.config_file, options.overlay, options.debug)
|
||||||
.context("failed to create config")?
|
.context("failed to create config")?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
if options.ask_exit {
|
if options.ask_exit {
|
||||||
print!("Press any key to exit...");
|
print!("Press any key to exit...");
|
||||||
|
io::stdout().flush()?;
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
io::stdin()
|
io::stdin()
|
||||||
.read_line(&mut buf)
|
.read_line(&mut buf)
|
||||||
|
@ -172,7 +173,12 @@ fn read_config(path: &Path) -> Result<Option<Config>> {
|
||||||
///
|
///
|
||||||
/// The config is automatically stored to file.
|
/// The config is automatically stored to file.
|
||||||
#[tracing::instrument(level = "debug")]
|
#[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,
|
// Detect terminal environment (doesn't work for all terminal emulators,
|
||||||
// especially on Windows)
|
// especially on Windows)
|
||||||
let det_bg = match termbg::rgb(std::time::Duration::from_millis(100)) {
|
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");
|
.expect("logo should not contain invalid color codes");
|
||||||
let mut title = format!("Welcome to {logo} Let's set up some colors first.");
|
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");
|
.expect("title should not contain invalid color codes");
|
||||||
|
|
||||||
let mut option_counter: u8 = 1;
|
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
|
// 1. Select color system
|
||||||
|
|
||||||
let select_color_system = || -> Result<(AnsiMode, &str)> {
|
let select_color_system = || -> Result<(AnsiMode, &str)> {
|
||||||
if det_ansi == Some(AnsiMode::Rgb) {
|
if det_ansi == Some(AnsiMode::Rgb) {
|
||||||
return Ok((AnsiMode::Rgb, "Detected color mode"));
|
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");
|
.expect("title should not contain invalid color codes");
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -274,12 +281,13 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<C
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// 2. Select light/dark mode
|
// 2. Select light/dark mode
|
||||||
|
|
||||||
let select_light_dark = || -> Result<(LightDark, &str)> {
|
let select_light_dark = || -> Result<(LightDark, &str)> {
|
||||||
if let Some(det_bg) = det_bg {
|
if let Some(det_bg) = det_bg {
|
||||||
return Ok((det_bg.theme(), "Detected background color"));
|
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");
|
.expect("title should not contain invalid color codes");
|
||||||
|
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -295,6 +303,7 @@ fn create_config(distro: Option<&String>, path: &Path, debug_: bool) -> Result<C
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// 3. Choose preset
|
// 3. Choose preset
|
||||||
|
|
||||||
// Create flag lines
|
// Create flag lines
|
||||||
let mut flags = Vec::with_capacity(Preset::COUNT);
|
let mut flags = Vec::with_capacity(Preset::COUNT);
|
||||||
let spacing = {
|
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| {
|
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");
|
.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);
|
||||||
printc("Available flag presets:", 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!();
|
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 {
|
loop {
|
||||||
print_flag_page(&pages[page as usize], page);
|
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!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_tracing_subsriber(debug: bool) -> Result<()> {
|
fn init_tracing_subsriber(debug_mode: bool) -> Result<()> {
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
@ -432,7 +501,7 @@ fn init_tracing_subsriber(debug: bool) -> Result<()> {
|
||||||
Targets::new().with_default(Subscriber::DEFAULT_MAX_LEVEL)
|
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)
|
targets.with_target(env!("CARGO_CRATE_NAME"), Level::DEBUG)
|
||||||
} else {
|
} else {
|
||||||
targets
|
targets
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::io::{self, Write};
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
@ -337,6 +338,7 @@ where
|
||||||
Ok(dst)
|
Ok(dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prints with color.
|
||||||
pub fn printc<S>(msg: S, mode: AnsiMode) -> Result<()>
|
pub fn printc<S>(msg: S, mode: AnsiMode) -> Result<()>
|
||||||
where
|
where
|
||||||
S: AsRef<str>,
|
S: AsRef<str>,
|
||||||
|
@ -351,9 +353,11 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug: bool) -> Result<()> {
|
/// Clears screen using ANSI escape codes.
|
||||||
if !debug {
|
pub fn clear_screen(title: Option<&str>, mode: AnsiMode, debug_mode: bool) -> Result<()> {
|
||||||
|
if !debug_mode {
|
||||||
print!("\x1b[2J\x1b[H");
|
print!("\x1b[2J\x1b[H");
|
||||||
|
io::stdout().flush()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(title) = title {
|
if let Some(title) = title {
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn default_lightness(term: &LightDark) -> Lightness {
|
pub fn default_lightness(term: LightDark) -> Lightness {
|
||||||
match term {
|
match term {
|
||||||
LightDark::Dark => {
|
LightDark::Dark => {
|
||||||
Lightness::new(0.65).expect("default lightness should not be invalid")
|
Lightness::new(0.65).expect("default lightness should not be invalid")
|
||||||
|
@ -34,7 +34,7 @@ impl Config {
|
||||||
|
|
||||||
pub fn lightness(&self) -> Lightness {
|
pub fn lightness(&self) -> 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::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::Write;
|
use std::io::{self, Write};
|
||||||
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;
|
||||||
|
@ -17,7 +17,8 @@ use tracing::debug;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::color_util::{
|
use crate::color_util::{
|
||||||
color, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, ToAnsiString,
|
color, printc, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor,
|
||||||
|
ToAnsiString,
|
||||||
};
|
};
|
||||||
use crate::distros::Distro;
|
use crate::distros::Distro;
|
||||||
use crate::presets::ColorProfile;
|
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.
|
/// Gets the absolute path of the neofetch command.
|
||||||
pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
||||||
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
||||||
|
|
Loading…
Reference in a new issue