154 lines
4.9 KiB
Rust
154 lines
4.9 KiB
Rust
// src/components/common/autocomplete.rs
|
|
|
|
use crate::config::colors::themes::Theme;
|
|
use crate::state::pages::form::FormState;
|
|
use common::proto::komp_ac::search::search_response::Hit;
|
|
use ratatui::{
|
|
layout::Rect,
|
|
style::{Color, Modifier, Style},
|
|
widgets::{Block, List, ListItem, ListState},
|
|
Frame,
|
|
};
|
|
use unicode_width::UnicodeWidthStr;
|
|
|
|
/// Renders an opaque dropdown list for simple string-based suggestions.
|
|
/// THIS IS THE RESTORED FUNCTION.
|
|
pub fn render_autocomplete_dropdown(
|
|
f: &mut Frame,
|
|
input_rect: Rect,
|
|
frame_area: Rect,
|
|
theme: &Theme,
|
|
suggestions: &[String],
|
|
selected_index: Option<usize>,
|
|
) {
|
|
if suggestions.is_empty() {
|
|
return;
|
|
}
|
|
let max_suggestion_width =
|
|
suggestions.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
|
|
let horizontal_padding: u16 = 2;
|
|
let dropdown_width = (max_suggestion_width + horizontal_padding).max(10);
|
|
let dropdown_height = (suggestions.len() as u16).min(5);
|
|
|
|
let mut dropdown_area = Rect {
|
|
x: input_rect.x,
|
|
y: input_rect.y + 1,
|
|
width: dropdown_width,
|
|
height: dropdown_height,
|
|
};
|
|
|
|
if dropdown_area.bottom() > frame_area.height {
|
|
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
|
|
}
|
|
if dropdown_area.right() > frame_area.width {
|
|
dropdown_area.x = frame_area.width.saturating_sub(dropdown_width);
|
|
}
|
|
dropdown_area.x = dropdown_area.x.max(0);
|
|
dropdown_area.y = dropdown_area.y.max(0);
|
|
|
|
let background_block =
|
|
Block::default().style(Style::default().bg(Color::DarkGray));
|
|
f.render_widget(background_block, dropdown_area);
|
|
|
|
let items: Vec<ListItem> = suggestions
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, s)| {
|
|
let is_selected = selected_index == Some(i);
|
|
let s_width = s.width() as u16;
|
|
let padding_needed = dropdown_width.saturating_sub(s_width);
|
|
let padded_s =
|
|
format!("{}{}", s, " ".repeat(padding_needed as usize));
|
|
|
|
ListItem::new(padded_s).style(if is_selected {
|
|
Style::default()
|
|
.fg(theme.bg)
|
|
.bg(theme.highlight)
|
|
.add_modifier(Modifier::BOLD)
|
|
} else {
|
|
Style::default().fg(theme.fg).bg(Color::DarkGray)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let list = List::new(items);
|
|
let mut list_state = ListState::default();
|
|
list_state.select(selected_index);
|
|
|
|
f.render_stateful_widget(list, dropdown_area, &mut list_state);
|
|
}
|
|
|
|
/// Renders an opaque dropdown list for rich `Hit`-based suggestions.
|
|
/// RENAMED from render_rich_autocomplete_dropdown
|
|
pub fn render_hit_autocomplete_dropdown(
|
|
f: &mut Frame,
|
|
input_rect: Rect,
|
|
frame_area: Rect,
|
|
theme: &Theme,
|
|
suggestions: &[Hit],
|
|
selected_index: Option<usize>,
|
|
form_state: &FormState,
|
|
) {
|
|
if suggestions.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let display_names: Vec<String> = suggestions
|
|
.iter()
|
|
.map(|hit| form_state.get_display_name_for_hit(hit))
|
|
.collect();
|
|
|
|
let max_suggestion_width =
|
|
display_names.iter().map(|s| s.width()).max().unwrap_or(0) as u16;
|
|
let horizontal_padding: u16 = 2;
|
|
let dropdown_width = (max_suggestion_width + horizontal_padding).max(10);
|
|
let dropdown_height = (suggestions.len() as u16).min(5);
|
|
|
|
let mut dropdown_area = Rect {
|
|
x: input_rect.x,
|
|
y: input_rect.y + 1,
|
|
width: dropdown_width,
|
|
height: dropdown_height,
|
|
};
|
|
|
|
if dropdown_area.bottom() > frame_area.height {
|
|
dropdown_area.y = input_rect.y.saturating_sub(dropdown_height);
|
|
}
|
|
if dropdown_area.right() > frame_area.width {
|
|
dropdown_area.x = frame_area.width.saturating_sub(dropdown_width);
|
|
}
|
|
dropdown_area.x = dropdown_area.x.max(0);
|
|
dropdown_area.y = dropdown_area.y.max(0);
|
|
|
|
let background_block =
|
|
Block::default().style(Style::default().bg(Color::DarkGray));
|
|
f.render_widget(background_block, dropdown_area);
|
|
|
|
let items: Vec<ListItem> = display_names
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, s)| {
|
|
let is_selected = selected_index == Some(i);
|
|
let s_width = s.width() as u16;
|
|
let padding_needed = dropdown_width.saturating_sub(s_width);
|
|
let padded_s =
|
|
format!("{}{}", s, " ".repeat(padding_needed as usize));
|
|
|
|
ListItem::new(padded_s).style(if is_selected {
|
|
Style::default()
|
|
.fg(theme.bg)
|
|
.bg(theme.highlight)
|
|
.add_modifier(Modifier::BOLD)
|
|
} else {
|
|
Style::default().fg(theme.fg).bg(Color::DarkGray)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
let list = List::new(items);
|
|
let mut list_state = ListState::default();
|
|
list_state.select(selected_index);
|
|
|
|
f.render_stateful_widget(list, dropdown_area, &mut list_state);
|
|
}
|