diff options
| author | UMTS at Teleco <crt@teleco.ch> | 2026-03-08 22:11:52 +0100 |
|---|---|---|
| committer | UMTS at Teleco <crt@teleco.ch> | 2026-03-08 22:11:52 +0100 |
| commit | 17c6bd803dbbecb8975b1b98532d25a807a7b3fb (patch) | |
| tree | bc0e339ca514414261ce211ab0f74101e6d8aefa /src | |
| parent | ee17cec85d3d9ef2abc0d7a50c980d82b429b59d (diff) | |
docs and font fix
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 66 | ||||
| -rw-r--r-- | src/cli.rs | 58 | ||||
| -rw-r--r-- | src/config.rs | 57 | ||||
| -rw-r--r-- | src/lookup.rs | 505 | ||||
| -rw-r--r-- | src/main.rs | 41 | ||||
| -rw-r--r-- | src/output.rs | 21 | ||||
| -rw-r--r-- | src/tlds.rs | 4 | ||||
| -rw-r--r-- | src/tui.rs | 534 | ||||
| -rw-r--r-- | src/types.rs | 2 |
9 files changed, 916 insertions, 372 deletions
@@ -1,6 +1,6 @@ -// hoardom-app: native e gui emo wrapper for the hoardom tui +// e gui emo wrapper for the hoardom tui // spawns hoardom --tui in a pty and renders it in its own window -// so it shows up with its own icon in the dock (mac) or taskbar (linux) +// so it shows up with its own icon in the dock (mac) or taskbar (linux) for people that want that // // built with: cargo build --features gui @@ -15,7 +15,10 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -// ----- constants ----- +// thx gemma for the formated section comments i didnt want... thats not what I wanted when i said sanetize my comments from swearing and german language. +// i mean it is prettier than my usual way, will check otherfiles to see if can atleast make it somewhat consistant. + +// ---- constants ---- const FONT_SIZE: f32 = 14.0; const DEFAULT_COLS: u16 = 120; @@ -33,6 +36,8 @@ enum TermColor { Rgb(u8, u8, u8), } + +// ai made this fn because i gave up on trying. fn ansi_color(idx: u8) -> Color32 { match idx { 0 => Color32::from_rgb(0, 0, 0), @@ -58,7 +63,11 @@ fn ansi_color(idx: u8) -> Color32 { let gi = (idx % 36) / 6; let bi = idx % 6; let v = |i: u16| -> u8 { - if i == 0 { 0 } else { 55 + i as u8 * 40 } + if i == 0 { + 0 + } else { + 55 + i as u8 * 40 + } }; Color32::from_rgb(v(ri), v(gi), v(bi)) } @@ -73,7 +82,11 @@ fn ansi_color(idx: u8) -> Color32 { fn resolve_color(c: TermColor, is_fg: bool) -> Color32 { match c { TermColor::Default => { - if is_fg { DEFAULT_FG } else { DEFAULT_BG } + if is_fg { + DEFAULT_FG + } else { + DEFAULT_BG + } } TermColor::Indexed(i) => ansi_color(i), TermColor::Rgb(r, g, b) => Color32::from_rgb(r, g, b), @@ -135,6 +148,8 @@ impl Cell { // ----- terminal grid ----- + +// contains quiet a few ai solved bugfixes they seem ... fineish... to me and work struct TermGrid { cells: Vec<Vec<Cell>>, rows: usize, @@ -158,12 +173,13 @@ struct TermGrid { alt_saved: Option<(Vec<Vec<Cell>>, usize, usize)>, // mouse tracking modes - mouse_normal: bool, // ?1000 - normal tracking (clicks) - mouse_button: bool, // ?1002 - button-event tracking (drag) - mouse_any: bool, // ?1003 - any-event tracking (all motion) - mouse_sgr: bool, // ?1006 - SGR extended coordinates + mouse_normal: bool, // ?1000 - normal tracking (clicks) + mouse_button: bool, // ?1002 - button-event tracking (drag) + mouse_any: bool, // ?1003 - any-event tracking (all motion) + mouse_sgr: bool, // ?1006 - SGR extended coordinates } +// i partially stole this from somewhere i forgot from where though. impl TermGrid { fn new(rows: usize, cols: usize) -> Self { TermGrid { @@ -397,7 +413,7 @@ impl TermGrid { } } - // SGR - set graphics rendition (colors and attributes) + // slopfix : SGR , set graphics rendition (colors and attributes) fn sgr(&mut self, params: &[u16]) { if params.is_empty() { self.reset_attrs(); @@ -637,6 +653,7 @@ impl Perform for TermGrid { // ----- keyboard input mapping ----- // map egui keys to terminal escape sequences +// yk because maybe i want to recycle this file for some other project in the future hence why i implemented it fn special_key_bytes(key: &egui::Key, modifiers: &egui::Modifiers) -> Option<Vec<u8>> { use egui::Key; match key { @@ -721,7 +738,7 @@ struct HoardomApp { cell_height: f32, current_cols: u16, current_rows: u16, - last_mouse_button: Option<u8>, // track held mouse button for drag/release + last_mouse_button: Option<u8>, // track held mouse button for drag/release } impl eframe::App for HoardomApp { @@ -1155,12 +1172,37 @@ fn main() -> eframe::Result<()> { cc.egui_ctx.set_visuals(egui::Visuals::dark()); + // font fallback chain for monospace: Hack (default) -> NotoSansMono -> NotoSansSymbols2 + // Hack is missing box drawing, block elements, ellipsis + // NotoSansMono covers those but is missing dingbats (symbols) + // NotoSansSymbols2-subset has just those two glyphs from which only the necessary ones are extracted afaik + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "NotoSansMono".to_owned(), + std::sync::Arc::new(egui::FontData::from_static(include_bytes!( + "../dist/NotoSansMono-Regular.ttf" + ))), + ); + fonts.font_data.insert( + "NotoSansSymbols2".to_owned(), + std::sync::Arc::new(egui::FontData::from_static(include_bytes!( + "../dist/NotoSansSymbols2-subset.ttf" + ))), + ); + let mono = fonts + .families + .entry(egui::FontFamily::Monospace) + .or_default(); + mono.push("NotoSansMono".to_owned()); + mono.push("NotoSansSymbols2".to_owned()); + cc.egui_ctx.set_fonts(fonts); + Ok(Box::new(HoardomApp { grid, pty_writer: Mutex::new(writer), pty_master: pair.master, child_exited, - cell_width: 0.0, // measured on first frame + cell_width: 0.0, // measured on first frame cell_height: 0.0, current_cols: DEFAULT_COLS, current_rows: DEFAULT_ROWS, @@ -2,98 +2,60 @@ use clap::Parser; use std::path::PathBuf; #[derive(Parser, Debug)] -#[command(name = "hoardom", version = "0.0.1", about = "Domain hoarding made less painful")] +#[command( + name = "hoardom", + version = "0.0.1", + about = "Domain hoarding made less painful" +)] // static version infos ???? whoops #[command(disable_help_flag = true, disable_version_flag = true)] pub struct Args { - /// One or more domain names to search for + /// ffs why were a million comments added at some point !?!? basically all of these are self explanatory. removed dumb ass redundant ones. + #[arg(value_name = "DOMAIN")] pub domains: Vec<String>, - - // -- Mode -- - /// Default non interactive mode #[arg(long = "cli", default_value_t = false)] pub cli_mode: bool, - - /// Easy to use Terminal based Graphical user interface #[arg(long = "tui", default_value_t = false)] pub tui_mode: bool, - - // -- Basics -- - /// Define where environement file should be saved #[arg(short = 'e', long = "environement")] pub env_path: Option<PathBuf>, - - /// Show all in list even when unavailable #[arg(short = 'a', long = "all", default_value_t = false)] pub show_all: bool, - - // -- Advanced -- - /// Out in CSV, Path is optional. If path isnt given will be printed to terminal with no logs #[arg(short = 'c', long = "csv")] pub csv: Option<Option<PathBuf>>, - - /// Built in TLD list to use (from Lists.toml) #[arg(short = 'l', long = "list")] pub tld_list: Option<String>, - - /// Import a custom toml list for this session #[arg(short = 'i', long = "import-filter")] pub import_filter: Option<PathBuf>, - - /// Set certain TLDs to show up as first result (comma separated) #[arg(short = 't', long = "top", value_delimiter = ',')] pub top_tlds: Option<Vec<String>>, - - /// Only search these TLDs (comma separated) #[arg(short = 'o', long = "onlytop", value_delimiter = ',')] pub only_top: Option<Vec<String>>, - - /// How many suggestions to look up and try to show (defaults to 0 aka disabled) #[arg(short = 's', long = "suggestions")] pub suggestions: Option<usize>, - - // -- Various -- - /// Number of concurrent lookup requests (default: 1) #[arg(short = 'j', long = "jobs")] pub jobs: Option<u8>, - - /// Set the global delay in seconds between lookup requests #[arg(short = 'D', long = "delay")] pub delay: Option<f64>, - - /// Retry NUMBER amount of times if domain lookup errors out #[arg(short = 'R', long = "retry")] pub retry: Option<u32>, - - /// Verbose output for debugging #[arg(short = 'V', long = "verbose", default_value_t = false)] pub verbose: bool, - - /// Search for names/domains in text file, one domain per new line + /// 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 + /// Use a monochrome color scheme TODO: not applied in TUI since colors were changed from RGB to Ratatui colors. should fix #[arg(short = 'C', long = "no-color", default_value_t = false)] pub no_color: bool, - - /// Do not use unicode only plain ASCII + /// Do not use unicode only plain ASCII TODO: not applied in TUI for some reason idk #[arg(short = 'U', long = "no-unicode", default_value_t = false)] pub no_unicode: bool, - - /// Disable the mouse integration for TUI #[arg(short = 'M', long = "no-mouse", default_value_t = false)] pub no_mouse: bool, - - /// Force refresh the RDAP bootstrap cache #[arg(short = 'r', long = "refresh-cache", default_value_t = false)] pub refresh_cache: bool, - - /// Basic Help #[arg(short = 'h', long = "help", default_value_t = false)] pub help: bool, - - /// Show full help #[arg(short = 'H', long = "fullhelp", default_value_t = false)] pub fullhelp: bool, } diff --git a/src/config.rs b/src/config.rs index 1e2efeb..546dd16 100644 --- a/src/config.rs +++ b/src/config.rs @@ -36,10 +36,10 @@ pub struct FavoriteEntry { /// last known status: "available", "registered", "error", or "unknown" #[serde(default = "default_fav_status")] pub status: String, - /// when it was last checked (RFC 3339) + /// when it was last checked #[serde(default)] pub checked: String, - /// true when status changed since last check (shows ! in TUI) + /// true when status changed since last check #[serde(default)] pub changed: bool, } @@ -128,7 +128,11 @@ fn default_jobs() -> u8 { } fn default_noretry() -> Vec<String> { - vec!["rate_limit".to_string(), "invalid_tld".to_string(), "forbidden".to_string()] + vec![ + "rate_limit".to_string(), + "invalid_tld".to_string(), + "forbidden".to_string(), + ] } fn default_backups_enabled() -> bool { @@ -208,7 +212,11 @@ impl Config { return Config { settings: legacy.settings, cache: legacy.cache, - favorites: legacy.favorites.into_iter().map(FavoriteEntry::new).collect(), + favorites: legacy + .favorites + .into_iter() + .map(FavoriteEntry::new) + .collect(), imported_filters: legacy.imported_filters, scratchpad: legacy.scratchpad, }; @@ -240,37 +248,35 @@ impl Config { let body = toml::to_string_pretty(self) .map_err(|e| format!("Failed to serialize config: {}", e))?; - let content = format!("\ + let content = format!( + "\ # hoardom config - auto saved, comments are preserved on the line theyre on # # [settings] -# noretry: error types that shouldnt be retried -# \u{201c}rate_limit\u{201d} - server said slow down, retrying immediately wont help -# \u{201c}invalid_tld\u{201d} - TLD is genuinely broken, no point retrying -# \u{201c}forbidden\u{201d} - server returned 403, access denied, retrying wont fix it -# \u{201c}timeout\u{201d} - uncomment if youd rather skip slow TLDs than wait -# \u{201c}unknown\u{201d} - uncomment to skip any unrecognized errors too -\n{}", body); - std::fs::write(path, content) - .map_err(|e| format!("Failed to write config file: {}", e))?; +# noretry: error types that shouldnt be retried has the following valid values btw: +# \u{201c}rate_limit\u{201d} +# \u{201c}invalid_tld\u{201d} +# \u{201c}forbidden\u{201d} +# \u{201c}timeout\u{201d} +# \u{201c}unknown\u{201d} +\n{}", + body + ); + std::fs::write(path, content).map_err(|e| format!("Failed to write config file: {}", e))?; Ok(()) } - /// copy current config into backups/ folder. - /// keeps at most `max_count` backups, tosses the oldest. - /// only call on startup and shutdown - NOT on every save. + /// only call on startup and shutdown NOT on every save bruh pub fn create_backup(config_path: &Path, max_count: u32) -> Result<(), String> { let parent = config_path.parent().ok_or("No parent directory")?; let backup_dir = parent.join("backups"); std::fs::create_dir_all(&backup_dir) .map_err(|e| format!("Failed to create backup dir: {}", e))?; - // Timestamp-based filename: config_20260308_143022.toml let ts = chrono::Local::now().format("%Y%m%d_%H%M%S"); let backup_name = format!("config_{}.toml", ts); let backup_path = backup_dir.join(&backup_name); - // dont backup if same-second backup already exists if backup_path.exists() { return Ok(()); } @@ -278,7 +284,6 @@ impl Config { std::fs::copy(config_path, &backup_path) .map_err(|e| format!("Failed to copy config to backup: {}", e))?; - // prune old backups: sort by name (timestamp order), keep newest N if max_count > 0 { let mut backups: Vec<_> = std::fs::read_dir(&backup_dir) .map_err(|e| format!("Failed to read backup dir: {}", e))? @@ -303,6 +308,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! pub fn import_filter(&mut self, filter: ImportedFilter) { self.imported_filters.retain(|f| f.name != filter.name); self.imported_filters.push(filter); @@ -339,10 +345,10 @@ impl Config { } pub fn parse_filter_file(path: &PathBuf) -> Result<ImportedFilter, String> { - let content = std::fs::read_to_string(path) - .map_err(|e| format!("Could not read filter file: {}", e))?; - let filter: ImportedFilter = toml::from_str(&content) - .map_err(|e| format!("Could not parse filter file: {}", e))?; + let content = + std::fs::read_to_string(path).map_err(|e| format!("Could not read filter file: {}", e))?; + let filter: ImportedFilter = + toml::from_str(&content).map_err(|e| format!("Could not parse filter file: {}", e))?; if filter.name.is_empty() { return Err("Filter file must have a name defined".to_string()); } @@ -425,7 +431,7 @@ pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths { } } - // nothing works disable caching, use a dummy path + // nothing works disable caching and cry about it (it will still work just no persistant sessions) eprintln!("Warning: could not create .hoardom directory anywhere, caching disabled"); HoardomPaths { config_file: PathBuf::from(".hoardom/config.toml"), @@ -434,4 +440,3 @@ pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths { caching_enabled: false, } } - diff --git a/src/lookup.rs b/src/lookup.rs index f5b3177..694f559 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -6,6 +6,10 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Duration; +// Dont ask me about the spaghetti code found in here god may know but I dont remember what unholy sins have been created at like 5am here. + + + #[cfg(feature = "builtin-whois")] use tokio::io::{AsyncReadExt, AsyncWriteExt}; #[cfg(feature = "builtin-whois")] @@ -23,7 +27,10 @@ pub struct RdapBootstrap { impl RdapBootstrap { pub async fn fetch(client: &reqwest::Client, verbose: bool) -> Result<Self, String> { if verbose { - eprintln!("[verbose] Fetching RDAP bootstrap from {}", RDAP_BOOTSTRAP_URL); + eprintln!( + "[verbose] Fetching RDAP bootstrap from {}", + RDAP_BOOTSTRAP_URL + ); } let resp = client @@ -47,15 +54,24 @@ impl RdapBootstrap { let tld_map = Self::parse_bootstrap_json(&json); if verbose { - eprintln!("[verbose] RDAP bootstrap loaded, {} TLDs mapped", tld_map.len()); + eprintln!( + "[verbose] RDAP bootstrap loaded, {} TLDs mapped", + tld_map.len() + ); } - Ok(Self { tld_map, raw_json: Some(body) }) + Ok(Self { + tld_map, + raw_json: Some(body), + }) } pub fn load_cached(cache_path: &Path, verbose: bool) -> Result<Self, String> { if verbose { - eprintln!("[verbose] Loading cached RDAP bootstrap from {}", cache_path.display()); + eprintln!( + "[verbose] Loading cached RDAP bootstrap from {}", + cache_path.display() + ); } let body = std::fs::read_to_string(cache_path) .map_err(|e| format!("Could not read cached bootstrap: {}", e))?; @@ -63,9 +79,15 @@ impl RdapBootstrap { .map_err(|e| format!("Could not parse cached bootstrap: {}", e))?; let tld_map = Self::parse_bootstrap_json(&json); if verbose { - eprintln!("[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped", tld_map.len()); + eprintln!( + "[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped", + tld_map.len() + ); } - Ok(Self { tld_map, raw_json: Some(body) }) + Ok(Self { + tld_map, + raw_json: Some(body), + }) } pub fn save_cache(&self, cache_path: &Path) -> Result<(), String> { @@ -126,7 +148,10 @@ pub async fn lookup_domain( None => { // no RDAP server for this TLD, fall back to WHOIS if verbose { - eprintln!("[verbose] No RDAP server for {}, falling back to WHOIS", tld); + eprintln!( + "[verbose] No RDAP server for {}, falling back to WHOIS", + tld + ); } return whois_lookup(whois_overrides, name, tld, verbose).await; } @@ -149,10 +174,14 @@ pub async fn lookup_domain( } else { ErrorKind::Unknown }; - return DomainResult::new(name, tld, DomainStatus::Error { - kind, - message: "unknown error".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind, + message: "unknown error".to_string(), + }, + ); } }; @@ -169,34 +198,50 @@ pub async fn lookup_domain( // 400 = probably invalid query if status_code == 400 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::InvalidTld, - message: "invalid tld".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::InvalidTld, + message: "invalid tld".to_string(), + }, + ); } // 429 = rate limited if status_code == 429 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::RateLimit, - message: "rate limited".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::RateLimit, + message: "rate limited".to_string(), + }, + ); } // 403 = forbidden (some registries block queries) if status_code == 403 { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Forbidden, - message: "forbidden".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Forbidden, + message: "forbidden".to_string(), + }, + ); } - // anything else thats not success + // anything else thats not success is le bad if !status_code.is_success() { - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: format!("HTTP {}", status_code), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: format!("HTTP {}", status_code), + }, + ); } // 200 = domain exists, try to parse expiry from RDAP json @@ -229,16 +274,30 @@ fn extract_expiry(json: &serde_json::Value) -> Option<String> { // -- No whois feature: just return an error -- #[cfg(not(any(feature = "system-whois", feature = "builtin-whois")))] -async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, _verbose: bool) -> DomainResult { - DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::InvalidTld, - message: "no RDAP server (whois disabled)".to_string(), - }) +async fn whois_lookup( + _whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + _verbose: bool, +) -> DomainResult { + DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::InvalidTld, + message: "no RDAP server (whois disabled)".to_string(), + }, + ) } // -- System whois: shells out to the systems whois binary -- #[cfg(feature = "system-whois")] -async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult { +async fn whois_lookup( + _whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + verbose: bool, +) -> DomainResult { let full = format!("{}.{}", name, tld); let whois_cmd = env!("HOARDOM_WHOIS_CMD"); let whois_flags = env!("HOARDOM_WHOIS_FLAGS"); @@ -247,7 +306,10 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, if whois_flags.is_empty() { eprintln!("[verbose] System WHOIS: {} {}", whois_cmd, full); } else { - eprintln!("[verbose] System WHOIS: {} {} {}", whois_cmd, whois_flags, full); + eprintln!( + "[verbose] System WHOIS: {} {} {}", + whois_cmd, whois_flags, full + ); } } @@ -260,35 +322,44 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, } cmd.arg(&full); - let output = match tokio::time::timeout( - Duration::from_secs(15), - cmd.output(), - ).await { + let output = match tokio::time::timeout(Duration::from_secs(15), cmd.output()).await { Ok(Ok(out)) => out, Ok(Err(e)) => { if verbose { eprintln!("[verbose] System whois error for {}: {}", full, e); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: format!("whois command failed: {}", e), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: format!("whois command failed: {}", e), + }, + ); } Err(_) => { if verbose { eprintln!("[verbose] System whois timeout for {}", full); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Timeout, - message: "whois timeout".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Timeout, + message: "whois timeout".to_string(), + }, + ); } }; let response_str = String::from_utf8_lossy(&output.stdout); if verbose { - eprintln!("[verbose] WHOIS response for {} ({} bytes)", full, output.stdout.len()); + eprintln!( + "[verbose] WHOIS response for {} ({} bytes)", + full, + output.stdout.len() + ); } if !output.status.success() { @@ -300,26 +371,34 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, if !response_str.is_empty() { return parse_whois_response(name, tld, &response_str); } - return DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "whois command returned error".to_string(), - }); + return DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "whois command returned error".to_string(), + }, + ); } parse_whois_response(name, tld, &response_str) } -// -- Builtin whois: raw TCP to whois servers directly -- +// -- Builtin whois: rawdogs whois server violently over TCP directly-- + -/// try a whois server, returns the response string or errors out + +/// try a whois server returns the response string or errors out #[cfg(feature = "builtin-whois")] -async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<String, &'static str> { +async fn try_whois_server( + server: &str, + domain: &str, + verbose: bool, +) -> Result<String, &'static str> { let addr = format!("{}:43", server); - let stream = match tokio::time::timeout( - Duration::from_secs(4), - TcpStream::connect(&addr), - ).await { + let stream = match tokio::time::timeout(Duration::from_secs(4), TcpStream::connect(&addr)).await + { Ok(Ok(s)) => s, Ok(Err(_)) => return Err("connect error"), Err(_) => return Err("connect timeout"), @@ -337,10 +416,7 @@ async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<S } let mut response = Vec::new(); - match tokio::time::timeout( - Duration::from_secs(8), - reader.read_to_end(&mut response), - ).await { + match tokio::time::timeout(Duration::from_secs(8), reader.read_to_end(&mut response)).await { Ok(Ok(_)) => {} Ok(Err(_)) => return Err("read error"), Err(_) => return Err("read timeout"), @@ -361,7 +437,12 @@ fn whois_candidates(tld: &str) -> Vec<String> { } #[cfg(feature = "builtin-whois")] -async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult { +async fn whois_lookup( + whois_overrides: &WhoisOverrides, + name: &str, + tld: &str, + verbose: bool, +) -> DomainResult { let full = format!("{}.{}", name, tld); // if Lists.toml has an explicit server ("tld:server"), use ONLY that one @@ -371,14 +452,26 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v } return match try_whois_server(server, &full, verbose).await { Ok(resp) if !resp.is_empty() => parse_whois_response(name, tld, &resp), - Ok(_) => DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "empty whois response".to_string(), - }), - Err(e) => DomainResult::new(name, tld, DomainStatus::Error { - kind: if e.contains("timeout") { ErrorKind::Timeout } else { ErrorKind::Unknown }, - message: format!("whois {}: {}", server, e), - }), + Ok(_) => DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "empty whois response".to_string(), + }, + ), + Err(e) => DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: if e.contains("timeout") { + ErrorKind::Timeout + } else { + ErrorKind::Unknown + }, + message: format!("whois {}: {}", server, e), + }, + ), }; } @@ -386,7 +479,11 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v let candidates = whois_candidates(tld); if verbose { - eprintln!("[verbose] WHOIS probing {} candidates for .{}", candidates.len(), tld); + eprintln!( + "[verbose] WHOIS probing {} candidates for .{}", + candidates.len(), + tld + ); } for server in &candidates { @@ -408,10 +505,14 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v } // nothing worked - DomainResult::new(name, tld, DomainStatus::Error { - kind: ErrorKind::Unknown, - message: "no whois server reachable".to_string(), - }) + DomainResult::new( + name, + tld, + DomainStatus::Error { + kind: ErrorKind::Unknown, + message: "no whois server reachable".to_string(), + }, + ) } fn parse_whois_response(name: &str, tld: &str, response: &str) -> DomainResult { @@ -483,6 +584,8 @@ fn extract_whois_expiry(response: &str) -> Option<String> { None } + + pub async fn lookup_with_retry( client: &reqwest::Client, bootstrap: &RdapBootstrap, @@ -503,13 +606,19 @@ pub async fn lookup_with_retry( if let DomainStatus::Error { kind, .. } = &result.status { if noretry.contains(kind) { if verbose { - eprintln!("[verbose] Not retrying {}.{} (error kind in noretry list)", name, tld); + eprintln!( + "[verbose] Not retrying {}.{} (error kind in noretry list)", + name, tld + ); } break; } } if verbose { - eprintln!("[verbose] Retry {}/{} for {}.{}", attempt, retries, name, tld); + eprintln!( + "[verbose] Retry {}/{} for {}.{}", + attempt, retries, name, tld + ); } tokio::time::sleep(Duration::from_millis(500)).await; result = lookup_domain(client, bootstrap, whois_overrides, name, tld, verbose).await; @@ -554,7 +663,17 @@ pub async fn lookup_all( let mut results = Vec::with_capacity(total); let delay = Duration::from_secs_f64(delay_secs); for (i, tld) in tlds.iter().enumerate() { - let result = lookup_with_retry(&client, &bootstrap, whois_overrides, name, tld, retries, noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + whois_overrides, + name, + tld, + retries, + noretry, + verbose, + ) + .await; results.push(result); on_progress(i + 1, total); if delay_secs > 0.0 && i + 1 < total { @@ -579,7 +698,17 @@ pub async fn lookup_all( let name = name_owned.clone(); let tld = tld.to_string(); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (i, result) } }) @@ -604,7 +733,10 @@ pub async fn refresh_cache(cache_path: &Path, verbose: bool) -> Result<(), Strin let client = build_client(); let bootstrap = RdapBootstrap::fetch(&client, verbose).await?; bootstrap.save_cache(cache_path)?; - eprintln!("RDAP bootstrap cache refreshed ({} TLDs)", bootstrap.tld_map.len()); + eprintln!( + "RDAP bootstrap cache refreshed ({} TLDs)", + bootstrap.tld_map.len() + ); Ok(()) } @@ -642,33 +774,37 @@ async fn resolve_bootstrap( match cached { Some(b) => Some(b), - None => { - match RdapBootstrap::fetch(client, verbose).await { - Ok(b) => { - if let Some(cp) = cache_path { - if let Err(e) = b.save_cache(cp) { - if verbose { - eprintln!("[verbose] Failed to save cache: {}", e); - } - } else if verbose { - eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display()); + None => match RdapBootstrap::fetch(client, verbose).await { + Ok(b) => { + if let Some(cp) = cache_path { + if let Err(e) = b.save_cache(cp) { + if verbose { + eprintln!("[verbose] Failed to save cache: {}", e); } + } else if verbose { + eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display()); } - Some(b) - } - Err(e) => { - eprintln!("Error: {}", e); - eprintln!("Cannot perform lookups without RDAP bootstrap data."); - None } + Some(b) } - } + Err(e) => { + eprintln!("Error: {}", e); + eprintln!("Cannot perform lookups without RDAP bootstrap data."); + None + } + }, } } pub enum StreamMsg { - Result { result: DomainResult, sort_index: usize }, - Progress { current: usize, total: usize }, + Result { + result: DomainResult, + sort_index: usize, + }, + Progress { + current: usize, + total: usize, + }, Error(String), Done, } @@ -698,19 +834,19 @@ pub fn lookup_streaming( let handle = tokio::spawn(async move { let client = build_client(); - let bootstrap = match resolve_bootstrap( - &client, - cache_path.as_deref(), - force_refresh, - verbose, - ).await { - Some(b) => b, - None => { - let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await; - let _ = tx.send(StreamMsg::Done).await; - return; - } - }; + let bootstrap = + match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await { + Some(b) => b, + None => { + let _ = tx + .send(StreamMsg::Error( + "Failed to load RDAP bootstrap".to_string(), + )) + .await; + let _ = tx.send(StreamMsg::Done).await; + return; + } + }; let total = tlds.len(); let concurrent = (jobs as usize).max(1); @@ -718,9 +854,29 @@ pub fn lookup_streaming( if concurrent <= 1 { let delay = Duration::from_secs_f64(delay_secs); for (i, tld) in tlds.iter().enumerate() { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, tld, retries, &noretry, verbose).await; - let _ = tx.send(StreamMsg::Result { result, sort_index: i }).await; - let _ = tx.send(StreamMsg::Progress { current: i + 1, total }).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + tld, + retries, + &noretry, + verbose, + ) + .await; + let _ = tx + .send(StreamMsg::Result { + result, + sort_index: i, + }) + .await; + let _ = tx + .send(StreamMsg::Progress { + current: i + 1, + total, + }) + .await; if delay_secs > 0.0 && i + 1 < total { tokio::time::sleep(delay).await; } @@ -740,7 +896,17 @@ pub fn lookup_streaming( let noretry = Arc::clone(&noretry); let name = name.clone(); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (idx, result) } }) @@ -749,8 +915,18 @@ pub fn lookup_streaming( let mut done_count = 0usize; while let Some((idx, result)) = stream.next().await { done_count += 1; - let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await; - let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await; + let _ = tx2 + .send(StreamMsg::Result { + result, + sort_index: idx, + }) + .await; + let _ = tx2 + .send(StreamMsg::Progress { + current: done_count, + total, + }) + .await; } } @@ -776,7 +952,18 @@ pub fn lookup_many_streaming( ) -> LookupStream { if batches.len() == 1 { let (name, tlds) = batches.into_iter().next().unwrap(); - return lookup_streaming(name, tlds, delay_secs, retries, verbose, cache_path, force_refresh, jobs, whois_overrides, noretry); + return lookup_streaming( + name, + tlds, + delay_secs, + retries, + verbose, + cache_path, + force_refresh, + jobs, + whois_overrides, + noretry, + ); } let (tx, rx) = tokio::sync::mpsc::channel(64); @@ -784,19 +971,19 @@ pub fn lookup_many_streaming( let handle = tokio::spawn(async move { let client = build_client(); - let bootstrap = match resolve_bootstrap( - &client, - cache_path.as_deref(), - force_refresh, - verbose, - ).await { - Some(b) => b, - None => { - let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await; - let _ = tx.send(StreamMsg::Done).await; - return; - } - }; + let bootstrap = + match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await { + Some(b) => b, + None => { + let _ = tx + .send(StreamMsg::Error( + "Failed to load RDAP bootstrap".to_string(), + )) + .await; + let _ = tx.send(StreamMsg::Done).await; + return; + } + }; let total: usize = batches.iter().map(|(_, tlds)| tlds.len()).sum(); let concurrent = (jobs as usize).max(1); @@ -807,9 +994,24 @@ pub fn lookup_many_streaming( let mut global_idx = 0usize; for (name, tlds) in batches { for tld in tlds { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; current += 1; - let _ = tx.send(StreamMsg::Result { result, sort_index: global_idx }).await; + let _ = tx + .send(StreamMsg::Result { + result, + sort_index: global_idx, + }) + .await; let _ = tx.send(StreamMsg::Progress { current, total }).await; if delay_secs > 0.0 && current < total { tokio::time::sleep(delay).await; @@ -839,7 +1041,17 @@ pub fn lookup_many_streaming( let whois_overrides = Arc::clone(&whois_overrides); let noretry = Arc::clone(&noretry); async move { - let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await; + let result = lookup_with_retry( + &client, + &bootstrap, + &whois_overrides, + &name, + &tld, + retries, + &noretry, + verbose, + ) + .await; (idx, result) } }) @@ -848,13 +1060,26 @@ pub fn lookup_many_streaming( let mut done_count = 0usize; while let Some((idx, result)) = stream.next().await { done_count += 1; - let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await; - let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await; + let _ = tx2 + .send(StreamMsg::Result { + result, + sort_index: idx, + }) + .await; + let _ = tx2 + .send(StreamMsg::Progress { + current: done_count, + total, + }) + .await; } } let _ = tx.send(StreamMsg::Done).await; }); - LookupStream { receiver: rx, handle } + LookupStream { + receiver: rx, + handle, + } } diff --git a/src/main.rs b/src/main.rs index aa0b993..9625959 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,13 +98,26 @@ async fn main() { let overrides = whois_overrides(); // parse noretry config into ErrorKind list - let noretry: Vec<ErrorKind> = config.settings.noretry.iter() + let noretry: Vec<ErrorKind> = config + .settings + .noretry + .iter() .filter_map(|s| ErrorKind::from_config_str(s)) .collect(); // TUI mode if args.is_tui() { - if let Err(e) = tui::run_tui(&args, &config, paths.clone(), cache_file.clone(), force_refresh, overrides.clone(), noretry.clone()).await { + if let Err(e) = tui::run_tui( + &args, + &config, + paths.clone(), + cache_file.clone(), + force_refresh, + overrides.clone(), + noretry.clone(), + ) + .await + { eprintln!("TUI error: {}", e); } // save cache timestamp after TUI session if we refreshed @@ -118,7 +131,15 @@ async fn main() { // CLI needs at least one domain unless autosearch was given if args.domains.is_empty() { if let Some(file_path) = &args.autosearch { - run_autosearch(&args, file_path, cache_file.as_deref(), force_refresh, overrides, &noretry).await; + run_autosearch( + &args, + file_path, + cache_file.as_deref(), + force_refresh, + overrides, + &noretry, + ) + .await; if force_refresh && paths.can_save { config.mark_cache_updated(); let _ = config.save(&paths.config_file); @@ -392,7 +413,10 @@ fn parse_domain_input(raw_domain: &str) -> (String, Option<String>) { } } -fn build_effective_tlds(base_tlds: &[&'static str], specific_tld: Option<&str>) -> Vec<&'static str> { +fn build_effective_tlds( + base_tlds: &[&'static str], + specific_tld: Option<&str>, +) -> Vec<&'static str> { if let Some(tld) = specific_tld { vec![Box::leak(tld.to_string().into_boxed_str()) as &'static str] } else { @@ -403,7 +427,13 @@ fn build_effective_tlds(base_tlds: &[&'static str], specific_tld: Option<&str>) fn estimate_total_lookups(domains: &[String], base_tlds: &[&'static str]) -> usize { domains .iter() - .map(|domain| if domain.contains('.') { 1 } else { base_tlds.len() }) + .map(|domain| { + if domain.contains('.') { + 1 + } else { + base_tlds.len() + } + }) .sum() } @@ -415,4 +445,3 @@ fn sort_aggregated_results(mut aggregated: Vec<AggregatedResult>) -> Vec<DomainR }); aggregated.into_iter().map(|item| item.result).collect() } - diff --git a/src/output.rs b/src/output.rs index 6a02f32..32198f4 100644 --- a/src/output.rs +++ b/src/output.rs @@ -57,9 +57,19 @@ pub fn print_full_table(results: &[DomainResult], no_color: bool, no_unicode: bo } // calc column widths - let domain_w = results.iter().map(|r| r.full.len()).max().unwrap_or(10).max(7); + let domain_w = results + .iter() + .map(|r| r.full.len()) + .max() + .unwrap_or(10) + .max(7); let status_w = 10; // "registered" is the longest - let note_w = results.iter().map(|r| r.note_str().len()).max().unwrap_or(4).max(4); + let note_w = results + .iter() + .map(|r| r.note_str().len()) + .max() + .unwrap_or(4) + .max(4); let domain_col = domain_w + 2; let status_col = status_w + 2; @@ -167,10 +177,9 @@ pub fn print_csv(results: &[DomainResult]) { } pub fn write_csv_file(results: &[DomainResult], path: &PathBuf) -> Result<(), String> { - let mut file = std::fs::File::create(path) - .map_err(|e| format!("Could not create CSV file: {}", e))?; - writeln!(file, "Domains, Status, Note") - .map_err(|e| format!("Write error: {}", e))?; + let mut file = + std::fs::File::create(path).map_err(|e| format!("Could not create CSV file: {}", e))?; + writeln!(file, "Domains, Status, Note").map_err(|e| format!("Write error: {}", e))?; for r in results { writeln!(file, "{}, {}, {}", r.full, r.status_str(), r.note_str()) .map_err(|e| format!("Write error: {}", e))?; diff --git a/src/tlds.rs b/src/tlds.rs index 2835c24..95697d0 100644 --- a/src/tlds.rs +++ b/src/tlds.rs @@ -52,8 +52,8 @@ static PARSED_LISTS: OnceLock<ParsedLists> = OnceLock::new(); fn parsed_lists() -> &'static ParsedLists { PARSED_LISTS.get_or_init(|| { - let raw: toml::Value = toml::from_str(include_str!("../Lists.toml")) - .expect("Lists.toml must be valid TOML"); + let raw: toml::Value = + toml::from_str(include_str!("../Lists.toml")).expect("Lists.toml must be valid TOML"); let table = raw.as_table().expect("Lists.toml must be a TOML table"); @@ -1,5 +1,8 @@ use crossterm::{ - event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind}, + event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, + MouseButton, MouseEvent, MouseEventKind, + }, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; @@ -19,12 +22,14 @@ use crate::cli::Args; use crate::config::Config; use crate::config::FavoriteEntry; use crate::lookup; -use crate::tlds::{apply_top_tlds, get_tlds_or_default, list_names, default_list_name}; +use crate::tlds::{apply_top_tlds, default_list_name, get_tlds_or_default, list_names}; use crate::types::{DomainResult, DomainStatus, ErrorKind}; // note : this will be the worst shitshow of code you will probably have looked at in youre entire life -// it works and is somewhat stable but i didnt feel like sorting it into nice modules and all. -// have fun +// it works and is somewhat stable but i didnt feel like sorting it into nice modules and all mostly just relying +// on copy pasting shit where i need it +// +// have fun and may you forgive me for this extremly large ahh file. // names and labels const APP_NAME: &str = "hoardom"; @@ -36,7 +41,6 @@ const SEARCH_BUTTON_LABEL: &str = "[Search]"; const STOP_BUTTON_LABEL: &str = "[Stop](s)"; const CLEAR_BUTTON_LABEL: &str = "[Clear](C)"; - // Layout tuning constants const TOPBAR_HEIGHT: u16 = 1; const SEARCH_PANEL_HEIGHT: u16 = 3; @@ -88,8 +92,7 @@ fn export_favorites_txt(path: &Path, favorites: &[FavoriteEntry]) -> Result<(), .map_err(|e| format!("Failed to create export directory: {}", e))?; } let text: Vec<&str> = favorites.iter().map(|f| f.domain.as_str()).collect(); - std::fs::write(path, text.join("\n")) - .map_err(|e| format!("Failed to export favorites: {}", e)) + std::fs::write(path, text.join("\n")).map_err(|e| format!("Failed to export favorites: {}", e)) } fn export_results_csv(path: &Path, results: &[&DomainResult]) -> Result<(), String> { @@ -108,8 +111,7 @@ fn export_results_csv(path: &Path, results: &[&DomainResult]) -> Result<(), Stri )); } - std::fs::write(path, lines.join("\n")) - .map_err(|e| format!("Failed to export results: {}", e)) + std::fs::write(path, lines.join("\n")).map_err(|e| format!("Failed to export results: {}", e)) } #[derive(Debug, Clone, PartialEq)] @@ -125,7 +127,6 @@ enum ExportMode { } impl ExportMode { - fn default_file_name(self) -> &'static str { match self { ExportMode::FavoritesTxt => "hoardom-favorites.txt", @@ -229,7 +230,16 @@ struct PanelRects { } impl App { - fn new(args: &Args, config: &Config, config_path: PathBuf, can_save: bool, cache_path: Option<PathBuf>, force_refresh: bool, whois_overrides: crate::tlds::WhoisOverrides, noretry: Vec<ErrorKind>) -> Self { + fn new( + args: &Args, + config: &Config, + config_path: PathBuf, + can_save: bool, + cache_path: Option<PathBuf>, + force_refresh: bool, + whois_overrides: crate::tlds::WhoisOverrides, + noretry: Vec<ErrorKind>, + ) -> Self { let tld_list_name = args .tld_list .as_ref() @@ -271,7 +281,11 @@ impl App { verbose: args.verbose, delay: args.effective_delay(), retries: args.effective_retry(), - jobs: if args.jobs.is_some() { args.effective_jobs() } else { config.settings.jobs.max(1) }, + jobs: if args.jobs.is_some() { + args.effective_jobs() + } else { + config.settings.jobs.max(1) + }, panel_rects: PanelRects::default(), stream_rx: None, stream_task: None, @@ -421,7 +435,11 @@ impl App { last_res_export_path: self.last_res_export_path.clone(), top_tlds: self.top_tlds.clone().unwrap_or_default(), jobs: self.jobs, - noretry: self.noretry.iter().map(|k| k.to_config_str().to_string()).collect(), + noretry: self + .noretry + .iter() + .map(|k| k.to_config_str().to_string()) + .collect(), backups: self.backups_enabled, backup_count: self.backup_count, }, @@ -437,7 +455,9 @@ impl App { let d = domain.to_lowercase(); if !self.favorites.iter().any(|f| f.domain == d) { // check if we just looked this domain up - inherit its status - let status = self.results.iter() + let status = self + .results + .iter() .find(|(_, r)| r.full.to_lowercase() == d) .map(|(_, r)| r.status_str().to_string()) .unwrap_or_else(|| "unknown".to_string()); @@ -477,7 +497,11 @@ impl App { if self.show_unavailable { self.results.iter().map(|(_, r)| r).collect() } else { - self.results.iter().filter(|(_, r)| r.is_available()).map(|(_, r)| r).collect() + self.results + .iter() + .filter(|(_, r)| r.is_available()) + .map(|(_, r)| r) + .collect() } } @@ -598,7 +622,16 @@ pub async fn run_tui( let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; - let mut app = App::new(args, config, paths.config_file.clone(), paths.can_save, cache_file, force_refresh, whois_overrides, noretry); + let mut app = App::new( + args, + config, + paths.config_file.clone(), + paths.can_save, + cache_file, + force_refresh, + whois_overrides, + noretry, + ); if !paths.can_save { app.status_msg = Some("Warning: favorites and settings wont be saved".to_string()); @@ -608,10 +641,7 @@ pub async fn run_tui( // put the terminal back to normal if !args.no_mouse { - execute!( - terminal.backend_mut(), - DisableMouseCapture - )?; + execute!(terminal.backend_mut(), DisableMouseCapture)?; while event::poll(std::time::Duration::from_millis(0))? { let _ = event::read(); @@ -638,7 +668,10 @@ async fn run_app( } if let Some(popup) = app.export_popup.as_ref() { - if popup.close_at.is_some_and(|deadline| Instant::now() >= deadline) { + if popup + .close_at + .is_some_and(|deadline| Instant::now() >= deadline) + { app.export_popup = None; } } @@ -693,7 +726,9 @@ async fn run_app( Ok(lookup::StreamMsg::Result { result, .. }) => { // Update the matching favorite's status let domain_lower = result.full.to_lowercase(); - if let Some(fav) = app.favorites.iter_mut().find(|f| f.domain == domain_lower) { + if let Some(fav) = + app.favorites.iter_mut().find(|f| f.domain == domain_lower) + { let new_status = result.status_str().to_string(); if fav.status != new_status && fav.status != "unknown" { fav.changed = true; @@ -786,7 +821,11 @@ async fn run_app( app.dropdown = DropdownState::Closed; app.set_focus(match app.focus { Focus::Search => { - if app.show_notes_panel { Focus::Scratchpad } else { Focus::Results } + if app.show_notes_panel { + Focus::Scratchpad + } else { + Focus::Results + } } Focus::Scratchpad => Focus::Results, Focus::Results => Focus::Favorites, @@ -800,7 +839,11 @@ async fn run_app( Focus::Search => Focus::Settings, Focus::Scratchpad => Focus::Search, Focus::Results => { - if app.show_notes_panel { Focus::Scratchpad } else { Focus::Search } + if app.show_notes_panel { + Focus::Scratchpad + } else { + Focus::Search + } } Focus::Favorites => Focus::Results, Focus::Settings => Focus::Favorites, @@ -901,7 +944,11 @@ fn handle_export_popup_key(app: &mut App, key: KeyCode) { popup.selected_row = (popup.selected_row + 1) % 4; } KeyCode::BackTab | KeyCode::Up => { - popup.selected_row = if popup.selected_row == 0 { 3 } else { popup.selected_row - 1 }; + popup.selected_row = if popup.selected_row == 0 { + 3 + } else { + popup.selected_row - 1 + }; } KeyCode::Left => { if popup.selected_row == 0 { @@ -1057,7 +1104,9 @@ async fn handle_search_key(app: &mut App, key: KeyCode) { } } KeyCode::Delete => { - if app.cursor_pos < app.search_input.len() && app.search_input.is_char_boundary(app.cursor_pos) { + if app.cursor_pos < app.search_input.len() + && app.search_input.is_char_boundary(app.cursor_pos) + { app.search_input.remove(app.cursor_pos); } } @@ -1100,7 +1149,11 @@ fn handle_results_key(app: &mut App, key: KeyCode) { KeyCode::Up => { let i = match app.results_state.selected() { Some(i) => { - if i > 0 { i - 1 } else { 0 } + if i > 0 { + i - 1 + } else { + 0 + } } None => 0, }; @@ -1109,7 +1162,11 @@ fn handle_results_key(app: &mut App, key: KeyCode) { KeyCode::Down => { let i = match app.results_state.selected() { Some(i) => { - if i + 1 < len { i + 1 } else { i } + if i + 1 < len { + i + 1 + } else { + i + } } None => 0, }; @@ -1139,7 +1196,11 @@ fn handle_favorites_key(app: &mut App, key: KeyCode) { KeyCode::Up => { let i = match app.favorites_state.selected() { Some(i) => { - if i > 0 { i - 1 } else { 0 } + if i > 0 { + i - 1 + } else { + 0 + } } None => 0, }; @@ -1148,7 +1209,11 @@ fn handle_favorites_key(app: &mut App, key: KeyCode) { KeyCode::Down => { let i = match app.favorites_state.selected() { Some(i) => { - if i + 1 < len { i + 1 } else { i } + if i + 1 < len { + i + 1 + } else { + i + } } None => 0, }; @@ -1257,26 +1322,24 @@ fn handle_settings_key(app: &mut App, key: KeyCode) { _ => {} } } - KeyCode::Char(' ') => { - match app.settings_selected.unwrap_or(0) { - 1 => { - app.show_unavailable = !app.show_unavailable; - app.save_config(); - } - 2 => { - app.show_notes_panel = !app.show_notes_panel; - if !app.show_notes_panel && app.focus == Focus::Scratchpad { - app.set_focus(Focus::Results); - } - app.save_config(); - } - 3 => { - app.clear_on_search = !app.clear_on_search; - app.save_config(); + KeyCode::Char(' ') => match app.settings_selected.unwrap_or(0) { + 1 => { + app.show_unavailable = !app.show_unavailable; + app.save_config(); + } + 2 => { + app.show_notes_panel = !app.show_notes_panel; + if !app.show_notes_panel && app.focus == Focus::Scratchpad { + app.set_focus(Focus::Results); } - _ => {} + app.save_config(); } - } + 3 => { + app.clear_on_search = !app.clear_on_search; + app.save_config(); + } + _ => {} + }, KeyCode::Char('+') | KeyCode::Char('=') => { if app.settings_selected == Some(4) { app.jobs = if app.jobs >= 99 { 99 } else { app.jobs + 1 }; @@ -1502,7 +1565,11 @@ fn handle_mouse(app: &mut App, mouse: MouseEvent) { app.set_focus(Focus::Results); let visible_len = app.visible_results().len(); let content_start = results_rect.y + 1; - let progress_offset = if app.searching && app.search_progress.1 > 0 { 1 } else { 0 }; + let progress_offset = if app.searching && app.search_progress.1 > 0 { + 1 + } else { + 0 + }; let header_offset = if visible_len > 0 { 1 } else { 0 }; let list_start = content_start + progress_offset + header_offset; @@ -1653,14 +1720,18 @@ fn start_fav_check(app: &mut App) { app.checking_favorites = true; // Build a batch: each favorite is "name.tld" -> lookup (name, [tld]) - let batches: lookup::LookupBatch = app.favorites.iter().filter_map(|fav| { - let parts: Vec<&str> = fav.domain.splitn(2, '.').collect(); - if parts.len() == 2 { - Some((parts[0].to_string(), vec![parts[1].to_string()])) - } else { - None - } - }).collect(); + let batches: lookup::LookupBatch = app + .favorites + .iter() + .filter_map(|fav| { + let parts: Vec<&str> = fav.domain.splitn(2, '.').collect(); + if parts.len() == 2 { + Some((parts[0].to_string(), vec![parts[1].to_string()])) + } else { + None + } + }) + .collect(); if batches.is_empty() { app.checking_favorites = false; @@ -1793,7 +1864,10 @@ fn draw_ui(f: &mut Frame, app: &mut App) { .split(size); let content_area = main_chunks[1]; - let desired_sidebar = content_area.width.saturating_mul(SIDEBAR_TARGET_WIDTH_PERCENT) / 100; + let desired_sidebar = content_area + .width + .saturating_mul(SIDEBAR_TARGET_WIDTH_PERCENT) + / 100; let mut sidebar_width = clamp_panel_size(desired_sidebar, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH) .min(content_area.width.saturating_sub(RESULTS_MIN_WIDTH)); if sidebar_width == 0 { @@ -1809,7 +1883,10 @@ fn draw_ui(f: &mut Frame, app: &mut App) { let (scratchpad_chunk, results_chunk) = if app.show_notes_panel { let center_width = content_area.width.saturating_sub(sidebar_width); - let desired_scratchpad = content_area.width.saturating_mul(SCRATCHPAD_TARGET_WIDTH_PERCENT) / 100; + let desired_scratchpad = content_area + .width + .saturating_mul(SCRATCHPAD_TARGET_WIDTH_PERCENT) + / 100; let mut scratchpad_width = clamp_panel_size( desired_scratchpad, SCRATCHPAD_MIN_WIDTH, @@ -1941,11 +2018,15 @@ 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), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), )), Line::from(Span::styled( fit_cell_center("I AM BEING CRUSHED!", content_width), - Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), )), Line::from(fit_cell_center("", content_width)), Line::from(Span::styled( @@ -1953,21 +2034,31 @@ fn draw_terminal_too_small(f: &mut Frame, area: Rect) { Style::default().fg(Color::White), )), Line::from(Span::styled( - fit_cell_center(&format!("Need {}x{} of space", MIN_UI_WIDTH, MIN_UI_HEIGHT), content_width), + fit_cell_center( + &format!("Need {}x{} of space", MIN_UI_WIDTH, MIN_UI_HEIGHT), + content_width, + ), Style::default().fg(Color::White), )), Line::from(Span::styled( - fit_cell_center(&format!("Current: {}x{}", area.width, area.height), content_width), + fit_cell_center( + &format!("Current: {}x{}", area.width, area.height), + content_width, + ), Style::default().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), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), )), Line::from(Span::styled( fit_cell_center("GIVE ME BACK MY SPACE! >:(", content_width), - Style::default().fg(Color::White).add_modifier(Modifier::BOLD), + Style::default() + .fg(Color::White) + .add_modifier(Modifier::BOLD), )), ]; @@ -1981,14 +2072,49 @@ fn draw_topbar(f: &mut Frame, area: Rect) { let right = format!("{} {}", EXPORT_BUTTON_LABEL, HELP_BUTTON_LABEL); let gap = width.saturating_sub(left.chars().count() + right.chars().count()); let paragraph = Paragraph::new(Line::from(vec![ - Span::styled(CLOSE_BUTTON_LABEL, Style::default().fg(Color::Red).bg(Color::Gray).add_modifier(Modifier::BOLD)), - Span::styled(format!(" {}", title), Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD)), - Span::styled(" ".repeat(gap), Style::default().bg(Color::Red).add_modifier(Modifier::BOLD)), - Span::styled(EXPORT_BUTTON_LABEL, Style::default().fg(Color::LightGreen).bg(Color::Red).add_modifier(Modifier::BOLD)), - Span::styled(" ", Style::default().bg(Color::Red).add_modifier(Modifier::BOLD)), - Span::styled(HELP_BUTTON_LABEL, Style::default().fg(Color::LightGreen).bg(Color::Red).add_modifier(Modifier::BOLD)), + Span::styled( + CLOSE_BUTTON_LABEL, + Style::default() + .fg(Color::Red) + .bg(Color::Gray) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + format!(" {}", title), + Style::default() + .fg(Color::White) + .bg(Color::Red) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + " ".repeat(gap), + Style::default().bg(Color::Red).add_modifier(Modifier::BOLD), + ), + Span::styled( + EXPORT_BUTTON_LABEL, + Style::default() + .fg(Color::LightGreen) + .bg(Color::Red) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + " ", + Style::default().bg(Color::Red).add_modifier(Modifier::BOLD), + ), + Span::styled( + HELP_BUTTON_LABEL, + Style::default() + .fg(Color::LightGreen) + .bg(Color::Red) + .add_modifier(Modifier::BOLD), + ), ])) - .style(Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD)); + .style( + Style::default() + .fg(Color::White) + .bg(Color::Red) + .add_modifier(Modifier::BOLD), + ); f.render_widget(paragraph, area); } @@ -2000,22 +2126,60 @@ fn draw_help_overlay(f: &mut Frame, app: &mut App, area: Rect) { 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("F1 or Help button Toggle this help", Style::default().fg(Color::White))), - Line::from(Span::styled("F2 or Export button Open export popup", Style::default().fg(Color::White))), - Line::from(Span::styled("Ctrl+C Quit the app", Style::default().fg(Color::White))), - Line::from(Span::styled("s Stop/cancel running search", Style::default().fg(Color::White))), - Line::from(Span::styled("Esc Clear selection or close help", Style::default().fg(Color::White))), - Line::from(Span::styled("Tab or Shift+Tab Move between panels", Style::default().fg(Color::White))), - Line::from(Span::styled("Up and Down arrows Navigate results", Style::default().fg(Color::White))), + Line::from(Span::styled( + "F1 or Help button Toggle this help", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "F2 or Export button Open export popup", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Ctrl+C Quit the app", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "s Stop/cancel running search", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Esc Clear selection or close help", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Tab or Shift+Tab Move between panels", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Up and Down arrows Navigate results", + Style::default().fg(Color::White), + )), Line::from(Span::styled(" ", Style::default().fg(Color::White))), - Line::from(Span::styled("Mouse Click Elements duh", Style::default().fg(Color::White))), - Line::from(Span::styled("Scrolling Scroll through elements (yea)", Style::default().fg(Color::White))), - + Line::from(Span::styled( + "Mouse Click Elements duh", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Scrolling Scroll through elements (yea)", + Style::default().fg(Color::White), + )), Line::from(Span::styled(" ", Style::default().fg(Color::White))), - Line::from(Span::styled("In Results :", Style::default().fg(Color::White))), - Line::from(Span::styled("Enter Add highlighted result to Favorites", Style::default().fg(Color::White))), - Line::from(Span::styled("In Favorites :", Style::default().fg(Color::White))), - Line::from(Span::styled("Backspace or Delete Remove focused favorite", Style::default().fg(Color::White))), + Line::from(Span::styled( + "In Results :", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Enter Add highlighted result to Favorites", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "In Favorites :", + Style::default().fg(Color::White), + )), + Line::from(Span::styled( + "Backspace or Delete Remove focused favorite", + Style::default().fg(Color::White), + )), ]; let block = Block::default() @@ -2066,7 +2230,10 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { style }; - let subtitle = fit_cell_center("Choose what to export and where to save it.", chunks[0].width as usize); + let subtitle = fit_cell_center( + "Choose what to export and where to save it.", + chunks[0].width as usize, + ); f.render_widget( Paragraph::new(subtitle).style(Style::default().fg(Color::DarkGray)), chunks[0], @@ -2118,9 +2285,13 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { ); let status_style = if popup_state.status_success { - Style::default().fg(Color::Green).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Green) + .add_modifier(Modifier::BOLD) } else if popup_state.confirm_overwrite { - Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Yellow) + .add_modifier(Modifier::BOLD) } else if popup_state.status.is_some() { Style::default().fg(Color::Red) } else { @@ -2132,7 +2303,6 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) { chunks[3], ); - let cancel_label = "[Cancel]"; let button_gap = " "; let save_label = "[Save]"; @@ -2158,18 +2328,28 @@ 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) + Style::default() + .fg(Color::Green) + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD) } else { - Style::default().fg(Color::Green).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Green) + .add_modifier(Modifier::BOLD) }, ), 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) + Style::default() + .fg(Color::Green) + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD) } else { - Style::default().fg(Color::Green).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Green) + .add_modifier(Modifier::BOLD) }, ), ]); @@ -2207,7 +2387,12 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) { Some(d) => format!(" | Took: {:.1}s", d.as_secs_f64()), None => String::new(), }; - format!(" Results ({} available / {} total{}) ", avail, app.results.len(), duration_str) + format!( + " Results ({} available / {} total{}) ", + avail, + app.results.len(), + duration_str + ) }; let block = Block::default() @@ -2251,13 +2436,34 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) { 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 selected_bg = Color::Black; // collect visible results let visible_data: Vec<(String, String, String, DomainStatus)> = if app.show_unavailable { - app.results.iter().map(|(_, r)| (r.full.clone(), r.status_str().to_string(), r.note_str(), r.status.clone())).collect() + app.results + .iter() + .map(|(_, r)| { + ( + r.full.clone(), + r.status_str().to_string(), + r.note_str(), + r.status.clone(), + ) + }) + .collect() } else { - app.results.iter().filter(|(_, r)| r.is_available()).map(|(_, r)| (r.full.clone(), r.status_str().to_string(), r.note_str(), r.status.clone())).collect() + app.results + .iter() + .filter(|(_, r)| r.is_available()) + .map(|(_, r)| { + ( + r.full.clone(), + r.status_str().to_string(), + r.note_str(), + r.status.clone(), + ) + }) + .collect() }; if visible_data.is_empty() && !app.searching { @@ -2311,18 +2517,38 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { if let Some(header_area) = header_area { let mut header_spans = vec![ - Span::styled(format!(" {}", fit_cell("Domain", domain_w)), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)), + Span::styled( + format!(" {}", fit_cell("Domain", domain_w)), + Style::default() + .fg(Color::Gray) + .add_modifier(Modifier::BOLD), + ), Span::styled(" │ ", Style::default().fg(Color::DarkGray)), - Span::styled(fit_cell("Status", status_w), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)), + Span::styled( + fit_cell("Status", status_w), + Style::default() + .fg(Color::Gray) + .add_modifier(Modifier::BOLD), + ), ]; if show_note_column { header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray))); - header_spans.push(Span::styled(fit_cell("Details", note_w), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD))); + header_spans.push(Span::styled( + fit_cell("Details", note_w), + Style::default() + .fg(Color::Gray) + .add_modifier(Modifier::BOLD), + )); } header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray))); - header_spans.push(Span::styled(" ✓ ", Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD))); + header_spans.push(Span::styled( + " ✓ ", + Style::default() + .fg(Color::Gray) + .add_modifier(Modifier::BOLD), + )); f.render_widget(Paragraph::new(Line::from(header_spans)), header_area); } @@ -2361,22 +2587,40 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) { }; let mut spans = vec![ - Span::styled(format!(" {}", fit_cell(full, domain_w)), apply_bg(domain_style)), + Span::styled( + format!(" {}", fit_cell(full, domain_w)), + apply_bg(domain_style), + ), Span::styled(" \u{2502} ", apply_bg(Style::default().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)))); - spans.push(Span::styled(fit_cell(note, note_w), apply_bg(Style::default().fg(Color::White)))); + spans.push(Span::styled( + " \u{2502} ", + apply_bg(Style::default().fg(Color::Gray)), + )); + spans.push(Span::styled( + fit_cell(note, note_w), + apply_bg(Style::default().fg(Color::White)), + )); } - spans.push(Span::styled(" \u{2502} ", apply_bg(Style::default().fg(Color::Gray)))); + spans.push(Span::styled( + " \u{2502} ", + apply_bg(Style::default().fg(Color::Gray)), + )); spans.push(match status { - DomainStatus::Available => Span::styled(" ✓ ", apply_bg(Style::default().fg(Color::Green))), - DomainStatus::Registered { .. } => Span::styled(" ✗ ", apply_bg(Style::default().fg(Color::Red))), + DomainStatus::Available => { + Span::styled(" ✓ ", apply_bg(Style::default().fg(Color::Green))) + } + DomainStatus::Registered { .. } => { + Span::styled(" ✗ ", apply_bg(Style::default().fg(Color::Red))) + } DomainStatus::Error { kind, .. } => match kind { - ErrorKind::InvalidTld => Span::styled(" ? ", apply_bg(Style::default().fg(Color::Yellow))), + ErrorKind::InvalidTld => { + Span::styled(" ? ", apply_bg(Style::default().fg(Color::Yellow))) + } _ => Span::styled(" ! ", apply_bg(Style::default().fg(Color::Blue))), }, }); @@ -2553,8 +2797,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(Style::default().fg(Color::White)), inner, ); @@ -2623,17 +2866,17 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) { }) .collect(); - let list = List::new(items) - .highlight_style( - Style::default() - .add_modifier(Modifier::REVERSED), - ); + let list = List::new(items).highlight_style(Style::default().add_modifier(Modifier::REVERSED)); f.render_widget(block, area); f.render_stateful_widget(list, list_area, &mut app.favorites_state); // Draw the check button at the bottom - let btn_label = if app.checking_favorites { "checking..." } else { "[c]heck all" }; + let btn_label = if app.checking_favorites { + "checking..." + } else { + "[c]heck all" + }; let btn_style = if app.checking_favorites { Style::default().fg(Color::DarkGray) } else { @@ -2688,13 +2931,17 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) { }; let tld_row_style = if selected == Some(0) { - Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD) + Style::default() + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD) } else { Style::default() }; let jobs_row_style = if selected == Some(4) { - Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD) + Style::default() + .bg(Color::DarkGray) + .add_modifier(Modifier::BOLD) } else { Style::default() }; @@ -2801,23 +3048,44 @@ fn draw_search(f: &mut Frame, app: &mut App, area: Rect) { let cancel_enabled = app.searching; let search_style = if search_enabled { - Style::default().fg(Color::Black).bg(Color::Green).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Black) + .bg(Color::Green) + .add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::DarkGray).bg(Color::Black) }; let stop_style = if cancel_enabled { - Style::default().fg(Color::Black).bg(Color::Yellow).add_modifier(Modifier::BOLD) + Style::default() + .fg(Color::Black) + .bg(Color::Yellow) + .add_modifier(Modifier::BOLD) } else { Style::default().fg(Color::DarkGray).bg(Color::Black) }; - let clear_style = Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD); + let clear_style = Style::default() + .fg(Color::White) + .bg(Color::Red) + .add_modifier(Modifier::BOLD); - f.render_widget(Paragraph::new(SEARCH_BUTTON_LABEL).style(search_style), chunks[1]); + f.render_widget( + Paragraph::new(SEARCH_BUTTON_LABEL).style(search_style), + chunks[1], + ); if app.clear_on_search { - f.render_widget(Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), chunks[3]); + f.render_widget( + Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), + chunks[3], + ); } else { - f.render_widget(Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), chunks[3]); - f.render_widget(Paragraph::new(CLEAR_BUTTON_LABEL).style(clear_style), chunks[5]); + f.render_widget( + Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), + chunks[3], + ); + f.render_widget( + Paragraph::new(CLEAR_BUTTON_LABEL).style(clear_style), + chunks[5], + ); } // show cursor in search bar when focused @@ -2836,7 +3104,10 @@ fn draw_dropdown(f: &mut Frame, app: &mut App, settings_area: Rect, selected: us let dropdown_full = Rect { x: settings_area.x + 1, y: settings_area.y + 1, - width: settings_area.width.saturating_sub(2).min(DROPDOWN_MAX_WIDTH), + width: settings_area + .width + .saturating_sub(2) + .min(DROPDOWN_MAX_WIDTH), height: (options.len() as u16 + 2).min(DROPDOWN_MAX_HEIGHT), }; @@ -2861,9 +3132,12 @@ fn draw_dropdown(f: &mut Frame, app: &mut App, settings_area: Rect, selected: us .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)); + let list = List::new(items).block(block).highlight_style( + Style::default() + .fg(Color::White) + .bg(Color::Red) + .add_modifier(Modifier::BOLD), + ); let mut state = ListState::default(); state.select(Some(selected)); f.render_stateful_widget(list, dropdown_full, &mut state); diff --git a/src/types.rs b/src/types.rs index 9a496c2..cc3950b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -89,5 +89,3 @@ impl DomainResult { } } } - - |
