// src/components/common/search_palette.rs use crate::config::colors::themes::Theme; use crate::state::app::search::SearchState; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, Clear, List, ListItem, Paragraph}, Frame, }; /// Renders the search palette dialog over the main UI. pub fn render_search_palette( f: &mut Frame, area: Rect, theme: &Theme, state: &SearchState, ) { // --- Dialog Area Calculation --- let height = (area.height as f32 * 0.7).min(30.0) as u16; let width = (area.width as f32 * 0.6).min(100.0) as u16; let dialog_area = Rect { x: area.x + (area.width - width) / 2, y: area.y + (area.height - height) / 4, width, height, }; f.render_widget(Clear, dialog_area); // Clear background let block = Block::default() .title(format!(" Search in '{}' ", state.table_name)) .borders(Borders::ALL) .border_style(Style::default().fg(theme.accent)); f.render_widget(block.clone(), dialog_area); // --- Inner Layout (Input + Results) --- let inner_chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints([ Constraint::Length(3), // For input box Constraint::Min(0), // For results list ]) .split(dialog_area); // --- Render Input Box --- let input_block = Block::default() .title("Query") .borders(Borders::ALL) .border_style(Style::default().fg(theme.border)); let input_text = Paragraph::new(state.input.as_str()) .block(input_block) .style(Style::default().fg(theme.fg)); f.render_widget(input_text, inner_chunks[0]); // Set cursor position f.set_cursor( inner_chunks[0].x + state.cursor_position as u16 + 1, inner_chunks[0].y + 1, ); // --- Render Results List --- if state.is_loading { let loading_p = Paragraph::new("Searching...") .style(Style::default().fg(theme.fg).add_modifier(Modifier::ITALIC)); f.render_widget(loading_p, inner_chunks[1]); } else { let list_items: Vec = state .results .iter() .map(|hit| { // Parse the JSON string to make it readable let content_summary = match serde_json::from_str::< serde_json::Value, >(&hit.content_json) { Ok(json) => { if let Some(obj) = json.as_object() { // Create a summary from the first few non-null string values obj.values() .filter_map(|v| v.as_str()) .filter(|s| !s.is_empty()) .take(3) .collect::>() .join(" | ") } else { "Non-object JSON".to_string() } } Err(_) => "Invalid JSON content".to_string(), }; let line = Line::from(vec![ Span::styled( format!("{:<4.2} ", hit.score), Style::default().fg(theme.accent), ), Span::raw(content_summary), ]); ListItem::new(line) }) .collect(); let results_list = List::new(list_items) .block(Block::default().title("Results")) .highlight_style( Style::default() .bg(theme.highlight) .fg(theme.bg) .add_modifier(Modifier::BOLD), ) .highlight_symbol(">> "); // We need a mutable ListState to render the selection let mut list_state = ratatui::widgets::ListState::default().with_selected(Some(state.selected_index)); f.render_stateful_widget(results_list, inner_chunks[1], &mut list_state); } }