feat: finalize search UX and release hardening

This commit is contained in:
stoorps 2026-03-21 16:53:33 +00:00
parent c63b2917da
commit 34f9543a78
Signed by: stoorps
SSH key fingerprint: SHA256:AZlPfu9hTu042EGtZElmDQoy+KvMOeShLDan/fYLoNI
44 changed files with 4983 additions and 94 deletions

View file

@ -0,0 +1,130 @@
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize)]
pub struct CliConfig {
#[serde(default)]
pub search: SearchConfig,
#[serde(default)]
pub theme: ThemeConfig,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
pub struct SearchConfig {
#[serde(default = "default_true")]
pub bottom_to_top: bool,
#[serde(default)]
pub skip_confirmation: bool,
}
impl Default for SearchConfig {
fn default() -> Self {
Self {
bottom_to_top: true,
skip_confirmation: false,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
pub struct ThemeConfig {
#[serde(default = "default_accent")]
pub accent: String,
#[serde(default = "default_accent_secondary")]
pub accent_secondary: String,
#[serde(default = "default_dim")]
pub dim: String,
}
impl Default for ThemeConfig {
fn default() -> Self {
Self {
accent: default_accent(),
accent_secondary: default_accent_secondary(),
dim: default_dim(),
}
}
}
pub fn load() -> Result<CliConfig, ConfigError> {
load_from_path(&default_path())
}
pub fn load_from_path(path: &Path) -> Result<CliConfig, ConfigError> {
match fs::read_to_string(path) {
Ok(contents) => toml::from_str(&contents).map_err(|source| ConfigError::Parse {
path: path.to_path_buf(),
source,
}),
Err(source) if source.kind() == std::io::ErrorKind::NotFound => Ok(CliConfig::default()),
Err(source) => Err(ConfigError::Read {
path: path.to_path_buf(),
source,
}),
}
}
pub fn default_path() -> PathBuf {
if let Some(path) = env::var_os("AIM_CONFIG_PATH") {
return PathBuf::from(path);
}
if let Some(config_home) = env::var_os("XDG_CONFIG_HOME") {
return PathBuf::from(config_home).join("aim/config.toml");
}
let home = env::var_os("HOME").unwrap_or_else(|| ".".into());
PathBuf::from(home).join(".config/aim/config.toml")
}
#[derive(Debug)]
pub enum ConfigError {
Read {
path: PathBuf,
source: std::io::Error,
},
Parse {
path: PathBuf,
source: toml::de::Error,
},
}
fn default_true() -> bool {
true
}
fn default_accent() -> String {
"#b388ff".to_owned()
}
fn default_accent_secondary() -> String {
"#d5c2ff".to_owned()
}
fn default_dim() -> String {
"#7f7396".to_owned()
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Read { path, source } => {
write!(
formatter,
"failed to read config {}: {source}",
path.display()
)
}
Self::Parse { path, source } => {
write!(
formatter,
"failed to parse config {}: {source}",
path.display()
)
}
}
}
}
impl std::error::Error for ConfigError {}