|
|
@@ -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);
|