the search tui is not working yet

This commit is contained in:
filipriec
2025-06-11 22:08:23 +02:00
parent c9131d4457
commit 3c0af05a3c
5 changed files with 47 additions and 55 deletions

View File

@@ -48,6 +48,7 @@ use crossterm::event::KeyCode;
use crossterm::event::{Event, KeyEvent};
use tokio::sync::mpsc;
use tokio::sync::mpsc::unbounded_channel;
use tracing::{info, error};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventOutcome {
@@ -122,11 +123,11 @@ impl EventHandler {
self.navigation_state.activate_find_file(options);
}
// REFACTORED: This function now safely handles state changes.
// This function handles state changes.
async fn handle_search_palette_event(
&mut self,
key_event: KeyEvent,
grpc_client: &mut GrpcClient,
mut grpc_client: GrpcClient,
form_state: &mut FormState,
app_state: &mut AppState,
) -> Result<EventOutcome> {
@@ -175,6 +176,7 @@ impl EventHandler {
_ => {}
}
// --- START CORRECTED LOGIC ---
if trigger_search {
search_state.is_loading = true;
search_state.results.clear();
@@ -182,14 +184,19 @@ impl EventHandler {
let query = search_state.input.clone();
let table_name = search_state.table_name.clone();
let mut client = grpc_client.clone();
let sender = self.search_result_sender.clone();
// We now move the grpc_client into the task, just like with login.
tokio::spawn(async move {
if let Ok(response) = client.search_table(table_name, query).await {
let _ = sender.send(response.hits);
} else {
let _ = sender.send(vec![]);
match grpc_client.search_table(table_name, query).await {
Ok(response) => {
info!("Search successful. Received {} hits.", response.hits.len());
let _ = sender.send(response.hits);
}
Err(e) => {
error!("gRPC search call failed: {}", e);
let _ = sender.send(vec![]);
}
}
});
}
@@ -232,7 +239,7 @@ impl EventHandler {
if app_state.ui.show_search_palette {
if let Event::Key(key_event) = event {
return self.handle_search_palette_event(key_event, grpc_client, form_state, app_state).await;
return self.handle_search_palette_event(key_event, grpc_client.clone(), form_state, app_state).await;
}
return Ok(EventOutcome::Ok(String::new()));
}

View File

@@ -1,6 +1,5 @@
// src/state/canvas_state.rs
pub trait CanvasState {
fn current_field(&self) -> usize;
fn current_cursor_pos(&self) -> usize;

View File

@@ -1,23 +1,19 @@
// src/state/pages/form.rs
use std::collections::HashMap; // NEW
use std::collections::HashMap;
use crate::config::colors::themes::Theme;
use ratatui::layout::Rect;
use ratatui::Frame;
use crate::state::app::highlight::HighlightState;
use crate::state::pages::canvas_state::CanvasState;
use crate::state::app::state::AppState;
use crate::components::common::search_palette::render_search_palette;
pub struct FormState {
pub id: i64,
// NEW fields for dynamic table context
pub profile_name: String,
pub table_name: String,
pub total_count: u64,
pub current_position: u64, // 1-based index, 0 or total_count + 1 for new entry
pub fields: Vec<String>, // Already dynamic, which is good
pub current_position: u64,
pub fields: Vec<String>,
pub values: Vec<String>,
pub current_field: usize,
pub has_unsaved_changes: bool,
@@ -25,9 +21,6 @@ pub struct FormState {
}
impl FormState {
/// Creates a new, empty FormState for a given table.
/// The position defaults to 1, representing either the first record
/// or the position for a new entry if the table is empty.
pub fn new(
profile_name: String,
table_name: String,
@@ -35,11 +28,10 @@ impl FormState {
) -> Self {
let values = vec![String::new(); fields.len()];
FormState {
id: 0, // Default to 0, indicating a new or unloaded record
id: 0,
profile_name,
table_name,
total_count: 0, // Will be fetched after initialization
// FIX: Default to 1. A position of 0 is an invalid state.
total_count: 0,
current_position: 1,
fields,
values,
@@ -49,6 +41,7 @@ impl FormState {
}
}
// This signature is now correct and only deals with form-related state.
pub fn render(
&self,
f: &mut Frame,
@@ -56,7 +49,6 @@ impl FormState {
theme: &Theme,
is_edit_mode: bool,
highlight_state: &HighlightState,
app_state: &AppState,
) {
let fields_str_slice: Vec<&str> =
self.fields.iter().map(|s| s.as_str()).collect();
@@ -65,7 +57,7 @@ impl FormState {
crate::components::form::form::render_form(
f,
area,
self, // Pass self as CanvasState
self,
&fields_str_slice,
&self.current_field,
&values_str_slice,
@@ -76,27 +68,19 @@ impl FormState {
self.total_count,
self.current_position,
);
if app_state.ui.show_search_palette {
if let Some(search_state) = &app_state.search_state {
render_search_palette(f, f.size(), theme, search_state);
}
}
}
/// Resets the form to a state for creating a new entry.
/// It clears all values and sets the position to be one after the last record.
// ... other methods are unchanged ...
pub fn reset_to_empty(&mut self) {
self.id = 0;
self.values.iter_mut().for_each(|v| v.clear());
self.current_field = 0;
self.current_cursor_pos = 0;
self.has_unsaved_changes = false;
// Set the position for a new entry.
if self.total_count > 0 {
self.current_position = self.total_count + 1;
} else {
self.current_position = 1; // If table is empty, new record is at position 1
self.current_position = 1;
}
}
@@ -113,26 +97,19 @@ impl FormState {
.expect("Invalid current_field index")
}
/// Updates the form's values from a data response and sets its position.
/// This is the single source of truth for populating the form after a data fetch.
pub fn update_from_response(
&mut self,
response_data: &HashMap<String, String>,
// FIX: Add new_position to make this method authoritative.
new_position: u64,
) {
// Create a new vector for the values, ensuring they are in the correct order.
self.values = self.fields.iter().map(|field_from_schema| {
// For each field from our schema, find the corresponding key in the
// response data by doing a case-insensitive comparison.
response_data
.iter()
.find(|(key_from_data, _)| key_from_data.eq_ignore_ascii_case(field_from_schema))
.map(|(_, value)| value.clone()) // If found, clone its value.
.unwrap_or_default() // If not found, use an empty string.
.map(|(_, value)| value.clone())
.unwrap_or_default()
}).collect();
// Now, do the same case-insensitive lookup for the 'id' field.
let id_str_opt = response_data
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case("id"))
@@ -149,7 +126,6 @@ impl FormState {
self.id = 0;
}
// FIX: Set the position from the provided parameter.
self.current_position = new_position;
self.has_unsaved_changes = false;
self.current_field = 0;
@@ -175,12 +151,10 @@ impl CanvasState for FormState {
}
fn get_current_input(&self) -> &str {
// Re-use the struct's own method
FormState::get_current_input(self)
}
fn get_current_input_mut(&mut self) -> &mut String {
// Re-use the struct's own method
FormState::get_current_input_mut(self)
}

View File

@@ -4,7 +4,7 @@ use crate::components::{
admin::add_logic::render_add_logic,
admin::render_add_table,
auth::{login::render_login, register::render_register},
common::dialog::render_dialog, // Make sure this is imported
common::dialog::render_dialog,
common::find_file_palette,
common::search_palette::render_search_palette,
form::form::render_form,
@@ -17,6 +17,7 @@ use crate::components::{
};
use crate::config::colors::themes::Theme;
use crate::modes::general::command_navigation::NavigationState;
use crate::state::pages::canvas_state::CanvasState;
use crate::state::app::buffer::BufferState;
use crate::state::app::highlight::HighlightState;
use crate::state::app::state::AppState;
@@ -24,7 +25,6 @@ use crate::state::pages::admin::AdminState;
use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState;
use crate::state::pages::canvas_state::CanvasState;
use crate::state::pages::form::FormState;
use crate::state::pages::intro::IntroState;
use ratatui::{
@@ -189,17 +189,13 @@ pub fn render_ui(
.split(form_actual_area)[1]
};
// --- FIX START ---
// The call to `form_state.render` is now separate from the popup rendering.
form_state.render(
f,
form_render_area,
theme,
is_event_handler_edit_mode,
highlight_state,
app_state,
);
// --- FIX END ---
}
if let Some(area) = buffer_list_area {
@@ -236,8 +232,7 @@ pub fn render_ui(
}
}
// --- FIX START ---
// This block now handles drawing popups over any view. This is the correct place for it.
// This block now correctly handles drawing popups over any view.
if app_state.ui.show_search_palette {
if let Some(search_state) = &app_state.search_state {
render_search_palette(f, f.area(), theme, search_state);
@@ -254,5 +249,4 @@ pub fn render_ui(
app_state.ui.dialog.is_loading,
);
}
// --- FIX END ---
}

View File

@@ -126,6 +126,24 @@ pub async fn run_ui() -> Result<()> {
loop {
let position_before_event = form_state.current_position;
let mut event_processed = false;
match event_handler.search_result_receiver.try_recv() {
Ok(hits) => {
if let Some(search_state) = app_state.search_state.as_mut() {
search_state.results = hits;
search_state.is_loading = false;
}
needs_redraw = true;
}
Err(mpsc::error::TryRecvError::Empty) => {
}
Err(mpsc::error::TryRecvError::Disconnected) => {
error!("Search result channel disconnected!");
}
}
if app_state.ui.show_search_palette {
needs_redraw = true;
}
if crossterm_event::poll(std::time::Duration::from_millis(1))? {
let event = event_reader.read_event().context("Failed to read terminal event")?;
event_processed = true;