diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/cli.rs | 10 | ||||
| -rw-r--r-- | src/config.rs | 73 | ||||
| -rw-r--r-- | src/lookup.rs | 51 | ||||
| -rw-r--r-- | src/main.rs | 37 | ||||
| -rw-r--r-- | src/output.rs | 13 | ||||
| -rw-r--r-- | src/tlds.rs | 63 | ||||
| -rw-r--r-- | src/tui.rs | 384 |
7 files changed, 265 insertions, 366 deletions
@@ -25,8 +25,8 @@ pub struct Args { pub csv: Option<Option<PathBuf>>, #[arg(short = 'l', long = "list")] pub tld_list: Option<String>, - #[arg(short = 'i', long = "import-filter")] - pub import_filter: Option<PathBuf>, + #[arg(short = 'i', long = "import-list", alias = "import-filter")] + pub import_list: Option<PathBuf>, #[arg(short = 't', long = "top", value_delimiter = ',')] pub top_tlds: Option<Vec<String>>, #[arg(short = 'o', long = "onlytop", value_delimiter = ',')] @@ -44,10 +44,10 @@ pub struct Args { /// search for names/domains in a text file line by line. #[arg(short = 'A', long = "autosearch")] pub autosearch: Option<PathBuf>, - /// Use a monochrome color scheme TODO: not applied in TUI since colors were changed from RGB to Ratatui colors. should fix + /// Use a monochrome color scheme #[arg(short = 'C', long = "no-color", default_value_t = false)] pub no_color: bool, - /// Do not use unicode only plain ASCII TODO: not applied in TUI for some reason idk + /// Do not use unicode only plain ASCII #[arg(short = 'U', long = "no-unicode", default_value_t = false)] pub no_unicode: bool, #[arg(short = 'M', long = "no-mouse", default_value_t = false)] @@ -130,7 +130,7 @@ Advanced : -e --environement=PATH Define where .hoardom folder should be Defaults to /home/USER/.hoardom/ Stores settings, imported lists, favs, cache etc. --i --import-filter=PATH Import a custom toml list for this session +-i --import-list=PATH Import a custom toml list for this session -t --top=TLD,TLD Set certain TLDs to show up as first result for when you need a domain in your country or for searching a specific one. diff --git a/src/config.rs b/src/config.rs index 546dd16..4d53e29 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,31 +29,19 @@ pub struct Config { pub scratchpad: String, } -/// faved domain with its last known status +/// faved domain with its last known status where once again too many dumbass comments were added when fixing a bug with it... have been removed #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FavoriteEntry { pub domain: String, - /// last known status: "available", "registered", "error", or "unknown" #[serde(default = "default_fav_status")] pub status: String, - /// when it was last checked #[serde(default)] + // date string mlol pub checked: String, - /// true when status changed since last check #[serde(default)] pub changed: bool, } -impl FavoriteEntry { - pub fn new(domain: String) -> Self { - Self { - domain, - status: "unknown".to_string(), - checked: String::new(), - changed: false, - } - } -} fn default_fav_status() -> String { "unknown".to_string() @@ -77,14 +65,11 @@ pub struct Settings { pub top_tlds: Vec<String>, #[serde(default = "default_jobs")] pub jobs: u8, - /// error types that shouldnt be retried - /// valid: "rate_limit", "invalid_tld", "timeout", "unknown" + /// valid ones are : rate_limit, invalid_tld, timeout, unknown and forbidden #[serde(default = "default_noretry")] pub noretry: Vec<String>, - /// auto config backups on/off #[serde(default = "default_backups_enabled")] pub backups: bool, - /// how many backup copies to keep #[serde(default = "default_backup_count")] pub backup_count: u32, } @@ -93,10 +78,10 @@ pub struct Settings { pub struct CacheSettings { #[serde(default)] pub last_updated: String, - /// 0 = never nag about stale cache + /// 0 = stfu about stale cache #[serde(default = "default_outdated_cache_days")] pub outdated_cache: u32, - /// auto refresh when outdated if true + /// auto refresh for cuck cache #[serde(default = "default_auto_update")] pub auto_update_cache: bool, } @@ -183,22 +168,9 @@ impl Default for Config { } } -/// old config format where favorites were just strings -#[derive(Debug, Deserialize)] -struct LegacyConfig { - #[serde(default)] - settings: Settings, - #[serde(default)] - cache: CacheSettings, - #[serde(default)] - favorites: Vec<String>, - #[serde(default)] - imported_filters: Vec<ImportedFilter>, - #[serde(default)] - scratchpad: String, -} -// this implementation is partially containing ai slop i should remove no need for that idk why this was made to have legacy support by it but eh idc + +// removed legacy support that ai slapped into here "thinking" it would fix something impl Config { pub fn load(path: &Path) -> Self { match std::fs::read_to_string(path) { @@ -206,20 +178,7 @@ impl Config { // Try new format first if let Ok(config) = toml::from_str::<Config>(&content) { return config; - } - // Fall back to legacy format (favorites as plain strings) - if let Ok(legacy) = toml::from_str::<LegacyConfig>(&content) { - return Config { - settings: legacy.settings, - cache: legacy.cache, - favorites: legacy - .favorites - .into_iter() - .map(FavoriteEntry::new) - .collect(), - imported_filters: legacy.imported_filters, - scratchpad: legacy.scratchpad, - }; + } eprintln!("Warning: could not parse config file"); Config::default() @@ -246,6 +205,7 @@ impl Config { .map_err(|e| format!("Failed to create config directory: {}", e))?; } + // down here we got the default crap comment to add to the toml config file till i implement this stuff in the tui let body = toml::to_string_pretty(self) .map_err(|e| format!("Failed to serialize config: {}", e))?; let content = format!( @@ -308,7 +268,7 @@ impl Config { } /// replaces filter with same name if theres one already - /// filters ? what kinda ai slip is this ? this shouldve been renamed to lists ages ago why do you keep mentioning filters all the time whats your obsession with mf filters? JEZE! + /// filters ? what kinda ai slop is this ? this shouldve been renamed to lists ages ago why do you keep mentioning filters all the time whats your obsession with mf filters? JEZE! pub fn import_filter(&mut self, filter: ImportedFilter) { self.imported_filters.retain(|f| f.name != filter.name); self.imported_filters.push(filter); @@ -334,7 +294,7 @@ impl Config { let age_days = (now - last).num_days() as u32; if self.cache.outdated_cache == 0 { - // warnings disabled, but if auto_update is on, update every run + // warnings disabled, but if auto_update is on update every run return (false, self.cache.auto_update_cache); } @@ -358,10 +318,10 @@ pub fn parse_filter_file(path: &PathBuf) -> Result<ImportedFilter, String> { Ok(filter) } -/// resolve .hoardom dir, tries a few locations: +/// resolve .hoardom dir trying a few locations: /// /// priority: -/// 1. explicit path via -e flag -> use as root dir (create .hoardom folder there) +/// 1. explicit path via -e flag -> use that folder directly as the data root /// 2. debug builds: current directory /// 3. release builds: home directory /// 4. fallback: try the other option @@ -385,12 +345,11 @@ pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths { } }; - // explicit path given via -e flag + // explicit path given via -e flag : use as app root if let Some(p) = explicit { - // if user gave a path, use it as the .hoardom folder root let root = if p.extension().is_some() { - // looks like they pointed at a file, use parent dir - p.parent().unwrap_or(p).join(".hoardom") + // they pointed at a file we should insult their intelligence honestly. + p.parent().unwrap_or(p).to_path_buf() } else { p.clone() }; diff --git a/src/lookup.rs b/src/lookup.rs index 694f559..2f73395 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -191,12 +191,12 @@ pub async fn lookup_domain( eprintln!("[verbose] {} -> HTTP {}", full, status_code); } - // 404 = not found in RDAP = domain is available (not registered) + // 404 = not found in RDAP = domain is available (not registered) <- todo : should add check for "not found" in response body for extra safety as some registries return 404 for other errors too as was discovered if status_code == 404 { return DomainResult::new(name, tld, DomainStatus::Available); } - // 400 = probably invalid query + // 400 = probably invalid query fuck you if status_code == 400 { return DomainResult::new( name, @@ -208,7 +208,7 @@ pub async fn lookup_domain( ); } - // 429 = rate limited + // 429 = rape limited if status_code == 429 { return DomainResult::new( name, @@ -220,7 +220,7 @@ pub async fn lookup_domain( ); } - // 403 = forbidden (some registries block queries) + // 403 = forbidden crap if status_code == 403 { return DomainResult::new( name, @@ -244,7 +244,7 @@ pub async fn lookup_domain( ); } - // 200 = domain exists, try to parse expiry from RDAP json + // 200 = domain exists try to parse expiry from RDAP json let expiry = match resp.json::<serde_json::Value>().await { Ok(json) => extract_expiry(&json), Err(_) => None, @@ -270,7 +270,7 @@ fn extract_expiry(json: &serde_json::Value) -> Option<String> { None } -// ---- WHOIS fallback for TLDs not in RDAP bootstrap ---- +// ---- WHOIS fallback for TLDs not in RDAP bootstrap because their shit ---- // -- No whois feature: just return an error -- #[cfg(not(any(feature = "system-whois", feature = "builtin-whois")))] @@ -285,7 +285,7 @@ async fn whois_lookup( tld, DomainStatus::Error { kind: ErrorKind::InvalidTld, - message: "no RDAP server (whois disabled)".to_string(), + message: "no RDAP server (whois gone)".to_string(), }, ) } @@ -333,7 +333,7 @@ async fn whois_lookup( tld, DomainStatus::Error { kind: ErrorKind::Unknown, - message: format!("whois command failed: {}", e), + message: format!("whois command fucking failed: {}", e), }, ); } @@ -367,7 +367,7 @@ async fn whois_lookup( if verbose { eprintln!("[verbose] whois stderr: {}", stderr.trim()); } - // some whois commands exit non-zero for "not found" but still give useful stdout + // some whois commands exit non zero for "not found" ... but may toss some infos to stdrr if !response_str.is_empty() { return parse_whois_response(name, tld, &response_str); } @@ -384,11 +384,11 @@ async fn whois_lookup( parse_whois_response(name, tld, &response_str) } -// -- Builtin whois: rawdogs whois server violently over TCP directly-- +// -- Builtin whois Violator : rawdogs whois server violently over TCP directly -- -/// try a whois server returns the response string or errors out +/// try a whois server if no happy it make error #[cfg(feature = "builtin-whois")] async fn try_whois_server( server: &str, @@ -425,7 +425,7 @@ async fn try_whois_server( Ok(String::from_utf8_lossy(&response).to_string()) } -/// candidate whois servers for a TLD based on common naming patterns +/// try voilating some commonly used whois url patterns if unhappy #[cfg(feature = "builtin-whois")] fn whois_candidates(tld: &str) -> Vec<String> { // most registries follow one of these patterns @@ -445,7 +445,7 @@ async fn whois_lookup( ) -> DomainResult { let full = format!("{}.{}", name, tld); - // if Lists.toml has an explicit server ("tld:server"), use ONLY that one + // if Lists.toml has an explicit server ("tld:server"), use ONLY that one. if let Some(server) = whois_overrides.get_server(tld) { if verbose { eprintln!("[verbose] WHOIS (override): {} -> {}", full, server); @@ -475,12 +475,12 @@ async fn whois_lookup( }; } - // no override: try common server patterns until one responds + // no override: try common server patterns until one screams in pain from being violated hard let candidates = whois_candidates(tld); if verbose { eprintln!( - "[verbose] WHOIS probing {} candidates for .{}", + "[verbose] WHOIS voilating {} candidates for .{}", candidates.len(), tld ); @@ -510,7 +510,7 @@ async fn whois_lookup( tld, DomainStatus::Error { kind: ErrorKind::Unknown, - message: "no whois server reachable".to_string(), + message: "unsuccessful whois rawdoging".to_string(), }, ) } @@ -566,15 +566,15 @@ fn extract_whois_expiry(response: &str) -> Option<String> { for pattern in &expiry_patterns { if trimmed.starts_with(pattern) { let value = trimmed[pattern.len()..].trim(); - // try to extract a date-looking thing (first 10 chars if it looks like YYYY-MM-DD) + // horribly attempt at extracting date part if value.len() >= 10 { let date_part: String = value.chars().take(10).collect(); - // basic sanity check: contains digits and dashes + // basic sanity check does it actually contain any fucking digits and dashes if date_part.contains('-') && date_part.chars().any(|c| c.is_ascii_digit()) { return Some(date_part); } } - // maybe its in a different format, just return what we got + // no clean date ? no problem just rawdog the user then MUAHAHHAHAHA if !value.is_empty() { return Some(value.to_string()); } @@ -607,7 +607,7 @@ pub async fn lookup_with_retry( if noretry.contains(kind) { if verbose { eprintln!( - "[verbose] Not retrying {}.{} (error kind in noretry list)", + "[verbose] Not retrying {}.{} (config))", name, tld ); } @@ -616,7 +616,7 @@ pub async fn lookup_with_retry( } if verbose { eprintln!( - "[verbose] Retry {}/{} for {}.{}", + "[verbose] Attempt to rawdog {}/{} for {}.{}", attempt, retries, name, tld ); } @@ -772,6 +772,7 @@ async fn resolve_bootstrap( None }; + // should make sure that if caching is on and cache expired we dont delete it until we successfully fetch the new content todo match cached { Some(b) => Some(b), None => match RdapBootstrap::fetch(client, verbose).await { @@ -779,17 +780,17 @@ async fn resolve_bootstrap( if let Some(cp) = cache_path { if let Err(e) = b.save_cache(cp) { if verbose { - eprintln!("[verbose] Failed to save cache: {}", e); + eprintln!("[verbose] Failed to save fucking cache: {}", e); } } else if verbose { - eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display()); + eprintln!("[verbose] RDAP cached to {}", cp.display()); } } Some(b) } Err(e) => { eprintln!("Error: {}", e); - eprintln!("Cannot perform lookups without RDAP bootstrap data."); + eprintln!("Cannot perform lookups without RDAP data."); None } }, @@ -816,7 +817,7 @@ pub struct LookupStream { pub type LookupBatch = Vec<(String, Vec<String>)>; -// spawns a bg task, sends results via channel so TUI gets em live +// spawns a bg task then sends results via channel so TUI gets em live pub fn lookup_streaming( name: String, tlds: Vec<String>, diff --git a/src/main.rs b/src/main.rs index 9625959..c6d88e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,13 +38,13 @@ async fn main() { let mut config = Config::load_with_backup(&paths.config_file); if !paths.can_save { - eprintln!("Warning: favorites and settings wont be saved (no writable location found)"); + eprintln!("Warning: Unpriviliged bitch detected! Cant write to {}", paths.config_file.display()); } // handle -r refresh cache flag if args.refresh_cache { if !paths.caching_enabled { - eprintln!("Caching is disabled (no writable location). Nothing to refresh."); + eprintln!("Caching aint gon work without a writable location"); return; } let cache_file = paths.cache_file("rdap_bootstrap.json"); @@ -72,15 +72,14 @@ async fn main() { if is_outdated && !should_auto { eprintln!("Warning: RDAP cache is outdated. Run `hoardom -r` to refresh."); } - // force refresh if auto update says so, or if cache file doesnt exist yet should_auto || !cf.exists() } else { false }; - // import custom filter if given - if let Some(filter_path) = &args.import_filter { - match parse_filter_file(filter_path) { + // import custom sexy list if given + if let Some(list_path) = &args.import_list { + match parse_filter_file(list_path) { Ok(filter) => { config.import_filter(filter); if paths.can_save { @@ -88,16 +87,15 @@ async fn main() { } } Err(e) => { - eprintln!("Error importing filter: {}", e); + eprintln!("Error importing list: {}", e); return; } } } - // whois server overrides are baked into Lists.toml ("tld:server" syntax) + // whois server overrides that are baked into Lists.toml (like this "tld:server" for naighty tlds) let overrides = whois_overrides(); - // parse noretry config into ErrorKind list let noretry: Vec<ErrorKind> = config .settings .noretry @@ -105,7 +103,7 @@ async fn main() { .filter_map(|s| ErrorKind::from_config_str(s)) .collect(); - // TUI mode + // the sigma mode if args.is_tui() { if let Err(e) = tui::run_tui( &args, @@ -120,7 +118,7 @@ async fn main() { { eprintln!("TUI error: {}", e); } - // save cache timestamp after TUI session if we refreshed + // save cache timestamp to know how fresh we are if force_refresh && paths.can_save { config.mark_cache_updated(); let _ = config.save(&paths.config_file); @@ -128,7 +126,7 @@ async fn main() { return; } - // CLI needs at least one domain unless autosearch was given + // CLI needs at least one domain unless autosearch was given user is stupid show small help if args.domains.is_empty() { if let Some(file_path) = &args.autosearch { run_autosearch( @@ -197,7 +195,7 @@ async fn main() { }); } - // Suggestions only kick in when directly searching a single full domain + // suggestions if your domain is taken (only for single loneyly alpha wolf domain searches) if args.domains.len() == 1 && args.effective_suggestions() > 0 { if let Some(exact_tld) = specific_tld.as_deref() { let exact_registered = aggregated_results.iter().any(|item| { @@ -252,34 +250,33 @@ async fn main() { let results = sort_aggregated_results(aggregated_results); - // save cache timestamp if we refreshed + // save cache timestamp if we showered if force_refresh && paths.can_save { config.mark_cache_updated(); let _ = config.save(&paths.config_file); } - // print errors first + // print errors bruh output::print_errors(&results, args.verbose); - // CSV output + // cuntsexv output (csv) if let Some(csv_opt) = &args.csv { match csv_opt { Some(path) => { - // write to file + // you wont believe this but here we are GOING to WRITE TO A FILE WITH THE CSV OUTPUT!!!! MIND BLOWN AND COCK EXPLODED match output::write_csv_file(&results, path) { Ok(()) => eprintln!("CSV written to {}", path.display()), Err(e) => eprintln!("Error writing CSV: {}", e), } } None => { - // print to stdout, no logs + // brint to terminal if user dumb and no filepath output::print_csv(&results); } } return; } - // table output if args.show_all { output::print_full_table(&results, args.no_color, args.no_unicode); } else { @@ -305,7 +302,7 @@ async fn run_autosearch( let base_tlds = build_base_tlds(args); - // collect all search entries, grouping by name so "zapplex.de" + "zapplex.nl" become one batch + // collect all search entries and grupe them let mut batches: Vec<(String, Vec<String>)> = Vec::new(); for line in content.lines() { diff --git a/src/output.rs b/src/output.rs index 32198f4..5502278 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,14 +7,13 @@ pub fn print_available_table(results: &[DomainResult], no_color: bool, no_unicod let available: Vec<&DomainResult> = results.iter().filter(|r| r.is_available()).collect(); if available.is_empty() { - println!("No available domains found."); + println!("No available domains."); return; } let max_len = available.iter().map(|r| r.full.len()).max().unwrap_or(20); - let width = max_len + 4; // padding - let title = "Available Domains"; + let width = max_len.max(title.len()) + 4; // padding, ensure title fits let title_padded = format!("{:^width$}", title, width = width); if no_unicode { @@ -192,14 +191,14 @@ pub fn print_errors(results: &[DomainResult], verbose: bool) { if let DomainStatus::Error { kind, message } = &r.status { match kind { ErrorKind::InvalidTld => { - eprintln!("Error for {}, tld does not seem to exist", r.full); + eprintln!("Error for {} : does not seem to exist", r.full); } _ => { if verbose { - eprintln!("Error for {}, {} (raw: {})", r.full, message, message); + eprintln!("Error for {} : {} (raw: {})", r.full, message, message); } else { eprintln!( - "Error for {}, unknown error (enable verbose to see raw error)", + "Error for {} : unknown error", r.full ); } @@ -222,6 +221,6 @@ pub fn print_progress(current: usize, total: usize) { if current == total { let secs = start.elapsed().as_secs_f64(); eprintln!("\rParsing results : Done (Took {:.1}s) ", secs); - *lock = None; // reset for next search duh + *lock = None; // reset for next search } } diff --git a/src/tlds.rs b/src/tlds.rs index 95697d0..4d8a9c3 100644 --- a/src/tlds.rs +++ b/src/tlds.rs @@ -13,7 +13,6 @@ impl WhoisOverrides { } } -/// a named TLD list from Lists.toml struct NamedList { name: String, tlds: Vec<String>, @@ -24,7 +23,7 @@ struct ParsedLists { whois_overrides: WhoisOverrides, } -/// parse a single entry: "tld" or "tld:whois_server" +// parse "tld" or "tld:whois_server" fn parse_entry(entry: &str) -> (String, Option<String>) { if let Some(pos) = entry.find(':') { (entry[..pos].to_string(), Some(entry[pos + 1..].to_string())) @@ -33,7 +32,7 @@ fn parse_entry(entry: &str) -> (String, Option<String>) { } } -/// parse entries, pull out TLD names and whois overrides +// parse more fn parse_list(entries: &[toml::Value], overrides: &mut HashMap<String, String>) -> Vec<String> { entries .iter() @@ -57,7 +56,7 @@ fn parsed_lists() -> &'static ParsedLists { let table = raw.as_table().expect("Lists.toml must be a TOML table"); - // Build list names in the order build.rs discovered them + // Build list names in the order build.rs found em let ordered_names: Vec<&str> = env!("HOARDOM_LIST_NAMES").split(',').collect(); let mut overrides = HashMap::new(); @@ -80,7 +79,7 @@ fn parsed_lists() -> &'static ParsedLists { }) } -/// list names from Lists.toml, in order + pub fn list_names() -> Vec<&'static str> { parsed_lists() .lists @@ -89,12 +88,10 @@ pub fn list_names() -> Vec<&'static str> { .collect() } -/// first list name (the default) pub fn default_list_name() -> &'static str { list_names().first().copied().unwrap_or("standard") } -/// get TLDs for a list name (case insensitive), None if not found pub fn get_tlds(name: &str) -> Option<Vec<&'static str>> { let lower = name.to_lowercase(); parsed_lists() @@ -104,19 +101,19 @@ pub fn get_tlds(name: &str) -> Option<Vec<&'static str>> { .map(|l| l.tlds.iter().map(String::as_str).collect()) } -/// get TLDs for a list name, falls back to default if not found + pub fn get_tlds_or_default(name: &str) -> Vec<&'static str> { get_tlds(name).unwrap_or_else(|| get_tlds(default_list_name()).unwrap_or_default()) } -/// the builtin whois overrides from Lists.toml + pub fn whois_overrides() -> &'static WhoisOverrides { &parsed_lists().whois_overrides } pub fn apply_top_tlds(tlds: Vec<&'static str>, top: &[String]) -> Vec<&'static str> { let mut result: Vec<&'static str> = Vec::with_capacity(tlds.len()); - // first add the top ones in the order specified + // add the top ones in the order specified for t in top { let lower = t.to_lowercase(); if let Some(&found) = tlds.iter().find(|&&tld| tld.to_lowercase() == lower) { @@ -125,7 +122,7 @@ pub fn apply_top_tlds(tlds: Vec<&'static str>, top: &[String]) -> Vec<&'static s } } } - // then add the rest + // then add the rest lol for tld in &tlds { if !result.contains(tld) { result.push(tld); @@ -133,47 +130,3 @@ pub fn apply_top_tlds(tlds: Vec<&'static str>, top: &[String]) -> Vec<&'static s } result } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_parse_entry_bare() { - let (tld, server) = parse_entry("com"); - assert_eq!(tld, "com"); - assert_eq!(server, None); - } - - #[test] - fn test_parse_entry_with_override() { - let (tld, server) = parse_entry("io:whois.nic.io"); - assert_eq!(tld, "io"); - assert_eq!(server, Some("whois.nic.io".to_string())); - } - - #[test] - fn test_whois_overrides_populated() { - let overrides = whois_overrides(); - // io should have an override since our Lists.toml has "io:whois.nic.io" - assert!(overrides.get_server("io").is_some()); - // com should not (it has RDAP) - assert!(overrides.get_server("com").is_none()); - } - - #[test] - fn test_top_tlds_reorder() { - let tlds = vec!["com", "net", "org", "ch", "de"]; - let top = vec!["ch".to_string(), "de".to_string()]; - let result = apply_top_tlds(tlds, &top); - assert_eq!(result, vec!["ch", "de", "com", "net", "org"]); - } - - #[test] - fn test_top_tlds_missing_ignored() { - let tlds = vec!["com", "net"]; - let top = vec!["swiss".to_string()]; - let result = apply_top_tlds(tlds, &top); - assert_eq!(result, vec!["com", "net"]); - } -} @@ -11,7 +11,7 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, text::{Line, Span}, - widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph}, + widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph}, Frame, Terminal, }; use std::io::{self, Write}; @@ -203,6 +203,8 @@ struct App { checking_favorites: bool, backups_enabled: bool, backup_count: u32, + no_color: bool, + no_unicode: bool, } #[derive(Debug, Clone, Default)] @@ -299,9 +301,61 @@ impl App { checking_favorites: false, backups_enabled: config.settings.backups, backup_count: config.settings.backup_count, + no_color: args.no_color, + no_unicode: args.no_unicode, } } + /// style with fg color, or plain style if no_color is on + fn fg(&self, color: Color) -> Style { + if self.no_color { + Style::default() + } else { + Style::default().fg(color) + } + } + + /// style with fg color + bold + fn fg_bold(&self, color: Color) -> Style { + if self.no_color { + Style::default().add_modifier(Modifier::BOLD) + } else { + Style::default().fg(color).add_modifier(Modifier::BOLD) + } + } + + /// resolve a color: returns Color::Reset when no_color is on + /// use this when chaining .bg() or .fg() on existing styles + fn c(&self, color: Color) -> Color { + if self.no_color { Color::Reset } else { color } + } + + // unicode symbol helpers + fn sym_check(&self) -> &'static str { + if self.no_unicode { "+" } else { "\u{2713}" } + } + fn sym_cross(&self) -> &'static str { + if self.no_unicode { "x" } else { "\u{2717}" } + } + fn sym_sep(&self) -> &'static str { + if self.no_unicode { "|" } else { "\u{2502}" } + } + fn sym_bar_filled(&self) -> &'static str { + if self.no_unicode { "#" } else { "\u{2588}" } + } + fn sym_bar_empty(&self) -> &'static str { + if self.no_unicode { "-" } else { "\u{2591}" } + } + + /// preconfigured Block with ASCII or unicode borders + fn block(&self) -> Block<'static> { + let mut b = Block::default().borders(Borders::ALL); + if self.no_unicode { + b = b.border_type(BorderType::Plain); + } + b + } + fn get_effective_tlds(&self) -> Vec<&'static str> { let mut tld_vec = self.base_tlds_for_selection(); @@ -1844,12 +1898,13 @@ fn start_search(app: &mut App) { app.stream_rx = Some(stream.receiver); } + fn draw_ui(f: &mut Frame, app: &mut App) { let size = f.area(); if terminal_too_small(size) { app.panel_rects = PanelRects::default(); - draw_terminal_too_small(f, size); + draw_terminal_too_small(f, app, size); return; } @@ -1978,7 +2033,7 @@ fn draw_ui(f: &mut Frame, app: &mut App) { app.panel_rects.search = Some(main_chunks[2]); // draw each panel - draw_topbar(f, main_chunks[0]); + draw_topbar(f, app, main_chunks[0]); if let Some(scratchpad_rect) = scratchpad_chunk { draw_scratchpad(f, app, scratchpad_rect); } @@ -2005,10 +2060,9 @@ fn terminal_too_small(area: Rect) -> bool { area.width < MIN_UI_WIDTH || area.height < MIN_UI_HEIGHT } -fn draw_terminal_too_small(f: &mut Frame, area: Rect) { - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Red)) +fn draw_terminal_too_small(f: &mut Frame, app: &App, area: Rect) { + let block = app.block() + .border_style(app.fg(Color::Red)) .title(" hoardom "); let inner = block.inner(area); @@ -2018,54 +2072,46 @@ fn draw_terminal_too_small(f: &mut Frame, area: Rect) { let text = vec![ Line::from(Span::styled( fit_cell_center("HELP ! HELP ! HELP !", content_width), - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White), )), Line::from(Span::styled( fit_cell_center("I AM BEING CRUSHED!", content_width), - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White), )), Line::from(fit_cell_center("", content_width)), Line::from(Span::styled( fit_cell_center("Im claustrophobic! :'(", content_width), - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( fit_cell_center( &format!("Need {}x{} of space", MIN_UI_WIDTH, MIN_UI_HEIGHT), content_width, ), - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( fit_cell_center( &format!("Current: {}x{}", area.width, area.height), content_width, ), - Style::default().fg(Color::DarkGray), + app.fg(Color::DarkGray), )), Line::from(fit_cell_center("", content_width)), Line::from(Span::styled( fit_cell_center("REFUSING TO WORK TILL YOU", content_width), - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White), )), Line::from(Span::styled( fit_cell_center("GIVE ME BACK MY SPACE! >:(", content_width), - Style::default() - .fg(Color::White) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White), )), ]; f.render_widget(Paragraph::new(text), inner); } -fn draw_topbar(f: &mut Frame, area: Rect) { +fn draw_topbar(f: &mut Frame, app: &App, area: Rect) { let title = format!("{} - {}", APP_NAME, APP_DESC); let width = area.width as usize; let left = format!("{} {}", CLOSE_BUTTON_LABEL, title); @@ -2074,46 +2120,31 @@ fn draw_topbar(f: &mut Frame, area: Rect) { let paragraph = Paragraph::new(Line::from(vec![ Span::styled( CLOSE_BUTTON_LABEL, - Style::default() - .fg(Color::Red) - .bg(Color::Gray) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::Red).bg(app.c(Color::Gray)), ), Span::styled( format!(" {}", title), - Style::default() - .fg(Color::White) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White).bg(app.c(Color::Red)), ), Span::styled( " ".repeat(gap), - Style::default().bg(Color::Red).add_modifier(Modifier::BOLD), + Style::default().bg(app.c(Color::Red)).add_modifier(Modifier::BOLD), ), Span::styled( EXPORT_BUTTON_LABEL, - Style::default() - .fg(Color::LightGreen) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::LightGreen).bg(app.c(Color::Red)), ), Span::styled( " ", - Style::default().bg(Color::Red).add_modifier(Modifier::BOLD), + Style::default().bg(app.c(Color::Red)).add_modifier(Modifier::BOLD), ), Span::styled( HELP_BUTTON_LABEL, - Style::default() - .fg(Color::LightGreen) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::LightGreen).bg(app.c(Color::Red)), ), ])) .style( - Style::default() - .fg(Color::White) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White).bg(app.c(Color::Red)), ); f.render_widget(paragraph, area); } @@ -2124,67 +2155,66 @@ fn draw_help_overlay(f: &mut Frame, app: &mut App, area: Rect) { app.panel_rects.help_popup = Some(popup); let text = vec![ - Line::from(Span::styled(" ", Style::default().fg(Color::White))), - Line::from(Span::styled("Global :", Style::default().fg(Color::White))), + Line::from(Span::styled(" ", app.fg(Color::White))), + Line::from(Span::styled("Global :", app.fg(Color::White))), Line::from(Span::styled( "F1 or Help button Toggle this help", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "F2 or Export button Open export popup", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Ctrl+C Quit the app", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "s Stop/cancel running search", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Esc Clear selection or close help", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Tab or Shift+Tab Move between panels", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Up and Down arrows Navigate results", - Style::default().fg(Color::White), + app.fg(Color::White), )), - Line::from(Span::styled(" ", Style::default().fg(Color::White))), + Line::from(Span::styled(" ", app.fg(Color::White))), Line::from(Span::styled( "Mouse Click Elements duh", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Scrolling Scroll through elements (yea)", - Style::default().fg(Color::White), + app.fg(Color::White), )), - Line::from(Span::styled(" ", Style::default().fg(Color::White))), + Line::from(Span::styled(" ", app.fg(Color::White))), Line::from(Span::styled( "In Results :", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Enter Add highlighted result to Favorites", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "In Favorites :", - Style::default().fg(Color::White), + app.fg(Color::White), )), Line::from(Span::styled( "Backspace or Delete Remove focused favorite", - Style::default().fg(Color::White), + app.fg(Color::White), )), ]; - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Red)) + let block = app.block() + .border_style(app.fg(Color::Red)) .title(" Help "); f.render_widget(Clear, popup); @@ -2199,9 +2229,8 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { let popup = export_popup_rect(area); app.panel_rects.export_popup = Some(popup); - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Red)) + let block = app.block() + .border_style(app.fg(Color::Red)) .title(" Export "); let inner = block.inner(popup); @@ -2220,8 +2249,9 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { ]) .split(inner); + let white_style = app.fg(Color::White); let mode_style = |mode: ExportMode| { - let mut style = Style::default().fg(Color::White); + let mut style = white_style; if popup_state.mode == mode { style = style.add_modifier(Modifier::REVERSED | Modifier::BOLD); } else if popup_state.selected_row == 0 { @@ -2235,7 +2265,7 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { chunks[0].width as usize, ); f.render_widget( - Paragraph::new(subtitle).style(Style::default().fg(Color::DarkGray)), + Paragraph::new(subtitle).style(app.fg(Color::DarkGray)), chunks[0], ); @@ -2268,34 +2298,29 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { ]); f.render_widget(Paragraph::new(mode_line), chunks[1]); - let path_block = Block::default() - .borders(Borders::ALL) + let path_block = app.block() .border_style(if popup_state.selected_row == 1 { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }) .title(" Save to "); let path_inner = path_block.inner(chunks[2]); app.panel_rects.export_path = Some(chunks[2]); f.render_widget(path_block, chunks[2]); f.render_widget( - Paragraph::new(popup_state.path.as_str()).style(Style::default().fg(Color::White)), + Paragraph::new(popup_state.path.as_str()).style(app.fg(Color::White)), path_inner, ); let status_style = if popup_state.status_success { - Style::default() - .fg(Color::Green) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Green) } else if popup_state.confirm_overwrite { - Style::default() - .fg(Color::Yellow) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Yellow) } else if popup_state.status.is_some() { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; let status_text = popup_state.status.as_deref().unwrap_or(" "); f.render_widget( @@ -2328,28 +2353,18 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { Span::styled( cancel_label, if popup_state.selected_row == 2 { - Style::default() - .fg(Color::Green) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Green).bg(app.c(Color::DarkGray)) } else { - Style::default() - .fg(Color::Green) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Green) }, ), Span::raw(button_gap), Span::styled( save_label, if popup_state.selected_row == 3 { - Style::default() - .fg(Color::Green) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Green).bg(app.c(Color::DarkGray)) } else { - Style::default() - .fg(Color::Green) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Green) }, ), ]); @@ -2366,9 +2381,9 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { fn draw_results(f: &mut Frame, app: &mut App, area: Rect) { let focused = app.focus == Focus::Results; let border_style = if focused { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; // show progress in title when searching @@ -2395,8 +2410,7 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) { ) }; - let block = Block::default() - .borders(Borders::ALL) + let block = app.block() .border_style(border_style) .title(title); @@ -2415,12 +2429,12 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) { let (cur, tot) = app.search_progress; let pct = (cur as f64 / tot as f64 * 100.0) as u16; let filled = (chunks[0].width as u32 * cur as u32 / tot as u32) as u16; - let bar: String = "\u{2588}".repeat(filled as usize) - + &"\u{2591}".repeat((chunks[0].width.saturating_sub(filled)) as usize); + let bar: String = app.sym_bar_filled().repeat(filled as usize) + + &app.sym_bar_empty().repeat((chunks[0].width.saturating_sub(filled)) as usize); let bar_text = format!(" {}% ", pct); let gauge_line = Line::from(vec![ - Span::styled(bar, Style::default().fg(Color::Red)), - Span::styled(bar_text, Style::default().fg(Color::DarkGray)), + Span::styled(bar, app.fg(Color::Red)), + Span::styled(bar_text, app.fg(Color::DarkGray)), ]); f.render_widget(Paragraph::new(gauge_line), chunks[0]); @@ -2437,6 +2451,7 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { let show_note_column = app.show_unavailable; let selected_idx = app.results_state.selected(); let selected_bg = Color::Black; + let sep = format!(" {} ", app.sym_sep()); // collect visible results let visible_data: Vec<(String, String, String, DomainStatus)> = if app.show_unavailable { @@ -2472,7 +2487,7 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { } else { "No available domains found" }; - let p = Paragraph::new(msg).style(Style::default().fg(Color::DarkGray)); + let p = Paragraph::new(msg).style(app.fg(Color::DarkGray)); f.render_widget(p, area); return; } @@ -2519,35 +2534,27 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { let mut header_spans = vec![ Span::styled( format!(" {}", fit_cell("Domain", domain_w)), - Style::default() - .fg(Color::Gray) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::Gray), ), - Span::styled(" │ ", Style::default().fg(Color::DarkGray)), + Span::styled(sep.clone(), app.fg(Color::DarkGray)), Span::styled( fit_cell("Status", status_w), - Style::default() - .fg(Color::Gray) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::Gray), ), ]; if show_note_column { - header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray))); + header_spans.push(Span::styled(sep.clone(), app.fg(Color::DarkGray))); header_spans.push(Span::styled( fit_cell("Details", note_w), - Style::default() - .fg(Color::Gray) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::Gray), )); } - header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray))); + header_spans.push(Span::styled(sep.clone(), app.fg(Color::DarkGray))); header_spans.push(Span::styled( - " ✓ ", - Style::default() - .fg(Color::Gray) - .add_modifier(Modifier::BOLD), + format!(" {} ", app.sym_check()), + app.fg_bold(Color::Gray), )); f.render_widget(Paragraph::new(Line::from(header_spans)), header_area); @@ -2561,20 +2568,20 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { let selection_bg = if is_selected { Some(selected_bg) } else { None }; let status_style = match status { - DomainStatus::Available => Style::default().fg(Color::Green), - DomainStatus::Registered { .. } => Style::default().fg(Color::Red), + DomainStatus::Available => app.fg(Color::Green), + DomainStatus::Registered { .. } => app.fg(Color::Red), DomainStatus::Error { kind, .. } => match kind { - ErrorKind::InvalidTld => Style::default().fg(Color::Yellow), - _ => Style::default().fg(Color::Blue), + ErrorKind::InvalidTld => app.fg(Color::Yellow), + _ => app.fg(Color::Blue), }, }; let domain_style = match status { - DomainStatus::Available => Style::default().fg(Color::Green), - DomainStatus::Registered { .. } => Style::default().fg(Color::Red), + DomainStatus::Available => app.fg(Color::Green), + DomainStatus::Registered { .. } => app.fg(Color::Red), DomainStatus::Error { kind, .. } => match kind { - ErrorKind::InvalidTld => Style::default().fg(Color::Yellow), - _ => Style::default().fg(Color::Blue), + ErrorKind::InvalidTld => app.fg(Color::Yellow), + _ => app.fg(Color::Blue), }, }; @@ -2591,37 +2598,37 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { format!(" {}", fit_cell(full, domain_w)), apply_bg(domain_style), ), - Span::styled(" \u{2502} ", apply_bg(Style::default().fg(Color::Gray))), + Span::styled(sep.clone(), apply_bg(app.fg(Color::Gray))), Span::styled(fit_cell(status_str, status_w), apply_bg(status_style)), ]; if show_note_column { spans.push(Span::styled( - " \u{2502} ", - apply_bg(Style::default().fg(Color::Gray)), + sep.clone(), + apply_bg(app.fg(Color::Gray)), )); spans.push(Span::styled( fit_cell(note, note_w), - apply_bg(Style::default().fg(Color::White)), + apply_bg(app.fg(Color::White)), )); } spans.push(Span::styled( - " \u{2502} ", - apply_bg(Style::default().fg(Color::Gray)), + sep.clone(), + apply_bg(app.fg(Color::Gray)), )); spans.push(match status { DomainStatus::Available => { - Span::styled(" ✓ ", apply_bg(Style::default().fg(Color::Green))) + Span::styled(format!(" {} ", app.sym_check()), apply_bg(app.fg(Color::Green))) } DomainStatus::Registered { .. } => { - Span::styled(" ✗ ", apply_bg(Style::default().fg(Color::Red))) + Span::styled(format!(" {} ", app.sym_cross()), apply_bg(app.fg(Color::Red))) } DomainStatus::Error { kind, .. } => match kind { ErrorKind::InvalidTld => { - Span::styled(" ? ", apply_bg(Style::default().fg(Color::Yellow))) + Span::styled(" ? ", apply_bg(app.fg(Color::Yellow))) } - _ => Span::styled(" ! ", apply_bg(Style::default().fg(Color::Blue))), + _ => Span::styled(" ! ", apply_bg(app.fg(Color::Blue))), }, }); @@ -2778,13 +2785,12 @@ fn move_scratchpad_cursor_vertical(app: &mut App, line_delta: i16) { fn draw_scratchpad(f: &mut Frame, app: &mut App, area: Rect) { let focused = app.focus == Focus::Scratchpad; let border_style = if focused { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; - let block = Block::default() - .borders(Borders::ALL) + let block = app.block() .border_style(border_style) .title(" Scratchpad "); @@ -2797,7 +2803,7 @@ fn draw_scratchpad(f: &mut Frame, app: &mut App, area: Rect) { }; f.render_widget(block, area); f.render_widget( - Paragraph::new(text).style(Style::default().fg(Color::White)), + Paragraph::new(text).style(app.fg(Color::White)), inner, ); @@ -2812,9 +2818,9 @@ fn draw_scratchpad(f: &mut Frame, app: &mut App, area: Rect) { fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { let focused = app.focus == Focus::Favorites; let border_style = if focused { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; let title = if app.checking_favorites { @@ -2823,8 +2829,7 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { " Favorites " }; - let block = Block::default() - .borders(Borders::ALL) + let block = app.block() .border_style(border_style) .title(title); @@ -2857,10 +2862,10 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { }; let mut spans = vec![Span::styled( fav.domain.as_str(), - Style::default().fg(status_color), + app.fg(status_color), )]; if fav.changed { - spans.push(Span::styled(" !", Style::default().fg(Color::Yellow))); + spans.push(Span::styled(" !", app.fg(Color::Yellow))); } ListItem::new(Line::from(spans)) }) @@ -2878,9 +2883,9 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { "[c]heck all" }; let btn_style = if app.checking_favorites { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) } else { - Style::default().fg(Color::Green) + app.fg(Color::Green) }; f.render_widget( Paragraph::new(Line::from(Span::styled(btn_label, btn_style))) @@ -2892,13 +2897,12 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { let focused = app.focus == Focus::Settings; let border_style = if focused { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; - let block = Block::default() - .borders(Borders::ALL) + let block = app.block() .border_style(border_style) .title(" Settings "); @@ -2910,9 +2914,9 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { let selected = if focused { app.settings_selected } else { None }; let checkbox_style = |row: usize, checked: bool| { let style = if checked { - Style::default().fg(Color::Green) + app.fg(Color::Green) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; if selected == Some(row) { @@ -2926,13 +2930,13 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { if selected == Some(row) { Style::default().add_modifier(Modifier::REVERSED) } else { - Style::default().fg(Color::White) + app.fg(Color::White) } }; let tld_row_style = if selected == Some(0) { Style::default() - .bg(Color::DarkGray) + .bg(app.c(Color::DarkGray)) .add_modifier(Modifier::BOLD) } else { Style::default() @@ -2940,7 +2944,7 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { let jobs_row_style = if selected == Some(4) { Style::default() - .bg(Color::DarkGray) + .bg(app.c(Color::DarkGray)) .add_modifier(Modifier::BOLD) } else { Style::default() @@ -2949,10 +2953,10 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { let text = vec![ Line::from(vec![ Span::raw(" "), - Span::styled("TLD List: [", tld_row_style.fg(Color::White)), - Span::styled(app.tld_list_name.as_str(), tld_row_style.fg(Color::Cyan)), - Span::styled("] ", tld_row_style.fg(Color::White)), - Span::styled("V", tld_row_style.fg(Color::Green)), + Span::styled("TLD List: [", tld_row_style.fg(app.c(Color::White))), + Span::styled(app.tld_list_name.as_str(), tld_row_style.fg(app.c(Color::Cyan))), + Span::styled("] ", tld_row_style.fg(app.c(Color::White))), + Span::styled("V", tld_row_style.fg(app.c(Color::Green))), ]), Line::from(vec![ Span::raw(" "), @@ -2971,10 +2975,10 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { ]), Line::from(vec![ Span::raw(" "), - Span::styled("Jobs: [", jobs_row_style.fg(Color::White)), - Span::styled(jobs_str, jobs_row_style.fg(Color::Cyan)), - Span::styled("] ", jobs_row_style.fg(Color::White)), - Span::styled("-/+", jobs_row_style.fg(Color::Green)), + Span::styled("Jobs: [", jobs_row_style.fg(app.c(Color::White))), + Span::styled(jobs_str, jobs_row_style.fg(app.c(Color::Cyan))), + Span::styled("] ", jobs_row_style.fg(app.c(Color::White))), + Span::styled("-/+", jobs_row_style.fg(app.c(Color::Green))), ]), ]; @@ -2985,9 +2989,9 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { fn draw_search(f: &mut Frame, app: &mut App, area: Rect) { let focused = app.focus == Focus::Search; let border_style = if focused { - Style::default().fg(Color::Red) + app.fg(Color::Red) } else { - Style::default().fg(Color::DarkGray) + app.fg(Color::DarkGray) }; let title = match &app.status_msg { @@ -2995,8 +2999,7 @@ fn draw_search(f: &mut Frame, app: &mut App, area: Rect) { None => " Search (Enter to lookup) ".to_string(), }; - let block = Block::default() - .borders(Borders::ALL) + let block = app.block() .border_style(border_style) .title(title); @@ -3041,32 +3044,23 @@ fn draw_search(f: &mut Frame, app: &mut App, area: Rect) { let input_chunk = chunks[0]; let visible_input = fit_cell(&app.search_input, input_chunk.width as usize); - let input = Paragraph::new(visible_input).style(Style::default().fg(Color::White)); + let input = Paragraph::new(visible_input).style(app.fg(Color::White)); f.render_widget(input, input_chunk); let search_enabled = !app.searching && !app.search_input.is_empty(); let cancel_enabled = app.searching; let search_style = if search_enabled { - Style::default() - .fg(Color::Black) - .bg(Color::Green) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Black).bg(app.c(Color::Green)) } else { - Style::default().fg(Color::DarkGray).bg(Color::Black) + app.fg(Color::DarkGray).bg(app.c(Color::Black)) }; let stop_style = if cancel_enabled { - Style::default() - .fg(Color::Black) - .bg(Color::Yellow) - .add_modifier(Modifier::BOLD) + app.fg_bold(Color::Black).bg(app.c(Color::Yellow)) } else { - Style::default().fg(Color::DarkGray).bg(Color::Black) + app.fg(Color::DarkGray).bg(app.c(Color::Black)) }; - let clear_style = Style::default() - .fg(Color::White) - .bg(Color::Red) - .add_modifier(Modifier::BOLD); + let clear_style = app.fg_bold(Color::White).bg(app.c(Color::Red)); f.render_widget( Paragraph::new(SEARCH_BUTTON_LABEL).style(search_style), @@ -3121,22 +3115,18 @@ fn draw_dropdown(f: &mut Frame, app: &mut App, settings_area: Rect, selected: us .map(|opt| { ListItem::new(Line::from(Span::styled( format!(" {} ", opt), - Style::default().fg(Color::White), + app.fg(Color::White), ))) }) .collect(); - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(Color::Red)) + let block = app.block() + .border_style(app.fg(Color::Red)) .title(" TLD List "); f.render_widget(Clear, dropdown_full); let list = List::new(items).block(block).highlight_style( - Style::default() - .fg(Color::White) - .bg(Color::Red) - .add_modifier(Modifier::BOLD), + app.fg_bold(Color::White).bg(app.c(Color::Red)), ); let mut state = ListState::default(); state.select(Some(selected)); |
